|
|
@@ -3,7 +3,7 @@
|
|
|
<div class="login-card">
|
|
|
<div class="content">
|
|
|
<div class="title">
|
|
|
- <h2>欢迎使用摄像机管理系统</h2>
|
|
|
+ <h3>Welcome to Camera Management System</h3>
|
|
|
</div>
|
|
|
<el-form
|
|
|
ref="loginFormRef"
|
|
|
@@ -14,7 +14,7 @@
|
|
|
<el-form-item prop="username">
|
|
|
<el-input
|
|
|
v-model.trim="loginFormData.name"
|
|
|
- placeholder="账号"
|
|
|
+ placeholder="Account"
|
|
|
:prefix-icon="User"
|
|
|
size="large"
|
|
|
tabindex="1"
|
|
|
@@ -24,7 +24,7 @@
|
|
|
<el-form-item prop="password" class="password-form-item">
|
|
|
<el-input
|
|
|
v-model.trim="loginFormData.password"
|
|
|
- placeholder="密码"
|
|
|
+ placeholder="Password"
|
|
|
:prefix-icon="Lock"
|
|
|
show-password
|
|
|
size="large"
|
|
|
@@ -32,7 +32,7 @@
|
|
|
type="password"
|
|
|
/>
|
|
|
<div class="forget-password">
|
|
|
- <el-link type="primary" @click="dialogVisible = true">忘记密码</el-link>
|
|
|
+ <el-link type="primary" @click="dialogVisible = true">Forgot Password</el-link>
|
|
|
</div>
|
|
|
</el-form-item>
|
|
|
<el-button
|
|
|
@@ -42,10 +42,10 @@
|
|
|
class="login-btn"
|
|
|
@click.prevent="handleLogin"
|
|
|
>
|
|
|
- 登 录
|
|
|
+ Login
|
|
|
</el-button>
|
|
|
<div class="login-tip">
|
|
|
- <span>初始密码请参阅说明书</span>
|
|
|
+ <span>For initial password, please refer to the manual or contact the administrator.</span>
|
|
|
</div>
|
|
|
</el-form>
|
|
|
</div>
|
|
|
@@ -53,14 +53,43 @@
|
|
|
<!-- 忘记密码对话框 -->
|
|
|
<el-dialog
|
|
|
v-model="dialogVisible"
|
|
|
- title="忘记密码"
|
|
|
- width="500"
|
|
|
+ title="Note"
|
|
|
+ width="800px"
|
|
|
+ :close-on-click-modal="false"
|
|
|
+ :close-on-press-escape="false"
|
|
|
+ @close="handleDialogClose"
|
|
|
>
|
|
|
- <span>请联系客服或管理员获取超级密码后在登录!</span>
|
|
|
+ <div class="forgot-password-dialog">
|
|
|
+ <div class="device-password-title">Device Password</div>
|
|
|
+ <div class="qrcode-container">
|
|
|
+ <qrcode-vue
|
|
|
+ :value="devicePassword"
|
|
|
+ :size="160"
|
|
|
+ :errorCorrectionLevel="level"
|
|
|
+ />
|
|
|
+ <span class="password-text">{{devicePassword}}</span>
|
|
|
+ </div>
|
|
|
+ <div class="reset-methods">
|
|
|
+ <div class="methods-title">How to Reset Password:</div>
|
|
|
+ <div class="method-item">
|
|
|
+ <strong>Method 1:</strong> Open PC software spd.exe, enter the obtained device password into the UID input box, click make, and generate a 12-digit super password.
|
|
|
+ </div>
|
|
|
+ <div class="method-item">
|
|
|
+ <strong>Method 2:</strong> Open mobile APP SPD, click "Enable QR code scanning", scan the QR code above to generate a 12-digit super password, or click the "Manual input" button, enter the device password, click confirm, and generate a super password.
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="password-input-container">
|
|
|
+ <el-input
|
|
|
+ v-model="superPassword"
|
|
|
+ placeholder="Enter Super Password"
|
|
|
+ size="default"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
- <el-button type="primary" @click="dialogVisible = false">
|
|
|
- 确认
|
|
|
+ <el-button style="width: 250px" type="primary" @click="handleResetPassword">
|
|
|
+ Reset to Default Password
|
|
|
</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
@@ -77,21 +106,27 @@ import { User, Lock } from '@element-plus/icons-vue'
|
|
|
import { type LoginRequestData } from '@/api/types/login'
|
|
|
import { setToken } from '@/utils/cache/cookies'
|
|
|
import { setRouter } from '@/router'
|
|
|
+import type { TimeParaData } from '@/api/types/setting'
|
|
|
+import { format } from 'date-fns'
|
|
|
+import { getCameraDeviceInfo, PutTimePara } from '@/api/setting'
|
|
|
+import QrcodeVue from 'qrcode.vue'
|
|
|
+import { cameraResetPassword } from '@/api/setting'
|
|
|
+
|
|
|
|
|
|
const router = useRouter()
|
|
|
-/** 登录表单元素的引用 */
|
|
|
const loginFormRef = ref<FormInstance | null>(null)
|
|
|
-/** 登录按钮 Loading */
|
|
|
const loading = ref(false)
|
|
|
-/** 忘记密码对话框是否可见 */
|
|
|
const dialogVisible = ref(false)
|
|
|
-/** 登录表单数据 */
|
|
|
const loginFormData: LoginRequestData = reactive({
|
|
|
name: '',
|
|
|
password: '',
|
|
|
code: ''
|
|
|
})
|
|
|
-/** 登录表单校验规则 */
|
|
|
+
|
|
|
+const devicePassword = ref('DEVICE_123456')
|
|
|
+const superPassword = ref('')
|
|
|
+const level = ref('H')
|
|
|
+
|
|
|
const loginFormRules: FormRules = {
|
|
|
name: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
|
|
|
password: [
|
|
|
@@ -100,7 +135,33 @@ const loginFormRules: FormRules = {
|
|
|
]
|
|
|
}
|
|
|
|
|
|
-/** 登录逻辑 */
|
|
|
+const handleDialogClose = () => {
|
|
|
+ dialogVisible.value = false
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+async function getDeviceInfo(){
|
|
|
+ const res = await getCameraDeviceInfo()
|
|
|
+ devicePassword.value = res.data.MacAddr.replace(/:/g, '')
|
|
|
+}
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ getDeviceInfo()
|
|
|
+})
|
|
|
+
|
|
|
+function handleResetPassword() {
|
|
|
+ cameraResetPassword({
|
|
|
+ ResetPassword: superPassword.value
|
|
|
+ }).then((res: string) => {
|
|
|
+ if (res.data === 'ok\n') {
|
|
|
+ ElMessage.success('Reset Password Successful')
|
|
|
+ handleDialogClose()
|
|
|
+ } else {
|
|
|
+ ElMessage.error('Reset Password Failed!')
|
|
|
+ }
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
const handleLogin = () => {
|
|
|
loginFormRef.value?.validate((valid: boolean, fields) => {
|
|
|
if (valid) {
|
|
|
@@ -109,18 +170,22 @@ const handleLogin = () => {
|
|
|
.login(loginFormData)
|
|
|
.then((res: string) => {
|
|
|
if (res == 'ok\n') {
|
|
|
- ElMessage.success('登录成功')
|
|
|
+ ElMessage.success('Login Successful')
|
|
|
const token = useUserStore().token
|
|
|
setToken(token)
|
|
|
setRouter()
|
|
|
router.push({ path: '/' })
|
|
|
+ const timeParaData: TimeParaData = {
|
|
|
+ time: format(new Date(), 'yyyy-MM-dd-HH-mm-ss')
|
|
|
+ }
|
|
|
+ PutTimePara(timeParaData)
|
|
|
} else {
|
|
|
- ElMessage.error('用户名或密码错误')
|
|
|
+ ElMessage.error('Incorrect username or password')
|
|
|
}
|
|
|
})
|
|
|
.catch(() => {
|
|
|
loginFormData.password = ''
|
|
|
- ElMessage.error('登录失败')
|
|
|
+ ElMessage.error('Login Failed')
|
|
|
})
|
|
|
.finally(() => {
|
|
|
loading.value = false
|
|
|
@@ -137,8 +202,9 @@ const handleLogin = () => {
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
- height: 100%;
|
|
|
- width: 100%;
|
|
|
+ height: 100vh;
|
|
|
+ width: 100vw;
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
|
.title {
|
|
|
text-align: center;
|
|
|
@@ -189,7 +255,7 @@ const handleLogin = () => {
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
:deep(.el-link) {
|
|
|
- font-size: 13px;
|
|
|
+ font-size: 11px;
|
|
|
|
|
|
&:hover {
|
|
|
text-decoration: underline;
|
|
|
@@ -203,6 +269,7 @@ const handleLogin = () => {
|
|
|
margin-top: 32px;
|
|
|
font-weight: 500;
|
|
|
letter-spacing: 1px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
|
&:hover {
|
|
|
transform: translateY(-2px);
|
|
|
@@ -212,11 +279,247 @@ const handleLogin = () => {
|
|
|
|
|
|
.login-tip {
|
|
|
text-align: center;
|
|
|
- font-size: 13px;
|
|
|
+ font-size: 11px;
|
|
|
margin-top: 16px;
|
|
|
color: #999;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+/* 忘记密码对话框样式 */
|
|
|
+.forgot-password-dialog {
|
|
|
+ padding: 0;
|
|
|
+ box-sizing: border-box;
|
|
|
+
|
|
|
+ .device-password-title {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #666;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ text-align: center;
|
|
|
+ font-weight: 500;
|
|
|
+ }
|
|
|
+
|
|
|
+ .qrcode-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column; /* 改为纵向排列 */
|
|
|
+ align-items: center; /* 居中对齐 */
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 28px;
|
|
|
+ padding: 0 20px;
|
|
|
+ gap: 10px;
|
|
|
+
|
|
|
+ .password-text {
|
|
|
+ font-size: 14px;
|
|
|
+ color: #333;
|
|
|
+ font-weight: 500;
|
|
|
+ word-break: break-all; // 防止长密码换行问题
|
|
|
+ text-align: center;
|
|
|
+ max-width: 160px; // 与二维码同宽
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .reset-methods {
|
|
|
+ margin-bottom: 28px;
|
|
|
+ padding: 0 20px;
|
|
|
+ text-align: left;
|
|
|
+
|
|
|
+ .methods-title {
|
|
|
+ font-size: 13px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #333;
|
|
|
+ text-align: left;
|
|
|
+ }
|
|
|
+
|
|
|
+ .method-item {
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 1.8;
|
|
|
+ margin-bottom: 10px;
|
|
|
+ color: #666;
|
|
|
+ text-indent: 0;
|
|
|
+
|
|
|
+ strong {
|
|
|
+ color: #333;
|
|
|
+ font-weight: 600;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .password-input-container {
|
|
|
+ margin-bottom: 20px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 0 20px;
|
|
|
+
|
|
|
+ :deep(.el-input) {
|
|
|
+ width: 100%;
|
|
|
+ max-width: 340px;
|
|
|
+
|
|
|
+ :deep(.el-input__wrapper) {
|
|
|
+ box-shadow: none;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 6px;
|
|
|
+ padding: 0 12px;
|
|
|
+ height: 40px;
|
|
|
+ background-color: #fafbfc;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ border-color: #d9d9d9;
|
|
|
+ background-color: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-input__inner) {
|
|
|
+ border: none;
|
|
|
+ padding: 0;
|
|
|
+ height: 40px;
|
|
|
+ font-size: 13px;
|
|
|
+ background-color: transparent;
|
|
|
+ color: #333;
|
|
|
+
|
|
|
+ &::placeholder {
|
|
|
+ color: #b3b3b3;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ :deep(.el-input__inner:focus) {
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 对话框整体样式优化 */
|
|
|
+:deep(.el-dialog) {
|
|
|
+ border-radius: 12px;
|
|
|
+ overflow: hidden;
|
|
|
+ box-shadow: 0 12px 48px rgba(0, 0, 0, 0.12);
|
|
|
+
|
|
|
+ .el-dialog__wrapper {
|
|
|
+ backdrop-filter: blur(2px);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 对话框标题样式 */
|
|
|
+:deep(.el-dialog__header) {
|
|
|
+ padding: 16px 15px;
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ margin: 0;
|
|
|
+ border-bottom: none;
|
|
|
+ margin-left: -20px;
|
|
|
+ margin-right: -20px;
|
|
|
+ margin-top: -20px;
|
|
|
+ width: calc(100% + 40px);
|
|
|
+ position: relative;
|
|
|
+ left: -20px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: space-between;
|
|
|
+ align-items: center;
|
|
|
+ overflow: visible;
|
|
|
+
|
|
|
+ .el-dialog__title {
|
|
|
+ color: white;
|
|
|
+ font-size: 18px;
|
|
|
+ font-weight: 600;
|
|
|
+ flex: 1;
|
|
|
+ margin: 0 30px;
|
|
|
+ min-width: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .el-dialog__headerbtn {
|
|
|
+ position: static;
|
|
|
+ top: auto;
|
|
|
+ right: auto;
|
|
|
+ transform: none;
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-left: 10px; // ← 就是这一行
|
|
|
+
|
|
|
+ //margin-left: 10px;
|
|
|
+
|
|
|
+ .el-dialog__close {
|
|
|
+ color: white;
|
|
|
+ font-size: 25px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ cursor: pointer;
|
|
|
+ width: 32px;
|
|
|
+ height: 32px;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ opacity: 0.8;
|
|
|
+ transform: rotate(90deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* 对话框关闭按钮 - 关键修复 */
|
|
|
+:deep(.el-dialog__close) {
|
|
|
+ color: white;
|
|
|
+ font-size: 25px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ position: relative;
|
|
|
+ width: auto;
|
|
|
+ height: auto;
|
|
|
+ padding: 0px;
|
|
|
+ border: none;
|
|
|
+ background: none;
|
|
|
+ outline: none;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ opacity: 0.8;
|
|
|
+ transform: rotate(90deg);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:focus {
|
|
|
+ outline: none;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-dialog__body) {
|
|
|
+ padding: 24px 0 0;
|
|
|
+ background-color: #fff;
|
|
|
+}
|
|
|
+
|
|
|
+/* 对话框页脚样式 */
|
|
|
+:deep(.el-dialog__footer) {
|
|
|
+ padding: 20px;
|
|
|
+ background-color: #fff;
|
|
|
+ text-align: center;
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 重置密码按钮样式 */
|
|
|
+:deep(.dialog-footer) {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ padding: 0;
|
|
|
+
|
|
|
+ .el-button {
|
|
|
+ width: 140px;
|
|
|
+ height: 40px;
|
|
|
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
|
+ border: none;
|
|
|
+ color: white;
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ border-radius: 6px;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ cursor: pointer;
|
|
|
+
|
|
|
+ &:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.3);
|
|
|
+ }
|
|
|
+
|
|
|
+ &:active {
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|