|
@@ -7,7 +7,7 @@
|
|
|
<el-icon><Refresh /></el-icon>
|
|
<el-icon><Refresh /></el-icon>
|
|
|
刷新所有
|
|
刷新所有
|
|
|
</el-button>
|
|
</el-button>
|
|
|
- <el-button type="primary" @click="showBrowserLoginDialog = true">
|
|
|
|
|
|
|
+ <el-button type="primary" @click="showPlatformSelect = true">
|
|
|
<el-icon><Monitor /></el-icon>
|
|
<el-icon><Monitor /></el-icon>
|
|
|
浏览器登录
|
|
浏览器登录
|
|
|
</el-button>
|
|
</el-button>
|
|
@@ -161,172 +161,57 @@
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
|
- <!-- 浏览器登录对话框 -->
|
|
|
|
|
|
|
+ <!-- 平台选择对话框 -->
|
|
|
<el-dialog
|
|
<el-dialog
|
|
|
- v-model="showBrowserLoginDialog"
|
|
|
|
|
- title="浏览器登录"
|
|
|
|
|
- width="500px"
|
|
|
|
|
- :close-on-click-modal="false"
|
|
|
|
|
- @closed="resetBrowserLogin"
|
|
|
|
|
|
|
+ v-model="showPlatformSelect"
|
|
|
|
|
+ title="选择登录平台"
|
|
|
|
|
+ width="450px"
|
|
|
|
|
+ :close-on-click-modal="true"
|
|
|
>
|
|
>
|
|
|
- <template v-if="!browserLoginSession.sessionId">
|
|
|
|
|
- <el-form :model="browserLoginForm" label-width="80px">
|
|
|
|
|
- <el-form-item label="平台">
|
|
|
|
|
- <el-select v-model="browserLoginForm.platform" placeholder="选择要登录的平台" style="width: 100%">
|
|
|
|
|
- <el-option
|
|
|
|
|
- v-for="platform in platforms"
|
|
|
|
|
- :key="platform.type"
|
|
|
|
|
- :label="platform.name"
|
|
|
|
|
- :value="platform.type"
|
|
|
|
|
- :disabled="!platform.supported"
|
|
|
|
|
- >
|
|
|
|
|
- <span class="platform-option">
|
|
|
|
|
- <span>{{ platform.name }}</span>
|
|
|
|
|
- <el-tag v-if="!platform.supported" size="small" type="info">适配中</el-tag>
|
|
|
|
|
- </span>
|
|
|
|
|
- </el-option>
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- <el-form-item label="分组">
|
|
|
|
|
- <el-select v-model="browserLoginForm.groupId" placeholder="选择分组" clearable style="width: 100%">
|
|
|
|
|
- <el-option
|
|
|
|
|
- v-for="group in groups"
|
|
|
|
|
- :key="group.id"
|
|
|
|
|
- :label="group.name"
|
|
|
|
|
- :value="group.id"
|
|
|
|
|
- />
|
|
|
|
|
- </el-select>
|
|
|
|
|
- </el-form-item>
|
|
|
|
|
- </el-form>
|
|
|
|
|
-
|
|
|
|
|
- <el-alert type="info" :closable="false" style="margin-top: 10px;">
|
|
|
|
|
- <template #title>
|
|
|
|
|
- 点击"开始登录"后会自动打开浏览器窗口,请在浏览器中完成平台登录,登录成功后系统会自动获取Cookie。
|
|
|
|
|
- </template>
|
|
|
|
|
- </el-alert>
|
|
|
|
|
- </template>
|
|
|
|
|
|
|
+ <el-form :model="browserLoginForm" label-width="80px">
|
|
|
|
|
+ <el-form-item label="平台">
|
|
|
|
|
+ <el-select v-model="browserLoginForm.platform" placeholder="选择要登录的平台" style="width: 100%">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="platform in platforms"
|
|
|
|
|
+ :key="platform.type"
|
|
|
|
|
+ :label="platform.name"
|
|
|
|
|
+ :value="platform.type"
|
|
|
|
|
+ :disabled="!platform.supported"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="platform-option">
|
|
|
|
|
+ <span>{{ platform.name }}</span>
|
|
|
|
|
+ <el-tag v-if="!platform.supported" size="small" type="info">适配中</el-tag>
|
|
|
|
|
+ </span>
|
|
|
|
|
+ </el-option>
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ <el-form-item label="分组">
|
|
|
|
|
+ <el-select v-model="browserLoginForm.groupId" placeholder="选择分组" clearable style="width: 100%">
|
|
|
|
|
+ <el-option
|
|
|
|
|
+ v-for="group in groups"
|
|
|
|
|
+ :key="group.id"
|
|
|
|
|
+ :label="group.name"
|
|
|
|
|
+ :value="group.id"
|
|
|
|
|
+ />
|
|
|
|
|
+ </el-select>
|
|
|
|
|
+ </el-form-item>
|
|
|
|
|
+ </el-form>
|
|
|
|
|
|
|
|
- <template v-else>
|
|
|
|
|
- <div class="browser-login-status">
|
|
|
|
|
- <el-icon
|
|
|
|
|
- class="status-icon"
|
|
|
|
|
- :class="browserLoginSession.status"
|
|
|
|
|
- v-if="browserLoginSession.status === 'pending' || browserLoginSession.status === 'fetching'"
|
|
|
|
|
- >
|
|
|
|
|
- <Loading />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- <el-icon
|
|
|
|
|
- class="status-icon success"
|
|
|
|
|
- v-else-if="browserLoginSession.status === 'success'"
|
|
|
|
|
- >
|
|
|
|
|
- <CircleCheck />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
- <el-icon
|
|
|
|
|
- class="status-icon error"
|
|
|
|
|
- v-else
|
|
|
|
|
- >
|
|
|
|
|
- <CircleClose />
|
|
|
|
|
- </el-icon>
|
|
|
|
|
-
|
|
|
|
|
- <div class="status-text">
|
|
|
|
|
- <template v-if="browserLoginSession.status === 'pending'">
|
|
|
|
|
- <h3>等待登录中...</h3>
|
|
|
|
|
- <p>请在打开的浏览器窗口中完成 {{ getPlatformName(browserLoginForm.platform as PlatformType) }} 登录</p>
|
|
|
|
|
- <div class="login-tips">
|
|
|
|
|
- <el-text type="info" size="small">
|
|
|
|
|
- <el-icon><InfoFilled /></el-icon>
|
|
|
|
|
- 扫码或输入账号密码登录后会自动检测
|
|
|
|
|
- </el-text>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- <template v-else-if="browserLoginSession.status === 'fetching'">
|
|
|
|
|
- <h3>
|
|
|
|
|
- <span class="fetching-text">正在获取账号信息</span>
|
|
|
|
|
- <span class="fetching-dots"></span>
|
|
|
|
|
- </h3>
|
|
|
|
|
- <div class="fetching-progress">
|
|
|
|
|
- <el-progress :percentage="fetchingProgress" :show-text="false" :stroke-width="4" />
|
|
|
|
|
- </div>
|
|
|
|
|
- <p class="fetching-hint">浏览器已关闭,正在后台静默获取数据</p>
|
|
|
|
|
- <div class="fetching-steps">
|
|
|
|
|
- <div class="step" :class="{ active: fetchingStep >= 1, done: fetchingStep > 1 }">
|
|
|
|
|
- <el-icon v-if="fetchingStep > 1"><CircleCheck /></el-icon>
|
|
|
|
|
- <el-icon v-else-if="fetchingStep === 1"><Loading /></el-icon>
|
|
|
|
|
- <span v-else>1</span>
|
|
|
|
|
- <span class="step-text">验证登录</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="step" :class="{ active: fetchingStep >= 2, done: fetchingStep > 2 }">
|
|
|
|
|
- <el-icon v-if="fetchingStep > 2"><CircleCheck /></el-icon>
|
|
|
|
|
- <el-icon v-else-if="fetchingStep === 2"><Loading /></el-icon>
|
|
|
|
|
- <span v-else>2</span>
|
|
|
|
|
- <span class="step-text">获取资料</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="step" :class="{ active: fetchingStep >= 3, done: fetchingStep > 3 }">
|
|
|
|
|
- <el-icon v-if="fetchingStep > 3"><CircleCheck /></el-icon>
|
|
|
|
|
- <el-icon v-else-if="fetchingStep === 3"><Loading /></el-icon>
|
|
|
|
|
- <span v-else>3</span>
|
|
|
|
|
- <span class="step-text">获取作品</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
- <template v-else-if="browserLoginSession.status === 'success'">
|
|
|
|
|
- <h3>登录成功!</h3>
|
|
|
|
|
- <div v-if="browserLoginSession.accountInfo" class="account-preview">
|
|
|
|
|
- <el-avatar :size="48" :src="browserLoginSession.accountInfo.avatarUrl || undefined">
|
|
|
|
|
- {{ browserLoginSession.accountInfo.accountName?.[0] }}
|
|
|
|
|
- </el-avatar>
|
|
|
|
|
- <div class="account-preview-info">
|
|
|
|
|
- <div class="account-preview-name">{{ browserLoginSession.accountInfo.accountName }}</div>
|
|
|
|
|
- <div class="account-preview-stats">
|
|
|
|
|
- <span>粉丝: {{ formatNumber(browserLoginSession.accountInfo.fansCount) }}</span>
|
|
|
|
|
- <span v-if="browserLoginSession.accountInfo.worksCount">
|
|
|
|
|
- · 作品: {{ browserLoginSession.accountInfo.worksCount }}
|
|
|
|
|
- </span>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <p>点击"保存账号"完成添加</p>
|
|
|
|
|
- </template>
|
|
|
|
|
- <template v-else>
|
|
|
|
|
- <h3>登录失败</h3>
|
|
|
|
|
- <p>{{ browserLoginSession.error || '未知错误' }}</p>
|
|
|
|
|
- </template>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </template>
|
|
|
|
|
|
|
+ <el-alert type="info" :closable="false" style="margin-top: 10px;">
|
|
|
|
|
+ <template #title>
|
|
|
|
|
+ 点击"开始登录"后会在右侧标签页中打开浏览器登录界面,登录成功后系统会自动获取Cookie。
|
|
|
|
|
+ </template>
|
|
|
|
|
+ </el-alert>
|
|
|
|
|
|
|
|
<template #footer>
|
|
<template #footer>
|
|
|
- <template v-if="!browserLoginSession.sessionId">
|
|
|
|
|
- <el-button @click="showBrowserLoginDialog = false">取消</el-button>
|
|
|
|
|
- <el-button
|
|
|
|
|
- type="primary"
|
|
|
|
|
- @click="startBrowserLogin"
|
|
|
|
|
- :loading="browserLoginLoading"
|
|
|
|
|
- :disabled="!browserLoginForm.platform"
|
|
|
|
|
- >
|
|
|
|
|
- 开始登录
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </template>
|
|
|
|
|
- <template v-else>
|
|
|
|
|
- <el-button @click="cancelBrowserLogin" :disabled="browserLoginLoading">
|
|
|
|
|
- 取消登录
|
|
|
|
|
- </el-button>
|
|
|
|
|
- <el-button
|
|
|
|
|
- v-if="browserLoginSession.status === 'success'"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- @click="confirmBrowserLogin"
|
|
|
|
|
- :loading="browserLoginLoading"
|
|
|
|
|
- >
|
|
|
|
|
- 保存账号
|
|
|
|
|
- </el-button>
|
|
|
|
|
- <el-button
|
|
|
|
|
- v-else-if="browserLoginSession.status !== 'pending'"
|
|
|
|
|
- type="primary"
|
|
|
|
|
- @click="resetBrowserLogin"
|
|
|
|
|
- >
|
|
|
|
|
- 重试
|
|
|
|
|
- </el-button>
|
|
|
|
|
- </template>
|
|
|
|
|
|
|
+ <el-button @click="showPlatformSelect = false">取消</el-button>
|
|
|
|
|
+ <el-button
|
|
|
|
|
+ type="primary"
|
|
|
|
|
+ @click="startBrowserLogin"
|
|
|
|
|
+ :disabled="!browserLoginForm.platform"
|
|
|
|
|
+ >
|
|
|
|
|
+ 开始登录
|
|
|
|
|
+ </el-button>
|
|
|
</template>
|
|
</template>
|
|
|
</el-dialog>
|
|
</el-dialog>
|
|
|
|
|
|
|
@@ -429,14 +314,16 @@
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
import { ref, reactive, onMounted, onUnmounted, watch } from 'vue';
|
|
import { ref, reactive, onMounted, onUnmounted, watch } from 'vue';
|
|
|
-import { Plus, Refresh, Monitor, Loading, CircleCheck, CircleClose, InfoFilled } from '@element-plus/icons-vue';
|
|
|
|
|
|
|
+import { Plus, Refresh, Monitor, Loading, CircleCheck, CircleClose } from '@element-plus/icons-vue';
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
import { accountsApi } from '@/api/accounts';
|
|
import { accountsApi } from '@/api/accounts';
|
|
|
import { PLATFORMS, PLATFORM_TYPES } from '@media-manager/shared';
|
|
import { PLATFORMS, PLATFORM_TYPES } from '@media-manager/shared';
|
|
|
import type { PlatformAccount, AccountGroup, PlatformType } from '@media-manager/shared';
|
|
import type { PlatformAccount, AccountGroup, PlatformType } from '@media-manager/shared';
|
|
|
import { useTaskQueueStore } from '@/stores/taskQueue';
|
|
import { useTaskQueueStore } from '@/stores/taskQueue';
|
|
|
|
|
+import { useTabsStore } from '@/stores/tabs';
|
|
|
|
|
|
|
|
const taskStore = useTaskQueueStore();
|
|
const taskStore = useTaskQueueStore();
|
|
|
|
|
+const tabsStore = useTabsStore();
|
|
|
|
|
|
|
|
// 监听任务列表变化,当 sync_account 任务完成时自动刷新账号列表
|
|
// 监听任务列表变化,当 sync_account 任务完成时自动刷新账号列表
|
|
|
watch(() => taskStore.tasks, (newTasks, oldTasks) => {
|
|
watch(() => taskStore.tasks, (newTasks, oldTasks) => {
|
|
@@ -457,18 +344,18 @@ watch(() => taskStore.tasks, (newTasks, oldTasks) => {
|
|
|
}
|
|
}
|
|
|
}, { deep: true });
|
|
}, { deep: true });
|
|
|
|
|
|
|
|
|
|
+// 监听账号刷新信号(当浏览器登录添加账号后触发)
|
|
|
|
|
+watch(() => tabsStore.accountRefreshTrigger, () => {
|
|
|
|
|
+ console.log('[Accounts] Account refresh triggered, reloading list...');
|
|
|
|
|
+ loadAccounts();
|
|
|
|
|
+});
|
|
|
|
|
+
|
|
|
const loading = ref(false);
|
|
const loading = ref(false);
|
|
|
const submitting = ref(false);
|
|
const submitting = ref(false);
|
|
|
const showAddDialog = ref(false);
|
|
const showAddDialog = ref(false);
|
|
|
-const showBrowserLoginDialog = ref(false);
|
|
|
|
|
-const browserLoginLoading = ref(false);
|
|
|
|
|
|
|
+const showPlatformSelect = ref(false);
|
|
|
const showRefreshDialog = ref(false);
|
|
const showRefreshDialog = ref(false);
|
|
|
|
|
|
|
|
-// 获取账号信息的进度状态
|
|
|
|
|
-const fetchingProgress = ref(0);
|
|
|
|
|
-const fetchingStep = ref(0);
|
|
|
|
|
-let fetchingTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
-
|
|
|
|
|
// 刷新账号的状态
|
|
// 刷新账号的状态
|
|
|
const refreshState = reactive({
|
|
const refreshState = reactive({
|
|
|
status: '' as '' | 'loading' | 'success' | 'expired' | 'failed',
|
|
status: '' as '' | 'loading' | 'success' | 'expired' | 'failed',
|
|
@@ -489,21 +376,6 @@ const browserLoginForm = reactive({
|
|
|
groupId: undefined as number | undefined,
|
|
groupId: undefined as number | undefined,
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-const browserLoginSession = reactive({
|
|
|
|
|
- sessionId: '',
|
|
|
|
|
- status: '' as '' | 'pending' | 'fetching' | 'success' | 'failed' | 'timeout' | 'cancelled',
|
|
|
|
|
- error: '',
|
|
|
|
|
- accountInfo: null as {
|
|
|
|
|
- accountId: string;
|
|
|
|
|
- accountName: string;
|
|
|
|
|
- avatarUrl: string;
|
|
|
|
|
- fansCount: number;
|
|
|
|
|
- worksCount: number;
|
|
|
|
|
- } | null,
|
|
|
|
|
-});
|
|
|
|
|
-
|
|
|
|
|
-let pollTimer: ReturnType<typeof setInterval> | null = null;
|
|
|
|
|
-
|
|
|
|
|
const platforms = PLATFORM_TYPES.map(type => PLATFORMS[type]);
|
|
const platforms = PLATFORM_TYPES.map(type => PLATFORMS[type]);
|
|
|
|
|
|
|
|
const filter = reactive({
|
|
const filter = reactive({
|
|
@@ -674,7 +546,7 @@ function resetRefreshState() {
|
|
|
function handleReLoginFromRefresh() {
|
|
function handleReLoginFromRefresh() {
|
|
|
showRefreshDialog.value = false;
|
|
showRefreshDialog.value = false;
|
|
|
browserLoginForm.platform = refreshState.platform;
|
|
browserLoginForm.platform = refreshState.platform;
|
|
|
- showBrowserLoginDialog.value = true;
|
|
|
|
|
|
|
+ showPlatformSelect.value = true;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function editAccount(account: PlatformAccount) {
|
|
function editAccount(account: PlatformAccount) {
|
|
@@ -695,171 +567,30 @@ async function deleteAccount(id: number) {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-// 浏览器登录功能
|
|
|
|
|
-async function startBrowserLogin() {
|
|
|
|
|
|
|
+// 浏览器登录功能 - 改为在标签页中打开
|
|
|
|
|
+function startBrowserLogin() {
|
|
|
if (!browserLoginForm.platform) {
|
|
if (!browserLoginForm.platform) {
|
|
|
ElMessage.warning('请选择平台');
|
|
ElMessage.warning('请选择平台');
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- browserLoginLoading.value = true;
|
|
|
|
|
- try {
|
|
|
|
|
- const result = await accountsApi.startBrowserLogin(browserLoginForm.platform);
|
|
|
|
|
- browserLoginSession.sessionId = result.sessionId;
|
|
|
|
|
- browserLoginSession.status = 'pending';
|
|
|
|
|
- ElMessage.success(result.message);
|
|
|
|
|
-
|
|
|
|
|
- // 开始轮询登录状态
|
|
|
|
|
- startPolling();
|
|
|
|
|
- } catch {
|
|
|
|
|
- // 错误已处理
|
|
|
|
|
- } finally {
|
|
|
|
|
- browserLoginLoading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function startPolling() {
|
|
|
|
|
- if (pollTimer) {
|
|
|
|
|
- clearInterval(pollTimer);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- pollTimer = setInterval(async () => {
|
|
|
|
|
- // 在 pending 或 fetching 状态下继续轮询
|
|
|
|
|
- if (!browserLoginSession.sessionId ||
|
|
|
|
|
- (browserLoginSession.status !== 'pending' && browserLoginSession.status !== 'fetching')) {
|
|
|
|
|
- stopPolling();
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- try {
|
|
|
|
|
- const status = await accountsApi.getBrowserLoginStatus(browserLoginSession.sessionId);
|
|
|
|
|
- const prevStatus = browserLoginSession.status;
|
|
|
|
|
- browserLoginSession.status = status.status as typeof browserLoginSession.status;
|
|
|
|
|
-
|
|
|
|
|
- // 状态变为 fetching 时启动进度动画
|
|
|
|
|
- if (status.status === 'fetching' && prevStatus !== 'fetching') {
|
|
|
|
|
- startFetchingAnimation();
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- if (status.status === 'success') {
|
|
|
|
|
- // 保存账号信息
|
|
|
|
|
- if (status.accountInfo) {
|
|
|
|
|
- browserLoginSession.accountInfo = status.accountInfo;
|
|
|
|
|
- }
|
|
|
|
|
- stopFetchingAnimation();
|
|
|
|
|
- ElMessage.success('账号信息获取完成!');
|
|
|
|
|
- stopPolling();
|
|
|
|
|
- } else if (status.status === 'fetching') {
|
|
|
|
|
- // 正在获取账号信息,继续轮询
|
|
|
|
|
- } else if (status.status !== 'pending') {
|
|
|
|
|
- stopFetchingAnimation();
|
|
|
|
|
- browserLoginSession.error = status.error || '登录失败';
|
|
|
|
|
- stopPolling();
|
|
|
|
|
- }
|
|
|
|
|
- } catch {
|
|
|
|
|
- // 会话可能已过期
|
|
|
|
|
- stopFetchingAnimation();
|
|
|
|
|
- browserLoginSession.status = 'failed';
|
|
|
|
|
- browserLoginSession.error = '会话已过期';
|
|
|
|
|
- stopPolling();
|
|
|
|
|
- }
|
|
|
|
|
- }, 2000);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 启动获取信息的进度动画
|
|
|
|
|
-function startFetchingAnimation() {
|
|
|
|
|
- fetchingProgress.value = 0;
|
|
|
|
|
- fetchingStep.value = 1;
|
|
|
|
|
|
|
+ const platformName = getPlatformName(browserLoginForm.platform as PlatformType);
|
|
|
|
|
|
|
|
- if (fetchingTimer) {
|
|
|
|
|
- clearInterval(fetchingTimer);
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 在标签页中打开浏览器
|
|
|
|
|
+ tabsStore.openBrowserTab(
|
|
|
|
|
+ browserLoginForm.platform,
|
|
|
|
|
+ `${platformName} 登录`,
|
|
|
|
|
+ browserLoginForm.groupId
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
- let elapsed = 0;
|
|
|
|
|
- fetchingTimer = setInterval(() => {
|
|
|
|
|
- elapsed += 100;
|
|
|
|
|
-
|
|
|
|
|
- // 进度条模拟(最多到95%,剩余5%在完成时填满)
|
|
|
|
|
- if (fetchingProgress.value < 95) {
|
|
|
|
|
- // 非线性增长,开始快后面慢
|
|
|
|
|
- const targetProgress = Math.min(95, Math.log(elapsed / 500 + 1) * 30);
|
|
|
|
|
- fetchingProgress.value = Math.min(95, targetProgress);
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- // 步骤更新
|
|
|
|
|
- if (elapsed > 1000 && fetchingStep.value < 2) {
|
|
|
|
|
- fetchingStep.value = 2;
|
|
|
|
|
- }
|
|
|
|
|
- if (elapsed > 3000 && fetchingStep.value < 3) {
|
|
|
|
|
- fetchingStep.value = 3;
|
|
|
|
|
- }
|
|
|
|
|
- }, 100);
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-// 停止获取信息的进度动画
|
|
|
|
|
-function stopFetchingAnimation() {
|
|
|
|
|
- if (fetchingTimer) {
|
|
|
|
|
- clearInterval(fetchingTimer);
|
|
|
|
|
- fetchingTimer = null;
|
|
|
|
|
- }
|
|
|
|
|
- fetchingProgress.value = 100;
|
|
|
|
|
- fetchingStep.value = 4;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function stopPolling() {
|
|
|
|
|
- if (pollTimer) {
|
|
|
|
|
- clearInterval(pollTimer);
|
|
|
|
|
- pollTimer = null;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function cancelBrowserLogin() {
|
|
|
|
|
- if (browserLoginSession.sessionId) {
|
|
|
|
|
- try {
|
|
|
|
|
- await accountsApi.cancelBrowserLogin(browserLoginSession.sessionId);
|
|
|
|
|
- } catch {
|
|
|
|
|
- // 忽略错误
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- resetBrowserLogin();
|
|
|
|
|
- showBrowserLoginDialog.value = false;
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-async function confirmBrowserLogin() {
|
|
|
|
|
- if (!browserLoginSession.sessionId || browserLoginSession.status !== 'success') {
|
|
|
|
|
- ElMessage.warning('登录未完成');
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 关闭平台选择对话框
|
|
|
|
|
+ showPlatformSelect.value = false;
|
|
|
|
|
|
|
|
- browserLoginLoading.value = true;
|
|
|
|
|
- try {
|
|
|
|
|
- await accountsApi.confirmBrowserLogin(
|
|
|
|
|
- browserLoginSession.sessionId,
|
|
|
|
|
- browserLoginForm.platform as string,
|
|
|
|
|
- browserLoginForm.groupId
|
|
|
|
|
- );
|
|
|
|
|
- ElMessage.success('账号添加成功');
|
|
|
|
|
- showBrowserLoginDialog.value = false;
|
|
|
|
|
- resetBrowserLogin();
|
|
|
|
|
- loadAccounts();
|
|
|
|
|
- } catch {
|
|
|
|
|
- // 错误已处理
|
|
|
|
|
- } finally {
|
|
|
|
|
- browserLoginLoading.value = false;
|
|
|
|
|
- }
|
|
|
|
|
-}
|
|
|
|
|
-
|
|
|
|
|
-function resetBrowserLogin() {
|
|
|
|
|
- stopPolling();
|
|
|
|
|
- stopFetchingAnimation();
|
|
|
|
|
- browserLoginSession.sessionId = '';
|
|
|
|
|
- browserLoginSession.status = '';
|
|
|
|
|
- browserLoginSession.error = '';
|
|
|
|
|
- browserLoginSession.accountInfo = null;
|
|
|
|
|
|
|
+ // 重置表单
|
|
|
browserLoginForm.platform = '';
|
|
browserLoginForm.platform = '';
|
|
|
browserLoginForm.groupId = undefined;
|
|
browserLoginForm.groupId = undefined;
|
|
|
- fetchingProgress.value = 0;
|
|
|
|
|
- fetchingStep.value = 0;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ ElMessage.success('已打开浏览器登录标签页');
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// 检查过期账号并提示重新登录
|
|
// 检查过期账号并提示重新登录
|
|
@@ -879,7 +610,7 @@ async function checkExpiredAccounts() {
|
|
|
}
|
|
}
|
|
|
).then(() => {
|
|
).then(() => {
|
|
|
browserLoginForm.platform = account.platform;
|
|
browserLoginForm.platform = account.platform;
|
|
|
- showBrowserLoginDialog.value = true;
|
|
|
|
|
|
|
+ showPlatformSelect.value = true;
|
|
|
}).catch(() => {
|
|
}).catch(() => {
|
|
|
// 用户取消
|
|
// 用户取消
|
|
|
});
|
|
});
|
|
@@ -913,8 +644,6 @@ onMounted(async () => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
onUnmounted(() => {
|
|
onUnmounted(() => {
|
|
|
- stopPolling();
|
|
|
|
|
- stopFetchingAnimation();
|
|
|
|
|
stopRefreshAnimation();
|
|
stopRefreshAnimation();
|
|
|
});
|
|
});
|
|
|
</script>
|
|
</script>
|
|
@@ -922,19 +651,32 @@ onUnmounted(() => {
|
|
|
<style lang="scss" scoped>
|
|
<style lang="scss" scoped>
|
|
|
@use '@/styles/variables.scss' as *;
|
|
@use '@/styles/variables.scss' as *;
|
|
|
|
|
|
|
|
|
|
+.accounts-page {
|
|
|
|
|
+ max-width: 1400px;
|
|
|
|
|
+ margin: 0 auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.page-header {
|
|
.page-header {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
- margin-bottom: 20px;
|
|
|
|
|
|
|
+ margin-bottom: 24px;
|
|
|
|
|
|
|
|
h2 {
|
|
h2 {
|
|
|
margin: 0;
|
|
margin: 0;
|
|
|
|
|
+ font-size: 22px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: $text-primary;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.header-actions {
|
|
.header-actions {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- gap: 10px;
|
|
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+
|
|
|
|
|
+ .el-button {
|
|
|
|
|
+ border-radius: $radius-base;
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -942,26 +684,71 @@ onUnmounted(() => {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
gap: 12px;
|
|
gap: 12px;
|
|
|
margin-bottom: 20px;
|
|
margin-bottom: 20px;
|
|
|
|
|
+ padding: 16px 20px !important;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-select) {
|
|
|
|
|
+ .el-input__wrapper {
|
|
|
|
|
+ border-radius: $radius-base;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.page-card {
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ border-radius: $radius-lg;
|
|
|
|
|
+ padding: 20px 24px;
|
|
|
|
|
+ box-shadow: $shadow-sm;
|
|
|
|
|
+ border: 1px solid $border-light;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-table) {
|
|
|
|
|
+ --el-table-border-color: #{$border-light};
|
|
|
|
|
+ --el-table-header-bg-color: #{$bg-base};
|
|
|
|
|
+
|
|
|
|
|
+ th.el-table__cell {
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: $text-primary;
|
|
|
|
|
+ background: $bg-base !important;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-table__row {
|
|
|
|
|
+ &:hover > td.el-table__cell {
|
|
|
|
|
+ background: $primary-color-light !important;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ .el-button--link {
|
|
|
|
|
+ font-weight: 500;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.account-cell {
|
|
.account-cell {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- gap: 12px;
|
|
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-avatar) {
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ border: 2px solid $border-light;
|
|
|
|
|
+ background: linear-gradient(135deg, $primary-color-light, #fff);
|
|
|
|
|
+ color: $primary-color;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
.account-info {
|
|
.account-info {
|
|
|
.account-name {
|
|
.account-name {
|
|
|
- font-weight: 500;
|
|
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: $text-primary;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.account-id {
|
|
.account-id {
|
|
|
font-size: 12px;
|
|
font-size: 12px;
|
|
|
color: $text-secondary;
|
|
color: $text-secondary;
|
|
|
|
|
+ margin-top: 2px;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-.browser-login-status,
|
|
|
|
|
.refresh-status {
|
|
.refresh-status {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
@@ -970,11 +757,9 @@ onUnmounted(() => {
|
|
|
text-align: center;
|
|
text-align: center;
|
|
|
|
|
|
|
|
.status-icon {
|
|
.status-icon {
|
|
|
- font-size: 64px;
|
|
|
|
|
|
|
+ font-size: 56px;
|
|
|
margin-bottom: 20px;
|
|
margin-bottom: 20px;
|
|
|
|
|
|
|
|
- &.pending,
|
|
|
|
|
- &.fetching,
|
|
|
|
|
&.loading {
|
|
&.loading {
|
|
|
color: $primary-color;
|
|
color: $primary-color;
|
|
|
animation: spin 1s linear infinite;
|
|
animation: spin 1s linear infinite;
|
|
@@ -995,6 +780,8 @@ onUnmounted(() => {
|
|
|
h3 {
|
|
h3 {
|
|
|
margin: 0 0 10px;
|
|
margin: 0 0 10px;
|
|
|
font-size: 18px;
|
|
font-size: 18px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ color: $text-primary;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
p {
|
|
p {
|
|
@@ -1006,11 +793,12 @@ onUnmounted(() => {
|
|
|
.account-preview {
|
|
.account-preview {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- gap: 12px;
|
|
|
|
|
|
|
+ gap: 14px;
|
|
|
margin: 15px 0;
|
|
margin: 15px 0;
|
|
|
- padding: 12px 20px;
|
|
|
|
|
- background: rgba($primary-color, 0.05);
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
|
|
+ padding: 16px 24px;
|
|
|
|
|
+ background: $primary-color-light;
|
|
|
|
|
+ border-radius: $radius-lg;
|
|
|
|
|
+ border: 1px solid $border-light;
|
|
|
|
|
|
|
|
.account-preview-info {
|
|
.account-preview-info {
|
|
|
text-align: left;
|
|
text-align: left;
|
|
@@ -1033,16 +821,6 @@ onUnmounted(() => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- .login-tips {
|
|
|
|
|
- margin-top: 16px;
|
|
|
|
|
-
|
|
|
|
|
- .el-text {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- align-items: center;
|
|
|
|
|
- gap: 4px;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
.fetching-text {
|
|
.fetching-text {
|
|
|
display: inline-block;
|
|
display: inline-block;
|
|
|
}
|
|
}
|
|
@@ -1053,8 +831,16 @@ onUnmounted(() => {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.fetching-progress {
|
|
.fetching-progress {
|
|
|
- width: 200px;
|
|
|
|
|
|
|
+ width: 220px;
|
|
|
margin: 16px 0;
|
|
margin: 16px 0;
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-progress-bar__outer) {
|
|
|
|
|
+ background: $border-light;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ :deep(.el-progress-bar__inner) {
|
|
|
|
|
+ background: linear-gradient(90deg, $primary-color, #64b5f6);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.fetching-hint {
|
|
.fetching-hint {
|
|
@@ -1065,30 +851,31 @@ onUnmounted(() => {
|
|
|
|
|
|
|
|
.fetching-steps {
|
|
.fetching-steps {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- gap: 30px;
|
|
|
|
|
|
|
+ gap: 36px;
|
|
|
|
|
|
|
|
.step {
|
|
.step {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- gap: 8px;
|
|
|
|
|
|
|
+ gap: 10px;
|
|
|
color: $text-secondary;
|
|
color: $text-secondary;
|
|
|
|
|
|
|
|
> span:first-child,
|
|
> span:first-child,
|
|
|
> .el-icon:first-child {
|
|
> .el-icon:first-child {
|
|
|
- width: 28px;
|
|
|
|
|
- height: 28px;
|
|
|
|
|
|
|
+ width: 32px;
|
|
|
|
|
+ height: 32px;
|
|
|
border-radius: 50%;
|
|
border-radius: 50%;
|
|
|
- background: #f0f0f0;
|
|
|
|
|
|
|
+ background: $bg-base;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
|
- font-size: 13px;
|
|
|
|
|
- font-weight: 500;
|
|
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.step-text {
|
|
.step-text {
|
|
|
- font-size: 12px;
|
|
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 500;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
&.active {
|
|
&.active {
|
|
@@ -1096,7 +883,7 @@ onUnmounted(() => {
|
|
|
|
|
|
|
|
> span:first-child,
|
|
> span:first-child,
|
|
|
> .el-icon:first-child {
|
|
> .el-icon:first-child {
|
|
|
- background: rgba($primary-color, 0.1);
|
|
|
|
|
|
|
+ background: $primary-color-light;
|
|
|
color: $primary-color;
|
|
color: $primary-color;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1109,7 +896,7 @@ onUnmounted(() => {
|
|
|
color: $success-color;
|
|
color: $success-color;
|
|
|
|
|
|
|
|
> .el-icon:first-child {
|
|
> .el-icon:first-child {
|
|
|
- background: rgba($success-color, 0.1);
|
|
|
|
|
|
|
+ background: $success-color-light;
|
|
|
color: $success-color;
|
|
color: $success-color;
|
|
|
animation: none;
|
|
animation: none;
|
|
|
}
|
|
}
|