login.vue 13 KB

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