feat: 增加系统API_Keys

--story=1015434 --user=王孝刚 【X-pack】系统API Key管理及接口文档 https://www.tapd.cn/57709429/s/1545690
This commit is contained in:
wxg0103 2024-07-09 14:48:08 +08:00
parent 60e65a8b17
commit 9ad82d5369
6 changed files with 366 additions and 16 deletions

View File

@ -0,0 +1,58 @@
import {Result} from '@/request/Result'
import {get, post, del, put} from '@/request/index'
import {type Ref} from 'vue'
const prefix = '/system/api_key'
/**
* API_KEY列表
*/
const getAPIKey: (loading?: Ref<boolean>) => Promise<Result<any>> = () => {
return get(`${prefix}/`)
}
/**
* API_KEY
*/
const postAPIKey: (loading?: Ref<boolean>) => Promise<Result<any>> = (
loading
) => {
return post(`${prefix}/`, {}, undefined, loading)
}
/**
* API_KEY
* @param application_id api_key_id
*/
const delAPIKey: (
api_key_id: String,
loading?: Ref<boolean>
) => Promise<Result<boolean>> = (api_key_id, loading) => {
return del(`${prefix}/${api_key_id}/`, undefined, undefined, loading)
}
/**
* API_KEY
* data {
* is_active: boolean
* }
* @param api_key_id
* @param data
* @param loading
*/
const putAPIKey: (
api_key_id: String,
data: any,
loading?: Ref<boolean>
) => Promise<Result<any>> = (api_key_id, data, loading) => {
return put(`${prefix}/${api_key_id}/`, data, undefined, loading)
}
export default {
getAPIKey,
postAPIKey,
delAPIKey,
putAPIKey
}

View File

@ -0,0 +1,185 @@
<template>
<el-dialog :title="$t('layout.topbar.avatar.apiKey')" v-model="dialogVisible" width="800">
<div class="mb-16 grey-background">
<el-text type="info">{{ $t('layout.topbar.avatar.apiServiceAddress') }}</el-text>
<p style="margin-top: 10px">
<a target="_blank" :href="apiUrl" class="vertical-middle lighter break-all" style="color: black;">
{{ apiUrl }}
</a>
<el-button type="primary" text @click="copyClick(apiUrl)">
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</p>
</div>
<el-button type="primary" class="mb-16" @click="createApiKey">
{{ $t('views.applicationOverview.appInfo.APIKeyDialog.creatApiKey') }}
</el-button>
<el-table :data="apiKey" class="mb-16" :loading="loading">
<el-table-column prop="secret_key" label="API Key">
<template #default="{ row }">
<span class="vertical-middle lighter break-all">
{{ row.secret_key }}
</span>
<el-button type="primary" text @click="copyClick(row.secret_key)">
<AppIcon iconName="app-copy"></AppIcon>
</el-button>
</template>
</el-table-column>
<el-table-column
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.status')"
width="60"
>
<template #default="{ row }">
<div @click.stop>
<el-switch size="small" v-model="row.is_active" @change="changeState($event, row)"/>
</div>
</template>
</el-table-column>
<el-table-column
prop="name"
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.creationDate')"
width="170"
>
<template #default="{ row }">
{{ datetimeFormat(row.create_time) }}
</template>
</el-table-column>
<el-table-column
:label="$t('views.applicationOverview.appInfo.APIKeyDialog.operations')"
align="left"
width="80"
>
<template #default="{ row }">
<span class="mr-4">
<el-tooltip
effect="dark"
:content="$t('views.applicationOverview.appInfo.APIKeyDialog.settings')"
placement="top"
>
<el-button type="primary" text @click.stop="settingApiKey(row)">
<el-icon><Setting/></el-icon>
</el-button>
</el-tooltip>
</span>
<el-tooltip
effect="dark"
:content="$t('views.applicationOverview.appInfo.APIKeyDialog.delete')"
placement="top"
>
<el-button type="primary" text @click="deleteApiKey(row)">
<el-icon>
<Delete/>
</el-icon>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<SettingAPIKeyDialog ref="SettingAPIKeyDialogRef" @refresh="refresh"/>
</el-dialog>
</template>
<script setup lang="ts">
import {ref, watch} from 'vue'
import {useRoute} from 'vue-router'
import {copyClick} from '@/utils/clipboard'
import overviewApi from '@/api/system-api-key'
import {datetimeFormat} from '@/utils/time'
import {MsgSuccess, MsgConfirm} from '@/utils/message'
import {t} from '@/locales'
import SettingAPIKeyDialog from "./SettingAPIKeyDialog.vue";
const route = useRoute()
const {
params: {id}
} = route
const props = defineProps({
userId: {
type: String,
default: ''
},
})
const emit = defineEmits(['addData'])
const apiUrl = window.location.origin + '/doc'
const SettingAPIKeyDialogRef = ref()
const dialogVisible = ref<boolean>(false)
const loading = ref(false)
const apiKey = ref<any>(null)
watch(dialogVisible, (bool) => {
if (!bool) {
apiKey.value = null
}
})
function settingApiKey(row: any) {
SettingAPIKeyDialogRef.value.open(row)
}
function deleteApiKey(row: any) {
MsgConfirm(
// @ts-ignore
`${t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm1')}: ${row.secret_key}?`,
t('views.applicationOverview.appInfo.APIKeyDialog.msgConfirm2'),
{
confirmButtonText: t('views.applicationOverview.appInfo.APIKeyDialog.confirmDelete'),
cancelButtonText: t('views.applicationOverview.appInfo.APIKeyDialog.cancel'),
confirmButtonClass: 'danger'
}
)
.then(() => {
overviewApi.delAPIKey(row.id, loading).then(() => {
MsgSuccess(t('views.applicationOverview.appInfo.APIKeyDialog.deleteSuccess'))
getApiKeyList()
})
})
.catch(() => {
})
}
function changeState(bool: Boolean, row: any) {
const obj = {
is_active: bool
}
const str = bool
? t('views.applicationOverview.appInfo.APIKeyDialog.enabledSuccess')
: t('views.applicationOverview.appInfo.APIKeyDialog.disabledSuccess')
overviewApi.putAPIKey(row.id, obj, loading).then((res) => {
MsgSuccess(str)
getApiKeyList()
})
}
function createApiKey() {
overviewApi.postAPIKey(loading).then((res) => {
getApiKeyList()
})
}
const open = () => {
getApiKeyList()
dialogVisible.value = true
}
function getApiKeyList() {
overviewApi.getAPIKey().then((res) => {
apiKey.value = res.data
})
}
function refresh() {
getApiKeyList()
}
defineExpose({open})
</script>
<style lang="scss" scope>
.grey-background {
background-color: #f2f2f2; /* 灰色背景 */
padding: 20px; /* 内边距 */
border-radius: 8px; /* 可选:添加圆角 */
}
</style>

View File

@ -0,0 +1,98 @@
<template>
<el-dialog :title="$t('views.applicationOverview.appInfo.SettingAPIKeyDialog.dialogTitle')" v-model="dialogVisible">
<el-form label-position="top" ref="settingFormRef" :model="form">
<el-form-item :label="$t('views.applicationOverview.appInfo.SettingAPIKeyDialog.allowCrossDomainLabel')"
@click.prevent>
<el-switch size="small" v-model="form.allow_cross_domain"></el-switch>
</el-form-item>
<el-form-item>
<el-input
v-model="form.cross_domain_list"
:placeholder="$t('views.applicationOverview.appInfo.SettingAPIKeyDialog.crossDomainPlaceholder')"
:rows="10"
type="textarea"
/>
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button
@click.prevent="dialogVisible = false">{{
$t('views.applicationOverview.appInfo.SettingAPIKeyDialog.cancelButtonText')
}}</el-button>
<el-button type="primary" @click="submit(settingFormRef)" :loading="loading">
{{ $t('views.applicationOverview.appInfo.SettingAPIKeyDialog.saveButtonText') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import {ref, watch} from 'vue'
import {useRoute} from 'vue-router'
import type {FormInstance} from 'element-plus'
import overviewApi from '@/api/system-api-key'
import {MsgSuccess} from '@/utils/message'
import {t} from '@/locales'
const route = useRoute()
const {
params: {id}
} = route
const emit = defineEmits(['refresh'])
const settingFormRef = ref()
const form = ref<any>({
allow_cross_domain: false,
cross_domain_list: ''
})
const dialogVisible = ref<boolean>(false)
const loading = ref(false)
const APIKeyId = ref('')
watch(dialogVisible, (bool) => {
if (!bool) {
form.value = {
allow_cross_domain: false,
cross_domain_list: ''
}
}
})
const open = (data: any) => {
APIKeyId.value = data.id
form.value.allow_cross_domain = data.allow_cross_domain
form.value.cross_domain_list = data.cross_domain_list?.length
? data.cross_domain_list?.join('\n')
: ''
dialogVisible.value = true
}
const submit = async (formEl: FormInstance | undefined) => {
if (!formEl) return
await formEl.validate((valid, fields) => {
if (valid) {
const obj = {
allow_cross_domain: form.value.allow_cross_domain,
cross_domain_list: form.value.cross_domain_list
? form.value.cross_domain_list.split('\n').filter(function (item: string) {
return item !== ''
})
: []
}
overviewApi.putAPIKey(APIKeyId.value, obj, loading).then((res) => {
emit('refresh')
//@ts-ignore
MsgSuccess(t('views.applicationOverview.appInfo.SettingAPIKeyDialog.successMessage'))
dialogVisible.value = false
})
}
})
}
defineExpose({open})
</script>
<style lang="scss" scope></style>

View File

@ -22,6 +22,9 @@
</div>
<el-dropdown-item class="border-t p-8" @click="openResetPassword">
{{ $t("layout.topbar.avatar.resetPassword") }}
</el-dropdown-item>
<el-dropdown-item class="border-t p-8" @click="openAPIKeyDialog">
{{ $t("layout.topbar.avatar.apiKey") }}
</el-dropdown-item>
<el-dropdown-item class="border-t" @click="openAbout"> {{ $t("layout.topbar.avatar.about") }} </el-dropdown-item>
<el-dropdown-item class="border-t" @click="logout"> {{ $t("layout.topbar.avatar.logout") }} </el-dropdown-item>
@ -30,6 +33,7 @@
</el-dropdown>
<ResetPassword ref="resetPasswordRef"></ResetPassword>
<AboutDialog ref="AboutDialogRef"></AboutDialog>
<APIKeyDialog :user-id="user.userInfo?.id" ref="APIKeyDialogRef" />
<UserPwdDialog ref="UserPwdDialogRef" />
</template>
<script setup lang="ts">
@ -39,16 +43,21 @@ import { useRouter } from 'vue-router'
import ResetPassword from './ResetPassword.vue'
import AboutDialog from './AboutDialog.vue'
import UserPwdDialog from '@/views/user-manage/component/UserPwdDialog.vue'
import APIKeyDialog from "./APIKeyDialog.vue";
const { user } = useStore()
const router = useRouter()
const UserPwdDialogRef = ref()
const AboutDialogRef = ref()
const APIKeyDialogRef = ref()
const resetPasswordRef = ref<InstanceType<typeof ResetPassword>>()
const openAbout = () => {
AboutDialogRef.value?.open()
}
function openAPIKeyDialog() {
APIKeyDialogRef.value.open()
}
const openResetPassword = () => {
resetPasswordRef.value?.open()

View File

@ -1,10 +1,6 @@
export default {
breadcrumb: {
},
sidebar: {
},
breadcrumb: {},
sidebar: {},
topbar: {
github: "Project address",
wiki: "User manual",
@ -18,21 +14,23 @@ export default {
resetPassword: "Change password",
about: "About",
logout: "Logout",
version:"Version",
dialog:{
newPassword:"New password",
version: "Version",
apiKey: "API Key",
apiServiceAddress: "API Service Address",
dialog: {
newPassword: "New password",
enterPassword: "Please enter new password",
confirmPassword: "Confirm password",
passwordLength:"Password length should be between 6 and 20 characters",
passwordMismatch:"Passwords do not match",
useEmail:"Use email",
passwordLength: "Password length should be between 6 and 20 characters",
passwordMismatch: "Passwords do not match",
useEmail: "Use email",
enterEmail: "Please enter email",
enterVerificationCode: "Please enter verification code",
getVerificationCode: "Get verification code",
verificationCodeSentSuccess:"Verification code sent successfully",
resend:"Resend",
cancel:"Cancel",
save:"Save",
verificationCodeSentSuccess: "Verification code sent successfully",
resend: "Resend",
cancel: "Cancel",
save: "Save",
}
}
},

View File

@ -19,6 +19,8 @@ export default {
about: "关于",
logout: "退出",
version:"版本号",
apiKey: "API Key 管理",
apiServiceAddress: "API 服务地址",
dialog:{
newPassword:"新密码",
enterPassword: "请输入修改密码",