123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735 |
- <template>
- <div class="login-container">
- <!-- 系统头部 -->
- <s-header
- :name="
- sys
- ? sys.title
- : sysTitle == 'AETI GLOBAL'
- ? sysTitle
- : $t('public.sysName')
- "
- :noback="true"
- />
- <!-- 主要内容区域 -->
- <div class="login-content">
- <!-- Logo区域 -->
- <div class="logo-section">
- <div class="logo-container">
- <img :src="logoName" alt="System Logo" class="system-logo" />
- </div>
- </div>
- <!-- 登录表单 -->
- <div class="form-container">
- <!-- 登录方式切换 -->
- <div class="login-type-tabs">
- <div
- class="tab-item"
- :class="{ active: loginType === 'password' }"
- @click="switchLoginType('password')"
- >
- {{ $t('login.passwordLogin') }}
- </div>
- <div
- class="tab-item"
- :class="{ active: loginType === 'sms' }"
- @click="switchLoginType('sms')"
- >
- {{ $t('login.smsLogin') }}
- </div>
- </div>
- <van-form @submit="onSubmit" class="login-form">
- <!-- 密码登录 -->
- <div v-if="loginType === 'password'">
- <!-- 用户名输入 -->
- <van-field
- v-model="userName"
- name="userName"
- :placeholder="$t('login.userNameInput')"
- :rules="[{ required: true, message: $t('login.userNameInput') }]"
- class="form-field"
- >
- <template #left-icon>
- <van-icon name="user-o" size="22" class="field-icon" />
- </template>
- </van-field>
- <!-- 密码输入 -->
- <van-field
- v-model="userPwd"
- name="userPwd"
- type="password"
- :placeholder="$t('login.passWordInput')"
- :rules="[{ required: true, message: $t('login.passWordInput') }]"
- class="form-field"
- >
- <template #left-icon>
- <van-icon name="lock" size="22" class="field-icon" />
- </template>
- </van-field>
- </div>
- <!-- 验证码登录 -->
- <div v-if="loginType === 'sms'">
- <!-- 手机号/邮箱输入 -->
- <van-field
- v-model="phoneOrEmail"
- name="phoneOrEmail"
- :placeholder="$t('login.phoneOrEmailInput')"
- :rules="phoneOrEmailRules"
- class="form-field"
- >
- <template #left-icon>
- <van-icon name="phone-o" size="22" class="field-icon" />
- </template>
- </van-field>
- <!-- 验证码输入 -->
- <van-field
- v-model="smsCode"
- name="smsCode"
- :placeholder="$t('login.smsCodeInput')"
- :rules="[{ required: true, message: $t('login.smsCodeInput') }]"
- class="form-field sms-field"
- >
- <template #left-icon>
- <van-icon name="shield-o" size="22" class="field-icon" />
- </template>
- <template #button>
- <van-button
- size="small"
- type="primary"
- :disabled="smsCountdown > 0 || !phoneOrEmail"
- @click="sendSmsCode"
- class="sms-button"
- >
- {{ smsCountdown > 0 ? `${smsCountdown}s` : $t('login.sendSmsCode') }}
- </van-button>
- </template>
- </van-field>
- </div>
- <!-- 记住密码和忘记密码 (仅密码登录时显示) -->
- <div v-if="loginType === 'password'" class="password-options">
- <div class="remember-me">
- <van-checkbox v-model="checked" shape="square" icon-size="20">
- {{ $t("login.checkedPassWord") }}
- </van-checkbox>
- </div>
- <div class="forgot-password" @click="forgetPassword">
- <span>{{ $t("login.forgetPassWord") }}</span>
- <van-icon name="arrow" size="16" />
- </div>
- </div>
- <!-- 登录按钮 -->
- <van-button
- round
- block
- type="primary"
- native-type="submit"
- class="login-button"
- >
- {{ $t("login.loginButton") }}
- </van-button>
- <!-- 微信登录 -->
- <div v-if="isInWeChat" class="wechat-login" @click="wxLoginHandler">
- <van-button round block class="wechat-button">
- <div class="wechat-container">
- <van-icon name="wechat" size="28" class="wechat-icon" />
- <span>{{ $t("login.loginWithWechat") }}</span>
- </div>
- </van-button>
- </div>
- <!-- 注册选项 -->
- <div class="register-option">
- <span class="register-link" @click="registerClick">{{
- $t("login.regusterButton")
- }}</span>
- </div>
- </van-form>
- </div>
- </div>
- </div>
- </template>
- <script>
- import md5 from "js-md5";
- import { onMounted, ref, computed, reactive } from "vue";
- import {
- showSuccessToast,
- showFailToast,
- showDialog,
- Dialog,
- Button,
- } from "vant";
- import { loginSys, getSys, getOpenid, sendSmsCode as sendSmsCodeApi, loginWithSms } from "../service/login";
- import {
- setLocal,
- getLocal,
- navigatorLanguage,
- styleUrl,
- } from "../common/js/utils";
- import sHeader from "../components/SimpleHeader";
- import { useRoute, useRouter } from "vue-router";
- import { useI18n } from "vue-i18n";
- import defaultLogo from "../assets/login/logo.png";
- import aetiLogo from "../assets/login/aetiLogo.png";
- export default {
- setup() {
- let languageName = ref(getLocal("curLang"));
- const { t } = useI18n();
- const checked = ref(false); // 是否记住密码状态
- const userName = ref("");
- const userPwd = ref("");
- const router = useRouter();
- const route = useRoute();
- const sys = ref(null);
- const currentLan = ref(""); // 当前语言
- const logoName = ref(defaultLogo); // Logo图片名称
- const sysTitle = ref(""); // 页头标题
-
- // 验证码登录相关
- const loginType = ref("password"); // 登录方式:password | sms
- const phoneOrEmail = ref(""); // 手机号或邮箱
- const smsCode = ref(""); // 验证码
- const smsCountdown = ref(0); // 验证码倒计时
- const countdownTimer = ref(null); // 倒计时定时器
- // 手机号/邮箱验证规则
- const phoneOrEmailRules = [
- { required: true, message: t('login.phoneOrEmailInput') },
- {
- validator: (value) => {
- const phoneRegex = /^1[3-9]\d{9}$/;
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
- return phoneRegex.test(value) || emailRegex.test(value);
- },
- message: t('login.phoneOrEmailFormatError')
- }
- ];
- // 切换登录方式
- const switchLoginType = (type) => {
- loginType.value = type;
- // 清空表单数据
- if (type === 'password') {
- phoneOrEmail.value = '';
- smsCode.value = '';
- clearCountdown();
- } else {
- userName.value = '';
- userPwd.value = '';
- }
- };
- // 清除倒计时
- const clearCountdown = () => {
- if (countdownTimer.value) {
- clearInterval(countdownTimer.value);
- countdownTimer.value = null;
- }
- smsCountdown.value = 0;
- };
- // 页面初始化
- onMounted(() => {
- // 加载样式
- styleUrl("login");
- // 如果没有语言缓存
- if (!getLocal("curLang")) {
- // 根据浏览器语言重新缓存到localstorage
- setLocal("curLang", navigatorLanguage());
- languageName.value = getLocal("curLang");
- }
- if (route.query.relation_admin_id) {
- getSysFun();
- }
- const savedCredentials = localStorage.getItem("savedCredentials");
- if (savedCredentials) {
- checked.value = true;
- const { savedUsername, savedPassword } = JSON.parse(savedCredentials);
- userName.value = savedUsername;
- userPwd.value = savedPassword;
- }
- getDomainFunc();
- });
- const getDomainFunc = async () => {
- const currentDomain = window.location.href;
- // console.log("href >>>", currentDomain);
- // console.log("hostname >>>", window.location.hostname);
- switch (true) {
- case currentDomain.includes("/aeti/"): // aeti是美国孙总portalmcc.com.cn
- logoName.value = aetiLogo;
- sysTitle.value = "AETI GLOBAL";
- break;
- default:
- logoName.value = defaultLogo;
- sysTitle.value = t("public.sysName");
- }
- };
- const getSysFun = async () => {
- const { data } = await getSys({
- relationAdminId: route.query.relation_admin_id,
- });
- if (data.code === "00000") {
- data.data.relationAdminId = route.query.relation_admin_id;
- setLocal("loginSys", JSON.stringify(data.data));
- sys.value = data.data;
- console.log("sys.value >>>", sys.value);
- }
- };
- // 发送验证码
- const sendSmsCode = async () => {
- if (!phoneOrEmail.value) {
- showFailToast(t('login.phoneOrEmailInput'));
- return;
- }
- try {
- const { data } = await sendSmsCodeApi({
- phoneOrEmail: phoneOrEmail.value,
- hostName: "Sunzee",
- });
- if (data.code === "00000") {
- showSuccessToast(t('login.smsCodeSent'));
- startCountdown();
- } else {
- showFailToast(data.message || t('login.smsCodeSendFailed'));
- }
- } catch (error) {
- showFailToast(t('login.smsCodeSendFailed'));
- }
- };
- // 开始倒计时
- const startCountdown = () => {
- smsCountdown.value = 60;
- countdownTimer.value = setInterval(() => {
- smsCountdown.value--;
- if (smsCountdown.value <= 0) {
- clearCountdown();
- }
- }, 1000);
- };
- // 登录
- const onSubmit = async (values) => {
- if (loginType.value === 'password') {
- // 密码登录
- const { data } = await loginSys({
- username: values.userName,
- password: md5(values.userPwd),
- hostName: "Sunzee",
- });
- console.log(checked.value);
- if (data.code === "00000") {
- setLocal("loginUser", JSON.stringify(data.data));
- if (checked.value) {
- const savedCredentials = JSON.stringify({
- savedUsername: values.userName,
- savedPassword: values.userPwd,
- });
- localStorage.setItem("savedCredentials", savedCredentials);
- } else {
- const savedCredentials = localStorage.getItem("savedCredentials");
- if (savedCredentials) {
- localStorage.removeItem("savedCredentials", savedCredentials);
- }
- }
- showSuccessToast(t("login.loginSucess"));
- localStorage.setItem("firstLogin", true);
- // 需要刷新页面,否则 axios.js 文件里的 token 不会被重置
- // window.location.href = '/shenze/';
- setTimeout(() => {
- router.push("/home");
- }, 200);
- } else {
- showFailToast(data.message);
- }
- } else {
- // 验证码登录
- const { data } = await loginWithSms({
- phoneOrEmail: values.phoneOrEmail,
- code: values.smsCode,
- hostName: "Sunzee",
- });
- if (data.code === "00000") {
- setLocal("loginUser", JSON.stringify(data.data));
- showSuccessToast(t("login.loginSucess"));
- localStorage.setItem("firstLogin", true);
- clearCountdown();
- setTimeout(() => {
- router.push("/home");
- }, 200);
- } else {
- showFailToast(data.message);
- }
- }
- };
- // 跳转注册页面
- const registerClick = async () => {
- await router.push("/register");
- };
- // 跳转忘记密码页面
- const forgetPassword = async () => {
- await router.push("/forgetpassword");
- };
- const state = reactive({
- isLoading: false,
- });
- // 微信登录
- const wxLoginHandler = async () => {
- state.isLoading = true;
- try {
- // 用户静默授权,获取 用户信息
- const { data } = await getOpenid({ hostName: "Sunzee" });
- console.log("微信登录:", data);
- if (data.code === "00000") {
- window.location.href = data.data;
- } else {
- showFailToast("微信登录失败:" + data.message);
- }
- } catch (error) {
- handleError(error.message || "微信登录失败,请重试");
- } finally {
- state.isLoading = false;
- }
- };
- const isInWeChat = computed(() => {
- const ua = window.navigator.userAgent.toLowerCase();
- return new RegExp("micromessenger").test(ua);
- });
- const handleError = (errMsg) => {
- showDialog({
- title: "错误提示",
- message: errMsg,
- });
- };
- return {
- checked,
- userName,
- userPwd,
- onSubmit,
- registerClick,
- forgetPassword,
- sys,
- isInWeChat,
- state,
- wxLoginHandler,
- getOpenid,
- currentLan,
- logoName,
- sysTitle,
- // 验证码登录相关
- loginType,
- phoneOrEmail,
- smsCode,
- smsCountdown,
- phoneOrEmailRules,
- switchLoginType,
- sendSmsCode,
- };
- },
- components: {
- sHeader,
- [Button.name]: Button,
- [Dialog.name]: Dialog,
- },
- };
- </script>
- <style lang="less" scoped>
- @theme-color: #4d6add;
- .login-container {
- display: flex;
- flex-direction: column;
- height: 100vh;
- overflow: hidden;
- }
- .login-content {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- padding-top: 30px;
- // justify-content: center;
- background: #f5f6fa;
- height: calc(100% - 45px);
- overflow: auto;
- overflow-x: hidden;
- }
- .logo-section {
- margin-bottom: 20px;
- .logo-container {
- width: 120px;
- height: 120px;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #fff;
- box-shadow: 0 4px 20px rgba(2, 77, 163, 0.25);
- border: 2px solid rgba(2, 77, 163, 0.1);
- padding: 10px;
- .system-logo {
- width: 90%;
- }
- }
- }
- .form-container {
- width: 100%;
- max-width: 400px;
- .login-type-tabs {
- display: flex;
- margin: 15px 15px 0 15px;
- background-color: #f5f9ff;
- border-radius: 12px;
- padding: 4px;
- margin-bottom: 0;
- .tab-item {
- flex: 1;
- text-align: center;
- padding: 12px 0;
- border-radius: 8px;
- font-size: 16px;
- font-weight: 500;
- color: #666;
- cursor: pointer;
- transition: all 0.3s ease;
- &.active {
- background-color: white;
- color: @theme-color;
- box-shadow: 0 2px 8px rgba(2, 77, 163, 0.15);
- }
- &:hover:not(.active) {
- color: @theme-color;
- }
- }
- }
- .login-form {
- margin: 15px;
- background-color: white;
- border-radius: 20px;
- padding: 20px;
- box-shadow: 0 8px 30px rgba(2, 77, 163, 0.15);
- }
- .form-field {
- margin-bottom: 22px;
- padding: 16px 15px;
- background-color: #f5f9ff;
- border-radius: 12px;
- :deep(.van-field__control) {
- padding-left: 10px;
- font-size: 16px;
- }
- .field-icon {
- color: @theme-color;
- margin-right: 8px;
- }
- :deep(.van-field__error-message) {
- margin-left: 10px;
- }
- &.sms-field {
- :deep(.van-field__button) {
- padding-left: 10px;
- }
- :deep(.van-field__left-icon) {
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .sms-button {
- background: linear-gradient(135deg, @theme-color, #0f6fee);
- border: none;
- border-radius: 8px;
- color: white;
- font-size: 14px;
- font-weight: 500;
- min-width: 100px;
- height: 36px;
- box-shadow: 0 2px 8px rgba(2, 77, 163, 0.3);
- &:disabled {
- background: #ccc;
- color: #999;
- box-shadow: none;
- }
- &:active:not(:disabled) {
- opacity: 0.9;
- transform: translateY(1px);
- }
- }
- }
- }
- }
- .password-options {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: -8px;
- margin-bottom: 20px;
- .remember-me {
- :deep(.van-checkbox__label) {
- color: #666;
- font-size: 14px;
- }
- :deep(.van-checkbox__icon--checked .van-icon) {
- background-color: @theme-color;
- border-color: @theme-color;
- }
- }
- .forgot-password {
- display: flex;
- align-items: center;
- color: @theme-color;
- font-size: 14px;
- cursor: pointer;
- span {
- margin-right: 4px;
- }
- &:hover {
- text-decoration: underline;
- }
- }
- }
- .login-button {
- height: 50px;
- font-size: 18px;
- font-weight: 500;
- color: white;
- background: linear-gradient(135deg, @theme-color, #0f6fee);
- border: none;
- margin-top: 15px;
- margin-bottom: 20px;
- box-shadow: 0 4px 15px rgba(2, 77, 163, 0.3);
- &:active {
- opacity: 0.95;
- transform: translateY(1px);
- }
- }
- .wechat-login {
- margin: 20px 0;
- .wechat-button {
- height: 50px;
- background-color: #07c160;
- border: none;
- color: white;
- font-weight: 500;
- &:active {
- background-color: #06ae56;
- }
- }
- .wechat-container {
- display: flex;
- align-items: center;
- justify-content: center;
- .wechat-icon {
- margin-right: 10px;
- color: white;
- }
- }
- }
- .register-option {
- text-align: center;
- margin-top: 25px;
- color: #666;
- font-size: 15px;
- .register-link {
- color: @theme-color;
- font-weight: 500;
- margin-left: 8px;
- cursor: pointer;
- &:hover {
- text-decoration: underline;
- }
- }
- }
- .footer-container {
- text-align: center;
- padding: 15px 0;
- .app-version {
- font-size: 13px;
- color: #888;
- margin-bottom: 5px;
- }
- .copyright {
- font-size: 12px;
- color: #999;
- }
- }
- @media (max-width: 480px) {
- .logo-section .logo-container {
- width: 150px;
- height: 150px;
- }
- .login-button,
- .wechat-button {
- height: 46px;
- font-size: 16px;
- }
- .form-field {
- padding: 12px 15px;
- }
- }
- </style>
|