Browse Source

b端

master
hx 1 week ago
parent
commit
3ec67608bb
  1. 8
      src/api/device/device.js
  2. 2135
      src/views/device/device/index.vue
  3. 496
      src/views/login.vue

8
src/api/device/device.js

@ -45,6 +45,14 @@ export function updateDevice(data) {
})
}
export function updateDeviceAliasRemark(data) {
return request({
url: '/device/device/alias-remark',
method: 'put',
data: data
})
}
export function delDevice(id) {
return request({
url: '/device/device/' + id,

2135
src/views/device/device/index.vue

File diff suppressed because it is too large

496
src/views/login.vue

@ -1,50 +1,194 @@
<template>
<div class="login">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">客户 GeoTag管理后台</h3>
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码"
@keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input v-model="loginForm.code" auto-complete="off" placeholder="谷歌验证码" style="width: 100%"
@keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input>
<!-- <div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div> -->
</el-form-item>
<!-- 人机验证 -->
<!-- <el-form-item prop='validateCode'>
<el-row :span="24">
<el-col :span="24">
<reCaptcha :sitekey="key" @getValidateCode='getValidateCode' v-model="loginForm.validateCode"></reCaptcha>
</el-col>
</el-row>
</el-form-item> -->
<!-- <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox> -->
<el-form-item style="width:100%;">
<el-button :loading="loading" size="medium" type="waming" style="width:100%;"
@click.native.prevent="handleLogin">
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span></span>
<!-- 左侧动态背景区域 -->
<div class="login-left">
<!-- 网格背景 -->
<div class="grid-overlay"></div>
<!-- 地图网格线 SVG -->
<svg class="map-grid" viewBox="0 0 100 100" preserveAspectRatio="none">
<line x1="20" y1="0" x2="20" y2="100" stroke="rgba(76,175,80,0.3)" stroke-width="0.1"/>
<line x1="40" y1="0" x2="40" y2="100" stroke="rgba(76,175,80,0.3)" stroke-width="0.1"/>
<line x1="60" y1="0" x2="60" y2="100" stroke="rgba(76,175,80,0.3)" stroke-width="0.1"/>
<line x1="80" y1="0" x2="80" y2="100" stroke="rgba(76,175,80,0.3)" stroke-width="0.1"/>
<line x1="0" y1="20" x2="100" y2="20" stroke="rgba(76,175,80,0.3)" stroke-width="0.1"/>
<line x1="0" y1="40" x2="100" y2="40" stroke="rgba(76,175,80,0.3)" stroke-width="0.1"/>
<line x1="0" y1="60" x2="100" y2="60" stroke="rgba(76,175,80,0.3)" stroke-width="0.1"/>
<line x1="0" y1="80" x2="100" y2="80" stroke="rgba(76,175,80,0.3)" stroke-width="0.1"/>
</svg>
<!-- 地图轮廓 SVG -->
<svg class="map-contours" viewBox="0 0 800 600" preserveAspectRatio="xMidYMid slice">
<path d="M100,100 Q200,50 300,100 T500,100 T700,150" stroke="rgba(255,255,255,0.4)" fill="none" stroke-width="1.5"/>
<path d="M50,200 Q150,150 250,200 T450,200 T650,250 T750,200" stroke="rgba(255,255,255,0.3)" fill="none" stroke-width="1"/>
<path d="M100,300 Q250,250 400,300 T700,350" stroke="rgba(255,255,255,0.35)" fill="none" stroke-width="1"/>
<path d="M50,400 Q200,350 350,400 T550,400 T750,450" stroke="rgba(255,255,255,0.25)" fill="none" stroke-width="1"/>
<path d="M100,500 Q300,450 500,500 T700,500" stroke="rgba(255,255,255,0.3)" fill="none" stroke-width="1"/>
<path d="M200,50 Q350,100 500,50 T800,100" stroke="rgba(76,175,80,0.3)" fill="none" stroke-width="1"/>
<path d="M0,150 Q200,200 400,150 T800,200" stroke="rgba(76,175,80,0.25)" fill="none" stroke-width="1"/>
</svg>
<!-- 连接线 SVG -->
<svg class="connection-lines" viewBox="0 0 100 100" preserveAspectRatio="none">
<line x1="12" y1="15" x2="50" y2="50" stroke="rgba(76,175,80,0.6)" stroke-width="0.15"/>
<line x1="78" y1="25" x2="50" y2="50" stroke="rgba(33,150,243,0.6)" stroke-width="0.15"/>
<line x1="18" y1="55" x2="50" y2="50" stroke="rgba(255,193,7,0.6)" stroke-width="0.15"/>
<line x1="70" y1="70" x2="50" y2="50" stroke="rgba(76,175,80,0.6)" stroke-width="0.15"/>
<line x1="88" y1="40" x2="50" y2="50" stroke="rgba(255,87,34,0.6)" stroke-width="0.15"/>
<line x1="35" y1="82" x2="50" y2="50" stroke="rgba(33,150,243,0.6)" stroke-width="0.15"/>
<line x1="5" y1="30" x2="50" y2="50" stroke="rgba(156,39,176,0.6)" stroke-width="0.15"/>
<line x1="85" y1="85" x2="50" y2="50" stroke="rgba(76,175,80,0.6)" stroke-width="0.15"/>
<line x1="12" y1="15" x2="78" y2="25" stroke="rgba(255,255,255,0.2)" stroke-width="0.05" stroke-dasharray="0.5,0.5"/>
<line x1="18" y1="55" x2="70" y2="70" stroke="rgba(255,255,255,0.2)" stroke-width="0.05" stroke-dasharray="0.5,0.5"/>
</svg>
<!-- 十字准星 SVG -->
<svg class="crosshair" width="600" height="600" viewBox="0 0 200 200">
<circle cx="100" cy="100" r="90" fill="none" stroke="rgba(76,175,80,0.2)" stroke-width="1" stroke-dasharray="5,3"/>
<circle cx="100" cy="100" r="80" fill="none" stroke="rgba(76,175,80,0.15)" stroke-width="0.5"/>
<line x1="100" y1="20" x2="100" y2="80" stroke="rgba(76,175,80,0.3)" stroke-width="0.5"/>
<line x1="100" y1="120" x2="100" y2="180" stroke="rgba(76,175,80,0.3)" stroke-width="0.5"/>
<line x1="20" y1="100" x2="80" y2="100" stroke="rgba(76,175,80,0.3)" stroke-width="0.5"/>
<line x1="120" y1="100" x2="180" y2="100" stroke="rgba(76,175,80,0.3)" stroke-width="0.5"/>
<path d="M 30 30 L 30 50 M 30 30 L 50 30" stroke="rgba(76,175,80,0.4)" stroke-width="1" fill="none"/>
<path d="M 170 30 L 170 50 M 170 30 L 150 30" stroke="rgba(76,175,80,0.4)" stroke-width="1" fill="none"/>
<path d="M 30 170 L 30 150 M 30 170 L 50 170" stroke="rgba(76,175,80,0.4)" stroke-width="1" fill="none"/>
<path d="M 170 170 L 170 150 M 170 170 L 150 170" stroke="rgba(76,175,80,0.4)" stroke-width="1" fill="none"/>
</svg>
<!-- 脉冲波纹 -->
<div class="ripple"></div>
<div class="ripple"></div>
<div class="ripple"></div>
<div class="ripple"></div>
<!-- 定位点 -->
<div class="location-pins">
<div class="pin">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
</div>
<div class="pin">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
</div>
<div class="pin">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
</div>
<div class="pin">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
</div>
<div class="pin">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
</div>
<div class="pin">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
</div>
<div class="pin">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
</div>
<div class="pin">
<svg viewBox="0 0 24 24" fill="currentColor">
<path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7zm0 9.5c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5z"/>
</svg>
</div>
</div>
<!-- 装饰圆环 -->
<div class="orbit-ring"></div>
<div class="orbit-ring"></div>
<div class="orbit-ring"></div>
<div class="orbit-ring"></div>
<!-- 粒子效果 -->
<div class="particles" id="particles"></div>
<!-- 中央超大定位标记 -->
<div class="center-marker">
<svg class="marker-icon" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="48" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="1"/>
<circle cx="50" cy="50" r="40" fill="none" stroke="rgba(76,175,80,0.2)" stroke-width="1.5"/>
<path d="M50 10 C32 10 20 24 20 42 C20 65 50 90 50 90 C50 90 80 65 80 42 C80 24 68 10 50 10 Z"
fill="rgba(76,175,80,0.85)"
stroke="rgba(255,255,255,0.7)"
stroke-width="2.5"/>
<circle cx="50" cy="38" r="12" fill="rgba(255,255,255,0.95)"/>
<circle cx="50" cy="38" r="7" fill="rgba(76,175,80,1)"/>
<circle cx="50" cy="38" r="18" fill="none" stroke="rgba(76,175,80,0.6)" stroke-width="2">
<animate attributeName="r" from="18" to="35" dur="2s" repeatCount="indefinite"/>
<animate attributeName="opacity" from="0.6" to="0" dur="2s" repeatCount="indefinite"/>
</circle>
<circle cx="50" cy="38" r="18" fill="none" stroke="rgba(76,175,80,0.4)" stroke-width="1.5">
<animate attributeName="r" from="18" to="45" dur="2s" begin="0.5s" repeatCount="indefinite"/>
<animate attributeName="opacity" from="0.4" to="0" dur="2s" begin="0.5s" repeatCount="indefinite"/>
</circle>
<circle cx="50" cy="38" r="18" fill="none" stroke="rgba(76,175,80,0.3)" stroke-width="1">
<animate attributeName="r" from="18" to="55" dur="2s" begin="1s" repeatCount="indefinite"/>
<animate attributeName="opacity" from="0.3" to="0" dur="2s" begin="1s" repeatCount="indefinite"/>
</circle>
</svg>
</div>
</div>
<!-- 右侧登录表单区域 -->
<div class="login-right">
<el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
<h3 class="title">GeoTag 企业客户后台</h3>
<el-form-item prop="username">
<el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
<svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码"
@keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
</el-input>
</el-form-item>
<el-form-item prop="code">
<el-input v-model="loginForm.code" auto-complete="off" placeholder="谷歌验证码" style="width: 100%"
@keyup.enter.native="handleLogin">
<svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
</el-input>
<!-- <div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div> -->
</el-form-item>
<!-- 人机验证 -->
<!-- <el-form-item prop='validateCode'>
<el-row :span="24">
<el-col :span="24">
<reCaptcha :sitekey="key" @getValidateCode='getValidateCode' v-model="loginForm.validateCode"></reCaptcha>
</el-col>
</el-row>
</el-form-item> -->
<!-- <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox> -->
<el-form-item style="width:100%;">
<el-button :loading="loading" size="medium" type="primary" style="width:100%;"
@click.native.prevent="handleLogin">
<span v-if="!loading"> </span>
<span v-else> 中...</span>
</el-button>
</el-form-item>
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2026 GeoTag All Rights Reserved</span>
</div>
</div>
</div>
</template>
@ -92,7 +236,6 @@ export default {
loading: false,
redirect: undefined
};
},
watch: {
$route: {
@ -105,7 +248,25 @@ export default {
created() {
// this.getCookie();
},
mounted() {
this.initParticles();
},
methods: {
initParticles() {
const particlesContainer = document.getElementById('particles');
if (particlesContainer) {
for (let i = 0; i < 40; i++) {
const particle = document.createElement('div');
particle.className = 'particle';
particle.style.left = Math.random() * 100 + '%';
particle.style.animationDelay = Math.random() * 25 + 's';
particle.style.animationDuration = (20 + Math.random() * 10) + 's';
particle.style.width = (2 + Math.random() * 3) + 'px';
particle.style.height = particle.style.width;
particlesContainer.appendChild(particle);
}
}
},
icoCreate(icoUrl) {
var link = document.querySelector("link[rel*='icon']") || document.createElement('link');
link.type = 'image/x-icon';
@ -155,25 +316,226 @@ export default {
<style rel="stylesheet/scss" lang="scss">
.login {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background-image: url("../assets/images/bg.png");
background-size: 100%;
}
/* 左侧动态背景区域 */
.login-left {
flex: 0 0 60%;
position: relative;
background: linear-gradient(135deg, #0a1929 0%, #0d3c61 30%, #01579b 60%, #006064 100%);
overflow: hidden;
}
/* 网格背景 */
.grid-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image:
linear-gradient(rgba(76,175,80,0.08) 1px, transparent 1px),
linear-gradient(90deg, rgba(76,175,80,0.08) 1px, transparent 1px);
background-size: 60px 60px;
}
/* 地图轮廓 */
.map-contours {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 90%;
height: 90%;
opacity: 0.15;
}
/* 定位点动画 */
.location-pins {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.pin {
position: absolute;
animation: pulsePin 3s ease-in-out infinite;
}
.pin svg {
width: 50px;
height: 50px;
filter: drop-shadow(0 0 15px currentColor);
}
.pin:nth-child(1) { top: 15%; left: 12%; animation-delay: 0s; color: rgba(76,175,80,0.9); }
.pin:nth-child(2) { top: 25%; left: 78%; animation-delay: 0.5s; color: rgba(33,150,243,0.9); }
.pin:nth-child(3) { top: 55%; left: 18%; animation-delay: 1s; color: rgba(255,193,7,0.9); }
.pin:nth-child(4) { top: 70%; left: 70%; animation-delay: 1.5s; color: rgba(76,175,80,0.9); }
.pin:nth-child(5) { top: 40%; left: 88%; animation-delay: 2s; color: rgba(255,87,34,0.9); }
.pin:nth-child(6) { top: 82%; left: 35%; animation-delay: 2.5s; color: rgba(33,150,243,0.9); }
.pin:nth-child(7) { top: 30%; left: 5%; animation-delay: 0.8s; color: rgba(156,39,176,0.9); }
.pin:nth-child(8) { top: 85%; left: 85%; animation-delay: 1.8s; color: rgba(76,175,80,0.9); }
@keyframes pulsePin {
0%, 100% { transform: scale(1); opacity: 0.7; }
50% { transform: scale(1.15); opacity: 1; }
}
/* 连接线 */
.connection-lines {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.2;
}
/* 中央大定位标记 */
.center-marker {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}
.marker-icon {
width: 350px;
height: 350px;
animation: float 4s ease-in-out infinite;
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
/* 装饰圆环 */
.orbit-ring {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 2px solid rgba(76, 175, 80, 0.15);
border-radius: 50%;
animation: rotate 60s linear infinite;
}
.orbit-ring:nth-child(1) { width: 500px; height: 500px; }
.orbit-ring:nth-child(2) { width: 700px; height: 700px; animation-duration: 80s; animation-direction: reverse; }
.orbit-ring:nth-child(3) { width: 900px; height: 900px; animation-duration: 100s; }
.orbit-ring:nth-child(4) { width: 1100px; height: 1100px; animation-duration: 120s; }
@keyframes rotate {
from { transform: translate(-50%, -50%) rotate(0deg); }
to { transform: translate(-50%, -50%) rotate(360deg); }
}
/* 粒子效果 */
.particles {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
.particle {
position: absolute;
width: 3px;
height: 3px;
background: rgba(76, 175, 80, 0.5);
border-radius: 50%;
animation: particleFloat 25s linear infinite;
}
@keyframes particleFloat {
0% { transform: translateY(100vh) rotate(0deg); opacity: 0; }
10% { opacity: 1; }
90% { opacity: 1; }
100% { transform: translateY(-100vh) rotate(720deg); opacity: 0; }
}
/* 十字准星 */
.crosshair {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
pointer-events: none;
}
/* 地图网格线 */
.map-grid {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.1;
}
/* 脉冲波纹 */
.ripple {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 2px solid rgba(76, 175, 80, 0.3);
border-radius: 50%;
animation: rippleEffect 4s ease-out infinite;
}
.ripple:nth-child(1) { animation-delay: 0s; }
.ripple:nth-child(2) { animation-delay: 1s; }
.ripple:nth-child(3) { animation-delay: 2s; }
.ripple:nth-child(4) { animation-delay: 3s; }
@keyframes rippleEffect {
0% {
width: 100px;
height: 100px;
opacity: 0.8;
}
100% {
width: 800px;
height: 800px;
opacity: 0;
}
}
/* 右侧登录表单区域 */
.login-right {
flex: 0 0 40%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%);
position: relative;
}
.title {
margin: 0px auto 30px auto;
text-align: center;
color: #0A1146;
font-size: 26px;
font-weight: 600;
}
.login-form {
border-radius: 6px;
border-radius: 8px;
background: #ffffff;
width: 400px;
padding: 25px 25px 5px 25px;
padding: 40px 35px 15px 35px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.08);
.el-input {
height: 38px;
@ -210,11 +572,11 @@ export default {
.el-login-footer {
height: 40px;
line-height: 40px;
position: fixed;
position: absolute;
bottom: 0;
width: 100%;
text-align: center;
color: #fff;
color: #999;
font-family: Arial;
font-size: 12px;
letter-spacing: 1px;
@ -223,4 +585,30 @@ export default {
.login-code-img {
height: 38px;
}
</style>
/* 响应式 */
@media (max-width: 1024px) {
.login-left {
flex: 0 0 50%;
}
.login-right {
flex: 0 0 50%;
}
.login-form {
width: 350px;
}
}
@media (max-width: 768px) {
.login-left {
display: none;
}
.login-right {
flex: 0 0 100%;
}
.login-form {
width: 90%;
max-width: 400px;
}
}
</style>
Loading…
Cancel
Save