之前调整

This commit is contained in:
浅念
2026-02-09 11:48:24 +08:00
parent 086cf94ae5
commit 2198380c9f
15 changed files with 674 additions and 150 deletions
+2 -2
View File
@@ -9,8 +9,8 @@ VITE_PORT = 3006
VITE_BASE_URL = / VITE_BASE_URL = /
# API 地址前缀 # API 地址前缀
VITE_API_URL = http://192.168.110.7:25928 # VITE_API_URL = http://219.157.255.213:25832
# VITE_API_URL = https://bsh.yw.server.hnzhwlkj.cn VITE_API_URL = https://bsh.yw.server.hnzhwlkj.cn
# 权限模式( frontend | backend ) # 权限模式( frontend | backend )
VITE_ACCESS_MODE = frontend VITE_ACCESS_MODE = frontend
File diff suppressed because one or more lines are too long
+63
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-63
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -4,8 +4,8 @@
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>数据看板</title> <title>数据看板</title>
<script type="module" crossorigin src="/assets/index-DFqZsSAt.js"></script> <script type="module" crossorigin src="/assets/index-CsXt3cEk.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DagmUA4t.css"> <link rel="stylesheet" crossorigin href="/assets/index-5Q9YfFGs.css">
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
+22
View File
@@ -3,6 +3,28 @@
</template> </template>
<script setup> <script setup>
import { onMounted } from 'vue'
// 检测是否是移动端
function isMobile() {
// 方法1: 检测用户代理
const userAgent = navigator.userAgent || navigator.vendor || window.opera
const mobileRegex = /android|webos|iphone|ipad|ipod|blackberry|iemobile|opera mini/i
const isMobileByUA = mobileRegex.test(userAgent.toLowerCase())
// 方法2: 检测屏幕宽度
const isMobileByWidth = window.innerWidth <= 768
return isMobileByUA || isMobileByWidth
}
// 全局移动端检测和跳转
onMounted(() => {
if (isMobile()) {
// 如果是移动端,跳转到移动端地址
window.location.href = 'https://bsh.yw.m.hnzhwlkj.cn/'
}
})
</script> </script>
<style> <style>
+3
View File
@@ -40,3 +40,6 @@ export function resetPassword(payload){
export function uploadImage(payload){ export function uploadImage(payload){
return request.post('/upload/image', payload) return request.post('/upload/image', payload)
} }
export function getconfig(params){
return request.get('/common/config/find',{params})
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 951 B

After

Width:  |  Height:  |  Size: 1.1 KiB

+496 -62
View File
@@ -20,35 +20,91 @@
<!-- 左侧导航 --> <!-- 左侧导航 -->
<aside :class="['sidebar', { open: sidebarOpen }]"> <aside :class="['sidebar', { open: sidebarOpen }]">
<ul> <ul>
<li <template v-for="(item, index) in menus" :key="item.id">
v-for="(item, index) in menus" <li
:key="item.id" @click.stop="toggleSubMenu(item, index)"
@click="onNav(item.id, index)" :class="{ active: activeMenu === index }"
:class="{ active: activeMenu === index }" class="parent-menu"
> >
<i class="iconfont-sys" v-html="item.icon" style="margin-right: 10px;"></i>{{ item.name }} <span class="menu-content">
</li> <i class="iconfont-sys" v-html="item.icon" style="margin-right: 10px;"></i>
{{ item.name }}
</span>
<i
v-if="item.children && item.children.length > 0"
class="arrow-icon"
:class="{ expanded: expandedMenus.has(item.id) }"
>▼</i>
</li>
<!-- 子菜单 -->
<transition name="submenu">
<ul v-if="item.children && item.children.length > 0 && expandedMenus.has(item.id)" class="submenu">
<li
v-for="children in item.children"
:key="children.id"
@click.stop="onNav(children.id, index, children.id)"
:class="['submenu-item', { active: activeChildId === children.id }]"
>
{{ children.name }}
</li>
</ul>
</transition>
</template>
</ul> </ul>
</aside> </aside>
<div v-if="sidebarOpen" class="drawer-overlay" @click="sidebarOpen = false"></div> <div v-if="sidebarOpen" class="drawer-overlay" @click="sidebarOpen = false"></div>
<!-- 右侧内容 --> <!-- 右侧内容 -->
<main class="content"> <main class="content">
<section v-for="m in menus" :key="m.id" :id="m.id" class="section"> <!-- 看板 banner -->
<h3>{{ m.name }}</h3> <div v-if="bannerImages.length" class="banner">
<div class="title-block"></div> <template v-if="bannerImages.length === 1">
<div class="card-grid"> <img :src="bannerImages[0]" alt="banner" />
<div class="card" v-for="(item, index) in m.cards" :key="index" @click="onCardClick(item)"> </template>
<div class="icon"> <template v-else>
<img src="/src/assets/card-icon.png" alt="icon" /> <el-carousel height="220px" indicator-position="outside" class="banner-carousel">
</div> <el-carousel-item v-for="(img, idx) in bannerImages" :key="`${img}-${idx}`">
<div class="info"> <img :src="img" alt="banner" />
<div class="title">{{ item.title }}</div> </el-carousel-item>
<div class="desc">{{ item.description }}</div> </el-carousel>
</template>
</div>
<template v-for="m in menus" :key="m.id">
<!-- 父级 section -->
<section :id="m.id" class="section" v-if="m.cards && m.cards.length > 0">
<h3>{{ m.name }}</h3>
<div class="title-block"></div>
<div class="card-grid">
<div class="card" v-for="(item, index) in m.cards" :key="index" @click="onCardClick(item)">
<div class="icon">
<img src="/src/assets/card-icon.png" alt="icon" />
</div>
<div class="info">
<div class="title">{{ item.title }}</div>
<div class="desc">{{ item.description }}</div>
</div>
</div> </div>
</div> </div>
</div> </section>
</section> <!-- 子级 section -->
<template v-for="children in m.processedChildren" :key="children.id">
<section :id="children.id" class="section" v-if="children.cards && children.cards.length > 0">
<h3>{{ children.parentName }} / {{ children.name }}</h3>
<div class="title-block"></div>
<div class="card-grid">
<div class="card" v-for="(item, index) in children.cards" :key="index" @click="onCardClick(item)">
<div class="icon">
<img src="/src/assets/card-icon.png" alt="icon" />
</div>
<div class="info">
<div class="title">{{ item.title }}</div>
<div class="desc">{{ item.description }}</div>
</div>
</div>
</div>
</section>
</template>
</template>
</main> </main>
<!-- 修改资料弹窗 --> <!-- 修改资料弹窗 -->
@@ -151,12 +207,45 @@
</div> </div>
</div> </div>
</div> </div>
<!-- 图片预览 -->
<div v-if="imagePreview.visible" class="image-preview-overlay" @click.self="closeImagePreview">
<div class="image-preview-box">
<div class="preview-header">
<span>{{ imagePreview.title }}</span>
<div class="preview-actions">
<button class="preview-btn" @click="downloadCurrentImage" :disabled="!currentPreviewImage">下载图片</button>
<button class="preview-btn" @click="prevImage" :disabled="imagePreview.images.length <= 1">上一张</button>
<button class="preview-btn" @click="nextImage" :disabled="imagePreview.images.length <= 1">下一张</button>
<button class="preview-btn" @click="closeImagePreview">关闭</button>
</div>
</div>
<div class="preview-body">
<img
v-if="currentPreviewImage"
:src="currentPreviewImage"
:alt="imagePreview.title"
/>
</div>
<div class="preview-footer" v-if="imagePreview.images.length > 1">
<div
v-for="(thumb, idx) in imagePreview.images"
:key="thumb"
class="preview-thumb"
:class="{ active: idx === imagePreview.currentIndex }"
@click="imagePreview.currentIndex = idx"
>
<img :src="thumb" :alt="`thumbnail-${idx}`" />
</div>
</div>
</div>
</div>
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted, computed } from "vue";
import { getMenus, getUser, updateUserInfo, resetPassword, uploadImage } from "../api"; import { getMenus, getUser, updateUserInfo, resetPassword, uploadImage,getconfig } from "../api";
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
const menus = ref([]); const menus = ref([]);
const profile = ref({ username: "", password: "" }); const profile = ref({ username: "", password: "" });
@@ -168,20 +257,64 @@ const pwdForm = ref({ password: "", password_new: "", confirm: "" });
const showUserMenu = ref(false); const showUserMenu = ref(false);
const showLogoutConfirm = ref(false); const showLogoutConfirm = ref(false);
const userInfo = ref({}); const userInfo = ref({});
const activeChildId = ref(null);
const bannerImages = ref([]);
const imagePreview = ref({
visible: false,
images: [],
currentIndex: 0,
title: '',
downloadImages: []
});
const currentPreviewImage = computed(() => imagePreview.value.images[imagePreview.value.currentIndex] || '');
const currentDownloadImage = computed(() => imagePreview.value.downloadImages[imagePreview.value.currentIndex] || '');
onMounted(async () => { onMounted(async () => {
try { try {
const { data: bannerData } = await getconfig({ code: 'pc_banner' })
const bannerStr = (bannerData?.value_pull || '').trim();
bannerImages.value = bannerStr
? bannerStr
.split(',')
.map((url) => url.trim())
.filter(Boolean)
: [];
const m = await getMenus(); const m = await getMenus();
const res = m.data; const res = m.data;
// 模拟数据结构:[{ id, name, cards: [{ title, desc }] }] // 处理数据结构,为父级和子级都准备 cards
menus.value = res.map((section) => ({ menus.value = res.map((section) => {
...section, // 处理父级的 nav_item
cards: section.nav_item.map((c) => const parentCards = (section.nav_item || []).map((c) =>
typeof c === "string" typeof c === "string"
? { title: c, description: "" } ? { title: c, description: "" }
: c : c
), );
}));
// 处理子级,为每个子级准备 cards 数据
const processedChildren = (section.children || []).map((children) => {
const childCards = (children.nav_item || []).map((c) =>
typeof c === "string"
? { title: c, description: "" }
: c
);
return {
...children,
cards: childCards,
parentName: section.name, // 保存父级名称,用于显示标题
};
});
return {
...section,
cards: parentCards,
processedChildren: processedChildren, // 保存处理后的子级数据
};
});
// 初始化展开当前激活的菜单项
const activeItem = menus.value[activeMenu.value];
if (activeItem && activeItem.children && activeItem.children.length > 0) {
expandedMenus.value.add(activeItem.id);
}
const {data} = await getUser(); const {data} = await getUser();
userInfo.value = data.user; userInfo.value = data.user;
} catch (e) { } catch (e) {
@@ -191,53 +324,70 @@ onMounted(async () => {
// 下载文件 // 下载文件
async function onCardClick(item){ async function onCardClick(item){
if (!item) return; if (!item) return;
const url = item.url_pull; const url_pull = item.url_pull;
const type = item.type; const type = item.type;
if (!url) return; if (type !== 'image' && !url_pull) return;
if (type === 'link') { if (type === 'link') {
window.open(url, '_blank'); window.open(url_pull, '_blank');
return;
}
if (type === 'image') {
const images = getImageList(item);
const downloadImages = getProxyList(item);
if (!images.length) {
ElMessage.warning('暂无可预览的图片');
return;
}
imagePreview.value = {
visible: true,
images,
currentIndex: 0,
title: item.title || '图片预览',
downloadImages: downloadImages.length ? downloadImages : images
};
return; return;
} }
if (type === 'file') { if (type === 'file') {
window.open(url, '_blank'); try {
// 对所有文件类型都使用 fetch 下载,确保真正的下载
const response = await fetch(url_pull, {
method: 'GET',
headers: {
Accept: '*/*'
}
})
// try { if (!response.ok) {
// // 对所有文件类型都使用 fetch 下载,确保真正的下载 throw new Error(`下载失败: ${response.status} ${response.statusText}`)
// const response = await fetch(url, { }
// method: 'GET', const blob = await response.blob()
// headers: { const url = window.URL.createObjectURL(blob)
// Accept: '*/*' const link = document.createElement('a')
// } link.href = url
// }) link.download = item.title || 'download'
link.style.display = 'none'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
window.URL.revokeObjectURL(url)
// if (!response.ok) { ElMessage.success('下载成功')
// throw new Error(`下载失败: ${response.status} ${response.statusText}`) } catch (error) {
// } console.error('下载失败:', error)
// const blob = await response.blob() const errorMessage = error instanceof Error ? error.message : '未知错误'
// const url = window.URL.createObjectURL(blob) ElMessage.error(`下载失败: ${errorMessage}`)
// const link = document.createElement('a') }
// link.href = url
// link.download = file.name || file.file_name || 'download'
// link.style.display = 'none'
// document.body.appendChild(link)
// link.click()
// document.body.removeChild(link)
// window.URL.revokeObjectURL(url)
// ElMessage.success('下载成功')
// } catch (error) {
// console.error('下载失败:', error)
// const errorMessage = error instanceof Error ? error.message : '未知错误'
// ElMessage.error(`下载失败: ${errorMessage}`)
// }
return; return;
} }
} }
const activeMenu = ref(0); const activeMenu = ref(0);
function onNav(id, index) { const expandedMenus = ref(new Set()); // 用于跟踪展开的菜单项
function onNav(id, index, childId = null) {
activeMenu.value = index; activeMenu.value = index;
activeChildId.value = childId;
const el = document.getElementById(id); const el = document.getElementById(id);
if (el) { if (el) {
const topbarHeight = 56; const topbarHeight = 56;
@@ -248,6 +398,111 @@ function onNav(id, index) {
} }
sidebarOpen.value = false; sidebarOpen.value = false;
} }
// 处理父级菜单点击事件(展开/收起子菜单)
function toggleSubMenu(item, index) {
// 设置选中状态并导航
activeMenu.value = index;
activeChildId.value = null;
const el = document.getElementById(item.id);
if (el) {
const topbarHeight = 56;
const mobileHeight = 44;
const offset = window.innerWidth <= 768 ? mobileHeight : topbarHeight;
const y = el.getBoundingClientRect().top + window.scrollY - offset;
window.scrollTo({ top: y, behavior: "smooth" });
}
sidebarOpen.value = false;
// 如果有子菜单,切换展开/收起状态(手风琴效果:同时只展开一个)
if (item.children && item.children.length > 0) {
if (expandedMenus.value.has(item.id)) {
// 如果当前菜单已展开,则关闭它
expandedMenus.value.delete(item.id);
} else {
// 如果当前菜单未展开,先关闭其他所有已展开的菜单,再展开当前菜单
expandedMenus.value.clear();
expandedMenus.value.add(item.id);
}
}
}
function getImageList(item = {}) {
const candidates = [];
if (Array.isArray(item.url_pull)) candidates.push(...item.url_pull);
if (typeof item.url_pull === 'string') {
candidates.push(...item.url_pull.split(','));
}
const unique = Array.from(
new Set(
candidates
.map((url) => (typeof url === 'string' ? url.trim() : url?.url || ''))
.filter(Boolean)
)
);
return unique;
}
function getProxyList(item = {}) {
const list = item.url_pull_proxy;
if (!list) return [];
if (Array.isArray(list)) {
return list.map((url) => (typeof url === 'string' ? url.trim() : '')).filter(Boolean);
}
if (typeof list === 'string') {
return list
.split(',')
.map((url) => url.trim())
.filter(Boolean);
}
return [];
}
function closeImagePreview() {
imagePreview.value.visible = false;
imagePreview.value.images = [];
imagePreview.value.currentIndex = 0;
imagePreview.value.downloadImages = [];
}
function prevImage() {
if (!imagePreview.value.images.length) return;
imagePreview.value.currentIndex =
(imagePreview.value.currentIndex - 1 + imagePreview.value.images.length) %
imagePreview.value.images.length;
}
function nextImage() {
if (!imagePreview.value.images.length) return;
imagePreview.value.currentIndex =
(imagePreview.value.currentIndex + 1) % imagePreview.value.images.length;
}
async function downloadImage(url, filename = 'image') {
try {
if (!url) throw new Error('无效的下载地址');
const link = document.createElement('a');
link.href = url;
link.download = filename || 'download';
link.target = '_blank';
link.style.display = 'none';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
ElMessage.success('图片已开始下载');
} catch (error) {
console.error('下载图片失败:', error);
ElMessage.error('下载图片失败');
}
}
function downloadCurrentImage() {
const img = currentDownloadImage.value || currentPreviewImage.value;
if (!img) {
ElMessage.warning('暂无可下载图片');
return;
}
downloadImage(img, imagePreview.value.title || 'image');
}
async function uploadAvatar(e){ async function uploadAvatar(e){
const file = e.target.files[0]; const file = e.target.files[0];
@@ -403,7 +658,67 @@ window.addEventListener('click', () => { showUserMenu.value = false; });
color: #009999; color: #009999;
font-weight: 600; font-weight: 600;
} }
.parent-menu {
display: flex;
justify-content: space-between;
align-items: center;
position: relative;
}
.menu-content {
flex: 1;
display: flex;
align-items: center;
}
.arrow-icon {
font-size: 10px;
color: #999;
transition: transform 0.3s ease;
transform: rotate(-90deg);
margin-left: 8px;
}
.arrow-icon.expanded {
transform: rotate(0deg);
}
.submenu {
list-style: none;
padding: 0;
margin: 0;
background: #f8f9fa;
overflow: hidden;
}
.sidebar .submenu-item {
padding: 8px 18px 8px 48px;
cursor: pointer;
color: #666;
font-size: 13px;
transition: all 0.2s;
border-left: 3px solid transparent;
}
.sidebar .submenu-item:hover,
.sidebar .submenu-item.active {
background: #eef6f6;
color: #009999;
border-left-color: #009999;
}
/* 子菜单展开/收起动画 */
.submenu-enter-active,
.submenu-leave-active {
transition: all 0.3s ease;
max-height: 500px;
}
.submenu-enter-from,
.submenu-leave-to {
max-height: 0;
opacity: 0;
}
/* 内容区 */ /* 内容区 */
.content { .content {
margin-left: 220px; margin-left: 220px;
@@ -512,6 +827,106 @@ window.addEventListener('click', () => { showUserMenu.value = false; });
margin-top: 8px; margin-top: 8px;
color: #555; color: #555;
} }
.image-preview-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 250;
padding: 20px;
}
.image-preview-box {
background: #fff;
border-radius: 12px;
width: min(900px, 96vw);
max-height: 90vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
.preview-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 18px;
border-bottom: 1px solid #f0f0f0;
font-weight: 600;
}
.preview-actions {
display: flex;
gap: 8px;
align-items: center;
font-size: 12px;
color: #666;
}
.preview-hint {
margin-right: 12px;
color: #999;
}
.preview-btn {
border: 1px solid #ccc;
background: #fff;
padding: 4px 12px;
border-radius: 4px;
cursor: pointer;
}
.preview-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.preview-body {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
background: #000;
padding: 16px;
}
.preview-body img {
max-width: 100%;
max-height: 70vh;
object-fit: contain;
border-radius: 8px;
}
.preview-footer {
display: flex;
gap: 8px;
padding: 10px 16px;
overflow-x: auto;
background: #fafafa;
}
.preview-thumb {
width: 60px;
height: 60px;
border: 2px solid transparent;
border-radius: 6px;
overflow: hidden;
cursor: pointer;
}
.preview-thumb.active {
border-color: #14C9C9;
}
.preview-thumb img {
width: 100%;
height: 100%;
object-fit: cover;
}
.actions { .actions {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
@@ -519,6 +934,25 @@ window.addEventListener('click', () => { showUserMenu.value = false; });
margin-top: 12px; margin-top: 12px;
} }
.banner {
margin-bottom: 24px;
border-radius: 12px;
overflow: hidden;
background: #fff;
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06);
}
.banner img {
width: 100%;
height: 220px;
object-fit: cover;
display: block;
}
:deep(.banner-carousel .el-carousel__container) {
height: 220px !important;
}
/* 移动端适配 */ /* 移动端适配 */
.drawer-overlay { .drawer-overlay {
display: none; display: none;
+84 -19
View File
@@ -1,41 +1,60 @@
<template> <template>
<div class="login-page"> <div class="login-page">
<div class="login-left"></div> <div class="login-left">
<div>
<img src="/src/assets/logopc.png" alt="logo" style="height: 24px;"/>
<p class="titles">豫皖大区</p>
<p class="titles">西门子家电<span>数据看板</span></p>
<p class="titles1">YUWAN REGION DATA DASHBOARD FOR SIEMENS HOME APPLIANCES</p>
</div>
</div>
<div class="login-right"> <div class="login-right">
<div class="login-card"> <div class="login-card">
<div class="corner-qrcode" @click="tabFlip"> <div
class="corner-qrcode"
@click="tabFlip"
:style="{ backgroundImage: `url(/src/assets/${tab === 'account' ? 'qrcode.png' : 'macgroup.png'})` }"
>
</div> </div>
<h2 class="title">{{ tab==='account' ? '账号登录' : '扫码登录' }}</h2> <h2 class="title">{{ tab==='account' ? '欢迎登录' : '扫码登录' }}</h2>
<div v-if="tab==='account'" class="login-form"> <div v-if="tab==='account'" class="login-form">
<el-form <el-form
ref="loginFormRef" ref="loginFormRef"
:model="loginForm" :model="loginForm"
:rules="loginFormRules" :rules="loginFormRules"
label-width="60px"
label-position="left" label-position="left"
> >
<el-form-item label="账号" prop="account"> <el-form-item prop="account">
<el-input <el-input
v-model="loginForm.account" v-model="loginForm.account"
placeholder="请输入账号" placeholder="请输入账号"
clearable clearable
prefix-icon="User"
maxlength="20" maxlength="20"
/> >
<template #prefix>
<span class="input-label">账号</span>
</template>
</el-input>
</el-form-item> </el-form-item>
<el-form-item label="密码" prop="password"> <el-form-item prop="password">
<el-input <el-input
v-model="loginForm.password" v-model="loginForm.password"
type="password" type="password"
placeholder="请输入密码" placeholder="请输入密码"
show-password show-password
clearable clearable
prefix-icon="Lock"
maxlength="20" maxlength="20"
/> >
<template #prefix>
<span class="input-label">密码</span>
</template>
</el-input>
</el-form-item> </el-form-item>
<div class="remember-account">
<el-checkbox v-model="rememberAccount">保持登录</el-checkbox>
</div>
<el-form-item> <el-form-item>
<el-button <el-button
@@ -72,7 +91,7 @@
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { apiLogin, apiqrcode } from '../api' import { apiLogin, apiqrcode,getconfig } from '../api'
import { isLoggedIn, saveLoginData, handleAuthRedirect } from '../utils/auth' import { isLoggedIn, saveLoginData, handleAuthRedirect } from '../utils/auth'
const router = useRouter() const router = useRouter()
const tab = ref('account') const tab = ref('account')
@@ -95,9 +114,22 @@ const loginFormRules = {
{ min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' } { min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }
] ]
} }
//获取登录背景图
const getBackgroundImage = async () => {
const res = await getconfig({ code: 'pc_loginbg' })
if (res.data.value_pull) {
const loginElement = document.getElementsByClassName('login-page')[0]
const bannerStr = res.data.value_pull.trim();
if (bannerStr && loginElement) {
// 按逗号分隔,过滤空字符串
loginElement.style.backgroundImage = `url(${bannerStr.split(',').map(url => url.trim()).filter(url => url)[0]})`
}
}
}
// 页面加载时检查登录状态 // 页面加载时检查登录状态
onMounted(() => { onMounted(() => {
getBackgroundImage()
if (isLoggedIn()) { if (isLoggedIn()) {
ElMessage.info('您已登录,正在跳转到首页...') ElMessage.info('您已登录,正在跳转到首页...')
router.replace('/dashboard') router.replace('/dashboard')
@@ -201,20 +233,37 @@ async function onScanLogin(){
/* left large image-ish area (you can replace with your image) */ /* left large image-ish area (you can replace with your image) */
.login-left{ .login-left{
flex:1; flex:1;
margin-left: 10%;
margin-top: -14%;
}
.titles {
font-size: 44px;
color: white;
font-weight: bold;
margin-top: 30px;
}
.titles span{
color:#009688 ;
}
.titles1{
font-size: 16px;
color: white;
margin-top: 30px;
} }
/* right card */ /* right card */
.login-right{ .login-right{
width:520px; width:600px;
display:flex; display:flex;
align-items:center; align-items:center;
justify-content:center; justify-content:center;
padding-right:6%; padding-right:10%;
} }
.login-card{ .login-card{
width:420px; width:420px;
background:#fff; background: rgb(0 10 34 / 50%);
border-radius:12px; border-radius:12px;
box-shadow: 0 10px 30px rgba(14,42,51,0.08); /* box-shadow: 0 10px 30px rgba(14,42,51,0.08); */
position:relative; position:relative;
} }
.login-form { .login-form {
@@ -227,6 +276,11 @@ async function onScanLogin(){
.login-form .el-input { .login-form .el-input {
width: 100%; width: 100%;
height: 55px;
font-size: 16px;
}
.input-label{
color: #333;
} }
.corner-qrcode{ .corner-qrcode{
position:absolute; position:absolute;
@@ -237,14 +291,16 @@ async function onScanLogin(){
font-size:12px; font-size:12px;
color:#009688; color:#009688;
cursor:pointer; cursor:pointer;
background: url('/src/assets/qrcode.png') no-repeat center; background-size: contain;
background-repeat: no-repeat;
background-position: center;
} }
/* title */ /* title */
.title{ .title{
text-align:center; text-align:center;
margin:60 0 12px; margin:60 0 12px;
font-size:20px; font-size:32px;
color:#333; color:#fff;
font-weight:700; font-weight:700;
} }
/* tabs */ /* tabs */
@@ -255,7 +311,13 @@ async function onScanLogin(){
/* form */ /* form */
.form label{ display:block; margin-top:8px; color:#666; font-size:13px;} .form label{ display:block; margin-top:8px; color:#666; font-size:13px;}
.form input{ width:100%; padding:10px; margin-top:6px; border-radius:6px; border:1px solid #eee; background:#fafafa;} .form input{ width:100%; padding:10px; margin-top:6px; border-radius:6px; border:1px solid #eee; background:#fafafa;}
.login-btn{ width:100%; padding:12px; margin-top:60px; border-radius:16px; border:none; background:#009688; color:#fff; cursor:pointer; font-size:16px;} .login-btn{ width:100%; padding:28px; margin-top:60px; border-radius:30px; border:none; background:#009688; color:#fff; cursor:pointer; font-size:20px;}
.login-btn:hover {
background-color:#009688;
border-color: #009688;
color: #fff;
outline: none;
}
.form-row { .form-row {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -313,4 +375,7 @@ async function onScanLogin(){
height: 400px; height: 400px;
} }
} }
:deep(.el-checkbox__label) {
color: #fff;
}
</style> </style>