新增上下限模式
This commit is contained in:
@@ -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) => {
|
||||||
|
|||||||
1
src/types/electron.d.ts
vendored
1
src/types/electron.d.ts
vendored
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user