pk优化版

This commit is contained in:
2026-02-26 13:15:19 +08:00
parent d4c0dcf6b1
commit 5c1911314f
22 changed files with 742 additions and 386 deletions

View File

@@ -16,6 +16,10 @@
<!-- 工具栏 -->
<div class="p-4 border-b border-gray-100 space-y-3">
<div class="flex flex-wrap gap-2">
<button @click="showAddDialog = true"
class="px-3 py-1.5 text-sm bg-green-100 text-green-700 hover:bg-green-200 rounded border border-green-300">
+ 添加主播
</button>
<button @click="selectAll"
class="px-3 py-1.5 text-sm bg-blue-100 text-blue-700 hover:bg-blue-200 rounded border border-blue-300">全选</button>
<button @click="selectNone"
@@ -164,6 +168,84 @@
</div>
</div>
</div>
<!-- 添加主播弹窗 -->
<div v-if="showAddDialog" class="fixed inset-0 bg-black/50 flex items-center justify-center"
style="z-index: 10000">
<div class="bg-white rounded-xl shadow-2xl w-full max-w-lg mx-4">
<div class="p-4 border-b border-gray-200 flex items-center justify-between">
<h3 class="text-lg font-semibold text-gray-600">添加主播</h3>
<button @click="closeAddDialog" class="text-gray-700 hover:text-gray-700 text-xl"></button>
</div>
<div class="p-4 space-y-4">
<!-- 主播ID输入 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">主播ID每行一个支持批量粘贴</label>
<textarea v-model="addForm.idsText" rows="6" placeholder="粘贴主播ID每行一个&#10;例如:&#10;anchor_001&#10;anchor_002&#10;anchor_003"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-blue-500 focus:outline-none resize-none font-mono"></textarea>
<div class="text-xs text-gray-400 mt-1">
已输入 {{ parsedIds.length }} 个ID
</div>
</div>
<!-- 邀请类型 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">邀请类型</label>
<div class="flex gap-3">
<button @click="addForm.invitationType = '1'"
:class="['px-4 py-2 rounded-lg text-sm border transition-all', addForm.invitationType === '1' ? 'bg-blue-500 text-white border-blue-500' : 'bg-white text-gray-600 border-gray-300 hover:border-blue-300']">
普票
</button>
<button @click="addForm.invitationType = '2'"
:class="['px-4 py-2 rounded-lg text-sm border transition-all', addForm.invitationType === '2' ? 'bg-yellow-500 text-white border-yellow-500' : 'bg-white text-gray-600 border-gray-300 hover:border-yellow-300']">
金票
</button>
</div>
</div>
<!-- 国家选择 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">国家</label>
<select v-model="addForm.country"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-blue-500 focus:outline-none">
<option v-for="c in COUNTRY_OPTIONS" :key="c.value" :value="c.value">{{ c.label }}</option>
</select>
</div>
<!-- 等级选择 -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">主播等级</label>
<select v-model="addForm.hostsLevel"
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:border-blue-500 focus:outline-none">
<optgroup v-for="parent in LEVEL_OPTIONS" :key="parent.value" :label="parent.label + '级'">
<option v-for="child in parent.children" :key="child.value" :value="child.value">
{{ child.label }}
</option>
</optgroup>
</select>
</div>
</div>
<!-- 底部按钮 -->
<div class="p-4 border-t border-gray-200 flex justify-between items-center">
<span v-if="addStatus" :class="['text-sm', addStatus.type === 'success' ? 'text-green-600' : 'text-red-600']">
{{ addStatus.message }}
</span>
<span v-else></span>
<div class="flex gap-3">
<button @click="closeAddDialog"
class="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg">
取消
</button>
<button @click="handleAddHosts" :disabled="addLoading || parsedIds.length === 0"
class="px-4 py-2 text-sm bg-green-500 text-white rounded-lg hover:bg-green-600 disabled:opacity-50 disabled:cursor-not-allowed">
{{ addLoading ? '导入中...' : `导入 ${parsedIds.length} 个主播` }}
</button>
</div>
</div>
</div>
</div>
</Teleport>
</template>
@@ -231,6 +313,57 @@ const maxCount = ref(100)
const selectedLevels = ref(new Set())
const showLevelDropdown = ref(false)
// 添加主播弹窗状态
const showAddDialog = ref(false)
const addLoading = ref(false)
const addStatus = ref(null)
const addForm = ref({
idsText: '',
invitationType: '1',
country: '美国',
hostsLevel: 'A1',
})
// 国家选项
const COUNTRY_OPTIONS = [
{ label: '美国', value: '美国', eng: 'United States' },
{ label: '英国', value: '英国', eng: 'United Kingdom' },
{ label: '加拿大', value: '加拿大', eng: 'Canada' },
{ label: '澳大利亚', value: '澳大利亚', eng: 'Australia' },
{ label: '德国', value: '德国', eng: 'Germany' },
{ label: '法国', value: '法国', eng: 'France' },
{ label: '日本', value: '日本', eng: 'Japan' },
{ label: '韩国', value: '韩国', eng: 'South Korea' },
{ label: '巴西', value: '巴西', eng: 'Brazil' },
{ label: '印度尼西亚', value: '印度尼西亚', eng: 'Indonesia' },
{ label: '墨西哥', value: '墨西哥', eng: 'Mexico' },
{ label: '菲律宾', value: '菲律宾', eng: 'Philippines' },
{ label: '越南', value: '越南', eng: 'Vietnam' },
{ label: '泰国', value: '泰国', eng: 'Thailand' },
{ label: '马来西亚', value: '马来西亚', eng: 'Malaysia' },
{ label: '沙特阿拉伯', value: '沙特阿拉伯', eng: 'Saudi Arabia' },
{ label: '西班牙', value: '西班牙', eng: 'Spain' },
{ label: '意大利', value: '意大利', eng: 'Italy' },
{ label: '土耳其', value: '土耳其', eng: 'Turkey' },
{ label: '埃及', value: '埃及', eng: 'Egypt' },
{ label: '尼日利亚', value: '尼日利亚', eng: 'Nigeria' },
{ label: '哥伦比亚', value: '哥伦比亚', eng: 'Colombia' },
{ label: '阿根廷', value: '阿根廷', eng: 'Argentina' },
{ label: '智利', value: '智利', eng: 'Chile' },
{ label: '秘鲁', value: '秘鲁', eng: 'Peru' },
{ label: '以色列', value: '以色列', eng: 'Israel' },
{ label: '伊拉克', value: '伊拉克', eng: 'Iraq' },
{ label: '约旦', value: '约旦', eng: 'Jordan' },
]
// 解析输入的主播ID列表
const parsedIds = computed(() => {
return addForm.value.idsText
.split('\n')
.map(line => line.trim())
.filter(id => id.length > 0)
})
// Lifecycle
watch(() => props.visible, (newVal) => {
if (newVal) {
@@ -273,6 +406,7 @@ const loadHosts = async () => {
if (!isElectron()) return
try {
const data = await window.electronAPI.loadAnchorData()
console.log('加载主播数据:', data)
hosts.value = data
selected.value = new Set()
} catch (e) {
@@ -413,22 +547,89 @@ const invertSelect = () => {
selected.value = next
}
const deleteSelected = () => {
const deleteSelected = async () => {
if (!selected.value.size) return
if (!confirm(`确认删除选中的 ${selected.value.size} 项吗?`)) return
const remaining = hosts.value.filter(h => !selected.value.has(h.anchorId))
hosts.value = remaining
selected.value = new Set()
if (isElectron()) {
try {
const ids = Array.from(selected.value)
const result = await window.electronAPI.deleteAnchorData(ids)
if (result.success) {
console.log('[HostListDialog] 删除成功,重新加载数据')
await loadHosts()
} else {
console.error('[HostListDialog] 删除失败:', result.error)
// fallback: 前端本地删除
hosts.value = hosts.value.filter(h => !selected.value.has(h.anchorId))
selected.value = new Set()
}
} catch (e) {
console.error('[HostListDialog] 删除异常:', e)
hosts.value = hosts.value.filter(h => !selected.value.has(h.anchorId))
selected.value = new Set()
}
} else {
hosts.value = hosts.value.filter(h => !selected.value.has(h.anchorId))
selected.value = new Set()
}
}
const onClose = () => emit('close')
const handleSave = async () => {
if (isElectron()) {
await window.electronAPI.saveAnchorData(JSON.parse(JSON.stringify(hosts.value)))
}
emit('save', hosts.value)
onClose()
}
// 关闭添加弹窗
const closeAddDialog = () => {
showAddDialog.value = false
addForm.value = { idsText: '', invitationType: '1', country: '美国', hostsLevel: 'A1' }
addStatus.value = null
addLoading.value = false
}
// 批量添加主播
const handleAddHosts = async () => {
const ids = parsedIds.value
if (ids.length === 0) return
addLoading.value = true
addStatus.value = null
// 查找国家英文名
const countryObj = COUNTRY_OPTIONS.find(c => c.value === addForm.value.country)
// 构造批量记录
const records = ids.map(hostsId => ({
hostsId,
invitationType: addForm.value.invitationType,
country: addForm.value.country || null,
countryEng: countryObj?.eng || null,
hostsLevel: addForm.value.hostsLevel || null,
createTime: Date.now(),
}))
if (isElectron()) {
try {
const result = await window.electronAPI.addAnchorData(records)
if (result.success) {
addStatus.value = { type: 'success', message: `成功导入 ${ids.length} 个主播` }
// 重新加载列表
await loadHosts()
// 延迟关闭弹窗
setTimeout(() => closeAddDialog(), 1000)
} else {
addStatus.value = { type: 'error', message: result.error || '导入失败' }
}
} catch (e) {
console.error('[HostListDialog] 添加主播失败:', e)
addStatus.value = { type: 'error', message: '导入异常: ' + String(e) }
}
} else {
addStatus.value = { type: 'error', message: '非 Electron 环境,无法添加' }
}
addLoading.value = false
}
</script>