login.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. <template>
  2. <div class="login">
  3. <img @click="onPath('/')" class="head" src="@/assets/images/head.png" alt="" />
  4. <div class="login-info flex-row-between">
  5. <div></div>
  6. <div class="login-bos">
  7. <el-form ref="loginRef" :model="loginForm" :rules="loginRules">
  8. <div class="login-type flex-row-between">
  9. <div :class="type == 1 ? 'hig' : ''" @click="onType(1)">账户登录</div>
  10. <div class="border"></div>
  11. <div :class="type == 2 ? 'hig' : ''" @click="onType(2)">验证码登录</div>
  12. </div>
  13. <template v-if="type == 1">
  14. <el-form-item prop="username">
  15. <el-input class="login-input" v-model="loginForm.username" placeholder="员工编号/手机号码">
  16. <template #prefix>
  17. <el-icon><User /></el-icon>
  18. </template>
  19. </el-input>
  20. </el-form-item>
  21. <el-form-item prop="password">
  22. <el-input class="login-input" type="password" v-model="loginForm.password" placeholder="输入登录密码" @keyup.enter="handleLogin">
  23. <template #prefix>
  24. <el-icon><Lock /></el-icon>
  25. </template>
  26. </el-input>
  27. </el-form-item>
  28. </template>
  29. <template v-else>
  30. <el-form-item prop="mobile">
  31. <el-input :maxlength="11" class="login-input" v-model="loginForm.mobile" placeholder="手机号">
  32. <template #prefix>
  33. <el-icon><Iphone /></el-icon>
  34. </template>
  35. </el-input>
  36. </el-form-item>
  37. <el-form-item prop="smsCode">
  38. <el-input :maxlength="6" class="login-input" v-model="loginForm.smsCode" placeholder="验证码">
  39. <template #prefix>
  40. <el-icon><Message /></el-icon>
  41. </template>
  42. <template #suffix>
  43. <span @click="sendSmsCode" :class="['code', countdown > 0 ? 'disabled' : '']">
  44. {{ codeText }}
  45. </span>
  46. </template>
  47. </el-input>
  48. </el-form-item>
  49. </template>
  50. <el-form-item>
  51. <el-button class="login-btn" type="primary" :loading="loading" @click.prevent="handleLogin">
  52. <span v-if="!loading">登录</span>
  53. <span v-else>登录中...</span>
  54. </el-button>
  55. </el-form-item>
  56. <div class="login-text flex-row-between">
  57. <div @click="handleForgetPassword">忘记密码?</div>
  58. <div class="border"></div>
  59. <div @click="onPath('/breg')">企业注册</div>
  60. <div @click="onPath('/reg')">个人注册</div>
  61. <!-- <router-link to="/register" class="register-link">新用户注册</router-link> -->
  62. </div>
  63. </el-form>
  64. </div>
  65. </div>
  66. <div class="login-foot flex-column-between">
  67. <div class="font-bos flex-row-center">
  68. <div>客户管理</div>
  69. <div style="margin: 0 10px">|</div>
  70. <div>供应商合作</div>
  71. <div style="margin: 0 10px">|</div>
  72. <div>关于我们</div>
  73. <div style="margin: 0 10px">|</div>
  74. <div>帮助中心</div>
  75. <div style="margin: 0 10px">|</div>
  76. <div>联系我们</div>
  77. </div>
  78. <div class="font-box">CopyRight @ 优易365 2026</div>
  79. </div>
  80. </div>
  81. </template>
  82. <script setup lang="ts">
  83. import { useUserStore } from '@/store/modules/user';
  84. import { smsCode } from '@/api/breg/index';
  85. import { LoginData } from '@/api/types';
  86. import { to } from 'await-to-js';
  87. import { User, Lock, Iphone, Message } from '@element-plus/icons-vue';
  88. import { onPath } from '@/utils/siteConfig';
  89. const userStore = useUserStore();
  90. const router = useRouter();
  91. const type = ref<number>(1);
  92. const loading = ref(false);
  93. const timer = ref<any>(null);
  94. const loginRef = ref<ElFormInstance>();
  95. const redirect = ref('/');
  96. const codeText = ref<string>('发送验证码');
  97. const countdown = ref<number>(0);
  98. const loginForm = ref<LoginData>({
  99. username: import.meta.env.VITE_APP_USERNAME,
  100. password: import.meta.env.VITE_APP_PASSWORD,
  101. mobile: '',
  102. smsCode: '',
  103. tenantId: '000000',
  104. rememberMe: false,
  105. code: '',
  106. uuid: '',
  107. clientId: '',
  108. grantType: 'password'
  109. } as LoginData);
  110. const loginRules: ElFormRules = {
  111. username: [{ required: true, trigger: 'blur', message: '请输入员工编号/手机号码' }],
  112. password: [{ required: true, trigger: 'blur', message: '请输入登录密码' }],
  113. mobile: [{ required: true, trigger: 'blur', message: '请输入手机号' }],
  114. smsCode: [{ required: true, trigger: 'blur', message: '请输入验证码' }]
  115. };
  116. const onType = (val: number) => {
  117. type.value = val;
  118. // 切换登录类型时更新 grantType
  119. loginForm.value.grantType = val === 1 ? 'password' : 'sms';
  120. };
  121. /**
  122. * 监听路由变化,获取重定向地址
  123. */
  124. watch(
  125. () => router.currentRoute.value,
  126. (newRoute: any) => {
  127. redirect.value = newRoute.query && newRoute.query.redirect && decodeURIComponent(newRoute.query.redirect);
  128. },
  129. { immediate: true }
  130. );
  131. /**
  132. * 处理登录
  133. */
  134. const handleLogin = () => {
  135. loginRef.value?.validate(async (valid: boolean) => {
  136. if (valid) {
  137. loading.value = true;
  138. // 勾选记住密码时保存到 localStorage
  139. if (loginForm.value.rememberMe) {
  140. localStorage.setItem('username', String(loginForm.value.username));
  141. localStorage.setItem('password', String(loginForm.value.password));
  142. localStorage.setItem('rememberMe', String(loginForm.value.rememberMe));
  143. } else {
  144. localStorage.removeItem('username');
  145. localStorage.removeItem('password');
  146. localStorage.removeItem('rememberMe');
  147. }
  148. // 调用登录
  149. const [err] = await to(userStore.login(loginForm.value));
  150. if (!err) {
  151. const redirectUrl = redirect.value || '/';
  152. onPath(redirectUrl);
  153. loading.value = false;
  154. } else {
  155. loading.value = false;
  156. }
  157. }
  158. });
  159. };
  160. // 启动倒计时
  161. const startCountdown = () => {
  162. countdown.value = 60;
  163. codeText.value = `${countdown.value}s 后重新发送`;
  164. timer.value = setInterval(() => {
  165. countdown.value--;
  166. if (countdown.value > 0) {
  167. codeText.value = `${countdown.value}s 后重新发送`;
  168. } else {
  169. clearInterval(timer.value);
  170. timer.value = null;
  171. codeText.value = '发送验证码';
  172. }
  173. }, 1000);
  174. };
  175. /**
  176. * 发送短信验证码
  177. */
  178. const sendSmsCode = () => {
  179. // 防止倒计时期间重复点击
  180. if (countdown.value > 0) return;
  181. const phone = loginForm.value.mobile;
  182. // 1. 基础格式校验
  183. if (!validateMobile(phone)) {
  184. ElMessage({
  185. message: '请输入正确的手机号码',
  186. type: 'warning'
  187. });
  188. return;
  189. }
  190. // TODO: 调用发送短信验证码接口
  191. smsCode({ phonenumber: phone })
  192. .then((smsRes: any) => {
  193. if (smsRes.code === 200) {
  194. ElMessage({
  195. message: '验证码已发送',
  196. type: 'success'
  197. });
  198. startCountdown(); // 开始倒计时
  199. } else {
  200. // 发送短信接口业务失败
  201. ElMessage.error(smsRes.msg || '发送验证码失败');
  202. }
  203. })
  204. .catch((err: any) => {
  205. // 发送短信接口网络错误或异常
  206. ElMessage.error(err.msg || '发送验证码请求异常');
  207. });
  208. };
  209. // 验证手机号
  210. const validateMobile = (phone: any) => {
  211. const reg = /^1[3-9]\d{9}$/;
  212. return reg.test(phone);
  213. };
  214. /**
  215. * 忘记密码
  216. */
  217. const handleForgetPassword = () => {
  218. ElMessage.info('请联系管理员重置密码');
  219. };
  220. /**
  221. * 获取保存的登录信息
  222. */
  223. const getLoginData = () => {
  224. const username = localStorage.getItem('username');
  225. const password = localStorage.getItem('password');
  226. const rememberMe = localStorage.getItem('rememberMe');
  227. if (username && password && rememberMe) {
  228. loginForm.value.username = username;
  229. loginForm.value.password = password;
  230. loginForm.value.rememberMe = Boolean(rememberMe);
  231. }
  232. };
  233. onMounted(() => {
  234. getLoginData();
  235. });
  236. </script>
  237. <style lang="scss" scoped>
  238. .login {
  239. height: 100%;
  240. min-height: 700px;
  241. width: 100%;
  242. background-image: url('@/assets/images/login/login1.png');
  243. overflow: auto;
  244. background-position: center center;
  245. background-repeat: no-repeat;
  246. background-size: cover;
  247. display: flex;
  248. flex-direction: column;
  249. justify-content: space-between;
  250. padding: 20px 0;
  251. .head {
  252. width: 185px;
  253. height: 90px;
  254. margin-left: 84px;
  255. cursor: pointer;
  256. }
  257. .login-info {
  258. width: 100%;
  259. height: 600px;
  260. background-image: url('@/assets/images/login/login2.png');
  261. overflow: hidden;
  262. background-position: center center;
  263. background-repeat: no-repeat;
  264. background-size: cover;
  265. margin-top: 10px;
  266. min-width: 1200px;
  267. padding: 0 5%;
  268. .login-bos {
  269. width: 520px;
  270. height: 420px;
  271. background: #ffffff;
  272. border-radius: 30px 30px 30px 30px;
  273. padding: 40px 85px 0 85px;
  274. :deep(.el-form-item) {
  275. margin-bottom: 18px;
  276. }
  277. :deep(.el-form-item__error) {
  278. padding-top: 4px;
  279. }
  280. .login-type {
  281. font-weight: 600;
  282. font-size: 22px;
  283. color: #101828;
  284. padding: 0 67px;
  285. margin-bottom: 40px;
  286. div {
  287. cursor: pointer;
  288. }
  289. .hig {
  290. color: #e7000b;
  291. }
  292. .border {
  293. width: 1px;
  294. height: 16px;
  295. background: #e6e8ec;
  296. }
  297. }
  298. .login-input {
  299. width: 350px;
  300. height: 50px;
  301. font-size: 16px;
  302. .code {
  303. font-size: 14px;
  304. color: #e7000b;
  305. cursor: pointer;
  306. }
  307. }
  308. :deep(.el-input__wrapper) {
  309. border: none;
  310. box-shadow: none;
  311. outline: none;
  312. background: #f4f6f8;
  313. }
  314. :deep(.el-input__prefix) {
  315. font-size: 18px;
  316. color: #9ca3af;
  317. }
  318. .login-btn {
  319. width: 350px;
  320. height: 50px;
  321. margin-top: 20px;
  322. font-size: 16px;
  323. background-color: #c8102e;
  324. border-color: #c8102e;
  325. &:hover {
  326. background-color: #a50d26;
  327. border-color: #a50d26;
  328. }
  329. }
  330. .login-text {
  331. font-size: 14px;
  332. color: #6a7282;
  333. padding: 0 80px;
  334. margin-top: 14px;
  335. div {
  336. cursor: pointer;
  337. &:hover {
  338. color: #c8102e;
  339. }
  340. }
  341. .border {
  342. width: 1px;
  343. height: 12px;
  344. background: #e6e8ec;
  345. }
  346. .register-link {
  347. color: #c8102e;
  348. text-decoration: none;
  349. &:hover {
  350. text-decoration: underline;
  351. }
  352. }
  353. }
  354. }
  355. }
  356. .font-bos {
  357. width: 100%;
  358. font-size: 13px;
  359. color: #999999;
  360. margin-top: 30px;
  361. }
  362. .font-box {
  363. width: 100%;
  364. font-size: 13px;
  365. color: #999999;
  366. margin-top: 20px;
  367. text-align: center;
  368. }
  369. }
  370. </style>