mirror of
https://github.com/fawney19/Aether.git
synced 2026-01-02 15:52:26 +08:00
refactor: 统一剪贴板复制功能到 useClipboard 组合式函数
将各个组件和视图中重复的剪贴板复制逻辑提取到 useClipboard 组合式函数。 增加 showToast 参数支持静默复制,减少代码重复,提高维护性。
This commit is contained in:
@@ -4,11 +4,11 @@ import { log } from '@/utils/logger'
|
|||||||
export function useClipboard() {
|
export function useClipboard() {
|
||||||
const { success, error: showError } = useToast()
|
const { success, error: showError } = useToast()
|
||||||
|
|
||||||
async function copyToClipboard(text: string): Promise<boolean> {
|
async function copyToClipboard(text: string, showToast = true): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
await navigator.clipboard.writeText(text)
|
await navigator.clipboard.writeText(text)
|
||||||
success('已复制到剪贴板')
|
if (showToast) success('已复制到剪贴板')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,17 +25,17 @@ export function useClipboard() {
|
|||||||
try {
|
try {
|
||||||
const successful = document.execCommand('copy')
|
const successful = document.execCommand('copy')
|
||||||
if (successful) {
|
if (successful) {
|
||||||
success('已复制到剪贴板')
|
if (showToast) success('已复制到剪贴板')
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
showError('复制失败,请手动复制')
|
if (showToast) showError('复制失败,请手动复制')
|
||||||
return false
|
return false
|
||||||
} finally {
|
} finally {
|
||||||
document.body.removeChild(textArea)
|
document.body.removeChild(textArea)
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('复制失败:', err)
|
log.error('复制失败:', err)
|
||||||
showError('复制失败,请手动选择文本进行复制')
|
if (showToast) showError('复制失败,请手动选择文本进行复制')
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -700,6 +700,7 @@ import {
|
|||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { useEscapeKey } from '@/composables/useEscapeKey'
|
import { useEscapeKey } from '@/composables/useEscapeKey'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
|
import { useClipboard } from '@/composables/useClipboard'
|
||||||
import Card from '@/components/ui/card.vue'
|
import Card from '@/components/ui/card.vue'
|
||||||
import Badge from '@/components/ui/badge.vue'
|
import Badge from '@/components/ui/badge.vue'
|
||||||
import Button from '@/components/ui/button.vue'
|
import Button from '@/components/ui/button.vue'
|
||||||
@@ -731,6 +732,7 @@ const emit = defineEmits<{
|
|||||||
'refreshProviders': []
|
'refreshProviders': []
|
||||||
}>()
|
}>()
|
||||||
const { success: showSuccess, error: showError } = useToast()
|
const { success: showSuccess, error: showError } = useToast()
|
||||||
|
const { copyToClipboard } = useClipboard()
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
model: GlobalModelResponse | null
|
model: GlobalModelResponse | null
|
||||||
@@ -763,16 +765,6 @@ function handleClose() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制到剪贴板
|
|
||||||
async function copyToClipboard(text: string) {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text)
|
|
||||||
showSuccess('已复制')
|
|
||||||
} catch {
|
|
||||||
showError('复制失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化日期
|
// 格式化日期
|
||||||
function formatDate(dateStr: string): string {
|
function formatDate(dateStr: string): string {
|
||||||
if (!dateStr) return '-'
|
if (!dateStr) return '-'
|
||||||
|
|||||||
@@ -661,6 +661,7 @@ import Button from '@/components/ui/button.vue'
|
|||||||
import Badge from '@/components/ui/badge.vue'
|
import Badge from '@/components/ui/badge.vue'
|
||||||
import Card from '@/components/ui/card.vue'
|
import Card from '@/components/ui/card.vue'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
|
import { useClipboard } from '@/composables/useClipboard'
|
||||||
import { getProvider, getProviderEndpoints } from '@/api/endpoints'
|
import { getProvider, getProviderEndpoints } from '@/api/endpoints'
|
||||||
import {
|
import {
|
||||||
KeyFormDialog,
|
KeyFormDialog,
|
||||||
@@ -706,6 +707,7 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { error: showError, success: showSuccess } = useToast()
|
const { error: showError, success: showSuccess } = useToast()
|
||||||
|
const { copyToClipboard } = useClipboard()
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const provider = ref<any>(null)
|
const provider = ref<any>(null)
|
||||||
@@ -1250,16 +1252,6 @@ function getHealthScoreBarColor(score: number): string {
|
|||||||
return 'bg-red-500 dark:bg-red-400'
|
return 'bg-red-500 dark:bg-red-400'
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制到剪贴板
|
|
||||||
async function copyToClipboard(text: string) {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text)
|
|
||||||
showSuccess('已复制到剪贴板')
|
|
||||||
} catch {
|
|
||||||
showError('复制失败', '错误')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 加载 Provider 信息
|
// 加载 Provider 信息
|
||||||
async function loadProvider() {
|
async function loadProvider() {
|
||||||
if (!props.providerId) return
|
if (!props.providerId) return
|
||||||
|
|||||||
@@ -224,6 +224,7 @@ import { Box, Edit, Trash2, Layers, Eye, Wrench, Zap, Brain, Power, Copy, Image,
|
|||||||
import Card from '@/components/ui/card.vue'
|
import Card from '@/components/ui/card.vue'
|
||||||
import Button from '@/components/ui/button.vue'
|
import Button from '@/components/ui/button.vue'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
|
import { useClipboard } from '@/composables/useClipboard'
|
||||||
import { getProviderModels, testModel, type Model } from '@/api/endpoints'
|
import { getProviderModels, testModel, type Model } from '@/api/endpoints'
|
||||||
import { updateModel } from '@/api/endpoints/models'
|
import { updateModel } from '@/api/endpoints/models'
|
||||||
import { parseTestModelError } from '@/utils/errorParser'
|
import { parseTestModelError } from '@/utils/errorParser'
|
||||||
@@ -239,6 +240,7 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { error: showError, success: showSuccess } = useToast()
|
const { error: showError, success: showSuccess } = useToast()
|
||||||
|
const { copyToClipboard } = useClipboard()
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -257,12 +259,7 @@ const sortedModels = computed(() => {
|
|||||||
|
|
||||||
// 复制模型 ID 到剪贴板
|
// 复制模型 ID 到剪贴板
|
||||||
async function copyModelId(modelId: string) {
|
async function copyModelId(modelId: string) {
|
||||||
try {
|
await copyToClipboard(modelId)
|
||||||
await navigator.clipboard.writeText(modelId)
|
|
||||||
showSuccess('已复制到剪贴板')
|
|
||||||
} catch {
|
|
||||||
showError('复制失败', '错误')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载模型
|
// 加载模型
|
||||||
|
|||||||
@@ -473,6 +473,7 @@
|
|||||||
import { ref, watch, computed } from 'vue'
|
import { ref, watch, computed } from 'vue'
|
||||||
import Button from '@/components/ui/button.vue'
|
import Button from '@/components/ui/button.vue'
|
||||||
import { useEscapeKey } from '@/composables/useEscapeKey'
|
import { useEscapeKey } from '@/composables/useEscapeKey'
|
||||||
|
import { useClipboard } from '@/composables/useClipboard'
|
||||||
import Card from '@/components/ui/card.vue'
|
import Card from '@/components/ui/card.vue'
|
||||||
import Badge from '@/components/ui/badge.vue'
|
import Badge from '@/components/ui/badge.vue'
|
||||||
import Separator from '@/components/ui/separator.vue'
|
import Separator from '@/components/ui/separator.vue'
|
||||||
@@ -505,6 +506,7 @@ const copiedStates = ref<Record<string, boolean>>({})
|
|||||||
const viewMode = ref<'compare' | 'formatted' | 'raw'>('compare')
|
const viewMode = ref<'compare' | 'formatted' | 'raw'>('compare')
|
||||||
const currentExpandDepth = ref(1)
|
const currentExpandDepth = ref(1)
|
||||||
const dataSource = ref<'client' | 'provider'>('client')
|
const dataSource = ref<'client' | 'provider'>('client')
|
||||||
|
const { copyToClipboard } = useClipboard()
|
||||||
const historicalPricing = ref<{
|
const historicalPricing = ref<{
|
||||||
input_price: string
|
input_price: string
|
||||||
output_price: string
|
output_price: string
|
||||||
@@ -784,7 +786,7 @@ function copyJsonToClipboard(tabName: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (data) {
|
if (data) {
|
||||||
navigator.clipboard.writeText(JSON.stringify(data, null, 2))
|
copyToClipboard(JSON.stringify(data, null, 2), false)
|
||||||
copiedStates.value[tabName] = true
|
copiedStates.value[tabName] = true
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
copiedStates.value[tabName] = false
|
copiedStates.value[tabName] = false
|
||||||
|
|||||||
@@ -650,6 +650,7 @@
|
|||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
import { useConfirm } from '@/composables/useConfirm'
|
import { useConfirm } from '@/composables/useConfirm'
|
||||||
|
import { useClipboard } from '@/composables/useClipboard'
|
||||||
import { adminApi, type AdminApiKey, type CreateStandaloneApiKeyRequest } from '@/api/admin'
|
import { adminApi, type AdminApiKey, type CreateStandaloneApiKeyRequest } from '@/api/admin'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -693,6 +694,7 @@ import { log } from '@/utils/logger'
|
|||||||
|
|
||||||
const { success, error } = useToast()
|
const { success, error } = useToast()
|
||||||
const { confirmDanger } = useConfirm()
|
const { confirmDanger } = useConfirm()
|
||||||
|
const { copyToClipboard } = useClipboard()
|
||||||
|
|
||||||
const apiKeys = ref<AdminApiKey[]>([])
|
const apiKeys = ref<AdminApiKey[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -927,20 +929,14 @@ function selectKey() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function copyKey() {
|
async function copyKey() {
|
||||||
try {
|
await copyToClipboard(newKeyValue.value)
|
||||||
await navigator.clipboard.writeText(newKeyValue.value)
|
|
||||||
success('API Key 已复制到剪贴板')
|
|
||||||
} catch {
|
|
||||||
error('复制失败,请手动复制')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyKeyPrefix(apiKey: AdminApiKey) {
|
async function copyKeyPrefix(apiKey: AdminApiKey) {
|
||||||
try {
|
try {
|
||||||
// 调用后端 API 获取完整密钥
|
// 调用后端 API 获取完整密钥
|
||||||
const response = await adminApi.getFullApiKey(apiKey.id)
|
const response = await adminApi.getFullApiKey(apiKey.id)
|
||||||
await navigator.clipboard.writeText(response.key)
|
await copyToClipboard(response.key)
|
||||||
success('完整密钥已复制到剪贴板')
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('复制密钥失败:', err)
|
log.error('复制密钥失败:', err)
|
||||||
error('复制失败,请重试')
|
error('复制失败,请重试')
|
||||||
|
|||||||
@@ -713,6 +713,7 @@ import ProviderModelFormDialog from '@/features/providers/components/ProviderMod
|
|||||||
import type { Model } from '@/api/endpoints'
|
import type { Model } from '@/api/endpoints'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
import { useConfirm } from '@/composables/useConfirm'
|
import { useConfirm } from '@/composables/useConfirm'
|
||||||
|
import { useClipboard } from '@/composables/useClipboard'
|
||||||
import { useRowClick } from '@/composables/useRowClick'
|
import { useRowClick } from '@/composables/useRowClick'
|
||||||
import { parseApiError } from '@/utils/errorParser'
|
import { parseApiError } from '@/utils/errorParser'
|
||||||
import {
|
import {
|
||||||
@@ -743,6 +744,7 @@ import { getProvidersSummary } from '@/api/endpoints/providers'
|
|||||||
import { getAllCapabilities, type CapabilityDefinition } from '@/api/endpoints'
|
import { getAllCapabilities, type CapabilityDefinition } from '@/api/endpoints'
|
||||||
|
|
||||||
const { success, error: showError } = useToast()
|
const { success, error: showError } = useToast()
|
||||||
|
const { copyToClipboard } = useClipboard()
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -1066,16 +1068,6 @@ function handleRowClick(event: MouseEvent, model: GlobalModelResponse) {
|
|||||||
selectModel(model)
|
selectModel(model)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 复制到剪贴板
|
|
||||||
async function copyToClipboard(text: string) {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text)
|
|
||||||
success('已复制')
|
|
||||||
} catch {
|
|
||||||
showError('复制失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function selectModel(model: GlobalModelResponse) {
|
async function selectModel(model: GlobalModelResponse) {
|
||||||
selectedModel.value = model
|
selectedModel.value = model
|
||||||
detailTab.value = 'basic'
|
detailTab.value = 'basic'
|
||||||
|
|||||||
@@ -701,6 +701,7 @@ import { ref, computed, onMounted, watch } from 'vue'
|
|||||||
import { useUsersStore } from '@/stores/users'
|
import { useUsersStore } from '@/stores/users'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
import { useConfirm } from '@/composables/useConfirm'
|
import { useConfirm } from '@/composables/useConfirm'
|
||||||
|
import { useClipboard } from '@/composables/useClipboard'
|
||||||
import { usageApi, type UsageByUser } from '@/api/usage'
|
import { usageApi, type UsageByUser } from '@/api/usage'
|
||||||
import { adminApi } from '@/api/admin'
|
import { adminApi } from '@/api/admin'
|
||||||
|
|
||||||
@@ -748,6 +749,7 @@ import { log } from '@/utils/logger'
|
|||||||
|
|
||||||
const { success, error } = useToast()
|
const { success, error } = useToast()
|
||||||
const { confirmDanger, confirmWarning } = useConfirm()
|
const { confirmDanger, confirmWarning } = useConfirm()
|
||||||
|
const { copyToClipboard } = useClipboard()
|
||||||
const usersStore = useUsersStore()
|
const usersStore = useUsersStore()
|
||||||
|
|
||||||
// 用户表单对话框状态
|
// 用户表单对话框状态
|
||||||
@@ -1001,12 +1003,7 @@ function selectApiKey() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function copyApiKey() {
|
async function copyApiKey() {
|
||||||
try {
|
await copyToClipboard(newApiKey.value)
|
||||||
await navigator.clipboard.writeText(newApiKey.value)
|
|
||||||
success('API Key已复制到剪贴板')
|
|
||||||
} catch {
|
|
||||||
error('复制失败,请手动复制')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function closeNewApiKeyDialog() {
|
async function closeNewApiKeyDialog() {
|
||||||
@@ -1035,8 +1032,7 @@ async function copyFullKey(apiKey: any) {
|
|||||||
try {
|
try {
|
||||||
// 调用后端 API 获取完整密钥
|
// 调用后端 API 获取完整密钥
|
||||||
const response = await adminApi.getFullApiKey(apiKey.id)
|
const response = await adminApi.getFullApiKey(apiKey.id)
|
||||||
await navigator.clipboard.writeText(response.key)
|
await copyToClipboard(response.key)
|
||||||
success('完整密钥已复制到剪贴板')
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
log.error('复制密钥失败:', err)
|
log.error('复制密钥失败:', err)
|
||||||
error(err.response?.data?.error?.message || err.response?.data?.detail || '未知错误', '复制密钥失败')
|
error(err.response?.data?.error?.message || err.response?.data?.detail || '未知错误', '复制密钥失败')
|
||||||
|
|||||||
@@ -342,6 +342,7 @@ import {
|
|||||||
Plus,
|
Plus,
|
||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
|
import { useClipboard } from '@/composables/useClipboard'
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
Table,
|
Table,
|
||||||
@@ -370,6 +371,7 @@ import { useRowClick } from '@/composables/useRowClick'
|
|||||||
import { log } from '@/utils/logger'
|
import { log } from '@/utils/logger'
|
||||||
|
|
||||||
const { success, error: showError } = useToast()
|
const { success, error: showError } = useToast()
|
||||||
|
const { copyToClipboard } = useClipboard()
|
||||||
|
|
||||||
// 状态
|
// 状态
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -565,16 +567,6 @@ function hasTieredPricing(model: PublicGlobalModel): boolean {
|
|||||||
return (tiered?.tiers?.length || 0) > 1
|
return (tiered?.tiers?.length || 0) > 1
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyToClipboard(text: string) {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text)
|
|
||||||
success('已复制')
|
|
||||||
} catch (err) {
|
|
||||||
log.error('复制失败:', err)
|
|
||||||
showError('复制失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
refreshData()
|
refreshData()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -352,6 +352,7 @@ import {
|
|||||||
} from 'lucide-vue-next'
|
} from 'lucide-vue-next'
|
||||||
import { useEscapeKey } from '@/composables/useEscapeKey'
|
import { useEscapeKey } from '@/composables/useEscapeKey'
|
||||||
import { useToast } from '@/composables/useToast'
|
import { useToast } from '@/composables/useToast'
|
||||||
|
import { useClipboard } from '@/composables/useClipboard'
|
||||||
import Card from '@/components/ui/card.vue'
|
import Card from '@/components/ui/card.vue'
|
||||||
import Badge from '@/components/ui/badge.vue'
|
import Badge from '@/components/ui/badge.vue'
|
||||||
import Button from '@/components/ui/button.vue'
|
import Button from '@/components/ui/button.vue'
|
||||||
@@ -375,6 +376,7 @@ const emit = defineEmits<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { success: showSuccess, error: showError } = useToast()
|
const { success: showSuccess, error: showError } = useToast()
|
||||||
|
const { copyToClipboard } = useClipboard()
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
model: PublicGlobalModel | null
|
model: PublicGlobalModel | null
|
||||||
@@ -408,15 +410,6 @@ function handleClose() {
|
|||||||
emit('update:open', false)
|
emit('update:open', false)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyToClipboard(text: string) {
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(text)
|
|
||||||
showSuccess('已复制')
|
|
||||||
} catch {
|
|
||||||
showError('复制失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getFirstTierPrice(
|
function getFirstTierPrice(
|
||||||
tieredPricing: TieredPricingConfig | undefined | null,
|
tieredPricing: TieredPricingConfig | undefined | null,
|
||||||
priceKey: 'input_price_per_1m' | 'output_price_per_1m' | 'cache_creation_price_per_1m' | 'cache_read_price_per_1m'
|
priceKey: 'input_price_per_1m' | 'output_price_per_1m' | 'cache_creation_price_per_1m' | 'cache_read_price_per_1m'
|
||||||
|
|||||||
Reference in New Issue
Block a user