feat: 将整体logo视觉替换为ai生成的logo

master
LCJ-MinYa 2 months ago
parent 6825193266
commit 6559b3eff2

@ -1,42 +1,42 @@
// 根据角色动态生成路由
import { defineFakeRoute } from "vite-plugin-fake-server/client";
import { defineFakeRoute } from 'vite-plugin-fake-server/client';
export default defineFakeRoute([
{
url: "/login",
method: "post",
response: ({ body }) => {
if (body.username === "admin") {
return {
success: true,
data: {
avatar: "https://avatars.githubusercontent.com/u/44761321",
username: "admin",
nickname: "小铭",
// 一个用户可能有多个角色
roles: ["admin"],
// 按钮级别权限
permissions: ["*:*:*"],
accessToken: "eyJhbGciOiJIUzUxMiJ9.admin",
refreshToken: "eyJhbGciOiJIUzUxMiJ9.adminRefresh",
expires: "2030/10/30 00:00:00"
}
};
} else {
return {
success: true,
data: {
avatar: "https://avatars.githubusercontent.com/u/52823142",
username: "common",
nickname: "小林",
roles: ["common"],
permissions: ["permission:btn:add", "permission:btn:edit"],
accessToken: "eyJhbGciOiJIUzUxMiJ9.common",
refreshToken: "eyJhbGciOiJIUzUxMiJ9.commonRefresh",
expires: "2030/10/30 00:00:00"
}
};
}
}
}
{
url: '/login',
method: 'post',
response: ({ body }) => {
if (body.username === 'admin') {
return {
success: true,
data: {
avatar: '/user_small.png',
username: 'admin',
nickname: 'Levi',
// 一个用户可能有多个角色
roles: ['admin'],
// 按钮级别权限
permissions: ['*:*:*'],
accessToken: 'eyJhbGciOiJIUzUxMiJ9.admin',
refreshToken: 'eyJhbGciOiJIUzUxMiJ9.adminRefresh',
expires: '2030/10/30 00:00:00',
},
};
} else {
return {
success: true,
data: {
avatar: 'https://avatars.githubusercontent.com/u/52823142',
username: 'common',
nickname: '小林',
roles: ['common'],
permissions: ['permission:btn:add', 'permission:btn:edit'],
accessToken: 'eyJhbGciOiJIUzUxMiJ9.common',
refreshToken: 'eyJhbGciOiJIUzUxMiJ9.commonRefresh',
expires: '2030/10/30 00:00:00',
},
};
}
},
},
]);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 96 KiB

@ -1,96 +1,102 @@
<script setup lang="ts">
import { ref, computed } from "vue";
import { noticesData } from "./data";
import NoticeList from "./components/NoticeList.vue";
import BellIcon from "@iconify-icons/ep/bell";
import { ref, computed } from 'vue';
import { noticesData } from './data';
import NoticeList from './components/NoticeList.vue';
import BellIcon from '@iconify-icons/ep/bell';
const noticesNum = ref(0);
const notices = ref(noticesData);
const notices = ref([]);
// const notices = ref(noticesData);
const activeKey = ref(noticesData[0]?.key);
notices.value.map(v => (noticesNum.value += v.list.length));
notices.value.map((v) => (noticesNum.value += v.list.length));
const getLabel = computed(
() => item =>
item.name + (item.list.length > 0 ? `(${item.list.length})` : "")
);
const getLabel = computed(() => (item) => item.name + (item.list.length > 0 ? `(${item.list.length})` : ''));
</script>
<template>
<el-dropdown trigger="click" placement="bottom-end">
<span
:class="[
'dropdown-badge',
'navbar-bg-hover',
'select-none',
Number(noticesNum) !== 0 && 'mr-[10px]'
]"
<el-dropdown
trigger="click"
placement="bottom-end"
>
<el-badge :value="Number(noticesNum) === 0 ? '' : noticesNum" :max="99">
<span class="header-notice-icon">
<IconifyIconOffline :icon="BellIcon" />
<span :class="['dropdown-badge', 'navbar-bg-hover', 'select-none', Number(noticesNum) !== 0 && 'mr-[10px]']">
<el-badge
:value="Number(noticesNum) === 0 ? '' : noticesNum"
:max="99"
>
<span class="header-notice-icon">
<IconifyIconOffline :icon="BellIcon" />
</span>
</el-badge>
</span>
</el-badge>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-tabs
v-model="activeKey"
:stretch="true"
class="dropdown-tabs"
:style="{ width: notices.length === 0 ? '200px' : '330px' }"
>
<el-empty
v-if="notices.length === 0"
description="暂无消息"
:image-size="60"
/>
<span v-else>
<template v-for="item in notices" :key="item.key">
<el-tab-pane :label="getLabel(item)" :name="`${item.key}`">
<el-scrollbar max-height="330px">
<div class="noticeList-container">
<NoticeList :list="item.list" :emptyText="item.emptyText" />
</div>
</el-scrollbar>
</el-tab-pane>
</template>
</span>
</el-tabs>
</el-dropdown-menu>
</template>
</el-dropdown>
<template #dropdown>
<el-dropdown-menu>
<el-tabs
v-model="activeKey"
:stretch="true"
class="dropdown-tabs"
:style="{ width: notices.length === 0 ? '200px' : '330px' }"
>
<el-empty
v-if="notices.length === 0"
description="暂无消息"
:image-size="60"
/>
<span v-else>
<template
v-for="item in notices"
:key="item.key"
>
<el-tab-pane
:label="getLabel(item)"
:name="`${item.key}`"
>
<el-scrollbar max-height="330px">
<div class="noticeList-container">
<NoticeList
:list="item.list"
:emptyText="item.emptyText"
/>
</div>
</el-scrollbar>
</el-tab-pane>
</template>
</span>
</el-tabs>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<style lang="scss" scoped>
.dropdown-badge {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 48px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 48px;
cursor: pointer;
.header-notice-icon {
font-size: 18px;
}
.header-notice-icon {
font-size: 18px;
}
}
.dropdown-tabs {
.noticeList-container {
padding: 15px 24px 0;
}
.noticeList-container {
padding: 15px 24px 0;
}
:deep(.el-tabs__header) {
margin: 0;
}
:deep(.el-tabs__header) {
margin: 0;
}
:deep(.el-tabs__nav-wrap)::after {
height: 1px;
}
:deep(.el-tabs__nav-wrap)::after {
height: 1px;
}
:deep(.el-tabs__nav-wrap) {
padding: 0 36px;
}
:deep(.el-tabs__nav-wrap) {
padding: 0 36px;
}
}
</style>

@ -1,157 +1,152 @@
import { storeToRefs } from "pinia";
import { getConfig } from "@/config";
import { emitter } from "@/utils/mitt";
import Avatar from "@/assets/user.jpg";
import { getTopMenu } from "@/router/utils";
import { useFullscreen } from "@vueuse/core";
import type { routeMetaType } from "../types";
import { useRouter, useRoute } from "vue-router";
import { router, remainingPaths } from "@/router";
import { computed, type CSSProperties } from "vue";
import { useAppStoreHook } from "@/store/modules/app";
import { useUserStoreHook } from "@/store/modules/user";
import { useGlobal, isAllEmpty } from "@pureadmin/utils";
import { usePermissionStoreHook } from "@/store/modules/permission";
import ExitFullscreen from "@iconify-icons/ri/fullscreen-exit-fill";
import Fullscreen from "@iconify-icons/ri/fullscreen-fill";
const errorInfo =
"The current routing configuration is incorrect, please check the configuration";
import { storeToRefs } from 'pinia';
import { getConfig } from '@/config';
import { emitter } from '@/utils/mitt';
import Avatar from '@/assets/user.jpg';
import { getTopMenu } from '@/router/utils';
import { useFullscreen } from '@vueuse/core';
import type { routeMetaType } from '../types';
import { useRouter, useRoute } from 'vue-router';
import { router, remainingPaths } from '@/router';
import { computed, type CSSProperties } from 'vue';
import { useAppStoreHook } from '@/store/modules/app';
import { useUserStoreHook } from '@/store/modules/user';
import { useGlobal, isAllEmpty } from '@pureadmin/utils';
import { usePermissionStoreHook } from '@/store/modules/permission';
import ExitFullscreen from '@iconify-icons/ri/fullscreen-exit-fill';
import Fullscreen from '@iconify-icons/ri/fullscreen-fill';
const errorInfo = 'The current routing configuration is incorrect, please check the configuration';
export function useNav() {
const route = useRoute();
const pureApp = useAppStoreHook();
const routers = useRouter().options.routes;
const { isFullscreen, toggle } = useFullscreen();
const { wholeMenus } = storeToRefs(usePermissionStoreHook());
/** 平台`layout`中所有`el-tooltip`的`effect`配置,默认`light` */
const tooltipEffect = getConfig()?.TooltipEffect ?? "light";
const getDivStyle = computed((): CSSProperties => {
const route = useRoute();
const pureApp = useAppStoreHook();
const routers = useRouter().options.routes;
const { isFullscreen, toggle } = useFullscreen();
const { wholeMenus } = storeToRefs(usePermissionStoreHook());
/** 平台`layout`中所有`el-tooltip`的`effect`配置,默认`light` */
const tooltipEffect = getConfig()?.TooltipEffect ?? 'light';
const getDivStyle = computed((): CSSProperties => {
return {
width: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
overflow: 'hidden',
};
});
/** 头像(如果头像为空则使用 src/assets/user.jpg */
const userAvatar = computed(() => {
return isAllEmpty(useUserStoreHook()?.avatar) ? Avatar : useUserStoreHook()?.avatar;
});
/** 昵称(如果昵称为空则显示用户名) */
const username = computed(() => {
return isAllEmpty(useUserStoreHook()?.nickname) ? useUserStoreHook()?.username : useUserStoreHook()?.nickname;
});
const avatarsStyle = computed(() => {
return username.value ? { marginRight: '10px' } : '';
});
const isCollapse = computed(() => {
return !pureApp.getSidebarStatus;
});
const device = computed(() => {
return pureApp.getDevice;
});
const { $storage, $config } = useGlobal<GlobalPropertiesApi>();
const layout = computed(() => {
return $storage?.layout?.layout;
});
const title = computed(() => {
return $config.Title;
});
/** 动态title */
function changeTitle(meta: routeMetaType) {
const Title = getConfig().Title;
if (Title) document.title = `${meta.title} | ${Title}`;
else document.title = meta.title;
}
/** 退出登录 */
function logout() {
useUserStoreHook().logOut();
}
function backTopMenu() {
router.push(getTopMenu()?.path);
}
function onPanel() {
emitter.emit('openPanel');
}
function toggleSideBar() {
pureApp.toggleSideBar();
}
function handleResize(menuRef) {
menuRef?.handleResize();
}
function resolvePath(route) {
if (!route.children) return console.error(errorInfo);
const httpReg = /^http(s?):\/\//;
const routeChildPath = route.children[0]?.path;
if (httpReg.test(routeChildPath)) {
return route.path + '/' + routeChildPath;
} else {
return routeChildPath;
}
}
function menuSelect(indexPath: string) {
if (wholeMenus.value.length === 0 || isRemaining(indexPath)) return;
emitter.emit('changLayoutRoute', indexPath);
}
/** 判断路径是否参与菜单 */
function isRemaining(path: string) {
return remainingPaths.includes(path);
}
/** 获取`logo` */
function getLogo() {
return new URL('/user_small.png', import.meta.url).href;
}
return {
width: "100%",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
overflow: "hidden"
route,
title,
device,
layout,
logout,
routers,
$storage,
isFullscreen,
Fullscreen,
ExitFullscreen,
toggle,
backTopMenu,
onPanel,
getDivStyle,
changeTitle,
toggleSideBar,
menuSelect,
handleResize,
resolvePath,
getLogo,
isCollapse,
pureApp,
username,
userAvatar,
avatarsStyle,
tooltipEffect,
};
});
/** 头像(如果头像为空则使用 src/assets/user.jpg */
const userAvatar = computed(() => {
return isAllEmpty(useUserStoreHook()?.avatar)
? Avatar
: useUserStoreHook()?.avatar;
});
/** 昵称(如果昵称为空则显示用户名) */
const username = computed(() => {
return isAllEmpty(useUserStoreHook()?.nickname)
? useUserStoreHook()?.username
: useUserStoreHook()?.nickname;
});
const avatarsStyle = computed(() => {
return username.value ? { marginRight: "10px" } : "";
});
const isCollapse = computed(() => {
return !pureApp.getSidebarStatus;
});
const device = computed(() => {
return pureApp.getDevice;
});
const { $storage, $config } = useGlobal<GlobalPropertiesApi>();
const layout = computed(() => {
return $storage?.layout?.layout;
});
const title = computed(() => {
return $config.Title;
});
/** 动态title */
function changeTitle(meta: routeMetaType) {
const Title = getConfig().Title;
if (Title) document.title = `${meta.title} | ${Title}`;
else document.title = meta.title;
}
/** 退出登录 */
function logout() {
useUserStoreHook().logOut();
}
function backTopMenu() {
router.push(getTopMenu()?.path);
}
function onPanel() {
emitter.emit("openPanel");
}
function toggleSideBar() {
pureApp.toggleSideBar();
}
function handleResize(menuRef) {
menuRef?.handleResize();
}
function resolvePath(route) {
if (!route.children) return console.error(errorInfo);
const httpReg = /^http(s?):\/\//;
const routeChildPath = route.children[0]?.path;
if (httpReg.test(routeChildPath)) {
return route.path + "/" + routeChildPath;
} else {
return routeChildPath;
}
}
function menuSelect(indexPath: string) {
if (wholeMenus.value.length === 0 || isRemaining(indexPath)) return;
emitter.emit("changLayoutRoute", indexPath);
}
/** 判断路径是否参与菜单 */
function isRemaining(path: string) {
return remainingPaths.includes(path);
}
/** 获取`logo` */
function getLogo() {
return new URL("/logo.svg", import.meta.url).href;
}
return {
route,
title,
device,
layout,
logout,
routers,
$storage,
isFullscreen,
Fullscreen,
ExitFullscreen,
toggle,
backTopMenu,
onPanel,
getDivStyle,
changeTitle,
toggleSideBar,
menuSelect,
handleResize,
resolvePath,
getLogo,
isCollapse,
pureApp,
username,
userAvatar,
avatarsStyle,
tooltipEffect
};
}

@ -1,25 +1,25 @@
<script setup lang="ts">
import Motion from "./utils/motion";
import { useRouter } from "vue-router";
import { message } from "@/utils/message";
import { loginRules } from "./utils/rule";
import { useNav } from "@/layout/hooks/useNav";
import type { FormInstance } from "element-plus";
import { useLayout } from "@/layout/hooks/useLayout";
import { useUserStoreHook } from "@/store/modules/user";
import { initRouter, getTopMenu } from "@/router/utils";
import { bg, avatar, illustration } from "./utils/static";
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
import { ref, reactive, toRaw, onMounted, onBeforeUnmount } from "vue";
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
import Motion from './utils/motion';
import { useRouter } from 'vue-router';
import { message } from '@/utils/message';
import { loginRules } from './utils/rule';
import { useNav } from '@/layout/hooks/useNav';
import type { FormInstance } from 'element-plus';
import { useLayout } from '@/layout/hooks/useLayout';
import { useUserStoreHook } from '@/store/modules/user';
import { initRouter, getTopMenu } from '@/router/utils';
import { bg, avatar, illustration } from './utils/static';
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
import { ref, reactive, toRaw, onMounted, onBeforeUnmount } from 'vue';
import { useDataThemeChange } from '@/layout/hooks/useDataThemeChange';
import dayIcon from "@/assets/svg/day.svg?component";
import darkIcon from "@/assets/svg/dark.svg?component";
import Lock from "@iconify-icons/ri/lock-fill";
import User from "@iconify-icons/ri/user-3-fill";
import dayIcon from '@/assets/svg/day.svg?component';
import darkIcon from '@/assets/svg/dark.svg?component';
import Lock from '@iconify-icons/ri/lock-fill';
import User from '@iconify-icons/ri/user-3-fill';
defineOptions({
name: "Login"
name: 'Login',
});
const router = useRouter();
const loading = ref(false);
@ -33,136 +33,139 @@ dataThemeChange(overallStyle.value);
const { title } = useNav();
const ruleForm = reactive({
username: "admin",
password: "admin123"
username: 'admin',
password: 'admin123',
});
const onLogin = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
loading.value = true;
useUserStoreHook()
.loginByUsername({ username: ruleForm.username, password: "admin123" })
.then(res => {
if (res.success) {
//
return initRouter().then(() => {
router.push(getTopMenu(true).path).then(() => {
message("登录成功", { type: "success" });
});
});
} else {
message("登录失败", { type: "error" });
}
})
.finally(() => (loading.value = false));
}
});
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
loading.value = true;
useUserStoreHook()
.loginByUsername({ username: ruleForm.username, password: 'admin123' })
.then((res) => {
if (res.success) {
//
return initRouter().then(() => {
router.push(getTopMenu(true).path).then(() => {
message('登录成功', { type: 'success' });
});
});
} else {
message('登录失败', { type: 'error' });
}
})
.finally(() => (loading.value = false));
}
});
};
/** 使用公共函数,避免`removeEventListener`失效 */
function onkeypress({ code }: KeyboardEvent) {
if (["Enter", "NumpadEnter"].includes(code)) {
onLogin(ruleFormRef.value);
}
if (['Enter', 'NumpadEnter'].includes(code)) {
onLogin(ruleFormRef.value);
}
}
onMounted(() => {
window.document.addEventListener("keypress", onkeypress);
window.document.addEventListener('keypress', onkeypress);
});
onBeforeUnmount(() => {
window.document.removeEventListener("keypress", onkeypress);
window.document.removeEventListener('keypress', onkeypress);
});
</script>
<template>
<div class="select-none">
<img :src="bg" class="wave" />
<div class="flex-c absolute right-5 top-3">
<!-- 主题 -->
<el-switch
v-model="dataTheme"
inline-prompt
:active-icon="dayIcon"
:inactive-icon="darkIcon"
@change="dataThemeChange"
/>
</div>
<div class="login-container">
<div class="img">
<component :is="toRaw(illustration)" />
</div>
<div class="login-box">
<div class="login-form">
<avatar class="avatar" />
<Motion>
<h2 class="outline-none">{{ title }}</h2>
</Motion>
<div class="select-none">
<img
:src="bg"
class="wave"
/>
<div class="flex-c absolute right-5 top-3">
<!-- 主题 -->
<el-switch
v-model="dataTheme"
inline-prompt
:active-icon="dayIcon"
:inactive-icon="darkIcon"
@change="dataThemeChange"
/>
</div>
<div class="login-container">
<div class="img">
<component :is="toRaw(illustration)" />
</div>
<div class="login-box">
<div class="login-form">
<!-- <avatar class="avatar" /> -->
<Motion>
<h2 class="outline-none">{{ title }}</h2>
</Motion>
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="loginRules"
size="large"
>
<Motion :delay="100">
<el-form-item
:rules="[
{
required: true,
message: '请输入账号',
trigger: 'blur'
}
]"
prop="username"
>
<el-input
v-model="ruleForm.username"
clearable
placeholder="账号"
:prefix-icon="useRenderIcon(User)"
/>
</el-form-item>
</Motion>
<el-form
ref="ruleFormRef"
:model="ruleForm"
:rules="loginRules"
size="large"
>
<Motion :delay="100">
<el-form-item
:rules="[
{
required: true,
message: '请输入账号',
trigger: 'blur',
},
]"
prop="username"
>
<el-input
v-model="ruleForm.username"
clearable
placeholder="账号"
:prefix-icon="useRenderIcon(User)"
/>
</el-form-item>
</Motion>
<Motion :delay="150">
<el-form-item prop="password">
<el-input
v-model="ruleForm.password"
clearable
show-password
placeholder="密码"
:prefix-icon="useRenderIcon(Lock)"
/>
</el-form-item>
</Motion>
<Motion :delay="150">
<el-form-item prop="password">
<el-input
v-model="ruleForm.password"
clearable
show-password
placeholder="密码"
:prefix-icon="useRenderIcon(Lock)"
/>
</el-form-item>
</Motion>
<Motion :delay="250">
<el-button
class="w-full mt-4"
size="default"
type="primary"
:loading="loading"
@click="onLogin(ruleFormRef)"
>
登录
</el-button>
</Motion>
</el-form>
<Motion :delay="250">
<el-button
class="w-full mt-4"
size="default"
type="primary"
:loading="loading"
@click="onLogin(ruleFormRef)"
>
登录
</el-button>
</Motion>
</el-form>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<style scoped>
@import url("@/style/login.css");
@import url('@/style/login.css');
</style>
<style lang="scss" scoped>
:deep(.el-input-group__append, .el-input-group__prepend) {
padding: 0;
padding: 0;
}
</style>

Loading…
Cancel
Save