style: 整体视觉升级(登录页分屏、菜单图标、列表卡片化)
- 新增 BaseIcon 组件(inline SVG,不引入新依赖) - 登录页:左右分屏(深色品牌视觉区 + 白色登录卡)、输入框前缀图标、 slogan 渐变文字、装饰光晕 - MainLayout 顶栏:品牌 mark + 用户头像 + 角色状态点,按钮带图标 - MainLayout 侧栏:菜单项加图标、active 态渐变+左色条、圆角+外边距 - 全局 token 扩展:brand/text 色阶、border-soft、shadow-card - 列表页工具栏/表格/分页改为卡片式(白底+阴影+圆角) - 表格 hover 行态、按钮 active 轻按下、主按钮带蓝色投影 - 清理 scoped 里冗余的 toolbar/pagination 样式 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
eaf99b5efa
commit
aaf2216b86
|
|
@ -0,0 +1,86 @@
|
||||||
|
<template>
|
||||||
|
<svg
|
||||||
|
:width="size"
|
||||||
|
:height="size"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="1.8"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="base-icon"
|
||||||
|
:aria-label="name"
|
||||||
|
>
|
||||||
|
<path v-for="(d, i) in currentPaths" :key="i" :d="d" />
|
||||||
|
</svg>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
name: { type: String, required: true },
|
||||||
|
size: { type: [Number, String], default: 18 }
|
||||||
|
})
|
||||||
|
|
||||||
|
const ICON_MAP = {
|
||||||
|
user: [
|
||||||
|
'M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2',
|
||||||
|
'M12 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8Z'
|
||||||
|
],
|
||||||
|
factory: [
|
||||||
|
'M3 21V11l6-3v3l6-3v13H3Z',
|
||||||
|
'M15 21h6V12l-6-4'
|
||||||
|
],
|
||||||
|
tag: [
|
||||||
|
'M20.59 13.41 13.41 20.59a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82Z',
|
||||||
|
'M7 7h.01'
|
||||||
|
],
|
||||||
|
box: [
|
||||||
|
'M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z',
|
||||||
|
'M3.3 7 12 12l8.7-5',
|
||||||
|
'M12 22V12'
|
||||||
|
],
|
||||||
|
chart: [
|
||||||
|
'M3 3v18h18',
|
||||||
|
'M7 14v-4',
|
||||||
|
'M11 14v-8',
|
||||||
|
'M15 14v-2',
|
||||||
|
'M19 14V6'
|
||||||
|
],
|
||||||
|
setting: [
|
||||||
|
'M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z',
|
||||||
|
'M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 1 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09a1.65 1.65 0 0 0-1-1.51 1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 1 1-2.83-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09a1.65 1.65 0 0 0 1.51-1 1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 1 1 2.83-2.83l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 1 1 2.83 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1Z'
|
||||||
|
],
|
||||||
|
grid: [
|
||||||
|
'M3 3h7v7H3z',
|
||||||
|
'M14 3h7v7h-7z',
|
||||||
|
'M14 14h7v7h-7z',
|
||||||
|
'M3 14h7v7H3z'
|
||||||
|
],
|
||||||
|
lock: [
|
||||||
|
'M5 11h14v10H5z',
|
||||||
|
'M8 11V7a4 4 0 1 1 8 0v4'
|
||||||
|
],
|
||||||
|
logout: [
|
||||||
|
'M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4',
|
||||||
|
'M16 17l5-5-5-5',
|
||||||
|
'M21 12H9'
|
||||||
|
],
|
||||||
|
key: [
|
||||||
|
'M21 2l-9.5 9.5',
|
||||||
|
'M15.5 7.5l1.5 1.5',
|
||||||
|
'M7 15a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPaths = computed(() => ICON_MAP[props.name] || [])
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.base-icon {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,33 +1,64 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="layout">
|
<div class="layout">
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
|
<div class="brand">
|
||||||
|
<div class="mark">M³</div>
|
||||||
<div class="logo-title">房地产新材料选材管理数据系统</div>
|
<div class="logo-title">房地产新材料选材管理数据系统</div>
|
||||||
|
</div>
|
||||||
<div class="user">
|
<div class="user">
|
||||||
|
<div class="avatar">{{ userInitial }}</div>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<div class="name">{{ user?.username || '用户' }}</div>
|
<div class="name">{{ user?.username || '用户' }}</div>
|
||||||
<div class="role">{{ isAdmin ? '管理员' : '普通账号' }}</div>
|
<div class="role">
|
||||||
|
<span class="role-dot" :class="{ admin: isAdmin }"></span>
|
||||||
|
{{ isAdmin ? '管理员' : '普通账号' }}
|
||||||
</div>
|
</div>
|
||||||
<el-button size="small" @click="openPassword">修改密码</el-button>
|
</div>
|
||||||
<el-button size="small" @click="onLogout">退出</el-button>
|
<el-button size="small" class="topbar-btn" @click="openPassword">
|
||||||
|
<BaseIcon name="key" :size="14" />
|
||||||
|
<span>修改密码</span>
|
||||||
|
</el-button>
|
||||||
|
<el-button size="small" class="topbar-btn" @click="onLogout">
|
||||||
|
<BaseIcon name="logout" :size="14" />
|
||||||
|
<span>退出</span>
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
<aside class="sidebar">
|
<aside class="sidebar">
|
||||||
<el-menu
|
<el-menu :default-active="active" class="menu" router>
|
||||||
:default-active="active"
|
<el-menu-item v-if="isAdmin" index="/users">
|
||||||
class="menu"
|
<BaseIcon name="user" :size="18" />
|
||||||
router
|
<span>用户管理</span>
|
||||||
>
|
</el-menu-item>
|
||||||
<el-menu-item v-if="isAdmin" index="/users">用户管理</el-menu-item>
|
<el-menu-item index="/factories">
|
||||||
<el-menu-item index="/factories">供应商库</el-menu-item>
|
<BaseIcon name="factory" :size="18" />
|
||||||
<el-menu-item v-if="isAdmin" index="/brands">品牌库</el-menu-item>
|
<span>供应商库</span>
|
||||||
<el-menu-item index="/materials">材料管理</el-menu-item>
|
</el-menu-item>
|
||||||
<el-menu-item v-if="isAdmin" index="/screen/overview">数据大屏</el-menu-item>
|
<el-menu-item v-if="isAdmin" index="/brands">
|
||||||
|
<BaseIcon name="tag" :size="18" />
|
||||||
|
<span>品牌库</span>
|
||||||
|
</el-menu-item>
|
||||||
|
<el-menu-item index="/materials">
|
||||||
|
<BaseIcon name="box" :size="18" />
|
||||||
|
<span>材料管理</span>
|
||||||
|
</el-menu-item>
|
||||||
|
<el-menu-item v-if="isAdmin" index="/screen/overview">
|
||||||
|
<BaseIcon name="chart" :size="18" />
|
||||||
|
<span>数据大屏</span>
|
||||||
|
</el-menu-item>
|
||||||
<el-sub-menu v-if="isAdmin" index="config">
|
<el-sub-menu v-if="isAdmin" index="config">
|
||||||
<template #title>配置项</template>
|
<template #title>
|
||||||
<el-menu-item index="/dictionary">材料分类</el-menu-item>
|
<BaseIcon name="setting" :size="18" />
|
||||||
|
<span>配置项</span>
|
||||||
|
</template>
|
||||||
|
<el-menu-item index="/dictionary">
|
||||||
|
<BaseIcon name="grid" :size="16" />
|
||||||
|
<span>材料分类</span>
|
||||||
|
</el-menu-item>
|
||||||
</el-sub-menu>
|
</el-sub-menu>
|
||||||
</el-menu>
|
</el-menu>
|
||||||
|
<div class="sidebar-footer">v1.0 · 2026</div>
|
||||||
</aside>
|
</aside>
|
||||||
<main class="main">
|
<main class="main">
|
||||||
<section class="content">
|
<section class="content">
|
||||||
|
|
@ -62,6 +93,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { useAuth } from '@/store/auth'
|
import { useAuth } from '@/store/auth'
|
||||||
import { changePassword } from '@/api/auth'
|
import { changePassword } from '@/api/auth'
|
||||||
|
import BaseIcon from '@/components/BaseIcon.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
@ -69,6 +101,10 @@ const { state, isAdmin, clearAuth } = useAuth()
|
||||||
|
|
||||||
const active = computed(() => route.path)
|
const active = computed(() => route.path)
|
||||||
const user = computed(() => state.user)
|
const user = computed(() => state.user)
|
||||||
|
const userInitial = computed(() => {
|
||||||
|
const name = state.user?.username || ''
|
||||||
|
return name ? name.charAt(0).toUpperCase() : '?'
|
||||||
|
})
|
||||||
|
|
||||||
const passwordVisible = ref(false)
|
const passwordVisible = ref(false)
|
||||||
const passwordForm = reactive({
|
const passwordForm = reactive({
|
||||||
|
|
@ -105,25 +141,49 @@ const onLogout = () => {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
background: var(--bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.topbar {
|
.topbar {
|
||||||
height: 64px;
|
height: 60px;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
box-shadow: 0 12px 24px rgba(15, 26, 42, 0.08);
|
border-bottom: 1px solid rgba(15, 26, 42, 0.06);
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.brand {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mark {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 9px;
|
||||||
|
background: linear-gradient(135deg, var(--brand-500), #6ea7d8);
|
||||||
|
color: #fff;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
box-shadow: 0 8px 18px rgba(78, 134, 184, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
.logo-title {
|
.logo-title {
|
||||||
font-size: 18px;
|
font-size: 16px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--brand-900, #1b2a41);
|
color: var(--brand-900, #1b2a41);
|
||||||
|
letter-spacing: 0.4px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user {
|
.user {
|
||||||
|
|
@ -132,18 +192,58 @@ const onLogout = () => {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: linear-gradient(135deg, #4e86b8, #7cb4e3);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
box-shadow: 0 6px 14px rgba(78, 134, 184, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
.user-info {
|
.user-info {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
line-height: 1.2;
|
line-height: 1.25;
|
||||||
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info .name {
|
.user-info .name {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-900);
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-info .role {
|
.user-info .role {
|
||||||
font-size: 12px;
|
font-size: 11px;
|
||||||
color: #6b7785;
|
color: var(--text-600);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 4px;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-dot {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #9aa6b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-dot.admin {
|
||||||
|
background: var(--accent-500);
|
||||||
|
box-shadow: 0 0 0 3px rgba(242, 178, 76, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.body {
|
.body {
|
||||||
|
|
@ -159,47 +259,73 @@ const onLogout = () => {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(circle at top left, rgba(124, 180, 227, 0.12), transparent 60%);
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
border-right: none;
|
border-right: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
padding-top: 12px;
|
padding: 16px 10px 0;
|
||||||
|
flex: 1;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu-item) {
|
:deep(.el-menu-item),
|
||||||
|
:deep(.el-sub-menu__title) {
|
||||||
|
color: rgba(255, 255, 255, 0.82);
|
||||||
|
font-size: 14px;
|
||||||
|
height: 46px;
|
||||||
|
line-height: 46px;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0 14px !important;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
transition: background 0.2s, color 0.2s;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-menu-item:hover),
|
||||||
|
:deep(.el-sub-menu__title:hover) {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
transition: all 0.2s ease;
|
background: rgba(255, 255, 255, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu-item.is-active) {
|
:deep(.el-menu-item.is-active) {
|
||||||
color: #7cb4e3;
|
color: #fff;
|
||||||
background-color: rgba(255, 255, 255, 0.12);
|
background: linear-gradient(135deg, rgba(124, 180, 227, 0.25), rgba(78, 134, 184, 0.18));
|
||||||
box-shadow: inset 3px 0 0 #7cb4e3;
|
box-shadow: inset 3px 0 0 #7cb4e3;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-menu-item:hover) {
|
|
||||||
color: #fff;
|
|
||||||
background-color: rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-sub-menu__title) {
|
|
||||||
color: #fff;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-sub-menu__title:hover) {
|
|
||||||
color: #fff;
|
|
||||||
background-color: rgba(255, 255, 255, 0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-sub-menu.is-active > .el-sub-menu__title) {
|
:deep(.el-sub-menu.is-active > .el-sub-menu__title) {
|
||||||
color: #7cb4e3;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-sub-menu .el-menu) {
|
:deep(.el-sub-menu .el-menu) {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-sub-menu .el-menu-item) {
|
||||||
|
padding-left: 40px !important;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-footer {
|
||||||
|
padding: 14px 18px;
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(255, 255, 255, 0.35);
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main {
|
.main {
|
||||||
|
|
|
||||||
|
|
@ -4,17 +4,25 @@
|
||||||
--brand-800: #203754;
|
--brand-800: #203754;
|
||||||
--brand-700: #2f4b6b;
|
--brand-700: #2f4b6b;
|
||||||
--brand-500: #4e86b8;
|
--brand-500: #4e86b8;
|
||||||
|
--brand-400: #6ea7d8;
|
||||||
|
--brand-300: #7cb4e3;
|
||||||
--accent-500: #f2b24c;
|
--accent-500: #f2b24c;
|
||||||
--accent-400: #ffd18a;
|
--accent-400: #ffd18a;
|
||||||
--bg: #f3f5fb;
|
--bg: #f3f5fb;
|
||||||
--bg-soft: #eef2f9;
|
--bg-soft: #eef2f9;
|
||||||
|
--border-soft: rgba(15, 26, 42, 0.06);
|
||||||
--text-900: #0f1a2a;
|
--text-900: #0f1a2a;
|
||||||
--text-600: #5c6b7a;
|
--text-600: #5c6b7a;
|
||||||
|
--text-400: #8b98a7;
|
||||||
--card: #ffffff;
|
--card: #ffffff;
|
||||||
--danger: #d64550;
|
--danger: #d64550;
|
||||||
|
--warning: #e8930f;
|
||||||
--success: #24a26a;
|
--success: #24a26a;
|
||||||
--radius-lg: 14px;
|
--radius-lg: 14px;
|
||||||
--radius-md: 10px;
|
--radius-md: 10px;
|
||||||
|
--radius-sm: 8px;
|
||||||
|
--shadow-card: 0 6px 24px rgba(15, 26, 42, 0.06);
|
||||||
|
--shadow-card-lg: 0 18px 40px rgba(15, 26, 42, 0.08);
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|
@ -54,25 +62,44 @@ a {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: 20px 24px 16px;
|
padding: 20px 24px 16px;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
gap: 14px;
|
||||||
|
|
||||||
.list-page .toolbar {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-page .page-title {
|
.list-page .page-title {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-page .toolbar {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 14px 16px;
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-page .table-wrap {
|
.list-page .table-wrap {
|
||||||
flex: 1 1 auto;
|
flex: 1 1 auto;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
}
|
}
|
||||||
|
|
||||||
.list-page .pagination {
|
.list-page .pagination {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
margin-top: 12px;
|
margin: 0;
|
||||||
|
padding: 10px 16px;
|
||||||
|
background: var(--card);
|
||||||
|
border: 1px solid var(--border-soft);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
|
|
@ -123,6 +150,12 @@ a {
|
||||||
.el-table {
|
.el-table {
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
--el-table-border-color: var(--border-soft);
|
||||||
|
--el-table-header-bg-color: var(--bg-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-table tr:hover > td {
|
||||||
|
background-color: rgba(78, 134, 184, 0.04) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-maxheight {
|
.table-maxheight {
|
||||||
|
|
@ -156,13 +189,28 @@ a {
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-button:active {
|
||||||
|
transform: translateY(1px);
|
||||||
|
}
|
||||||
|
|
||||||
.el-button--primary {
|
.el-button--primary {
|
||||||
background: linear-gradient(135deg, var(--brand-500), #6ea7d8);
|
background: linear-gradient(135deg, var(--brand-500), var(--brand-400));
|
||||||
border: none;
|
border: none;
|
||||||
|
box-shadow: 0 6px 14px rgba(78, 134, 184, 0.25);
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-button--primary:hover {
|
.el-button--primary:hover {
|
||||||
background: linear-gradient(135deg, #3f7bb1, #7db7e7);
|
background: linear-gradient(135deg, #3f7bb1, #7db7e7);
|
||||||
|
box-shadow: 0 8px 18px rgba(78, 134, 184, 0.32);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-pagination {
|
||||||
|
--el-pagination-button-bg-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-dialog {
|
.el-dialog {
|
||||||
|
|
|
||||||
|
|
@ -194,16 +194,6 @@ onMounted(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.toolbar {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
|
||||||
.pagination {
|
|
||||||
margin-top: 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
.dialog-scroll :deep(.el-dialog__body) {
|
.dialog-scroll :deep(.el-dialog__body) {
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
|
||||||
|
|
@ -213,12 +213,6 @@ onMounted(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.pagination {
|
|
||||||
margin-top: 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-scroll :deep(.el-dialog__body) {
|
.dialog-scroll :deep(.el-dialog__body) {
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,80 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="login">
|
<div class="login">
|
||||||
<div class="login-card">
|
<aside class="brand-panel">
|
||||||
<div class="title">房地产新材料选材管理数据系统</div>
|
<div class="brand-top">
|
||||||
<div class="subtitle">登录系统</div>
|
<div class="brand-mark">M³</div>
|
||||||
<el-form :model="form" @submit.prevent>
|
<div class="brand-name">
|
||||||
<el-form-item label="用户名">
|
<div class="brand-title">房地产新材料选材管理</div>
|
||||||
<el-input v-model="form.username" placeholder="请输入用户名" />
|
<div class="brand-sub">Material Management Platform</div>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="密码">
|
|
||||||
<el-input v-model="form.password" type="password" placeholder="请输入密码" show-password />
|
|
||||||
</el-form-item>
|
|
||||||
<el-button type="primary" class="btn" :loading="loading" @click="onLogin">登录</el-button>
|
|
||||||
</el-form>
|
|
||||||
<div class="hint">默认接口地址:/api</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="brand-slogan">
|
||||||
|
<div class="slogan-main">数字化管理材料供应链</div>
|
||||||
|
<div class="slogan-desc">统一的供应商、品牌与材料主数据,让选材决策更高效、更透明。</div>
|
||||||
|
</div>
|
||||||
|
<ul class="brand-highlights">
|
||||||
|
<li>
|
||||||
|
<span class="dot" />
|
||||||
|
<div>
|
||||||
|
<b>供应商库 / 品牌库</b>
|
||||||
|
<p>规范维护材料来源主数据</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="dot" />
|
||||||
|
<div>
|
||||||
|
<b>材料目录 / 审核流程</b>
|
||||||
|
<p>分级填报,规范审批</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<span class="dot" />
|
||||||
|
<div>
|
||||||
|
<b>数据大屏</b>
|
||||||
|
<p>多维度统计一屏掌握</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="orb orb-1" />
|
||||||
|
<div class="orb orb-2" />
|
||||||
|
<div class="grid-bg" />
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section class="form-panel">
|
||||||
|
<div class="login-card">
|
||||||
|
<div class="card-head">
|
||||||
|
<div class="card-title">欢迎登录</div>
|
||||||
|
<div class="card-sub">请输入账号信息进入系统</div>
|
||||||
|
</div>
|
||||||
|
<el-form :model="form" size="large" @submit.prevent>
|
||||||
|
<el-form-item>
|
||||||
|
<el-input v-model="form.username" placeholder="用户名" @keyup.enter="onLogin">
|
||||||
|
<template #prefix>
|
||||||
|
<BaseIcon name="user" :size="16" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-input
|
||||||
|
v-model="form.password"
|
||||||
|
type="password"
|
||||||
|
placeholder="密码"
|
||||||
|
show-password
|
||||||
|
@keyup.enter="onLogin"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<BaseIcon name="lock" :size="16" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-button type="primary" class="submit" :loading="loading" @click="onLogin">
|
||||||
|
登 录
|
||||||
|
</el-button>
|
||||||
|
</el-form>
|
||||||
|
<div class="copyright">© {{ year }} 房地产新材料选材管理数据系统</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
@ -23,14 +83,13 @@ import { useRouter } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { login } from '@/api/auth'
|
import { login } from '@/api/auth'
|
||||||
import { useAuth } from '@/store/auth'
|
import { useAuth } from '@/store/auth'
|
||||||
|
import BaseIcon from '@/components/BaseIcon.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const { setAuth } = useAuth()
|
const { setAuth } = useAuth()
|
||||||
const form = reactive({
|
const form = reactive({ username: '', password: '' })
|
||||||
username: '',
|
|
||||||
password: ''
|
|
||||||
})
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
|
const year = new Date().getFullYear()
|
||||||
|
|
||||||
const onLogin = async () => {
|
const onLogin = async () => {
|
||||||
if (!form.username || !form.password) {
|
if (!form.username || !form.password) {
|
||||||
|
|
@ -54,39 +113,219 @@ const onLogin = async () => {
|
||||||
.login {
|
.login {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
background: #0e1a2a;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-panel {
|
||||||
|
position: relative;
|
||||||
|
flex: 1.1 1 0;
|
||||||
|
padding: 56px 60px 48px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 32px;
|
||||||
|
background: linear-gradient(135deg, #0e1a2a 0%, #1b2a41 45%, #2f4b6b 100%);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-top {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-mark {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: linear-gradient(135deg, #7cb4e3, #4e86b8);
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 800;
|
||||||
|
letter-spacing: 0;
|
||||||
|
box-shadow: 0 12px 24px rgba(78, 134, 184, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-sub {
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.55);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-slogan {
|
||||||
|
margin-top: 28px;
|
||||||
|
z-index: 1;
|
||||||
|
max-width: 440px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slogan-main {
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.3;
|
||||||
|
background: linear-gradient(135deg, #ffffff 0%, #7cb4e3 100%);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
background-clip: text;
|
||||||
|
color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slogan-desc {
|
||||||
|
margin-top: 14px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-highlights {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: auto 0 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 18px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-highlights li {
|
||||||
|
display: flex;
|
||||||
|
gap: 14px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-highlights b {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.brand-highlights p {
|
||||||
|
margin: 2px 0 0;
|
||||||
|
color: rgba(255, 255, 255, 0.55);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background: #7cb4e3;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-top: 6px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow: 0 0 0 4px rgba(124, 180, 227, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
filter: blur(60px);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb-1 {
|
||||||
|
width: 340px;
|
||||||
|
height: 340px;
|
||||||
|
background: radial-gradient(circle, rgba(124, 180, 227, 0.55), transparent 70%);
|
||||||
|
top: -80px;
|
||||||
|
right: -60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.orb-2 {
|
||||||
|
width: 280px;
|
||||||
|
height: 280px;
|
||||||
|
background: radial-gradient(circle, rgba(242, 178, 76, 0.35), transparent 70%);
|
||||||
|
bottom: -80px;
|
||||||
|
left: -60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid-bg {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-image: linear-gradient(rgba(255, 255, 255, 0.04) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
|
||||||
|
background-size: 40px 40px;
|
||||||
|
mask-image: radial-gradient(circle at center, #000 55%, transparent 100%);
|
||||||
|
-webkit-mask-image: radial-gradient(circle at center, #000 55%, transparent 100%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-panel {
|
||||||
|
flex: 1 1 0;
|
||||||
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: linear-gradient(135deg, #1b2a41 0%, #3c6f8f 60%, #7cb4e3 100%);
|
background: #f3f5fb;
|
||||||
|
padding: 40px 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-card {
|
.login-card {
|
||||||
width: 405px;
|
width: 100%;
|
||||||
padding: 32px;
|
max-width: 420px;
|
||||||
border-radius: 16px;
|
padding: 44px 40px;
|
||||||
background: rgba(255, 255, 255, 0.95);
|
border-radius: 20px;
|
||||||
box-shadow: 0 18px 40px rgba(15, 26, 42, 0.2);
|
background: #fff;
|
||||||
border: 1px solid rgba(255, 255, 255, 0.6);
|
box-shadow: 0 24px 60px rgba(15, 26, 42, 0.08);
|
||||||
|
border: 1px solid rgba(15, 26, 42, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.card-title {
|
||||||
font-size: 24px;
|
font-size: 26px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #1b2a41;
|
color: #0f1a2a;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.card-sub {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #5c6b7a;
|
color: #5c6b7a;
|
||||||
margin-bottom: 20px;
|
margin: 8px 0 28px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.submit {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 44px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 4px;
|
||||||
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hint {
|
.copyright {
|
||||||
margin-top: 12px;
|
margin-top: 24px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #9aa6b2;
|
color: #9aa6b2;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #f8f9fc;
|
||||||
|
box-shadow: 0 0 0 1px rgba(15, 26, 42, 0.08) inset;
|
||||||
|
padding-left: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper.is-focus),
|
||||||
|
:deep(.el-input__wrapper:hover) {
|
||||||
|
box-shadow: 0 0 0 1px rgba(78, 134, 184, 0.55) inset;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__prefix) {
|
||||||
|
color: #8b98a7;
|
||||||
|
margin-right: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 960px) {
|
||||||
|
.brand-panel {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -741,12 +741,6 @@ onMounted(() => {
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination {
|
|
||||||
margin-top: 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-scroll :deep(.el-dialog__body) {
|
.dialog-scroll :deep(.el-dialog__body) {
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
|
||||||
|
|
@ -213,12 +213,6 @@ onMounted(() => {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.pagination {
|
|
||||||
margin-top: 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-scroll :deep(.el-dialog__body) {
|
.dialog-scroll :deep(.el-dialog__body) {
|
||||||
max-height: 60vh;
|
max-height: 60vh;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue