2163 lines
76 KiB
Vue
2163 lines
76 KiB
Vue
<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> |