This commit is contained in:
Your Name
2026-07-27 15:20:15 +08:00
parent 4970d8f8d3
commit 8ba13a8ff9
10 changed files with 424 additions and 39 deletions
+1 -1
View File
@@ -63,7 +63,7 @@ const handleLogout = () => {
</a-menu-item>
<a-menu-item key="/accounts">
<template #icon><UserOutlined /></template>
<span>账号管理</span>
<span>{{ auth.isAdmin ? '账号管理' : '我的账号' }}</span>
</a-menu-item>
<a-menu-item key="/messages">
<template #icon><MessageOutlined /></template>
+23 -9
View File
@@ -1,6 +1,6 @@
<script setup>
import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
import { useRouter } from 'vue-router'
import { useRoute, useRouter } from 'vue-router'
import api from '../api'
import { message } from 'ant-design-vue'
import { useAuthStore } from '../stores/auth'
@@ -39,6 +39,7 @@ import {
import { useIsMobile } from '../composables/useIsMobile'
const auth = useAuthStore()
const route = useRoute()
const router = useRouter()
const isMobile = useIsMobile()
const profileModalWidth = computed(() => (isMobile.value ? 'calc(100vw - 32px)' : 860))
@@ -983,6 +984,10 @@ const goAccountRulesPage = (accountId) => {
}
const openAddModal = () => {
if (!auth.canWrite) {
message.warning('当前账号为只读角色,不能添加托管账号')
return
}
if (!canAddAccount.value) {
if (canPurchaseSlots.value) {
purchaseVisible.value = true
@@ -1705,13 +1710,21 @@ const clearCookie = async () => {
}
}
onMounted(() => {
auth.fetchMe()
fetchPaymentConfig()
fetchDeviceProfiles()
fetchAccounts()
fetchRules()
onMounted(async () => {
await Promise.all([
auth.fetchMe(),
fetchPaymentConfig(),
fetchDeviceProfiles(),
fetchAccounts(),
fetchRules(),
])
startReplyQueueSummaryPolling()
if (route.query.action === 'add') {
openAddModal()
const nextQuery = { ...route.query }
delete nextQuery.action
router.replace({ path: route.path, query: nextQuery })
}
// 不再定时轮询账号列表;进入页面加载一次,之后由各操作(启动/停止/登录等)
// 或手动点击「刷新」按钮按需刷新
})
@@ -1797,6 +1810,7 @@ onUnmounted(() => {
购买额度
</a-button>
<a-button
v-if="auth.canWrite"
type="primary"
class="gradient-btn"
:disabled="!canAddAccount && !canPurchaseSlots"
@@ -2052,7 +2066,7 @@ onUnmounted(() => {
<UserOutlined style="font-size: 4rem; color: var(--text-muted); margin-bottom: 16px;" />
<h3>暂无托管账号</h3>
<p style="color: var(--text-secondary); margin-bottom: 20px;">添加一个抖音账号开始自动化回复工作吧</p>
<a-button type="primary" class="gradient-btn" @click="openAddModal">
<a-button v-if="auth.canWrite" type="primary" class="gradient-btn" @click="openAddModal">
<template #icon><PlusOutlined /></template>
立即添加
</a-button>
@@ -2257,7 +2271,7 @@ onUnmounted(() => {
placeholder="0 或留空则继承系统默认"
/>
<div class="field-hint">
设置 N 秒后,同一账号的待回复会话会依次排队:第 1 条在 N 秒后发送,第 2 条在 2N 秒后发送,以此类推;各账号队列互不影响。0 或留空表示继承系统默认,当前生效 {{ editForm.reply_delay_effective }} 秒(0 表示不启用兜底排队、立即回复)。
设置 N 秒后,同一账号队列为空时,首条回复等待 0 秒并立即发送;后续待回复会话按 N 秒、2N 秒依次排队。各账号队列互不影响,实际发送仍受全局带宽队列保护。0 或留空表示继承系统默认,当前生效 {{ editForm.reply_delay_effective }} 秒(0 表示不启用兜底排队、收到消息后立即回复)。
</div>
</a-form-item>
</a-col>
+68 -12
View File
@@ -2,18 +2,25 @@
import { ref, onMounted, onUnmounted } from 'vue'
import api from '../api'
import MessageBubble from '../components/MessageBubble.vue'
import { useAuthStore } from '../stores/auth'
import {
UserOutlined,
MessageOutlined,
CheckCircleOutlined,
ClockCircleOutlined,
ArrowRightOutlined,
ThunderboltOutlined
ThunderboltOutlined,
SettingOutlined,
PlusOutlined
} from '@ant-design/icons-vue'
const auth = useAuthStore()
const stats = ref({
totalAccounts: 0,
activeAccounts: 0,
myAccounts: 0,
myActiveAccounts: 0,
totalMessages: 0,
repliedMessages: 0,
replyRate: '0%'
@@ -24,14 +31,15 @@ const loading = ref(true)
const fetchStats = async () => {
try {
const [accountsRes, statsRes] = await Promise.all([
api.get(`/accounts`),
const [accountStatsRes, statsRes] = await Promise.all([
api.get(`/dashboard/account-stats`),
api.get(`/logs/stats`)
])
const accounts = accountsRes.data
stats.value.totalAccounts = accounts.length
stats.value.activeAccounts = accounts.filter(a => a.status === 'online').length
stats.value.totalAccounts = accountStatsRes.data.total_accounts || 0
stats.value.activeAccounts = accountStatsRes.data.online_accounts || 0
stats.value.myAccounts = accountStatsRes.data.my_accounts || 0
stats.value.myActiveAccounts = accountStatsRes.data.my_online_accounts || 0
// 后端全量统计所有消息,不受列表条数限制
stats.value.totalMessages = statsRes.data.total || 0
@@ -84,8 +92,27 @@ onUnmounted(() => {
多账户自动回复RPA后台支持快捷扫码登录状态持久化保存以及自定义关键字规则精准答复
</p>
</div>
<div class="banner-icon">
<ThunderboltOutlined style="font-size: 4rem; color: #c084fc; opacity: 0.3;" />
<div class="banner-side">
<div v-if="auth.canWrite" class="banner-actions">
<router-link to="/accounts">
<a-button size="large">
<template #icon><UserOutlined /></template>
{{ auth.isAdmin ? '账号管理' : `我的账号(${stats.myAccounts}` }}
</a-button>
</router-link>
<router-link
v-if="auth.canWrite"
:to="{ path: '/accounts', query: { action: 'add' } }"
>
<a-button type="primary" size="large" class="gradient-btn">
<template #icon><PlusOutlined /></template>
{{ auth.isAdmin ? '添加账号' : '添加自己的账号' }}
</a-button>
</router-link>
</div>
<div class="banner-icon">
<ThunderboltOutlined style="font-size: 4rem; color: #c084fc; opacity: 0.3;" />
</div>
</div>
</div>
@@ -98,7 +125,7 @@ onUnmounted(() => {
<UserOutlined />
</div>
<div class="stat-info">
<span class="stat-label">托管账号</span>
<span class="stat-label">全平台托管账号</span>
<h2 class="stat-value">{{ stats.totalAccounts }}</h2>
</div>
</div>
@@ -111,7 +138,7 @@ onUnmounted(() => {
<CheckCircleOutlined />
</div>
<div class="stat-info">
<span class="stat-label">在线运行</span>
<span class="stat-label">全平台在线运行</span>
<h2 class="stat-value text-green">{{ stats.activeAccounts }}</h2>
</div>
</div>
@@ -199,8 +226,9 @@ onUnmounted(() => {
<div class="quick-actions-grid" style="margin-top: 20px;">
<router-link to="/accounts" class="quick-action-card">
<UserOutlined class="action-icon text-gradient" />
<span>账号配置</span>
<p>扫码登录并托管多个抖音账号</p>
<span>{{ auth.isAdmin ? '账号管理' : '我的账号' }}</span>
<p v-if="auth.isAdmin">管理全平台账号配置与运行状态</p>
<p v-else>仅查看和管理自己添加的账号当前 {{ stats.myAccounts }} </p>
</router-link>
<router-link to="/rules" class="quick-action-card">
@@ -225,6 +253,20 @@ onUnmounted(() => {
border-left: 4px solid var(--primary-color);
}
.banner-side {
display: flex;
align-items: center;
gap: 28px;
flex-shrink: 0;
}
.banner-actions {
display: flex;
gap: 12px;
flex-wrap: wrap;
justify-content: flex-end;
}
.stat-card {
display: flex;
align-items: center;
@@ -436,6 +478,20 @@ onUnmounted(() => {
display: none;
}
.banner-side,
.banner-actions {
width: 100%;
justify-content: flex-start;
}
.banner-actions > a {
flex: 1 1 180px;
}
.banner-actions :deep(.ant-btn) {
width: 100%;
}
.stat-card {
padding: 16px;
}
+1 -1
View File
@@ -740,7 +740,7 @@ onMounted(() => {
/>
<div class="field-hint">
账号未单独设置时使用此间隔同一账号的第 1 条待回复在 N 发送 2 条在 2N 秒后发送以此类推各账号队列互不影响设为 0 表示不启用兜底排队收到消息后立即回复
账号未单独设置时使用此间隔同一账号队列为空时首条回复等待 0 并立即发送后续待回复会话按 N 2N 秒依次排队各账号队列互不影响实际发送仍受全局带宽队列保护设为 0 表示不启用兜底排队收到消息后立即回复
</div>
</a-form-item>