之前调整

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

4
.env
View File

@ -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

File diff suppressed because one or more lines are too long

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

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

File diff suppressed because one or more lines are too long

4
dist/index.html vendored
View File

@ -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>

View File

@ -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>

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

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

View File

@ -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;

View File

@ -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>