mirror of
				https://github.com/continew-org/continew-admin-ui.git
				synced 2025-10-31 20:58:40 +08:00 
			
		
		
		
	feat(system/client): 新增客户端管理
This commit is contained in:
		| @@ -7,22 +7,22 @@ const BASE_URL = '/auth' | ||||
|  | ||||
| /** @desc 账号登录 */ | ||||
| export function accountLogin(req: T.AccountLoginReq) { | ||||
|   return http.post<T.LoginResp>(`${BASE_URL}/account`, req) | ||||
|   return http.post<T.LoginResp>(`${BASE_URL}/login`, req) | ||||
| } | ||||
|  | ||||
| /** @desc 手机号登录 */ | ||||
| export function phoneLogin(req: T.PhoneLoginReq) { | ||||
|   return http.post<T.LoginResp>(`${BASE_URL}/phone`, req) | ||||
|   return http.post<T.LoginResp>(`${BASE_URL}/login`, req) | ||||
| } | ||||
|  | ||||
| /** @desc 邮箱登录 */ | ||||
| export function emailLogin(req: T.EmailLoginReq) { | ||||
|   return http.post<T.LoginResp>(`${BASE_URL}/email`, req) | ||||
|   return http.post<T.LoginResp>(`${BASE_URL}/login`, req) | ||||
| } | ||||
|  | ||||
| /** @desc 三方账号登录 */ | ||||
| export function socialLogin(source: string, req: any) { | ||||
|   return http.post<T.LoginResp>(`/oauth/${source}`, req) | ||||
| export function socialLogin(req: any) { | ||||
|   return http.post<T.LoginResp>(`${BASE_URL}/login`, req) | ||||
| } | ||||
|  | ||||
| /** @desc 三方账号登录授权 */ | ||||
|   | ||||
| @@ -41,8 +41,20 @@ export interface RouteItem { | ||||
|   affix: boolean | ||||
| } | ||||
|  | ||||
| /** 认证类型 */ | ||||
| export enum AuthTypeEnum { | ||||
|   ACCOUNT = 'account', | ||||
|   PHONE = 'phone', | ||||
|   EMAIL = 'email', | ||||
|   SOCIAL_AUTH = 'socialAuth', | ||||
| } | ||||
| export interface AuthReq { | ||||
|   clientId: string | ||||
|   authType: string | ||||
| } | ||||
|  | ||||
| /** 账号登录请求参数 */ | ||||
| export interface AccountLoginReq { | ||||
| export interface AccountLoginReq extends AuthReq { | ||||
|   username: string | ||||
|   password: string | ||||
|   captcha: string | ||||
| @@ -50,13 +62,13 @@ export interface AccountLoginReq { | ||||
| } | ||||
|  | ||||
| /** 手机号登录请求参数 */ | ||||
| export interface PhoneLoginReq { | ||||
| export interface PhoneLoginReq extends AuthReq { | ||||
|   phone: string | ||||
|   captcha: string | ||||
| } | ||||
|  | ||||
| /** 邮箱登录请求参数 */ | ||||
| export interface EmailLoginReq { | ||||
| export interface EmailLoginReq extends AuthReq { | ||||
|   email: string | ||||
|   captcha: string | ||||
| } | ||||
|   | ||||
							
								
								
									
										77
									
								
								src/apis/system/client.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								src/apis/system/client.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,77 @@ | ||||
| import http from '@/utils/http' | ||||
|  | ||||
| const BASE_URL = '/system/client' | ||||
|  | ||||
| export interface ClientResp { | ||||
|   id: string | ||||
|   clientId: string | ||||
|   clientKey: string | ||||
|   clientSecret: string | ||||
|   authType: string | ||||
|   clientType: string | ||||
|   activeTimeout: string | ||||
|   timeout: string | ||||
|   status: string | ||||
|   createUser: string | ||||
|   createTime: string | ||||
|   updateUser: string | ||||
|   updateTime: string | ||||
|   createUserString: string | ||||
|   updateUserString: string | ||||
| } | ||||
| export interface ClientDetailResp { | ||||
|   id: string | ||||
|   clientId: string | ||||
|   clientKey: string | ||||
|   clientSecret: string | ||||
|   authType: string | ||||
|   clientType: string | ||||
|   activeTimeout: string | ||||
|   timeout: string | ||||
|   status: string | ||||
|   createUser: string | ||||
|   createTime: string | ||||
|   updateUser: string | ||||
|   updateTime: string | ||||
|   createUserString: string | ||||
|   updateUserString: string | ||||
| } | ||||
| export interface ClientQuery { | ||||
|   clientKey: string | ||||
|   clientSecret: string | ||||
|   authType: string[] | ||||
|   clientType: string | ||||
|   status: string | ||||
|   sort: Array<string> | ||||
| } | ||||
| export interface ClientPageQuery extends ClientQuery, PageQuery {} | ||||
|  | ||||
| /** @desc 查询系统授权列表 */ | ||||
| export function listClient(query: ClientPageQuery) { | ||||
|   return http.get<PageRes<ClientResp[]>>(`${BASE_URL}`, query) | ||||
| } | ||||
|  | ||||
| /** @desc 查询系统授权详情 */ | ||||
| export function getClient(id: string) { | ||||
|   return http.get<ClientDetailResp>(`${BASE_URL}/${id}`) | ||||
| } | ||||
|  | ||||
| /** @desc 新增系统授权 */ | ||||
| export function addClient(data: any) { | ||||
|   return http.post(`${BASE_URL}`, data) | ||||
| } | ||||
|  | ||||
| /** @desc 修改系统授权 */ | ||||
| export function updateClient(data: any, id: string) { | ||||
|   return http.put(`${BASE_URL}/${id}`, data) | ||||
| } | ||||
|  | ||||
| /** @desc 删除系统授权 */ | ||||
| export function deleteClient(id: string) { | ||||
|   return http.del(`${BASE_URL}/${id}`) | ||||
| } | ||||
|  | ||||
| /** @desc 导出系统授权 */ | ||||
| export function exportClient(query: ClientQuery) { | ||||
|   return http.download(`${BASE_URL}/export`, query) | ||||
| } | ||||
| @@ -3,6 +3,7 @@ import { computed, reactive, ref } from 'vue' | ||||
| import { resetRouter } from '@/router' | ||||
| import { | ||||
|   type AccountLoginReq, | ||||
|   AuthTypeEnum, | ||||
|   type EmailLoginReq, | ||||
|   type PhoneLoginReq, | ||||
|   type UserInfo, | ||||
| @@ -71,7 +72,7 @@ const storeSetup = () => { | ||||
|  | ||||
|   // 三方账号登录 | ||||
|   const socialLogin = async (source: string, req: any) => { | ||||
|     const res = await socialLoginApi(source, req) | ||||
|     const res = await socialLoginApi({ ...req, source, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeEnum.SOCIAL_AUTH }) | ||||
|     setToken(res.data.token) | ||||
|     token.value = res.data.token | ||||
|   } | ||||
|   | ||||
							
								
								
									
										1
									
								
								src/types/components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/types/components.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -13,7 +13,6 @@ declare module 'vue' { | ||||
|     Chart: typeof import('./../components/Chart/index.vue')['default'] | ||||
|     CronForm: typeof import('./../components/GenCron/CronForm/index.vue')['default'] | ||||
|     CronModal: typeof import('./../components/GenCron/CronModal/index.vue')['default'] | ||||
|     CronModel: typeof import('./../components/GenCron/CronModel/index.vue')['default'] | ||||
|     DateRangePicker: typeof import('./../components/DateRangePicker/index.vue')['default'] | ||||
|     DayForm: typeof import('./../components/GenCron/CronForm/component/day-form.vue')['default'] | ||||
|     FilePreview: typeof import('./../components/FilePreview/index.vue')['default'] | ||||
|   | ||||
							
								
								
									
										1
									
								
								src/types/env.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								src/types/env.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -6,6 +6,7 @@ interface ImportMetaEnv { | ||||
|   readonly VITE_API_BASE_URL: string | ||||
|   readonly VITE_BASE: string | ||||
|   readonly VITE_APP_SETTING: string | ||||
|   readonly VITE_CLIENT_ID: string | ||||
| } | ||||
|  | ||||
| interface ImportMeta { | ||||
|   | ||||
| @@ -43,6 +43,7 @@ import { useStorage } from '@vueuse/core' | ||||
| import { getImageCaptcha } from '@/apis/common' | ||||
| import { useTabsStore, useUserStore } from '@/stores' | ||||
| import { encryptByRsa } from '@/utils/encrypt' | ||||
| import { AuthTypeEnum } from '@/apis' | ||||
|  | ||||
| const loginConfig = useStorage('login-config', { | ||||
|   rememberMe: true, | ||||
| @@ -119,6 +120,8 @@ const handleLogin = async () => { | ||||
|       password: encryptByRsa(form.password) || '', | ||||
|       captcha: form.captcha, | ||||
|       uuid: form.uuid, | ||||
|       clientId: import.meta.env.VITE_CLIENT_ID, | ||||
|       authType: AuthTypeEnum.ACCOUNT, | ||||
|     }) | ||||
|     tabsStore.reset() | ||||
|     const { redirect, ...othersQuery } = router.currentRoute.value.query | ||||
| @@ -132,6 +135,8 @@ const handleLogin = async () => { | ||||
|     loginConfig.value.username = rememberMe ? form.username : '' | ||||
|     Message.success('欢迎使用') | ||||
|   } catch (error) { | ||||
|     console.log('error', error) | ||||
|  | ||||
|     getCaptcha() | ||||
|     form.captcha = '' | ||||
|   } finally { | ||||
|   | ||||
| @@ -40,7 +40,7 @@ | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { type FormInstance, Message } from '@arco-design/web-vue' | ||||
| import type { BehaviorCaptchaReq } from '@/apis' | ||||
| import { AuthTypeEnum, type BehaviorCaptchaReq } from '@/apis' | ||||
| // import { type BehaviorCaptchaReq, getEmailCaptcha } from '@/apis' | ||||
| import { useTabsStore, useUserStore } from '@/stores' | ||||
| import * as Regexp from '@/utils/regexp' | ||||
| @@ -69,7 +69,7 @@ const handleLogin = async () => { | ||||
|     const isInvalid = await formRef.value?.validate() | ||||
|     if (isInvalid) return | ||||
|     loading.value = true | ||||
|     await userStore.emailLogin(form) | ||||
|     await userStore.emailLogin({ ...form, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeEnum.EMAIL }) | ||||
|     tabsStore.reset() | ||||
|     const { redirect, ...othersQuery } = router.currentRoute.value.query | ||||
|     await router.push({ | ||||
|   | ||||
| @@ -40,7 +40,7 @@ | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { type FormInstance, Message } from '@arco-design/web-vue' | ||||
| import type { BehaviorCaptchaReq } from '@/apis' | ||||
| import { AuthTypeEnum, type BehaviorCaptchaReq } from '@/apis' | ||||
| // import { type BehaviorCaptchaReq, getSmsCaptcha } from '@/apis' | ||||
| import { useTabsStore, useUserStore } from '@/stores' | ||||
| import * as Regexp from '@/utils/regexp' | ||||
| @@ -69,7 +69,7 @@ const handleLogin = async () => { | ||||
|   if (isInvalid) return | ||||
|   try { | ||||
|     loading.value = true | ||||
|     await userStore.phoneLogin(form) | ||||
|     await userStore.phoneLogin({ ...form, clientId: import.meta.env.VITE_CLIENT_ID, authType: AuthTypeEnum.PHONE }) | ||||
|     tabsStore.reset() | ||||
|     const { redirect, ...othersQuery } = router.currentRoute.value.query | ||||
|     await router.push({ | ||||
|   | ||||
							
								
								
									
										202
									
								
								src/views/system/client/ClientAddModal.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								src/views/system/client/ClientAddModal.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| <template> | ||||
|   <a-modal | ||||
|     v-model:visible="visible" | ||||
|     :title="title" | ||||
|     :mask-closable="false" | ||||
|     :esc-to-close="false" | ||||
|     draggable | ||||
|     :width="width >= 600 ? 600 : '100%'" | ||||
|     @before-ok="save" | ||||
|     @close="reset" | ||||
|   > | ||||
|     <GiForm ref="formRef" v-model="form" :options="options" :columns="columns" /> | ||||
|   </a-modal> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="tsx"> | ||||
| import { Message } from '@arco-design/web-vue' | ||||
| import { useWindowSize } from '@vueuse/core' | ||||
| import CryptoJS from 'crypto-js' | ||||
| import { addClient, getClient, updateClient } from '@/apis/system/client' | ||||
| import { type Columns, GiForm, type Options } from '@/components/GiForm' | ||||
| import { useResetReactive } from '@/hooks' | ||||
| import { useDict } from '@/hooks/app' | ||||
|  | ||||
| const emit = defineEmits<{ | ||||
|   (e: 'save-success'): void | ||||
| }>() | ||||
|  | ||||
| const { width } = useWindowSize() | ||||
|  | ||||
| const dataId = ref('') | ||||
| const visible = ref(false) | ||||
| const isUpdate = computed(() => !!dataId.value) | ||||
| const title = computed(() => (isUpdate.value ? '修改客户端管理' : '新增客户端管理')) | ||||
| const formRef = ref<InstanceType<typeof GiForm>>() | ||||
| const { auth_type_enum, client_type, dis_enable_status_enum } = useDict('auth_type_enum', 'client_type', 'dis_enable_status_enum') | ||||
|  | ||||
| const options: Options = { | ||||
|   form: { layout: 'vertical' }, | ||||
|   btns: { hide: true }, | ||||
|   grid: { cols: 2 }, | ||||
|  | ||||
| } | ||||
|  | ||||
| const [form, resetForm] = useResetReactive({ | ||||
|   activeTimeout: 1800, | ||||
|   timeout: 86400, | ||||
|   isConcurrent: 1, | ||||
|   isShare: 1, | ||||
|   status: 1, | ||||
| }) | ||||
| const handleGenerate = () => { | ||||
|   const timestamp = Date.now() | ||||
|   form.clientSecret = CryptoJS.MD5(`${timestamp}`).toString(CryptoJS.enc.Hex) | ||||
| } | ||||
| const columns: Columns = reactive([ | ||||
|   { | ||||
|     label: '客户端Key', | ||||
|     field: 'clientKey', | ||||
|     type: 'input', | ||||
|     rules: [{ required: true, message: '请输入客户端Key' }], | ||||
|     span: 2, | ||||
|     disabled: () => { | ||||
|       return isUpdate.value | ||||
|     }, | ||||
|   }, | ||||
|   { | ||||
|     label: '客户端秘钥', | ||||
|     field: 'clientSecret', | ||||
|     type: 'input', | ||||
|     rules: [{ required: true, message: '请输入客户端秘钥' }], | ||||
|     span: 2, | ||||
|     disabled: () => { | ||||
|       return isUpdate.value | ||||
|     }, | ||||
|     slots: { | ||||
|       append: () => ( | ||||
|         <a-button onClick={handleGenerate}> | ||||
|           {{ | ||||
|             default: '随机生成', | ||||
|             icon: <icon-refresh />, | ||||
|           }} | ||||
|         </a-button> | ||||
|       ), | ||||
|     }, | ||||
|   }, | ||||
|   { | ||||
|     label: '认证类型', | ||||
|     field: 'authType', | ||||
|     type: 'select', | ||||
|     options: auth_type_enum, | ||||
|     props: { | ||||
|       multiple: true, | ||||
|     }, | ||||
|     rules: [{ required: true, message: '请输入认证类型' }], | ||||
|   }, | ||||
|   { | ||||
|     label: '客户端类型', | ||||
|     field: 'clientType', | ||||
|     type: 'select', | ||||
|     options: client_type, | ||||
|     rules: [{ required: true, message: '请输入客户端类型' }], | ||||
|   }, | ||||
|   { | ||||
|     label: () => ( | ||||
|       <a-tooltip content="-1 代表不限制,永不冻结"> | ||||
|         Token最低活跃频率 | ||||
|         <icon-info-circle-fill /> | ||||
|       </a-tooltip> | ||||
|     ), | ||||
|     field: 'activeTimeout', | ||||
|     type: 'input-number', | ||||
|     rules: [{ required: true, message: 'Token最低活跃频率不能为空' }], | ||||
|     slots: { | ||||
|       append: () => ( | ||||
|         <span style={{ width: '30px', textAlign: 'center' }}>秒</span> | ||||
|       ), | ||||
|     }, | ||||
|  | ||||
|   }, | ||||
|   { | ||||
|     label: () => ( | ||||
|       <a-tooltip content="-1 代表永不过期"> | ||||
|         Token有效期 | ||||
|         <icon-info-circle-fill /> | ||||
|       </a-tooltip> | ||||
|     ), | ||||
|     field: 'timeout', | ||||
|     type: 'input-number', | ||||
|     rules: [{ required: true, message: 'Token有效期不能为空' }], | ||||
|     slots: { | ||||
|       append: () => ( | ||||
|         <span style={{ width: '30px', textAlign: 'center' }}>秒</span> | ||||
|       ), | ||||
|     }, | ||||
|   }, | ||||
|   { | ||||
|     label: '状态', | ||||
|     field: 'status', | ||||
|     type: 'radio-group', | ||||
|     props: { | ||||
|       type: 'button', | ||||
|       options: dis_enable_status_enum, | ||||
|     }, | ||||
|     rules: [{ required: true, message: '请选择状态' }], | ||||
|   }, | ||||
| ]) | ||||
|  | ||||
| // 重置 | ||||
| const reset = () => { | ||||
|   formRef.value?.formRef?.resetFields() | ||||
|   resetForm() | ||||
| } | ||||
|  | ||||
| // 保存 | ||||
| const save = async () => { | ||||
|   try { | ||||
|     const isInvalid = await formRef.value?.formRef?.validate() | ||||
|     if (isInvalid) return false | ||||
|     if (isUpdate.value) { | ||||
|       await updateClient(form, dataId.value) | ||||
|       Message.success('修改成功') | ||||
|     } else { | ||||
|       await addClient(form) | ||||
|       Message.success('新增成功') | ||||
|     } | ||||
|     emit('save-success') | ||||
|     return true | ||||
|   } catch (error) { | ||||
|     return false | ||||
|   } | ||||
| } | ||||
|  | ||||
| // 新增 | ||||
| const onAdd = async () => { | ||||
|   reset() | ||||
|   dataId.value = '' | ||||
|   visible.value = true | ||||
| } | ||||
|  | ||||
| // 修改 | ||||
| const onUpdate = async (id: string) => { | ||||
|   reset() | ||||
|   dataId.value = id | ||||
|   const { data } = await getClient(id) | ||||
|   Object.assign(form, data) | ||||
|   visible.value = true | ||||
| } | ||||
|  | ||||
| defineExpose({ onAdd, onUpdate }) | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="scss"> | ||||
| :deep(.arco-input-append) { | ||||
|   padding: 0; | ||||
|   .arco-btn { | ||||
|     border-top-left-radius: 0; | ||||
|     border-bottom-left-radius: 0; | ||||
|     border: 1px solid transparent; | ||||
|   } | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										63
									
								
								src/views/system/client/ClientDetailDrawer.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								src/views/system/client/ClientDetailDrawer.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| <template> | ||||
|   <a-drawer v-model:visible="visible" title="客户端管理详情" :width="width >= 600 ? 600 : '100%'" :footer="false"> | ||||
|     <a-descriptions :column="1" size="large" class="general-description"> | ||||
|       <a-descriptions-item label="ID">{{ dataDetail?.id }}</a-descriptions-item> | ||||
|       <a-descriptions-item label="客户端ID">{{ dataDetail?.clientId }}</a-descriptions-item> | ||||
|       <a-descriptions-item label="客户端Key">{{ dataDetail?.clientKey }}</a-descriptions-item> | ||||
|       <a-descriptions-item label="客户端秘钥">{{ dataDetail?.clientSecret }}</a-descriptions-item> | ||||
|       <a-descriptions-item label="认证类型"> | ||||
|         <a-space> | ||||
|           <GiCellTag v-for="(item, index) in dataDetail?.authType" :key="index" :value="item" :dict="auth_type_enum" /> | ||||
|         </a-space> | ||||
|       </a-descriptions-item> | ||||
|       <a-descriptions-item label="客户端类型"> | ||||
|         <GiCellTag :value="dataDetail?.clientType" :dict="client_type" /> | ||||
|       </a-descriptions-item> | ||||
|       <a-descriptions-item label="Token最低活跃频率">{{ dataDetail?.activeTimeout }}</a-descriptions-item> | ||||
|       <a-descriptions-item label="Token有效期">{{ dataDetail?.timeout }}</a-descriptions-item> | ||||
|       <a-descriptions-item label="状态"> | ||||
|         <GiCellTag :value="dataDetail?.status" :dict="dis_enable_status_enum" /> | ||||
|       </a-descriptions-item> | ||||
|       <a-descriptions-item label="创建人">{{ dataDetail?.createUserString }}</a-descriptions-item> | ||||
|       <a-descriptions-item label="创建时间">{{ dataDetail?.createTime }}</a-descriptions-item> | ||||
|       <a-descriptions-item label="更新人">{{ dataDetail?.updateUserString }}</a-descriptions-item> | ||||
|       <a-descriptions-item label="更新时间">{{ dataDetail?.updateTime }}</a-descriptions-item> | ||||
|     </a-descriptions> | ||||
|   </a-drawer> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { useWindowSize } from '@vueuse/core' | ||||
| import { type ClientDetailResp, getClient as getDetail } from '@/apis/system/client' | ||||
| import { useDict } from '@/hooks/app' | ||||
| import GiCellTag from '@/components/GiCell/GiCellTag.vue' | ||||
|  | ||||
| const { | ||||
|   auth_type_enum, | ||||
|   client_type, | ||||
|   dis_enable_status_enum, | ||||
| } = useDict('auth_type_enum', 'client_type', 'dis_enable_status_enum') | ||||
|  | ||||
| const { width } = useWindowSize() | ||||
|  | ||||
| const dataId = ref('') | ||||
| const dataDetail = ref<ClientDetailResp>() | ||||
| const visible = ref(false) | ||||
|  | ||||
| // 查询详情 | ||||
| const getDataDetail = async () => { | ||||
|   const { data } = await getDetail(dataId.value) | ||||
|   dataDetail.value = data | ||||
| } | ||||
|  | ||||
| // 打开 | ||||
| const onOpen = async (id: string) => { | ||||
|   dataId.value = id | ||||
|   await getDataDetail() | ||||
|   visible.value = true | ||||
| } | ||||
|  | ||||
| defineExpose({ onOpen }) | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="scss"></style> | ||||
							
								
								
									
										219
									
								
								src/views/system/client/index.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								src/views/system/client/index.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,219 @@ | ||||
| <template> | ||||
|   <div class="table-page"> | ||||
|     <GiTable | ||||
|       title="客户端管理" | ||||
|       row-key="id" | ||||
|       :data="dataList" | ||||
|       :columns="columns" | ||||
|       :loading="loading" | ||||
|       :scroll="{ x: '100%', y: '100%', minWidth: 1000 }" | ||||
|       :pagination="pagination" | ||||
|       :disabled-tools="['size']" | ||||
|       :disabled-column-keys="['name']" | ||||
|       @refresh="search" | ||||
|     > | ||||
|       <template #toolbar-left> | ||||
|         <a-input v-model="queryForm.clientKey" placeholder="请输入客户端Key" allow-clear @change="search"> | ||||
|           <template #prefix> | ||||
|             <icon-search /> | ||||
|           </template> | ||||
|         </a-input> | ||||
|         <a-input v-model="queryForm.clientSecret" placeholder="请输入客户端秘钥" allow-clear @change="search"> | ||||
|           <template #prefix> | ||||
|             <icon-search /> | ||||
|           </template> | ||||
|         </a-input> | ||||
|         <a-select | ||||
|           v-model="queryForm.clientType" | ||||
|           :options="client_type" | ||||
|           placeholder="请选择客户端类型" | ||||
|           allow-clear | ||||
|           style="width: 150px" | ||||
|           @change="search" | ||||
|         /> | ||||
|         <a-select | ||||
|           v-model="queryForm.status" :options="dis_enable_status_enum" placeholder="请选择状态" allow-clear | ||||
|           style="width: 150px" | ||||
|           @change="search" | ||||
|         > | ||||
|           <template #prefix> | ||||
|             <icon-search /> | ||||
|           </template> | ||||
|         </a-select> | ||||
|         <a-button @click="reset"> | ||||
|           <template #icon> | ||||
|             <icon-refresh /> | ||||
|           </template> | ||||
|           <template #default>重置</template> | ||||
|         </a-button> | ||||
|       </template> | ||||
|       <template #toolbar-right> | ||||
|         <a-button v-permission="['system:client:add']" type="primary" @click="onAdd"> | ||||
|           <template #icon> | ||||
|             <icon-plus /> | ||||
|           </template> | ||||
|           <template #default>新增</template> | ||||
|         </a-button> | ||||
|         <a-button v-permission="['system:client:export']" @click="onExport"> | ||||
|           <template #icon> | ||||
|             <icon-download /> | ||||
|           </template> | ||||
|           <template #default>导出</template> | ||||
|         </a-button> | ||||
|       </template> | ||||
|       <template #action="{ record }"> | ||||
|         <a-space> | ||||
|           <a-link v-permission="['system:client:detail']" title="详情" @click="onDetail(record)">详情</a-link> | ||||
|           <a-link v-permission="['system:client:update']" title="修改" @click="onUpdate(record)">修改</a-link> | ||||
|           <a-link | ||||
|             v-permission="['system:client:delete']" | ||||
|             status="danger" | ||||
|             :disabled="record.disabled" | ||||
|             :title="record.disabled ? '不可删除' : '删除'" | ||||
|             @click="onDelete(record)" | ||||
|           > | ||||
|             删除 | ||||
|           </a-link> | ||||
|         </a-space> | ||||
|       </template> | ||||
|     </GiTable> | ||||
|  | ||||
|     <ClientAddModal ref="ClientAddModalRef" @save-success="search" /> | ||||
|     <ClientDetailDrawer ref="ClientDetailDrawerRef" /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="tsx"> | ||||
| import type { LabelValue } from '@arco-design/web-vue/es/tree-select/interface' | ||||
| import ClientAddModal from './ClientAddModal.vue' | ||||
| import ClientDetailDrawer from './ClientDetailDrawer.vue' | ||||
| import { type ClientQuery, type ClientResp, deleteClient, exportClient, listClient } from '@/apis/system/client' | ||||
| import type { TableInstanceColumns } from '@/components/GiTable/type' | ||||
| import { useDownload, useTable } from '@/hooks' | ||||
| import { useDict } from '@/hooks/app' | ||||
| import { isMobile } from '@/utils' | ||||
| import has from '@/utils/has' | ||||
| import GiCellTag from '@/components/GiCell/GiCellTag.vue' | ||||
| import GiCellTags from '@/components/GiCell/GiCellTags.vue' | ||||
|  | ||||
| defineOptions({ name: 'Client' }) | ||||
|  | ||||
| const { | ||||
|   auth_type_enum, | ||||
|   client_type, | ||||
|   dis_enable_status_enum, | ||||
| } = useDict('auth_type_enum', 'client_type', 'dis_enable_status_enum') | ||||
|  | ||||
| const queryForm = reactive<ClientQuery>({ | ||||
|   clientKey: '', | ||||
|   clientSecret: '', | ||||
|   authType: [] as string[], | ||||
|   clientType: '', | ||||
|   status: '', | ||||
|   sort: ['id,desc'], | ||||
| }) | ||||
| const formatAuthType = (data: string[]) => { | ||||
|   return data.map((item: string) => { | ||||
|     return auth_type_enum.value.find((d: LabelValue) => d.value === item).label | ||||
|   }) | ||||
| } | ||||
| const { | ||||
|   tableData: dataList, | ||||
|   loading, | ||||
|   pagination, | ||||
|   search, | ||||
|   handleDelete, | ||||
| } = useTable((page) => listClient({ ...queryForm, ...page }), { immediate: true }) | ||||
| const columns: TableInstanceColumns[] = [ | ||||
|   { title: '客户端ID', dataIndex: 'clientId', slotName: 'clientId', ellipsis: true, tooltip: true }, | ||||
|   { title: '客户端Key', dataIndex: 'clientKey', slotName: 'clientKey', ellipsis: true, tooltip: true, align: 'center' }, | ||||
|   { title: '客户端秘钥', dataIndex: 'clientSecret', slotName: 'clientSecret', ellipsis: true, tooltip: true, align: 'center' }, | ||||
|   { | ||||
|     title: '认证类型', | ||||
|     dataIndex: 'authType', | ||||
|     slotName: 'authType', | ||||
|     ellipsis: true, | ||||
|     tooltip: true, | ||||
|     align: 'center', | ||||
|     render: ({ record }) => { | ||||
|       return ( | ||||
|         <GiCellTags data={formatAuthType(record.authType)} /> | ||||
|       ) | ||||
|     }, | ||||
|   }, | ||||
|   { | ||||
|     title: '客户端类型', | ||||
|     dataIndex: 'clientType', | ||||
|     slotName: 'clientType', | ||||
|     ellipsis: true, | ||||
|     tooltip: true, | ||||
|     align: 'center', | ||||
|     render: ({ record }) => { | ||||
|       return <GiCellTag value={record.clientType} dict={client_type.value} /> | ||||
|     }, | ||||
|   }, | ||||
|   { title: 'Token最低活跃频率', dataIndex: 'activeTimeout', slotName: 'activeTimeout', align: 'center' }, | ||||
|   { title: 'Token有效期', dataIndex: 'timeout', slotName: 'timeout', align: 'center' }, | ||||
|   { | ||||
|     title: '状态', | ||||
|     dataIndex: 'status', | ||||
|     slotName: 'status', | ||||
|     align: 'center', | ||||
|     render: ({ record }) => { | ||||
|       return <GiCellTag value={record.status} dict={dis_enable_status_enum.value} /> | ||||
|     }, | ||||
|   }, | ||||
|   { title: '创建时间', dataIndex: 'createTime', slotName: 'createTime' }, | ||||
|   { | ||||
|     title: '操作', | ||||
|     dataIndex: 'action', | ||||
|     slotName: 'action', | ||||
|     width: 160, | ||||
|     align: 'center', | ||||
|     fixed: !isMobile() ? 'right' : undefined, | ||||
|     show: has.hasPermOr(['system:client:detail', 'system:client:update', 'system:client:delete']), | ||||
|   }, | ||||
| ] | ||||
|  | ||||
| // 重置 | ||||
| const reset = () => { | ||||
|   queryForm.clientKey = '' | ||||
|   queryForm.clientSecret = '' | ||||
|   queryForm.authType = [] | ||||
|   queryForm.clientType = '' | ||||
|   queryForm.status = '' | ||||
|   search() | ||||
| } | ||||
|  | ||||
| // 删除 | ||||
| const onDelete = (record: ClientResp) => { | ||||
|   return handleDelete(() => deleteClient(record.id), { | ||||
|     content: `是否确定删除该条数据?`, | ||||
|     showModal: true, | ||||
|   }) | ||||
| } | ||||
|  | ||||
| // 导出 | ||||
| const onExport = () => { | ||||
|   useDownload(() => exportClient(queryForm)) | ||||
| } | ||||
|  | ||||
| const ClientAddModalRef = ref<InstanceType<typeof ClientAddModal>>() | ||||
| // 新增 | ||||
| const onAdd = () => { | ||||
|   ClientAddModalRef.value?.onAdd() | ||||
| } | ||||
|  | ||||
| // 修改 | ||||
| const onUpdate = (record: ClientResp) => { | ||||
|   ClientAddModalRef.value?.onUpdate(record.id) | ||||
| } | ||||
|  | ||||
| const ClientDetailDrawerRef = ref<InstanceType<typeof ClientDetailDrawer>>() | ||||
| // 详情 | ||||
| const onDetail = (record: ClientResp) => { | ||||
|   ClientDetailDrawerRef.value?.onOpen(record.id) | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped lang="scss"></style> | ||||
		Reference in New Issue
	
	Block a user
	 KAI
					KAI