Browse Source

✅ test: 企业微信扫码登录

WanGxC 1 year ago
parent
commit
121c44937e

+ 1 - 0
src/i18n/pages/login/zh-cn.ts

@@ -3,6 +3,7 @@ export default {
 	label: {
 		one1: '用户名登录',
 		two2: '手机号登录',
+		qrcode: '企业微信扫码登录',
 	},
 	link: {
 		one3: '第三方登录',

+ 8 - 0
src/views/demo/api.ts

@@ -40,3 +40,11 @@ export function DelObj(id: number) {
         data: { id },
     });
 }
+
+export function getWechatCode() {
+    return request({
+        url: '/api/system/qrcode/link',
+        method: 'get',
+        // params: query,
+    });
+}

+ 100 - 85
src/views/demo/index.vue

@@ -4,105 +4,120 @@ import { Connection, Picture as IconPicture } from '@element-plus/icons-vue'
 import { getParentAsin } from '../adManage/sp/api'
 import { useShopInfo } from '../../stores/shopInfo'
 import { storeToRefs } from 'pinia'
+import { getWechatCode } from './api'
 
+// const count = ref(0)
+// const load = () => {
+//   count.value += 2
+// }
+// const parentLoading = ref(false)
+// const shopInfo = useShopInfo()
+// const { profile } = storeToRefs(shopInfo)
+//
+// const gridOptions = <any>reactive({
+//   height: 500,
+//   align: null,
+//   columns: [
+//     { type: 'checkbox', width: 30 },
+//     { field: 'parentAsin', title: '父ASIN', slots: { default: 'parentAsin_default' } },
+//   ],
+//   data: [],
+// })
+//
+// async function fetchParentAsin() {
+//   parentLoading.value = true
+//   const body = { profileId: profile.value.profile_id }
+//   try {
+//     const response = await getParentAsin(body)
+//     gridOptions.data = response.data
+//   } catch (error) {
+//     console.log('error:', error)
+//   } finally {
+//     parentLoading.value = false
+//   }
+// }
+//
+// function handleScroll(event: any) {
+//   // 获取滚动容器
+//   const target = event.$event.target
+//   // console.log('target:', target)
+//   // // 计算是否达到底部:容器高度 + 滚动顶部距离 >= 滚动内容的总高度
+//   if (target.scrollHeight - (target.scrollTop + target.clientHeight) < 1) {
+//     parentLoading.value = true
+//     console.log('执行了')
+//     load()
+//     parentLoading.value = false
+//   }
+// }
+//
+// onMounted(() => {
+//   fetchParentAsin()
+// })
 
-const count = ref(0)
-const load = () => {
-  count.value += 2
-}
-const parentLoading = ref(false)
-const shopInfo = useShopInfo()
-const { profile } = storeToRefs(shopInfo)
-
-const gridOptions = <any>reactive({
-  height: 500,
-  align: null,
-  columns: [
-    { type: 'checkbox', width: 30 },
-    { field: 'parentAsin', title: '父ASIN', slots: { default: 'parentAsin_default' } },
-  ],
-  data: [],
-})
-
-async function fetchParentAsin() {
-  parentLoading.value = true
-  const body = { profileId: profile.value.profile_id }
+async function getCode() {
   try {
-    const response = await getParentAsin(body)
-    gridOptions.data = response.data
+    const response = await getWechatCode()
+    console.log('response', response.data)
+    setTimeout(() => {
+      window.location.href = response.data
+    }, 0)
   } catch (error) {
     console.log('error:', error)
-  } finally {
-    parentLoading.value = false
   }
 }
-
-function handleScroll(event: any) {
-  // 获取滚动容器
-  const target = event.$event.target
-  // console.log('target:', target)
-  // // 计算是否达到底部:容器高度 + 滚动顶部距离 >= 滚动内容的总高度
-  if (target.scrollHeight - (target.scrollTop + target.clientHeight) < 1) {
-    parentLoading.value = true
-    console.log('执行了')
-    load()
-    parentLoading.value = false
-  }
-}
-
-onMounted(() => {
-  fetchParentAsin()
-})
 </script>
 
 <template>
-  <ul v-infinite-scroll="load" class="infinite-list" style="overflow: auto">
-    <li v-for="i in count" :key="i" class="infinite-list-item">{{ i }}</li>
-  </ul>
-  <vxe-grid
-      v-loading="parentLoading"
-      v-bind="gridOptions"
-      :header-row-style="changeHeaderCellStyle"
-      :header-cell-style="changeHeaderCellStyle"
-      class="ml-2 mb-2 mt-0 border border-r-0 rounded-bl-md rounded-tl-md overflow-hidden"
-      style="flex: 0 1 260px; overflow: auto"
-      @scroll.native="handleScroll">
-    <template #parentAsin_default="{ row }">
-      <div style="display: flex">
-        <el-image class="w-14 h-14 mr-1 border rounded" v-if="row.Image" :src="row.Image" alt="" fit="contain"/>
-        <el-image v-else class="w-12 h-12 mr-1 border rounded">
-          <template #error>
-            <div class="image-slot">
-              <el-icon>
-                <icon-picture/>
-              </el-icon>
-            </div>
-          </template>
-        </el-image>
+  <!--<ul v-infinite-scroll="load" class="infinite-list" style="overflow: auto">-->
+  <!--  <li v-for="i in count" :key="i" class="infinite-list-item">{{ i }}</li>-->
+  <!--</ul>-->
+  <!--<vxe-grid-->
+  <!--    v-loading="parentLoading"-->
+  <!--    v-bind="gridOptions"-->
+  <!--    :header-row-style="changeHeaderCellStyle"-->
+  <!--    :header-cell-style="changeHeaderCellStyle"-->
+  <!--    class="ml-2 mb-2 mt-0 border border-r-0 rounded-bl-md rounded-tl-md overflow-hidden"-->
+  <!--    style="flex: 0 1 260px; overflow: auto"-->
+  <!--    @scroll.native="handleScroll">-->
+  <!--  <template #parentAsin_default="{ row }">-->
+  <!--    <div style="display: flex">-->
+  <!--      <el-image class="w-14 h-14 mr-1 border rounded" v-if="row.Image" :src="row.Image" alt="" fit="contain"/>-->
+  <!--      <el-image v-else class="w-12 h-12 mr-1 border rounded">-->
+  <!--        <template #error>-->
+  <!--          <div class="image-slot">-->
+  <!--            <el-icon>-->
+  <!--              <icon-picture/>-->
+  <!--            </el-icon>-->
+  <!--          </div>-->
+  <!--        </template>-->
+  <!--      </el-image>-->
 
-        <div class="flex flex-col justify-center">
-          <div class="font-medium text-black">{{ row.parentAsin }}</div>
-          <div>
-            ASIN:<span class="text-black">{{ row.asinNum }}</span>
-            <el-link
-                :href="row.amazonUrl"
-                target="_blank"
-                :disabled="!row.amazonUrl"
-                :underline="false"
-                :icon="Connection"
-                :style="{ marginLeft: '2px', marginBottom: '2px', color: row.amazonUrl ? '#3a83f7' : '#c0c4cc' }">
-            </el-link>
-            <span class="text-gray-300 mx-1">|</span>
-            SKU:<span class="text-black">{{ row.skuNum }}</span>
-          </div>
-        </div>
-      </div>
-    </template>
-  </vxe-grid>
+  <!--      <div class="flex flex-col justify-center">-->
+  <!--        <div class="font-medium text-black">{{ row.parentAsin }}</div>-->
+  <!--        <div>-->
+  <!--          ASIN:<span class="text-black">{{ row.asinNum }}</span>-->
+  <!--          <el-link-->
+  <!--              :href="row.amazonUrl"-->
+  <!--              target="_blank"-->
+  <!--              :disabled="!row.amazonUrl"-->
+  <!--              :underline="false"-->
+  <!--              :icon="Connection"-->
+  <!--              :style="{ marginLeft: '2px', marginBottom: '2px', color: row.amazonUrl ? '#3a83f7' : '#c0c4cc' }">-->
+  <!--          </el-link>-->
+  <!--          <span class="text-gray-300 mx-1">|</span>-->
+  <!--          SKU:<span class="text-black">{{ row.skuNum }}</span>-->
+  <!--        </div>-->
+  <!--      </div>-->
+  <!--    </div>-->
+  <!--  </template>-->
+  <!--</vxe-grid>-->
+
+  <div>
+    <el-button type="primary" @click="getCode" class="m-2.5">发送</el-button>
+  </div>
 </template>
 
 <style scoped>
-
 .infinite-list {
   height: 300px;
   padding: 0;

+ 33 - 14
src/views/system/login/api.ts

@@ -1,21 +1,40 @@
-import { request } from "/@/utils/service";
+import { request } from '/@/utils/service'
+import { Local, Session } from '/@/utils/storage'
+
+const token = Session.get('token')
 
 export function getCaptcha() {
-    return request({
-        url: '/api/captcha/',
-        method: 'get',
-    });
+  return request({
+    url: '/api/captcha/',
+    method: 'get',
+  })
 }
+
 export function login(params: object) {
-    return request({
-        url: '/api/login/',
-        method: 'post',
-        data: params
-    });
+  return request({
+    url: '/api/login/',
+    method: 'post',
+    data: params,
+  })
 }
+
 export function getUserInfo() {
-    return request({
-        url: '/api/system/user/user_info/',
-        method: 'get',
-    });
+  return request({
+    url: '/api/system/user/user_info/',
+    method: 'get',
+  })
+}
+
+export function getWorkWeChatCode() {
+  return request({
+    url: '/api/system/wechat/start',
+    method: 'get',
+  })
+}
+export function postWorkWeChatCode(body) {
+  return request({
+    url: '/api/system/wechat/login',
+    method: 'post',
+    data: body
+  })
 }

+ 296 - 243
src/views/system/login/component/account.vue

@@ -1,255 +1,308 @@
 <template>
-	<el-form ref="formRef" size="large" class="login-content-form" :model="state.ruleForm" :rules="rules">
-		<el-form-item class="login-animation1" prop="username">
-			<el-input type="text" :placeholder="$t('message.account.accountPlaceholder1')" v-model="ruleForm.username"
-				clearable autocomplete="off">
-				<template #prefix>
-					<el-icon class="el-input__icon"><ele-User /></el-icon>
-				</template>
-			</el-input>
-		</el-form-item>
-		<el-form-item class="login-animation2" prop="password">
-			<el-input :type="isShowPassword ? 'text' : 'password'" :placeholder="$t('message.account.accountPlaceholder2')"
-				v-model="ruleForm.password">
-				<template #prefix>
-					<el-icon class="el-input__icon"><ele-Unlock /></el-icon>
-				</template>
-				<template #suffix>
-					<i class="iconfont el-input__icon login-content-password"
-						:class="isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
-						@click="isShowPassword = !isShowPassword">
-					</i>
-				</template>
-			</el-input>
-		</el-form-item>
-		<el-form-item class="login-animation3" v-if="isShowCaptcha" prop="captcha">
-			<el-col :span="15">
-				<el-input type="text" maxlength="4" :placeholder="$t('message.account.accountPlaceholder3')"
-					v-model="ruleForm.captcha" clearable autocomplete="off">
-					<template #prefix>
-						<el-icon class="el-input__icon"><ele-Position /></el-icon>
-					</template>
-				</el-input>
-			</el-col>
-			<el-col :span="1"></el-col>
-			<el-col :span="8">
-				<el-button class="login-content-captcha">
-					<el-image :src="ruleForm.captchaImgBase" @click="refreshCaptcha" />
-				</el-button>
-			</el-col>
-		</el-form-item>
-		<el-form-item class="login-animation4">
-			<el-button type="primary" class="login-content-submit" round @keyup.enter="loginClick" @click="loginClick"
-				:loading="loading.signIn">
-				<span>{{ $t('message.account.accountBtnText') }}</span>
-			</el-button>
-		</el-form-item>
-	</el-form>
+  <el-form ref="formRef" size="large" class="login-content-form" :model="state.ruleForm" :rules="rules">
+    <el-form-item class="login-animation1" prop="username">
+      <el-input
+        type="text"
+        :placeholder="$t('message.account.accountPlaceholder1')"
+        v-model="ruleForm.username"
+        clearable
+        autocomplete="off">
+        <template #prefix>
+          <el-icon class="el-input__icon">
+            <ele-User />
+          </el-icon>
+        </template>
+      </el-input>
+    </el-form-item>
+    <el-form-item class="login-animation2" prop="password">
+      <el-input
+        :type="isShowPassword ? 'text' : 'password'"
+        :placeholder="$t('message.account.accountPlaceholder2')"
+        v-model="ruleForm.password">
+        <template #prefix>
+          <el-icon class="el-input__icon">
+            <ele-Unlock />
+          </el-icon>
+        </template>
+        <template #suffix>
+          <i
+            class="iconfont el-input__icon login-content-password"
+            :class="isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'"
+            @click="isShowPassword = !isShowPassword">
+          </i>
+        </template>
+      </el-input>
+    </el-form-item>
+    <el-form-item class="login-animation3" v-if="isShowCaptcha" prop="captcha">
+      <el-col :span="15">
+        <el-input
+          type="text"
+          maxlength="4"
+          :placeholder="$t('message.account.accountPlaceholder3')"
+          v-model="ruleForm.captcha"
+          clearable
+          autocomplete="off">
+          <template #prefix>
+            <el-icon class="el-input__icon">
+              <ele-Position />
+            </el-icon>
+          </template>
+        </el-input>
+      </el-col>
+      <el-col :span="1"></el-col>
+      <el-col :span="8">
+        <el-button class="login-content-captcha">
+          <el-image :src="ruleForm.captchaImgBase" @click="refreshCaptcha" />
+        </el-button>
+      </el-col>
+    </el-form-item>
+    <el-form-item class="login-animation4">
+      <el-button
+        type="primary"
+        class="login-content-submit"
+        round
+        @keyup.enter="loginClick"
+        @click="loginClick"
+        :loading="loading.signIn">
+        <span>{{ $t('message.account.accountBtnText') }}</span>
+      </el-button>
+      <el-button type="primary" class="login-content-submit" round @click="qrLoginClick"> 企业微信扫码登录</el-button>
+    </el-form-item>
+  </el-form>
 </template>
 
 <script lang="ts">
-import { toRefs, reactive, defineComponent, computed, onMounted, onUnmounted, ref } from 'vue';
-import { useRoute, useRouter } from 'vue-router';
-import { ElMessage, FormInstance, FormRules } from 'element-plus';
-import { useI18n } from 'vue-i18n';
-import Cookies from 'js-cookie';
-import { storeToRefs } from 'pinia';
-import { useThemeConfig } from '/@/stores/themeConfig';
-import { initFrontEndControlRoutes } from '/@/router/frontEnd';
-import { initBackEndControlRoutes } from '/@/router/backEnd';
-import { Session } from '/@/utils/storage';
-import { formatAxis } from '/@/utils/formatTime';
-import { NextLoading } from '/@/utils/loading';
-import * as loginApi from '/@/views/system/login/api';
-import { useUserInfo } from '/@/stores/userInfo';
-import { DictionaryStore } from '/@/stores/dictionary';
-import { SystemConfigStore } from '/@/stores/systemConfig';
-import { BtnPermissionStore } from '/@/plugin/permission/store.permission';
-import { Md5 } from 'ts-md5';
-import { errorMessage } from '/@/utils/message';
+import { toRefs, reactive, defineComponent, computed, onMounted, onUnmounted, ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { ElMessage, FormInstance, FormRules } from 'element-plus'
+import { useI18n } from 'vue-i18n'
+import Cookies from 'js-cookie'
+import { storeToRefs } from 'pinia'
+import { useThemeConfig } from '/@/stores/themeConfig'
+import { initFrontEndControlRoutes } from '/@/router/frontEnd'
+import { initBackEndControlRoutes } from '/@/router/backEnd'
+import { Session } from '/@/utils/storage'
+import { formatAxis } from '/@/utils/formatTime'
+import { NextLoading } from '/@/utils/loading'
+import * as loginApi from '/@/views/system/login/api'
+import { useUserInfo } from '/@/stores/userInfo'
+import { DictionaryStore } from '/@/stores/dictionary'
+import { SystemConfigStore } from '/@/stores/systemConfig'
+import { BtnPermissionStore } from '/@/plugin/permission/store.permission'
+import { Md5 } from 'ts-md5'
+import { errorMessage } from '/@/utils/message'
+import { getWorkWeChatCode, postWorkWeChatCode } from '../api'
 
 export default defineComponent({
-	name: 'loginAccount',
-	setup() {
-		const { t } = useI18n();
-		const storesThemeConfig = useThemeConfig();
-		const { themeConfig } = storeToRefs(storesThemeConfig);
-		const { userInfos } = storeToRefs(useUserInfo());
-		const route = useRoute();
-		const router = useRouter();
-		const state = reactive({
-			isShowPassword: false,
-			ruleForm: {
-				username: '',
-				password: '',
-				captcha: '',
-				captchaKey: '',
-				captchaImgBase: '',
-			},
-			loading: {
-				signIn: false,
-			},
-		});
-		const rules = reactive<FormRules>({
-			username: [
-				{ required: true, message: '请填写账号', trigger: 'blur' },
-			],
-			password: [
-				{
-					required: true,
-					message: '请填写密码',
-					trigger: 'blur',
-				},
-			],
-			captcha: [
-				{
-					required: true,
-					message: '请填写验证码',
-					trigger: 'blur',
-				},
-			],
-		})
-		const formRef = ref();
-		// 时间获取
-		const currentTime = computed(() => {
-			return formatAxis(new Date());
-		});
-		// 是否关闭验证码
-		const isShowCaptcha = computed(() => {
-			return SystemConfigStore().systemConfig['base.captcha_state'];
-		});
-
-		const getCaptcha = async () => {
-			loginApi.getCaptcha().then((ret: any) => {
-				state.ruleForm.captchaImgBase = ret.data.image_base;
-				state.ruleForm.captchaKey = ret.data.key;
-			});
-		};
-		const refreshCaptcha = async () => {
-			loginApi.getCaptcha().then((ret: any) => {
-				state.ruleForm.captchaImgBase = ret.data.image_base;
-				state.ruleForm.captchaKey = ret.data.key;
-			});
-		};
-		const loginClick = async () => {
-			if (!formRef.value) return
-			await formRef.value.validate((valid: any) => {
-				if (valid) {
-					loginApi.login({ ...state.ruleForm, password: Md5.hashStr(state.ruleForm.password) }).then((res: any) => {
-						if (res.code === 2000) {
-							Session.set('token', res.data.access);
-							Cookies.set('username', res.data.name);
-							if (!themeConfig.value.isRequestRoutes) {
-								// 前端控制路由,2、请注意执行顺序
-								initFrontEndControlRoutes();
-								loginSuccess();
-							} else {
-								// 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
-								// 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
-								initBackEndControlRoutes();
-								// 执行完 initBackEndControlRoutes,再执行 signInSuccess
-								loginSuccess();
-							}
-						} else if (res.code === 4000) {
-							// 登录错误之后,刷新验证码
-							refreshCaptcha();
-						}
-					});
-				} else {
-					errorMessage("请填写登录信息")
-				}
-			})
-
-		};
-		const getUserInfo = () => {
-			useUserInfo().setUserInfos();
-		};
-
-
-		// 登录成功后的跳转
-		const loginSuccess = () => {
-			//登录成功获取用户信息,获取系统字典数据
-			getUserInfo();
-			//获取所有字典
-			DictionaryStore().getSystemDictionarys();
-
-			// 初始化登录成功时间问候语
-			let currentTimeInfo = currentTime.value;
-			// 登录成功,跳到转首页
-			// 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
-			if (route.query?.redirect) {
-				router.push({
-					path: <string>route.query?.redirect,
-					query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
-				});
-			} else {
-				router.push('/');
-			}
-			// 登录成功提示
-			// 关闭 loading
-			state.loading.signIn = true;
-			const signInText = t('message.signInText');
-			ElMessage.success(`${currentTimeInfo},${signInText}`);
-			// 添加 loading,防止第一次进入界面时出现短暂空白
-			NextLoading.start();
-		};
-		onMounted(() => {
-			getCaptcha();
-			//获取系统配置
-			SystemConfigStore().getSystemConfigs();
-		});
-
-
-		return {
-			refreshCaptcha,
-			loginClick,
-			loginSuccess,
-			isShowCaptcha,
-			state,
-			formRef,
-			rules,
-			...toRefs(state),
-		};
-	},
-});
+  name: 'loginAccount',
+  setup() {
+    const { t } = useI18n()
+    const storesThemeConfig = useThemeConfig()
+    const { themeConfig } = storeToRefs(storesThemeConfig)
+    const { userInfos } = storeToRefs(useUserInfo())
+    const route = useRoute()
+    const router = useRouter()
+    const state = reactive({
+      isShowPassword: false,
+      ruleForm: {
+        username: '',
+        password: '',
+        captcha: '',
+        captchaKey: '',
+        captchaImgBase: '',
+      },
+      loading: {
+        signIn: false,
+      },
+    })
+    const rules = reactive<FormRules>({
+      username: [{ required: true, message: '请填写账号', trigger: 'blur' }],
+      password: [
+        {
+          required: true,
+          message: '请填写密码',
+          trigger: 'blur',
+        },
+      ],
+      captcha: [
+        {
+          required: true,
+          message: '请填写验证码',
+          trigger: 'blur',
+        },
+      ],
+    })
+    const formRef = ref()
+    // 时间获取
+    const currentTime = computed(() => {
+      return formatAxis(new Date())
+    })
+    // 是否关闭验证码
+    const isShowCaptcha = computed(() => {
+      return SystemConfigStore().systemConfig['base.captcha_state']
+    })
+
+    const getCaptcha = async () => {
+      loginApi.getCaptcha().then((ret: any) => {
+        state.ruleForm.captchaImgBase = ret.data.image_base
+        state.ruleForm.captchaKey = ret.data.key
+      })
+    }
+    const refreshCaptcha = async () => {
+      loginApi.getCaptcha().then((ret: any) => {
+        state.ruleForm.captchaImgBase = ret.data.image_base
+        state.ruleForm.captchaKey = ret.data.key
+      })
+    }
+    const loginClick = async () => {
+      if (!formRef.value) return
+      await formRef.value.validate((valid: any) => {
+        if (valid) {
+          loginApi.login({ ...state.ruleForm, password: Md5.hashStr(state.ruleForm.password) }).then((res: any) => {
+            if (res.code === 2000) {
+              Session.set('token', res.data.access)
+              Cookies.set('username', res.data.name)
+              if (!themeConfig.value.isRequestRoutes) {
+                // 前端控制路由,2、请注意执行顺序
+                initFrontEndControlRoutes()
+                loginSuccess()
+              } else {
+                // 模拟后端控制路由,isRequestRoutes 为 true,则开启后端控制路由
+                // 添加完动态路由,再进行 router 跳转,否则可能报错 No match found for location with path "/"
+                initBackEndControlRoutes()
+                // 执行完 initBackEndControlRoutes,再执行 signInSuccess
+                loginSuccess()
+              }
+            } else if (res.code === 4000) {
+              // 登录错误之后,刷新验证码
+              refreshCaptcha()
+            }
+          })
+        } else {
+          errorMessage('请填写登录信息')
+        }
+      })
+    }
+    const getUserInfo = () => {
+      useUserInfo().setUserInfos()
+    }
+
+    // 登录成功后的跳转
+    const loginSuccess = () => {
+      //登录成功获取用户信息,获取系统字典数据
+      getUserInfo()
+      //获取所有字典
+      DictionaryStore().getSystemDictionarys()
+
+      // 初始化登录成功时间问候语
+      let currentTimeInfo = currentTime.value
+      // 登录成功,跳到转首页
+      // 如果是复制粘贴的路径,非首页/登录页,那么登录成功后重定向到对应的路径中
+      if (route.query?.redirect) {
+        router.push({
+          path: <string>route.query?.redirect,
+          query: Object.keys(<string>route.query?.params).length > 0 ? JSON.parse(<string>route.query?.params) : '',
+        })
+      } else {
+        router.push('/')
+      }
+      // 登录成功提示
+      // 关闭 loading
+      state.loading.signIn = true
+      const signInText = t('message.signInText')
+      ElMessage.success(`${currentTimeInfo},${signInText}`)
+      // 添加 loading,防止第一次进入界面时出现短暂空白
+      NextLoading.start()
+    }
+
+    const qrCode = ref('')
+
+    async function qrLoginClick() {
+      try {
+        const response = await getWorkWeChatCode()
+        window.location.href = response.data
+        qrCode.value = response.data
+        // if (qrCode.value) {
+          console.log(123, this.$route.query.code)
+        //   await WorkWeChatCodeLogin()
+        // }
+      } catch (error) {
+        console.log('error:', error)
+      }
+    }
+
+    async function WorkWeChatCodeLogin() {
+      const post = {
+        code: qrCode.value,
+        state: 'Wechat',
+      }
+      try {
+        const response = await postWorkWeChatCode(post)
+        console.log('response', response.data)
+      } catch (error) {
+        console.log('error:', error)
+      }
+    }
+
+    onMounted(() => {
+      getCaptcha()
+      //获取系统配置
+      SystemConfigStore().getSystemConfigs()
+    })
+
+    return {
+      refreshCaptcha,
+      loginClick,
+      loginSuccess,
+      isShowCaptcha,
+      state,
+      formRef,
+      rules,
+      ...toRefs(state),
+      qrLoginClick,
+      qrCode,
+      WorkWeChatCodeLogin,
+    }
+  },
+})
 </script>
 
 <style scoped lang="scss">
 .login-content-form {
-	margin-top: 20px;
-
-	@for $i from 1 through 4 {
-		.login-animation#{$i} {
-			opacity: 0;
-			animation-name: error-num;
-			animation-duration: 0.5s;
-			animation-fill-mode: forwards;
-			animation-delay: calc($i/10) + s;
-		}
-	}
-
-	.login-content-password {
-		display: inline-block;
-		width: 20px;
-		cursor: pointer;
-
-		&:hover {
-			color: #909399;
-		}
-	}
-
-	.login-content-captcha {
-		width: 100%;
-		padding: 0;
-		font-weight: bold;
-		letter-spacing: 5px;
-	}
-
-	.login-content-submit {
-		width: 100%;
-		letter-spacing: 2px;
-		font-weight: 300;
-		margin-top: 15px;
-	}
+  margin-top: 20px;
+
+  @for $i from 1 through 4 {
+    .login-animation#{$i} {
+      opacity: 0;
+      animation-name: error-num;
+      animation-duration: 0.5s;
+      animation-fill-mode: forwards;
+      animation-delay: calc($i/10) + s;
+    }
+  }
+
+  .login-content-password {
+    display: inline-block;
+    width: 20px;
+    cursor: pointer;
+
+    &:hover {
+      color: #909399;
+    }
+  }
+
+  .login-content-captcha {
+    width: 100%;
+    padding: 0;
+    font-weight: bold;
+    letter-spacing: 5px;
+  }
+
+  .login-content-submit {
+    width: 100%;
+    letter-spacing: 2px;
+    font-weight: 300;
+    margin-top: 15px;
+  }
 }
 </style>

+ 317 - 296
src/views/system/login/index.vue

@@ -1,313 +1,334 @@
-<template>
-	<div class="login-container flex">
-		<div class="login-left">
-			<div class="login-left-logo">
-				<!-- <img src="src/assets/ansjer_image.png"/> -->
-				<el-image style="width: 110px; height: 110px" :src="AnsjerImg" fit="contain" />
-				<!-- 登录页左上角logo后面的文字 -->
-				<!-- <div class="login-left-logo-text">
-					<span>{{ getThemeConfig.globalViceTitle }}</span>
-					<span class="login-left-logo-text-msg">{{ getThemeConfig.globalViceTitleMsg }}</span>
-				</div> -->
-			</div>
-			<div class="login-left-img">
-				<img :src="loginMain"  alt=""/>
-			</div>
-			<img :src="loginBg" class="login-left-waves"  alt=""/>
-		</div>
-		<div class="login-right flex">
-			<div class="login-right-warp flex-margin">
-				<span class="login-right-warp-one"></span>
-				<span class="login-right-warp-two"></span>
-				<div class="login-right-warp-mian">
-					<div class="login-right-warp-main-title">{{ getThemeConfig.globalTitle }} 欢迎您!</div>
-					<div class="login-right-warp-main-form">
-						<div v-if="!state.isScan">
-							<el-tabs v-model="state.tabsActiveName">
-								<el-tab-pane :label="$t('message.label.one1')" name="account">
-									<Account />
-								</el-tab-pane>
-								<!-- TODO 手机号码登录未接入,展示隐藏 -->
-								<!-- <el-tab-pane :label="$t('message.label.two2')" name="mobile">
-									<Mobile />
-								</el-tab-pane> -->
-							</el-tabs>
-						</div>
-						<Scan v-if="state.isScan" />
-						<div class="login-content-main-sacn" @click="state.isScan = !state.isScan">
-							<i class="iconfont" :class="state.isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
-							<div class="login-content-main-sacn-delta"></div>
-						</div>
-					</div>
-				</div>
-			</div>
-		</div>
-
-		<div class="login-authorization">
-			<p>Copyright © 2023-2024 Ansjer 版权所有</p>
-			<!-- <p class="la-other">
-				<a href="https://beian.miit.gov.cn" target="_blank">晋ICP备18005113号-3</a>
-				|
-				<a href="https://django-vue-admin.com" target="_blank">帮助</a>
-				|
-				<a href="#">隐私</a>
-				|
-				<a href="#">条款</a>
-			</p> -->
-		</div>
-	</div>
-</template>
-
 <script setup lang="ts" name="loginIndex">
-import { defineAsyncComponent, onMounted, reactive, computed } from 'vue';
-import { storeToRefs } from 'pinia';
-import { useThemeConfig } from '/@/stores/themeConfig';
-import { NextLoading } from '/@/utils/loading';
-import logoMini from '/@/assets/logo-mini.svg';
-import loginMain from '/@/assets/login-main.svg';
-import loginBg from '/@/assets/login-bg.svg';
-import AnsjerImg from '/@/assets/ansjer_image.png';
+import { defineAsyncComponent, onMounted, reactive, computed, ref } from 'vue'
+import { storeToRefs } from 'pinia'
+import { useThemeConfig } from '/@/stores/themeConfig'
+import { NextLoading } from '/@/utils/loading'
+import logoMini from '/@/assets/logo-mini.svg'
+import loginMain from '/@/assets/login-main.svg'
+import loginBg from '/@/assets/login-bg.svg'
+import AnsjerImg from '/@/assets/ansjer_image.png'
+import { getWorkWeChatCode } from './api'
 
 // 引入组件
-const Account = defineAsyncComponent(() => import('/@/views/system/login/component/account.vue'));
-const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'));
-const Scan = defineAsyncComponent(() => import('/@/views/system/login/component/scan.vue'));
+const Account = defineAsyncComponent(() => import('/@/views/system/login/component/account.vue'))
+const Mobile = defineAsyncComponent(() => import('/@/views/system/login/component/mobile.vue'))
+const Scan = defineAsyncComponent(() => import('/@/views/system/login/component/scan.vue'))
 
 // 定义变量内容
-const storesThemeConfig = useThemeConfig();
-const { themeConfig } = storeToRefs(storesThemeConfig);
+const storesThemeConfig = useThemeConfig()
+const { themeConfig } = storeToRefs(storesThemeConfig)
 const state = reactive({
-	tabsActiveName: 'account',
-	isScan: false,
-});
+  tabsActiveName: 'account',
+  isScan: false,
+})
 
 // 获取布局配置信息
 const getThemeConfig = computed(() => {
-	return themeConfig.value;
-});
+  return themeConfig.value
+})
+
+const qrCode = ref('')
+
+async function scanClick() {
+  try {
+    if (!state.isScan) {
+      const response = await getWorkWeChatCode()
+      qrCode.value = response.data
+      console.log('qrCode.value', qrCode.value)
+      state.isScan = !state.isScan
+    }
+  } catch (error) {
+    console.log('error:', error)
+  }
+}
+
 // 页面加载时
 onMounted(() => {
-	NextLoading.done();
-});
+  NextLoading.done()
+})
 </script>
 
+<template>
+  <div class="login-container flex">
+    <div class="login-left">
+      <div class="login-left-logo">
+        <!-- <img src="src/assets/ansjer_image.png"/> -->
+        <el-image style="width: 110px; height: 110px" :src="AnsjerImg" fit="contain" />
+        <!-- 登录页左上角logo后面的文字 -->
+        <!-- <div class="login-left-logo-text">
+          <span>{{ getThemeConfig.globalViceTitle }}</span>
+          <span class="login-left-logo-text-msg">{{ getThemeConfig.globalViceTitleMsg }}</span>
+        </div> -->
+      </div>
+      <div class="login-left-img">
+        <img :src="loginMain" alt="" />
+      </div>
+      <img :src="loginBg" class="login-left-waves" alt="" />
+    </div>
+    <div class="login-right flex">
+      <div class="login-right-warp flex-margin">
+        <span class="login-right-warp-one"></span>
+        <span class="login-right-warp-two"></span>
+        <div class="login-right-warp-mian">
+          <div class="login-right-warp-main-title">{{ getThemeConfig.globalTitle }} 欢迎您!</div>
+          <div class="login-right-warp-main-form">
+            <div v-if="!state.isScan">
+              <el-tabs v-model="state.tabsActiveName">
+                <el-tab-pane :label="$t('message.label.one1')" name="account">
+                  <Account />
+                </el-tab-pane>
+                <!-- TODO 手机号码登录未接入,展示隐藏 -->
+                <!-- <el-tab-pane :label="$t('message.label.two2')" name="mobile">
+                  <Mobile />
+                </el-tab-pane> -->
+                <!-- 二维码扫码登录 -->
+                <!--<el-tab-pane :label="$t('message.label.qrcode')" name="QRCode">-->
+                <!--  <Account />-->
+                <!--</el-tab-pane>-->
+              </el-tabs>
+            </div>
+            <Scan v-if="state.isScan && qrCode" :qrCode="qrCode" />
+            <div class="login-content-main-sacn" @click="scanClick">
+              <i class="iconfont" :class="state.isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
+              <div class="login-content-main-sacn-delta"></div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+
+    <div class="login-authorization">
+      <p>Copyright © 2023-2024 Ansjer 版权所有</p>
+      <!-- <p class="la-other">
+        <a href="https://beian.miit.gov.cn" target="_blank">晋ICP备18005113号-3</a>
+        |
+        <a href="https://django-vue-admin.com" target="_blank">帮助</a>
+        |
+        <a href="#">隐私</a>
+        |
+        <a href="#">条款</a>
+      </p> -->
+    </div>
+  </div>
+</template>
+
 <style scoped lang="scss">
 .login-container {
-	height: 100%;
-	background: var(--el-color-white);
-
-	.login-left {
-		flex: 1;
-		position: relative;
-		background-color: rgba(211, 239, 255, 1);
-		margin-right: 100px;
-
-		.login-left-logo {
-			display: flex;
-			align-items: center;
-			position: absolute;
-			top: 50px;
-			left: 80px;
-			z-index: 1;
-			animation: logoAnimation 0.3s ease;
-
-			img {
-				width: 52px;
-				height: 52px;
-			}
-
-			.login-left-logo-text {
-				display: flex;
-				flex-direction: column;
-
-				span {
-					margin-left: 10px;
-					font-size: 16px;
-					color: var(--el-color-primary);
-				}
-
-				.login-left-logo-text-msg {
-					font-size: 12px;
-					color: var(--el-color-primary);
-				}
-			}
-		}
-
-		.login-left-img {
-			position: absolute;
-			top: 50%;
-			left: 50%;
-			transform: translate(-50%, -50%);
-			width: 100%;
-			height: 52%;
-
-			img {
-				width: 100%;
-				height: 100%;
-				animation: error-num 0.6s ease;
-			}
-		}
-
-		.login-left-waves {
-			position: absolute;
-			top: 0;
-			right: -100px;
-		}
-	}
-
-	.login-right {
-		width: 700px;
-
-		.login-right-warp {
-			border: 1px solid var(--el-color-primary-light-3);
-			border-radius: 3px;
-			width: 500px;
-			height: 500px;
-			position: relative;
-			overflow: hidden;
-			background-color: var(--el-color-white);
-
-			.login-right-warp-one,
-			.login-right-warp-two {
-				position: absolute;
-				display: block;
-				width: inherit;
-				height: inherit;
-
-				&::before,
-				&::after {
-					content: '';
-					position: absolute;
-					z-index: 1;
-				}
-			}
-
-			.login-right-warp-one {
-				&::before {
-					filter: hue-rotate(0deg);
-					top: 0px;
-					left: 0;
-					width: 100%;
-					height: 3px;
-					background: linear-gradient(90deg, transparent, var(--el-color-primary));
-					animation: loginLeft 3s linear infinite;
-				}
-
-				&::after {
-					filter: hue-rotate(60deg);
-					top: -100%;
-					right: 2px;
-					width: 3px;
-					height: 100%;
-					background: linear-gradient(180deg, transparent, var(--el-color-primary));
-					animation: loginTop 3s linear infinite;
-					animation-delay: 0.7s;
-				}
-			}
-
-			.login-right-warp-two {
-				&::before {
-					filter: hue-rotate(120deg);
-					bottom: 2px;
-					right: -100%;
-					width: 100%;
-					height: 3px;
-					background: linear-gradient(270deg, transparent, var(--el-color-primary));
-					animation: loginRight 3s linear infinite;
-					animation-delay: 1.4s;
-				}
-
-				&::after {
-					filter: hue-rotate(300deg);
-					bottom: -100%;
-					left: 0px;
-					width: 3px;
-					height: 100%;
-					background: linear-gradient(360deg, transparent, var(--el-color-primary));
-					animation: loginBottom 3s linear infinite;
-					animation-delay: 2.1s;
-				}
-			}
-
-			.login-right-warp-mian {
-				display: flex;
-				flex-direction: column;
-				height: 100%;
-
-				.login-right-warp-main-title {
-					height: 130px;
-					line-height: 130px;
-					font-size: 27px;
-					text-align: center;
-					letter-spacing: 3px;
-					animation: logoAnimation 0.3s ease;
-					animation-delay: 0.3s;
-					color: var(--el-text-color-primary);
-				}
-
-				.login-right-warp-main-form {
-					flex: 1;
-					padding: 0 50px 50px;
-
-					.login-content-main-sacn {
-						position: absolute;
-						top: 0;
-						right: 0;
-						width: 50px;
-						height: 50px;
-						overflow: hidden;
-						cursor: pointer;
-						transition: all ease 0.3s;
-						color: var(--el-color-primary);
-
-						&-delta {
-							position: absolute;
-							width: 35px;
-							height: 70px;
-							z-index: 2;
-							top: 2px;
-							right: 21px;
-							background: var(--el-color-white);
-							transform: rotate(-45deg);
-						}
-
-						&:hover {
-							opacity: 1;
-							transition: all ease 0.3s;
-							color: var(--el-color-primary) !important;
-						}
-
-						i {
-							width: 47px;
-							height: 50px;
-							display: inline-block;
-							font-size: 48px;
-							position: absolute;
-							right: 1px;
-							top: 0px;
-						}
-					}
-				}
-			}
-		}
-	}
-
-	.login-authorization {
-		position: fixed;
-		bottom: 30px;
-		left: 0;
-		right: 0;
-		text-align: center;
-
-		p {
-			font-size: 14px;
-			color: rgba(0, 0, 0, 0.5);
-		}
-
-		a {
-			color: var(--el-color-primary);
-			margin: 0 5px;
-		}
-	}
+  height: 100%;
+  background: var(--el-color-white);
+
+  .login-left {
+    flex: 1;
+    position: relative;
+    background-color: rgba(211, 239, 255, 1);
+    margin-right: 100px;
+
+    .login-left-logo {
+      display: flex;
+      align-items: center;
+      position: absolute;
+      top: 50px;
+      left: 80px;
+      z-index: 1;
+      animation: logoAnimation 0.3s ease;
+
+      img {
+        width: 52px;
+        height: 52px;
+      }
+
+      .login-left-logo-text {
+        display: flex;
+        flex-direction: column;
+
+        span {
+          margin-left: 10px;
+          font-size: 16px;
+          color: var(--el-color-primary);
+        }
+
+        .login-left-logo-text-msg {
+          font-size: 12px;
+          color: var(--el-color-primary);
+        }
+      }
+    }
+
+    .login-left-img {
+      position: absolute;
+      top: 50%;
+      left: 50%;
+      transform: translate(-50%, -50%);
+      width: 100%;
+      height: 52%;
+
+      img {
+        width: 100%;
+        height: 100%;
+        animation: error-num 0.6s ease;
+      }
+    }
+
+    .login-left-waves {
+      position: absolute;
+      top: 0;
+      right: -100px;
+    }
+  }
+
+  .login-right {
+    width: 700px;
+
+    .login-right-warp {
+      border: 1px solid var(--el-color-primary-light-3);
+      border-radius: 3px;
+      width: 500px;
+      height: 500px;
+      position: relative;
+      overflow: hidden;
+      background-color: var(--el-color-white);
+
+      .login-right-warp-one,
+      .login-right-warp-two {
+        position: absolute;
+        display: block;
+        width: inherit;
+        height: inherit;
+
+        &::before,
+        &::after {
+          content: '';
+          position: absolute;
+          z-index: 1;
+        }
+      }
+
+      .login-right-warp-one {
+        &::before {
+          filter: hue-rotate(0deg);
+          top: 0px;
+          left: 0;
+          width: 100%;
+          height: 3px;
+          background: linear-gradient(90deg, transparent, var(--el-color-primary));
+          animation: loginLeft 3s linear infinite;
+        }
+
+        &::after {
+          filter: hue-rotate(60deg);
+          top: -100%;
+          right: 2px;
+          width: 3px;
+          height: 100%;
+          background: linear-gradient(180deg, transparent, var(--el-color-primary));
+          animation: loginTop 3s linear infinite;
+          animation-delay: 0.7s;
+        }
+      }
+
+      .login-right-warp-two {
+        &::before {
+          filter: hue-rotate(120deg);
+          bottom: 2px;
+          right: -100%;
+          width: 100%;
+          height: 3px;
+          background: linear-gradient(270deg, transparent, var(--el-color-primary));
+          animation: loginRight 3s linear infinite;
+          animation-delay: 1.4s;
+        }
+
+        &::after {
+          filter: hue-rotate(300deg);
+          bottom: -100%;
+          left: 0px;
+          width: 3px;
+          height: 100%;
+          background: linear-gradient(360deg, transparent, var(--el-color-primary));
+          animation: loginBottom 3s linear infinite;
+          animation-delay: 2.1s;
+        }
+      }
+
+      .login-right-warp-mian {
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+
+        .login-right-warp-main-title {
+          height: 130px;
+          line-height: 130px;
+          font-size: 27px;
+          text-align: center;
+          letter-spacing: 3px;
+          animation: logoAnimation 0.3s ease;
+          animation-delay: 0.3s;
+          color: var(--el-text-color-primary);
+        }
+
+        .login-right-warp-main-form {
+          flex: 1;
+          padding: 0 50px 50px;
+
+          .login-content-main-sacn {
+            position: absolute;
+            top: 0;
+            right: 0;
+            width: 50px;
+            height: 50px;
+            overflow: hidden;
+            cursor: pointer;
+            transition: all ease 0.3s;
+            color: var(--el-color-primary);
+
+            &-delta {
+              position: absolute;
+              width: 35px;
+              height: 70px;
+              z-index: 2;
+              top: 2px;
+              right: 21px;
+              background: var(--el-color-white);
+              transform: rotate(-45deg);
+            }
+
+            &:hover {
+              opacity: 1;
+              transition: all ease 0.3s;
+              color: var(--el-color-primary) !important;
+            }
+
+            i {
+              width: 47px;
+              height: 50px;
+              display: inline-block;
+              font-size: 48px;
+              position: absolute;
+              right: 1px;
+              top: 0px;
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .login-authorization {
+    position: fixed;
+    bottom: 30px;
+    left: 0;
+    right: 0;
+    text-align: center;
+
+    p {
+      font-size: 14px;
+      color: rgba(0, 0, 0, 0.5);
+    }
+
+    a {
+      color: var(--el-color-primary);
+      margin: 0 5px;
+    }
+  }
 }
 </style>