Compare commits

...

2 Commits

Author SHA1 Message Date
Sisyphus
366056dcac fix(F006-W18): 申请APIKey Step3表格格式错乱+复制失败修复
1. 复制失败根因: navigator.clipboard API 要求 HTTPS/localhost,
   frp 走 http://direv.cloud:8080 是非安全上下文不可用。
   修法: 加 execCommand(copy) 降级方案 (复用 OpenAppListView copyText 逻辑)

2. 表格格式错乱根因: 5 列固定宽 (160+280+弹性+180+320) 超 640px 弹窗,
   AppSecret 列 input+button 横排挤不下。
   修法: 列宽重新分配 (公司120/AppID180/ConnectCode160/过期100/Secret200),
   AppSecret 单元格改 flex-column 竖排 (input 上 button 下),
   code 文本加 text-overflow ellipsis 防撑宽
2026-07-10 11:15:02 +08:00
Sisyphus
3efa5db533 fix(F006): 删除重发二维码功能 + 详情页ConnectCode字段
connectCode/扫码绑定场景从未落地(平台助手/小程序/PC均未消费),
重发二维码按钮和详情页ConnectCode/过期时间是无用功能, 删除清理.
后端端点/数据库字段保留不动(铁律8 不碰没坏的东西)
2026-07-10 11:01:56 +08:00
2 changed files with 61 additions and 100 deletions

View File

@ -139,11 +139,35 @@ async function submit() {
function onClose() { updateVisible(false); if (result.value) emit("refresh") }
async function copySecret(text: string) {
// 优先用现代 Clipboard API (HTTPS / localhost 才允许)
// frp 走 http://direv.cloud:8080 是非安全上下文, clipboard API 不可用, 需降级
try {
await navigator.clipboard.writeText(text)
ElMessage.success('已复制')
if (navigator.clipboard?.writeText) {
await navigator.clipboard.writeText(text)
ElMessage.success('已复制')
return
}
throw new Error('no clipboard api')
} catch {
ElMessage.error('复制失败,请手动选择文本')
// 降级方案: document.execCommand('copy') - HTTP 下也能用
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')
}
}
}
@ -330,23 +354,25 @@ function notifyContact() {
请立即复制 connectCode 和 appSecret!关弹窗后无法再次查看
</el-alert>
<p>已为 {{ result.length }} 个客户生成 APIKey:</p>
<el-table :data="result" border class="rr-apply-table">
<el-table-column prop="customerName" label="公司名称" width="160" />
<el-table-column prop="appId" label="AppID" width="280">
<el-table :data="result" border class="rr-apply-table rr-result-table" size="small">
<el-table-column prop="customerName" label="公司名称" width="120" show-overflow-tooltip />
<el-table-column prop="appId" label="AppID" min-width="180" show-overflow-tooltip>
<template #default="{ row }">
<code>{{ row.appId }}</code>
<code class="rr-cell-code">{{ row.appId }}</code>
</template>
</el-table-column>
<el-table-column prop="connectCode" label="ConnectCode">
<el-table-column prop="connectCode" label="ConnectCode" min-width="160" show-overflow-tooltip>
<template #default="{ row }">
<code class="rr-code-accent">{{ row.connectCode }}</code>
<code class="rr-cell-code rr-code-accent">{{ row.connectCode }}</code>
</template>
</el-table-column>
<el-table-column prop="expireAt" label="过期时间" width="180" />
<el-table-column label="AppSecret" min-width="320">
<el-table-column prop="expireAt" label="过期时间" width="100" show-overflow-tooltip />
<el-table-column label="AppSecret" min-width="200">
<template #default="{ row }">
<el-input :model-value="row.appSecret" readonly type="password" size="small" show-password style="width: 220px; margin-right: 8px;" />
<el-button size="small" type="primary" @click="copySecret(row.appSecret)">复制</el-button>
<div class="rr-secret-cell">
<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>
</div>
</template>
</el-table-column>
</el-table>
@ -496,4 +522,25 @@ function notifyContact() {
:deep(.rr-apply-dialog .rr-apply-table th.el-table-column--selection .cell) {
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>

View File

@ -6,10 +6,9 @@
* - 操作列(根据状态显示):
* * 详情(任意状态,弹窗)
* * 重置密钥(status=0 或 1)
* * 重发二维码(status=0 或 1, W4.4 后端补 resend-qr)
* * 撤销(status=0 或 1)
* * 删除(status=2)
* - 5 个弹窗:详情/撤销确认/删除确认/重置密钥/重发连接码
* - 4 个弹窗:详情/撤销确认/删除确认/重置密钥
* - 申请 APIKey 走 ApplyApiKeyDialog 2 步向导
*
* 后端端点:
@ -40,8 +39,6 @@ interface OpenApp {
totalCallCount: number | string
lastUsedAt?: string | null
createDate: string
connectCode?: string
connectCodeExpire?: string | number
activatedAt?: string | null
lastUsedIp?: string | null
ipWhitelist?: string | null
@ -91,11 +88,6 @@ const resetSecretVisible = ref(false)
const resetSecretTarget = ref<OpenApp | null>(null)
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) {
if (!ts) return '--'
const n = typeof ts === 'string' ? Date.parse(ts) : ts
@ -127,8 +119,6 @@ async function load() {
totalCallCount: it.totalCallCount ?? 0,
lastUsedAt: it.lastUsedAt,
createDate: it.createDate,
connectCode: it.connectCode,
connectCodeExpire: it.connectCodeExpire,
activatedAt: it.activatedAt,
lastUsedIp: it.lastUsedIp,
ipWhitelist: it.ipWhitelist,
@ -258,29 +248,6 @@ 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 = '已复制') {
// 优先用现代 Clipboard API (HTTPS / localhost 才允许)
try {
@ -378,7 +345,6 @@ onMounted(() => { load(); loadCustomers() })
:row="row"
:actions="[
{ 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: '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 }
@ -402,10 +368,6 @@ onMounted(() => { load(); loadCustomers() })
<el-descriptions-item label="状态">
<span :class="['rr-badge', `rr-badge-${currentRow.status}`]">{{ STATUS_LABEL[currentRow.status] || currentRow.status }}</span>
</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="IP 白名单">{{ currentRow.ipWhitelist || '—' }}</el-descriptions-item>
<el-descriptions-item label="激活时间">{{ currentRow.activatedAt || '—' }}</el-descriptions-item>
@ -483,54 +445,6 @@ onMounted(() => { load(); loadCustomers() })
</template>
</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 步) -->
<ApplyApiKeyDialog v-model="applyDialogVisible" @refresh="onApplySubmitted" />
</div>