.env.development
@@ -13,7 +13,7 @@ # çæ§å°å VITE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/applications' # xxl-job æ§å¶å°å°å VITE_APP_XXL_JOB_ADMIN = 'http://localhost:9100/xxl-job-admin' # powerjob æ§å¶å°å°å VITE_APP_POWERJOB_ADMIN = 'http://localhost:7700/' VITE_APP_PORT = 80 .env.production
@@ -10,8 +10,8 @@ # çæ§å°å VITE_APP_MONITRO_ADMIN = '/admin/applications' # çæ§å°å VITE_APP_XXL_JOB_ADMIN = '/xxl-job-admin' # powerjob æ§å¶å°å°å VITE_APP_POWERJOB_ADMIN = '/powerjob' # ç产ç¯å¢ VITE_APP_BASE_API = '/prod-api' .gitignore
@@ -1,4 +1,5 @@ .DS_Store .history node_modules/ dist/ npm-debug.log* src/api/login.ts
@@ -2,6 +2,7 @@ import { AxiosPromise } from 'axios'; import { LoginData, LoginResult, VerifyCodeResult, TenantInfo } from './types'; import { UserInfo } from '@/api/system/user/types'; import { da } from 'element-plus/es/locale'; /** * @param data {LoginData} @@ -9,11 +10,9 @@ */ export function login(data: LoginData): AxiosPromise<LoginResult> { const params = { tenantId: data.tenantId, username: data.username.trim(), password: data.password, code: data.code, uuid: data.uuid ...data, clientId: data.clientId || 'e5cd7e4891bf95d1d19206ce24a7b32e', grantType: data.grantType || 'password' }; return request({ url: '/auth/login', @@ -60,6 +59,22 @@ timeout: 20000 }); } /** * ç¬¬ä¸æ¹ç»å½ * @param source ç¬¬ä¸æ¹ç»å½ç±»å * */ export function callback(data: LoginData): AxiosPromise<any> { const LoginData = { ...data, clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e', grantType: 'social' }; return request({ url: '/auth/social/callback', method: 'post', data: LoginData }); } // è·åç¨æ·è¯¦ç»ä¿¡æ¯ export function getInfo(): AxiosPromise<UserInfo> { src/api/system/client/index.ts
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,80 @@ import request from '@/utils/request'; import { AxiosPromise } from 'axios'; import { ClientVO, ClientForm, ClientQuery } from '@/api/system/client/types'; /** * æ¥è¯¢å®¢æ·ç«¯ç®¡çå表 * @param query * @returns {*} */ export const listClient = (query?: ClientQuery): AxiosPromise<ClientVO[]> => { return request({ url: '/system/client/list', method: 'get', params: query }); }; /** * æ¥è¯¢å®¢æ·ç«¯ç®¡çè¯¦ç» * @param id */ export const getClient = (id: string | number): AxiosPromise<ClientVO> => { return request({ url: '/system/client/' + id, method: 'get' }); }; /** * æ°å¢å®¢æ·ç«¯ç®¡ç * @param data */ export const addClient = (data: ClientForm) => { return request({ url: '/system/client', method: 'post', data: data }); }; /** * ä¿®æ¹å®¢æ·ç«¯ç®¡ç * @param data */ export const updateClient = (data: ClientForm) => { return request({ url: '/system/client', method: 'put', data: data }); }; /** * å é¤å®¢æ·ç«¯ç®¡ç * @param id */ export const delClient = (id: string | number | Array<string | number>) => { return request({ url: '/system/client/' + id, method: 'delete' }); }; /** * ç¶æä¿®æ¹ * @param id ID * @param status ç¶æ */ export function changeStatus(id: number | string, status: string) { const data = { id, status }; return request({ url: '/system/client/changeStatus', method: 'put', data: data }); } src/api/system/client/types.ts
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,138 @@ export interface ClientVO { /** * id */ id: string | number; /** * 客æ·ç«¯id */ clientId: string | number; /** * 客æ·ç«¯key */ clientKey: string; /** * 客æ·ç«¯ç§é¥ */ clientSecret: string; /** * ææç±»å */ grantTypeList: string[]; /** * 设å¤ç±»å */ deviceType: string; /** * tokenæ´»è·è¶ æ¶æ¶é´ */ activeTimeout: number; /** * tokenåºå®è¶ æ¶ */ timeout: number; /** * ç¶æï¼0æ£å¸¸ 1åç¨ï¼ */ status: string; } export interface ClientForm extends BaseEntity { /** * id */ id?: string | number; /** * 客æ·ç«¯id */ clientId?: string | number; /** * 客æ·ç«¯key */ clientKey?: string; /** * 客æ·ç«¯ç§é¥ */ clientSecret?: string; /** * ææç±»å */ grantTypeList?: string[]; /** * 设å¤ç±»å */ deviceType?: string; /** * tokenæ´»è·è¶ æ¶æ¶é´ */ activeTimeout?: number; /** * tokenåºå®è¶ æ¶ */ timeout?: number; /** * ç¶æï¼0æ£å¸¸ 1åç¨ï¼ */ status?: string; } export interface ClientQuery extends PageQuery { /** * 客æ·ç«¯id */ clientId?: string | number; /** * 客æ·ç«¯key */ clientKey?: string; /** * 客æ·ç«¯ç§é¥ */ clientSecret?: string; /** * ææç±»å */ grantType?: string; /** * 设å¤ç±»å */ deviceType?: string; /** * tokenæ´»è·è¶ æ¶æ¶é´ */ activeTimeout?: number; /** * tokenåºå®è¶ æ¶ */ timeout?: number; /** * ç¶æï¼0æ£å¸¸ 1åç¨ï¼ */ status?: string; } src/api/system/social/auth.ts
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,24 @@ import request from '@/utils/request'; // ç»å®è´¦å· export function authBinding(source: string) { return request({ url: '/auth/binding/' + source, method: 'get' }); } // è§£ç»è´¦å· export function authUnlock(authId: string) { return request({ url: '/auth/unlock/' + authId, method: 'delete' }); } //è·åææå表 export function getAuthList() { return request({ url: '/system/social/list', method: 'get' }); } src/api/types.ts
@@ -15,19 +15,24 @@ * ç»å½è¯·æ± */ export interface LoginData { tenantId: string; username: string; password: string; tenantId?: string; username?: string; password?: string; rememberMe?: boolean; socialCode?: string, socialState?: string, source?: string, code?: string; uuid?: string; clientId: string; grantType: string; } /** * ç»å½ååº */ export interface LoginResult { token: string; access_token: string; } /** 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,82 @@ <template> <div v-loading="loading" class="social-login"></div> </template> <script setup lang="ts"> import { login, callback } from '@/api/login'; import { setToken } from '@/utils/auth'; import Cookies from 'js-cookie'; import { getToken } from '@/utils/auth'; import { LoginData } from '@/api/types'; const route = useRoute(); const loading = ref(true); /** * æ¥æ¶Routeä¼ éçåæ° * @param {Object} route.query. */ const code = route.query.code as string; const state = route.query.state as string; const source = route.query.source as string; const tenantId = Cookies.get("tenantId") ? Cookies.get("tenantId") as string : '000000'; const processResponse = async (res: any) => { if (res.code !== 200) { throw new Error(res.msg); } setToken(res.data.access_token); ElMessage.success(res.msg); location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index'; }; const handleError = (error: any) => { ElMessage.error(error.message); location.href = import.meta.env.VITE_APP_CONTEXT_PATH + 'index'; }; const callbackByCode = async (data: LoginData) => { try { const res = await callback(data); await processResponse(res); loading.value = false; } catch (error) { handleError(error); } }; const loginByCode = async (data: LoginData) => { try { const res = await login(data); await processResponse(res); loading.value = false; } catch (error) { handleError(error); } }; const init = async () => { const data: LoginData = { socialCode: code, socialState: state, tenantId: tenantId, source: source, clientId: 'e5cd7e4891bf95d1d19206ce24a7b32e', grantType: 'social' }; if (!getToken()) { await loginByCode(data); } else { await callbackByCode(data); } }; onMounted(() => { nextTick(() => { init(); }); }); </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 @@ -176,4 +181,5 @@ } }); export default router; src/store/modules/user.ts
@@ -23,8 +23,8 @@ const [err, res] = await to(loginApi(userInfo)); if (res) { const data = res.data; setToken(data.token); token.value = data.token; setToken(data.access_token); token.value = data.access_token; return Promise.resolve(); } return Promise.reject(err); src/types/env.d.ts
@@ -65,7 +65,7 @@ VITE_APP_BASE_URL: string; VITE_APP_CONTEXT_PATH: string; VITE_APP_MONITRO_ADMIN: string; VITE_APP_XXL_JOB_ADMIN: string; VITE_APP_POWERJOB_ADMIN: string; VITE_APP_ENV: string; } interface ImportMeta { src/utils/auth.ts
@@ -4,6 +4,6 @@ export const getToken = () => tokenStorage.value; export const setToken = (token: string) => (tokenStorage.value = token); export const setToken = (access_token: string) => (tokenStorage.value = access_token); export const removeToken = () => (tokenStorage.value = null); src/views/index.vue
@@ -21,7 +21,7 @@ * åå¸å¼é Lock4j 注解éãå·¥å ·é å¤ç§å¤æ ·<br /> * åå¸å¼å¹ç Lock4j åºäºåå¸å¼éå®ç°<br /> * åå¸å¼é¾è·¯è¿½è¸ª SkyWalking æ¯æé¾è·¯è¿½è¸ªãç½æ ¼åæã度éèåãå¯è§å<br /> * åå¸å¼ä»»å¡è°åº¦ Xxl-Job 髿§è½ é«å¯é ææ©å±<br /> * åå¸å¼ä»»å¡è°åº¦ PowerJob 髿§è½ é«å¯é ææ©å±<br /> * æä»¶åå¨ Minio æ¬å°åå¨<br /> * æä»¶åå¨ ä¸çãé¿éãè ¾è®¯ äºåå¨<br /> * çæ§æ¡æ¶ SpringBoot-Admin å ¨æ¹ä½æå¡çæ§<br /> src/views/login.vue
@@ -4,7 +4,8 @@ <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> @@ -14,12 +15,14 @@ </el-input> </el-form-item> <el-form-item prop="password"> <el-input v-model="loginForm.password" type="password" size="large" auto-complete="off" placeholder="å¯ç " @keyup.enter="handleLogin"> <el-input v-model="loginForm.password" type="password" size="large" auto-complete="off" placeholder="å¯ç " @keyup.enter="handleLogin"> <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template> </el-input> </el-form-item> <el-form-item prop="code" v-if="captchaEnabled"> <el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="éªè¯ç " style="width: 63%" @keyup.enter="handleLogin"> <el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="éªè¯ç " style="width: 63%" @keyup.enter="handleLogin"> <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template> </el-input> <div class="login-code"> @@ -36,6 +39,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 +63,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 +106,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 +171,41 @@ * è·åç§æ·å表 */ 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; } } } //æ£æµç§æ·éæ©æ¡çåå watch(() => loginForm.value.tenantId, (val: string) => { Cookies.set("tenantId", loginForm.value.tenantId, { expires: 30 }) }); /** * ç¬¬ä¸æ¹ç»å½ * @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 +218,7 @@ background-image: url("../assets/images/login-background.jpg"); background-size: cover; } .title { margin: 0px auto 30px auto; text-align: center; @@ -190,32 +230,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 +271,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/monitor/powerjob/index.vue
ÎļþÃû´Ó src/views/monitor/xxljob/index.vue ÐÞ¸Ä @@ -5,5 +5,5 @@ </template> <script setup lang="ts"> const url = ref(import.meta.env.VITE_APP_XXL_JOB_ADMIN); const url = ref(import.meta.env.VITE_APP_POWERJOB_ADMIN); </script> src/views/system/client/index.vue
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,344 @@ <template> <div class="p-2"> <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> <div class="search" v-show="showSearch"> <el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="100px"> <el-form-item label="客æ·ç«¯key" prop="clientKey"> <el-input v-model="queryParams.clientKey" placeholder="请è¾å ¥å®¢æ·ç«¯key" clearable @keyup.enter="handleQuery" /> </el-form-item> <el-form-item label="客æ·ç«¯ç§é¥" prop="clientSecret"> <el-input v-model="queryParams.clientSecret" placeholder="请è¾å ¥å®¢æ·ç«¯ç§é¥" clearable @keyup.enter="handleQuery" /> </el-form-item> <el-form-item label="ç¶æ" prop="status"> <el-select v-model="queryParams.status" placeholder="ç¶æ" clearable> <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </el-form-item> <el-form-item> <el-button type="primary" icon="Search" @click="handleQuery">æç´¢</el-button> <el-button icon="Refresh" @click="resetQuery">éç½®</el-button> </el-form-item> </el-form> </div> </transition> <el-card shadow="never"> <template #header> <el-row :gutter="10" class="mb8"> <el-col :span="1.5"> <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['system:client:add']">æ°å¢</el-button> </el-col> <el-col :span="1.5"> <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['system:client:edit']">ä¿®æ¹</el-button> </el-col> <el-col :span="1.5"> <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['system:client:remove']">å é¤</el-button> </el-col> <el-col :span="1.5"> <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['system:client:export']">导åº</el-button> </el-col> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> </el-row> </template> <el-table v-loading="loading" :data="clientList" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55" align="center" /> <el-table-column label="id" align="center" prop="id" v-if="true" /> <el-table-column label="客æ·ç«¯id" align="center" prop="clientId" /> <el-table-column label="客æ·ç«¯key" align="center" prop="clientKey" /> <el-table-column label="客æ·ç«¯ç§é¥" align="center" prop="clientSecret" /> <el-table-column label="ææç±»å" align="center"> <template #default="scope"> <div> <template v-for="type in scope.row.grantTypeList"> <dict-tag class="el-check-tag" :options="sys_grant_type" :value="type" /> </template> </div> </template> </el-table-column> <el-table-column label="设å¤ç±»å" align="center"> <template #default="scope"> <dict-tag :options="sys_device_type" :value="scope.row.deviceType" /> </template> </el-table-column> <el-table-column label="Tokenæ´»è·è¶ æ¶æ¶é´" align="center" prop="activeTimeout" /> <el-table-column label="Tokenåºå®è¶ æ¶æ¶é´" align="center" prop="timeout" /> <el-table-column label="ç¶æ" align="center" key="status"> <template #default="scope"> <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch> </template> </el-table-column> <el-table-column label="æä½" align="center" class-name="small-padding fixed-width"> <template #default="scope"> <el-tooltip content="ä¿®æ¹" placement="top"> <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['system:client:edit']"></el-button> </el-tooltip> <el-tooltip content="å é¤" placement="top"> <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['system:client:remove']"></el-button> </el-tooltip> </template> </el-table-column> </el-table> <pagination v-show="total>0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" /> </el-card> <!-- æ·»å æä¿®æ¹å®¢æ·ç«¯ç®¡çå¯¹è¯æ¡ --> <el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body> <el-form ref="clientFormRef" :model="form" :rules="rules" label-width="100px"> <el-form-item label="客æ·ç«¯key" prop="clientKey"> <el-input v-model="form.clientKey" :disabled="form.id != null" placeholder="请è¾å ¥å®¢æ·ç«¯key" /> </el-form-item> <el-form-item label="客æ·ç«¯ç§é¥" prop="clientSecret"> <el-input v-model="form.clientSecret" :disabled="form.id != null" placeholder="请è¾å ¥å®¢æ·ç«¯ç§é¥" /> </el-form-item> <el-form-item label="ææç±»å" prop="grantTypeList"> <el-select v-model="form.grantTypeList" multiple placeholder="请è¾å ¥ææç±»å"> <el-option v-for="dict in sys_grant_type" :key="dict.value" :label="dict.label" :value="dict.value" ></el-option> </el-select> </el-form-item> <el-form-item label="设å¤ç±»å" prop="deviceType"> <el-select v-model="form.deviceType" placeholder="请è¾å ¥è®¾å¤ç±»å"> <el-option v-for="dict in sys_device_type" :key="dict.value" :label="dict.label" :value="dict.value" ></el-option> </el-select> </el-form-item> <el-form-item prop="activeTimeout" label-width="auto"> <template #label> <span> <el-tooltip content="æå®æ¶é´æ æä½åè¿æï¼åä½ï¼ç§ï¼ï¼é»è®¤30åéï¼1800ç§ï¼" placement="top"> <el-icon><question-filled /></el-icon> </el-tooltip> Tokenæ´»è·è¶ æ¶æ¶é´ </span> </template> <el-input v-model="form.activeTimeout" placeholder="请è¾å ¥Tokenæ´»è·è¶ æ¶æ¶é´" /> </el-form-item> <el-form-item prop="timeout" label-width="auto"> <template #label> <span> <el-tooltip content="æå®æ¶é´å¿ å®è¿æï¼åä½ï¼ç§ï¼ï¼é»è®¤ä¸å¤©ï¼604800ç§ï¼" placement="top"> <el-icon><question-filled /></el-icon> </el-tooltip> Tokenåºå®è¶ æ¶æ¶é´ </span> </template> <el-input v-model="form.timeout" placeholder="请è¾å ¥Tokenåºå®è¶ æ¶æ¶é´" /> </el-form-item> <el-form-item label="ç¶æ"> <el-radio-group v-model="form.status"> <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value"> {{ dict.label }} </el-radio> </el-radio-group> </el-form-item> </el-form> <template #footer> <div class="dialog-footer"> <el-button :loading="buttonLoading" type="primary" @click="submitForm">ç¡® å®</el-button> <el-button @click="cancel">å æ¶</el-button> </div> </template> </el-dialog> </div> </template> <script setup name="Client" lang="ts"> import { listClient, getClient, delClient, addClient, updateClient, changeStatus } from '@/api/system/client'; import { ClientVO, ClientQuery, ClientForm } from '@/api/system/client/types'; import { ComponentInternalInstance } from 'vue'; import { ElForm } from 'element-plus'; const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { sys_normal_disable } = toRefs<any>(proxy?.useDict("sys_normal_disable")); const { sys_grant_type } = toRefs<any>(proxy?.useDict("sys_grant_type")); const { sys_device_type } = toRefs<any>(proxy?.useDict("sys_device_type")); const clientList = ref<ClientVO[]>([]); const buttonLoading = ref(false); const loading = ref(true); const showSearch = ref(true); const ids = ref<Array<string | number>>([]); const single = ref(true); const multiple = ref(true); const total = ref(0); const queryFormRef = ref(ElForm); const clientFormRef = ref(ElForm); const dialog = reactive<DialogOption>({ visible: false, title: '' }); const initFormData: ClientForm = { id: undefined, clientId: undefined, clientKey: undefined, clientSecret: undefined, grantTypeList: undefined, deviceType: undefined, activeTimeout: undefined, timeout: undefined, status: undefined, } const data = reactive<PageData<ClientForm, ClientQuery>>({ form: {...initFormData}, queryParams: { pageNum: 1, pageSize: 10, clientId: undefined, clientKey: undefined, clientSecret: undefined, grantType: undefined, deviceType: undefined, activeTimeout: undefined, timeout: undefined, status: undefined, }, rules: { id: [ { required: true, message: "idä¸è½ä¸ºç©º", trigger: "blur" } ], clientId: [ { required: true, message: "客æ·ç«¯idä¸è½ä¸ºç©º", trigger: "blur" } ], clientKey: [ { required: true, message: "客æ·ç«¯keyä¸è½ä¸ºç©º", trigger: "blur" } ], clientSecret: [ { required: true, message: "客æ·ç«¯ç§é¥ä¸è½ä¸ºç©º", trigger: "blur" } ], grantTypeList: [ { required: true, message: "ææç±»åä¸è½ä¸ºç©º", trigger: "change" } ], deviceType: [ { required: true, message: "设å¤ç±»åä¸è½ä¸ºç©º", trigger: "change" } ], } }); const { queryParams, form, rules } = toRefs(data); /** æ¥è¯¢å®¢æ·ç«¯ç®¡çå表 */ const getList = async () => { loading.value = true; const res = await listClient(queryParams.value); clientList.value = res.rows; total.value = res.total; loading.value = false; } /** åæ¶æé® */ const cancel = () => { reset(); dialog.visible = false; } /** 表åéç½® */ const reset = () => { form.value = {...initFormData}; clientFormRef.value.resetFields(); } /** æç´¢æé®æä½ */ const handleQuery = () => { queryParams.value.pageNum = 1; getList(); } /** éç½®æé®æä½ */ const resetQuery = () => { queryFormRef.value.resetFields(); handleQuery(); } /** å¤éæ¡é䏿°æ® */ const handleSelectionChange = (selection: ClientVO[]) => { ids.value = selection.map(item => item.id); single.value = selection.length != 1; multiple.value = !selection.length; } /** æ°å¢æé®æä½ */ const handleAdd = () => { dialog.visible = true; dialog.title = "æ·»å 客æ·ç«¯ç®¡ç"; nextTick(() => { reset(); }); } /** ä¿®æ¹æé®æä½ */ const handleUpdate = (row?: ClientVO) => { loading.value = true dialog.visible = true; dialog.title = "ä¿®æ¹å®¢æ·ç«¯ç®¡ç"; nextTick(async () => { reset(); const _id = row?.id || ids.value[0] const res = await getClient(_id); loading.value = false; Object.assign(form.value, res.data); }); } /** æäº¤æé® */ const submitForm = () => { clientFormRef.value.validate(async (valid: boolean) => { if (valid) { buttonLoading.value = true; if (form.value.id) { await updateClient(form.value).finally(() => buttonLoading.value = false); } else { await addClient(form.value).finally(() => buttonLoading.value = false); } proxy?.$modal.msgSuccess("ä¿®æ¹æå"); dialog.visible = false; await getList(); } }); } /** å é¤æé®æä½ */ const handleDelete = async (row?: ClientVO) => { const _ids = row?.id || ids.value; await proxy?.$modal.confirm('æ¯å¦ç¡®è®¤å é¤å®¢æ·ç«¯ç®¡çç¼å·ä¸º"' + _ids + '"çæ°æ®é¡¹ï¼').finally(() => loading.value = false); await delClient(_ids); proxy?.$modal.msgSuccess("å 餿å"); await getList(); } /** å¯¼åºæé®æä½ */ const handleExport = () => { proxy?.download('system/client/export', { ...queryParams.value }, `client_${new Date().getTime()}.xlsx`) } /** ç¶æä¿®æ¹ */ const handleStatusChange = async (row: ClientVO) => { let text = row.status === "0" ? "å¯ç¨" : "åç¨" try { await proxy?.$modal.confirm('确认è¦"' + text + '"å?'); await changeStatus(row.id, row.status); proxy?.$modal.msgSuccess(text + "æå"); } catch (err) { row.status = row.status === "0" ? "1" : "0"; } } onMounted(() => { getList(); }); </script> src/views/system/menu/index.vue
@@ -180,7 +180,7 @@ </el-col> <el-col :span="12" v-if="form.menuType === 'C'"> <el-form-item> <el-input v-model="form.query" placeholder="请è¾å ¥è·¯ç±åæ°" maxlength="255" /> <el-input v-model="form.queryParam" placeholder="请è¾å ¥è·¯ç±åæ°" maxlength="255" /> <template #label> <span> <el-tooltip content='访é®è·¯ç±çé»è®¤ä¼ éåæ°ï¼å¦ï¼`{"id": 1, "name": "ry"}`' placement="top"> 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="#" @click="authUrl('wechar');" 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="#" @click="authUrl('qq');" title="ä½¿ç¨ QQ è´¦å·ææç»å½"> <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>