新增上下限模式

This commit is contained in:
2026-04-21 10:03:36 +08:00
parent 8d9aa4718d
commit 39020c3171
3 changed files with 189 additions and 3 deletions

View File

@@ -111,12 +111,23 @@
</div> </div>
</div> </div>
<!-- 接收上限 - 紧凑布局 --> <!-- 上下限模式 -->
<label class="flex items-center gap-2 border-l border-gray-200 pl-4 ml-2 cursor-pointer">
<input type="checkbox" v-model="gateEnabled" class="w-4 h-4" />
<span class="text-gray-700 font-medium whitespace-nowrap">上下限模式</span>
</label>
<!-- 接收上下限 - 紧凑布局 -->
<div class="flex items-center gap-2 border-l border-gray-200 pl-4 ml-2"> <div class="flex items-center gap-2 border-l border-gray-200 pl-4 ml-2">
<span class="text-gray-700 font-medium whitespace-nowrap">接收上限</span> <span class="text-gray-700 font-medium whitespace-nowrap">接收上限</span>
<input type="number" min="0" placeholder="无限制" v-model.number="maxCount" <input type="number" min="0" placeholder="无限制" v-model.number="maxCount"
@change="updateMaxCount(maxCount)" @change="updateMaxCount(maxCount)"
class="w-20 px-2 py-1 border border-gray-300 rounded text-sm focus:border-blue-500 focus:outline-none" /> class="w-20 px-2 py-1 border border-gray-300 rounded text-sm focus:border-blue-500 focus:outline-none" />
<template v-if="gateEnabled">
<span class="text-gray-700 font-medium whitespace-nowrap">主播下限</span>
<input type="number" min="0" placeholder="5" v-model.number="minCount"
class="w-20 px-2 py-1 border border-gray-300 rounded text-sm focus:border-blue-500 focus:outline-none" />
</template>
</div> </div>
</div> </div>
</div> </div>
@@ -255,14 +266,21 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, watch, onMounted } from 'vue' import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
import { isElectron } from '../utils/electronBridge' import { isElectron } from '../utils/electronBridge'
import { getPermissions } from '@/utils/storage' import { getPermissions } from '@/utils/storage'
import { usePythonBridge } from '@/utils/pythonBridge'
const props = defineProps({ const props = defineProps({
visible: { type: Boolean, required: true } visible: { type: Boolean, required: true }
}) })
const emit = defineEmits(['close', 'save']) const emit = defineEmits(['close', 'save'])
const { controlCheckTask } = usePythonBridge()
const HOST_LIST_MIN_COUNT_KEY = 'host_list_dialog_min_count'
const HOST_LIST_GATE_ENABLED_KEY = 'host_list_dialog_gate_enabled'
const HOST_LIST_GATE_STATE_KEY = 'host_list_dialog_gate_state'
const HOST_LIST_MONITOR_INTERVAL_MS = 30000
// Level Options // Level Options
const LEVEL_OPTIONS = [ const LEVEL_OPTIONS = [
@@ -315,9 +333,13 @@ const filters = ref({
minOnlineFans: '', minOnlineFans: '',
maxOnlineFans: '', maxOnlineFans: '',
}) })
const gateEnabled = ref(false)
const minCount = ref(5)
const maxCount = ref() const maxCount = ref()
const checkTaskGateState = ref(null)
const selectedLevels = ref(new Set()) const selectedLevels = ref(new Set())
const showLevelDropdown = ref(false) const showLevelDropdown = ref(false)
let hostListMonitorTimer = null
const resolveRestrictedMaxAnchorCount = (fallbackValue = 9999999) => { const resolveRestrictedMaxAnchorCount = (fallbackValue = 9999999) => {
const permissions = getPermissions() const permissions = getPermissions()
@@ -379,6 +401,7 @@ const parsedIds = computed(() => {
watch(() => props.visible, (newVal) => { watch(() => props.visible, (newVal) => {
if (newVal) { if (newVal) {
document.body.style.overflow = 'hidden' document.body.style.overflow = 'hidden'
loadLocalGateConfig()
loadHosts() loadHosts()
loadConfig() loadConfig()
} else { } else {
@@ -387,11 +410,19 @@ watch(() => props.visible, (newVal) => {
}) })
onMounted(() => { onMounted(() => {
loadLocalGateConfig()
loadConfig()
if (props.visible) { if (props.visible) {
document.body.style.overflow = 'hidden' document.body.style.overflow = 'hidden'
loadHosts() loadHosts()
loadConfig()
} }
startHostListMonitor()
})
onUnmounted(() => {
stopHostListMonitor()
}) })
// 监听过滤器变化,同步到后端配置 // 监听过滤器变化,同步到后端配置
@@ -460,6 +491,117 @@ const loadConfig = async () => {
} }
} }
const startHostListMonitor = () => {
stopHostListMonitor()
if (!isElectron()) return
hostListMonitorTimer = setInterval(async () => {
if (!gateEnabled.value) return
await loadHosts()
}, HOST_LIST_MONITOR_INTERVAL_MS)
}
const stopHostListMonitor = () => {
if (!hostListMonitorTimer) return
clearInterval(hostListMonitorTimer)
hostListMonitorTimer = null
}
const loadLocalGateConfig = () => {
try {
const savedGateEnabled = localStorage.getItem(HOST_LIST_GATE_ENABLED_KEY)
gateEnabled.value = savedGateEnabled === 'true'
const savedMinCount = localStorage.getItem(HOST_LIST_MIN_COUNT_KEY)
if (savedMinCount !== null && savedMinCount !== '') {
const parsedMinCount = Number(savedMinCount)
if (Number.isFinite(parsedMinCount) && parsedMinCount >= 0) {
minCount.value = parsedMinCount
}
}
const savedGateState = localStorage.getItem(HOST_LIST_GATE_STATE_KEY)
if (savedGateState === 'true') {
checkTaskGateState.value = true
} else if (savedGateState === 'false') {
checkTaskGateState.value = false
}
} catch (e) {
console.error('[HostListDialog] 读取本地门控配置失败:', e)
}
}
const persistGateEnabled = () => {
try {
localStorage.setItem(HOST_LIST_GATE_ENABLED_KEY, String(gateEnabled.value))
} catch (e) {
console.error('[HostListDialog] 保存上下限模式失败:', e)
}
}
const persistMinCount = () => {
try {
if (minCount.value === '' || minCount.value === null || minCount.value === undefined) {
localStorage.removeItem(HOST_LIST_MIN_COUNT_KEY)
return
}
localStorage.setItem(HOST_LIST_MIN_COUNT_KEY, String(minCount.value))
} catch (e) {
console.error('[HostListDialog] 保存主播下限失败:', e)
}
}
const persistGateState = (value) => {
try {
if (value === null || value === undefined) {
localStorage.removeItem(HOST_LIST_GATE_STATE_KEY)
return
}
localStorage.setItem(HOST_LIST_GATE_STATE_KEY, String(value))
} catch (e) {
console.error('[HostListDialog] 保存门控状态失败:', e)
}
}
const syncControlCheckTaskGate = async (forceSync = false) => {
if (!isElectron()) return
if (!gateEnabled.value) return
const hostCount = filteredHosts.value.length
const upperLimit = Number(maxCount.value)
const lowerLimit = Number(minCount.value)
let nextState = null
if (Number.isFinite(upperLimit) && upperLimit >= 0 && hostCount > upperLimit) {
nextState = false
} else if (Number.isFinite(lowerLimit) && lowerLimit >= 0 && hostCount < lowerLimit) {
nextState = true
} else if (forceSync && checkTaskGateState.value !== null) {
nextState = checkTaskGateState.value
}
if (nextState === null) return
if (!forceSync && checkTaskGateState.value === nextState) return
try {
const result = await controlCheckTask(nextState, true)
if (result?.success === false) {
console.error('[HostListDialog] controlCheckTask 调用失败:', result.error)
return
}
checkTaskGateState.value = nextState
persistGateState(nextState)
console.log('[HostListDialog] 主播库门控状态已更新:', nextState, '当前数量:', hostCount)
} catch (e) {
console.error('[HostListDialog] controlCheckTask 异常:', e)
}
}
// Helpers // Helpers
const getAllChildLevels = (parentValue) => { const getAllChildLevels = (parentValue) => {
const parent = LEVEL_OPTIONS.find(p => p.value === parentValue) const parent = LEVEL_OPTIONS.find(p => p.value === parentValue)
@@ -548,6 +690,33 @@ const updateMaxCount = async (value) => {
} }
} }
watch(minCount, () => {
persistMinCount()
})
watch(gateEnabled, async (enabled) => {
persistGateEnabled()
if (enabled) {
void syncControlCheckTaskGate(true)
return
}
checkTaskGateState.value = null
persistGateState(null)
if (!isElectron()) return
try {
const result = await controlCheckTask(true, false)
if (result?.success === false) {
console.error('[HostListDialog] 关闭上下限模式时恢复放行失败:', result.error)
}
} catch (e) {
console.error('[HostListDialog] 关闭上下限模式时恢复放行异常:', e)
}
})
const filteredHosts = computed(() => { const filteredHosts = computed(() => {
return hosts.value.filter(h => { return hosts.value.filter(h => {
if (!filters.value.gold && h.invitationType === 2) return false if (!filters.value.gold && h.invitationType === 2) return false
@@ -565,6 +734,13 @@ const filteredHosts = computed(() => {
}) })
}) })
watch(
[() => filteredHosts.value.length, minCount, maxCount, gateEnabled],
() => {
void syncControlCheckTaskGate()
}
)
const selectedCount = computed(() => selected.value.size) const selectedCount = computed(() => selected.value.size)
const toggleSelect = (id) => { const toggleSelect = (id) => {

View File

@@ -205,6 +205,7 @@ export interface ElectronAPI {
visitAnchor: (id: string) => Promise<void> visitAnchor: (id: string) => Promise<void>
exportData: (data: string) => Promise<void> exportData: (data: string) => Promise<void>
controlTask: (data: string) => Promise<{ success: boolean; message?: string; error?: string }> controlTask: (data: string) => Promise<{ success: boolean; message?: string; error?: string }>
controlCheckTask: (payload: { isRunning: boolean; model: boolean }) => Promise<{ success: boolean; message?: string; error?: string }>
getBrotherInfo: () => Promise<string> getBrotherInfo: () => Promise<string>
queryBrotherInfo: (data: string) => Promise<string> queryBrotherInfo: (data: string) => Promise<string>
getAllBrotherInfo: () => Promise<string> getAllBrotherInfo: () => Promise<string>

View File

@@ -106,6 +106,14 @@ export function usePythonBridge() {
await window.electronAPI.tk.controlTask(data); await window.electronAPI.tk.controlTask(data);
}; };
const controlCheckTask = async (isRunning, model) => {
if (!inElectron) return { success: false, error: 'Not in Electron' };
return await window.electronAPI.tk.controlCheckTask({
isRunning: Boolean(isRunning),
model: Boolean(model)
});
};
const getBrotherInfo = async () => { const getBrotherInfo = async () => {
if (!inElectron) return { total: 0, valid: 0 }; if (!inElectron) return { total: 0, valid: 0 };
const res = await window.electronAPI.tk.getBrotherInfo(); const res = await window.electronAPI.tk.getBrotherInfo();
@@ -242,6 +250,7 @@ export function usePythonBridge() {
getTkLoginStatus, getTkLoginStatus,
// New Fan Workbench exports // New Fan Workbench exports
controlTask, controlTask,
controlCheckTask,
getBrotherInfo, getBrotherInfo,
Specifystreaming, Specifystreaming,
storageSetInfos, storageSetInfos,