优化验证码框 滑动正常,滑动定位异常

pull/751/head
huppygo 2025-03-24 23:02:51 +08:00
parent c6e9644882
commit 80ec9b7e15
7 changed files with 319 additions and 15125 deletions

View File

@ -50,7 +50,7 @@
}
},
"__VTJ_BLOCK__": true,
"__VERSION__": "1736401534792",
"__VERSION__": "1742828337536",
"id": "7pdk4mxyf",
"nodes": [
{
@ -308,6 +308,17 @@
},
"directives": [],
"events": {}
},
{
"id": "13ajd7cw",
"name": "div",
"from": "",
"invisible": false,
"locked": false,
"children": "容器文本内容示例",
"props": {},
"directives": [],
"events": {}
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,7 @@
{
"__VTJ_PROJECT__": true,
"id": "vue-element-plus-admin",
"platform": "web",
"name": "ElementAdmin",
"homepage": "",
"description": "一套基于vue3、element-plus、typesScript、vite4的后台集成方案。",
@ -9,17 +10,25 @@
"package": "vue",
"version": "latest",
"library": "Vue",
"urls": ["@vtj/materials/deps/vue/vue.global.prod.js"],
"urls": [
"@vtj/materials/deps/vue/vue.global.prod.js"
],
"assetsLibrary": "VueMaterial",
"required": true,
"official": true,
"enabled": true
"enabled": true,
"platform": [
"web",
"h5"
]
},
{
"package": "vue-router",
"version": "latest",
"library": "VueRouter",
"urls": ["@vtj/materials/deps/vue-router/vue-router.global.prod.js"],
"urls": [
"@vtj/materials/deps/vue-router/vue-router.global.prod.js"
],
"assetsLibrary": "VueRouterMaterial",
"required": true,
"official": true,
@ -29,7 +38,9 @@
"package": "@vtj/utils",
"version": "latest",
"library": "VtjUtils",
"urls": ["@vtj/materials/deps/@vtj/utils/index.umd.js"],
"urls": [
"@vtj/materials/deps/@vtj/utils/index.umd.js"
],
"required": true,
"official": true,
"enabled": true
@ -44,7 +55,11 @@
],
"required": true,
"official": true,
"enabled": true
"enabled": true,
"platform": [
"web",
"h5"
]
},
{
"package": "@vueuse/core",
@ -56,7 +71,11 @@
],
"required": false,
"official": true,
"enabled": true
"enabled": true,
"platform": [
"web",
"h5"
]
},
{
"package": "element-plus",
@ -73,7 +92,8 @@
"assetsLibrary": "ElementPlusMaterial",
"required": false,
"official": true,
"enabled": true
"enabled": true,
"platform": "web"
},
{
"package": "@vtj/ui",
@ -90,21 +110,8 @@
"assetsLibrary": "VtjUIMaterial",
"required": false,
"official": true,
"enabled": true
},
{
"package": "@vtj/charts",
"version": "latest",
"library": "VtjCharts",
"urls": [
"@vtj/materials/deps/echarts/echarts.min.js",
"@vtj/materials/deps/@vtj/charts/index.umd.js"
],
"assetsUrl": "@vtj/materials/assets/charts/index.umd.js",
"assetsLibrary": "VtjChartsMaterial",
"required": false,
"official": true,
"enabled": true
"enabled": true,
"platform": "web"
},
{
"package": "ant-design-vue",
@ -126,13 +133,48 @@
"assetsLibrary": "AntdvMaterial",
"required": false,
"official": true,
"enabled": false
"enabled": false,
"platform": [
"web"
]
},
{
"package": "@vtj/charts",
"version": "latest",
"library": "VtjCharts",
"urls": [
"@vtj/materials/deps/echarts/echarts.min.js",
"@vtj/materials/deps/@vtj/charts/index.umd.js"
],
"assetsUrl": "@vtj/materials/assets/charts/index.umd.js",
"assetsLibrary": "VtjChartsMaterial",
"required": false,
"official": true,
"enabled": true,
"platform": [
"web",
"h5"
]
},
{
"package": "mockjs",
"version": "latest",
"library": "Mock",
"urls": [
"@vtj/materials/deps/mockjs/mock-min.js"
],
"required": false,
"official": true,
"enabled": true
},
{
"package": "vant",
"version": "latest",
"library": "vant",
"urls": ["@vtj/materials/deps/vant/index.css", "@vtj/materials/deps/vant/vant.min.js"],
"urls": [
"@vtj/materials/deps/vant/index.css",
"@vtj/materials/deps/vant/vant.min.js"
],
"assetsUrl": "@vtj/materials/assets/vant/index.umd.js",
"assetsLibrary": "VantMaterial",
"required": false,
@ -174,6 +216,9 @@
"blocks": [],
"apis": [],
"meta": [],
"config": {},
"config": {
"title": ""
},
"uniConfig": {},
"__BASE_PATH__": "/"
}

View File

@ -1,3 +1,29 @@
[English](./README.md) | **中文**
## 介绍
vue-element-plus-admin 是一个基于 `element-plus` 免费开源的中后台模版。使用了最新的`vue3``vite``TypeScript`等主流技术开发,开箱即用的中后台前端解决方案,可以用来作为项目的启动模版,也可用于学习参考。并且时刻关注着最新技术动向,尽可能的第一时间更新。
vue-element-plus-admin 的定位是后台集成方案,不太适合当基础模板来进行二次开发。因为集成了很多你可能用不到的功能,会造成不少的代码冗余。如果你的项目不关注这方面的问题,也可以直接基于它进行二次开发。
## 特性
- **最新技术栈**:使用 Vue3/vite4 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**: 可配置的主题
- **国际化**:内置完善的国际化方案
- **自定义数据** 内置 Mock 数据方案
- **权限** 内置完善的动态路由权限生成方案
- **组件** 二次封装了多个常用的组件
- **示例** 内置丰富的示例
## 文档
获取代码
git clone
安装依赖

View File

@ -1,9 +1,4 @@
<div align="center"> <a href="https://github.com/kailong321200875/vue-element-plus-admin"> <img width="100" src="./public/logo.png"> </a> <br> <br>
[![license](https://img.shields.io/github/license/kailong321200875/vue-element-plus-admin.svg)](LICENSE) [![repo-size](https://img.shields.io/github/repo-size/kailong321200875/vue-element-plus-admin.svg)](repo-size) [![last-commit](https://img.shields.io/github/last-commit/kailong321200875/vue-element-plus-admin.svg)](last-commit) [![stars](https://img.shields.io/github/stars/kailong321200875/vue-element-plus-admin.svg)](stars) [![forks](https://img.shields.io/github/forks/kailong321200875/vue-element-plus-admin.svg)](forks) [![release](https://img.shields.io/github/release/kailong321200875/vue-element-plus-admin.svg)](release) [![watchers](https://img.shields.io/github/watchers/kailong321200875/vue-element-plus-admin.svg)](watchers)
<h1>vue-element-plus-admin</h1>
</div>
[English](./README.md) | **中文**
@ -13,7 +8,7 @@ vue-element-plus-admin 是一个基于 `element-plus` 免费开源的中后台
vue-element-plus-admin 的定位是后台集成方案,不太适合当基础模板来进行二次开发。因为集成了很多你可能用不到的功能,会造成不少的代码冗余。如果你的项目不关注这方面的问题,也可以直接基于它进行二次开发。
如需要基础模版,请切换到 `mini` 分支,`mini` 只简单集成了一些如:布局、动态菜单等常用布局功能,更适合开发者进行二次开发。
## 特性
@ -26,123 +21,28 @@ vue-element-plus-admin 的定位是后台集成方案,不太适合当基础模
- **组件** 二次封装了多个常用的组件
- **示例** 内置丰富的示例
## 预览
- [vue-element-plus-admin](https://element-plus-admin.cn/) - 完整版 github 站点
- [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - 完整版 gitee 站点
帐号:**admin/admin**
在线例子默认不适用菜单过滤,而是直接使用静态路由表
## 文档
[文档地址 Github](https://element-plus-admin-doc.cn/)
[文档地址 Gitee](https://kailong110120130.gitee.io/vue-element-plus-admin-doc)
## 前序准备
- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) - 项目开发环境
- [Vite](https://vitejs.dev/) - 熟悉 vite 特性
- [Vue3](https://v3.vuejs.org/) - 熟悉 Vue 基础语法
- [TypeScript](https://www.typescriptlang.org/) - 熟悉 `TypeScript` 基本语法
- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
- [Element-Plus](https://element-plus.org/) - element-plus 基本使用
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
## 安装和使用
- 获取代码
```bash
git clone https://github.com/kailong321200875/vue-element-plus-admin.git
```
- 安装依赖
```bash
获取代码
git clone
安装依赖
cd vue-element-plus-admin
pnpm config set registry https://registry.npmmirror.com/
pnpm install
```
- 运行
```bash
运行
pnpm run dev
```
- 打包
```bash
打包
pnpm run build:pro
```
## 更新日志
问题
1.集成到本地项目 本地启动运行可以看到设计器入口
设计器是在开发环境提供辅助开发服务的,不是在线的
在线参考 https://gitee.com/newgateway/vtj/tree/master/lcdp
[更新日志](./CHANGELOG.md)
npm install -g lerna@latest pnpm@latest --registry=https://registry.npmmirror.com
## 如何贡献
<a href="https://github.com/kailong321200875/vue-element-plus-admin/graphs/contributors">
<img src="https://contrib.rocks/image?repo=kailong321200875/vue-element-plus-admin" />
</a>
你可以[提一个 issue](https://github.com/kailong321200875/vue-element-plus-admin/issues/new) 或者提交一个 Pull Request。
**Pull Request:**
1. Fork 代码
2. 创建自己的分支: `git checkout -b feat/xxxx`
3. 提交你的修改: `git commit -am 'feat(function): add xxxxx'`
4. 推送您的分支: `git push origin feat/xxxx`
5. 提交 `pull request`
## Git 贡献提交规范
- `feat` 新功能
- `fix` 修补 bug
- `docs` 文档
- `style` 格式、样式(不影响代码运行的变动)
- `refactor` 重构(即不是新增功能,也不是修改 BUG 的代码)
- `perf` 优化相关,比如提升性能、体验
- `test` 添加测试
- `build` 编译相关的修改,对项目构建或者依赖的改动
- `ci` 持续集成修改
- `chore` 构建过程或辅助工具的变动
- `revert` 回滚到上一个版本
- `workflow` 工作流改进
- `mod` 不确定分类的修改
- `wip` 开发中
- `types` 类型
## 浏览器支持
本地开发推荐使用 `Chrome 80+` 浏览器
支持现代浏览器, 不支持 IE
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## Donate
如果你觉得这个项目有帮助,欢迎赞助以示支持~
[Paypal Me](https://www.paypal.com/paypalme/ckl94)
<img src="https://gitee.com/kailong110120130/my-image/raw/master/pay.jpg" />
### 我的二维码
如有项目合作或项目外包,扫码加我好友,请备注来意。
<img src="https://gitee.com/kailong110120130/my-image/raw/master/me.jpg" />
## 许可证
[MIT](./LICENSE)
2.关于下载依赖报错error (ERR_INVALID_THIS)
pnpm升级到 8.3.1
npm install -g pnpm@8.3.1

View File

@ -160,8 +160,10 @@ const show = () => {
instance.value.init()
} else {
console.error('验证码组件实例不存在或没有init方法')
//
refresh()
}
}, 500)
}, 100) //
})
}
}

View File

@ -1,11 +1,17 @@
<template>
<div class="verify-slide">
<!-- 验证码图片面板 -->
<div class="verify-img-panel" :style="{ width: imgSize.width, height: imgSize.height }">
<!-- 背景图片 -->
<img v-if="backImgBase" :src="backImgBase" alt="背景图" class="back-img" />
<div v-else class="verify-loading">加载中...</div>
<!-- 刷新按钮 -->
<div class="verify-refresh" @click="refresh">
<i class="iconfont icon-refresh"></i>
</div>
<!-- 缺口 -->
<div
class="verify-gap"
:style="{
@ -16,12 +22,15 @@
}"
v-if="backImgBase"
></div>
<!-- 拼图块 -->
<div
class="verify-sub-block"
:style="{
width: blockSize.width + 'px',
height: blockSize.height + 'px',
top: top + 'px',
left: moveBlockLeft + 'px',
background: `url(${blockImgBase})`,
backgroundSize: `${imgSize.width} ${imgSize.height}`,
backgroundPosition: `-${left}px -${top}px`
@ -29,44 +38,44 @@
v-if="blockImgBase"
></div>
</div>
<!-- 滑动条区域 -->
<div
class="verify-bar-area"
:style="{ width: imgSize.width, height: barSize.height }"
@mouseover="showTips"
@mouseout="hideTips"
>
<span class="verify-msg" v-text="text"></span>
<!-- 提示文本 -->
<span class="verify-msg">{{ text }}</span>
<!-- 已滑动的进度条 -->
<div
class="verify-left-bar"
:style="{
width: leftBarWidth,
height: barSize.height,
borderTopLeftRadius: barSize.height / 2 + 'px',
borderBottomLeftRadius: barSize.height / 2 + 'px'
width: leftBarWidth + 'px',
height: barSize.height + 'px'
}"
>
<span class="verify-msg" v-text="finishText"></span>
<div
class="verify-move-block"
@touchstart="start"
@mousedown="start"
:style="{
width: barSize.height,
height: barSize.height,
left: moveBlockLeft,
borderRadius: barSize.height / 2 + 'px'
}"
>
<i :class="iconClass" :style="{ lineHeight: barSize.height + 'px' }"></i>
<div class="verify-sub-block" :style="{ transform: `translate(${moveBlockLeft}px, ${-top}px)` }"></div>
</div>
<span class="verify-msg">{{ finishText }}</span>
</div>
<!-- 滑块 -->
<div
class="verify-move-block"
@mousedown="start"
:style="{
width: barSize.height + 'px',
height: barSize.height + 'px',
left: moveBlockLeft + 'px'
}"
>
<i :class="iconClass"></i>
</div>
</div>
</div>
</template>
<script setup>
import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
import { computed, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import * as LoginApi from '@/api/login'
@ -79,10 +88,6 @@ const props = defineProps({
type: String,
default: 'fixed'
},
vSpace: {
type: Number,
default: 5
},
explain: {
type: String,
default: '向右滑动完成验证'
@ -111,7 +116,6 @@ const props = defineProps({
})
const emit = defineEmits(['success', 'error', 'ready'])
const { t } = useI18n()
//
@ -124,16 +128,10 @@ const text = ref(props.explain)
const finishText = ref('')
const top = ref(0)
const left = ref(0)
const moveBlockBackgroundPosition = ref('')
//
const startMoveTime = ref(null)
const endMovetime = ref(null)
const startX = ref(0)
const oldX = ref(0)
const currX = ref(0)
const isMove = ref(false)
const trail = reactive([])
//
const iconClass = computed(() => {
@ -142,37 +140,25 @@ const iconClass = computed(() => {
//
onMounted(() => {
console.log('验证码组件挂载')
init()
window.addEventListener('touchmove', move)
window.addEventListener('mousemove', move)
window.addEventListener('touchend', end)
window.addEventListener('mouseup', end)
})
//
onBeforeUnmount(() => {
window.removeEventListener('touchmove', move)
window.removeEventListener('mousemove', move)
window.removeEventListener('touchend', end)
window.removeEventListener('mouseup', end)
//
document.addEventListener('mousemove', handleMouseMove)
document.addEventListener('mouseup', handleMouseUp)
document.addEventListener('touchmove', handleTouchMove)
document.addEventListener('touchend', handleTouchEnd)
})
//
const init = async () => {
console.log('验证码初始化开始,类型:', props.captchaType)
console.log('验证码初始化开始')
try {
console.log('开始请求验证码API')
const res = await LoginApi.getCode({ captchaType: props.captchaType })
console.log('验证码API响应:', res)
if (res && res.repCode === '0000') {
const { repData } = res
console.log('验证码数据:', {
x: repData.x,
y: repData.y,
hasOriginalImage: !!repData.originalImageBase64,
hasJigsawImage: !!repData.jigsawImageBase64
})
backImgBase.value = 'data:image/png;base64,' + repData.originalImageBase64
blockImgBase.value = 'data:image/png;base64,' + repData.jigsawImageBase64
@ -207,59 +193,56 @@ const refresh = () => {
init()
}
//
const showTips = () => {
if (!status.value) {
text.value = props.explain
}
}
//
const hideTips = () => {
if (!status.value) {
text.value = props.explain
}
}
//
const start = (e) => {
if (status.value) return
text.value = ''
startMoveTime.value = new Date().getTime()
console.log('开始滑动')
isMove.value = true
startX.value = e.clientX || e.touches[0].clientX
oldX.value = moveBlockLeft.value
trail.length = 0
startX.value = e.clientX || (e.touches && e.touches[0].clientX)
}
//
const move = (e) => {
//
const handleMouseMove = (e) => {
if (!isMove.value) return
moveSlider(e.clientX)
}
//
const handleTouchMove = (e) => {
if (!isMove.value) return
moveSlider(e.touches[0].clientX)
}
//
const moveSlider = (clientX) => {
const moveX = clientX - startX.value
const newLeft = Math.max(0, Math.min(moveX, parseInt(props.barSize.width) - parseInt(props.barSize.height)))
const eventX = e.clientX || (e.touches ? e.touches[0].clientX : 0)
if (eventX === 0) return
const moveX = eventX - startX.value
const newMoveBlockLeft = oldX.value + moveX
//
if (newMoveBlockLeft < 0) {
moveBlockLeft.value = 0
} else if (newMoveBlockLeft > props.barSize.width - props.barSize.height) {
moveBlockLeft.value = props.barSize.width - props.barSize.height
} else {
moveBlockLeft.value = newMoveBlockLeft
leftBarWidth.value = newMoveBlockLeft + props.barSize.height / 2
}
trail.push(moveBlockLeft.value)
moveBlockLeft.value = newLeft
leftBarWidth.value = newLeft
}
//
const handleMouseUp = () => {
if (!isMove.value) return
endSlide()
}
//
const handleTouchEnd = () => {
if (!isMove.value) return
endSlide()
}
//
const end = async () => {
const endSlide = async () => {
if (!isMove.value) return
console.log('结束滑动')
isMove.value = false
endMovetime.value = new Date().getTime()
if (moveBlockLeft.value === 0) return
try {
//
@ -272,13 +255,15 @@ const end = async () => {
token: window.sessionStorage.getItem('token')
})
console.log('验证结果:', res)
if (res && res.repCode === '0000') {
status.value = true
text.value = ''
finishText.value = t('captcha.verifySuccess')
setTimeout(() => {
if (props.mode === 'pop') {
//
emit('success', {
captchaVerification: res.repData.captchaVerification
})
@ -287,14 +272,24 @@ const end = async () => {
} else {
status.value = false
text.value = t('captcha.verifyFail')
//
moveBlockLeft.value = 0
leftBarWidth.value = 0
setTimeout(() => {
refresh()
}, 1000)
}
} catch (error) {
console.error('验证失败', error)
console.error('验证失败:', error)
status.value = false
text.value = t('captcha.verifyError')
//
moveBlockLeft.value = 0
leftBarWidth.value = 0
setTimeout(() => {
refresh()
}, 1000)
@ -312,12 +307,14 @@ const end = async () => {
position: relative;
background-color: #f5f5f5;
overflow: hidden;
border-radius: 4px;
}
.verify-img-panel .back-img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
.verify-img-panel .verify-refresh {
@ -334,11 +331,13 @@ const end = async () => {
color: #fff;
font-size: 20px;
z-index: 10;
border-radius: 0 4px 0 4px;
}
.verify-img-panel .verify-gap {
position: absolute;
background-color: #fff;
background-color: rgba(255, 255, 255, 0.7);
box-shadow: 0 0 4px rgba(0, 0, 0, 0.2);
z-index: 1;
}
@ -350,6 +349,7 @@ const end = async () => {
border-radius: 4px;
line-height: 40px;
margin-top: 5px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}
.verify-bar-area .verify-msg {
@ -361,35 +361,43 @@ const end = async () => {
z-index: 2;
color: #88949d;
font-size: 14px;
user-select: none;
}
.verify-bar-area .verify-left-bar {
position: absolute;
top: 0;
left: 0;
background-color: #d1e9fe;
background: linear-gradient(to right, #1890ff, #36a6ff);
height: 40px;
border-radius: 4px;
z-index: 1;
transition: width 0.2s;
}
.verify-bar-area .verify-move-block {
position: absolute;
top: 0;
left: 0;
background-color: #1890ff;
background: linear-gradient(135deg, #1890ff, #36a6ff);
color: #fff;
cursor: pointer;
box-shadow: 0 0 2px #1890ff;
transition: background 0.2s;
cursor: grab;
box-shadow: 0 0 4px rgba(24, 144, 255, 0.5);
transition: all 0.2s;
z-index: 3;
display: flex;
align-items: center;
justify-content: center;
user-select: none;
}
.verify-bar-area .verify-move-block:hover {
background-color: #0c7ad9;
background: linear-gradient(135deg, #0c7ad9, #1890ff);
box-shadow: 0 0 8px rgba(24, 144, 255, 0.5);
}
.verify-bar-area .verify-move-block:active {
cursor: grabbing;
}
.verify-bar-area .verify-move-block .verify-sub-block {
@ -397,7 +405,11 @@ const end = async () => {
width: 50px;
height: 50px;
top: -55px;
box-shadow: 0 0 2px #1890ff;
background-size: 100% !important;
box-shadow: 0 0 8px rgba(24, 144, 255, 0.5);
border-radius: 2px;
overflow: hidden;
transition: transform 0.2s;
}
.verify-loading {
@ -407,6 +419,7 @@ const end = async () => {
height: 100%;
background-color: #f5f5f5;
color: #88949d;
border-radius: 4px;
}
.iconfont {
@ -414,5 +427,34 @@ const end = async () => {
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
pointer-events: none;
font-size: 20px;
}
/* 增强拼图块样式 */
.verify-img-panel .verify-sub-block {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5) !important;
border: 1px solid rgba(255, 255, 255, 0.8) !important;
z-index: 10 !important;
}
/* 添加图标备用样式 */
.icon-refresh::before {
content: "↻";
}
.icon-right::before {
content: "→";
}
.icon-check::before {
content: "✓";
}
/* 增强滑块样式 */
.verify-bar-area .verify-move-block {
border-radius: 4px !important;
border: 1px solid rgba(255, 255, 255, 0.8) !important;
box-shadow: 0 0 8px rgba(24, 144, 255, 0.8) !important;
}
</style>