.gitignore | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/api/login.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/api/system/social/auth.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/assets/icons/svg/gitee.svg | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/layout/components/socialLogin/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/permission.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/router/index.ts | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/login.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/system/user/profile/index.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
src/views/system/user/profile/thirdParty.vue | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
.gitignore
@@ -1,4 +1,5 @@ .DS_Store .history node_modules/ dist/ npm-debug.log* src/api/login.ts
@@ -60,6 +60,24 @@ timeout: 20000 }); } /** * ç¬¬ä¸æ¹ç»å½ * @param source ç¬¬ä¸æ¹ç»å½ç±»å * */ export function socialLogin(source: string, code: any, state: any): AxiosPromise<any> { const data = { code, state }; return request({ url: '/auth/social-login/' + source, method: 'get', headers: { isToken: true }, params: data }); } // è·åç¨æ·è¯¦ç»ä¿¡æ¯ export function getInfo(): AxiosPromise<UserInfo> { src/api/system/social/auth.ts
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,33 @@ import request from '@/utils/request'; // ç»å®è´¦å· export function authBinding(source: string) { return request({ url: '/auth/binding/' + source, method: 'get', headers: { isToken: true } }); } // è§£ç»è´¦å· export function authUnlock(authId: string) { return request({ url: '/auth/unlock/' + authId, method: 'delete', headers: { isToken: true } }); } //è·åææå表 export function getAuthList() { return request({ url: '/system/social/list', method: 'get', headers: { isToken: true } }); } src/assets/icons/svg/gitee.svg
¶Ô±ÈÐÂÎļþ @@ -0,0 +1 @@ <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1686919908144" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2521" width="200" height="200" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M512 992C246.895625 992 32 777.104375 32 512S246.895625 32 512 32s480 214.895625 480 480-214.895625 480-480 480z m242.9521875-533.3278125h-272.56875a23.7121875 23.7121875 0 0 0-23.71125 23.7121875l-0.024375 59.255625c0 13.08 10.6078125 23.7121875 23.6878125 23.7121875h165.96c13.104375 0 23.7121875 10.6078125 23.7121875 23.6878125v11.855625a71.1121875 71.1121875 0 0 1-71.1121875 71.1121875h-225.215625a23.7121875 23.7121875 0 0 1-23.6878125-23.7121875V423.1278125a71.1121875 71.1121875 0 0 1 71.0878125-71.1121875h331.824375a23.7121875 23.7121875 0 0 0 23.6878125-23.71125l0.0721875-59.2565625a23.7121875 23.7121875 0 0 0-23.68875-23.7121875H423.08a177.76875 177.76875 0 0 0-177.76875 177.7921875V754.953125c0 13.1034375 10.60875 23.7121875 23.713125 23.7121875h349.63125a159.984375 159.984375 0 0 0 159.984375-159.984375V482.36a23.7121875 23.7121875 0 0 0-23.7121875-23.6878125z" fill="#515151" p-id="2522"></path></svg> src/layout/components/socialLogin/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,36 @@ <template> <div v-loading="loading" class="social-login"></div> </template> <script setup lang="ts"> import { socialLogin } from '@/api/login'; import { setToken } from '@/utils/auth'; const route = useRoute(); const router = useRouter(); /** * æ¥æ¶Routeä¼ éçåæ° * @param {Object} route.query. */ const code = route.query.code; const state = route.query.state; const source = route.query.source as string; const loading = ref(true); await socialLogin(source, code, state) .then(async (res) => { if (res.code !== 200) { ElMessage.error(res.msg); router.go(-2); return; } loading.value = false; setToken(res.msg); ElMessage.success('ç»å½æå'); router.go(-2); }) .catch(() => { loading.value = false; }); </script> src/permission.ts
@@ -10,7 +10,7 @@ import usePermissionStore from '@/store/modules/permission'; NProgress.configure({ showSpinner: false }); const whiteList = ['/login', '/register']; const whiteList = ['/login', '/register', '/social-login']; router.beforeEach(async (to, from, next) => { NProgress.start(); src/router/index.ts
@@ -38,6 +38,11 @@ ] }, { path: '/social-login', hidden: true, component: () => import('@/layout/components/socialLogin/index.vue') }, { path: '/login', component: () => import('@/views/login.vue'), hidden: true src/views/login.vue
@@ -4,7 +4,7 @@ <h3 class="title">RuoYi-Vue-Pluså¤ç§æ·ç®¡çç³»ç»</h3> <el-form-item prop="tenantId" v-if="tenantEnabled"> <el-select v-model="loginForm.tenantId" filterable placeholder="è¯·éæ©/è¾å ¥å ¬å¸åç§°" style="width: 100%"> <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"> </el-option> <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"></el-option> <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template> </el-select> </el-form-item> @@ -36,6 +36,20 @@ <router-link class="link-type" :to="'/register'">ç«å³æ³¨å</router-link> </div> </el-form-item> <div style="display: flex;justify-content: flex-end;flex-direction: row;"> <el-button circle> <svg-icon icon-class="qq" @click="doSocialLogin('QQ')" /> </el-button> <el-button circle> <svg-icon icon-class="wechat" @click="doSocialLogin('Wechat')" /> </el-button> <el-button circle> <svg-icon icon-class="gitee" @click="doSocialLogin('gitee')" /> </el-button> <el-button circle> <svg-icon icon-class="github" @click="doSocialLogin('github')" /> </el-button> </div> </el-form> <!-- åºé¨ --> <div class="el-login-footer"> @@ -46,30 +60,31 @@ <script setup lang="ts"> import { getCodeImg, getTenantList } from '@/api/login'; import { authBinding } from '@/api/system/social/auth'; import Cookies from 'js-cookie'; import { encrypt, decrypt } from '@/utils/jsencrypt'; import { useUserStore } from '@/store/modules/user'; import { LoginData, TenantVO } from '@/api/types'; import { FormRules } from 'element-plus'; import { ElForm, FormRules } from 'element-plus'; import { to } from 'await-to-js'; const userStore = useUserStore(); const router = useRouter(); const loginForm = ref<LoginData>({ tenantId: "000000", username: 'admin', password: 'admin123', rememberMe: false, code: '', uuid: '' tenantId: "000000", username: 'admin', password: 'admin123', rememberMe: false, code: '', uuid: '' }); const loginRules: FormRules = { tenantId: [{ required: true, trigger: "blur", message: "请è¾å ¥æ¨çç§æ·ç¼å·" }], username: [{ required: true, trigger: 'blur', message: '请è¾å ¥æ¨çè´¦å·' }], password: [{ required: true, trigger: 'blur', message: '请è¾å ¥æ¨çå¯ç ' }], code: [{ required: true, trigger: 'change', message: '请è¾å ¥éªè¯ç ' }] tenantId: [{ required: true, trigger: "blur", message: "请è¾å ¥æ¨çç§æ·ç¼å·" }], username: [{ required: true, trigger: 'blur', message: '请è¾å ¥æ¨çè´¦å·' }], password: [{ required: true, trigger: 'blur', message: '请è¾å ¥æ¨çå¯ç ' }], code: [{ required: true, trigger: 'change', message: '请è¾å ¥éªè¯ç ' }] }; const codeUrl = ref(''); @@ -88,64 +103,64 @@ const tenantList = ref<TenantVO[]>([]); const handleLogin = () => { loginRef.value.validate(async (valid:boolean, fields: any) => { if (valid) { loading.value = true; // å¾éäºéè¦è®°ä½å¯ç è®¾ç½®å¨ cookie ä¸è®¾ç½®è®°ä½ç¨æ·ååå¯ç if (loginForm.value.rememberMe) { Cookies.set("tenantId", loginForm.value.tenantId, { expires: 30 }); Cookies.set('username', loginForm.value.username, { expires: 30 }); Cookies.set('password', String(encrypt(loginForm.value.password)), { expires: 30 }); Cookies.set('rememberMe', String(loginForm.value.rememberMe), { expires: 30 }); } else { // å¦åç§»é¤ Cookies.remove("tenantId"); Cookies.remove('username'); Cookies.remove('password'); Cookies.remove('rememberMe'); } // è°ç¨actionçç»å½æ¹æ³ // prittier-ignore const [err] = await to(userStore.login(loginForm.value)); if (!err) { await router.push({ path: redirect.value || '/' }); } else { loading.value = false; // éæ°è·åéªè¯ç if (captchaEnabled.value) { await getCode(); } } } else { console.log('error submit!', fields); loginRef.value.validate(async (valid: boolean, fields: any) => { if (valid) { loading.value = true; // å¾éäºéè¦è®°ä½å¯ç è®¾ç½®å¨ cookie ä¸è®¾ç½®è®°ä½ç¨æ·ååå¯ç if (loginForm.value.rememberMe) { Cookies.set("tenantId", loginForm.value.tenantId, { expires: 30 }); Cookies.set('username', loginForm.value.username, { expires: 30 }); Cookies.set('password', String(encrypt(loginForm.value.password)), { expires: 30 }); Cookies.set('rememberMe', String(loginForm.value.rememberMe), { expires: 30 }); } else { // å¦åç§»é¤ Cookies.remove("tenantId"); Cookies.remove('username'); Cookies.remove('password'); Cookies.remove('rememberMe'); } // è°ç¨actionçç»å½æ¹æ³ // prittier-ignore const [err] = await to(userStore.login(loginForm.value)); if (!err) { await router.push({ path: redirect.value || '/' }); } else { loading.value = false; // éæ°è·åéªè¯ç if (captchaEnabled.value) { await getCode(); } }); } } else { console.log('error submit!', fields); } }); }; /** * è·åéªè¯ç */ const getCode = async () => { const res = await getCodeImg(); const { data } = res; captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled; if (captchaEnabled.value) { codeUrl.value = 'data:image/gif;base64,' + data.img; loginForm.value.uuid = data.uuid; } const res = await getCodeImg(); const { data } = res; captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled; if (captchaEnabled.value) { codeUrl.value = 'data:image/gif;base64,' + data.img; loginForm.value.uuid = data.uuid; } }; const getCookie = () => { const tenantId = Cookies.get("tenantId"); const username = Cookies.get('username'); const password = Cookies.get('password'); const rememberMe = Cookies.get('rememberMe'); loginForm.value = { tenantId: tenantId === undefined ? loginForm.value.tenantId : tenantId, username: username === undefined ? loginForm.value.username : username, password: password === undefined ? loginForm.value.password : (decrypt(password) as string), rememberMe: rememberMe === undefined ? false : Boolean(rememberMe) }; const tenantId = Cookies.get("tenantId"); const username = Cookies.get('username'); const password = Cookies.get('password'); const rememberMe = Cookies.get('rememberMe'); loginForm.value = { tenantId: tenantId === undefined ? loginForm.value.tenantId : tenantId, username: username === undefined ? loginForm.value.username : username, password: password === undefined ? loginForm.value.password : (decrypt(password) as string), rememberMe: rememberMe === undefined ? false : Boolean(rememberMe) }; } @@ -153,20 +168,35 @@ * è·åç§æ·å表 */ const initTenantList = async () => { const { data } = await getTenantList(); tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled; if (tenantEnabled.value) { tenantList.value = data.voList; if (tenantList.value != null && tenantList.value.length !== 0) { loginForm.value.tenantId = tenantList.value[0].tenantId; } const { data } = await getTenantList(); tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled; if (tenantEnabled.value) { tenantList.value = data.voList; if (tenantList.value != null && tenantList.value.length !== 0) { loginForm.value.tenantId = tenantList.value[0].tenantId; } } } /** * ç¬¬ä¸æ¹ç»å½ * @param type */ const doSocialLogin = (type: string) => { authBinding(type).then((res: any) => { if (res.code === 200) { window.location.href = res.msg; } else { ElMessage.error(res.msg); } }); }; onMounted(() => { getCode(); initTenantList(); getCookie(); getCode(); initTenantList(); getCookie(); }); </script> @@ -179,6 +209,7 @@ background-image: url("../assets/images/login-background.jpg"); background-size: cover; } .title { margin: 0px auto 30px auto; text-align: center; @@ -190,32 +221,39 @@ background: #ffffff; width: 400px; padding: 25px 25px 5px 25px; .el-input { height: 40px; input { height: 40px; } } .input-icon { height: 39px; width: 14px; margin-left: 0px; } } .login-tip { font-size: 13px; text-align: center; color: #bfbfbf; } .login-code { width: 33%; height: 40px; float: right; img { cursor: pointer; vertical-align: middle; } } .el-login-footer { height: 40px; line-height: 40px; @@ -224,10 +262,11 @@ width: 100%; text-align: center; color: #fff; font-family: Arial,serif; font-family: Arial, serif; font-size: 12px; letter-spacing: 1px; } .login-code-img { height: 40px; padding-left: 12px; src/views/system/user/profile/index.vue
@@ -55,6 +55,9 @@ <el-tab-pane label="ä¿®æ¹å¯ç " name="resetPwd"> <resetPwd /> </el-tab-pane> <el-tab-pane label="ç¬¬ä¸æ¹åºç¨" name="thirdParty"> <thirdParty :auths="state.auths" /> </el-tab-pane> </el-tabs> </el-card> </el-col> @@ -66,13 +69,16 @@ import userAvatar from "./userAvatar.vue"; import userInfo from "./userInfo.vue"; import resetPwd from "./resetPwd.vue"; import thirdParty from "./thirdParty.vue"; import { getAuthList } from "@/api/system/social/auth"; import { getUserProfile } from "@/api/system/user"; const activeTab = ref("userinfo"); const state = ref<{ user: any; roleGroup: string; postGroup: string}>({ const state = ref<{ user: any; roleGroup: string; postGroup: string; auths:any[]}>({ user: {}, roleGroup: '', postGroup: '' postGroup: '', auths: [], }); const userForm = ref({}); @@ -85,7 +91,13 @@ state.value.postGroup = res.data.postGroup; }; const getAuths = async () => { const res = await getAuthList(); state.value.auths = res.data; }; onMounted(() => { getUser(); getAuths(); }) </script> src/views/system/user/profile/thirdParty.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,140 @@ <template> <div> <el-table :data="auths" style="width: 100%; height: 100%; font-size: 10px"> <el-table-column label="åºå·" width="50" type="index"></el-table-column> <el-table-column label="ç»å®è´¦å·å¹³å°" width="140" align="center" prop="source" show-overflow-tooltip /> <el-table-column label="头å" width="120" align="center" prop="avatar"> <template v-slot="scope"> <img :src="scope.row.avatar" style="width: 45px; height: 45px" /> </template> </el-table-column> <el-table-column label="ç³»ç»è´¦å·" width="180" align="center" prop="userName" :show-overflow-tooltip="true" /> <el-table-column label="ç»å®æ¶é´" width="180" align="center" prop="createTime" /> <el-table-column label="æä½" width="80" align="center" class-name="small-padding fixed-width"> <template v-slot="scope"> <el-button size="small" type="text" @click="unlockAuth(scope.row)">è§£ç»</el-button> </template> </el-table-column> </el-table> <div id="git-user-binding"> <h4 class="provider-desc">ä½ å¯ä»¥ç»å®ä»¥ä¸ç¬¬ä¸æ¹å¸å·</h4> <div id="authlist" class="user-bind"> <a class="third-app" href="#" @click="authUrl('gitee');" title="ä½¿ç¨ Gitee è´¦å·ææç»å½"> <div class="git-other-login-icon"> <svg-icon icon-class="gitee" /> </div> <span class="app-name">Gitee</span> </a> <a class="third-app" href="#" @click="authUrl('github');" title="ä½¿ç¨ GitHub è´¦å·ææç»å½"> <div class="git-other-login-icon"> <svg-icon icon-class="github" /> </div> <span class="app-name">Github</span> </a> <a class="third-app" href="#" title="åè½å¼åä¸..."> <div class="git-other-login-icon"> <svg-icon icon-class="wechat" /> </div> <span class="app-name">WeiXin</span> </a> <a class="third-app" href="#" title="åè½å¼åä¸..."> <div class="git-other-login-icon"> <svg-icon icon-class="qq" /> </div> <span class="app-name">QQ</span> </a> </div> </div> </div> </template> <script lang="ts" setup> import { authUnlock, authBinding } from "@/api/system/social/auth"; import { PropType } from "vue"; const props = defineProps({ auths: { type: Object as PropType<any>, } }); const auths = computed(() => props.auths); const unlockAuth = (row: any) => { ElMessageBox.confirm('æ¨ç¡®å®è¦è§£é¤"' + row.source + '"çè´¦å·ç»å®åï¼') .then(() => { return authUnlock(row.id); }).then((res: any) => { if (res.code === 200) { ElMessage.success("è§£ç»æå"); } else { ElMessage.error(res.msg); } }).catch(() => { }); }; const authUrl = (source: string) => { authBinding(source).then((res: any) => { if (res.code === 200) { window.location.href = res.msg; } else { ElMessage.error(res.msg); } }); }; </script> <style type="text/css"> .user-bind .third-app { display: -webkit-box; display: -ms-flexbox; display: flex; -webkit-box-orient: vertical; -webkit-box-direction: normal; -ms-flex-direction: column; flex-direction: column; -webkit-box-align: center; -ms-flex-align: center; align-items: center; min-width: 80px; float: left; } .user-bind { font-size: 1rem; text-align: start; height: 50px; margin-top: 10px; } .git-other-login-icon>img { height: 32px; } a { text-decoration: none; cursor: pointer; color: #005980; } .provider-desc { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Liberation Sans", "PingFang SC", "Microsoft YaHei", "Hiragino Sans GB", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", "ST Heiti", SimHei, SimSun, "WenQuanYi Zen Hei Sharp", sans-serif; font-size: 1.071rem; } td>img { height: 20px; width: 20px; display: inline-block; border-radius: 50%; margin-right: 5px; } </style>