2.4.7 优化过滤 新增重置任务

This commit is contained in:
2026-04-01 13:27:27 +08:00
parent 466a853905
commit b2f9dbf2a2
11 changed files with 160 additions and 60 deletions

View File

@@ -125,6 +125,8 @@ onMounted(() => {
window.addEventListener('beforeunload', handleBeforeUnload) window.addEventListener('beforeunload', handleBeforeUnload)
window.addEventListener('storage', handleStorageChange) window.addEventListener('storage', handleStorageChange)
// 监听账号组配置更新事件
window.addEventListener('config-updated', handleConfigUpdate)
loadConfig() loadConfig()
@@ -135,6 +137,7 @@ onMounted(() => {
onUnmounted(() => { onUnmounted(() => {
window.removeEventListener('beforeunload', handleBeforeUnload) window.removeEventListener('beforeunload', handleBeforeUnload)
window.removeEventListener('storage', handleStorageChange) window.removeEventListener('storage', handleStorageChange)
window.removeEventListener('config-updated', handleConfigUpdate)
stopHealthCheck() stopHealthCheck()
}) })
@@ -149,6 +152,12 @@ const handleStorageChange = (e) => {
} }
} }
// 处理配置更新事件
const handleConfigUpdate = () => {
console.log('[App] 收到配置更新事件,重新加载配置')
loadConfig()
}
let healthCheckInterval = null let healthCheckInterval = null
const startHealthCheck = () => { const startHealthCheck = () => {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -68,7 +68,7 @@
<!-- 下拉菜单 --> <!-- 下拉菜单 -->
<div v-if="showLevelDropdown" <div v-if="showLevelDropdown"
class="absolute top-full left-4 mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 p-3 w-72"> class="absolute top-full left-4 mt-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 p-3 w-72">
<div class="text-xs text-gray-500 mb-2">选择接收的主播等级不选则接收全部</div> <div class="text-xs text-gray-500 mb-2">选择接收的主播等级选则过滤掉</div>
<div class="space-y-2 max-h-60 overflow-auto"> <div class="space-y-2 max-h-60 overflow-auto">
<div v-for="parent in LEVEL_OPTIONS" :key="parent.value" <div v-for="parent in LEVEL_OPTIONS" :key="parent.value"
class="border border-gray-100 rounded p-2"> class="border border-gray-100 rounded p-2">
@@ -93,10 +93,16 @@
</div> </div>
</div> </div>
<div class="mt-2 pt-2 border-t border-gray-100 flex justify-between"> <div class="mt-2 pt-2 border-t border-gray-100 flex justify-between">
<button @click="updateLevelFilter(new Set())" <div class="flex gap-2">
<button @click="selectAllLevels()"
class="text-xs text-gray-500 hover:text-gray-700"> class="text-xs text-gray-500 hover:text-gray-700">
清空选择 全选
</button> </button>
<button @click="selectNoneLevels()"
class="text-xs text-gray-500 hover:text-gray-700">
全不选
</button>
</div>
<button @click="showLevelDropdown = false" <button @click="showLevelDropdown = false"
class="text-xs text-blue-600 hover:text-blue-700"> class="text-xs text-blue-600 hover:text-blue-700">
完成 完成
@@ -308,7 +314,7 @@ const filters = ref({
minOnlineFans: '', minOnlineFans: '',
maxOnlineFans: '', maxOnlineFans: '',
}) })
const maxCount = ref(100) const maxCount = ref()
const selectedLevels = ref(new Set()) const selectedLevels = ref(new Set())
const showLevelDropdown = ref(false) const showLevelDropdown = ref(false)
@@ -418,10 +424,15 @@ const loadConfig = async () => {
try { try {
const config = await window.electronAPI.getAutomationConfig() const config = await window.electronAPI.getAutomationConfig()
if (config?.filters?.maxAnchorCount !== undefined) { if (config?.filters?.maxAnchorCount !== undefined) {
maxCount.value = config.filters.maxAnchorCount // 如果后端返回 9999999前端显示为空
maxCount.value = config.filters.maxAnchorCount === 9999999 ? '' : config.filters.maxAnchorCount
} }
if (config?.filters?.hostsLevelList) { if (config?.filters?.hostsLevelList) {
selectedLevels.value = new Set(config.filters.hostsLevelList) // 计算反向等级列表:后端存储的是要过滤的等级,前端需要的是要显示的等级
const allLevels = LEVEL_OPTIONS.flatMap(parent => parent.children.map(child => child.value))
const excludedLevels = config.filters.hostsLevelList
const includedLevels = allLevels.filter(level => !excludedLevels.includes(level))
selectedLevels.value = new Set(includedLevels)
} }
// 加载进阶票/普票过滤配置 // 加载进阶票/普票过滤配置
if (config?.filters?.gold !== undefined) { if (config?.filters?.gold !== undefined) {
@@ -461,10 +472,21 @@ const updateLevelFilter = async (levels) => {
selectedLevels.value = levels selectedLevels.value = levels
if (!isElectron()) return if (!isElectron()) return
try { try {
// 计算反向等级列表:前端勾选的是要显示的,后端需要的是要过滤的(不显示的)
const allLevels = LEVEL_OPTIONS.flatMap(parent => parent.children.map(child => child.value))
const includedLevels = Array.from(levels)
// 当全不选时,传给后端的是空数组(不过滤任何等级)
// 当有选择时,传给后端的是未选择的等级(过滤这些等级)
let excludedLevels = []
if (includedLevels.length > 0) {
excludedLevels = allLevels.filter(level => !includedLevels.includes(level))
}
await window.electronAPI.updateAutomationConfig({ await window.electronAPI.updateAutomationConfig({
filters: { hostsLevelList: Array.from(levels) } filters: { hostsLevelList: excludedLevels }
}) })
console.log('[HostListDialog] 等级过滤已更新:', Array.from(levels)) console.log('[HostListDialog] 等级过滤已更新:', excludedLevels)
} catch (e) { } catch (e) {
console.error('更新等级配置失败:', e) console.error('更新等级配置失败:', e)
} }
@@ -493,14 +515,27 @@ const toggleParentLevel = (parentValue) => {
updateLevelFilter(newSet) updateLevelFilter(newSet)
} }
const selectAllLevels = () => {
// 全选所有等级
const allLevels = LEVEL_OPTIONS.flatMap(parent => parent.children.map(child => child.value))
updateLevelFilter(new Set(allLevels))
}
const selectNoneLevels = () => {
// 全不选所有等级
updateLevelFilter(new Set())
}
const updateMaxCount = async (value) => { const updateMaxCount = async (value) => {
// value is already updated via v-model // value is already updated via v-model
if (!isElectron()) return if (!isElectron()) return
try { try {
// 如果不填写,传 9999999 表示无限制
const maxAnchorCount = value || 9999999
await window.electronAPI.updateAutomationConfig({ await window.electronAPI.updateAutomationConfig({
filters: { maxAnchorCount: value } filters: { maxAnchorCount }
}) })
console.log('[HostListDialog] 主播数据上限已更新:', value) console.log('[HostListDialog] 主播数据上限已更新:', maxAnchorCount)
} catch (e) { } catch (e) {
console.error('更新配置失败:', e) console.error('更新配置失败:', e)
} }

View File

@@ -307,18 +307,20 @@ const showInviteList = async () => {
// 复制主播 ID // 复制主播 ID
const copyAnchorId = (id) => { const copyAnchorId = (id) => {
// 去除前面的 @ 符号
const cleanId = id.startsWith('@') ? id.substring(1) : id
if (navigator.clipboard && window.isSecureContext) { if (navigator.clipboard && window.isSecureContext) {
// 现代浏览器使用 Clipboard API // 现代浏览器使用 Clipboard API
navigator.clipboard.writeText(id).then(() => { navigator.clipboard.writeText(cleanId).then(() => {
showCopySuccess() showCopySuccess()
}).catch(err => { }).catch(err => {
console.error('复制失败:', err) console.error('复制失败:', err)
// 回退到传统方法 // 回退到传统方法
fallbackCopyTextToClipboard(id) fallbackCopyTextToClipboard(cleanId)
}) })
} else { } else {
// 传统方法 // 传统方法
fallbackCopyTextToClipboard(id) fallbackCopyTextToClipboard(cleanId)
} }
} }

View File

@@ -19,11 +19,13 @@
</div> </div>
<div class="country">{{ item.country }}</div> <div class="country">{{ item.country }}</div>
<div class="stat-item"> <div class="stat-item">
<img class="stat-icon" src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/gold.png" alt="" /> <img class="stat-icon" src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/gold.png"
alt="" />
<span>金币: <b>{{ item.coin }}K</b></span> <span>金币: <b>{{ item.coin }}K</b></span>
</div> </div>
<div class="stat-item"> <div class="stat-item">
<img class="stat-icon" src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/session.png" alt="" /> <img class="stat-icon" src="https://vv-1317974657.cos.ap-shanghai.myqcloud.com/util/session.png"
alt="" />
<span>场次: <b>{{ item.pkNumber }}</b></span> <span>场次: <b>{{ item.pkNumber }}</b></span>
</div> </div>
</div> </div>
@@ -66,35 +68,20 @@
<!-- 国家 --> <!-- 国家 -->
<div class="form-row"> <div class="form-row">
<el-select-v2 <el-select-v2 v-model="formData.country" :options="countryOptions" placeholder="请选择国家" filterable
v-model="formData.country" style="width: 100%" />
:options="countryOptions"
placeholder="请选择国家"
filterable
style="width: 100%"
/>
</div> </div>
<!-- 性别 --> <!-- 性别 -->
<div class="form-row"> <div class="form-row">
<el-select-v2 <el-select-v2 v-model="formData.gender" :options="genderOptions" placeholder="请选择性别"
v-model="formData.gender" style="width: 100%" />
:options="genderOptions"
placeholder="请选择性别"
style="width: 100%"
/>
</div> </div>
<!-- PK时间 --> <!-- PK时间 -->
<div class="form-row"> <div class="form-row">
<el-date-picker <el-date-picker v-model="formData.pkTime" type="datetime" placeholder="请选择PK时间北京时间" style="width: 100%"
v-model="formData.pkTime" :formatter="formatBeijingTime" :parser="parseBeijingTime" value-format="x" />
type="datetime"
placeholder="请选择PK时间"
style="width: 100%"
format="YYYY/MM/DD HH:mm"
value-format="x"
/>
</div> </div>
<!-- 金币和场次 --> <!-- 金币和场次 -->
@@ -136,13 +123,8 @@
<el-dialog v-model="showAnchorDialog" title="选择我的主播" width="800" align-center> <el-dialog v-model="showAnchorDialog" title="选择我的主播" width="800" align-center>
<div class="anchor-dialog-content"> <div class="anchor-dialog-content">
<div class="anchor-list"> <div class="anchor-list">
<div <div v-for="(item, index) in anchorLibrary" :key="index" class="anchor-item"
v-for="(item, index) in anchorLibrary" :class="{ selected: selectedAnchor === item }" @click="selectedAnchor = item">
:key="index"
class="anchor-item"
:class="{ selected: selectedAnchor === item }"
@click="selectedAnchor = item"
>
<img class="anchor-avatar" :src="item.headerIcon" alt="" /> <img class="anchor-avatar" :src="item.headerIcon" alt="" />
<div class="anchor-info"> <div class="anchor-info">
<div class="anchor-name">{{ item.anchorId }}</div> <div class="anchor-name">{{ item.anchorId }}</div>
@@ -167,12 +149,7 @@
<el-dialog v-model="showTopDialog" title="置顶" width="500" align-center> <el-dialog v-model="showTopDialog" title="置顶" width="500" align-center>
<div class="top-dialog-content"> <div class="top-dialog-content">
<p class="top-tip">置顶后您的PK信息将在首页优先展示可以获得更多曝光机会</p> <p class="top-tip">置顶后您的PK信息将在首页优先展示可以获得更多曝光机会</p>
<el-select-v2 <el-select-v2 v-model="topDuration" :options="topDurationOptions" placeholder="请选择置顶时长" style="width: 100%" />
v-model="topDuration"
:options="topDurationOptions"
placeholder="请选择置顶时长"
style="width: 100%"
/>
<div class="dialog-btns"> <div class="dialog-btns">
<div class="reset-btn" @click="showTopDialog = false">取消</div> <div class="reset-btn" @click="showTopDialog = false">取消</div>
<div class="confirm-btn" @click="confirmTop">确认置顶</div> <div class="confirm-btn" @click="confirmTop">确认置顶</div>
@@ -194,7 +171,7 @@
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from 'vue' import { ref, onMounted, computed } from 'vue'
import { import {
getPkInfo, getPkInfo,
releasePkInfo, releasePkInfo,
@@ -227,6 +204,36 @@ const list = ref([])
const page = ref(0) const page = ref(0)
const formatTime = TimestamptolocalTime const formatTime = TimestamptolocalTime
// 北京时间格式化函数
function formatBeijingTime(timestamp) {
// 创建一个UTC时间的Date对象
const utcDate = new Date(timestamp)
// 将UTC时间转换为北京时间UTC+8
const beijingDate = new Date(utcDate.getTime() + 8 * 60 * 60 * 1000)
const year = beijingDate.getUTCFullYear()
const month = String(beijingDate.getUTCMonth() + 1).padStart(2, '0')
const day = String(beijingDate.getUTCDate()).padStart(2, '0')
const hours = String(beijingDate.getUTCHours()).padStart(2, '0')
const minutes = String(beijingDate.getUTCMinutes()).padStart(2, '0')
return `${year}/${month}/${day} ${hours}:${minutes}`
}
// 解析北京时间字符串为时间戳
function parseBeijingTime(dateString) {
const [datePart, timePart] = dateString.split(' ')
const [year, month, day] = datePart.split('/').map(Number)
const [hours, minutes] = timePart.split(':').map(Number)
// 创建一个UTC时间的Date对象将北京时间的小时减去8小时
const utcDate = new Date(Date.UTC(year, month - 1, day, hours - 8, minutes, 0, 0))
// 返回UTC时间的时间戳毫秒
return utcDate.getTime()
}
// 表单数据 // 表单数据
const formData = ref({ const formData = ref({
anchorName: '', anchorName: '',

View File

@@ -86,7 +86,8 @@
<PermissionMask permission-key="webAi" title="自动私信工作台未开通" description="您当前没有使用自动私信功能的权限" <PermissionMask permission-key="webAi" title="自动私信工作台未开通" description="您当前没有使用自动私信功能的权限"
:placeholder-image="placeholderWebAi" :contacts="serviceContacts"> :placeholder-image="placeholderWebAi" :contacts="serviceContacts">
<div v-if="autoDmMode === 'config'" class="h-full w-full bg-slate-50 overflow-auto"> <div v-if="autoDmMode === 'config'" class="h-full w-full bg-slate-50 overflow-auto">
<ConfigPage @go-to-browser="handleGoToBrowser" @logout="$emit('logout')" /> <ConfigPage @go-to-browser="handleGoToBrowser" @logout="$emit('logout')"
@config-updated="handleConfigUpdated" />
</div> </div>
<div v-show="autoDmMode === 'browser'" class="h-full w-full"> <div v-show="autoDmMode === 'browser'" class="h-full w-full">
<YoloBrowser v-bind="$attrs" :nav-sidebar-width="navSidebarWidth" @go-back="handleBackToConfig" <YoloBrowser v-bind="$attrs" :nav-sidebar-width="navSidebarWidth" @go-back="handleBackToConfig"
@@ -256,6 +257,12 @@ const handleStopAll = () => {
emit('stop-all') emit('stop-all')
} }
// 处理配置更新事件
const handleConfigUpdated = () => {
// 触发自定义事件通知 YoloBrowser 重新加载配置
window.dispatchEvent(new CustomEvent('config-updated'))
}
// Watch for view changes to manage native Electron BrowserViews // Watch for view changes to manage native Electron BrowserViews
watch(currentView, async (newVal, oldVal) => { watch(currentView, async (newVal, oldVal) => {
// 懒加载 Web 端 iframe仅非 Electron // 懒加载 Web 端 iframe仅非 Electron

View File

@@ -363,7 +363,7 @@ import GreetingDialog from '../components/GreetingDialog.vue'
import AIConfigDialog from '../components/AIConfigDialog.vue' import AIConfigDialog from '../components/AIConfigDialog.vue'
import { isElectron } from '../utils/electronBridge' import { isElectron } from '../utils/electronBridge'
const emit = defineEmits(['goToBrowser', 'logout']) const emit = defineEmits(['goToBrowser', 'logout', 'configUpdated'])
const CONFIG_KEY = 'autoDm_runConfig' const CONFIG_KEY = 'autoDm_runConfig'
@@ -793,6 +793,8 @@ const handleStart = async (specificGroupIndex) => {
rotationStatus.value = status rotationStatus.value = status
handleStatusChange(status) handleStatusChange(status)
warmingUp.value = false //关闭遮罩 warmingUp.value = false //关闭遮罩
// 触发自定义事件通知配置已更新
emit('configUpdated')
emit('goToBrowser') emit('goToBrowser')
} }

View File

@@ -109,10 +109,42 @@ onMounted(() => {
buildViewMap(props.accountGroups) buildViewMap(props.accountGroups)
} }
// 监听本地存储变化,当配置更新时重新加载
window.addEventListener('storage', handleStorageChange)
// 监听自定义配置更新事件
window.addEventListener('config-updated', handleConfigUpdate)
// Listeners specific to browser view // Listeners specific to browser view
// ... // ...
}) })
onUnmounted(() => {
// 清理监听器
window.removeEventListener('storage', handleStorageChange)
window.removeEventListener('config-updated', handleConfigUpdate)
})
// 处理本地存储变化
const handleStorageChange = (event) => {
if (event.key === CONFIG_KEY) {
loadConfig()
// 强制重新构建 viewAccountMap确保使用最新的账号信息
if (props.accountGroups) {
buildViewMap(props.accountGroups)
}
}
}
// 处理配置更新事件
const handleConfigUpdate = () => {
console.log('[YoloBrowser] 收到配置更新事件,重新加载配置')
loadConfig()
// 强制重新构建 viewAccountMap确保使用最新的账号信息
if (props.accountGroups) {
buildViewMap(props.accountGroups)
}
}
// Original loadConfig logic // Original loadConfig logic
const loadConfig = () => { const loadConfig = () => {
try { try {
@@ -142,7 +174,7 @@ const buildViewMap = (groups) => {
watch(() => props.accountGroups, (newVal) => { watch(() => props.accountGroups, (newVal) => {
if (newVal) buildViewMap(newVal) if (newVal) buildViewMap(newVal)
}) }, { deep: true })
// Actions // Actions
const handleTabSwitch = async (tab) => { const handleTabSwitch = async (tab) => {
@@ -166,6 +198,12 @@ const handleTabSwitch = async (tab) => {
const handleViewSwitch = async (viewId) => { const handleViewSwitch = async (viewId) => {
selectedViewId.value = viewId selectedViewId.value = viewId
// 重新加载配置,确保获取最新的账号信息
loadConfig()
// 强制重新构建 viewAccountMap确保使用最新的账号信息
if (props.accountGroups) {
buildViewMap(props.accountGroups)
}
if (isElectron()) { if (isElectron()) {
await window.electronAPI.switchToView(viewId) await window.electronAPI.switchToView(viewId)
} }

View File

@@ -359,7 +359,7 @@ async function loadPkList() {
} }
} catch (e) { } catch (e) {
console.error('加载 PK 列表失败', e) console.error('加载 PK 列表失败', e)
ElMessage.error('加载失败,请稍后重试') // ElMessage.error('加载失败,请稍后重试')
} finally { } finally {
loading.value = false loading.value = false
// 数据加载完成后,使用 nextTick 强制重新计算滚动位置 // 数据加载完成后,使用 nextTick 强制重新计算滚动位置

View File

@@ -632,7 +632,7 @@ const toggleFilter = (filterName) => {
const resetTask = async () => { const resetTask = async () => {
try { try {
ElMessageBox.confirm( ElMessageBox.confirm(
'确定要重置任务吗?这将重启 Python 服务并重新加载工作台数据,可能会中断当前正在进行的操作。', '确定要重置任务吗?这将重启服务并中断当前正在进行的获取主播和大哥的任务。',
'重置任务', '重置任务',
{ {
confirmButtonText: '确定', confirmButtonText: '确定',