Compare commits
No commits in common. "366056dcac42a3d2975f093069cbe6955f0edfd6" and "8af8ed143d6e2eed12c73e8c92568a301a081237" have entirely different histories.
366056dcac
...
8af8ed143d
@ -139,35 +139,11 @@ async function submit() {
|
|||||||
function onClose() { updateVisible(false); if (result.value) emit("refresh") }
|
function onClose() { updateVisible(false); if (result.value) emit("refresh") }
|
||||||
|
|
||||||
async function copySecret(text: string) {
|
async function copySecret(text: string) {
|
||||||
// 优先用现代 Clipboard API (HTTPS / localhost 才允许)
|
|
||||||
// frp 走 http://direv.cloud:8080 是非安全上下文, clipboard API 不可用, 需降级
|
|
||||||
try {
|
try {
|
||||||
if (navigator.clipboard?.writeText) {
|
await navigator.clipboard.writeText(text)
|
||||||
await navigator.clipboard.writeText(text)
|
ElMessage.success('已复制')
|
||||||
ElMessage.success('已复制')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
throw new Error('no clipboard api')
|
|
||||||
} catch {
|
} catch {
|
||||||
// 降级方案: document.execCommand('copy') - HTTP 下也能用
|
ElMessage.error('复制失败,请手动选择文本')
|
||||||
try {
|
|
||||||
const ta = document.createElement('textarea')
|
|
||||||
ta.value = text
|
|
||||||
ta.style.position = 'fixed'
|
|
||||||
ta.style.opacity = '0'
|
|
||||||
ta.style.pointerEvents = 'none'
|
|
||||||
document.body.appendChild(ta)
|
|
||||||
ta.select()
|
|
||||||
const ok = document.execCommand('copy')
|
|
||||||
document.body.removeChild(ta)
|
|
||||||
if (ok) {
|
|
||||||
ElMessage.success('已复制')
|
|
||||||
} else {
|
|
||||||
ElMessage.warning('浏览器阻止自动复制,请手动双击输入框选中后 Cmd/Ctrl+C')
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
ElMessage.warning('浏览器阻止自动复制,请手动双击输入框选中后 Cmd/Ctrl+C')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -354,25 +330,23 @@ function notifyContact() {
|
|||||||
请立即复制 connectCode 和 appSecret!关弹窗后无法再次查看
|
请立即复制 connectCode 和 appSecret!关弹窗后无法再次查看
|
||||||
</el-alert>
|
</el-alert>
|
||||||
<p>已为 {{ result.length }} 个客户生成 APIKey:</p>
|
<p>已为 {{ result.length }} 个客户生成 APIKey:</p>
|
||||||
<el-table :data="result" border class="rr-apply-table rr-result-table" size="small">
|
<el-table :data="result" border class="rr-apply-table">
|
||||||
<el-table-column prop="customerName" label="公司名称" width="120" show-overflow-tooltip />
|
<el-table-column prop="customerName" label="公司名称" width="160" />
|
||||||
<el-table-column prop="appId" label="AppID" min-width="180" show-overflow-tooltip>
|
<el-table-column prop="appId" label="AppID" width="280">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<code class="rr-cell-code">{{ row.appId }}</code>
|
<code>{{ row.appId }}</code>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="connectCode" label="ConnectCode" min-width="160" show-overflow-tooltip>
|
<el-table-column prop="connectCode" label="ConnectCode">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<code class="rr-cell-code rr-code-accent">{{ row.connectCode }}</code>
|
<code class="rr-code-accent">{{ row.connectCode }}</code>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="expireAt" label="过期时间" width="100" show-overflow-tooltip />
|
<el-table-column prop="expireAt" label="过期时间" width="180" />
|
||||||
<el-table-column label="AppSecret" min-width="200">
|
<el-table-column label="AppSecret" min-width="320">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="rr-secret-cell">
|
<el-input :model-value="row.appSecret" readonly type="password" size="small" show-password style="width: 220px; margin-right: 8px;" />
|
||||||
<el-input :model-value="row.appSecret" readonly type="password" size="small" show-password />
|
<el-button size="small" type="primary" @click="copySecret(row.appSecret)">复制</el-button>
|
||||||
<el-button size="small" type="primary" @click="copySecret(row.appSecret)">复制</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@ -522,25 +496,4 @@ function notifyContact() {
|
|||||||
:deep(.rr-apply-dialog .rr-apply-table th.el-table-column--selection .cell) {
|
:deep(.rr-apply-dialog .rr-apply-table th.el-table-column--selection .cell) {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Step3 结果表格: AppSecret 单元格紧凑布局 */
|
|
||||||
.rr-secret-cell {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
padding: 2px 0;
|
|
||||||
}
|
|
||||||
.rr-secret-cell .el-input {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
/* code 文本省略, 不撑列宽 */
|
|
||||||
.rr-cell-code {
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
font-size: 12px;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -6,9 +6,10 @@
|
|||||||
* - 操作列(根据状态显示):
|
* - 操作列(根据状态显示):
|
||||||
* * 详情(任意状态,弹窗)
|
* * 详情(任意状态,弹窗)
|
||||||
* * 重置密钥(status=0 或 1)
|
* * 重置密钥(status=0 或 1)
|
||||||
|
* * 重发二维码(status=0 或 1, W4.4 后端补 resend-qr)
|
||||||
* * 撤销(status=0 或 1)
|
* * 撤销(status=0 或 1)
|
||||||
* * 删除(status=2)
|
* * 删除(status=2)
|
||||||
* - 4 个弹窗:详情/撤销确认/删除确认/重置密钥
|
* - 5 个弹窗:详情/撤销确认/删除确认/重置密钥/重发连接码
|
||||||
* - 申请 APIKey 走 ApplyApiKeyDialog 2 步向导
|
* - 申请 APIKey 走 ApplyApiKeyDialog 2 步向导
|
||||||
*
|
*
|
||||||
* 后端端点:
|
* 后端端点:
|
||||||
@ -39,6 +40,8 @@ interface OpenApp {
|
|||||||
totalCallCount: number | string
|
totalCallCount: number | string
|
||||||
lastUsedAt?: string | null
|
lastUsedAt?: string | null
|
||||||
createDate: string
|
createDate: string
|
||||||
|
connectCode?: string
|
||||||
|
connectCodeExpire?: string | number
|
||||||
activatedAt?: string | null
|
activatedAt?: string | null
|
||||||
lastUsedIp?: string | null
|
lastUsedIp?: string | null
|
||||||
ipWhitelist?: string | null
|
ipWhitelist?: string | null
|
||||||
@ -88,6 +91,11 @@ const resetSecretVisible = ref(false)
|
|||||||
const resetSecretTarget = ref<OpenApp | null>(null)
|
const resetSecretTarget = ref<OpenApp | null>(null)
|
||||||
const newSecret = ref('')
|
const newSecret = ref('')
|
||||||
|
|
||||||
|
const resendQrVisible = ref(false)
|
||||||
|
const resendQrTarget = ref<OpenApp | null>(null)
|
||||||
|
const newConnectCode = ref('')
|
||||||
|
const newConnectCodeExpire = ref('')
|
||||||
|
|
||||||
function fmtDate(ts?: string | number | null) {
|
function fmtDate(ts?: string | number | null) {
|
||||||
if (!ts) return '--'
|
if (!ts) return '--'
|
||||||
const n = typeof ts === 'string' ? Date.parse(ts) : ts
|
const n = typeof ts === 'string' ? Date.parse(ts) : ts
|
||||||
@ -119,6 +127,8 @@ async function load() {
|
|||||||
totalCallCount: it.totalCallCount ?? 0,
|
totalCallCount: it.totalCallCount ?? 0,
|
||||||
lastUsedAt: it.lastUsedAt,
|
lastUsedAt: it.lastUsedAt,
|
||||||
createDate: it.createDate,
|
createDate: it.createDate,
|
||||||
|
connectCode: it.connectCode,
|
||||||
|
connectCodeExpire: it.connectCodeExpire,
|
||||||
activatedAt: it.activatedAt,
|
activatedAt: it.activatedAt,
|
||||||
lastUsedIp: it.lastUsedIp,
|
lastUsedIp: it.lastUsedIp,
|
||||||
ipWhitelist: it.ipWhitelist,
|
ipWhitelist: it.ipWhitelist,
|
||||||
@ -248,6 +258,29 @@ async function resetSecretConfirm() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function resendQrHandle(row: OpenApp) {
|
||||||
|
resendQrTarget.value = row
|
||||||
|
newConnectCode.value = ''
|
||||||
|
newConnectCodeExpire.value = ''
|
||||||
|
resendQrVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resendQrConfirm() {
|
||||||
|
if (!resendQrTarget.value) return
|
||||||
|
try {
|
||||||
|
const data = await httpPost<{ connectCode: string; connectCodeExpire: string | number }>(
|
||||||
|
`/sys/openapi/app/resend-qr/${resendQrTarget.value.id}`
|
||||||
|
)
|
||||||
|
newConnectCode.value = data?.connectCode || ''
|
||||||
|
newConnectCodeExpire.value = data?.connectCodeExpire
|
||||||
|
? String(data.connectCodeExpire)
|
||||||
|
: ''
|
||||||
|
ElMessage.success('连接码已重发,旧码已作废')
|
||||||
|
} catch (e: any) {
|
||||||
|
ElMessage.error(e?.msg || e?.message || '重发失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function copyText(text: string, label = '已复制') {
|
async function copyText(text: string, label = '已复制') {
|
||||||
// 优先用现代 Clipboard API (HTTPS / localhost 才允许)
|
// 优先用现代 Clipboard API (HTTPS / localhost 才允许)
|
||||||
try {
|
try {
|
||||||
@ -345,6 +378,7 @@ onMounted(() => { load(); loadCustomers() })
|
|||||||
:row="row"
|
:row="row"
|
||||||
:actions="[
|
:actions="[
|
||||||
{ label: '详情', type: 'primary', onClick: detailHandle },
|
{ label: '详情', type: 'primary', onClick: detailHandle },
|
||||||
|
{ label: '重发二维码', type: 'primary', when: (r: any) => r.status === 0 || r.status === 1, onClick: resendQrHandle },
|
||||||
{ label: '重置密钥', type: 'warning', when: (r: any) => r.status === 0 || r.status === 1, onClick: resetSecretHandle },
|
{ label: '重置密钥', type: 'warning', when: (r: any) => r.status === 0 || r.status === 1, onClick: resetSecretHandle },
|
||||||
{ label: '撤销', type: 'danger', divided: true, when: (r: any) => r.status === 0 || r.status === 1, onClick: revokeHandle },
|
{ label: '撤销', type: 'danger', divided: true, when: (r: any) => r.status === 0 || r.status === 1, onClick: revokeHandle },
|
||||||
{ label: '删除', type: 'danger', divided: true, when: (r: any) => r.status === 2, onClick: deleteHandle }
|
{ label: '删除', type: 'danger', divided: true, when: (r: any) => r.status === 2, onClick: deleteHandle }
|
||||||
@ -368,6 +402,10 @@ onMounted(() => { load(); loadCustomers() })
|
|||||||
<el-descriptions-item label="状态">
|
<el-descriptions-item label="状态">
|
||||||
<span :class="['rr-badge', `rr-badge-${currentRow.status}`]">{{ STATUS_LABEL[currentRow.status] || currentRow.status }}</span>
|
<span :class="['rr-badge', `rr-badge-${currentRow.status}`]">{{ STATUS_LABEL[currentRow.status] || currentRow.status }}</span>
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="ConnectCode">
|
||||||
|
<code class="rr-code-accent">{{ currentRow.connectCode || '—' }}</code>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="ConnectCode 过期">{{ fmtDate(currentRow.connectCodeExpire) }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="权限范围">{{ currentRow.scopes }}</el-descriptions-item>
|
<el-descriptions-item label="权限范围">{{ currentRow.scopes }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="IP 白名单">{{ currentRow.ipWhitelist || '—' }}</el-descriptions-item>
|
<el-descriptions-item label="IP 白名单">{{ currentRow.ipWhitelist || '—' }}</el-descriptions-item>
|
||||||
<el-descriptions-item label="激活时间">{{ currentRow.activatedAt || '—' }}</el-descriptions-item>
|
<el-descriptions-item label="激活时间">{{ currentRow.activatedAt || '—' }}</el-descriptions-item>
|
||||||
@ -445,6 +483,54 @@ onMounted(() => { load(); loadCustomers() })
|
|||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<!-- 重发连接码(二维码)弹窗 -->
|
||||||
|
<el-dialog
|
||||||
|
v-model="resendQrVisible"
|
||||||
|
title="重发连接码"
|
||||||
|
width="560"
|
||||||
|
:style="{ borderRadius: '12px', overflow: 'hidden' }"
|
||||||
|
align-center
|
||||||
|
destroy-on-close
|
||||||
|
>
|
||||||
|
<el-alert v-if="!newConnectCode" type="warning" :closable="false" style="margin-bottom: 16px">
|
||||||
|
<p>重发后会生成新的 connectCode,旧码立即作废</p>
|
||||||
|
<p>新码有效期 2 小时,请尽快发给客户扫码激活</p>
|
||||||
|
</el-alert>
|
||||||
|
<el-alert v-if="newConnectCode" type="error" :closable="false" style="margin-bottom: 16px">
|
||||||
|
<p>新连接码仅显示一次!请立即复制并通过安全渠道发给客户</p>
|
||||||
|
<p>关闭弹窗后无法再次查看(只能再次重发)</p>
|
||||||
|
</el-alert>
|
||||||
|
<p v-if="resendQrTarget && !newConnectCode">
|
||||||
|
app: <code class="rr-code-accent">{{ resendQrTarget.appId }}</code>
|
||||||
|
({{ resendQrTarget.appName }})
|
||||||
|
</p>
|
||||||
|
<div v-if="newConnectCode" style="margin-top: 12px;">
|
||||||
|
<p style="margin-bottom: 6px; font-size: 13px; color: var(--el-text-color-regular);">
|
||||||
|
新的 ConnectCode: <span style="color: var(--el-text-color-secondary); font-size: 12px;">(明文,可直接选中复制)</span>
|
||||||
|
</p>
|
||||||
|
<el-input
|
||||||
|
v-model="newConnectCode"
|
||||||
|
readonly
|
||||||
|
type="text"
|
||||||
|
size="default"
|
||||||
|
style="font-family: ui-monospace, monospace;"
|
||||||
|
@focus="onSecretInputFocus"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button type="primary" @click="copyText(newConnectCode, '连接码已复制')">复制</el-button>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<p v-if="newConnectCodeExpire" style="margin-top: 8px; font-size: 12px; color: var(--el-text-color-secondary);">
|
||||||
|
过期时间: {{ fmtDate(newConnectCodeExpire) }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button v-if="!newConnectCode" @click="resendQrVisible = false">取消</el-button>
|
||||||
|
<el-button v-if="!newConnectCode" type="primary" @click="resendQrConfirm">确认重发</el-button>
|
||||||
|
<el-button v-if="newConnectCode" type="primary" @click="resendQrVisible = false">我已复制,关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 申请 APIKey 向导(2 步) -->
|
<!-- 申请 APIKey 向导(2 步) -->
|
||||||
<ApplyApiKeyDialog v-model="applyDialogVisible" @refresh="onApplySubmitted" />
|
<ApplyApiKeyDialog v-model="applyDialogVisible" @refresh="onApplySubmitted" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user