添加主播功能
This commit is contained in:
@@ -26,6 +26,10 @@
|
|||||||
class="px-3 py-1.5 text-sm bg-red-100 text-red-600 hover:bg-red-200 rounded disabled:opacity-50">
|
class="px-3 py-1.5 text-sm bg-red-100 text-red-600 hover:bg-red-200 rounded disabled:opacity-50">
|
||||||
删除选中
|
删除选中
|
||||||
</button>
|
</button>
|
||||||
|
<button v-if="user.tenantId == 13259" @click="showBatchImport = 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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 筛选 -->
|
<!-- 筛选 -->
|
||||||
@@ -163,6 +167,66 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Teleport>
|
</Teleport>
|
||||||
|
|
||||||
|
<!-- 批量导入弹窗 -->
|
||||||
|
<Teleport to="body">
|
||||||
|
<div v-if="showBatchImport" 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 flex flex-col 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="closeBatchImport" class="text-gray-700 hover:text-gray-900 text-xl">✕</button>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 space-y-4">
|
||||||
|
<div>
|
||||||
|
<label class="block text-sm font-medium text-gray-700 mb-1">
|
||||||
|
主播ID(每行一个)
|
||||||
|
</label>
|
||||||
|
<textarea v-model="batchInput" rows="8" placeholder="123456 789012 345678"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm font-mono focus:border-blue-500 focus:outline-none resize-none"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap items-center gap-4">
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-sm font-medium text-gray-700">国家:</span>
|
||||||
|
<select v-model="batchCountry"
|
||||||
|
class="px-2 py-1 border border-gray-300 rounded text-sm focus:border-blue-500 focus:outline-none">
|
||||||
|
<option v-for="c in COUNTRY_OPTIONS" :key="c" :value="c">{{ c }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-sm font-medium text-gray-700">等级:</span>
|
||||||
|
<select v-model="batchLevel"
|
||||||
|
class="px-2 py-1 border border-gray-300 rounded text-sm focus:border-blue-500 focus:outline-none">
|
||||||
|
<option v-for="l in ALL_LEVELS" :key="l" :value="l">{{ l }}</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<span class="text-sm font-medium text-gray-700">票种:</span>
|
||||||
|
<label class="flex items-center gap-1.5 cursor-pointer">
|
||||||
|
<input type="radio" v-model.number="batchInvitationType" :value="1" class="w-4 h-4" />
|
||||||
|
<span class="text-sm">普票</span>
|
||||||
|
</label>
|
||||||
|
<label class="flex items-center gap-1.5 cursor-pointer">
|
||||||
|
<input type="radio" v-model.number="batchInvitationType" :value="2" class="w-4 h-4" />
|
||||||
|
<span class="text-sm text-yellow-600">金票</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="batchInput.trim()" class="text-sm p-3 bg-gray-50 rounded-lg">
|
||||||
|
<span class="text-green-600">可导入:{{ batchParsed.valid.length }} 条</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="p-4 border-t border-gray-200 flex justify-end gap-3">
|
||||||
|
<button @click="closeBatchImport"
|
||||||
|
class="px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg">取消</button>
|
||||||
|
<button @click="doBatchImport" :disabled="!batchParsed.valid.length || batchImporting"
|
||||||
|
class="px-4 py-2 text-sm bg-green-500 text-white rounded-lg hover:bg-green-600 disabled:opacity-50">
|
||||||
|
{{ batchImporting ? '导入中...' : '导入' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -229,6 +293,22 @@ const maxCount = ref(100)
|
|||||||
const selectedLevels = ref(new Set())
|
const selectedLevels = ref(new Set())
|
||||||
const showLevelDropdown = ref(false)
|
const showLevelDropdown = ref(false)
|
||||||
|
|
||||||
|
// 批量导入
|
||||||
|
const showBatchImport = ref(false)
|
||||||
|
const batchInput = ref('')
|
||||||
|
const batchCountry = ref('美国')
|
||||||
|
const batchLevel = ref('B3')
|
||||||
|
const batchInvitationType = ref(1)
|
||||||
|
const batchImporting = ref(false)
|
||||||
|
|
||||||
|
const COUNTRY_OPTIONS = ['美国', '英国', '加拿大', '澳大利亚', '德国', '法国', '日本', '韩国', '巴西', '印度尼西亚', '墨西哥', '菲律宾', '越南', '泰国', '马来西亚', '沙特阿拉伯', '西班牙', '意大利', '土耳其', '埃及', '尼日利亚', '哥伦比亚', '阿根廷', '智利', '秘鲁', '以色列', '伊拉克', '约旦']
|
||||||
|
const ALL_LEVELS = [
|
||||||
|
'A1', 'A2', 'A3',
|
||||||
|
'B1', 'B2', 'B3', 'B4', 'B5',
|
||||||
|
'C1', 'C2', 'C3', 'C4', 'C5',
|
||||||
|
'D1', 'D2', 'D3', 'D4', 'D5',
|
||||||
|
]
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
watch(() => props.visible, (newVal) => {
|
watch(() => props.visible, (newVal) => {
|
||||||
if (newVal) {
|
if (newVal) {
|
||||||
@@ -239,13 +319,22 @@ watch(() => props.visible, (newVal) => {
|
|||||||
document.body.style.overflow = ''
|
document.body.style.overflow = ''
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
let user = ref()
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (props.visible) {
|
if (props.visible) {
|
||||||
document.body.style.overflow = 'hidden'
|
document.body.style.overflow = 'hidden'
|
||||||
loadHosts()
|
loadHosts()
|
||||||
loadConfig()
|
loadConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load User Data
|
||||||
|
try {
|
||||||
|
const userData = localStorage.getItem('user_data')
|
||||||
|
if (userData) {
|
||||||
|
user.value = JSON.parse(userData)
|
||||||
|
console.log('user.value', user.value)
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听过滤器变化,同步到后端配置
|
// 监听过滤器变化,同步到后端配置
|
||||||
@@ -422,6 +511,46 @@ const deleteSelected = () => {
|
|||||||
|
|
||||||
const onClose = () => emit('close')
|
const onClose = () => emit('close')
|
||||||
|
|
||||||
|
// 批量导入 - 解析ID列表
|
||||||
|
const batchParsed = computed(() => {
|
||||||
|
const lines = batchInput.value.split('\n').map(l => l.trim()).filter(Boolean)
|
||||||
|
const valid = lines.map(id => ({ anchorId: id, country: batchCountry.value, hostsLevel: batchLevel.value }))
|
||||||
|
return { valid }
|
||||||
|
})
|
||||||
|
|
||||||
|
const closeBatchImport = () => {
|
||||||
|
showBatchImport.value = false
|
||||||
|
batchInput.value = ''
|
||||||
|
batchCountry.value = '美国'
|
||||||
|
batchLevel.value = 'B3'
|
||||||
|
batchInvitationType.value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
const doBatchImport = async () => {
|
||||||
|
const { valid } = batchParsed.value
|
||||||
|
if (!valid.length) return
|
||||||
|
if (!isElectron()) return
|
||||||
|
batchImporting.value = true
|
||||||
|
try {
|
||||||
|
const result = await window.electronAPI.addAnchorData({
|
||||||
|
anchors: valid,
|
||||||
|
invitationType: batchInvitationType.value
|
||||||
|
})
|
||||||
|
if (result.success) {
|
||||||
|
alert(`导入完成:新增 ${result.added} 个,跳过 ${result.skipped} 个重复`)
|
||||||
|
closeBatchImport()
|
||||||
|
await loadHosts()
|
||||||
|
} else {
|
||||||
|
alert(`导入失败:${result.error}`)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('批量导入失败:', e)
|
||||||
|
alert('批量导入失败')
|
||||||
|
} finally {
|
||||||
|
batchImporting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
if (isElectron()) {
|
if (isElectron()) {
|
||||||
await window.electronAPI.saveAnchorData(JSON.parse(JSON.stringify(hosts.value)))
|
await window.electronAPI.saveAnchorData(JSON.parse(JSON.stringify(hosts.value)))
|
||||||
|
|||||||
1
src/types/electron.d.ts
vendored
1
src/types/electron.d.ts
vendored
@@ -111,6 +111,7 @@ export interface ElectronAPI {
|
|||||||
loadAIConfig: () => Promise<Record<string, unknown>>
|
loadAIConfig: () => Promise<Record<string, unknown>>
|
||||||
loadAnchorData: () => Promise<unknown[]>
|
loadAnchorData: () => Promise<unknown[]>
|
||||||
saveAnchorData: (data: unknown[]) => Promise<{ success: boolean }>
|
saveAnchorData: (data: unknown[]) => Promise<{ success: boolean }>
|
||||||
|
addAnchorData: (data: { anchors: { anchorId: string; country: string; hostsLevel: string }[]; invitationType: number }) => Promise<{ success: boolean; added?: number; skipped?: number; error?: string }>
|
||||||
saveRunConfig: (config: Record<string, unknown>) => Promise<{ success: boolean }>
|
saveRunConfig: (config: Record<string, unknown>) => Promise<{ success: boolean }>
|
||||||
loadRunConfig: () => Promise<Record<string, unknown> | null>
|
loadRunConfig: () => Promise<Record<string, unknown> | null>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user