修复国家检测bug
This commit is contained in:
@@ -48,6 +48,17 @@ export default {
|
|||||||
prompt: 'Stop crawling specified number',
|
prompt: 'Stop crawling specified number',
|
||||||
setHostNum: 'Set crawling quantity',
|
setHostNum: 'Set crawling quantity',
|
||||||
unlimitedQuantity: 'Unlimited crawling quantity',
|
unlimitedQuantity: 'Unlimited crawling quantity',
|
||||||
|
refreshCountry: 'Refresh Country',
|
||||||
|
refreshSuccess: 'Refresh Successful',
|
||||||
|
refreshFailed: 'Refresh Failed',
|
||||||
|
enterCountryPrompt: 'Unable to automatically obtain country information due to network issues. Please manually enter the country name (in Chinese)',
|
||||||
|
enterCountryTitle: 'Failed to Get Country',
|
||||||
|
confirm: 'Confirm',
|
||||||
|
cancel: 'Cancel',
|
||||||
|
countryPlaceholder: 'e.g., 美国, 日本, 英国',
|
||||||
|
countryRequired: 'Please enter country name',
|
||||||
|
countrySetSuccess: 'Country Set Successfully',
|
||||||
|
unknown: 'Unknown',
|
||||||
},
|
},
|
||||||
hostList: {
|
hostList: {
|
||||||
placeCountry: 'Select country',
|
placeCountry: 'Select country',
|
||||||
@@ -156,6 +167,15 @@ export default {
|
|||||||
starting: 'Starting...',
|
starting: 'Starting...',
|
||||||
pleaseEnterCountryName: 'Please enter the country name in Chinese',
|
pleaseEnterCountryName: 'Please enter the country name in Chinese',
|
||||||
getCountryFailed: 'Failed to get country',
|
getCountryFailed: 'Failed to get country',
|
||||||
|
refreshCountry: 'Refresh Country',
|
||||||
|
refreshSuccess: 'Refresh Successful',
|
||||||
|
refreshFailed: 'Refresh Failed',
|
||||||
|
enterCountryPrompt: 'Unable to automatically obtain country information due to network issues. Please manually enter the country name (in Chinese)',
|
||||||
|
enterCountryTitle: 'Failed to Get Country',
|
||||||
|
countryPlaceholder: 'e.g., 美国, 日本, 英国',
|
||||||
|
countryRequired: 'Please enter country name',
|
||||||
|
countrySetSuccess: 'Country Set Successfully',
|
||||||
|
unknown: 'Unknown',
|
||||||
},
|
},
|
||||||
countries: {
|
countries: {
|
||||||
// ... (truncated common countries for brevity, or include all if critical. I'll include a subset or all if possible. The file read showed all.)
|
// ... (truncated common countries for brevity, or include all if critical. I'll include a subset or all if possible. The file read showed all.)
|
||||||
|
|||||||
@@ -48,6 +48,17 @@ export default {
|
|||||||
prompt: '达到数量后停止爬取',
|
prompt: '达到数量后停止爬取',
|
||||||
setHostNum: '设置爬取数量',
|
setHostNum: '设置爬取数量',
|
||||||
unlimitedQuantity: '不限爬取数量',
|
unlimitedQuantity: '不限爬取数量',
|
||||||
|
refreshCountry: '刷新国家',
|
||||||
|
refreshSuccess: '刷新成功',
|
||||||
|
refreshFailed: '刷新失败',
|
||||||
|
enterCountryPrompt: '由于网络原因无法自动获取国家信息,请手动输入当前网络所在国家(中文名)',
|
||||||
|
enterCountryTitle: '获取国家失败',
|
||||||
|
confirm: '确定',
|
||||||
|
cancel: '取消',
|
||||||
|
countryPlaceholder: '例如:美国、日本、英国',
|
||||||
|
countryRequired: '请输入国家名称',
|
||||||
|
countrySetSuccess: '国家设置成功',
|
||||||
|
unknown: '未知',
|
||||||
},
|
},
|
||||||
hostList: {
|
hostList: {
|
||||||
placeCountry: '选择国家',
|
placeCountry: '选择国家',
|
||||||
@@ -156,6 +167,15 @@ export default {
|
|||||||
stopping: '正在停止...',
|
stopping: '正在停止...',
|
||||||
starting: '正在启动...',
|
starting: '正在启动...',
|
||||||
enterRoomId: '请输入直播间id',
|
enterRoomId: '请输入直播间id',
|
||||||
|
refreshCountry: '刷新国家',
|
||||||
|
refreshSuccess: '刷新成功',
|
||||||
|
refreshFailed: '刷新失败',
|
||||||
|
enterCountryPrompt: '由于网络原因无法自动获取国家信息,请手动输入当前网络所在国家(中文名)',
|
||||||
|
enterCountryTitle: '获取国家失败',
|
||||||
|
countryPlaceholder: '例如:美国、日本、英国',
|
||||||
|
countryRequired: '请输入国家名称',
|
||||||
|
countrySetSuccess: '国家设置成功',
|
||||||
|
unknown: '未知',
|
||||||
},
|
},
|
||||||
countries: {
|
countries: {
|
||||||
AD: "安道尔", AE: "阿拉伯联合酋长国", AF: "阿富汗", AG: "安提瓜和巴布达", AI: "安圭拉", AL: "阿尔巴尼亚", AM: "亚美尼亚", AO: "安哥拉", AQ: "南极洲", AR: "阿根廷", AS: "美属萨摩亚", AT: "奥地利", AU: "澳大利亚", AU1: "澳大利亚", AW: "阿鲁巴", AX: "奥兰群岛", AZ: "阿塞拜疆",
|
AD: "安道尔", AE: "阿拉伯联合酋长国", AF: "阿富汗", AG: "安提瓜和巴布达", AI: "安圭拉", AL: "阿尔巴尼亚", AM: "亚美尼亚", AO: "安哥拉", AQ: "南极洲", AR: "阿根廷", AS: "美属萨摩亚", AT: "奥地利", AU: "澳大利亚", AU1: "澳大利亚", AW: "阿鲁巴", AX: "奥兰群岛", AZ: "阿塞拜疆",
|
||||||
|
|||||||
@@ -325,6 +325,34 @@
|
|||||||
|
|
||||||
<!-- 打招呼内容弹窗 -->
|
<!-- 打招呼内容弹窗 -->
|
||||||
<GreetingDialog :visible="showGreetingDialog" @close="showGreetingDialog = false" @confirm="handleGreetingConfirm" />
|
<GreetingDialog :visible="showGreetingDialog" @close="showGreetingDialog = false" @confirm="handleGreetingConfirm" />
|
||||||
|
|
||||||
|
<!-- 预热 Loading 遮罩 -->
|
||||||
|
<transition name="fade">
|
||||||
|
<div v-if="warmingUp"
|
||||||
|
class="fixed inset-0 z-[9999] flex items-center justify-center bg-black/30 backdrop-blur-sm">
|
||||||
|
<div
|
||||||
|
class="bg-white/90 rounded-2xl shadow-2xl border border-white/60 px-6 py-5 flex items-center gap-4">
|
||||||
|
<!-- spinner -->
|
||||||
|
<div class="w-10 h-10 rounded-full border-4 border-gray-200 border-t-blue-500 animate-spin"></div>
|
||||||
|
|
||||||
|
<div class="space-y-1">
|
||||||
|
<div class="text-sm font-semibold text-gray-900">正在预热视图</div>
|
||||||
|
<div class="text-xs text-gray-500">
|
||||||
|
这会提升后台视图渲染稳定性,请稍候…
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 可选:进度小点点动画 -->
|
||||||
|
<div class="flex gap-1 pt-1">
|
||||||
|
<span
|
||||||
|
class="w-1.5 h-1.5 rounded-full bg-blue-400 animate-bounce [animation-delay:-0.2s]"></span>
|
||||||
|
<span
|
||||||
|
class="w-1.5 h-1.5 rounded-full bg-blue-400 animate-bounce [animation-delay:-0.1s]"></span>
|
||||||
|
<span class="w-1.5 h-1.5 rounded-full bg-blue-400 animate-bounce"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -635,7 +663,7 @@ const handleSleepTimeInput = (val) => {
|
|||||||
config.value.sleepTime = parseInt(val) || 0
|
config.value.sleepTime = parseInt(val) || 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const warmingUp = ref(false)
|
||||||
// Start/Stop
|
// Start/Stop
|
||||||
const handleStart = async (specificGroupIndex) => {
|
const handleStart = async (specificGroupIndex) => {
|
||||||
const activeGroupIndex = specificGroupIndex ?? 0
|
const activeGroupIndex = specificGroupIndex ?? 0
|
||||||
@@ -710,7 +738,8 @@ const handleStart = async (specificGroupIndex) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预热所有视图,确保后台视图完成渲染,解决自动化执行失败问题
|
// 预热所有视图,确保后台视图完成渲染,解决自动化执行失败问题
|
||||||
|
warmingUp.value = true
|
||||||
try {
|
try {
|
||||||
console.log('[ConfigPage] 预热所有视图...')
|
console.log('[ConfigPage] 预热所有视图...')
|
||||||
await window.electronAPI.warmUpViews()
|
await window.electronAPI.warmUpViews()
|
||||||
@@ -758,6 +787,7 @@ const handleStart = async (specificGroupIndex) => {
|
|||||||
const status = await window.electronAPI.getRotationStatus()
|
const status = await window.electronAPI.getRotationStatus()
|
||||||
rotationStatus.value = status
|
rotationStatus.value = status
|
||||||
handleStatusChange(status)
|
handleStatusChange(status)
|
||||||
|
warmingUp.value = false //关闭遮罩
|
||||||
emit('goToBrowser')
|
emit('goToBrowser')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,3 +853,14 @@ const togglePasswordVisibility = (gIndex, aIndex) => {
|
|||||||
showPasswordMap.value[key] = !showPasswordMap.value[key]
|
showPasswordMap.value[key] = !showPasswordMap.value[key]
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
<style scoped>
|
||||||
|
.fade-enter-active,
|
||||||
|
.fade-leave-active {
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade-enter-from,
|
||||||
|
.fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
143
src/stores/countryStore.js
Normal file
143
src/stores/countryStore.js
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
|
import { getCountryName } from '@/utils/countryUtil'
|
||||||
|
|
||||||
|
export const useCountryStore = defineStore('country', () => {
|
||||||
|
// 状态
|
||||||
|
const countryData = ref('') // 中文国家名
|
||||||
|
const countryDataEN = ref('') // 英文国家名
|
||||||
|
const isLoading = ref(false) // 是否正在获取
|
||||||
|
const hasInitialized = ref(false) // 是否已初始化
|
||||||
|
const lastFetchTime = ref(null) // 上次获取时间
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 IP 国家信息
|
||||||
|
* @param {Function} t - 国际化函数
|
||||||
|
* @param {boolean} showDialog - 获取失败时是否显示弹窗(默认 true,只在第一次获取时显示)
|
||||||
|
*/
|
||||||
|
const fetchCountryInfo = async (t, showDialog = true) => {
|
||||||
|
if (isLoading.value) return
|
||||||
|
|
||||||
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://ipapi.co/json/')
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('请求失败')
|
||||||
|
}
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
countryDataEN.value = data.country_name
|
||||||
|
countryData.value = getCountryName(data.country)
|
||||||
|
lastFetchTime.value = Date.now()
|
||||||
|
hasInitialized.value = true
|
||||||
|
|
||||||
|
return { success: true }
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取国家信息失败:', error)
|
||||||
|
|
||||||
|
// 只在允许显示弹窗且未初始化时才显示
|
||||||
|
if (showDialog && !hasInitialized.value) {
|
||||||
|
showCountryInputDialog(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success: false, error }
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新国家信息
|
||||||
|
* @param {Function} t - 国际化函数
|
||||||
|
*/
|
||||||
|
const refreshCountry = async (t) => {
|
||||||
|
if (isLoading.value) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await fetchCountryInfo(t, false) // 刷新时不自动弹窗
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
ElMessage.success(t('workbenchesSetup.refreshSuccess') || t('hostsList.refreshSuccess') || '刷新成功')
|
||||||
|
} else {
|
||||||
|
// 刷新失败时,给用户选择是否手动输入
|
||||||
|
ElMessageBox.confirm(
|
||||||
|
t('workbenchesSetup.refreshFailed') || t('hostsList.refreshFailed') || '刷新失败,是否手动输入国家?',
|
||||||
|
t('workbenchesSetup.enterCountryTitle') || t('hostsList.enterCountryTitle') || '提示',
|
||||||
|
{
|
||||||
|
confirmButtonText: t('workbenchesSetup.confirm') || t('hostsList.confirm') || '手动输入',
|
||||||
|
cancelButtonText: t('workbenchesSetup.cancel') || t('hostsList.cancel') || '取消',
|
||||||
|
type: 'warning',
|
||||||
|
}
|
||||||
|
).then(() => {
|
||||||
|
showCountryInputDialog(t)
|
||||||
|
}).catch(() => {
|
||||||
|
// 用户取消
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(t('workbenchesSetup.refreshFailed') || t('hostsList.refreshFailed') || '刷新失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 显示手动输入国家的弹窗
|
||||||
|
* @param {Function} t - 国际化函数
|
||||||
|
*/
|
||||||
|
const showCountryInputDialog = (t) => {
|
||||||
|
ElMessageBox.prompt(
|
||||||
|
t('workbenchesSetup.enterCountryPrompt') || t('hostsList.enterCountryPrompt') || '由于网络原因无法自动获取国家信息,请手动输入当前网络所在国家(中文名)',
|
||||||
|
t('workbenchesSetup.enterCountryTitle') || t('hostsList.enterCountryTitle') || '获取国家失败',
|
||||||
|
{
|
||||||
|
confirmButtonText: t('workbenchesSetup.confirm') || t('hostsList.confirm') || '确定',
|
||||||
|
cancelButtonText: t('workbenchesSetup.cancel') || t('hostsList.cancel') || '取消',
|
||||||
|
inputPlaceholder: t('workbenchesSetup.countryPlaceholder') || t('hostsList.countryPlaceholder') || '例如:美国、日本、英国',
|
||||||
|
inputValidator: (value) => {
|
||||||
|
if (!value || value.trim() === '') {
|
||||||
|
return t('workbenchesSetup.countryRequired') || t('hostsList.countryRequired') || '请输入国家名称'
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).then(({ value }) => {
|
||||||
|
countryData.value = value.trim()
|
||||||
|
countryDataEN.value = value.trim()
|
||||||
|
hasInitialized.value = true
|
||||||
|
lastFetchTime.value = Date.now()
|
||||||
|
ElMessage.success(t('workbenchesSetup.countrySetSuccess') || t('hostsList.countrySetSuccess') || '国家设置成功')
|
||||||
|
}).catch(() => {
|
||||||
|
// 用户取消输入
|
||||||
|
if (!hasInitialized.value) {
|
||||||
|
countryData.value = t('workbenchesSetup.unknown') || t('hostsList.unknown') || '未知'
|
||||||
|
countryDataEN.value = 'Unknown'
|
||||||
|
hasInitialized.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化国家信息(只在第一次调用时获取)
|
||||||
|
* @param {Function} t - 国际化函数
|
||||||
|
*/
|
||||||
|
const initCountryInfo = async (t) => {
|
||||||
|
if (hasInitialized.value) {
|
||||||
|
return // 已经初始化过,不再重复获取
|
||||||
|
}
|
||||||
|
await fetchCountryInfo(t, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状态
|
||||||
|
countryData,
|
||||||
|
countryDataEN,
|
||||||
|
isLoading,
|
||||||
|
hasInitialized,
|
||||||
|
lastFetchTime,
|
||||||
|
|
||||||
|
// 方法
|
||||||
|
fetchCountryInfo,
|
||||||
|
refreshCountry,
|
||||||
|
showCountryInputDialog,
|
||||||
|
initCountryInfo,
|
||||||
|
}
|
||||||
|
})
|
||||||
@@ -53,16 +53,22 @@ export function usePythonBridge() {
|
|||||||
await window.electronAPI.tk.visitAnchor(id);
|
await window.electronAPI.tk.visitAnchor(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// backStageloginStatus
|
// 查询后台登录状态(合并接口,通过 account 参数区分)
|
||||||
const backStageloginStatus = async () => {
|
// account: 公会账号,不传则返回所有账号状态
|
||||||
|
const backStageloginStatus = async (account) => {
|
||||||
if (!inElectron) return null;
|
if (!inElectron) return null;
|
||||||
return await window.electronAPI.tk.checkBackStageLoginStatus();
|
try {
|
||||||
|
const res = await window.electronAPI.tk.checkBackStageLoginStatus(account);
|
||||||
|
return typeof res === 'string' ? JSON.parse(res) : res;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('backStageloginStatus error:', e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// backStageloginStatusCopy
|
// 兼容旧接口:查询副账号登录状态(内部调用合并后的接口)
|
||||||
const backStageloginStatusCopy = async () => {
|
const backStageloginStatusCopy = async (account) => {
|
||||||
if (!inElectron) return null;
|
return await backStageloginStatus(account);
|
||||||
return await window.electronAPI.tk.checkBackStageLoginStatusCopy();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// exportToExcel
|
// exportToExcel
|
||||||
@@ -125,9 +131,17 @@ export function usePythonBridge() {
|
|||||||
await window.electronAPI.tk.openRoom(id);
|
await window.electronAPI.tk.openRoom(id);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clipboard helper - maybe use navigator.clipboard directly in Vue component?
|
// Clipboard helper - 优先使用 Python RPC,fallback 到浏览器 API
|
||||||
// Original used python bridge for clipboard.
|
|
||||||
const setClipboards = async (text) => {
|
const setClipboards = async (text) => {
|
||||||
|
if (inElectron) {
|
||||||
|
try {
|
||||||
|
const result = await window.electronAPI.tk.setClipboard(text);
|
||||||
|
if (result.success) return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Electron clipboard failed, fallback to browser:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Fallback to browser API
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.writeText(text);
|
await navigator.clipboard.writeText(text);
|
||||||
return true;
|
return true;
|
||||||
@@ -137,6 +151,78 @@ export function usePythonBridge() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========== 新增接口 ==========
|
||||||
|
|
||||||
|
// 启动大哥监控(TikTok 登录)
|
||||||
|
const startBrotherMonitor = async () => {
|
||||||
|
if (!inElectron) return { success: false, error: 'Not in Electron' };
|
||||||
|
try {
|
||||||
|
return await window.electronAPI.tk.startBrotherMonitor();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('startBrotherMonitor error:', e);
|
||||||
|
return { success: false, error: String(e) };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取大哥模块 TikTok 登录状态
|
||||||
|
const getBrotherLoginStatus = async () => {
|
||||||
|
if (!inElectron) return { isLoggedIn: false };
|
||||||
|
try {
|
||||||
|
const res = await window.electronAPI.tk.getBrotherLoginStatus();
|
||||||
|
return JSON.parse(res);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('getBrotherLoginStatus error:', e);
|
||||||
|
return { isLoggedIn: false };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开大哥个人主页
|
||||||
|
const visitGifter = async (data) => {
|
||||||
|
if (!inElectron) return { success: false };
|
||||||
|
try {
|
||||||
|
// data 可以是 { id: 'xxx' } 或 { uniqueId: 'xxx' }
|
||||||
|
return await window.electronAPI.tk.visitGifter(JSON.stringify(data));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('visitGifter error:', e);
|
||||||
|
return { success: false, error: String(e) };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 关闭所有浏览器
|
||||||
|
const closeAllBrowsers = async () => {
|
||||||
|
if (!inElectron) return { success: false, error: 'Not in Electron' };
|
||||||
|
try {
|
||||||
|
return await window.electronAPI.tk.closeAllBrowsers();
|
||||||
|
} catch (e) {
|
||||||
|
console.error('closeAllBrowsers error:', e);
|
||||||
|
return { success: false, error: String(e) };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 加密存储账号信息
|
||||||
|
const storageAccountInfo = async (data) => {
|
||||||
|
if (!inElectron) return { success: false, error: 'Not in Electron' };
|
||||||
|
try {
|
||||||
|
// data: { key: string, data: object }
|
||||||
|
return await window.electronAPI.tk.storageAccount(JSON.stringify(data));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('storageAccountInfo error:', e);
|
||||||
|
return { success: false, error: String(e) };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 解密读取账号信息
|
||||||
|
const readAccountInfo = async (key) => {
|
||||||
|
if (!inElectron) return { status: 'error', message: 'Not in Electron', data: null };
|
||||||
|
try {
|
||||||
|
const res = await window.electronAPI.tk.readAccount(JSON.stringify({ key }));
|
||||||
|
return JSON.parse(res);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('readAccountInfo error:', e);
|
||||||
|
return { status: 'error', message: String(e), data: null };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
fetchDataConfig,
|
fetchDataConfig,
|
||||||
fetchDataCount,
|
fetchDataCount,
|
||||||
@@ -156,6 +242,13 @@ export function usePythonBridge() {
|
|||||||
storageSetInfos,
|
storageSetInfos,
|
||||||
readSetInfos,
|
readSetInfos,
|
||||||
openAnchorIdRooms,
|
openAnchorIdRooms,
|
||||||
setClipboards
|
setClipboards,
|
||||||
|
// 新增接口
|
||||||
|
startBrotherMonitor,
|
||||||
|
getBrotherLoginStatus,
|
||||||
|
visitGifter,
|
||||||
|
closeAllBrowsers,
|
||||||
|
storageAccountInfo,
|
||||||
|
readAccountInfo
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,9 +109,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Status Info -->
|
<!-- Status Info -->
|
||||||
<div class="bg-slate-50 px-4 py-2 rounded-xl border border-slate-100 text-sm">
|
<div class="bg-slate-50 px-4 py-2 rounded-xl border border-slate-100 text-sm flex items-center gap-2">
|
||||||
<span class="text-slate-500">{{ $t('hostsList.currentNetwork') || '当前网络' }}:</span>
|
<span class="text-slate-500">{{ $t('hostsList.currentNetwork') || '当前网络' }}:</span>
|
||||||
<span class="ml-2 font-bold text-primary">{{ countryData }}</span>
|
<span class="font-bold text-primary">{{ countryData }}</span>
|
||||||
|
<button @click="refreshCountry" :disabled="isRefreshingCountry"
|
||||||
|
class="p-1 rounded-md hover:bg-slate-200 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
:title="$t('hostsList.refreshCountry') || '刷新国家'">
|
||||||
|
<span class="material-icons-round text-slate-500 text-base" :class="{ 'animate-spin': isRefreshingCountry }">refresh</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -218,6 +223,7 @@ import { getUser } from "@/utils/storage";
|
|||||||
import { getCountryName } from "@/utils/countryUtil";
|
import { getCountryName } from "@/utils/countryUtil";
|
||||||
import { ElMessage, ElMessageBox, ElLoading } from "element-plus";
|
import { ElMessage, ElMessageBox, ElLoading } from "element-plus";
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useCountryStore } from '@/stores/countryStore';
|
||||||
|
|
||||||
// Mock API calls if not present
|
// Mock API calls if not present
|
||||||
// Ideally we should import these from api file, but for simplicity I will mock them or use empty callbacks
|
// Ideally we should import these from api file, but for simplicity I will mock them or use empty callbacks
|
||||||
@@ -228,6 +234,7 @@ import { useI18n } from 'vue-i18n';
|
|||||||
import { tkhostdata, getCountryinfo } from "@/api/account";
|
import { tkhostdata, getCountryinfo } from "@/api/account";
|
||||||
|
|
||||||
const { t, locale } = useI18n();
|
const { t, locale } = useI18n();
|
||||||
|
const countryStore = useCountryStore();
|
||||||
|
|
||||||
// Component State
|
// Component State
|
||||||
const queryFormData = ref({
|
const queryFormData = ref({
|
||||||
@@ -263,7 +270,9 @@ const streamdialogVisible = ref(false);
|
|||||||
const streamdialogVisibletext = ref(false);
|
const streamdialogVisibletext = ref(false);
|
||||||
const filterdialogVisible = ref(false);
|
const filterdialogVisible = ref(false);
|
||||||
const textarea = ref("");
|
const textarea = ref("");
|
||||||
const countryData = ref("");
|
// 使用共享 store 的国家信息
|
||||||
|
const countryData = computed(() => countryStore.countryData);
|
||||||
|
const isRefreshingCountry = computed(() => countryStore.isLoading);
|
||||||
const userInfo = ref({});
|
const userInfo = ref({});
|
||||||
const options = ref([]);
|
const options = ref([]);
|
||||||
|
|
||||||
@@ -311,7 +320,10 @@ const timerId = ref(null);
|
|||||||
// Lifecycle
|
// Lifecycle
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
userInfo.value = getUser() || { tenantId: 0, id: 0 };
|
userInfo.value = getUser() || { tenantId: 0, id: 0 };
|
||||||
getIpInfo();
|
|
||||||
|
// 使用共享 store 初始化国家信息
|
||||||
|
await countryStore.initCountryInfo(t);
|
||||||
|
|
||||||
getCountry();
|
getCountry();
|
||||||
getlist();
|
getlist();
|
||||||
|
|
||||||
@@ -321,7 +333,7 @@ onMounted(async () => {
|
|||||||
// savedSettings might be object already if backend returned object, or string
|
// savedSettings might be object already if backend returned object, or string
|
||||||
const data = typeof savedSettings === 'string' ? JSON.parse(savedSettings) : savedSettings;
|
const data = typeof savedSettings === 'string' ? JSON.parse(savedSettings) : savedSettings;
|
||||||
queryFormData.value = data;
|
queryFormData.value = data;
|
||||||
|
|
||||||
if (data.anchor_ids && data.anchor_ids.length > 0) {
|
if (data.anchor_ids && data.anchor_ids.length > 0) {
|
||||||
streamdialogVisibletext.value = true;
|
streamdialogVisibletext.value = true;
|
||||||
textarea.value = data.anchor_ids.join("\n");
|
textarea.value = data.anchor_ids.join("\n");
|
||||||
@@ -546,15 +558,9 @@ function openTikTok() {
|
|||||||
loginTikTok();
|
loginTikTok();
|
||||||
}
|
}
|
||||||
|
|
||||||
// IP / Country
|
// 刷新国家信息 - 使用共享 store
|
||||||
const getIpInfo = async () => {
|
const refreshCountry = async () => {
|
||||||
try {
|
await countryStore.refreshCountry(t);
|
||||||
const response = await fetch("https://ipapi.co/json/");
|
|
||||||
if (response.ok) {
|
|
||||||
const data = await response.json();
|
|
||||||
countryData.value = getCountryName(data.country);
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function getCountry() {
|
function getCountry() {
|
||||||
|
|||||||
@@ -115,9 +115,15 @@
|
|||||||
<h2 class="font-bold text-slate-800 dark:text-white">{{ $t('workbenchesSetup.workbenches') }}</h2>
|
<h2 class="font-bold text-slate-800 dark:text-white">{{ $t('workbenchesSetup.workbenches') }}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-4 text-sm">
|
<div class="flex items-center gap-4 text-sm">
|
||||||
<div class="text-slate-500">{{ $t('workbenchesSetup.network') }}: <span class="text-blue-600 font-bold">{{
|
<div class="flex items-center gap-2 text-slate-500">
|
||||||
locale
|
<span>{{ $t('workbenchesSetup.network') }}:</span>
|
||||||
== 'zh' ? countryData : countryDataEN }}</span></div>
|
<span class="text-blue-600 font-bold">{{ locale == 'zh' ? countryData : countryDataEN }}</span>
|
||||||
|
<button @click="refreshCountry" :disabled="isRefreshingCountry"
|
||||||
|
class="ml-1 p-1 rounded-md hover:bg-slate-100 dark:hover:bg-slate-800 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
:title="$t('workbenchesSetup.refreshCountry') || '刷新国家'">
|
||||||
|
<span class="material-icons-round text-slate-500 text-base" :class="{ 'animate-spin': isRefreshingCountry }">refresh</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-slate-500">指定国家:</span>
|
<span class="text-slate-500">指定国家:</span>
|
||||||
<select v-model="country_info"
|
<select v-model="country_info"
|
||||||
@@ -301,15 +307,18 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
|||||||
import { getCountryName } from '@/utils/countryUtil'
|
import { getCountryName } from '@/utils/countryUtil'
|
||||||
import { tkaccountuseinfo, getExpiredTime } from '@/api/account'
|
import { tkaccountuseinfo, getExpiredTime } from '@/api/account'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
const { locale } = useI18n()
|
import { useCountryStore } from '@/stores/countryStore'
|
||||||
|
|
||||||
|
const { t, locale } = useI18n()
|
||||||
|
const countryStore = useCountryStore()
|
||||||
//导入python交互方法
|
//导入python交互方法
|
||||||
const { fetchDataConfig, fetchDataCount, loginBackStage, loginTikTok, backStageloginStatus, backStageloginStatusCopy, getTkLoginStatus } = usePythonBridge();
|
const { fetchDataConfig, fetchDataCount, loginBackStage, loginTikTok, backStageloginStatus, backStageloginStatusCopy, getTkLoginStatus } = usePythonBridge();
|
||||||
|
|
||||||
|
|
||||||
//ip国家
|
//ip国家 - 使用共享 store
|
||||||
let countryData = ref('');
|
const countryData = computed(() => countryStore.countryData);
|
||||||
//英文国家
|
const countryDataEN = computed(() => countryStore.countryDataEN);
|
||||||
let countryDataEN = ref('');
|
const isRefreshingCountry = computed(() => countryStore.isLoading);
|
||||||
let country_info = ref('全部');
|
let country_info = ref('全部');
|
||||||
let country_Lst = ref();
|
let country_Lst = ref();
|
||||||
//获取主播数量的定时器
|
//获取主播数量的定时器
|
||||||
@@ -403,7 +412,9 @@ onMounted(async () => {
|
|||||||
tkaccountuse(tkData.value[0].account, 0)
|
tkaccountuse(tkData.value[0].account, 0)
|
||||||
tkaccountuse(tkData.value[1].account, 1)
|
tkaccountuse(tkData.value[1].account, 1)
|
||||||
|
|
||||||
getIpInfo()
|
// 使用共享 store 初始化国家信息
|
||||||
|
countryStore.initCountryInfo(t)
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Check if user exists before calling getExpiredTime
|
// Check if user exists before calling getExpiredTime
|
||||||
if (getUser()?.tenantId) {
|
if (getUser()?.tenantId) {
|
||||||
@@ -420,27 +431,26 @@ onMounted(async () => {
|
|||||||
}, 1000 * 20)
|
}, 1000 * 20)
|
||||||
})
|
})
|
||||||
|
|
||||||
const getIpInfo = async () => {
|
// 刷新国家信息 - 使用共享 store
|
||||||
|
const refreshCountry = async () => {
|
||||||
|
await countryStore.refreshCountry(t);
|
||||||
|
// 刷新成功后获取国家列表
|
||||||
|
if (countryStore.countryData) {
|
||||||
|
fetchCountryList(countryStore.countryData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取国家列表
|
||||||
|
const fetchCountryList = async (countryName) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch('https://ipapi.co/json/');
|
const url = `https://datasave.api.yolozs.com/api/save_data/country_info?countryName=${countryName}`;
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('请求失败');
|
|
||||||
}
|
|
||||||
const data = await response.json();
|
|
||||||
console.log('IP信息:', data.country);
|
|
||||||
countryDataEN.value = data.country_name
|
|
||||||
countryData.value = getCountryName(data.country);
|
|
||||||
|
|
||||||
const url = `https://datasave.api.yolozs.com/api/save_data/country_info?countryName=${countryData.value}`;
|
|
||||||
|
|
||||||
const res = await fetch(url);
|
const res = await fetch(url);
|
||||||
|
|
||||||
const countryres = await res.json();
|
const countryres = await res.json();
|
||||||
country_Lst.value = countryres.data
|
if (countryres.data) {
|
||||||
|
country_Lst.value = countryres.data;
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('请求出错:', error);
|
console.error('获取国家列表失败:', error);
|
||||||
// Optional: Re-enable if needed, but alert can be annoying
|
|
||||||
// ElMessageBox.prompt('请输入将要获取国家的中文名', '获取国家失败', { ... })
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -586,12 +596,12 @@ const loginTK = (index) => {
|
|||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
isLogin.value[1] = true;
|
isLogin.value[1] = true;
|
||||||
statusTimer = setInterval(() => {
|
statusTimer = setInterval(() => {
|
||||||
getloginStatus();
|
getloginStatus(tkData.value[index].account);
|
||||||
}, 2000)
|
}, 2000)
|
||||||
} else if (index == 1) {
|
} else if (index == 1) {
|
||||||
isLogin.value[0] = true;
|
isLogin.value[0] = true;
|
||||||
statusTimerCopy = setInterval(() => {
|
statusTimerCopy = setInterval(() => {
|
||||||
getloginStatusCopy();
|
getloginStatus(tkData.value[index].account);
|
||||||
}, 2000)
|
}, 2000)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -617,32 +627,47 @@ const checkTkLoginStatus = () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function getloginStatus() {
|
function getloginStatus(account) {
|
||||||
backStageloginStatus().then((res) => {
|
backStageloginStatus(account).then((res) => {
|
||||||
const data = JSON.parse(res);
|
console.log("登录状态", res)
|
||||||
tkData.value[data.index].code = data.code
|
|
||||||
|
|
||||||
if (data.code == 1) {
|
if (!res) return;
|
||||||
|
const data = res;
|
||||||
|
|
||||||
|
// 根据 account 找到对应的 index
|
||||||
|
const index = tkData.value.findIndex(item => item.account === account);
|
||||||
|
if (index === -1) return;
|
||||||
|
|
||||||
|
// 兼容新旧接口返回格式
|
||||||
|
// 新格式: { status, account, loginStatus, message }
|
||||||
|
// 旧格式: { code, index, message }
|
||||||
|
const isSuccess = data.message === "登录成功"
|
||||||
|
tkData.value[index].code = isSuccess ? 1 : 0;
|
||||||
|
|
||||||
|
if (isSuccess) {
|
||||||
clearInterval(statusTimer);
|
clearInterval(statusTimer);
|
||||||
|
clearInterval(statusTimerCopy);
|
||||||
statusTimer = null;
|
statusTimer = null;
|
||||||
|
statusTimerCopy = null;
|
||||||
submitting.value = false
|
submitting.value = false
|
||||||
|
isLogin.value[0] = false;
|
||||||
isLogin.value[1] = false;
|
isLogin.value[1] = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function getloginStatusCopy() {
|
// function getloginStatusCopy() {
|
||||||
backStageloginStatusCopy().then((res) => {
|
// backStageloginStatusCopy().then((res) => {
|
||||||
const data = JSON.parse(res);
|
// const data = JSON.parse(res);
|
||||||
tkData.value[data.index].code = data.code
|
// tkData.value[data.index].code = data.code
|
||||||
|
|
||||||
if (data.code == 1) {
|
// if (data.code == 1) {
|
||||||
clearInterval(statusTimer);
|
// clearInterval(statusTimer);
|
||||||
statusTimer = null;
|
// statusTimer = null;
|
||||||
submitting.value = false
|
// submitting.value = false
|
||||||
isLogin.value[0] = false;
|
// isLogin.value[0] = false;
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
|
|
||||||
function tkaccountuse(id, index) {
|
function tkaccountuse(id, index) {
|
||||||
|
|||||||
Reference in New Issue
Block a user