态势感知平台-账号密码登录及验证码接口对接治理平台登录接口

This commit is contained in:
fmk1023
2026-01-04 15:15:51 +08:00
parent f3b63fdfd9
commit e90006368a
4 changed files with 167 additions and 249 deletions

View File

@@ -40,7 +40,8 @@
<!-- <li><router-link to="/intro" class="LNK">关于我们</router-link></li>-->
<!-- </ul>-->
<a class="login-btn" @click="handleLogin('login')">登录</a>
<a v-if="!isLogin" class="login-btn" @click="handleLogin('login')">登录</a>
<UserAvatar v-else/>
</div>
</div>
</div>
@@ -48,7 +49,12 @@
<script setup lang="ts">
import {ref, reactive} from 'vue';
import UserAvatar from './UserAvatar.vue';
import {emitEvent} from "@/utils/eventBus";
import { useAccountStore } from '@/stores/user';
import { storeToRefs } from 'pinia';
const userInfo = useAccountStore();
const { isLogin } = storeToRefs(userInfo);
const dataSubMenus = [
{

View File

@@ -34,17 +34,22 @@
</div>
</div>
<form class="login-form" @submit.prevent="handleLogin">
<d-form
ref="loginFormRef"
:data="loginForm"
:rules="loginRules"
layout="vertical"
class="login-form-container"
>
<template v-if="loginType === 'account'">
<div class="form-item">
<d-form-item field="username" label="账号">
<d-input
v-model="loginForm.account"
v-model="loginForm.username"
placeholder="账户(邮箱或手机号)"
size="lg"
/>
</div>
<div class="form-item">
</d-form-item>
<d-form-item field="password" label="密码">
<d-input
v-model="loginForm.password"
type="password"
@@ -52,45 +57,49 @@
show-password
size="lg"
/>
</div>
<div class="form-item verify-row">
<d-input
v-model="loginForm.verifyCode"
placeholder="请输入验证码"
size="lg"
class="verify-input"
/>
<div class="captcha-box">
<span>x j 7 2</span>
<!-- <img class="code" :src="codeDir" alt="验证码图片" @click="getCaptchaUrl" />-->
</d-form-item>
<d-form-item field="code" label="验证码">
<div class="verify-row">
<d-input
v-model="loginForm.code"
placeholder="请输入验证码"
size="lg"
class="verify-input"
/>
<div class="captcha-box" @click="getCaptchaUrl">
<img v-if="codeDir" :src="codeDir" alt="验证码" class="captcha-img" />
<span v-else class="loading-text">加载中</span>
</div>
</div>
</div>
</d-form-item>
</template>
<template v-else>
<div class="form-item">
<d-form-item field="mobile">
<d-input
v-model="loginForm.mobile"
placeholder="请输入手机号"
size="lg"
/>
</div>
<div class="form-item verify-row">
<d-input
v-model="loginForm.smsCode"
placeholder="请输入验证码"
size="lg"
class="verify-input"
/>
<d-button
variant="text"
class="get-code-btn"
:disabled="isCounting"
@click="getSmsCode"
>
{{ isCounting ? `${count}s后重试` : '获取验证码' }}
</d-button>
</div>
</d-form-item>
<d-form-item field="smsCode">
<div class="verify-row">
<d-input
v-model="loginForm.smsCode"
placeholder="请输入验证码"
size="lg"
class="verify-input"
/>
<d-button
variant="text"
class="get-code-btn"
:disabled="isCounting"
@click="getSmsCode"
>
{{ isCounting ? `${count}s后重试` : '获取验证码' }}
</d-button>
</div>
</d-form-item>
</template>
<div class="extra-row">
@@ -112,8 +121,7 @@
>
登录
</d-button>
</form>
</d-form>
<div class="footer-links">
<a href="#">隐私政策</a>
@@ -124,18 +132,21 @@
</template>
<script setup lang="ts">
import { ref, reactive,onMounted } from 'vue';
import { ref, reactive, onMounted,defineProps } from 'vue';
import { useModel } from '@/utils/hooks/useModel';
// 引入 DevUI 组件 (确保已安装)
import { Modal as DModal } from 'vue-devui/modal';
import { Input as DInput } from 'vue-devui/input';
import { Button as DButton } from 'vue-devui/button';
import { Checkbox as DCheckbox } from 'vue-devui/checkbox';
import { Message } from 'vue-devui/message';
import {useRouter} from "vue-router";
import {getCaptcha} from "@/api/jyh/auth";
import {
Modal as DModal,
Input as DInput,
Button as DButton,
Checkbox as DCheckbox,
Form as DForm,
FormItem as DFormItem,
Message
} from 'vue-devui';
import { useRouter } from "vue-router";
// 引入页面2风格的接口逻辑
import { getCaptcha, login } from "@/api/jyh/auth";
// 定义 Props 和 Emits
const props = withDefaults(defineProps<{
modelValue: boolean;
showClose?: boolean;
@@ -154,35 +165,48 @@ const { vModels } = useModel(props, emits);
const router = useRouter();
// --- 状态管理 ---
const loginType = ref<'account' | 'mobile'>('account'); // 当前登录方式
const loginType = ref<'account' | 'mobile'>('account');
const loading = ref(false);
const rememberMe = ref(false);
const loginFormRef = ref();
const codeDir = ref(''); // 验证码图片路径
// 表单数据
const loginForm = reactive({
account: '',
username: '',
password: '',
verifyCode: '', // 图形验证码
code: '',
uuid: '',
mobile: '',
smsCode: '' // 短信验证码
smsCode: ''
});
const loginRules = {
username: [{ required: true, message: '请输入账户', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
code: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
mobile: [{ required: true, message: '请输入手机号', trigger: 'blur' }],
smsCode: [{ required: true, message: '请输入短信验证码', trigger: 'blur' }]
};
// 短信倒计时
const count = ref(60);
const isCounting = ref(false);
let timer: any = null;
const codeDir = ref('');
const getCaptchaUrl = () => {
getCaptcha().then((res) => {
const imageUrl = URL.createObjectURL(res.data);
codeDir.value = imageUrl;
getCaptcha().then((res: any) => {
// 页面2通常返回的是包含 img (base64) 和 uuid 的对象
if (res.data && res.data.img) {
codeDir.value = res.data.img;
loginForm.uuid = res.data.uuid;
} else {
// 兼容页面1原本的 Blob 模式
const imageUrl = URL.createObjectURL(res.data);
codeDir.value = imageUrl;
}
});
};
const handleClose = (close: Function) => {
emits('close');
close();
@@ -193,10 +217,7 @@ const getSmsCode = async () => {
Message.warning('请输入手机号');
return;
}
// TODO: 调用发送短信接口
Message.success('验证码已发送');
// 开始倒计时
isCounting.value = true;
count.value = 60;
timer = setInterval(() => {
@@ -209,245 +230,125 @@ const getSmsCode = async () => {
};
const handleLogin = async () => {
// 简单校验
if (loginType.value === 'account') {
if (!loginForm.account || !loginForm.password) {
Message.warning('请输入账户和密码');
return;
}
} else {
if (!loginForm.mobile || !loginForm.smsCode) {
Message.warning('请输入手机号和验证码');
return;
}
}
const validate = await loginFormRef.value.validate();
if (!validate) return;
loading.value = true;
try {
// TODO: 调用实际登录接口
// const res = await api.login(...)
const loginParams = loginType.value === 'account'
? { username: loginForm.username, password: loginForm.password, code: loginForm.code, uuid: loginForm.uuid }
: { mobile: loginForm.mobile, smsCode: loginForm.smsCode };
// 模拟登录延迟
await new Promise(resolve => setTimeout(resolve, 1000));
// const res = await login(loginParams);
login(loginParams.username, loginParams.code, getRsaCode(loginParams.password))
.then(async (res) => {
if (res.data.code === 200) {
Message.success('登录成功');
const { access_token } = res.data.data;
const account = useAccountStore();
await account.checkIsLogin(access_token, res.data.data);
emits('close');
return;
}
Message.error(res.data.msg);
formModel.code = '';
getCaptchaUrl();
})
.catch((err) => {
console.log(err);
formModel.code = '';
getCaptchaUrl();
});
Message.success('登录成功');
// router.push({ name: `Pricing` });
window.open(`${import.meta.env.VITE_BASE_URL}Dashboard`, '_blank');
emits('close', true); // 关闭弹窗
emits('close', true);
emits('login-success', { user: 'test' });
} catch (error) {
console.error(error);
getCaptchaUrl(); // 失败刷新验证码
} finally {
loading.value = false;
}
};
const switchToRegister = () => {
// 切换到注册模式或跳转
emits('go-register');
};
onMounted(() => {
getCaptchaUrl();
});
</script>
<style lang="scss">
/* 使用全局样式或穿透样式覆盖 DevUI 默认 Modal 样式 */
.tcode-login-modal {
:deep(.devui-modal) {
width: 600px !important;
}
.devui-modal__header {
border-bottom: none;
padding: 0;
}
.devui-modal__body {
padding: 0; /* 移除默认内边距,由内部控制 */
}
.btn-close {
z-index: 10; /* 确保关闭按钮在最上层 */
}
}
</style>
<style lang="scss" scoped>
/* --- 布局样式 --- */
.login-header {
text-align: center;
padding: 40px 0 20px;
.logo-img {
height: 48px; /* 根据实际 Logo 调整 */
width: auto;
display: inline-block;
}
.logo-img { height: 48px; width: auto; display: inline-block; }
}
.login-body {
padding: 0 50px 40px; /* 两侧留白 */
}
.login-body { padding: 0 50px 40px; }
/* --- Tabs 样式 --- */
.login-tabs {
display: flex;
justify-content: center;
margin-bottom: 30px;
border-bottom: 1px solid #eee;
.tab-item {
font-size: 16px;
color: #666;
padding: 10px 30px;
cursor: pointer;
position: relative;
transition: color 0.3s;
font-size: 16px; color: #666; padding: 10px 30px; cursor: pointer; position: relative;
&.active {
color: #d12323; /* 红色高亮 */
font-weight: 500;
&::after {
content: '';
position: absolute;
bottom: -1px; /* 盖住底边框 */
left: 0;
width: 100%;
height: 2px;
background-color: #d12323;
}
}
&:hover {
color: #d12323;
color: #d12323; font-weight: 500;
&::after { content: ''; position: absolute; bottom: -1px; left: 0; width: 100%; height: 2px; background-color: #d12323; }
}
}
}
/* --- 表单样式 --- */
.login-form {
.form-item {
margin-bottom: 20px;
}
/* 覆盖 DevUI Input 样式以匹配设计 */
:deep(.devui-input__wrapper) {
border-radius: 4px;
border-color: #ddd;
box-shadow: none;
//&:hover, &.is-focus {
// border-color: #d12323; /* 聚焦时红色边框 */
//}
}
:deep(.devui-input__inner) {
height: 44px; /* 增加输入框高度 */
font-size: 14px;
}
}
/* 验证码行 */
.verify-row {
display: flex;
gap: 10px;
align-items: center;
.verify-input {
flex: 1;
}
/* 图形验证码 */
display: flex; gap: 10px; align-items: center;
.verify-input { flex: 1; }
.captcha-box {
width: 100px;
height: 44px;
border: 1px solid #ddd;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
background-color: #f9f9f9;
font-size: 18px;
letter-spacing: 4px;
color: #333;
/* 实际使用 img 标签 */
}
width: 100px; height: 44px; border: 1px solid #ddd; border-radius: 4px;
display: flex; align-items: center; justify-content: center;
cursor: pointer; background-color: #f9f9f9; overflow: hidden;
/* 获取验证码按钮 */
.get-code-btn {
//color: #d12323 !important;
font-size: 14px;
padding: 0 10px;
&:disabled {
color: #999 !important;
}
.captcha-img { width: 100%; height: 30px; object-fit: cover; }
.loading-text { font-size: 12px; color: #999; }
}
}
/* --- 辅助功能行 --- */
.extra-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
font-size: 14px;
color: #666;
:deep(.devui-checkbox-label) {
color: #666;
}
.right-links {
.link-btn {
//color: #d12323;
text-decoration: none;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.divider {
margin: 0 8px;
color: #ddd;
}
}
display: flex; justify-content: space-between; align-items: center;
margin-top: 10px; margin-bottom: 30px; font-size: 14px; color: #666;
}
///* --- 登录按钮 --- */
//.submit-btn {
// background-color: #d12323 !important; /* 强制红色背景 */
// border-color: #d12323 !important;
// color: #fff !important;
// font-size: 16px;
// height: 48px;
// border-radius: 4px;
//
// &:hover {
// background-color: #b01d1d !important;
// border-color: #b01d1d !important;
// }
//}
/* --- 底部链接 --- */
.footer-links {
text-align: center;
margin-top: 40px;
a {
color: #999;
font-size: 14px;
text-decoration: none;
margin: 0 10px;
&:hover {
color: #666;
}
}
}
.right-links {
.link-btn {
text-decoration: none;
cursor: pointer;
}
.divider {
margin: 0 8px;
color: #ddd;
}
}
/* 覆盖 DevUI FormItem 间距 */
:deep(.devui-form-item) {
margin-bottom: 20px;
}
</style>

11
src/utils/jsencrypt.ts Normal file
View File

@@ -0,0 +1,11 @@
import JSEncrypt from 'jsencrypt';
const PUBLIC_KEY =
'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCcXz5g5hNGAOgFOVhiYY8pYlrH3gXmHkYRBqqEHsVaKqjdY9PQbkfSb9VkD78VYwFyTSzMZn5e1A5XXXDTUL3PezflMc/GjvseB87OQ3a6XDHw5tVXiJruc72oN9GKEvh5TjQUzi2S1sTHXg8vdD/niXw+K/icrQFdzSpy1PWc6QIDAQAB';
const pubKey = `-----BEGIN PUBLIC KEY-----${PUBLIC_KEY}-----END PUBLIC KEY-----`;
export default (str: string) => {
const encryptStr = new JSEncrypt();
encryptStr.setPublicKey(pubKey);
return encryptStr.encrypt(str.toString());
};

View File

@@ -38,12 +38,12 @@ export default (config: any) => {
},
proxy: {
// 配置跨域代理
'/gateway/official/u-auth/index/getCaptcha': {
// target: 'http://223.76.236.155:20011', // 目标服务器地址
target: 'http://222.20.126.217:9700', // 目标服务器地址
changeOrigin: true, // 允许跨域
rewrite: (path) => path.replace('/gateway/official/u-auth', '/gateway/u-auth') // 重写路径
},
// '/gateway/official/u-auth/index/getCaptcha': {
// // target: 'http://223.76.236.155:20011', // 目标服务器地址
// target: 'http://222.20.126.217:9700', // 目标服务器地址
// changeOrigin: true, // 允许跨域
// rewrite: (path) => path.replace('/gateway/official/u-auth', '/gateway/u-auth') // 重写路径
// },
'/gateway': {
// target: 'http://223.76.236.155:20011', // 目标服务器地址
target: 'http://222.20.126.217:10010', // 目标服务器地址