840 lines
27 KiB
Vue
840 lines
27 KiB
Vue
<template>
|
||
<div class="account-management">
|
||
<div class="page-header">
|
||
<h1>账号管理</h1>
|
||
</div>
|
||
|
||
<div class="account-tabs">
|
||
<el-tabs v-model="activeTab" class="account-tabs-nav">
|
||
<el-tab-pane label="全部" name="all">
|
||
<div class="account-list-container">
|
||
<div class="account-search">
|
||
<el-input
|
||
v-model="searchKeyword"
|
||
placeholder="输入名称或账号搜索"
|
||
prefix-icon="Search"
|
||
clearable
|
||
@clear="handleSearch"
|
||
@input="handleSearch"
|
||
/>
|
||
<div class="action-buttons">
|
||
<el-button type="primary" @click="handleAddAccount">添加账号</el-button>
|
||
<el-button type="info" @click="fetchAccounts" :loading="false">
|
||
<el-icon :class="{ 'is-loading': appStore.isAccountRefreshing }"><Refresh /></el-icon>
|
||
<span v-if="appStore.isAccountRefreshing">刷新中</span>
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="filteredAccounts.length > 0" class="account-list">
|
||
<el-table :data="filteredAccounts" style="width: 100%">
|
||
<el-table-column label="头像" width="80">
|
||
<template #default="scope">
|
||
<el-avatar :src="scope.row.avatar" :size="40" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="name" label="名称" width="180" />
|
||
<el-table-column prop="platform" label="平台">
|
||
<template #default="scope">
|
||
<el-tag
|
||
:type="getPlatformTagType(scope.row.platform)"
|
||
effect="plain"
|
||
>
|
||
{{ scope.row.platform }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态">
|
||
<template #default="scope">
|
||
<el-tag
|
||
:type="scope.row.status === '正常' ? 'success' : 'danger'"
|
||
effect="plain"
|
||
>
|
||
{{ scope.row.status }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
||
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<div v-else class="empty-data">
|
||
<el-empty description="暂无账号数据" />
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane label="快手" name="kuaishou">
|
||
<div class="account-list-container">
|
||
<div class="account-search">
|
||
<el-input
|
||
v-model="searchKeyword"
|
||
placeholder="输入名称或账号搜索"
|
||
prefix-icon="Search"
|
||
clearable
|
||
@clear="handleSearch"
|
||
@input="handleSearch"
|
||
/>
|
||
<div class="action-buttons">
|
||
<el-button type="primary" @click="handleAddAccount">添加账号</el-button>
|
||
<el-button type="info" @click="fetchAccounts" :loading="false">
|
||
<el-icon :class="{ 'is-loading': appStore.isAccountRefreshing }"><Refresh /></el-icon>
|
||
<span v-if="appStore.isAccountRefreshing">刷新中</span>
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="filteredKuaishouAccounts.length > 0" class="account-list">
|
||
<el-table :data="filteredKuaishouAccounts" style="width: 100%">
|
||
<el-table-column label="头像" width="80">
|
||
<template #default="scope">
|
||
<el-avatar :src="scope.row.avatar" :size="40" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="name" label="名称" width="180" />
|
||
<el-table-column prop="platform" label="平台">
|
||
<template #default="scope">
|
||
<el-tag
|
||
:type="getPlatformTagType(scope.row.platform)"
|
||
effect="plain"
|
||
>
|
||
{{ scope.row.platform }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态">
|
||
<template #default="scope">
|
||
<el-tag
|
||
:type="scope.row.status === '正常' ? 'success' : 'danger'"
|
||
effect="plain"
|
||
>
|
||
{{ scope.row.status }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
||
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<div v-else class="empty-data">
|
||
<el-empty description="暂无快手账号数据" />
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane label="抖音" name="douyin">
|
||
<div class="account-list-container">
|
||
<div class="account-search">
|
||
<el-input
|
||
v-model="searchKeyword"
|
||
placeholder="输入名称或账号搜索"
|
||
prefix-icon="Search"
|
||
clearable
|
||
@clear="handleSearch"
|
||
@input="handleSearch"
|
||
/>
|
||
<div class="action-buttons">
|
||
<el-button type="primary" @click="handleAddAccount">添加账号</el-button>
|
||
<el-button type="info" @click="fetchAccounts" :loading="false">
|
||
<el-icon :class="{ 'is-loading': appStore.isAccountRefreshing }"><Refresh /></el-icon>
|
||
<span v-if="appStore.isAccountRefreshing">刷新中</span>
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="filteredDouyinAccounts.length > 0" class="account-list">
|
||
<el-table :data="filteredDouyinAccounts" style="width: 100%">
|
||
<el-table-column label="头像" width="80">
|
||
<template #default="scope">
|
||
<el-avatar :src="scope.row.avatar" :size="40" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="name" label="名称" width="180" />
|
||
<el-table-column prop="platform" label="平台">
|
||
<template #default="scope">
|
||
<el-tag
|
||
:type="getPlatformTagType(scope.row.platform)"
|
||
effect="plain"
|
||
>
|
||
{{ scope.row.platform }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态">
|
||
<template #default="scope">
|
||
<el-tag
|
||
:type="scope.row.status === '正常' ? 'success' : 'danger'"
|
||
effect="plain"
|
||
>
|
||
{{ scope.row.status }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
||
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<div v-else class="empty-data">
|
||
<el-empty description="暂无抖音账号数据" />
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane label="视频号" name="channels">
|
||
<div class="account-list-container">
|
||
<div class="account-search">
|
||
<el-input
|
||
v-model="searchKeyword"
|
||
placeholder="输入名称或账号搜索"
|
||
prefix-icon="Search"
|
||
clearable
|
||
@clear="handleSearch"
|
||
@input="handleSearch"
|
||
/>
|
||
<div class="action-buttons">
|
||
<el-button type="primary" @click="handleAddAccount">添加账号</el-button>
|
||
<el-button type="info" @click="fetchAccounts" :loading="false">
|
||
<el-icon :class="{ 'is-loading': appStore.isAccountRefreshing }"><Refresh /></el-icon>
|
||
<span v-if="appStore.isAccountRefreshing">刷新中</span>
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="filteredChannelsAccounts.length > 0" class="account-list">
|
||
<el-table :data="filteredChannelsAccounts" style="width: 100%">
|
||
<el-table-column label="头像" width="80">
|
||
<template #default="scope">
|
||
<el-avatar :src="scope.row.avatar" :size="40" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="name" label="名称" width="180" />
|
||
<el-table-column prop="platform" label="平台">
|
||
<template #default="scope">
|
||
<el-tag
|
||
:type="getPlatformTagType(scope.row.platform)"
|
||
effect="plain"
|
||
>
|
||
{{ scope.row.platform }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态">
|
||
<template #default="scope">
|
||
<el-tag
|
||
:type="scope.row.status === '正常' ? 'success' : 'danger'"
|
||
effect="plain"
|
||
>
|
||
{{ scope.row.status }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
||
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<div v-else class="empty-data">
|
||
<el-empty description="暂无视频号账号数据" />
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
|
||
<el-tab-pane label="小红书" name="xiaohongshu">
|
||
<div class="account-list-container">
|
||
<div class="account-search">
|
||
<el-input
|
||
v-model="searchKeyword"
|
||
placeholder="输入名称或账号搜索"
|
||
prefix-icon="Search"
|
||
clearable
|
||
@clear="handleSearch"
|
||
@input="handleSearch"
|
||
/>
|
||
<div class="action-buttons">
|
||
<el-button type="primary" @click="handleAddAccount">添加账号</el-button>
|
||
<el-button type="info" @click="fetchAccounts" :loading="false">
|
||
<el-icon :class="{ 'is-loading': appStore.isAccountRefreshing }"><Refresh /></el-icon>
|
||
<span v-if="appStore.isAccountRefreshing">刷新中</span>
|
||
</el-button>
|
||
</div>
|
||
</div>
|
||
|
||
<div v-if="filteredXiaohongshuAccounts.length > 0" class="account-list">
|
||
<el-table :data="filteredXiaohongshuAccounts" style="width: 100%">
|
||
<el-table-column label="头像" width="80">
|
||
<template #default="scope">
|
||
<el-avatar :src="scope.row.avatar" :size="40" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="name" label="名称" width="180" />
|
||
<el-table-column prop="platform" label="平台">
|
||
<template #default="scope">
|
||
<el-tag
|
||
:type="getPlatformTagType(scope.row.platform)"
|
||
effect="plain"
|
||
>
|
||
{{ scope.row.platform }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column prop="status" label="状态">
|
||
<template #default="scope">
|
||
<el-tag
|
||
:type="scope.row.status === '正常' ? 'success' : 'danger'"
|
||
effect="plain"
|
||
>
|
||
{{ scope.row.status }}
|
||
</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作">
|
||
<template #default="scope">
|
||
<el-button size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
||
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
|
||
<div v-else class="empty-data">
|
||
<el-empty description="暂无小红书账号数据" />
|
||
</div>
|
||
</div>
|
||
</el-tab-pane>
|
||
</el-tabs>
|
||
</div>
|
||
|
||
<!-- 添加/编辑账号对话框 -->
|
||
<el-dialog
|
||
v-model="dialogVisible"
|
||
:title="dialogType === 'add' ? '添加账号' : '编辑账号'"
|
||
width="500px"
|
||
:close-on-click-modal="false"
|
||
:close-on-press-escape="!sseConnecting"
|
||
:show-close="!sseConnecting"
|
||
>
|
||
<el-form :model="accountForm" label-width="80px" :rules="rules" ref="accountFormRef">
|
||
<el-form-item label="平台" prop="platform">
|
||
<el-select
|
||
v-model="accountForm.platform"
|
||
placeholder="请选择平台"
|
||
style="width: 100%"
|
||
:disabled="sseConnecting"
|
||
>
|
||
<el-option label="快手" value="快手" />
|
||
<el-option label="抖音" value="抖音" />
|
||
<el-option label="视频号" value="视频号" />
|
||
<el-option label="小红书" value="小红书" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="名称" prop="name">
|
||
<el-input
|
||
v-model="accountForm.name"
|
||
placeholder="请输入账号名称"
|
||
:disabled="sseConnecting"
|
||
/>
|
||
</el-form-item>
|
||
|
||
<!-- 二维码显示区域 -->
|
||
<div v-if="sseConnecting" class="qrcode-container">
|
||
<div v-if="qrCodeData && !loginStatus" class="qrcode-wrapper">
|
||
<p class="qrcode-tip">请使用对应平台APP扫描二维码登录</p>
|
||
<img :src="qrCodeData" alt="登录二维码" class="qrcode-image" />
|
||
</div>
|
||
<div v-else-if="!qrCodeData && !loginStatus" class="loading-wrapper">
|
||
<el-icon class="is-loading"><Refresh /></el-icon>
|
||
<span>请求中...</span>
|
||
</div>
|
||
<div v-else-if="loginStatus === '200'" class="success-wrapper">
|
||
<el-icon><CircleCheckFilled /></el-icon>
|
||
<span>添加成功</span>
|
||
</div>
|
||
<div v-else-if="loginStatus === '500'" class="error-wrapper">
|
||
<el-icon><CircleCloseFilled /></el-icon>
|
||
<span>添加失败,请稍后再试</span>
|
||
</div>
|
||
</div>
|
||
</el-form>
|
||
<template #footer>
|
||
<span class="dialog-footer">
|
||
<el-button @click="dialogVisible = false" :disabled="sseConnecting && !loginStatus">取消</el-button>
|
||
<el-button
|
||
type="primary"
|
||
@click="submitAccountForm"
|
||
:loading="sseConnecting"
|
||
:disabled="sseConnecting"
|
||
>
|
||
{{ sseConnecting ? '请求中' : '确认' }}
|
||
</el-button>
|
||
</span>
|
||
</template>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, reactive, computed, onMounted, onBeforeUnmount } from 'vue'
|
||
import { Refresh, CircleCheckFilled, CircleCloseFilled } from '@element-plus/icons-vue'
|
||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||
import { accountApi } from '@/api/account'
|
||
import { useAccountStore } from '@/stores/account'
|
||
import { useAppStore } from '@/stores/app'
|
||
|
||
// 获取账号状态管理
|
||
const accountStore = useAccountStore()
|
||
// 获取应用状态管理
|
||
const appStore = useAppStore()
|
||
|
||
// 当前激活的标签页
|
||
const activeTab = ref('all')
|
||
|
||
// 搜索关键词
|
||
const searchKeyword = ref('')
|
||
|
||
// 获取账号数据
|
||
const fetchAccounts = async () => {
|
||
if (appStore.isAccountRefreshing) return
|
||
|
||
appStore.setAccountRefreshing(true)
|
||
|
||
try {
|
||
const res = await accountApi.getValidAccounts()
|
||
if (res.code === 200 && res.data) {
|
||
accountStore.setAccounts(res.data)
|
||
ElMessage.success('账号数据获取成功')
|
||
// 标记为已访问
|
||
if (appStore.isFirstTimeAccountManagement) {
|
||
appStore.setAccountManagementVisited()
|
||
}
|
||
} else {
|
||
ElMessage.error('获取账号数据失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('获取账号数据失败:', error)
|
||
ElMessage.error('获取账号数据失败')
|
||
} finally {
|
||
appStore.setAccountRefreshing(false)
|
||
}
|
||
}
|
||
|
||
// 页面加载时获取账号数据
|
||
onMounted(() => {
|
||
// 只有第一次进入时才获取数据
|
||
if (appStore.isFirstTimeAccountManagement) {
|
||
fetchAccounts()
|
||
}
|
||
})
|
||
|
||
// 获取平台标签类型
|
||
const getPlatformTagType = (platform) => {
|
||
const typeMap = {
|
||
'快手': 'success',
|
||
'抖音': 'danger',
|
||
'视频号': 'warning',
|
||
'小红书': 'info'
|
||
}
|
||
return typeMap[platform] || 'info'
|
||
}
|
||
|
||
// 过滤后的账号列表
|
||
const filteredAccounts = computed(() => {
|
||
if (!searchKeyword.value) return accountStore.accounts
|
||
return accountStore.accounts.filter(account =>
|
||
account.name.includes(searchKeyword.value)
|
||
)
|
||
})
|
||
|
||
// 按平台过滤的账号列表
|
||
const filteredKuaishouAccounts = computed(() => {
|
||
return filteredAccounts.value.filter(account => account.platform === '快手')
|
||
})
|
||
|
||
const filteredDouyinAccounts = computed(() => {
|
||
return filteredAccounts.value.filter(account => account.platform === '抖音')
|
||
})
|
||
|
||
const filteredChannelsAccounts = computed(() => {
|
||
return filteredAccounts.value.filter(account => account.platform === '视频号')
|
||
})
|
||
|
||
const filteredXiaohongshuAccounts = computed(() => {
|
||
return filteredAccounts.value.filter(account => account.platform === '小红书')
|
||
})
|
||
|
||
// 搜索处理
|
||
const handleSearch = () => {
|
||
// 搜索逻辑已通过计算属性实现
|
||
}
|
||
|
||
// 对话框相关
|
||
const dialogVisible = ref(false)
|
||
const dialogType = ref('add') // 'add' 或 'edit'
|
||
const accountFormRef = ref(null)
|
||
|
||
// 账号表单
|
||
const accountForm = reactive({
|
||
id: null,
|
||
name: '',
|
||
platform: '',
|
||
status: '正常'
|
||
})
|
||
|
||
// 表单验证规则
|
||
const rules = {
|
||
platform: [{ required: true, message: '请选择平台', trigger: 'change' }],
|
||
name: [{ required: true, message: '请输入账号名称', trigger: 'blur' }]
|
||
}
|
||
|
||
// SSE连接状态
|
||
const sseConnecting = ref(false)
|
||
const qrCodeData = ref('')
|
||
const loginStatus = ref('')
|
||
|
||
// 添加账号
|
||
const handleAddAccount = () => {
|
||
dialogType.value = 'add'
|
||
Object.assign(accountForm, {
|
||
id: null,
|
||
name: '',
|
||
platform: '',
|
||
status: '正常'
|
||
})
|
||
// 重置SSE状态
|
||
sseConnecting.value = false
|
||
qrCodeData.value = ''
|
||
loginStatus.value = ''
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
// 编辑账号
|
||
const handleEdit = (row) => {
|
||
dialogType.value = 'edit'
|
||
Object.assign(accountForm, { ...row })
|
||
dialogVisible.value = true
|
||
}
|
||
|
||
// 删除账号
|
||
const handleDelete = (row) => {
|
||
ElMessageBox.confirm(
|
||
`确定要删除账号 ${row.name} 吗?`,
|
||
'警告',
|
||
{
|
||
confirmButtonText: '确定',
|
||
cancelButtonText: '取消',
|
||
type: 'warning',
|
||
}
|
||
)
|
||
.then(async () => {
|
||
try {
|
||
// 调用API删除账号
|
||
const response = await accountApi.deleteAccount(row.id)
|
||
|
||
if (response.code === 200) {
|
||
// 从状态管理中删除账号
|
||
accountStore.deleteAccount(row.id)
|
||
ElMessage({
|
||
type: 'success',
|
||
message: '删除成功',
|
||
})
|
||
} else {
|
||
ElMessage.error(response.msg || '删除失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('删除账号失败:', error)
|
||
ElMessage.error('删除账号失败')
|
||
}
|
||
})
|
||
.catch(() => {
|
||
// 取消删除
|
||
})
|
||
}
|
||
|
||
// SSE事件源对象
|
||
let eventSource = null
|
||
|
||
// 关闭SSE连接
|
||
const closeSSEConnection = () => {
|
||
if (eventSource) {
|
||
eventSource.close()
|
||
eventSource = null
|
||
}
|
||
}
|
||
|
||
// 建立SSE连接
|
||
const connectSSE = (platform, name) => {
|
||
// 关闭可能存在的连接
|
||
closeSSEConnection()
|
||
|
||
// 设置连接状态
|
||
sseConnecting.value = true
|
||
qrCodeData.value = ''
|
||
loginStatus.value = ''
|
||
|
||
// 获取平台类型编号
|
||
const platformTypeMap = {
|
||
'小红书': '1',
|
||
'视频号': '2',
|
||
'抖音': '3',
|
||
'快手': '4'
|
||
}
|
||
|
||
const type = platformTypeMap[platform] || '1'
|
||
|
||
// 创建SSE连接
|
||
const baseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5409'
|
||
const url = `${baseUrl}/login?type=${type}&id=${encodeURIComponent(name)}`
|
||
|
||
eventSource = new EventSource(url)
|
||
|
||
// 监听消息
|
||
eventSource.onmessage = (event) => {
|
||
const data = event.data
|
||
console.log('SSE消息:', data)
|
||
|
||
// 如果还没有二维码数据,且数据长度较长,认为是二维码
|
||
if (!qrCodeData.value && data.length > 100) {
|
||
try {
|
||
// 确保数据是有效的base64编码
|
||
// 如果数据已经包含了data:image前缀,直接使用
|
||
if (data.startsWith('data:image')) {
|
||
qrCodeData.value = data
|
||
} else {
|
||
// 否则添加前缀
|
||
qrCodeData.value = `data:image/png;base64,${data}`
|
||
}
|
||
console.log('设置二维码数据,长度:', data.length)
|
||
} catch (error) {
|
||
console.error('处理二维码数据出错:', error)
|
||
}
|
||
}
|
||
// 如果收到状态码
|
||
else if (data === '200' || data === '500') {
|
||
loginStatus.value = data
|
||
|
||
// 如果登录成功,添加账号到状态管理
|
||
if (data === '200') {
|
||
setTimeout(() => {
|
||
const newId = accountStore.accounts.length > 0 ?
|
||
Math.max(...accountStore.accounts.map(a => a.id)) + 1 : 1
|
||
|
||
const newAccount = {
|
||
id: newId,
|
||
name: accountForm.name,
|
||
platform: accountForm.platform,
|
||
status: '正常',
|
||
avatar: '/vite.svg' // 默认头像
|
||
}
|
||
|
||
// 添加到状态管理
|
||
accountStore.addAccount(newAccount)
|
||
|
||
// 关闭连接
|
||
closeSSEConnection()
|
||
|
||
// 1秒后关闭对话框
|
||
setTimeout(() => {
|
||
dialogVisible.value = false
|
||
sseConnecting.value = false
|
||
ElMessage.success('账号添加成功')
|
||
|
||
// 显示更新账号信息提示并刷新账号列表
|
||
ElMessage({
|
||
type: 'info',
|
||
message: '更新账号信息中...',
|
||
duration: 2000
|
||
})
|
||
|
||
// 触发刷新操作
|
||
isRefreshing.value = true
|
||
fetchAccounts()
|
||
}, 1000)
|
||
}, 1000)
|
||
} else {
|
||
// 登录失败,关闭连接
|
||
closeSSEConnection()
|
||
|
||
// 2秒后重置状态,允许重试
|
||
setTimeout(() => {
|
||
sseConnecting.value = false
|
||
qrCodeData.value = ''
|
||
loginStatus.value = ''
|
||
}, 2000)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 监听错误
|
||
eventSource.onerror = (error) => {
|
||
console.error('SSE连接错误:', error)
|
||
ElMessage.error('连接服务器失败,请稍后再试')
|
||
closeSSEConnection()
|
||
sseConnecting.value = false
|
||
}
|
||
}
|
||
|
||
// 提交账号表单
|
||
const submitAccountForm = () => {
|
||
accountFormRef.value.validate(async (valid) => {
|
||
if (valid) {
|
||
if (dialogType.value === 'add') {
|
||
// 建立SSE连接
|
||
connectSSE(accountForm.platform, accountForm.name)
|
||
} else {
|
||
// 编辑账号逻辑保持不变
|
||
try {
|
||
// 更新状态管理中的账号
|
||
accountStore.updateAccount(accountForm.id, accountForm)
|
||
ElMessage.success('更新成功')
|
||
dialogVisible.value = false
|
||
} catch (error) {
|
||
console.error('更新账号失败:', error)
|
||
ElMessage.error('更新账号失败')
|
||
}
|
||
}
|
||
} else {
|
||
return false
|
||
}
|
||
})
|
||
}
|
||
|
||
// 组件卸载前关闭SSE连接
|
||
onBeforeUnmount(() => {
|
||
closeSSEConnection()
|
||
})
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@use '@/styles/variables.scss' as *;
|
||
|
||
@keyframes rotate {
|
||
from {
|
||
transform: rotate(0deg);
|
||
}
|
||
to {
|
||
transform: rotate(360deg);
|
||
}
|
||
}
|
||
|
||
.account-management {
|
||
.page-header {
|
||
margin-bottom: 20px;
|
||
|
||
h1 {
|
||
font-size: 24px;
|
||
color: $text-primary;
|
||
margin: 0;
|
||
}
|
||
}
|
||
|
||
.account-tabs {
|
||
background-color: #fff;
|
||
border-radius: 4px;
|
||
box-shadow: $box-shadow-light;
|
||
|
||
.account-tabs-nav {
|
||
padding: 20px;
|
||
}
|
||
}
|
||
|
||
.account-list-container {
|
||
.account-search {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
margin-bottom: 20px;
|
||
|
||
.el-input {
|
||
width: 300px;
|
||
}
|
||
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 10px;
|
||
|
||
.el-icon.is-loading {
|
||
animation: rotate 1s linear infinite;
|
||
}
|
||
}
|
||
}
|
||
|
||
.account-list {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.empty-data {
|
||
padding: 40px 0;
|
||
}
|
||
}
|
||
|
||
// 二维码容器样式
|
||
.qrcode-container {
|
||
margin-top: 20px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 250px;
|
||
|
||
.qrcode-wrapper {
|
||
text-align: center;
|
||
|
||
.qrcode-tip {
|
||
margin-bottom: 15px;
|
||
color: #606266;
|
||
}
|
||
|
||
.qrcode-image {
|
||
max-width: 200px;
|
||
max-height: 200px;
|
||
border: 1px solid #ebeef5;
|
||
}
|
||
}
|
||
|
||
.loading-wrapper, .success-wrapper, .error-wrapper {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10px;
|
||
|
||
.el-icon {
|
||
font-size: 48px;
|
||
|
||
&.is-loading {
|
||
animation: rotate 1s linear infinite;
|
||
}
|
||
}
|
||
|
||
span {
|
||
font-size: 16px;
|
||
}
|
||
}
|
||
|
||
.success-wrapper .el-icon {
|
||
color: #67c23a;
|
||
}
|
||
|
||
.error-wrapper .el-icon {
|
||
color: #f56c6c;
|
||
}
|
||
}
|
||
}
|
||
</style> |