chore(config): 初始化项目配置文件
- 添加 .editorconfig 文件统一代码风格 - 配置 .env.development 环境变量文件 - 创建 .env.example 环境变量示例文件 - 设置 .eslintignore 忽略检查规则 - 配置 .eslintrc.js 代码检查规则 - 添加 .gitignore 文件忽略版本控制 - 设置 .prettierignore 忽略格式化规则 - 新增隐私政策HTML页面文件 - 创建API密钥编辑组件基础结构
This commit is contained in:
133
src/layout/components/header-notice.vue
Normal file
133
src/layout/components/header-notice.vue
Normal file
@@ -0,0 +1,133 @@
|
||||
<!-- 顶栏消息通知 -->
|
||||
<template>
|
||||
<a-dropdown
|
||||
v-model:visible="visible"
|
||||
placement="bottom"
|
||||
:trigger="['click']"
|
||||
:overlay-style="{ padding: '0 10px' }"
|
||||
>
|
||||
<a-badge :count="unreadNum" dot class="ele-notice-trigger" :offset="[4, 6]">
|
||||
<bell-outlined style="padding: 8px 0"/>
|
||||
</a-badge>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, ref} from 'vue';
|
||||
// import {useChatStore} from '@/store/modules/chat';
|
||||
import {useUserStore} from '@/store/modules/user';
|
||||
import {pageChatMessage} from '@/api/system/chat';
|
||||
import {ChatMessage} from '@/api/system/chat/model';
|
||||
|
||||
// const chatStore = useChatStore();
|
||||
const userStore = useUserStore();
|
||||
// 是否显示
|
||||
const visible = ref<boolean>(false);
|
||||
// 通知数据
|
||||
const notice = ref<ChatMessage[]>([]);
|
||||
|
||||
console.log(userStore.info?.userId,'.....userId')
|
||||
// chatStore.connectSocketIO(userStore.info?.userId || 0);
|
||||
|
||||
// 未读数量
|
||||
const unreadNum = computed(() => {
|
||||
return notice.value.length;
|
||||
});
|
||||
|
||||
/* 查询数据 */
|
||||
// const query = () => {
|
||||
// pageNotices({ status: 0 })
|
||||
// .then((result) => {
|
||||
// notice.value = result?.list;
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// message.error(e.message);
|
||||
// });
|
||||
//
|
||||
// pageTodos({ status: 0 })
|
||||
// .then((result) => {
|
||||
// todo.value = result?.list;
|
||||
// })
|
||||
// .catch((e) => {
|
||||
// message.error(e.message);
|
||||
// });
|
||||
// };
|
||||
|
||||
/* 查询未读数量 */
|
||||
const queryUnReadNum = () => {
|
||||
const toUserId = Number(userStore.info?.userId || 0);
|
||||
console.log(toUserId);
|
||||
const status = 0;
|
||||
pageChatMessage({toUserId, status, keywords: ''}).then((result) => {
|
||||
console.log(result);
|
||||
notice.value = result?.list || [];
|
||||
});
|
||||
};
|
||||
|
||||
queryUnReadNum();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import {BellOutlined} from '@ant-design/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'HeaderNotice',
|
||||
components: {
|
||||
BellOutlined
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ele-notice-trigger.ant-badge {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.ele-notice-pop {
|
||||
&.ant-dropdown-menu {
|
||||
padding: 0;
|
||||
width: 286px;
|
||||
max-width: 100%;
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
// 内容
|
||||
.ant-list-item {
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
transition: background-color 0.3s;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: hsla(0, 0%, 60%, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.ant-tag {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
// 操作按钮
|
||||
.ele-notice-actions {
|
||||
border-top: 1px solid hsla(0, 0%, 60%, 0.15);
|
||||
|
||||
& > .ele-cell-content {
|
||||
line-height: 46px;
|
||||
text-align: center;
|
||||
transition: background-color 0.3s;
|
||||
cursor: pointer;
|
||||
color: inherit;
|
||||
|
||||
&:hover {
|
||||
background: hsla(0, 0%, 60%, 0.05);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ele-cell-content {
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
</style>
|
||||
309
src/layout/components/header-tools.vue
Normal file
309
src/layout/components/header-tools.vue
Normal file
@@ -0,0 +1,309 @@
|
||||
<!-- 顶栏右侧区域 -->
|
||||
<template>
|
||||
<div class="ele-admin-header-tool">
|
||||
<!-- 消息通知 -->
|
||||
<div class="ele-admin-header-tool-item" @click="openUrl(`/user/notice`)">
|
||||
<header-notice/>
|
||||
</div>
|
||||
<!-- 全屏切换 -->
|
||||
<div
|
||||
:class="[
|
||||
'ele-admin-header-tool-item',
|
||||
{ 'hidden-sm-and-down': styleResponsive }
|
||||
]"
|
||||
@click="toggleFullscreen"
|
||||
>
|
||||
<fullscreen-exit-outlined v-if="fullscreen"/>
|
||||
<fullscreen-outlined v-else/>
|
||||
</div>
|
||||
<!-- 用户信息 -->
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<a-dropdown placement="bottom" :overlay-style="{ minWidth: '280px' }">
|
||||
<div class="ele-admin-header-avatar">
|
||||
<a-avatar :src="loginUser?.avatar">
|
||||
<template v-if="!loginUser?.avatar" #icon>
|
||||
<user-outlined/>
|
||||
</template>
|
||||
</a-avatar>
|
||||
<span :class="{ 'hidden-sm-and-down': styleResponsive }">
|
||||
{{ loginUser.nickname }}
|
||||
</span>
|
||||
<down-outlined style="margin-left: 6px"/>
|
||||
</div>
|
||||
<template #overlay>
|
||||
<a-menu :selectable="false" @click="onUserDropClick">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<div class="user-profile">
|
||||
<div class="user-info">
|
||||
<div class="nickname">
|
||||
<div class="ele-text-placeholder">
|
||||
<span class="text-gray-500 text-lg">{{
|
||||
website?.websiteName
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="text-gray-400">
|
||||
用户ID:<span class="ele-text-secondary" @click="copyText(loginUser.userId)">{{
|
||||
loginUser.userId
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="text-gray-400">
|
||||
昵称:<span class="ele-text-secondary">{{
|
||||
loginUser.nickname
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="text-gray-400">
|
||||
手机:<span class="ele-text-secondary">{{
|
||||
loginUser.mobile
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="text-gray-400">
|
||||
租户:<span class="ele-text-secondary" @click="copyText(loginUser.tenantId)">{{
|
||||
loginUser.tenantId
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="text-gray-400">
|
||||
角色:
|
||||
<template
|
||||
v-for="(item, index) in loginUser.roles"
|
||||
:key="item.roleId"
|
||||
>
|
||||
<a-tag v-if="index === 0">
|
||||
<div class="role-name">
|
||||
<span>{{ item.roleName }}</span>
|
||||
</div>
|
||||
</a-tag>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
<a-menu-divider/>
|
||||
<a-menu-item key="profile">
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content"> {{ t('layout.header.profile') }}</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
<a-menu-divider/>
|
||||
<a-menu-item key="password">
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content"> {{ t('layout.header.password') }}</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
<template v-if="loginUser.username == 'admin' || loginUser.username == 'superAdmin'">
|
||||
<a-menu-divider/>
|
||||
<a-menu-item key="accessKey">
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content"> {{ t('layout.header.accessKey') }}</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
<a-menu-divider/>
|
||||
<a-menu-item key="system">
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content"> {{ t('layout.header.system') }}</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
</template>
|
||||
<a-menu-divider/>
|
||||
<a-menu-item key="logout">
|
||||
<div class="ele-cell" align="center">
|
||||
<div class="ele-cell-content">
|
||||
{{ t('layout.header.logout') }}
|
||||
</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</div>
|
||||
<!-- 主题设置 -->
|
||||
<!-- <div class="ele-admin-header-tool-item" @click="openSetting">-->
|
||||
<!-- <more-outlined/>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
<!-- 修改密码弹窗 -->
|
||||
<password-modal v-model:visible="passwordVisible"/>
|
||||
<!-- 主题设置抽屉 -->
|
||||
<setting-drawer v-model:visible="settingVisible"/>
|
||||
<!-- 二维码 -->
|
||||
<Qrcode v-model:visible="showQrcode" :data="SiteUrl" @done="hideShare"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, createVNode, ref} from 'vue';
|
||||
import {useRouter} from 'vue-router';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {Modal} from 'ant-design-vue/es';
|
||||
import {
|
||||
DownOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
FullscreenOutlined,
|
||||
FullscreenExitOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import {copyText, openUrl} from '@/utils/common';
|
||||
import {useThemeStore} from '@/store/modules/theme';
|
||||
import HeaderNotice from './header-notice.vue';
|
||||
import PasswordModal from './password-modal.vue';
|
||||
import SettingDrawer from './setting-drawer.vue';
|
||||
import {useUserStore} from '@/store/modules/user';
|
||||
import {logout} from '@/utils/page-tab-util';
|
||||
import {listRoles} from '@/api/system/role';
|
||||
import { useSiteStore } from '@/store/modules/site';
|
||||
import Qrcode from "@/components/QrCode/index.vue";
|
||||
import {AppInfo} from "@/api/cms/cmsWebsite/model";
|
||||
import {getCmsWebsiteFieldByCode} from "@/api/cms/cmsWebsiteField";
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const {styleResponsive} = storeToRefs(themeStore);
|
||||
const SiteUrl = localStorage.getItem('SiteUrl');
|
||||
// 是否显示二维码
|
||||
const showQrcode = ref(false);
|
||||
// 使用网站信息 store
|
||||
const siteStore = useSiteStore();
|
||||
// const TENANT_ID = localStorage.getItem('TenantId');
|
||||
// const TENANT_NAME = localStorage.getItem('TenantName');
|
||||
const emit = defineEmits<{
|
||||
(e: 'fullscreen'): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
// 是否是全屏
|
||||
fullscreen: boolean;
|
||||
}>();
|
||||
|
||||
const {push} = useRouter();
|
||||
const {t} = useI18n();
|
||||
const userStore = useUserStore();
|
||||
// 是否显示修改密码弹窗
|
||||
const passwordVisible = ref(false);
|
||||
|
||||
// 是否显示主题设置抽屉
|
||||
const settingVisible = ref(false);
|
||||
|
||||
// 当前用户信息
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
const website = ref<AppInfo>();
|
||||
|
||||
/* 用户信息下拉点击 */
|
||||
const onUserDropClick = ({key}) => {
|
||||
if (key === 'password') {
|
||||
passwordVisible.value = true;
|
||||
} else if (key === 'profile') {
|
||||
push('/user/profile');
|
||||
} else if (key === 'accessKey') {
|
||||
push('/system/access-key');
|
||||
} else if (key === 'taskAdd') {
|
||||
push('/user/task/add');
|
||||
} else if (key === 'myTask') {
|
||||
push('/user/task/index');
|
||||
} else if (key === 'skin') {
|
||||
settingVisible.value = true;
|
||||
} else if (key === 'system') {
|
||||
push('/system/setting');
|
||||
} else if (key === 'logout') {
|
||||
// 退出登录
|
||||
Modal.confirm({
|
||||
title: t('layout.logout.title'),
|
||||
content: t('layout.logout.message'),
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
logout();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const hideShare = () => {
|
||||
showQrcode.value = false;
|
||||
}
|
||||
|
||||
/* 打开主题设置抽屉 */
|
||||
// const openSetting = () => {
|
||||
// settingVisible.value = true;
|
||||
// };
|
||||
|
||||
/* 切换全屏 */
|
||||
const toggleFullscreen = () => {
|
||||
emit('fullscreen');
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
// 查询网站信息
|
||||
if (!localStorage.getItem('WebsiteId')) {
|
||||
siteStore.fetchSiteInfo().catch(console.error);
|
||||
}
|
||||
// 查询商户角色的roleId
|
||||
if (!localStorage.getItem('RoleIdByMerchant')) {
|
||||
listRoles({roleCode: 'merchant'}).then((res) => {
|
||||
if (res.length > 0) {
|
||||
const item = res[0];
|
||||
localStorage.setItem('RoleIdByMerchant', `${item.roleId}`);
|
||||
localStorage.setItem('RoleNameByMerchant', `${item.roleName}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
// 检查是否启动自定义接口
|
||||
if(import.meta.env.PROD){
|
||||
getCmsWebsiteFieldByCode('ApiUrl').then(res => {
|
||||
if(res){
|
||||
localStorage.setItem('ApiUrl', `${res.value}`);
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
reload();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from '@/layout/menu-icons';
|
||||
|
||||
export default {
|
||||
components: icons,
|
||||
data() {
|
||||
return {
|
||||
iconData: [
|
||||
{
|
||||
title: '已引入的图标',
|
||||
icons: Object.keys(icons)
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-select {
|
||||
color: #bfbfbf;
|
||||
}
|
||||
|
||||
.user-profile {
|
||||
display: flex;
|
||||
|
||||
.user-info {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.nickname {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ele-admin-header-tool-item {
|
||||
:deep(.ant-select-tree-switcher) {
|
||||
background-color: #ff00fe;
|
||||
}
|
||||
|
||||
:deep(.ant-select-tree-indent) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
33
src/layout/components/header-wechat.vue
Normal file
33
src/layout/components/header-wechat.vue
Normal file
@@ -0,0 +1,33 @@
|
||||
<!-- 顶栏消息通知 -->
|
||||
<template>
|
||||
<a-popover>
|
||||
<template #content>
|
||||
<p class="ele-text-placeholder" style="text-align: center">
|
||||
请使用微信扫描二维码
|
||||
</p>
|
||||
<a-image
|
||||
:preview="false"
|
||||
:width="200"
|
||||
:height="200"
|
||||
src="https://file.wsdns.cn/20230518/6b3c88423e0e437f81b3adbf63d30cba.png"
|
||||
/>
|
||||
<p class="ele-text-placeholder" style="text-align: center">
|
||||
客服电话:0771-5386339
|
||||
</p>
|
||||
</template>
|
||||
<div class="ele-admin-header-tool-item">
|
||||
<wechat-outlined style="padding: 8px 0" />
|
||||
</div>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { WechatOutlined } from '@ant-design/icons-vue';
|
||||
</script>
|
||||
<style lang="less">
|
||||
.wechat-box {
|
||||
.title {
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
52
src/layout/components/i18n-icon.vue
Normal file
52
src/layout/components/i18n-icon.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<!-- 国际化语言切换组件 -->
|
||||
<template>
|
||||
<a-dropdown
|
||||
:placement="placement"
|
||||
:overlay-style="{ minWidth: '120px', paddingTop: '17px' }"
|
||||
>
|
||||
<slot>
|
||||
<global-outlined :style="style" />
|
||||
</slot>
|
||||
<template #overlay>
|
||||
<a-menu :selected-keys="language" @click="changeLanguage">
|
||||
<a-menu-item key="en">English</a-menu-item>
|
||||
<a-menu-item key="zh_CN">简体中文</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { GlobalOutlined } from '@ant-design/icons-vue';
|
||||
import { I18N_CACHE_NAME } from '@/config/setting';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
// dropdown placement
|
||||
placement?: any;
|
||||
// 自定义样式
|
||||
style?: CSSProperties;
|
||||
}>(),
|
||||
{
|
||||
placement: 'bottom',
|
||||
style: () => {
|
||||
return { transform: 'scale(1.08)' };
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const { locale } = useI18n();
|
||||
|
||||
// 当前显示语言
|
||||
const language = computed(() => [locale.value]);
|
||||
|
||||
/* 切换语言 */
|
||||
const changeLanguage = ({ key }) => {
|
||||
locale.value = key;
|
||||
localStorage.setItem(I18N_CACHE_NAME, key);
|
||||
window.location.reload();
|
||||
};
|
||||
</script>
|
||||
17
src/layout/components/menu-title.vue
Normal file
17
src/layout/components/menu-title.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<span>{{ item.meta.title }}</span>
|
||||
<div v-if="item.meta && item.meta.badge" class="ele-menu-badge">
|
||||
<a-badge
|
||||
:count="item.meta.badge"
|
||||
:number-style="{ background: item.meta.badgeColor as string }"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { MenuItemType } from 'ele-admin-pro/es';
|
||||
|
||||
defineProps<{
|
||||
item: MenuItemType;
|
||||
}>();
|
||||
</script>
|
||||
26
src/layout/components/page-footer.vue
Normal file
26
src/layout/components/page-footer.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<!-- 全局页脚 -->
|
||||
<template>
|
||||
<div v-if="config.setting?.showAdminCopyright" class="ele-text-center" style="padding: 16px 0">
|
||||
<div class="ele-text-secondary" style="margin-top: 8px">
|
||||
{{ t('layout.footer.copyright') }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {useWebsiteSettingStore} from "@/store/modules/setting";
|
||||
import {getSettingByKey} from "@/api/system/setting";
|
||||
|
||||
const {t} = useI18n();
|
||||
const config = useWebsiteSettingStore();
|
||||
|
||||
const reload = async () => {
|
||||
if (!config.setting) {
|
||||
const info = await getSettingByKey('privacy');
|
||||
config.setSetting(info)
|
||||
}
|
||||
}
|
||||
|
||||
reload();
|
||||
</script>
|
||||
148
src/layout/components/password-modal.vue
Normal file
148
src/layout/components/password-modal.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<!-- 修改密码弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="420"
|
||||
:title="t('layout.header.password')"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:body-style="{ paddingBottom: '16px' }"
|
||||
@update:visible="updateVisible"
|
||||
@cancel="onCancel"
|
||||
@ok="onOk"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { sm: 6 } : { flex: '90px' }"
|
||||
:wrapper-col="styleResponsive ? { sm: 18 } : { flex: '1' }"
|
||||
>
|
||||
<a-form-item :label="t('login.oldPassword')" name="oldPassword">
|
||||
<a-input-password
|
||||
v-model:value="form.oldPassword"
|
||||
placeholder="请输入旧密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('login.newPassword')" name="password">
|
||||
<a-input-password
|
||||
v-model:value="form.password"
|
||||
placeholder="请输入新密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('login.confirm')" name="password2">
|
||||
<a-input-password
|
||||
v-model:value="form.password2"
|
||||
placeholder="请再次输入新密码"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { updatePassword } from '@/api/layout';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
const { t } = useI18n();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
}>();
|
||||
|
||||
defineProps<{
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 提交loading
|
||||
const loading = ref<boolean>(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields } = useFormData({
|
||||
oldPassword: '',
|
||||
password: '',
|
||||
password2: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
oldPassword: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入旧密码',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
password: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入新密码',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
password2: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
validator: async (_rule: Rule, value: string) => {
|
||||
if (!value) {
|
||||
return Promise.reject('请再次输入新密码');
|
||||
}
|
||||
if (value !== form.password) {
|
||||
return Promise.reject('两次输入密码不一致');
|
||||
}
|
||||
return Promise.resolve();
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 修改visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
/* 保存修改 */
|
||||
const onOk = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
updatePassword(form)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 关闭回调 */
|
||||
const onCancel = () => {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
loading.value = false;
|
||||
};
|
||||
</script>
|
||||
747
src/layout/components/setting-drawer.vue
Normal file
747
src/layout/components/setting-drawer.vue
Normal file
@@ -0,0 +1,747 @@
|
||||
<!-- 主题设置抽屉 -->
|
||||
<template>
|
||||
<a-drawer
|
||||
:width="280"
|
||||
:visible="visible"
|
||||
:body-style="{ padding: 0 }"
|
||||
:header-style="{
|
||||
position: 'absolute',
|
||||
top: '16px',
|
||||
right: 0,
|
||||
padding: 0,
|
||||
background: 'none'
|
||||
}"
|
||||
:z-index="1001"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<div :class="['ele-setting-wrapper', { 'ele-setting-dark': darkMode }]">
|
||||
<div class="ele-setting-title">{{ t('layout.setting.title') }}</div>
|
||||
<!-- 侧栏风格 -->
|
||||
<div
|
||||
v-if="layoutStyle !== 'top'"
|
||||
class="ele-setting-theme ele-text-primary"
|
||||
>
|
||||
<a-tooltip :title="t('layout.setting.sideStyles.dark')">
|
||||
<div
|
||||
class="ele-bg-base ele-side-dark"
|
||||
@click="updateSideStyle('dark')"
|
||||
>
|
||||
<check-outlined v-if="sideStyle === 'dark'"/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="t('layout.setting.sideStyles.light')">
|
||||
<div class="ele-bg-base" @click="updateSideStyle('light')">
|
||||
<check-outlined v-if="sideStyle === 'light'"/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<!-- 顶栏风格 -->
|
||||
<div class="ele-setting-theme ele-text-primary">
|
||||
<a-tooltip :title="t('layout.setting.headStyles.light')">
|
||||
<div
|
||||
class="ele-bg-base ele-head-light"
|
||||
@click="updateHeadStyle('light')"
|
||||
>
|
||||
<check-outlined v-if="headStyle === 'light'"/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="t('layout.setting.headStyles.dark')">
|
||||
<div
|
||||
class="ele-bg-base ele-head-dark"
|
||||
@click="updateHeadStyle('dark')"
|
||||
>
|
||||
<check-outlined v-if="headStyle === 'dark'"/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="t('layout.setting.headStyles.primary')">
|
||||
<div
|
||||
class="ele-bg-base ele-head-primary"
|
||||
@click="updateHeadStyle('primary')"
|
||||
>
|
||||
<div class="ele-bg-primary"></div>
|
||||
<check-outlined v-if="headStyle === 'primary'"/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<!-- 主题色 -->
|
||||
<div class="ele-setting-colors">
|
||||
<div
|
||||
v-for="item in themes"
|
||||
:key="item.name"
|
||||
:style="{ 'background-color': item.color || item.value }"
|
||||
class="ele-setting-color-item"
|
||||
@click="updateColor(item.value)"
|
||||
>
|
||||
<check-outlined v-if="item.value ? item.value === color : !color"/>
|
||||
<a-tooltip :title="t('layout.setting.colors.' + item.name)">
|
||||
<div class="ele-setting-color-tooltip"></div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<!-- 颜色选择器 -->
|
||||
<ele-color-picker
|
||||
v-model:value="colorValue"
|
||||
:predefine="predefineColors"
|
||||
custom-class="ele-setting-color-picker"
|
||||
@change="updateColor"
|
||||
/>
|
||||
</div>
|
||||
<!-- 暗黑模式 -->
|
||||
<div class="ele-setting-item">
|
||||
<div class="setting-item-title">{{ t('layout.setting.darkMode') }}</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch size="small" :checked="darkMode" @change="updateDarkMode"/>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider/>
|
||||
<!-- 导航布局 -->
|
||||
<div
|
||||
:class="[
|
||||
'ele-setting-title ele-text-secondary',
|
||||
{ 'hidden-xs-only': styleResponsive }
|
||||
]"
|
||||
>
|
||||
{{ t('layout.setting.layoutStyle') }}
|
||||
</div>
|
||||
<div
|
||||
:class="[
|
||||
'ele-setting-theme ele-text-primary',
|
||||
{ 'hidden-xs-only': styleResponsive }
|
||||
]"
|
||||
>
|
||||
<a-tooltip :title="t('layout.setting.layoutStyles.side')">
|
||||
<div
|
||||
class="ele-bg-base ele-side-dark"
|
||||
@click="updateLayoutStyle('side')"
|
||||
>
|
||||
<check-outlined v-if="layoutStyle === 'side'"/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="t('layout.setting.layoutStyles.top')">
|
||||
<div
|
||||
class="ele-bg-base ele-head-dark ele-layout-top"
|
||||
@click="updateLayoutStyle('top')"
|
||||
>
|
||||
<check-outlined v-if="layoutStyle === 'top'"/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="t('layout.setting.layoutStyles.mix')">
|
||||
<div
|
||||
class="ele-bg-base ele-layout-mix"
|
||||
@click="updateLayoutStyle('mix')"
|
||||
>
|
||||
<check-outlined v-if="layoutStyle === 'mix'"/>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<!-- 侧栏菜单布局 -->
|
||||
<div
|
||||
v-if="layoutStyle !== 'top'"
|
||||
:class="['ele-setting-item', { 'hidden-xs-only': styleResponsive }]"
|
||||
>
|
||||
<div class="setting-item-title">
|
||||
{{ t('layout.setting.sideMenuStyle') }}
|
||||
</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch
|
||||
size="small"
|
||||
:checked="sideMenuStyle === 'mix'"
|
||||
@change="updateSideMenuStyle"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 内容区域定宽 -->
|
||||
<div :class="['ele-setting-item', { 'hidden-xs-only': styleResponsive }]">
|
||||
<div class="setting-item-title">
|
||||
{{ t('layout.setting.bodyFull') }}
|
||||
</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch
|
||||
size="small"
|
||||
:checked="!bodyFull"
|
||||
@change="updateBodyFull"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<a-divider :class="{ 'hidden-xs-only': styleResponsive }"/>
|
||||
<div class="ele-setting-title ele-text-secondary">
|
||||
{{ t('layout.setting.other') }}
|
||||
</div>
|
||||
<!-- 固定主体 -->
|
||||
<div class="ele-setting-item">
|
||||
<div class="setting-item-title">
|
||||
{{ t('layout.setting.fixedBody') }}
|
||||
</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch
|
||||
size="small"
|
||||
:checked="fixedBody"
|
||||
@change="updateFixedBody"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 固定顶栏 -->
|
||||
<div class="ele-setting-item">
|
||||
<div class="setting-item-title">
|
||||
{{ t('layout.setting.fixedHeader') }}
|
||||
</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch
|
||||
size="small"
|
||||
:disabled="fixedBody"
|
||||
:checked="fixedHeader"
|
||||
@change="updateFixedHeader"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 固定侧栏 -->
|
||||
<div
|
||||
v-if="layoutStyle !== 'top'"
|
||||
:class="['ele-setting-item', { 'hidden-xs-only': styleResponsive }]"
|
||||
>
|
||||
<div class="setting-item-title">
|
||||
{{ t('layout.setting.fixedSidebar') }}
|
||||
</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch
|
||||
size="small"
|
||||
:disabled="fixedBody"
|
||||
:checked="fixedSidebar"
|
||||
@change="updateFixedSidebar"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- logo 置于顶栏 -->
|
||||
<div
|
||||
v-if="layoutStyle !== 'top'"
|
||||
:class="['ele-setting-item', { 'hidden-xs-only': styleResponsive }]"
|
||||
>
|
||||
<div class="setting-item-title">
|
||||
{{ t('layout.setting.logoAutoSize') }}
|
||||
</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch
|
||||
size="small"
|
||||
:checked="logoAutoSize"
|
||||
@change="updateLogoAutoSize"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 移动端响应式 -->
|
||||
<div class="ele-setting-item">
|
||||
<div class="setting-item-title">
|
||||
{{ t('layout.setting.styleResponsive') }}
|
||||
</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch
|
||||
size="small"
|
||||
:checked="styleResponsive"
|
||||
@change="updateStyleResponsive"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 侧栏彩色图标 -->
|
||||
<div v-if="layoutStyle !== 'top'" class="ele-setting-item">
|
||||
<div class="setting-item-title">
|
||||
{{ t('layout.setting.colorfulIcon') }}
|
||||
</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch
|
||||
size="small"
|
||||
:checked="colorfulIcon"
|
||||
@change="updateColorfulIcon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 侧栏排他展开 -->
|
||||
<div v-if="layoutStyle !== 'top'" class="ele-setting-item">
|
||||
<div class="setting-item-title">
|
||||
{{ t('layout.setting.sideUniqueOpen') }}
|
||||
</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch
|
||||
size="small"
|
||||
:checked="sideUniqueOpen"
|
||||
@change="updateSideUniqueOpen"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 全局页脚 -->
|
||||
<div class="ele-setting-item">
|
||||
<div class="setting-item-title">
|
||||
{{ t('layout.setting.showFooter') }}
|
||||
</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch
|
||||
size="small"
|
||||
:checked="showFooter"
|
||||
@change="updateShowFooter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 色弱模式 -->
|
||||
<div class="ele-setting-item">
|
||||
<div class="setting-item-title">{{ t('layout.setting.weakMode') }}</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch size="small" :checked="weakMode" @change="updateWeakMode"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 页签 -->
|
||||
<div class="ele-setting-item">
|
||||
<div class="setting-item-title">{{ t('layout.setting.showTabs') }}</div>
|
||||
<div class="setting-item-control">
|
||||
<a-switch size="small" :checked="showTabs" @change="updateShowTabs"/>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 页签风格 -->
|
||||
<div v-if="showTabs" class="ele-setting-item">
|
||||
<div class="setting-item-title">{{ t('layout.setting.tabStyle') }}</div>
|
||||
<div class="setting-item-control">
|
||||
<a-select
|
||||
size="small"
|
||||
:value="tabStyle"
|
||||
style="width: 80px"
|
||||
@change="updateTabStyle"
|
||||
>
|
||||
<a-select-option value="default">
|
||||
{{ t('layout.setting.tabStyles.default') }}
|
||||
</a-select-option>
|
||||
<a-select-option value="dot">
|
||||
{{ t('layout.setting.tabStyles.dot') }}
|
||||
</a-select-option>
|
||||
<a-select-option value="card">
|
||||
{{ t('layout.setting.tabStyles.card') }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 切换动画 -->
|
||||
<div class="ele-setting-item">
|
||||
<div class="setting-item-title">
|
||||
{{ t('layout.setting.transitionName') }}
|
||||
</div>
|
||||
<div class="setting-item-control">
|
||||
<a-select
|
||||
size="small"
|
||||
:value="transitionName"
|
||||
style="width: 100px"
|
||||
@change="updateTransitionName"
|
||||
>
|
||||
<a-select-option value="slide-right">
|
||||
{{ t('layout.setting.transitions.slideRight') }}
|
||||
</a-select-option>
|
||||
<a-select-option value="slide-bottom">
|
||||
{{ t('layout.setting.transitions.slideBottom') }}
|
||||
</a-select-option>
|
||||
<a-select-option value="zoom-in">
|
||||
{{ t('layout.setting.transitions.zoomIn') }}
|
||||
</a-select-option>
|
||||
<a-select-option value="zoom-out">
|
||||
{{ t('layout.setting.transitions.zoomOut') }}
|
||||
</a-select-option>
|
||||
<a-select-option value="fade">
|
||||
{{ t('layout.setting.transitions.fade') }}
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 提示 -->
|
||||
<a-divider/>
|
||||
<a-alert show-icon type="warning" :message="t('layout.setting.tips')">
|
||||
<template #icon>
|
||||
<sound-outlined/>
|
||||
</template>
|
||||
</a-alert>
|
||||
<!-- 重置 -->
|
||||
<a-button block type="dashed" @click="resetSetting">
|
||||
{{ t('layout.setting.reset') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</a-drawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref} from 'vue';
|
||||
import {useI18n} from 'vue-i18n';
|
||||
import {storeToRefs} from 'pinia';
|
||||
import {message} from 'ant-design-vue/es';
|
||||
import {CheckOutlined, SoundOutlined} from '@ant-design/icons-vue';
|
||||
import {messageLoading} from 'ele-admin-pro/es';
|
||||
import type {
|
||||
ThemeItem,
|
||||
HeadStyleType,
|
||||
SideStyleType,
|
||||
LayoutStyleType,
|
||||
TabStyleType
|
||||
} from 'ele-admin-pro/es';
|
||||
import {useThemeStore} from '@/store/modules/theme';
|
||||
|
||||
defineProps<{
|
||||
// drawer 是否显示, v-model
|
||||
visible: boolean;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:visible', value: boolean): void;
|
||||
}>();
|
||||
|
||||
const {t} = useI18n();
|
||||
const themeStore = useThemeStore();
|
||||
|
||||
const {
|
||||
showTabs,
|
||||
showFooter,
|
||||
headStyle,
|
||||
sideStyle,
|
||||
layoutStyle,
|
||||
sideMenuStyle,
|
||||
tabStyle,
|
||||
transitionName,
|
||||
fixedHeader,
|
||||
fixedSidebar,
|
||||
fixedBody,
|
||||
bodyFull,
|
||||
logoAutoSize,
|
||||
colorfulIcon,
|
||||
sideUniqueOpen,
|
||||
styleResponsive,
|
||||
weakMode,
|
||||
darkMode,
|
||||
color
|
||||
} = storeToRefs(themeStore);
|
||||
|
||||
// 主题列表
|
||||
const themes = ref<ThemeItem[]>([
|
||||
{
|
||||
name: 'default',
|
||||
color: '#1890ff'
|
||||
},
|
||||
{
|
||||
name: 'dust',
|
||||
value: '#5f80c7'
|
||||
},
|
||||
{
|
||||
name: 'sunset',
|
||||
value: '#faad14'
|
||||
},
|
||||
{
|
||||
name: 'volcano',
|
||||
value: '#f5686f'
|
||||
},
|
||||
{
|
||||
name: 'purple',
|
||||
value: '#9266f9'
|
||||
},
|
||||
{
|
||||
name: 'green',
|
||||
value: '#33cc99'
|
||||
},
|
||||
{
|
||||
name: 'geekblue',
|
||||
value: '#32a2d4'
|
||||
}
|
||||
]);
|
||||
|
||||
// 颜色选择器预设颜色
|
||||
const predefineColors = ref<string[]>([
|
||||
'#f5222d',
|
||||
'#fa541c',
|
||||
'#fa8c16',
|
||||
'#faad14',
|
||||
'#a0d911',
|
||||
'#52c41a',
|
||||
'#13c2c2',
|
||||
'#2f54eb',
|
||||
'#722ed1',
|
||||
'#eb2f96'
|
||||
]);
|
||||
|
||||
// 颜色选择器选中颜色
|
||||
const colorValue = ref<string | undefined>(void 0);
|
||||
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
const updateShowTabs = (value: boolean) => {
|
||||
themeStore.setShowTabs(value);
|
||||
};
|
||||
|
||||
const updateShowFooter = (value: boolean) => {
|
||||
themeStore.setShowFooter(value);
|
||||
};
|
||||
|
||||
const updateHeadStyle = (value: HeadStyleType) => {
|
||||
themeStore.setHeadStyle(value);
|
||||
};
|
||||
|
||||
const updateSideStyle = (value: SideStyleType) => {
|
||||
themeStore.setSideStyle(value);
|
||||
};
|
||||
|
||||
const updateLayoutStyle = (value: LayoutStyleType) => {
|
||||
themeStore.setLayoutStyle(value);
|
||||
};
|
||||
|
||||
const updateSideMenuStyle = (value: boolean) => {
|
||||
themeStore.setSideMenuStyle(value ? 'mix' : 'default');
|
||||
};
|
||||
|
||||
const updateTabStyle = (value: TabStyleType) => {
|
||||
themeStore.setTabStyle(value);
|
||||
};
|
||||
|
||||
const updateTransitionName = (value: string) => {
|
||||
themeStore.setTransitionName(value);
|
||||
};
|
||||
|
||||
const updateFixedHeader = (value: boolean) => {
|
||||
themeStore.setFixedHeader(value);
|
||||
};
|
||||
|
||||
const updateFixedSidebar = (value: boolean) => {
|
||||
themeStore.setFixedSidebar(value);
|
||||
};
|
||||
|
||||
const updateFixedBody = (value: boolean) => {
|
||||
themeStore.setFixedBody(value);
|
||||
};
|
||||
|
||||
const updateBodyFull = (value: boolean) => {
|
||||
themeStore.setBodyFull(!value);
|
||||
};
|
||||
|
||||
const updateLogoAutoSize = (value: boolean) => {
|
||||
themeStore.setLogoAutoSize(value);
|
||||
};
|
||||
|
||||
const updateStyleResponsive = (value: boolean) => {
|
||||
themeStore.setStyleResponsive(value);
|
||||
updateVisible(false);
|
||||
};
|
||||
|
||||
const updateColorfulIcon = (value: boolean) => {
|
||||
themeStore.setColorfulIcon(value);
|
||||
};
|
||||
|
||||
const updateSideUniqueOpen = (value: boolean) => {
|
||||
themeStore.setSideUniqueOpen(value);
|
||||
};
|
||||
|
||||
const updateWeakMode = (value: boolean) => {
|
||||
themeStore.setWeakMode(value);
|
||||
};
|
||||
|
||||
const updateDarkMode = (value: boolean) => {
|
||||
doWithLoading(() => themeStore.setDarkMode(value));
|
||||
};
|
||||
|
||||
const updateColor = (value?: string) => {
|
||||
doWithLoading(() => themeStore.setColor(value));
|
||||
};
|
||||
|
||||
const resetSetting = () => {
|
||||
doWithLoading(() => themeStore.resetSetting());
|
||||
};
|
||||
|
||||
const doWithLoading = (fun: () => Promise<void>) => {
|
||||
const hide = messageLoading('正在加载主题..', 0);
|
||||
setTimeout(() => {
|
||||
fun()
|
||||
.then(() => {
|
||||
hide();
|
||||
initColorValue();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
console.error(e);
|
||||
message.error('主题加载失败');
|
||||
});
|
||||
}, 0);
|
||||
};
|
||||
|
||||
const initColorValue = () => {
|
||||
if (color?.value && !themes.value.some((t) => t.value === color.value)) {
|
||||
colorValue.value = color.value;
|
||||
} else {
|
||||
colorValue.value = void 0;
|
||||
}
|
||||
};
|
||||
|
||||
initColorValue();
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ele-setting-wrapper {
|
||||
padding: 20px 18px;
|
||||
|
||||
.ele-setting-title {
|
||||
font-size: 13px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
/* 主题风格 */
|
||||
|
||||
.ele-setting-theme > div {
|
||||
width: 52px;
|
||||
height: 36px;
|
||||
line-height: 1;
|
||||
border-radius: 3px;
|
||||
margin: 0 20px 30px 0;
|
||||
padding: 16px 0 0 26px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:before,
|
||||
&:after,
|
||||
& > .ele-bg-primary {
|
||||
content: '';
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
background: #fff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
&:after {
|
||||
width: 14px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.ele-side-dark:after,
|
||||
&.ele-head-dark:before,
|
||||
&.ele-layout-mix:before,
|
||||
&.ele-layout-mix:after {
|
||||
background: #001529;
|
||||
}
|
||||
|
||||
&.ele-head-light:before,
|
||||
&.ele-head-dark:before,
|
||||
& > .ele-bg-primary {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
&.ele-layout-top {
|
||||
padding-left: 19px;
|
||||
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 主题色选择 */
|
||||
|
||||
.ele-setting-colors {
|
||||
color: #fff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ele-setting-color-item {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
border-radius: 2px;
|
||||
margin: 8px 8px 0 0;
|
||||
display: inline-block;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
vertical-align: top;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
.ele-setting-color-tooltip {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* 主题配置项 */
|
||||
|
||||
.ele-setting-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.setting-item-title {
|
||||
flex: 1;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.setting-item-control {
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-divider {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ant-alert + .ant-btn {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
/* 暗黑模式 */
|
||||
|
||||
&.ele-setting-dark .ele-setting-theme > div {
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.55);
|
||||
|
||||
&:before,
|
||||
&:after,
|
||||
& > .ele-bg-primary {
|
||||
background: #1f1f1f;
|
||||
}
|
||||
|
||||
&.ele-side-dark:after,
|
||||
&.ele-head-dark:before,
|
||||
&.ele-layout-mix:before,
|
||||
&.ele-layout-mix:after {
|
||||
background: #262626;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 颜色选择器 */
|
||||
.ele-setting-color-picker.ele-color-picker-trigger {
|
||||
padding: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin-top: 8px;
|
||||
border: none !important;
|
||||
background: none !important;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.15);
|
||||
|
||||
& > .ele-color-picker-trigger-inner {
|
||||
background: none;
|
||||
|
||||
&.is-empty {
|
||||
background: conic-gradient(from 90deg at 50% 50%,
|
||||
rgb(255, 0, 0) -19.41deg,
|
||||
rgb(255, 0, 0) 18.76deg,
|
||||
rgb(255, 138, 0) 59.32deg,
|
||||
rgb(255, 230, 0) 99.87deg,
|
||||
rgb(20, 255, 0) 141.65deg,
|
||||
rgb(0, 163, 255) 177.72deg,
|
||||
rgb(5, 0, 255) 220.23deg,
|
||||
rgb(173, 0, 255) 260.13deg,
|
||||
rgb(255, 0, 199) 300.69deg,
|
||||
rgb(255, 0, 0) 340.59deg,
|
||||
rgb(255, 0, 0) 378.76deg);
|
||||
|
||||
& + .ele-color-picker-trigger-arrow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
372
src/layout/index.vue
Normal file
372
src/layout/index.vue
Normal file
@@ -0,0 +1,372 @@
|
||||
<template>
|
||||
<ele-pro-layout
|
||||
:menus="menus"
|
||||
:tabs="tabs"
|
||||
:collapse="collapse"
|
||||
:side-nav-collapse="sideNavCollapse"
|
||||
:body-fullscreen="bodyFullscreen"
|
||||
:show-tabs="showTabs"
|
||||
:show-footer="showFooter"
|
||||
:head-style="headStyle"
|
||||
:side-style="sideStyle"
|
||||
:layout-style="layoutStyle"
|
||||
:side-menu-style="sideMenuStyle"
|
||||
:tab-style="tabStyle"
|
||||
:fixed-header="fixedHeader"
|
||||
:fixed-sidebar="fixedSidebar"
|
||||
:fixed-body="fixedBody"
|
||||
:body-full="bodyFull"
|
||||
:logo-auto-size="logoAutoSize"
|
||||
:colorful-icon="colorfulIcon"
|
||||
:side-unique-open="sideUniqueOpen"
|
||||
:style-responsive="styleResponsive"
|
||||
:hide-footers="HIDE_FOOTERS"
|
||||
:hide-sidebars="HIDE_SIDEBARS"
|
||||
:repeatable-tabs="REPEATABLE_TABS"
|
||||
:home-title="HOME_TITLE || t('layout.home')"
|
||||
:home-path="HOME_PATH"
|
||||
:layout-path="LAYOUT_PATH"
|
||||
:redirect-path="REDIRECT_PATH"
|
||||
:locale="locale"
|
||||
:i18n="i18n"
|
||||
@update:collapse="updateCollapse"
|
||||
@update:side-nav-collapse="updateSideNavCollapse"
|
||||
@update:body-fullscreen="updateBodyFullscreen"
|
||||
@tab-add="addPageTab"
|
||||
@tab-remove="removePageTab"
|
||||
@tab-remove-all="removeAllPageTab"
|
||||
@tab-remove-left="removeLeftPageTab"
|
||||
@tab-remove-right="removeRightPageTab"
|
||||
@tab-remove-other="removeOtherPageTab"
|
||||
@reload-page="reloadPageTab"
|
||||
@logo-click="onLogoClick"
|
||||
@screen-size-change="screenSizeChange"
|
||||
@set-home-components="setHomeComponents"
|
||||
@tab-context-menu="onTabContextMenu"
|
||||
>
|
||||
<!-- 路由出口 -->
|
||||
<router-layout />
|
||||
<!-- logo 图标 -->
|
||||
<template #logo>
|
||||
<a-space class="sys-logo flex items-center justify-center">
|
||||
<AAvatar
|
||||
shape="square"
|
||||
:preview="false"
|
||||
:size="28"
|
||||
v-if="logoPath"
|
||||
:src="logoPath"
|
||||
alt="logo"
|
||||
/>
|
||||
<template v-if="!collapse">
|
||||
{{ projectName }}
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
<!-- 顶栏右侧区域 -->
|
||||
<template #right>
|
||||
<header-tools :fullscreen="fullscreen" @fullscreen="onFullscreen" />
|
||||
</template>
|
||||
<!-- 全局页脚 -->
|
||||
<template #footer>
|
||||
<page-footer />
|
||||
</template>
|
||||
<!-- 菜单图标 -->
|
||||
<template #icon="{ icon }">
|
||||
<component :is="icon" class="ant-menu-item-icon" />
|
||||
</template>
|
||||
<!-- 自定义菜单标题增加徽章、小红点 -->
|
||||
<template #title="{ item }">
|
||||
<menu-title :item="item" />
|
||||
</template>
|
||||
<template #top-title="{ item }">
|
||||
<menu-title :item="item" />
|
||||
</template>
|
||||
<template #nav-title="{ item }">
|
||||
<menu-title :item="item" />
|
||||
</template>
|
||||
</ele-pro-layout>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { toggleFullscreen, isFullscreen } from 'ele-admin-pro/es';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import RouterLayout from '@/components/RouterLayout/index.vue';
|
||||
import HeaderTools from './components/header-tools.vue';
|
||||
import PageFooter from './components/page-footer.vue';
|
||||
import MenuTitle from './components/menu-title.vue';
|
||||
import {
|
||||
HIDE_SIDEBARS,
|
||||
HIDE_FOOTERS,
|
||||
REPEATABLE_TABS,
|
||||
HOME_TITLE,
|
||||
HOME_PATH,
|
||||
LAYOUT_PATH,
|
||||
REDIRECT_PATH,
|
||||
I18N_ENABLE
|
||||
} from '@/config/setting';
|
||||
import {
|
||||
addPageTab,
|
||||
removePageTab,
|
||||
removeAllPageTab,
|
||||
removeLeftPageTab,
|
||||
removeRightPageTab,
|
||||
removeOtherPageTab,
|
||||
reloadPageTab,
|
||||
setHomeComponents
|
||||
} from '@/utils/page-tab-util';
|
||||
import type { TabCtxMenuOption } from 'ele-admin-pro/es/ele-pro-layout/types';
|
||||
import { hasPermission } from '@/utils/permission';
|
||||
|
||||
const { push } = useRouter();
|
||||
const { t, locale } = useI18n();
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 是否刷新页面
|
||||
if(localStorage.getItem('Reload')){
|
||||
window.location.reload();
|
||||
localStorage.removeItem('Reload')
|
||||
}
|
||||
|
||||
const themeStore = useThemeStore();
|
||||
// 网站名称
|
||||
const projectName = t('layout.system');
|
||||
// 网站LOGO
|
||||
const logoPath = '/logo.png';
|
||||
|
||||
// 是否全屏
|
||||
const fullscreen = ref(false);
|
||||
|
||||
// 菜单数据
|
||||
const { menus } = storeToRefs(userStore);
|
||||
|
||||
// 布局风格
|
||||
const {
|
||||
tabs,
|
||||
collapse,
|
||||
sideNavCollapse,
|
||||
bodyFullscreen,
|
||||
showTabs,
|
||||
showFooter,
|
||||
headStyle,
|
||||
sideStyle,
|
||||
layoutStyle,
|
||||
sideMenuStyle,
|
||||
tabStyle,
|
||||
fixedHeader,
|
||||
fixedSidebar,
|
||||
fixedBody,
|
||||
bodyFull,
|
||||
logoAutoSize,
|
||||
colorfulIcon,
|
||||
sideUniqueOpen,
|
||||
styleResponsive
|
||||
} = storeToRefs(themeStore);
|
||||
|
||||
/* 侧栏折叠切换 */
|
||||
const updateCollapse = (value: boolean) => {
|
||||
themeStore.setCollapse(value);
|
||||
};
|
||||
|
||||
/* 双侧栏一级折叠切换 */
|
||||
const updateSideNavCollapse = (value: boolean) => {
|
||||
themeStore.setSideNavCollapse(value);
|
||||
};
|
||||
|
||||
/* 内容区域全屏切换 */
|
||||
const updateBodyFullscreen = (value: boolean) => {
|
||||
themeStore.setBodyFullscreen(value);
|
||||
};
|
||||
|
||||
/* logo 点击事件 */
|
||||
const onLogoClick = (isHome: boolean) => {
|
||||
if (hasPermission('sys:company:profile')) {
|
||||
push(`/`);
|
||||
return false;
|
||||
}
|
||||
isHome || push(LAYOUT_PATH);
|
||||
};
|
||||
|
||||
/* 监听屏幕尺寸改变 */
|
||||
const screenSizeChange = () => {
|
||||
themeStore.updateScreenSize();
|
||||
fullscreen.value = isFullscreen();
|
||||
};
|
||||
|
||||
/* 全屏切换 */
|
||||
const onFullscreen = () => {
|
||||
try {
|
||||
fullscreen.value = toggleFullscreen();
|
||||
} catch (e) {
|
||||
message.error('您的浏览器不支持全屏模式');
|
||||
}
|
||||
};
|
||||
|
||||
/* 页签右键菜单点击事件 */
|
||||
const onTabContextMenu = ({
|
||||
key,
|
||||
tabKey,
|
||||
item,
|
||||
active
|
||||
}: TabCtxMenuOption) => {
|
||||
switch (key) {
|
||||
case 'reload': // 刷新
|
||||
reloadPageTab({
|
||||
isHome: !item,
|
||||
fullPath: item?.fullPath ?? tabKey
|
||||
});
|
||||
break;
|
||||
case 'close': // 关闭当前
|
||||
removePageTab({
|
||||
key: item?.fullPath ?? tabKey,
|
||||
active
|
||||
});
|
||||
break;
|
||||
case 'left': // 关闭左侧
|
||||
removeLeftPageTab({
|
||||
key: tabKey,
|
||||
active
|
||||
});
|
||||
break;
|
||||
case 'right': // 关闭右侧
|
||||
removeRightPageTab({
|
||||
key: tabKey,
|
||||
active
|
||||
});
|
||||
break;
|
||||
case 'other': // 关闭其他
|
||||
removeOtherPageTab({
|
||||
key: tabKey,
|
||||
active
|
||||
});
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/* 菜单标题国际化 */
|
||||
const i18n = (_path: string, key?: string) => {
|
||||
if (!I18N_ENABLE || !key) {
|
||||
return;
|
||||
}
|
||||
const k = 'route.' + key + '._name';
|
||||
const title = t(k);
|
||||
if (title !== k) {
|
||||
return title;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as MenuIcons from './menu-icons';
|
||||
|
||||
export default {
|
||||
name: 'EleLayout',
|
||||
components: MenuIcons
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
// 侧栏菜单徽章样式,定位在右侧垂直居中并调小尺寸
|
||||
.ele-menu-badge {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 14px;
|
||||
line-height: 1;
|
||||
margin-top: -9px;
|
||||
font-size: 0;
|
||||
|
||||
.ant-badge-count {
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
border-radius: 9px;
|
||||
box-shadow: none;
|
||||
min-width: 18px;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.ant-scroll-number-only {
|
||||
height: 18px;
|
||||
|
||||
& > p.ant-scroll-number-only-unit {
|
||||
height: 18px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 父级菜单标题中右侧多定位一点,避免与箭头重合
|
||||
.ant-menu-submenu-title > .ant-menu-title-content .ele-menu-badge {
|
||||
right: 36px;
|
||||
}
|
||||
|
||||
// 折叠悬浮中样式调整
|
||||
.ant-menu-submenu-popup {
|
||||
.ant-menu-submenu-title > .ant-menu-title-content .ele-menu-badge {
|
||||
right: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
// 顶栏菜单标题中样式调整
|
||||
.ele-admin-header-nav{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.ele-admin-header-nav > .ant-menu {
|
||||
& > .ant-menu-item,
|
||||
& > .ant-menu-submenu > .ant-menu-submenu-title {
|
||||
& > .ant-menu-title-content .ele-menu-badge {
|
||||
position: static;
|
||||
right: auto;
|
||||
top: auto;
|
||||
display: inline-block;
|
||||
vertical-align: 5px;
|
||||
margin: 0 0 0 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 双侧栏时一级侧栏菜单中样式调整,定位在右上角
|
||||
.ele-admin-sidebar-nav-menu > .ant-menu {
|
||||
& > .ant-menu-item,
|
||||
& > .ant-menu-submenu > .ant-menu-submenu-title {
|
||||
& > .ant-menu-title-content .ele-menu-badge {
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 双侧栏时一级侧栏菜单折叠后样式调整
|
||||
.ele-admin-nav-collapse .ele-admin-sidebar-nav-menu > .ant-menu {
|
||||
& > .ant-menu-item,
|
||||
& > .ant-menu-submenu > .ant-menu-submenu-title {
|
||||
& > .ant-menu-title-content .ele-menu-badge {
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单折叠后在 tooltip 中不显示徽章
|
||||
.ant-tooltip-inner .ele-menu-badge {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// logo
|
||||
.ele-admin-logo {
|
||||
margin-right: 12px;
|
||||
img {
|
||||
border-radius: 4px !important;
|
||||
}
|
||||
}
|
||||
|
||||
svg.md-editor-icon{
|
||||
width: 27px !important;
|
||||
height: 27px !important;
|
||||
}
|
||||
</style>
|
||||
153
src/layout/menu-icons.ts
Normal file
153
src/layout/menu-icons.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
/** 菜单用到的图标 */
|
||||
export {
|
||||
HomeOutlined,
|
||||
SettingOutlined,
|
||||
TeamOutlined,
|
||||
DesktopOutlined,
|
||||
FileTextOutlined,
|
||||
TableOutlined,
|
||||
AppstoreOutlined,
|
||||
CheckCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
UserOutlined,
|
||||
TagOutlined,
|
||||
IdcardOutlined,
|
||||
BarChartOutlined,
|
||||
AuditOutlined,
|
||||
PicLeftOutlined,
|
||||
BellOutlined,
|
||||
CloseCircleOutlined,
|
||||
QuestionCircleOutlined,
|
||||
SoundOutlined,
|
||||
ApartmentOutlined,
|
||||
DashboardOutlined,
|
||||
OneToOneOutlined,
|
||||
DragOutlined,
|
||||
InteractionOutlined,
|
||||
BankOutlined,
|
||||
BlockOutlined,
|
||||
CheckSquareOutlined,
|
||||
ProfileOutlined,
|
||||
WarningOutlined,
|
||||
FolderOutlined,
|
||||
YoutubeOutlined,
|
||||
ControlOutlined,
|
||||
EllipsisOutlined,
|
||||
CalendarOutlined,
|
||||
AppstoreAddOutlined,
|
||||
FileSearchOutlined,
|
||||
EnvironmentOutlined,
|
||||
CompassOutlined,
|
||||
FontSizeOutlined,
|
||||
SketchOutlined,
|
||||
BgColorsOutlined,
|
||||
PrinterOutlined,
|
||||
QrcodeOutlined,
|
||||
BarcodeOutlined,
|
||||
PictureOutlined,
|
||||
LinkOutlined,
|
||||
AlertOutlined,
|
||||
HistoryOutlined,
|
||||
ChromeOutlined,
|
||||
CodeOutlined,
|
||||
AntDesignOutlined,
|
||||
ReadOutlined,
|
||||
CrownOutlined,
|
||||
LaptopOutlined,
|
||||
ShoppingCartOutlined,
|
||||
SkinOutlined,
|
||||
ShopOutlined,
|
||||
ShoppingOutlined,
|
||||
AliyunOutlined,
|
||||
GiftOutlined,
|
||||
InsuranceOutlined,
|
||||
FileZipOutlined,
|
||||
SearchOutlined,
|
||||
RedEnvelopeOutlined,
|
||||
MoneyCollectOutlined,
|
||||
TagsOutlined,
|
||||
PayCircleOutlined,
|
||||
RocketOutlined,
|
||||
BarsOutlined,
|
||||
WalletOutlined,
|
||||
UngroupOutlined,
|
||||
ToolOutlined,
|
||||
HourglassOutlined,
|
||||
FormatPainterOutlined,
|
||||
CarOutlined,
|
||||
DownloadOutlined,
|
||||
UploadOutlined,
|
||||
MailOutlined,
|
||||
CloudOutlined,
|
||||
ClearOutlined,
|
||||
CoffeeOutlined,
|
||||
CopyrightOutlined,
|
||||
CompressOutlined,
|
||||
CalculatorOutlined,
|
||||
AimOutlined,
|
||||
AudioOutlined,
|
||||
BugOutlined,
|
||||
CloudSyncOutlined,
|
||||
CommentOutlined,
|
||||
DisconnectOutlined,
|
||||
DingtalkOutlined,
|
||||
ExpandOutlined,
|
||||
DollarOutlined,
|
||||
FolderOpenOutlined,
|
||||
ExperimentOutlined,
|
||||
ForkOutlined,
|
||||
FolderAddOutlined,
|
||||
FunnelPlotOutlined,
|
||||
GlobalOutlined,
|
||||
LikeOutlined,
|
||||
LayoutOutlined,
|
||||
MergeCellsOutlined,
|
||||
MehOutlined,
|
||||
MobileOutlined,
|
||||
NodeExpandOutlined,
|
||||
PaperClipOutlined,
|
||||
ScanOutlined,
|
||||
ShakeOutlined,
|
||||
ThunderboltOutlined,
|
||||
TrademarkOutlined,
|
||||
UnlockOutlined,
|
||||
UsbOutlined,
|
||||
WhatsAppOutlined,
|
||||
WomanOutlined,
|
||||
WifiOutlined,
|
||||
VerifiedOutlined,
|
||||
UserSwitchOutlined,
|
||||
UsergroupAddOutlined,
|
||||
UsergroupDeleteOutlined,
|
||||
UserAddOutlined,
|
||||
UserDeleteOutlined,
|
||||
TranslationOutlined,
|
||||
TransactionOutlined,
|
||||
TrophyOutlined,
|
||||
StarOutlined,
|
||||
SplitCellsOutlined,
|
||||
ShareAltOutlined,
|
||||
RestOutlined,
|
||||
PartitionOutlined,
|
||||
MedicineBoxOutlined,
|
||||
HeartOutlined,
|
||||
GatewayOutlined,
|
||||
FlagOutlined,
|
||||
FireOutlined,
|
||||
FieldNumberOutlined,
|
||||
FieldStringOutlined,
|
||||
AppleOutlined,
|
||||
AndroidOutlined,
|
||||
GithubOutlined,
|
||||
Html5Outlined,
|
||||
QqOutlined,
|
||||
AlipayOutlined,
|
||||
RedditOutlined,
|
||||
InstagramOutlined,
|
||||
WechatOutlined,
|
||||
BorderOutlined,
|
||||
PieChartOutlined,
|
||||
AreaChartOutlined,
|
||||
KeyOutlined,
|
||||
LockOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
Reference in New Issue
Block a user