修复并发编辑问题
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { api, jsonBody } from '../api'
|
||||
import Icon from './Icon.vue'
|
||||
|
||||
const props = defineProps({ account: { type: Object, required: true } })
|
||||
const emit = defineEmits(['close', 'saved'])
|
||||
|
||||
// concurrency is only adjustable for custom upstreams — others are system-fixed.
|
||||
const canEditConcurrency = props.account.type === 'custom'
|
||||
|
||||
const weight = ref(Number(props.account.weight) || 0)
|
||||
const concurrency = ref(Number(props.account.concurrency) || 1)
|
||||
const status = ref('')
|
||||
const isError = ref(false)
|
||||
const submitting = ref(false)
|
||||
|
||||
async function submit() {
|
||||
submitting.value = true; status.value = ''; isError.value = false
|
||||
const payload = { weight: Number(weight.value) || 0 }
|
||||
if (canEditConcurrency) payload.concurrency = Math.max(1, Number(concurrency.value) || 1)
|
||||
try {
|
||||
const r = await api(`/tokens/${props.account.pool}/${props.account.id}`, jsonBody('PATCH', payload))
|
||||
if (r.ok) {
|
||||
status.value = '✓ 已保存'; emit('saved', payload)
|
||||
setTimeout(() => emit('close'), 600)
|
||||
} else {
|
||||
status.value = r.data?.detail || '保存失败'; isError.value = true
|
||||
}
|
||||
} catch (e) {
|
||||
status.value = String(e); isError.value = true
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="fixed inset-0 z-50 bg-black/70 backdrop-blur-sm flex items-start justify-center p-4 overflow-y-auto"
|
||||
@click.self="emit('close')">
|
||||
<div class="card !shadow-2xl my-12 w-full max-w-md">
|
||||
<div class="px-5 py-4 border-b border-white/[0.06] flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold">编辑账号</h2>
|
||||
<button @click="emit('close')" class="text-white/40 hover:text-white">
|
||||
<Icon name="close" class="w-5 h-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="p-5 space-y-3">
|
||||
<div>
|
||||
<label class="lbl">账户</label>
|
||||
<input :value="account.email || account.id" disabled class="field font-mono" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="lbl">类型</label>
|
||||
<input :value="account.type" disabled class="field" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="lbl">权重 <span class="text-white/35">(高的优先)</span></label>
|
||||
<input v-model.number="weight" type="number" class="field" placeholder="0" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="lbl">并发数 <span class="text-white/35">{{ canEditConcurrency ? '(单账号)' : '(系统固定,不可调整)' }}</span></label>
|
||||
<input v-if="canEditConcurrency" v-model.number="concurrency" type="number" min="1" class="field" placeholder="1" />
|
||||
<input v-else :value="account.type === 'grok' ? 10 : 1" disabled class="field" />
|
||||
</div>
|
||||
<p v-if="status" class="text-xs" :class="isError ? 'text-rose-400' : 'text-emerald-400'">{{ status }}</p>
|
||||
<div class="flex justify-end gap-2 pt-2">
|
||||
<button @click="emit('close')" class="btn-soft">取消</button>
|
||||
<button @click="submit" :disabled="submitting" class="btn-primary">{{ submitting ? '保存中…' : '保存' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -4,6 +4,7 @@ import { api, jsonBody } from '../api'
|
||||
import { fmtTs, fmtIso, fmtDate, fmtClock } from '../utils/format'
|
||||
import ImportModal from '../components/ImportModal.vue'
|
||||
import UpstreamModal from '../components/UpstreamModal.vue'
|
||||
import AccountEditModal from '../components/AccountEditModal.vue'
|
||||
import Icon from '../components/Icon.vue'
|
||||
|
||||
const rows = ref([])
|
||||
@@ -13,6 +14,15 @@ const showImport = ref(false)
|
||||
const showUpstream = ref(false)
|
||||
const editingUpstream = ref(null)
|
||||
function editUpstream(a) { editingUpstream.value = a; showUpstream.value = true }
|
||||
const editingAccount = ref(null)
|
||||
function editAccount(a) { editingAccount.value = a }
|
||||
// Reflect the saved values in the table without a full reload.
|
||||
function applyEdit(payload) {
|
||||
const row = editingAccount.value
|
||||
if (!row) return
|
||||
if (payload.weight != null) row.weight = payload.weight
|
||||
if (payload.concurrency != null) row.concurrency = payload.concurrency
|
||||
}
|
||||
|
||||
const typeFilter = ref('') // '' | 'openai' | 'adobe' | 'runway' | 'leonardo'
|
||||
const statusFilter = ref('') // '' | 'active' | 'quota' | 'disabled'
|
||||
@@ -101,13 +111,6 @@ const pageNumbers = computed(() => {
|
||||
|
||||
let pendingTimer = null
|
||||
|
||||
// Inline-edit an account field (weight / concurrency) → PATCH /tokens/{pool}/{id}.
|
||||
async function saveField(row, field, value) {
|
||||
const v = Number(value)
|
||||
if (Number.isNaN(v)) return
|
||||
await api(`/tokens/${row.pool}/${row.id}`, jsonBody('PATCH', { [field]: v }))
|
||||
}
|
||||
|
||||
async function loadAccounts() {
|
||||
loading.value = true
|
||||
quotaStatus.value = ''
|
||||
@@ -463,19 +466,14 @@ onMounted(loadAccounts)
|
||||
:class="a.remaining > 0 ? 'text-emerald-300' : 'text-rose-300'">{{ a.remaining }}{{ a.type === 'grok' ? '%' : '' }}</span>
|
||||
<span v-else class="text-white/25" :title="a._quotaError || ''">—</span>
|
||||
</td>
|
||||
<!-- weight (editable, all accounts) -->
|
||||
<td class="px-3 py-3.5 align-middle text-center whitespace-nowrap">
|
||||
<input type="number" :value="a.weight" @change="saveField(a, 'weight', $event.target.value)"
|
||||
class="w-12 bg-white/5 border border-white/10 rounded px-1 py-0.5 text-xs text-center tabular-nums focus:border-indigo-400 outline-none"
|
||||
title="权重(高的优先)" />
|
||||
<!-- weight (edit via modal) -->
|
||||
<td class="px-3 py-3.5 align-middle text-center whitespace-nowrap text-xs tabular-nums text-white/70">
|
||||
{{ a.weight ?? 0 }}
|
||||
</td>
|
||||
<!-- concurrency (editable only for custom upstreams; others = system fixed) -->
|
||||
<td class="px-3 py-3.5 align-middle text-center whitespace-nowrap">
|
||||
<input v-if="a.type === 'custom'" type="number" min="1" :value="a.concurrency || 1"
|
||||
@change="saveField(a, 'concurrency', $event.target.value)"
|
||||
class="w-12 bg-white/5 border border-white/10 rounded px-1 py-0.5 text-xs text-center tabular-nums focus:border-indigo-400 outline-none"
|
||||
title="并发数(仅上游可设)" />
|
||||
<span v-else class="text-white/25 text-xs" title="系统固定">{{ a.type === 'grok' ? 10 : 1 }}</span>
|
||||
<!-- concurrency (custom = configured value; others = system fixed) -->
|
||||
<td class="px-3 py-3.5 align-middle text-center whitespace-nowrap text-xs tabular-nums">
|
||||
<span v-if="a.type === 'custom'" class="text-white/70">{{ a.concurrency || 1 }}</span>
|
||||
<span v-else class="text-white/25" title="系统固定">{{ a.type === 'grok' ? 10 : 1 }}</span>
|
||||
</td>
|
||||
<!-- reset_after -->
|
||||
<td class="px-3 py-3.5 align-middle text-xs whitespace-nowrap">
|
||||
@@ -528,6 +526,9 @@ onMounted(loadAccounts)
|
||||
<!-- actions -->
|
||||
<td class="px-3 py-3.5 align-middle whitespace-nowrap">
|
||||
<div class="flex items-center justify-end gap-2">
|
||||
<button v-if="a.type !== 'custom'" @click="editAccount(a)" class="act" title="编辑">
|
||||
<Icon name="config" class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
<button v-if="a.type === 'custom'" @click="editUpstream(a)" class="act" title="编辑上游">
|
||||
<Icon name="config" class="w-3.5 h-3.5" />
|
||||
</button>
|
||||
@@ -558,6 +559,7 @@ onMounted(loadAccounts)
|
||||
|
||||
<ImportModal v-if="showImport" @close="showImport = false" @imported="loadAccounts" />
|
||||
<UpstreamModal v-if="showUpstream" :account="editingUpstream" @close="showUpstream = false; editingUpstream = null" @imported="loadAccounts" />
|
||||
<AccountEditModal v-if="editingAccount" :account="editingAccount" @saved="applyEdit" @close="editingAccount = null" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -303,7 +303,6 @@ const sourcePill = (s) => ({
|
||||
<div v-if="e.error" class="mt-1 text-[11px] text-rose-300/85 truncate flex items-center gap-1.5 cursor-pointer hover:text-rose-200 transition-colors"
|
||||
:title="e.error + ' — 点击复制'"
|
||||
@click.stop="copyError(e)">
|
||||
<Icon name="copy" class="w-3 h-3 shrink-0" />
|
||||
{{ e.error }}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user