diff --git a/lesingle-creation-frontend/src/utils/clipboard.ts b/lesingle-creation-frontend/src/utils/clipboard.ts new file mode 100644 index 0000000..255eed3 --- /dev/null +++ b/lesingle-creation-frontend/src/utils/clipboard.ts @@ -0,0 +1,38 @@ +/** + * 使用 execCommand 复制(兼容非安全上下文,如 http + 局域网 IP)。 + */ +function copyWithExecCommand(text: string): boolean { + const textarea = document.createElement('textarea') + textarea.value = text + textarea.setAttribute('readonly', 'readonly') + textarea.style.position = 'fixed' + textarea.style.left = '-9999px' + textarea.style.top = '0' + textarea.style.opacity = '0' + document.body.appendChild(textarea) + textarea.focus() + textarea.select() + textarea.setSelectionRange(0, text.length) + let ok = false + try { + ok = document.execCommand('copy') + } finally { + document.body.removeChild(textarea) + } + return ok +} + +/** + * 复制文本到剪贴板。优先 Clipboard API;不可用时或非安全上下文用 execCommand 兜底。 + */ +export async function copyTextToClipboard(text: string): Promise { + if (typeof window !== 'undefined' && window.isSecureContext && navigator.clipboard?.writeText) { + try { + await navigator.clipboard.writeText(text) + return true + } catch { + // 权限失败等继续走兜底 + } + } + return copyWithExecCommand(text) +} diff --git a/lesingle-creation-frontend/src/views/system/tenants/Index.vue b/lesingle-creation-frontend/src/views/system/tenants/Index.vue index 1f9f8a2..260bee6 100644 --- a/lesingle-creation-frontend/src/views/system/tenants/Index.vue +++ b/lesingle-creation-frontend/src/views/system/tenants/Index.vue @@ -244,6 +244,7 @@ import { } from '@/api/tenants' import { menusApi, type Menu } from '@/api/menus' import { getIconComponent } from '@/utils/menu' +import { copyTextToClipboard } from '@/utils/clipboard' const loading = ref(false) const dataSource = ref([]) @@ -334,14 +335,15 @@ const formatDate = (dateStr?: string) => { return new Date(dateStr).toLocaleDateString('zh-CN') } -// #3 复制登录地址 -const copyLoginUrl = (code: string) => { +// #3 复制登录地址(非 HTTPS / 局域网访问时 navigator.clipboard 不可用,需 execCommand 兜底) +const copyLoginUrl = async (code: string) => { const url = `${window.location.origin}/${code}/login` - navigator.clipboard.writeText(url).then(() => { + const ok = await copyTextToClipboard(url) + if (ok) { message.success('登录地址已复制') - }).catch(() => { - message.info(`登录地址:${url}`) - }) + } else { + message.warning(`复制失败,请手动复制:${url}`) + } } // #2 后端搜索 @@ -705,14 +707,15 @@ const loginUrlForCreated = computed(() => { }) // 复制管理员账号信息 -const copyAdminInfo = () => { +const copyAdminInfo = async () => { if (!createdAdmin.value) return const info = `机构:${lastCreatedName.value}\n账号:${createdAdmin.value.username}\n密码:${createdAdmin.value.password}\n登录地址:${loginUrlForCreated.value}` - navigator.clipboard.writeText(info).then(() => { + const ok = await copyTextToClipboard(info) + if (ok) { message.success('账号信息已复制到剪贴板') - }).catch(() => { - message.info(info) - }) + } else { + message.warning('复制失败,请手动选中并复制弹窗内信息') + } } const handleCancel = () => {