feat: 系统设置页面增加个人资料和开发者标签页
- 使用 naive-ui Tabs 组件实现标签页布局,对齐 jcloud dashboard 前端 - 个人资料标签页:显示和编辑用户信息(用户名、手机、邮箱)、邮箱通知设置 - 开发者标签页:API 访问(创建/重新生成 API Key)、SSH 密钥管理、功能标志配置 - 创建账户相关 API 接口文件(account.ts),使用 jcloud.api.account.get 获取用户信息 - 创建 ClickToCopyField 组件用于一键复制文本内容 - API 接口与 jcloud dashboard 保持一致,实现相同的功能
This commit is contained in:
parent
4b3ebaa7ed
commit
911ae5e53b
259
src/shared/api/account.ts
Normal file
259
src/shared/api/account.ts
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { get_session_api_headers } from './auth'
|
||||||
|
|
||||||
|
// 创建或重新生成 API Secret
|
||||||
|
export const createApiSecret = async (): Promise<{ success: boolean; data?: { api_key: string; api_secret: string }; message?: string }> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`/api/action/jcloud.api.account.create_api_secret`,
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
headers: get_session_api_headers(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = response.data
|
||||||
|
if (result?.message) {
|
||||||
|
return { success: true, data: result.message }
|
||||||
|
}
|
||||||
|
return { success: true, data: result }
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.response?.data?.detail || error.response?.data?.message || error.message || '创建 API Secret 失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新个人资料
|
||||||
|
export interface UpdateProfileParams {
|
||||||
|
first_name?: string
|
||||||
|
last_name?: string
|
||||||
|
username?: string
|
||||||
|
mobile_no?: string
|
||||||
|
email?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const updateProfile = async (params: UpdateProfileParams): Promise<{ success: boolean; message?: string }> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`/api/action/jcloud.api.account.update_profile`,
|
||||||
|
params,
|
||||||
|
{
|
||||||
|
headers: get_session_api_headers(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return { success: true, message: response.data?.message || '更新成功' }
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.response?.data?.detail || error.response?.data?.message || error.message || '更新个人资料失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取邮箱列表
|
||||||
|
export const getEmails = async (): Promise<{ success: boolean; data?: Array<{ type: string; value: string }>; message?: string }> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/api/action/jcloud.api.account.get_emails`,
|
||||||
|
{
|
||||||
|
headers: get_session_api_headers(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = response.data
|
||||||
|
return { success: true, data: result?.message || result }
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.response?.data?.detail || error.response?.data?.message || error.message || '获取邮箱列表失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新邮箱
|
||||||
|
export const updateEmails = async (data: Array<{ type: string; value: string }>): Promise<{ success: boolean; message?: string }> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`/api/action/jcloud.api.account.update_emails`,
|
||||||
|
{ data: JSON.stringify(data) },
|
||||||
|
{
|
||||||
|
headers: get_session_api_headers(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return { success: true, message: response.data?.message || '更新成功' }
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.response?.data?.detail || error.response?.data?.message || error.message || '更新邮箱失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户 SSH 密钥列表
|
||||||
|
export const getUserSSHKeys = async (): Promise<{ success: boolean; data?: Array<any>; message?: string }> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/api/action/jcloud.api.account.get_user_ssh_keys`,
|
||||||
|
{
|
||||||
|
headers: get_session_api_headers(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = response.data
|
||||||
|
return { success: true, data: result?.message || result || [] }
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.response?.data?.detail || error.response?.data?.message || error.message || '获取 SSH 密钥列表失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 SSH 密钥
|
||||||
|
export const addSSHKey = async (sshPublicKey: string): Promise<{ success: boolean; message?: string }> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`/api/action/jcloud.api.client.insert`,
|
||||||
|
{
|
||||||
|
pg: {
|
||||||
|
pagetype: 'User SSH Key',
|
||||||
|
ssh_public_key: sshPublicKey,
|
||||||
|
user: '' // 后端会自动获取当前用户
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: get_session_api_headers(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return { success: true, message: response.data?.message || '添加成功' }
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.response?.data?.detail || error.response?.data?.message || error.message || '添加 SSH 密钥失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置 SSH 密钥为默认
|
||||||
|
export const markKeyAsDefault = async (keyName: string): Promise<{ success: boolean; message?: string }> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`/api/action/jcloud.api.account.mark_key_as_default`,
|
||||||
|
{ key_name: keyName },
|
||||||
|
{
|
||||||
|
headers: get_session_api_headers(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return { success: true, message: response.data?.message || '设置成功' }
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.response?.data?.detail || error.response?.data?.message || error.message || '设置默认密钥失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除 SSH 密钥
|
||||||
|
export const deleteSSHKey = async (keyName: string): Promise<{ success: boolean; message?: string }> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`/api/action/jcloud.api.client.delete`,
|
||||||
|
{
|
||||||
|
pagetype: 'User SSH Key',
|
||||||
|
name: keyName
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: get_session_api_headers(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return { success: true, message: response.data?.message || '删除成功' }
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.response?.data?.detail || error.response?.data?.message || error.message || '删除 SSH 密钥失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取功能标志
|
||||||
|
export const getFeatureFlags = async (): Promise<{ success: boolean; data?: Record<string, boolean>; message?: string }> => {
|
||||||
|
try {
|
||||||
|
// 这里需要根据实际 API 调整
|
||||||
|
const response = await axios.get(
|
||||||
|
`/api/action/jcloud.api.account.get_feature_flags`,
|
||||||
|
{
|
||||||
|
headers: get_session_api_headers(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = response.data
|
||||||
|
return { success: true, data: result?.message || result }
|
||||||
|
} catch (error: any) {
|
||||||
|
// 如果 API 不存在,返回空对象
|
||||||
|
return { success: false, data: {}, message: '功能标志 API 不可用' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新功能标志
|
||||||
|
export const updateFeatureFlags = async (values: Record<string, boolean>): Promise<{ success: boolean; message?: string }> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.post(
|
||||||
|
`/api/action/jcloud.api.account.update_feature_flags`,
|
||||||
|
{ values },
|
||||||
|
{
|
||||||
|
headers: get_session_api_headers(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return { success: true, message: response.data?.message || '更新成功' }
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.response?.data?.detail || error.response?.data?.message || error.message || '更新功能标志失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户信息(包含 API Key)
|
||||||
|
// 使用 jcloud.api.account.get API 获取账户信息
|
||||||
|
export const getUserAccountInfo = async (): Promise<{ success: boolean; data?: any; message?: string }> => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(
|
||||||
|
`/api/action/jcloud.api.account.get`,
|
||||||
|
{
|
||||||
|
headers: get_session_api_headers(),
|
||||||
|
withCredentials: true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const result = response.data?.message || response.data
|
||||||
|
|
||||||
|
if (result?.user) {
|
||||||
|
return { success: true, data: result.user }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false, message: 'API 返回的数据中未找到用户信息' }
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: error.response?.data?.detail || error.response?.data?.message || error.message || '获取用户信息失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
src/shared/components/ClickToCopyField.vue
Normal file
78
src/shared/components/ClickToCopyField.vue
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<div class="click-to-copy-field">
|
||||||
|
<n-input
|
||||||
|
:value="textContent"
|
||||||
|
readonly
|
||||||
|
:placeholder="placeholder"
|
||||||
|
class="copy-input"
|
||||||
|
>
|
||||||
|
<template #suffix>
|
||||||
|
<n-button
|
||||||
|
quaternary
|
||||||
|
size="small"
|
||||||
|
@click="handleCopy"
|
||||||
|
:loading="copying"
|
||||||
|
class="copy-button"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<n-icon>
|
||||||
|
<Icon :icon="copied ? 'tabler:check' : 'tabler:copy'" />
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
</n-button>
|
||||||
|
</template>
|
||||||
|
</n-input>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { NInput, NButton, NIcon, useMessage } from 'naive-ui'
|
||||||
|
import { Icon } from '@iconify/vue'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
textContent: string
|
||||||
|
placeholder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
placeholder: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const message = useMessage()
|
||||||
|
const copying = ref(false)
|
||||||
|
const copied = ref(false)
|
||||||
|
|
||||||
|
const handleCopy = async () => {
|
||||||
|
if (!props.textContent) return
|
||||||
|
|
||||||
|
copying.value = true
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(props.textContent)
|
||||||
|
copied.value = true
|
||||||
|
message.success('已复制到剪贴板')
|
||||||
|
setTimeout(() => {
|
||||||
|
copied.value = false
|
||||||
|
}, 2000)
|
||||||
|
} catch (error) {
|
||||||
|
message.error('复制失败')
|
||||||
|
} finally {
|
||||||
|
copying.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.click-to-copy-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-input {
|
||||||
|
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copy-button {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user