之前调整
This commit is contained in:
parent
086cf94ae5
commit
2198380c9f
4
.env
4
.env
@ -9,8 +9,8 @@ VITE_PORT = 3006
|
||||
VITE_BASE_URL = /
|
||||
|
||||
# API 地址前缀
|
||||
VITE_API_URL = http://192.168.110.7:25928
|
||||
# VITE_API_URL = https://bsh.yw.server.hnzhwlkj.cn
|
||||
# VITE_API_URL = http://219.157.255.213:25832
|
||||
VITE_API_URL = https://bsh.yw.server.hnzhwlkj.cn
|
||||
|
||||
# 权限模式( frontend | backend )
|
||||
VITE_ACCESS_MODE = frontend
|
||||
|
||||
1
dist/assets/index-5Q9YfFGs.css
vendored
Normal file
1
dist/assets/index-5Q9YfFGs.css
vendored
Normal file
File diff suppressed because one or more lines are too long
63
dist/assets/index-CsXt3cEk.js
vendored
Normal file
63
dist/assets/index-CsXt3cEk.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/assets/index-CsXt3cEk.js.map
vendored
Normal file
1
dist/assets/index-CsXt3cEk.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
63
dist/assets/index-DFqZsSAt.js
vendored
63
dist/assets/index-DFqZsSAt.js
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-DFqZsSAt.js.map
vendored
1
dist/assets/index-DFqZsSAt.js.map
vendored
File diff suppressed because one or more lines are too long
1
dist/assets/index-DagmUA4t.css
vendored
1
dist/assets/index-DagmUA4t.css
vendored
File diff suppressed because one or more lines are too long
4
dist/index.html
vendored
4
dist/index.html
vendored
@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||
<title>数据看板</title>
|
||||
<script type="module" crossorigin src="/assets/index-DFqZsSAt.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DagmUA4t.css">
|
||||
<script type="module" crossorigin src="/assets/index-CsXt3cEk.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-5Q9YfFGs.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
22
src/App.vue
22
src/App.vue
@ -3,6 +3,28 @@
|
||||
</template>
|
||||
|
||||
<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>
|
||||
|
||||
<style>
|
||||
|
||||
@ -39,4 +39,7 @@ export function resetPassword(payload){
|
||||
//图片上传
|
||||
export function uploadImage(payload){
|
||||
return request.post('/upload/image', payload)
|
||||
}
|
||||
export function getconfig(params){
|
||||
return request.get('/common/config/find',{params})
|
||||
}
|
||||
BIN
src/assets/logopc.png
Normal file
BIN
src/assets/logopc.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
src/assets/macgroup.png
Normal file
BIN
src/assets/macgroup.png
Normal file
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 |
@ -20,35 +20,91 @@
|
||||
<!-- 左侧导航 -->
|
||||
<aside :class="['sidebar', { open: sidebarOpen }]">
|
||||
<ul>
|
||||
<li
|
||||
v-for="(item, index) in menus"
|
||||
:key="item.id"
|
||||
@click="onNav(item.id, index)"
|
||||
:class="{ active: activeMenu === index }"
|
||||
>
|
||||
<i class="iconfont-sys" v-html="item.icon" style="margin-right: 10px;"></i>{{ item.name }}
|
||||
</li>
|
||||
<template v-for="(item, index) in menus" :key="item.id">
|
||||
<li
|
||||
@click.stop="toggleSubMenu(item, index)"
|
||||
:class="{ active: activeMenu === index }"
|
||||
class="parent-menu"
|
||||
>
|
||||
<span class="menu-content">
|
||||
<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>
|
||||
</aside>
|
||||
<div v-if="sidebarOpen" class="drawer-overlay" @click="sidebarOpen = false"></div>
|
||||
|
||||
<!-- 右侧内容 -->
|
||||
<main class="content">
|
||||
<section v-for="m in menus" :key="m.id" :id="m.id" class="section">
|
||||
<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>
|
||||
<!-- 看板 banner -->
|
||||
<div v-if="bannerImages.length" class="banner">
|
||||
<template v-if="bannerImages.length === 1">
|
||||
<img :src="bannerImages[0]" alt="banner" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-carousel height="220px" indicator-position="outside" class="banner-carousel">
|
||||
<el-carousel-item v-for="(img, idx) in bannerImages" :key="`${img}-${idx}`">
|
||||
<img :src="img" alt="banner" />
|
||||
</el-carousel-item>
|
||||
</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>
|
||||
</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>
|
||||
|
||||
<!-- 修改资料弹窗 -->
|
||||
@ -151,12 +207,45 @@
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { getMenus, getUser, updateUserInfo, resetPassword, uploadImage } from "../api";
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import { getMenus, getUser, updateUserInfo, resetPassword, uploadImage,getconfig } from "../api";
|
||||
import { ElMessage } from 'element-plus'
|
||||
const menus = ref([]);
|
||||
const profile = ref({ username: "", password: "" });
|
||||
@ -168,20 +257,64 @@ const pwdForm = ref({ password: "", password_new: "", confirm: "" });
|
||||
const showUserMenu = ref(false);
|
||||
const showLogoutConfirm = ref(false);
|
||||
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 () => {
|
||||
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 res = m.data;
|
||||
// 模拟数据结构:[{ id, name, cards: [{ title, desc }] }]
|
||||
menus.value = res.map((section) => ({
|
||||
...section,
|
||||
cards: section.nav_item.map((c) =>
|
||||
// 处理数据结构,为父级和子级都准备 cards
|
||||
menus.value = res.map((section) => {
|
||||
// 处理父级的 nav_item
|
||||
const parentCards = (section.nav_item || []).map((c) =>
|
||||
typeof c === "string"
|
||||
? { title: c, description: "" }
|
||||
: 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();
|
||||
userInfo.value = data.user;
|
||||
} catch (e) {
|
||||
@ -191,53 +324,70 @@ onMounted(async () => {
|
||||
// 下载文件
|
||||
async function onCardClick(item){
|
||||
if (!item) return;
|
||||
const url = item.url_pull;
|
||||
const url_pull = item.url_pull;
|
||||
const type = item.type;
|
||||
if (!url) return;
|
||||
if (type !== 'image' && !url_pull) return;
|
||||
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;
|
||||
}
|
||||
|
||||
if (type === 'file') {
|
||||
window.open(url, '_blank');
|
||||
|
||||
// try {
|
||||
// // 对所有文件类型都使用 fetch 下载,确保真正的下载
|
||||
// const response = await fetch(url, {
|
||||
// method: 'GET',
|
||||
// headers: {
|
||||
// Accept: '*/*'
|
||||
// }
|
||||
// })
|
||||
try {
|
||||
// 对所有文件类型都使用 fetch 下载,确保真正的下载
|
||||
const response = await fetch(url_pull, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
Accept: '*/*'
|
||||
}
|
||||
})
|
||||
|
||||
// if (!response.ok) {
|
||||
// throw new Error(`下载失败: ${response.status} ${response.statusText}`)
|
||||
// }
|
||||
// const blob = await response.blob()
|
||||
// const url = window.URL.createObjectURL(blob)
|
||||
// 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)
|
||||
if (!response.ok) {
|
||||
throw new Error(`下载失败: ${response.status} ${response.statusText}`)
|
||||
}
|
||||
const blob = await response.blob()
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
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)
|
||||
|
||||
// ElMessage.success('下载成功')
|
||||
// } catch (error) {
|
||||
// console.error('下载失败:', error)
|
||||
// const errorMessage = error instanceof Error ? error.message : '未知错误'
|
||||
// ElMessage.error(`下载失败: ${errorMessage}`)
|
||||
// }
|
||||
ElMessage.success('下载成功')
|
||||
} catch (error) {
|
||||
console.error('下载失败:', error)
|
||||
const errorMessage = error instanceof Error ? error.message : '未知错误'
|
||||
ElMessage.error(`下载失败: ${errorMessage}`)
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const activeMenu = ref(0);
|
||||
function onNav(id, index) {
|
||||
const expandedMenus = ref(new Set()); // 用于跟踪展开的菜单项
|
||||
function onNav(id, index, childId = null) {
|
||||
activeMenu.value = index;
|
||||
activeChildId.value = childId;
|
||||
const el = document.getElementById(id);
|
||||
if (el) {
|
||||
const topbarHeight = 56;
|
||||
@ -248,6 +398,111 @@ function onNav(id, index) {
|
||||
}
|
||||
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){
|
||||
const file = e.target.files[0];
|
||||
@ -403,7 +658,67 @@ window.addEventListener('click', () => { showUserMenu.value = false; });
|
||||
color: #009999;
|
||||
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 {
|
||||
margin-left: 220px;
|
||||
@ -512,6 +827,106 @@ window.addEventListener('click', () => { showUserMenu.value = false; });
|
||||
margin-top: 8px;
|
||||
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 {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@ -519,6 +934,25 @@ window.addEventListener('click', () => { showUserMenu.value = false; });
|
||||
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 {
|
||||
display: none;
|
||||
|
||||
@ -1,41 +1,60 @@
|
||||
<template>
|
||||
<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-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>
|
||||
<h2 class="title">{{ tab==='account' ? '账号登录' : '扫码登录' }}</h2>
|
||||
<h2 class="title">{{ tab==='account' ? '欢迎登录' : '扫码登录' }}</h2>
|
||||
<div v-if="tab==='account'" class="login-form">
|
||||
<el-form
|
||||
ref="loginFormRef"
|
||||
:model="loginForm"
|
||||
:rules="loginFormRules"
|
||||
label-width="60px"
|
||||
label-position="left"
|
||||
>
|
||||
<el-form-item label="账号" prop="account">
|
||||
<el-form-item prop="account">
|
||||
<el-input
|
||||
v-model="loginForm.account"
|
||||
placeholder="请输入账号"
|
||||
clearable
|
||||
prefix-icon="User"
|
||||
maxlength="20"
|
||||
/>
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="input-label">账号</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="密码" prop="password">
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="loginForm.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
show-password
|
||||
clearable
|
||||
prefix-icon="Lock"
|
||||
maxlength="20"
|
||||
/>
|
||||
>
|
||||
<template #prefix>
|
||||
<span class="input-label">密码</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<div class="remember-account">
|
||||
<el-checkbox v-model="rememberAccount">保持登录</el-checkbox>
|
||||
</div>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
@ -72,7 +91,7 @@
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { apiLogin, apiqrcode } from '../api'
|
||||
import { apiLogin, apiqrcode,getconfig } from '../api'
|
||||
import { isLoggedIn, saveLoginData, handleAuthRedirect } from '../utils/auth'
|
||||
const router = useRouter()
|
||||
const tab = ref('account')
|
||||
@ -95,9 +114,22 @@ const loginFormRules = {
|
||||
{ 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(() => {
|
||||
getBackgroundImage()
|
||||
if (isLoggedIn()) {
|
||||
ElMessage.info('您已登录,正在跳转到首页...')
|
||||
router.replace('/dashboard')
|
||||
@ -201,20 +233,37 @@ async function onScanLogin(){
|
||||
/* left large image-ish area (you can replace with your image) */
|
||||
.login-left{
|
||||
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 */
|
||||
.login-right{
|
||||
width:520px;
|
||||
width:600px;
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
padding-right:6%;
|
||||
padding-right:10%;
|
||||
}
|
||||
.login-card{
|
||||
width:420px;
|
||||
background:#fff;
|
||||
background: rgb(0 10 34 / 50%);
|
||||
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;
|
||||
}
|
||||
.login-form {
|
||||
@ -227,6 +276,11 @@ async function onScanLogin(){
|
||||
|
||||
.login-form .el-input {
|
||||
width: 100%;
|
||||
height: 55px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.input-label{
|
||||
color: #333;
|
||||
}
|
||||
.corner-qrcode{
|
||||
position:absolute;
|
||||
@ -237,14 +291,16 @@ async function onScanLogin(){
|
||||
font-size:12px;
|
||||
color:#009688;
|
||||
cursor:pointer;
|
||||
background: url('/src/assets/qrcode.png') no-repeat center;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
/* title */
|
||||
.title{
|
||||
text-align:center;
|
||||
margin:60 0 12px;
|
||||
font-size:20px;
|
||||
color:#333;
|
||||
font-size:32px;
|
||||
color:#fff;
|
||||
font-weight:700;
|
||||
}
|
||||
/* tabs */
|
||||
@ -255,7 +311,13 @@ async function onScanLogin(){
|
||||
/* form */
|
||||
.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;}
|
||||
.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 {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -313,4 +375,7 @@ async function onScanLogin(){
|
||||
height: 400px;
|
||||
}
|
||||
}
|
||||
:deep(.el-checkbox__label) {
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user