Account.vue 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <template>
  2. <el-form size="large" class="login-content-form" ref="ruleFormRef" :model="state.ruleForm" @submit.native.prevent>
  3. <motion :delay="100">
  4. <el-form-item class="mb30" prop="username" :rules="[{ required: true, message: '请填写账号', trigger: 'blur' }]">
  5. <el-input
  6. type="text"
  7. class="inputDeep"
  8. placeholder="请填写账号"
  9. v-model="state.ruleForm.username"
  10. clearable
  11. @keyup.enter="onSignIn(ruleFormRef)"
  12. autocomplete="off"
  13. @input="blurUserName"
  14. >
  15. <template #prefix>
  16. <SvgIcon name="ele-User" class="el-input__icon" />
  17. </template>
  18. </el-input>
  19. </el-form-item>
  20. </motion>
  21. <motion :delay="200">
  22. <el-form-item class="mb30" prop="password" :rules="[{ required: true, message: '请填写密码', trigger: 'blur' }]">
  23. <el-input
  24. class="inputDeep"
  25. clearable
  26. show-password
  27. placeholder="请填写密码"
  28. v-model="state.ruleForm.password"
  29. @keyup.enter="onSignIn(ruleFormRef)"
  30. autocomplete="off"
  31. >
  32. <template #prefix>
  33. <SvgIcon name="ele-Unlock" class="el-input__icon" />
  34. </template>
  35. </el-input>
  36. </el-form-item>
  37. </motion>
  38. <motion :delay="400" v-if="themeConfig.isLoginMessageCode">
  39. <el-form-item prop="msgCode" class="mb30" :rules="[{ required: msgCodeRequired, message: '请填写短信验证码', trigger: 'blur' }]">
  40. <el-col :span="11">
  41. <el-input
  42. type="text"
  43. maxlength="6"
  44. placeholder="请填写短信验证码"
  45. v-model="state.ruleForm.msgCode"
  46. clearable
  47. autocomplete="off"
  48. class="inputDeep"
  49. @keyup.enter="onSignIn(ruleFormRef)"
  50. >
  51. <template #prefix>
  52. <SvgIcon name="ele-ChatDotSquare" class="el-input__icon" />
  53. </template>
  54. </el-input>
  55. </el-col>
  56. <el-col :span="12" :offset="1">
  57. <el-button class="login-content-code w100" :disabled="isDisabled" @click="getIdentifyCodeBtn">{{
  58. isDisabled ? countText : click
  59. }}</el-button>
  60. </el-col>
  61. </el-form-item>
  62. </motion>
  63. <motion :delay="400">
  64. <el-form-item>
  65. <el-button type="primary" class="login-content-submit" round @click="onSignIn(ruleFormRef)" :loading="state.loading">登录</el-button>
  66. <puzzle-Code :show="showCode" @success="success" @close="close" @fail="fail" :imgs="imgList"></puzzle-Code>
  67. </el-form-item>
  68. </motion>
  69. <motion :delay="500"> 运营管理系统 <span class="color-danger font-bold">v5.0</span> </motion>
  70. <motion :delay="500">
  71. <div class="login-msg">
  72. <div>联系管理员<b>重置密码</b></div>
  73. <!-- <el-button link type="primary" class="font16" @click="forgetPwd">忘记密码</el-button> -->
  74. </div>
  75. </motion>
  76. </el-form>
  77. </template>
  78. <script setup lang="ts" name="loginAccount">
  79. import { reactive, computed, ref, defineAsyncComponent } from 'vue';
  80. import { useRoute, useRouter } from 'vue-router';
  81. import { ElMessage, ElNotification } from 'element-plus';
  82. import { storeToRefs } from 'pinia';
  83. import { useThemeConfig } from '@/stores/themeConfig';
  84. import { initFrontEndControlRoutes } from '@/router/frontEnd';
  85. import { initBackEndControlRoutes } from '@/router/backEnd';
  86. import { Session, Local, Cookie } from '@/utils/storage';
  87. import { formatAxis } from '@/utils/formatTime';
  88. import { NextLoading } from '@/utils/loading';
  89. import type { FormInstance } from 'element-plus';
  90. import { sendCode, signIn, whiteList } from '@/api/login';
  91. import { JSEncrypt } from 'jsencrypt'; // rsa加密
  92. import { getImageUrl, throttle } from '@/utils/tools';
  93. import Motion from '@/utils/motion';
  94. //引入'vue3-puzzle-vcode'插件
  95. import puzzleCode from 'vue3-puzzle-vcode';
  96. // 定义变量内容
  97. const storesThemeConfig = useThemeConfig(); // 主题配置
  98. const { themeConfig } = storeToRefs(storesThemeConfig); // 主题配置
  99. const route = useRoute(); // 路由
  100. const router = useRouter(); // 路由
  101. const state = reactive<any>({
  102. ruleForm: {
  103. username: '', // 账号
  104. password: '', // 密码
  105. msgCode: '', // 短信验证码
  106. },
  107. loading: false, // 加载
  108. });
  109. const ruleFormRef = ref<FormInstance>(); // 表单ref
  110. // 时间获取
  111. const currentTime = computed(() => {
  112. return formatAxis(new Date());
  113. });
  114. const count = ref(300); // 倒计时
  115. const countText = ref('s后重新获取'); // 倒计时文本
  116. const click = ref('获取验证码'); // 点击
  117. const isDisabled = ref(false); // 是否禁用
  118. const msgCodeRequired = ref(false); // 短信验证码是否必填
  119. // 验证账号是否是必须填写短信验证码
  120. const blurUserName = throttle(() => {
  121. if (themeConfig.value.isLoginMessageCode) {
  122. whiteList({ UserName: state.ruleForm.username })
  123. .then((res: any) => {
  124. msgCodeRequired.value = res.result;
  125. isDisabled.value = false;
  126. count.value = 300;
  127. })
  128. .catch(() => {
  129. msgCodeRequired.value = false;
  130. });
  131. }
  132. }, 300);
  133. const getIdentifyCodeBtn = () => {
  134. if (!state.ruleForm.username) {
  135. ruleFormRef.value?.validateField('username');
  136. return;
  137. }
  138. // state.loading = true;
  139. // 获取短信验证码
  140. sendCode(state.ruleForm.username)
  141. .then((res: any) => {
  142. if (res.result != '验证码发送成功') {
  143. countText.value = res.result;
  144. ElNotification({
  145. title: '提示',
  146. message: res.result,
  147. type: 'info',
  148. });
  149. isDisabled.value = true;
  150. } else {
  151. countDown();
  152. ElMessage.success('短信验证码已发送,请注意查收');
  153. }
  154. })
  155. .catch(() => {
  156. // state.loading = false;
  157. });
  158. };
  159. // 倒计时
  160. const countDown = () => {
  161. if (count.value === 0) {
  162. isDisabled.value = false;
  163. click.value = '获取验证码';
  164. count.value = 300;
  165. countText.value = `${count.value}s后重新获取`;
  166. return;
  167. } else {
  168. count.value--;
  169. click.value = count.value + 's后重新获取';
  170. countText.value = `${count.value}s后重新获取`;
  171. isDisabled.value = true;
  172. setTimeout(() => {
  173. countDown();
  174. }, 1000);
  175. }
  176. };
  177. const imgList = [getImageUrl('login/code1.png'), getImageUrl('login/code2.png'), getImageUrl('login/code3.png'), getImageUrl('login/code4.png')];
  178. const showCode = ref(false); // 是否展示验证码
  179. const success = () => {
  180. // 验证成功
  181. showCode.value = false;
  182. state.loading = true;
  183. // 新建一个JSEncrypt对象
  184. const encryptor = new JSEncrypt({ default_key_size: '2048' });
  185. // 设置公钥
  186. const publicKey =
  187. '-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgw+/x6IQPkH0A4eoF63jkLThsOXWyNBdcL9LATGy/G1yTHOr1RyKJB//iNug+V8DIoIHuFTlhgLHDbSqxvRWMONxIIF289riS6bDI4Ox/pFmOfmElFRk0lKGihaTE2Aefd6g/N+RfLLaHWztY+/voVeDTiOIw9y3tokIxjKwuJ/mQ66MkKh78AqQjjSD/3jcBP8ZhMyCJOK9XQcqvhD6WBFWkxlAqKOWggDU7YohfrbNkg3bd0oGE6zCE2EHhkcQbzGCh3lu1zf4TfKMXD+PPrr5JWDNYQTXFQklqgae+Puge7xxZGYRoi5YpIUnkQGm6zpPxhIOdxlz+Yb5geSJUQIDAQAB-----END PUBLIC KEY-----';
  188. encryptor.setPublicKey(publicKey); // publicKey为公钥
  189. // 加密数据
  190. const submitObj = {
  191. username: encryptor.encrypt(state.ruleForm.username),
  192. password: encryptor.encrypt(state.ruleForm.password),
  193. msgCode: encryptor.encrypt(state.ruleForm.msgCode),
  194. };
  195. signIn(submitObj)
  196. .then(async (res: any) => {
  197. //登录
  198. // 存储 token 到浏览器缓存
  199. Cookie.set('token', res.result);
  200. if (!themeConfig.value.isRequestRoutes) {
  201. // 前端控制路由,2、请注意执行顺序
  202. const isNoPower = await initFrontEndControlRoutes();
  203. signInSuccess(isNoPower);
  204. } else {
  205. // 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
  206. // 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
  207. const isNoPower = await initBackEndControlRoutes();
  208. // 执行完 initBackEndControlRoutes,再执行 signInSuccess
  209. signInSuccess(isNoPower);
  210. }
  211. })
  212. .catch(() => {
  213. state.loading = false;
  214. });
  215. };
  216. const close = () => {
  217. // 关闭验证码
  218. showCode.value = false;
  219. };
  220. const fail = () => {
  221. // 验证失败
  222. };
  223. // 登录
  224. const onSignIn = throttle(async (formEl: FormInstance | undefined) => {
  225. if (!formEl) return;
  226. await formEl.validate((valid: boolean) => {
  227. if (!valid) return;
  228. showCode.value = true;
  229. });
  230. }, 1000);
  231. // 登录成功后的跳转
  232. const signInSuccess = (isNoPower: boolean | undefined) => {
  233. if (isNoPower) {
  234. state.loading = false;
  235. ElNotification({
  236. title: '提示',
  237. message: '抱歉,您没有登录权限,请联系管理员',
  238. type: 'warning',
  239. });
  240. Session.clear();
  241. Cookie.clear();
  242. Local.clear();
  243. } else {
  244. // 初始化登录成功时间问候语
  245. let currentTimeInfo = currentTime.value;
  246. // 登录成功,跳到转首页
  247. /*// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
  248. if (route.query?.redirect) {
  249. router.push({
  250. path: <string>route.query?.redirect,
  251. query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
  252. });
  253. } else {
  254. router.push('/');
  255. }*/
  256. router.push('/');
  257. // 设置登录成功后的时间问候语
  258. Cookie.set('userName', state.ruleForm.username);
  259. // 登录成功提示
  260. // 关闭 loading
  261. state.loading = true;
  262. const signInText = '欢迎回来!';
  263. ElNotification({
  264. type: 'success',
  265. title: `${currentTimeInfo}`,
  266. message: `${signInText}`,
  267. });
  268. NextLoading.start();
  269. }
  270. };
  271. </script>
  272. <style scoped lang="scss">
  273. .inputDeep {
  274. :deep(.el-input__wrapper) {
  275. box-shadow: 0 0 0 0 var(--el-input-border-color, var(--el-border-color)) inset;
  276. border-radius: 0;
  277. border-bottom: 1px solid var(--el-input-border-color, var(--el-border-color));
  278. }
  279. :deep(.el-form-item.is-error .el-input__wrapper.is-focus) {
  280. box-shadow: 0 0 0 0 var(--el-input-border-color, var(--el-border-color)) inset;
  281. }
  282. }
  283. .login-content-form {
  284. margin-top: 20px;
  285. font-size: var(--el-font-size-medium);
  286. :deep(.el-input--large) {
  287. font-size: var(--el-font-size-medium);
  288. }
  289. :deep(.el-form-item__error) {
  290. font-size: var(--el-font-size-medium);
  291. }
  292. .login-content-submit {
  293. width: 100%;
  294. margin-top: 25px;
  295. height: 50px;
  296. border-radius: 30px;
  297. font-size: 16px;
  298. letter-spacing: 5px;
  299. }
  300. .login-msg {
  301. margin-top: 10px;
  302. display: flex;
  303. justify-content: space-between;
  304. align-items: center;
  305. color: var(--el-color-primary);
  306. b {
  307. color: #999;
  308. padding-left: 4px;
  309. }
  310. }
  311. }
  312. </style>