Files
autoAiWorkSys/admin/src/views/account/pla_account_detail.vue
张成 21fe005c19 1
2026-04-08 14:09:26 +08:00

2163 lines
76 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="account-detail-container">
<!-- 页面头部 -->
<div class="page-header">
<div class="header-left">
<Button type="text" @click="goBack" class="back-btn">
<Icon type="ios-arrow-back" size="20" />
返回
</Button>
<div class="header-title">
<h2>账号详情</h2>
</div>
</div>
</div>
<!-- 账号信息卡片 -->
<Card class="account-info-card" :bordered="false">
<div class="account-info">
<div class="account-header">
<div class="account-avatar">
<Icon type="ios-person" size="40" color="#2d8cf0" />
</div>
<div class="account-basic">
<h3 class="account-name">{{ accountInfo.name }}</h3>
<div class="account-meta">
<Tag :color="getPlatformColor(accountInfo.platform_type)">
{{ getPlatformText(accountInfo.platform_type) }}
</Tag>
<Tag :color="accountInfo.is_online ? 'success' : 'default'">
{{ accountInfo.is_online ? '在线' : '离线' }}
</Tag>
<Tag :color="accountInfo.is_logged_in ? 'success' : 'warning'">
{{ accountInfo.is_logged_in ? '已登录' : '未登录' }}
</Tag>
</div>
</div>
<div class="account-actions">
<Dropdown @on-click="handleRunAction">
<Button type="primary">
运行
<Icon type="ios-arrow-down"></Icon>
</Button>
<DropdownMenu slot="list">
<DropdownItem v-for="action in actionMenuList" :key="action.value" :name="action.value">
<Icon :type="action.icon"></Icon>
{{ action.label }}
</DropdownItem>
</DropdownMenu>
</Dropdown>
</div>
</div>
<div class="account-details">
<Row :gutter="16">
<Col span="8">
<div class="detail-item">
<span class="label">设备SN码</span>
<span class="value">{{ accountInfo.sn_code }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">登录名</span>
<span class="value">{{ accountInfo.login_name }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">密码</span>
<span class="value">{{ accountInfo.pwd ? '******' : '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">搜索关键词</span>
<span class="value">{{ accountInfo.keyword || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">创建时间</span>
<span class="value">{{ formatDateTime(accountInfo.create_time) }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">最后更新</span>
<span class="value">{{ formatDateTime(accountInfo.last_modify_time) || '-' }}</span>
</div>
</Col>
</Row>
</div>
</div>
</Card>
<!-- 授权信息卡片 -->
<Card class="config-card" title="授权信息" :bordered="false" style="margin-bottom: 16px;">
<Row :gutter="16">
<Col span="8">
<div class="detail-item">
<span class="label">授权日期</span>
<span class="value">{{ formatDateTime(accountInfo.authorization_date) || '未授权' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">授权天数</span>
<span class="value">{{ accountInfo.authorization_days || 0 }} </span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">剩余天数</span>
<span class="value">
<Tag :color="getRemainingDaysColor(accountInfo.remaining_days)">
{{ accountInfo.remaining_days || 0 }}
</Tag>
</span>
</div>
</Col>
</Row>
<Row :gutter="16" style="margin-top: 12px;">
<Col span="8">
<div class="detail-item">
<span class="label">过期时间</span>
<span class="value" :class="{ 'text-danger': isExpired(accountInfo) }">
{{ getExpireDate(accountInfo) }}
</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">授权状态</span>
<span class="value">
<Tag :color="getAuthorizationStatusColor(accountInfo)">
{{ getAuthorizationStatus(accountInfo) }}
</Tag>
</span>
</div>
</Col>
</Row>
</Card>
<!-- 配置信息卡片 -->
<Card class="config-card" title="基础配置" :bordered="false" style="margin-bottom: 16px;">
<Row :gutter="16">
<Col span="8">
<div class="detail-item">
<span class="label">启用状态</span>
<span class="value">
<Tag :color="accountInfo.is_enabled === 1 ? 'success' : 'default'">
{{ accountInfo.is_enabled === 1 ? '启用' : '禁用' }}
</Tag>
</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">职位类型</span>
<span class="value">{{ getJobTypeName(accountInfo.job_type_id) || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">用户地址</span>
<span class="value">{{ accountInfo.user_address || '-' }}</span>
</div>
</Col>
</Row>
</Card>
<!-- 排序优先级配置 -->
<Card class="config-card" title="排序优先级配置" :bordered="false" style="margin-bottom: 16px;">
<div v-if="priorityList.length > 0" class="priority-display">
<div v-for="item in priorityList" :key="item.key" class="priority-item-display">
<span class="priority-label">{{ getPriorityLabel(item.key) }}</span>
<span class="priority-value">{{ item.weight }}%</span>
</div>
<div class="priority-total-display">
<span>总权重<strong :class="{ 'weight-warning': totalWeight !== 100 }">{{ totalWeight
}}%</strong></span>
</div>
</div>
<div v-else class="empty-config">暂无配置</div>
</Card>
<!-- 自动投递配置 -->
<Card class="config-card" title="自动投递配置" :bordered="false" style="margin-bottom: 16px;">
<Row :gutter="16">
<Col span="8">
<div class="detail-item">
<span class="label">自动投递</span>
<span class="value">
<Tag :color="deliverConfig.auto_deliver ? 'success' : 'default'">
{{ deliverConfig.auto_deliver ? '开启' : '关闭' }}
</Tag>
</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">投递间隔(分钟)</span>
<span class="value">{{ deliverConfig.deliver_interval || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">最低薪资()</span>
<span class="value">{{ deliverConfig.min_salary || deliverConfig.min_salary === 0 ?
deliverConfig.min_salary : '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">最高薪资()</span>
<span class="value">{{ deliverConfig.max_salary || deliverConfig.max_salary === 0 ?
deliverConfig.max_salary : '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">滚动页数</span>
<span class="value">{{ deliverConfig.page_count || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">每次最多投递数</span>
<span class="value">{{ deliverConfig.max_deliver || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">同公司重复投递间隔()</span>
<span class="value">{{ deliverConfig.repeat_deliver_days != null ? deliverConfig.repeat_deliver_days : '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">过滤关键词</span>
<span class="value">{{ deliverConfig.filter_keywords || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">排除关键词</span>
<span class="value">{{ deliverConfig.exclude_keywords || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">投递开始时间</span>
<span class="value">{{ deliverConfig.deliver_start_time || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">投递结束时间</span>
<span class="value">{{ deliverConfig.deliver_end_time || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">是否仅工作日</span>
<span class="value">
<Tag :color="deliverConfig.deliver_workdays_only === 1 ? 'success' : 'default'">
{{ deliverConfig.deliver_workdays_only === 1 ? '仅工作日' : '包含周末' }}
</Tag>
</span>
</div>
</Col>
</Row>
</Card>
<!-- 自动沟通配置 -->
<Card class="config-card" title="自动沟通配置" :bordered="false" style="margin-bottom: 16px;">
<Row :gutter="16">
<Col span="8">
<div class="detail-item">
<span class="label">自动沟通</span>
<span class="value">
<Tag :color="chatConfig.auto_chat === 1 ? 'success' : 'default'">
{{ chatConfig.auto_chat === 1 ? '开启' : '关闭' }}
</Tag>
</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">沟通间隔(分钟)</span>
<span class="value">{{ chatConfig.chat_interval || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">是否沟通外包岗位</span>
<span class="value">
<Tag :color="chatConfig.is_chat_outsourcing === 1 ? 'success' : 'default'">
{{ chatConfig.is_chat_outsourcing === 1 ? '是' : '否' }}
</Tag>
</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">沟通开始时间</span>
<span class="value">{{ chatConfig.chat_start_time || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">沟通结束时间</span>
<span class="value">{{ chatConfig.chat_end_time || '-' }}</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">是否仅工作日</span>
<span class="value">
<Tag :color="chatConfig.chat_workdays_only === 1 ? 'success' : 'default'">
{{ chatConfig.chat_workdays_only === 1 ? '仅工作日' : '包含周末' }}
</Tag>
</span>
</div>
</Col>
</Row>
</Card>
<!-- 自动活跃配置 -->
<Card class="config-card" title="自动活跃配置" :bordered="false" style="margin-bottom: 16px;">
<Row :gutter="16">
<Col span="8">
<div class="detail-item">
<span class="label">自动活跃</span>
<span class="value">
<Tag :color="activeConfig.auto_active === 1 ? 'success' : 'default'">
{{ activeConfig.auto_active === 1 ? '开启' : '关闭' }}
</Tag>
</span>
</div>
</Col>
<Col span="8">
<div class="detail-item">
<span class="label">活跃间隔(分钟)</span>
<span class="value">{{ activeConfig.active_interval || '-' }}</span>
</div>
</Col>
<Col span="24">
<div class="detail-item">
<span class="label">活跃动作配置</span>
<div class="value code-block-readonly" style="margin-top: 8px;">
<pre>{{ formatJSON(activeConfig.active_actions_json) }}</pre>
</div>
</div>
</Col>
</Row>
</Card>
<!-- Tab 切换区域 -->
<Card class="tabs-card" :bordered="false">
<Tabs v-model="activeTab" @on-click="handleTabChange">
<div slot="extra" class="tabs-extra">
<Button type="primary" icon="ios-refresh" @click="handleRefresh">刷新</Button>
</div>
<TabPane name="tasks" label="任务列表">
<div class="tab-content">
<div class="tab-body">
<Table :columns="taskColumns" :data="tasksData" :loading="tasksLoading" border>
</Table>
<Page :current="tasksPageOption.page" :total="tasksPageOption.total"
:page-size="tasksPageOption.pageSize" show-total show-elevator show-sizer
@on-change="queryTasks" @on-page-size-change="handleTasksPageSizeChange"
style="margin-top: 16px; text-align: right;">
</Page>
</div>
</div>
</TabPane>
<TabPane name="commands" label="指令列表">
<div class="tab-content">
<div class="tab-body">
<Table :columns="commandColumns" :data="commandsData" :loading="commandsLoading" border>
</Table>
<Page :current="commandsPageOption.page" :total="commandsPageOption.total"
:page-size="commandsPageOption.pageSize" show-total show-elevator show-sizer
@on-change="queryCommands" @on-page-size-change="handleCommandsPageSizeChange"
style="margin-top: 16px; text-align: right;">
</Page>
</div>
</div>
</TabPane>
</Tabs>
</Card>
<!-- 指令详情弹窗 -->
<Modal v-model="commandDetailVisible" title="指令详情" width="60%" :mask-closable="false"
@on-cancel="closeCommandDetail">
<div v-if="currentCommandDetail" class="command-detail-content">
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<span class="label">指令ID</span>
<span class="value">{{ currentCommandDetail.id }}</span>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<span class="label">序号</span>
<span class="value">{{ currentCommandDetail.sequence }}</span>
</div>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<span class="label">指令类型</span>
<Tag :color="getCommandTypeColor(currentCommandDetail.command_type)">
{{ currentCommandDetail.command_type }}
</Tag>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<span class="label">指令名称</span>
<span class="value">{{ currentCommandDetail.command_name }}</span>
</div>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<span class="label">状态</span>
<Tag :color="getCommandStatusColor(currentCommandDetail.status)">
{{ getCommandStatusText(currentCommandDetail.status) }}
</Tag>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<span class="label">执行时长</span>
<span class="value">{{ getExecutionTime(currentCommandDetail.duration) }}</span>
</div>
</Col>
</Row>
<Row :gutter="16">
<Col span="12">
<div class="detail-item">
<span class="label">重试次数</span>
<span class="value">{{ currentCommandDetail.retry_count || 0 }}</span>
</div>
</Col>
<Col span="12">
<div class="detail-item">
<span class="label">创建时间</span>
<span class="value">{{ formatDateTime(currentCommandDetail.create_time) }}</span>
</div>
</Col>
</Row>
<!-- 指令参数 -->
<div v-if="currentCommandDetail.parameters" class="detail-section">
<h4 class="section-title">指令参数</h4>
<div class="code-block ">
<pre>{{ formatJSON(currentCommandDetail.parameters) }}</pre>
</div>
</div>
<!-- 执行结果 -->
<div v-if="currentCommandDetail.result" class="detail-section">
<h4 class="section-title">执行结果</h4>
<div class="code-block" style="min-height: 400px;">
<pre>{{ formatJSON(currentCommandDetail.result) }}</pre>
</div>
</div>
<!-- 错误信息 -->
<div v-if="currentCommandDetail.error_message" class="detail-section">
<h4 class="section-title error-title">错误信息</h4>
<div class="error-block">
<pre>{{ currentCommandDetail.error_message }}</pre>
</div>
</div>
<!-- 截图信息 -->
<div v-if="currentCommandDetail.screenshot_path" class="detail-section">
<h4 class="section-title">执行截图</h4>
<div class="screenshot-block">
<img :src="currentCommandDetail.screenshot_path" alt="执行截图" class="screenshot-img"
@error="handleImageError" />
</div>
</div>
</div>
<div slot="footer">
<Button @click="closeCommandDetail">关闭</Button>
<Button v-if="currentCommandDetail && currentCommandDetail.status === 'failed'" type="primary"
:loading="retryCommandLoading" @click="retryCommand(currentCommandDetail)">
重试
</Button>
</div>
</Modal>
<!-- 二维码弹窗 -->
<Modal v-model="qrCodeVisible" title="登录二维码" width="500" :mask-closable="false" @on-cancel="closeQrCode">
<div class="qr-code-content">
<div v-if="qrCodeLoading" class="qr-code-loading">
<Icon type="ios-loading" size="40" class="loading-icon" />
<p>正在获取二维码...</p>
</div>
<div v-else-if="qrCodeError" class="qr-code-error">
<Icon type="ios-close-circle" size="40" color="#ed4014" />
<p>{{ qrCodeError }}</p>
</div>
<div v-else-if="qrCodeData" class="qr-code-display">
<div v-if="qrCodeData.qrCode || qrCodeData.qr_code_url || qrCodeData.oos_url || qrCodeData.image || qrCodeData.data"
class="qr-code-image">
<img :src="qrCodeData.qrCode || qrCodeData.qr_code_url || qrCodeData.oos_url || qrCodeData.image || qrCodeData.data"
alt="登录二维码" @error="handleQrCodeImageError" />
</div>
<div v-else class="qr-code-text">
<pre>{{ formatJSON(qrCodeData) }}</pre>
</div>
<div class="qr-code-tip">
<p>请使用手机扫描二维码登录</p>
<p v-if="qrCodeData.qr_code_url || qrCodeData.oos_url || qrCodeData.url" class="qr-code-url">
{{ qrCodeData.qr_code_url || qrCodeData.oos_url || qrCodeData.url }}
</p>
</div>
</div>
</div>
<div slot="footer">
<Button @click="closeQrCode">关闭</Button>
<Button type="primary" @click="refreshQrCode" :loading="qrCodeLoading">刷新二维码</Button>
</div>
</Modal>
<!-- 简历详情弹窗 -->
<ResumeInfoDetail ref="resumeDetail" @on-close="handleResumeDetailClose" />
</div>
</template>
<script>
import plaAccountServer from '@/api/profile/pla_account_server.js'
import taskStatusServer from '@/api/task/task_status_server.js'
import jobTypesServer from '@/api/work/job_types_server.js'
import ResumeInfoDetail from './resume_info_detail.vue'
export default {
name: 'PlaAccountDetail',
components: {
ResumeInfoDetail
},
data() {
return {
accountInfo: {},
activeTab: 'tasks',
// 职位类型选项
jobTypeOptions: [],
// 配置数据
priorityList: [],
deliverConfig: {
auto_deliver: 0,
deliver_interval: 30,
min_salary: 0,
max_salary: 0,
page_count: 3,
max_deliver: 10,
repeat_deliver_days: 30,
filter_keywords: '',
exclude_keywords: ''
},
chatConfig: {
auto_chat: 0,
chat_interval: 30,
is_chat_outsourcing: 0,
chat_start_time: '',
chat_end_time: '',
chat_workdays_only: 1
},
activeConfig: {
auto_active: 0,
active_interval: 60,
active_actions_json: ''
},
// 运行操作菜单列表(统一使用下划线命名)
actionMenuList: [
{
value: 'get_login_qr_code',
label: '用户登录',
icon: 'ios-log-in',
commandType: 'get_login_qr_code',
commandName: '获取登录二维码'
},
{
value: 'open_bot_detection',
label: '打开测试页',
icon: 'ios-bug',
commandType: 'open_bot_detection',
commandName: '打开测试页'
},
{
value: 'get_user_info',
label: '获取用户信息',
icon: 'ios-person',
commandType: 'get_user_info',
commandName: '获取用户信息'
},
{
value: 'get_online_resume',
label: '获取用户简历',
icon: 'ios-document',
commandType: 'get_online_resume',
commandName: '获取在线简历'
},
{
value: 'parse_and_view_resume',
label: '解析简历',
icon: 'ios-paper',
isCustomAction: true
},
{
value: 'search_jobs',
label: '搜索岗位',
icon: 'ios-search',
commandType: 'search_jobs',
commandName: '搜索岗位'
},
{
value: 'get_job_list',
label: '获取岗位列表',
icon: 'ios-list-box',
commandType: 'get_job_list',
commandName: '获取岗位列表'
},
{
value: 'get_chat_list',
label: '获取聊天列表',
icon: 'ios-chatbubbles',
commandType: 'get_chat_list',
commandName: '获取聊天列表'
}
],
// 任务列表相关
tasksLoading: false,
tasksData: [],
tasksPageOption: {
page: 1,
pageSize: 10,
total: 0
},
taskColumns: [
{ title: '任务ID', key: 'id', minWidth: 100 },
{
title: '任务类型',
key: 'taskType',
minWidth: 120,
render: (h, params) => {
const typeMap = {
'search': { text: '搜索', color: 'blue' },
'apply': { text: '申请', color: 'green' },
'chat': { text: '聊天', color: 'purple' }
}
const type = typeMap[params.row.taskType] || { text: params.row.taskType, color: 'default' }
return h('Tag', { props: { color: type.color } }, type.text)
}
},
{ title: '任务名称', key: 'taskName', minWidth: 200 },
{
title: '状态',
key: 'status',
minWidth: 100,
render: (h, params) => {
const statusMap = {
'pending': { text: '待执行', color: 'default' },
'running': { text: '执行中', color: 'blue' },
'completed': { text: '已完成', color: 'success' },
'success': { text: '成功', color: 'success' },
'failed': { text: '失败', color: 'error' },
'cancelled': { text: '已取消', color: 'warning' }
}
const status = statusMap[params.row.status] || { text: params.row.status, color: 'default' }
return h('Tag', { props: { color: status.color } }, status.text)
}
},
{
title: '创建时间',
key: 'create_time',
minWidth: 150,
render: (h, params) => {
return h('span', this.formatDateTime(params.row.create_time))
}
},
{
title: '结束时间',
key: 'endTime',
minWidth: 150,
render: (h, params) => {
return h('span', this.formatDateTime(params.row.endTime) || '-')
}
},
{
title: '操作',
key: 'action',
width: 150,
render: (h, params) => {
const btns = []
btns.push({
title: '重试',
type: 'primary',
click: () => this.retryTask(params.row)
})
if (params.row.status === 'pending' || params.row.status === 'running') {
btns.push({
title: '取消',
type: 'warning',
click: () => this.cancelTask(params.row)
})
}
return h('div', btns.map(btn =>
h('Button', {
props: {
type: btn.type,
size: 'small'
},
style: { marginRight: '5px' },
on: {
click: btn.click
}
}, btn.title)
))
}
}
],
// 指令列表相关
commandsLoading: false,
commandsData: [],
commandsPageOption: {
page: 1,
pageSize: 10,
total: 0
},
// 指令详情弹窗
commandDetailVisible: false,
retryCommandLoading: false,
currentCommandDetail: null,
// 二维码弹窗
qrCodeVisible: false,
qrCodeLoading: false,
qrCodeError: '',
qrCodeData: null,
commandColumns: [
{ title: 'ID', key: 'id' },
{ title: '序号', key: 'sequence' },
{
title: '指令类型',
key: 'command_type',
render: (h, params) => {
const typeMap = {
'browser': 'blue',
'search': 'cyan',
'fill': 'geekblue',
'click': 'purple',
'wait': 'orange',
'screenshot': 'magenta',
'extract': 'green'
}
return h('Tag', {
props: { color: typeMap[params.row.command_type] || 'default' }
}, params.row.command_type)
}
},
{ title: '指令名称', key: 'command_name' },
{
title: '状态',
key: 'status',
render: (h, params) => {
const statusMap = {
'pending': { text: '待执行', color: 'default' },
'running': { text: '执行中', color: 'blue' },
'completed': { text: '已完成', color: 'success' },
'failed': { text: '失败', color: 'error' },
'cancelled': { text: '已取消', color: 'warning' }
}
const status = statusMap[params.row.status] || { text: params.row.status, color: 'default' }
return h('Tag', {
props: { color: status.color }
}, status.text)
}
},
{
title: '执行时长(s)',
key: 'duration',
render: (h, params) => {
const duration = params.row.duration || 0
const seconds = Math.round(duration / 1000)
return h('span', `${seconds}`)
}
},
{ title: '重试次数', key: 'retry_count' },
{
title: '创建时间',
key: 'create_time',
minWidth: 150,
render: (h, params) => {
return h('span', this.formatDateTime(params.row.create_time))
}
},
{
title: '操作',
key: 'action',
width: 180,
render: (h, params) => {
const btns = []
// 详情按钮
btns.push({
title: '详情',
type: 'primary',
click: () => this.showCommandDetail(params.row)
})
// 重试按钮(只在失败状态时显示)
btns.push({
title: '重试',
type: 'warning',
click: () => this.retryCommand(params.row)
})
return h('div', btns.map(btn =>
h('Button', {
props: {
type: btn.type,
size: 'small'
},
style: { marginRight: '5px' },
on: {
click: btn.click
}
}, btn.title)
))
}
}
]
}
},
mounted() {
this.loadJobTypes()
this.loadAccountInfo()
this.queryTasks(1)
this.queryCommands(1)
},
computed: {
// 从路由参数获取账号ID
accountId() {
return this.$route.query.id
},
// 计算总权重
totalWeight() {
return this.priorityList.reduce((sum, item) => sum + (Number(item.weight) || 0), 0)
}
},
methods: {
// 返回上一页
goBack() {
this.$router.go(-1)
},
// 加载账号信息
async loadAccountInfo() {
try {
const res = await plaAccountServer.getById(this.accountId)
this.accountInfo = res.data || {}
// 解析配置数据
this.parseConfigData(this.accountInfo)
} catch (error) {
console.error('加载账号信息失败:', error)
this.$Message.error('加载账号信息失败')
this.accountInfo = {}
}
},
// 解析配置数据
parseConfigData(accountInfo) {
// 解析排序优先级配置
if (accountInfo.is_salary_priority) {
const priorityConfig = typeof accountInfo.is_salary_priority === 'string'
? JSON.parse(accountInfo.is_salary_priority)
: accountInfo.is_salary_priority
if (Array.isArray(priorityConfig)) {
this.priorityList = priorityConfig.map(item => ({
key: item.key,
weight: item.weight || 0
}))
} else {
this.priorityList = []
}
} else {
this.priorityList = []
}
// 解析自动投递配置
if (accountInfo.deliver_config) {
const deliverConfig = typeof accountInfo.deliver_config === 'string'
? JSON.parse(accountInfo.deliver_config)
: accountInfo.deliver_config
this.deliverConfig = {
auto_deliver: accountInfo.auto_deliver !== undefined ? accountInfo.auto_deliver : 0,
deliver_interval: deliverConfig.deliver_interval || 30,
min_salary: deliverConfig.min_salary || 0,
max_salary: deliverConfig.max_salary || 0,
page_count: deliverConfig.page_count || 3,
max_deliver: deliverConfig.max_deliver || 10,
repeat_deliver_days: deliverConfig.repeat_deliver_days != null ? deliverConfig.repeat_deliver_days : 30,
filter_keywords: Array.isArray(deliverConfig.filter_keywords)
? deliverConfig.filter_keywords.join(',')
: (deliverConfig.filter_keywords || ''),
exclude_keywords: Array.isArray(deliverConfig.exclude_keywords)
? deliverConfig.exclude_keywords.join(',')
: (deliverConfig.exclude_keywords || ''),
deliver_start_time: deliverConfig.time_range?.start_time || '09:00',
deliver_end_time: deliverConfig.time_range?.end_time || '18:00',
deliver_workdays_only: deliverConfig.time_range?.workdays_only !== undefined ? deliverConfig.time_range.workdays_only : 1
}
} else {
this.deliverConfig = {
auto_deliver: accountInfo.auto_deliver !== undefined ? accountInfo.auto_deliver : 0,
deliver_interval: 30,
min_salary: 0,
max_salary: 0,
page_count: 3,
max_deliver: 10,
repeat_deliver_days: 30,
filter_keywords: '',
exclude_keywords: '',
deliver_start_time: '09:00',
deliver_end_time: '18:00',
deliver_workdays_only: 1
}
}
// 解析自动沟通配置
if (accountInfo.chat_strategy) {
const chatStrategy = typeof accountInfo.chat_strategy === 'string'
? JSON.parse(accountInfo.chat_strategy)
: accountInfo.chat_strategy
this.chatConfig = {
auto_chat: accountInfo.auto_chat !== undefined ? accountInfo.auto_chat : 0,
chat_interval: chatStrategy.chat_interval || 30,
is_chat_outsourcing: chatStrategy.is_chat_outsourcing !== undefined ? chatStrategy.is_chat_outsourcing : 0,
chat_start_time: chatStrategy.time_range?.start_time || '09:00',
chat_end_time: chatStrategy.time_range?.end_time || '18:00',
chat_workdays_only: chatStrategy.time_range?.workdays_only !== undefined ? chatStrategy.time_range.workdays_only : 1
}
} else {
this.chatConfig = {
auto_chat: accountInfo.auto_chat !== undefined ? accountInfo.auto_chat : 0,
chat_interval: 30,
is_chat_outsourcing: 0,
chat_start_time: '09:00',
chat_end_time: '18:00',
chat_workdays_only: 1
}
}
// 解析自动活跃配置
if (accountInfo.active_actions) {
const activeActions = typeof accountInfo.active_actions === 'string'
? JSON.parse(accountInfo.active_actions)
: accountInfo.active_actions
this.activeConfig = {
auto_active: accountInfo.auto_active !== undefined ? accountInfo.auto_active : 0,
active_interval: activeActions.active_interval || 60,
active_actions_json: activeActions.actions || []
}
} else {
this.activeConfig = {
auto_active: accountInfo.auto_active !== undefined ? accountInfo.auto_active : 0,
active_interval: 60,
active_actions_json: []
}
}
},
// 加载职位类型
async loadJobTypes() {
try {
const res = await jobTypesServer.getAll()
if (res.code === 0 && res.data) {
this.jobTypeOptions = res.data.map(item => ({
value: item.id,
label: item.name
}))
}
} catch (err) {
console.error('加载职位类型失败:', err)
}
},
// 获取职位类型名称
getJobTypeName(jobTypeId) {
if (!jobTypeId) return ''
const jobType = this.jobTypeOptions.find(item => item.value === jobTypeId)
return jobType ? jobType.label : ''
},
// 获取优先级标签
getPriorityLabel(key) {
const labelMap = {
'distance': '距离',
'salary': '薪资',
'work_years': '工作年限',
'education': '学历'
}
return labelMap[key] || key
},
// 查询任务列表
async queryTasks(page) {
this.tasksLoading = true
try {
this.tasksPageOption.page = page
const param = {
seachOption: {
account_id: this.accountId
},
pageOption: this.tasksPageOption
}
const res = await plaAccountServer.getTasks(this.accountId, param)
console.log('res', res);
this.tasksData = res.data.rows || []
this.tasksPageOption.total = res.data.count || 0
} catch (error) {
this.$Message.error('加载任务列表失败')
this.tasksData = []
this.tasksPageOption.total = 0
} finally {
this.tasksLoading = false
}
},
// 查询指令列表
async queryCommands(page) {
this.commandsLoading = true
try {
this.commandsPageOption.page = page
const param = {
seachOption: {
account_id: this.accountId
},
pageOption: this.commandsPageOption
}
const res = await plaAccountServer.getCommands(this.accountId, param)
this.commandsData = res.data.rows || []
this.commandsPageOption.total = res.data.count || 0
} catch (error) {
this.$Message.error('加载指令列表失败')
this.commandsData = []
this.commandsPageOption.total = 0
} finally {
this.commandsLoading = false
}
},
// 重试任务
async retryTask(task) {
try {
await taskStatusServer.retry(task)
this.$Message.success('重试任务成功')
this.queryTasks(this.tasksPageOption.page)
} catch (error) {
console.error('重试任务失败:', error)
this.$Message.error('重试任务失败')
}
},
// 重试指令(指令列表与指令详情弹窗共用,成功后刷新列表与详情)
async retryCommand(command) {
const isFromDetail = this.commandDetailVisible && this.currentCommandDetail && this.currentCommandDetail.id === command.id
if (!isFromDetail) {
this.$Modal.confirm({
title: '确认重试',
content: `确定要重试指令"${command.command_name}"吗?`,
onOk: async () => {
await this.doRetryCommand(command)
}
})
return
}
await this.doRetryCommand(command)
},
async doRetryCommand(command) {
this.retryCommandLoading = true
try {
await plaAccountServer.retryCommand(command.id)
this.$Message.success('重试指令成功')
this.queryCommands(this.commandsPageOption.page)
if (this.commandDetailVisible && this.currentCommandDetail && this.currentCommandDetail.id === command.id) {
const res = await plaAccountServer.getCommandDetail(this.accountId, command.id)
this.currentCommandDetail = res.data || {}
}
} catch (error) {
console.error('重试指令失败:', error)
this.$Message.error(error.message || '重试指令失败')
} finally {
this.retryCommandLoading = false
}
},
// 取消任务
async cancelTask(task) {
this.$Modal.confirm({
title: '确认取消',
content: '确定要取消这个任务吗?',
onOk: async () => {
try {
await taskStatusServer.cancel(task)
this.$Message.success('取消任务成功')
this.queryTasks(this.tasksPageOption.page)
} catch (error) {
console.error('取消任务失败:', error)
this.$Message.error('取消任务失败')
}
}
})
},
// 刷新任务列表
refreshTasks() {
this.queryTasks(this.tasksPageOption.page)
},
// 刷新指令列表
refreshCommands() {
this.queryCommands(this.commandsPageOption.page)
},
// Tab切换
handleTabChange(name) {
this.activeTab = name
if (name === 'tasks') {
this.queryTasks(this.tasksPageOption.page)
} else if (name === 'commands') {
this.queryCommands(this.commandsPageOption.page)
}
},
// 任务列表分页大小改变
handleTasksPageSizeChange(pageSize) {
this.tasksPageOption.pageSize = pageSize
this.tasksPageOption.page = 1
this.queryTasks(1)
},
// 指令列表分页大小改变
handleCommandsPageSizeChange(pageSize) {
this.commandsPageOption.pageSize = pageSize
this.commandsPageOption.page = 1
this.queryCommands(1)
},
// 处理刷新
handleRefresh() {
if (this.activeTab === 'tasks') {
this.refreshTasks()
} else if (this.activeTab === 'commands') {
this.refreshCommands()
}
},
// 处理运行操作
async handleRunAction(action) {
// 如果是获取登录二维码,检查登录状态
if (action === 'get_login_qr_code') {
// 如果未登录,直接显示二维码
if (!this.accountInfo.is_logged_in) {
this.showQrCode()
return
}
// 如果已登录,显示确认框
this.$Modal.confirm({
title: '确认用户登录',
content: '该账号已登录,确定要重新获取登录二维码吗?',
onOk: () => {
this.showQrCode()
}
})
return
}
// 如果是解析简历的自定义操作
if (action === 'parse_and_view_resume') {
this.handleParseAndViewResume()
return
}
// 从菜单列表中查找对应的配置
const actionItem = this.actionMenuList.find(item => item.value === action)
if (!actionItem) {
this.$Message.error('未知的操作类型')
return
}
const config = {
name: actionItem.label,
commandType: actionItem.commandType,
commandName: actionItem.commandName
}
this.$Modal.confirm({
title: `确认${config.name}`,
content: `确定要执行"${config.name}"操作吗?`,
onOk: async () => {
try {
await plaAccountServer.runCommand({
id: this.accountId,
commandType: config.commandType,
commandName: config.commandName
})
this.$Message.success(`${config.name}指令执行成功`)
// 刷新指令列表
setTimeout(() => {
this.queryCommands(this.commandsPageOption.page)
}, 1000)
} catch (error) {
console.error(`${config.name}失败:`, error)
this.$Message.error(`${config.name}失败: ${error.message || '执行失败'}`)
}
}
})
},
// 处理解析并查看简历
async handleParseAndViewResume() {
this.$Modal.confirm({
title: '确认解析简历',
content: '确定要解析该账号的在线简历吗系统会自动获取简历并进行AI分析。',
onOk: async () => {
const loadingMsg = this.$Message.loading({
content: '正在解析简历,请稍候...',
duration: 0
})
try {
// 调用后端接口解析简历
const res = await plaAccountServer.parseResume(this.accountId)
loadingMsg()
this.$Message.success('简历解析成功')
// 打开简历详情弹窗
if (res.data && res.data.resumeId) {
this.$refs.resumeDetail.show(res.data.resumeId)
}
} catch (error) {
loadingMsg()
console.error('解析简历失败:', error)
this.$Message.error('解析简历失败: ' + (error.message || '请稍后重试'))
}
}
})
},
// 关闭简历详情弹窗
handleResumeDetailClose() {
// 可以在这里添加关闭后的逻辑
},
// 显示二维码
async showQrCode() {
this.qrCodeVisible = true
this.qrCodeLoading = true
this.qrCodeError = ''
this.qrCodeData = null
try {
// 执行获取二维码指令
const res = await plaAccountServer.runCommand({
id: this.accountId,
commandType: 'get_login_qr_code',
commandName: '获取登录二维码'
})
// 从响应中直接获取二维码数据
if (res.data && res.data.success && res.data.result) {
const result = res.data.result
// 支持多种数据结构
if (result.qr_code_url) {
this.qrCodeData = {
qrCode: result.qr_code_url,
oos_url: result.oos_url,
url: result.qr_code_url
}
} else if (result.oos_url) {
this.qrCodeData = {
qrCode: result.oos_url,
oos_url: result.oos_url,
url: result.oos_url
}
} else if (result.data) {
// 如果 data 是对象
if (typeof result.data === 'object') {
this.qrCodeData = {
qrCode: result.data.qr_code_url || result.data.oos_url || result.data.qrCode || result.data.image,
oos_url: result.data.oos_url,
url: result.data.qr_code_url || result.data.oos_url,
...result.data
}
} else {
// 如果 data 是字符串可能是图片URL
this.qrCodeData = {
qrCode: result.data,
data: result.data,
url: result.data
}
}
} else {
// 其他格式,直接使用整个结果
this.qrCodeData = result
}
this.qrCodeLoading = false
} else {
// 如果没有直接返回结果,轮询等待指令完成
await this.waitForQrCodeCommand()
}
} catch (error) {
console.error('获取二维码失败:', error)
this.qrCodeError = error.message || '获取二维码失败'
this.qrCodeLoading = false
}
},
// 轮询等待二维码指令完成
async waitForQrCodeCommand(maxAttempts = 30, interval = 1000) {
let attempts = 0
while (attempts < maxAttempts) {
try {
// 刷新指令列表
await this.queryCommands(this.commandsPageOption.page)
// 查找最新的获取登录二维码指令(按创建时间倒序)
const qrCodeCommands = this.commandsData.filter(cmd =>
cmd.command_name === '获取登录二维码'
)
if (qrCodeCommands.length > 0) {
const latestCommand = qrCodeCommands[0]
// 如果指令已完成
if (latestCommand.status === 'completed') {
await this.fetchQrCodeFromCommand(latestCommand.id)
return
}
// 如果指令失败
if (latestCommand.status === 'failed') {
this.qrCodeError = latestCommand.error_message || '获取二维码失败'
this.qrCodeLoading = false
return
}
// 如果指令还在执行中,继续等待
}
attempts++
await new Promise(resolve => setTimeout(resolve, interval))
} catch (error) {
console.error('等待二维码指令失败:', error)
this.qrCodeError = error.message || '等待二维码指令失败'
this.qrCodeLoading = false
return
}
}
// 超时
this.qrCodeError = '获取二维码超时,请稍后刷新'
this.qrCodeLoading = false
},
// 从指令详情获取二维码数据
async fetchQrCodeFromCommand(commandId) {
try {
// 获取指令详情
const res = await plaAccountServer.getCommandDetail(this.accountId, commandId)
const commandDetail = res.data
if (!commandDetail) {
this.qrCodeError = '未找到指令详情'
this.qrCodeLoading = false
return
}
// 从执行结果中提取二维码数据
if (commandDetail.result) {
const result = typeof commandDetail.result === 'string'
? JSON.parse(commandDetail.result)
: commandDetail.result
// 查找二维码数据(支持多种数据结构)
if (result.data) {
// 如果 data 是对象,可能包含二维码信息
if (typeof result.data === 'object') {
this.qrCodeData = result.data
} else {
// 如果 data 是字符串可能是图片URL
this.qrCodeData = { qrCode: result.data, data: result.data }
}
} else if (result.qrCode) {
this.qrCodeData = { qrCode: result.qrCode }
} else if (result.image) {
this.qrCodeData = { image: result.image }
} else if (result.qrcode) {
this.qrCodeData = { qrCode: result.qrcode }
} else if (typeof result === 'string') {
// 如果结果直接是字符串可能是base64或URL
this.qrCodeData = { qrCode: result, data: result }
} else {
this.qrCodeData = result
}
// 如果还没有找到二维码图片,尝试从结果中查找
if (!this.qrCodeData.qrCode && !this.qrCodeData.image && !this.qrCodeData.data) {
// 尝试查找所有可能的图片字段
const imageFields = ['qr_code', 'qrCode', 'qrcode', 'qr', 'code', 'img', 'image', 'url']
for (const field of imageFields) {
if (this.qrCodeData[field]) {
this.qrCodeData = { qrCode: this.qrCodeData[field] }
break
}
}
}
} else {
this.qrCodeError = '未找到二维码数据'
}
this.qrCodeLoading = false
} catch (error) {
console.error('获取二维码数据失败:', error)
this.qrCodeError = error.message || '获取二维码数据失败'
this.qrCodeLoading = false
}
},
// 刷新二维码
refreshQrCode() {
this.showQrCode()
},
// 关闭二维码弹窗
closeQrCode() {
this.qrCodeVisible = false
this.qrCodeLoading = false
this.qrCodeError = ''
this.qrCodeData = null
},
// 获取平台颜色
getPlatformColor(platformId) {
const colorMap = {
'1': 'blue', // Boss直聘
'2': 'green' // 猎聘
}
return colorMap[platformId] || 'default'
},
// 获取平台文本
getPlatformText(platformId) {
const textMap = {
'1': 'Boss直聘',
'2': '猎聘'
}
return textMap[platformId] || platformId
},
// 显示指令详情
async showCommandDetail(command) {
try {
const res = await plaAccountServer.getCommandDetail(this.accountId, command.id)
this.currentCommandDetail = res.data || command
this.commandDetailVisible = true
} catch (error) {
console.error('获取指令详情失败:', error)
this.$Message.error('获取指令详情失败')
// 如果接口不存在,直接使用当前指令数据
this.currentCommandDetail = command
this.commandDetailVisible = true
}
},
// 关闭指令详情弹窗
closeCommandDetail() {
this.commandDetailVisible = false
this.currentCommandDetail = null
},
// 获取指令类型颜色
getCommandTypeColor(type) {
const colorMap = {
'browser': 'blue',
'search': 'cyan',
'fill': 'geekblue',
'click': 'purple',
'wait': 'orange',
'screenshot': 'magenta',
'extract': 'green'
}
return colorMap[type] || 'default'
},
// 获取指令状态颜色
getCommandStatusColor(status) {
const colorMap = {
'pending': 'default',
'running': 'blue',
'completed': 'success',
'failed': 'error',
'cancelled': 'warning'
}
return colorMap[status] || 'default'
},
// 获取指令状态文本
getCommandStatusText(status) {
const textMap = {
'pending': '待执行',
'running': '执行中',
'completed': '已完成',
'failed': '失败',
'cancelled': '已取消'
}
return textMap[status] || status
},
// 获取执行时间
getExecutionTime(duration) {
if (!duration) return '-'
const seconds = Math.round(duration / 1000)
return `${seconds}`
},
// 格式化JSON
formatJSON(data) {
try {
return JSON.stringify(data, null, 2)
} catch (error) {
return data
}
},
// 处理图片加载错误
handleImageError(event) {
event.target.style.display = 'none'
this.$Message.warning('截图加载失败')
},
// 处理二维码图片加载错误
handleQrCodeImageError(event) {
event.target.style.display = 'none'
this.$Message.warning('二维码图片加载失败,请刷新重试')
},
// 获取剩余天数颜色
getRemainingDaysColor(remainingDays) {
if (!remainingDays || remainingDays <= 0) {
return 'error'
} else if (remainingDays <= 7) {
return 'warning'
} else {
return 'success'
}
},
// 获取过期时间
getExpireDate(accountInfo) {
if (!accountInfo.authorization_date || !accountInfo.authorization_days) {
return '未授权'
}
const authDate = new Date(accountInfo.authorization_date)
const expireDate = new Date(authDate.getTime() + accountInfo.authorization_days * 24 * 60 * 60 * 1000)
return this.formatDateTime(expireDate)
},
// 判断是否过期
isExpired(accountInfo) {
const remainingDays = accountInfo.remaining_days || 0
return remainingDays <= 0
},
// 获取授权状态
getAuthorizationStatus(accountInfo) {
if (!accountInfo.authorization_date || !accountInfo.authorization_days) {
return '未授权'
}
const remainingDays = accountInfo.remaining_days || 0
if (remainingDays <= 0) {
return '已过期'
} else if (remainingDays <= 7) {
return '即将过期'
} else {
return '正常'
}
},
// 获取授权状态颜色
getAuthorizationStatusColor(accountInfo) {
if (!accountInfo.authorization_date || !accountInfo.authorization_days) {
return 'default'
}
const remainingDays = accountInfo.remaining_days || 0
if (remainingDays <= 0) {
return 'error'
} else if (remainingDays <= 7) {
return 'warning'
} else {
return 'success'
}
},
// 格式化日期时间
formatDateTime(datetime) {
if (!datetime) return '-'
const date = new Date(datetime)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
const hours = String(date.getHours()).padStart(2, '0')
const minutes = String(date.getMinutes()).padStart(2, '0')
const seconds = String(date.getSeconds()).padStart(2, '0')
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
}
}
}
</script>
<style scoped>
.account-detail-container {
background: #f7f8fa;
min-height: 600px;
}
/* 页面头部 */
.page-header {
background: #fff;
padding: 16px 20px;
margin-bottom: 16px;
border-bottom: 1px solid #e8eaec;
display: flex;
align-items: center;
}
.tabs-card>>>.ivu-tabs-bar {
padding: 12px;
margin-bottom: 0px;
}
.header-left {
display: flex;
align-items: center;
}
.back-btn {
display: flex;
align-items: center;
padding: 8px 12px;
margin-right: 16px;
color: #2d8cf0;
font-size: 14px;
}
.back-btn:hover {
background: #f0f5ff;
}
.header-title h2 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #17233d;
}
/* 账号信息卡片 */
.account-info-card {
margin-bottom: 16px;
}
.account-info {
padding: 20px;
}
.account-header {
display: flex;
align-items: center;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #e8eaec;
}
.account-avatar {
margin-right: 16px;
display: flex;
align-items: center;
justify-content: center;
width: 60px;
height: 60px;
background: #f0f5ff;
border-radius: 50%;
}
.account-basic {
flex: 1;
}
.account-name {
margin: 0 0 8px 0;
font-size: 18px;
font-weight: 600;
color: #17233d;
}
.account-meta {
display: flex;
gap: 8px;
}
.account-actions {
margin-left: auto;
display: flex;
align-items: center;
}
.account-details .detail-item {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.account-details .label {
min-width: 100px;
color: #515a6e;
font-weight: 500;
}
.account-details .value {
color: #17233d;
word-break: break-all;
}
/* Tab卡片 */
.tabs-card {
background: #fff;
}
.tabs-card>>>.ivu-card-body {
padding: 0;
}
/* Tab内容 */
.tab-content {
display: flex;
flex-direction: column;
min-height: 500px;
}
.tab-body {
flex: 1;
padding: 20px;
overflow: visible;
min-height: 500px;
}
/* TabPane 最小高度 */
.tabs-card>>>.ivu-tabs-content {
min-height: 500px;
}
.tabs-card>>>.ivu-tabs-tabpane {
min-height: 500px;
}
/* Tab右侧按钮 */
.tabs-extra {
display: flex;
align-items: center;
padding-right: 16px;
}
/* 响应式设计 */
@media (max-width: 768px) {
.account-header {
flex-direction: column;
align-items: flex-start;
}
.account-actions {
margin-left: 0;
margin-top: 16px;
width: 100%;
}
.account-details .detail-item {
flex-direction: column;
align-items: flex-start;
}
.account-details .label {
margin-bottom: 4px;
}
}
/* 表格样式优化 */
.tab-body>>>.ivu-table {
font-size: 13px;
}
.tab-body>>>.ivu-table th {
background: #f8f8f9;
font-weight: 600;
}
.tab-body>>>.ivu-table-stripe .ivu-table-body tr:nth-child(2n) td {
background-color: #fafafa;
}
.tab-body>>>.ivu-table td {
padding: 12px 8px;
}
/* 加载状态 */
.ivu-spin-fix {
background: rgba(255, 255, 255, 0.8);
}
/* 指令详情弹窗样式 */
.command-detail-content {
max-height: 600px;
overflow-y: auto;
}
.command-detail-content .detail-item {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.command-detail-content .label {
min-width: 80px;
color: #515a6e;
font-weight: 500;
}
.command-detail-content .value {
color: #17233d;
word-break: break-all;
}
.detail-section {
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid #e8eaec;
}
.section-title {
margin: 0 0 12px 0;
font-size: 14px;
font-weight: 600;
color: #17233d;
}
.error-title {
color: #ed4014;
}
.code-block {
background: #f8f8f9;
border: 1px solid #e8eaec;
border-radius: 4px;
padding: 12px;
max-height: 200px;
overflow-y: auto;
}
.code-block pre {
margin: 0;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 12px;
line-height: 1.4;
color: #17233d;
white-space: pre-wrap;
word-break: break-all;
}
.error-block {
background: #fef0f0;
border: 1px solid #fbc4c4;
border-radius: 4px;
padding: 12px;
max-height: 150px;
overflow-y: auto;
}
.error-block pre {
margin: 0;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 12px;
line-height: 1.4;
color: #ed4014;
white-space: pre-wrap;
word-break: break-all;
}
.screenshot-block {
text-align: center;
padding: 12px;
background: #f8f8f9;
border: 1px solid #e8eaec;
border-radius: 4px;
}
.screenshot-img {
max-width: 100%;
max-height: 400px;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 弹窗内容滚动条样式 */
.command-detail-content::-webkit-scrollbar,
.code-block::-webkit-scrollbar,
.error-block::-webkit-scrollbar {
width: 6px;
height: 6px;
}
.command-detail-content::-webkit-scrollbar-track,
.code-block::-webkit-scrollbar-track,
.error-block::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 3px;
}
.command-detail-content::-webkit-scrollbar-thumb,
.code-block::-webkit-scrollbar-thumb,
.error-block::-webkit-scrollbar-thumb {
background: #c1c1c1;
border-radius: 3px;
}
.command-detail-content::-webkit-scrollbar-thumb:hover,
.code-block::-webkit-scrollbar-thumb:hover,
.error-block::-webkit-scrollbar-thumb:hover {
background: #a8a8a8;
}
/* 二维码弹窗样式 */
.qr-code-content {
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
}
.qr-code-loading {
text-align: center;
padding: 40px;
}
.loading-icon {
animation: spin 1s linear infinite;
color: #2d8cf0;
}
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.qr-code-loading p {
margin-top: 16px;
color: #515a6e;
font-size: 14px;
}
.qr-code-error {
text-align: center;
padding: 40px;
}
.qr-code-error p {
margin-top: 16px;
color: #ed4014;
font-size: 14px;
}
.qr-code-display {
width: 100%;
text-align: center;
}
.qr-code-image {
margin-bottom: 20px;
}
.qr-code-image img {
max-width: 100%;
max-height: 400px;
border: 1px solid #e8eaec;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.qr-code-text {
background: #f8f8f9;
border: 1px solid #e8eaec;
border-radius: 4px;
padding: 12px;
margin-bottom: 20px;
max-height: 200px;
overflow-y: auto;
}
.qr-code-text pre {
margin: 0;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 12px;
line-height: 1.4;
color: #17233d;
white-space: pre-wrap;
word-break: break-all;
}
.qr-code-tip {
margin-top: 20px;
padding-top: 20px;
border-top: 1px solid #e8eaec;
}
.qr-code-tip p {
margin: 8px 0;
color: #515a6e;
font-size: 14px;
}
.qr-code-url {
color: #2d8cf0;
word-break: break-all;
font-size: 12px;
}
/* 配置卡片样式 */
.config-card {
background: #fff;
}
.config-card .detail-item {
display: flex;
align-items: center;
margin-bottom: 12px;
}
.config-card .label {
min-width: 120px;
color: #515a6e;
font-weight: 500;
}
.config-card .value {
color: #17233d;
word-break: break-all;
flex: 1;
}
/* 排序优先级展示 */
.priority-display {
padding: 16px;
background: #f8f8f9;
border-radius: 4px;
}
.priority-item-display {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px;
margin-bottom: 12px;
background: #fff;
border-radius: 4px;
border: 1px solid #e8eaec;
}
.priority-item-display:last-child {
margin-bottom: 0;
}
.priority-item-display .priority-label {
font-weight: 500;
color: #515a6e;
min-width: 120px;
}
.priority-item-display .priority-value {
color: #2d8cf0;
font-weight: 500;
font-size: 14px;
}
.priority-total-display {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #e8eaec;
font-size: 14px;
}
.priority-total-display .weight-warning {
color: #ed4014;
}
.empty-config {
padding: 20px;
text-align: center;
color: #c5c8ce;
}
/* 只读代码块 */
.code-block-readonly {
background: #f8f8f9;
border: 1px solid #e8eaec;
border-radius: 4px;
padding: 12px;
max-height: 200px;
overflow-y: auto;
}
.code-block-readonly pre {
margin: 0;
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 12px;
line-height: 1.4;
color: #17233d;
white-space: pre-wrap;
word-break: break-all;
}
</style>