891 lines
39 KiB
Vue
891 lines
39 KiB
Vue
<template>
|
||
<div class="content-view">
|
||
<div class="table-head-tool">
|
||
<div>
|
||
<Button type="primary" @click="showAddWarp">新增账号</Button>
|
||
<Button type="success" @click="batchParseLocation" :loading="batchParseLoading"
|
||
class="ml10">批量解析位置</Button>
|
||
</div>
|
||
|
||
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
|
||
<FormItem :label-width="20" class="flex">
|
||
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
|
||
:placeholder="seachTypePlaceholder">
|
||
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
|
||
</Select>
|
||
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
|
||
placeholder="请输入关键字" @on-search="query(1)" />
|
||
</FormItem>
|
||
<FormItem label="平台">
|
||
<Select v-model="gridOption.param.seachOption.platform_type" style="width: 120px" clearable
|
||
@on-change="query(1)">
|
||
<Option value="boss">Boss直聘</Option>
|
||
<Option value="liepin">猎聘</Option>
|
||
</Select>
|
||
</FormItem>
|
||
<FormItem label="在线状态">
|
||
<Select v-model="gridOption.param.seachOption.is_online" style="width: 120px" clearable
|
||
@on-change="query(1)">
|
||
<Option :value="true">在线</Option>
|
||
<Option :value="false">离线</Option>
|
||
</Select>
|
||
</FormItem>
|
||
<FormItem>
|
||
<Button type="primary" @click="query(1)">查询</Button>
|
||
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
|
||
<Button type="default" @click="exportCsv">导出</Button>
|
||
</FormItem>
|
||
</Form>
|
||
</div>
|
||
<div class="table-body">
|
||
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
|
||
@changePage="query"></tables>
|
||
</div>
|
||
|
||
<!-- 新增弹窗(只包含必填字段) -->
|
||
<editModal ref="editModal" :columns="addColumns" :rules="gridOption.rules" @on-save="handleSaveSuccess">
|
||
</editModal>
|
||
|
||
<!-- 编辑组件(使用 FloatPanel,包含所有字段) -->
|
||
<PlaAccountEdit ref="accountEdit" @on-save="handleSaveSuccess" />
|
||
|
||
<!-- 授权设置弹窗 -->
|
||
<Modal
|
||
v-model="authorizationModal.visible"
|
||
title="设置授权"
|
||
:mask-closable="false"
|
||
width="600"
|
||
@on-ok="handleSaveAuthorization"
|
||
@on-cancel="handleCancelAuthorization"
|
||
>
|
||
<Form ref="authorizationForm" :model="authorizationModal.formData" :label-width="120">
|
||
<FormItem label="账号名称">
|
||
<Input v-model="authorizationModal.formData.accountName" disabled />
|
||
</FormItem>
|
||
<FormItem label="设备SN码">
|
||
<Input v-model="authorizationModal.formData.sn_code" disabled />
|
||
</FormItem>
|
||
<FormItem label="授权日期">
|
||
<DatePicker
|
||
v-model="authorizationModal.formData.authorization_date"
|
||
type="date"
|
||
placeholder="请选择授权日期(不选择则使用当前时间)"
|
||
style="width: 100%;"
|
||
format="yyyy-MM-dd"
|
||
/>
|
||
</FormItem>
|
||
<FormItem label="授权天数">
|
||
<div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap;">
|
||
<InputNumber
|
||
v-model="authorizationModal.formData.authorization_days"
|
||
:min="0"
|
||
placeholder="请输入授权天数"
|
||
style="width: 200px;"
|
||
/>
|
||
<div style="display: flex; gap: 8px; flex-wrap: wrap;">
|
||
<Button size="small" @click="setQuickAuthorizationDays(7)">7天</Button>
|
||
<Button size="small" @click="setQuickAuthorizationDays(30)">30天</Button>
|
||
<Button size="small" @click="setQuickAuthorizationDays(90)">90天</Button>
|
||
<Button size="small" @click="setQuickAuthorizationDays(180)">180天</Button>
|
||
<Button size="small" @click="setQuickAuthorizationDays(365)">365天</Button>
|
||
</div>
|
||
</div>
|
||
</FormItem>
|
||
<FormItem label="过期时间" v-if="authorizationModal.formData.authorization_date && authorizationModal.formData.authorization_days">
|
||
<span :style="{ color: getExpireDateColor(authorizationModal.formData) }">
|
||
{{ getExpireDate(authorizationModal.formData) }}
|
||
</span>
|
||
</FormItem>
|
||
</Form>
|
||
</Modal>
|
||
|
||
<!-- 简历详情弹窗 - 使用 ResumeInfoDetail 组件 -->
|
||
<ResumeInfoDetail ref="resumeDetail" @on-close="handleResumeDetailClose" />
|
||
|
||
<!-- 原简历详情弹窗(备份,暂时保留) -->
|
||
<Modal v-model="resumeModal.visible" :title="resumeModal.title" width="900" :footer-hide="true" v-if="false">
|
||
<div v-if="resumeModal.loading" style="text-align: center; padding: 40px;">
|
||
<Spin size="large"></Spin>
|
||
<p style="margin-top: 20px;">加载简历数据中...</p>
|
||
</div>
|
||
<div v-else-if="resumeModal.data" class="resume-detail">
|
||
<!-- 基本信息 -->
|
||
<Card title="基本信息" style="margin-bottom: 16px;">
|
||
<Row :gutter="16">
|
||
<Col span="8">
|
||
<p><strong>姓名:</strong>{{ resumeModal.data.fullName || '-' }}</p>
|
||
</Col>
|
||
<Col span="8">
|
||
<p><strong>性别:</strong>{{ resumeModal.data.gender || '-' }}</p>
|
||
</Col>
|
||
<Col span="8">
|
||
<p><strong>年龄:</strong>{{ resumeModal.data.age || '-' }}</p>
|
||
</Col>
|
||
</Row>
|
||
<Row :gutter="16" style="margin-top: 12px;">
|
||
<Col span="8">
|
||
<p><strong>电话:</strong>{{ resumeModal.data.phone || '-' }}</p>
|
||
</Col>
|
||
<Col span="8">
|
||
<p><strong>邮箱:</strong>{{ resumeModal.data.email || '-' }}</p>
|
||
</Col>
|
||
<Col span="8">
|
||
<p><strong>所在地:</strong>{{ resumeModal.data.location || '-' }}</p>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
|
||
<!-- 教育背景 -->
|
||
<Card title="教育背景" style="margin-bottom: 16px;">
|
||
<Row :gutter="16">
|
||
<Col span="8">
|
||
<p><strong>学历:</strong>{{ resumeModal.data.education || '-' }}</p>
|
||
</Col>
|
||
<Col span="8">
|
||
<p><strong>专业:</strong>{{ resumeModal.data.major || '-' }}</p>
|
||
</Col>
|
||
<Col span="8">
|
||
<p><strong>毕业院校:</strong>{{ resumeModal.data.school || '-' }}</p>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
|
||
<!-- 工作信息 -->
|
||
<Card title="工作信息" style="margin-bottom: 16px;">
|
||
<Row :gutter="16">
|
||
<Col span="8">
|
||
<p><strong>工作年限:</strong>{{ resumeModal.data.workYears || '-' }}</p>
|
||
</Col>
|
||
<Col span="8">
|
||
<p><strong>当前职位:</strong>{{ resumeModal.data.currentPosition || '-' }}</p>
|
||
</Col>
|
||
<Col span="8">
|
||
<p><strong>当前公司:</strong>{{ resumeModal.data.currentCompany || '-' }}</p>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
|
||
<!-- 期望信息 -->
|
||
<Card title="期望信息" style="margin-bottom: 16px;">
|
||
<Row :gutter="16">
|
||
<Col span="8">
|
||
<p><strong>期望职位:</strong>{{ resumeModal.data.expectedPosition || '-' }}</p>
|
||
</Col>
|
||
<Col span="8">
|
||
<p><strong>期望薪资:</strong>{{ resumeModal.data.expectedSalary || '-' }}</p>
|
||
</Col>
|
||
<Col span="8">
|
||
<p><strong>期望地点:</strong>{{ resumeModal.data.expectedLocation || '-' }}</p>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
|
||
<!-- 技能标签 -->
|
||
<Card title="技能标签" style="margin-bottom: 16px;"
|
||
v-if="resumeModal.data.skills || resumeModal.data.aiSkillTags">
|
||
<div v-if="resumeModal.data.aiSkillTags && resumeModal.data.aiSkillTags.length > 0">
|
||
<Tag v-for="(skill, index) in resumeModal.data.aiSkillTags" :key="index" color="blue"
|
||
style="margin: 4px;">
|
||
{{ skill }}
|
||
</Tag>
|
||
</div>
|
||
<div v-else-if="resumeModal.data.skills">
|
||
<Tag v-for="(skill, index) in parseSkills(resumeModal.data.skills)" :key="index" color="blue"
|
||
style="margin: 4px;">
|
||
{{ skill }}
|
||
</Tag>
|
||
</div>
|
||
<p v-else style="color: #999;">暂无技能标签</p>
|
||
</Card>
|
||
|
||
<!-- AI分析 -->
|
||
<Card title="AI分析" style="margin-bottom: 16px;" v-if="resumeModal.data.aiCompetitiveness">
|
||
<Row :gutter="16">
|
||
<Col span="24">
|
||
<p><strong>竞争力评分:</strong>
|
||
<Tag :color="getScoreColor(resumeModal.data.aiCompetitiveness)" size="large">
|
||
{{ resumeModal.data.aiCompetitiveness }} 分
|
||
</Tag>
|
||
</p>
|
||
</Col>
|
||
</Row>
|
||
<Row :gutter="16" style="margin-top: 12px;">
|
||
<Col span="24">
|
||
<p><strong>优势分析:</strong></p>
|
||
<p style="color: #19be6b; padding: 8px; background: #f0f9ff; border-radius: 4px;">
|
||
{{ resumeModal.data.aiStrengths || '-' }}
|
||
</p>
|
||
</Col>
|
||
</Row>
|
||
<Row :gutter="16" style="margin-top: 12px;">
|
||
<Col span="24">
|
||
<p><strong>劣势分析:</strong></p>
|
||
<p style="color: #ed4014; padding: 8px; background: #fff1f0; border-radius: 4px;">
|
||
{{ resumeModal.data.aiWeaknesses || '-' }}
|
||
</p>
|
||
</Col>
|
||
</Row>
|
||
<Row :gutter="16" style="margin-top: 12px;">
|
||
<Col span="24">
|
||
<p><strong>职业建议:</strong></p>
|
||
<p style="color: #2d8cf0; padding: 8px; background: #f0faff; border-radius: 4px;">
|
||
{{ resumeModal.data.aiCareerSuggestion || '-' }}
|
||
</p>
|
||
</Col>
|
||
</Row>
|
||
</Card>
|
||
|
||
<!-- 项目经验 -->
|
||
<Card title="项目经验" style="margin-bottom: 16px;" v-if="resumeModal.data.projectExperience">
|
||
<Timeline v-if="parseProjectExp(resumeModal.data.projectExperience).length > 0">
|
||
<TimelineItem v-for="(project, index) in parseProjectExp(resumeModal.data.projectExperience)"
|
||
:key="index">
|
||
<p><strong>{{ project.name || project.projectName }}</strong></p>
|
||
<p style="color: #999; font-size: 12px;">{{ project.roleName || project.role }}</p>
|
||
<p style="margin-top: 8px;">{{ project.projectDesc || project.description }}</p>
|
||
<p v-if="project.performance" style="margin-top: 4px; color: #19be6b;">
|
||
<Icon type="ios-trophy" /> {{ project.performance }}
|
||
</p>
|
||
</TimelineItem>
|
||
</Timeline>
|
||
<p v-else style="color: #999;">暂无项目经验</p>
|
||
</Card>
|
||
|
||
<!-- 工作经历 -->
|
||
<Card title="工作经历" v-if="resumeModal.data.workExperience">
|
||
<Timeline v-if="parseWorkExp(resumeModal.data.workExperience).length > 0">
|
||
<TimelineItem v-for="(work, index) in parseWorkExp(resumeModal.data.workExperience)"
|
||
:key="index">
|
||
<p><strong>{{ work.companyName }}</strong> - {{ work.positionName }}</p>
|
||
<p style="color: #999; font-size: 12px;">
|
||
{{ work.startDate }} ~ {{ work.endDate }}
|
||
</p>
|
||
<p style="margin-top: 8px;">{{ work.workContent || work.description }}</p>
|
||
</TimelineItem>
|
||
</Timeline>
|
||
<p v-else style="color: #999;">暂无工作经历</p>
|
||
</Card>
|
||
</div>
|
||
<div v-else style="text-align: center; padding: 40px; color: #999;">
|
||
<Icon type="ios-document-outline" size="60" />
|
||
<p style="margin-top: 20px;">暂无简历数据</p>
|
||
</div>
|
||
</Modal>
|
||
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import plaAccountServer from '@/api/profile/pla_account_server.js'
|
||
import jobTypesServer from '@/api/work/job_types_server.js'
|
||
import PlaAccountEdit from './pla_account_edit.vue'
|
||
import ResumeInfoDetail from './resume_info_detail.vue'
|
||
|
||
export default {
|
||
components: {
|
||
ResumeInfoDetail,
|
||
PlaAccountEdit
|
||
},
|
||
data() {
|
||
let rules = {}
|
||
rules["name"] = [{ required: true, message: '请填写账户名', trigger: 'blur' }]
|
||
rules["sn_code"] = [{ required: true, message: '请填写设备SN码', trigger: 'blur' }]
|
||
rules["platform_type"] = [{ required: true, message: '请选择平台', trigger: 'change' }]
|
||
rules["login_name"] = [{ required: true, message: '请填写登录名', trigger: 'blur' }]
|
||
|
||
return {
|
||
serverInstance: plaAccountServer,
|
||
jobTypeOptions: [],
|
||
batchParseLoading: false,
|
||
seachTypes: [
|
||
{ key: 'name', value: '账户名' },
|
||
{ key: 'login_name', value: '登录名' },
|
||
{ key: 'sn_code', value: '设备SN码' }
|
||
],
|
||
resumeModal: {
|
||
visible: false,
|
||
loading: false,
|
||
title: '在线简历详情',
|
||
data: null
|
||
},
|
||
authorizationModal: {
|
||
visible: false,
|
||
formData: {
|
||
id: null,
|
||
accountName: '',
|
||
sn_code: '',
|
||
authorization_date: null,
|
||
authorization_days: 0
|
||
}
|
||
},
|
||
gridOption: {
|
||
param: {
|
||
seachOption: {
|
||
key: 'name',
|
||
value: '',
|
||
platform_type: null,
|
||
is_online: null,
|
||
is_online: null
|
||
},
|
||
pageOption: {
|
||
page: 1,
|
||
pageSize: 20
|
||
}
|
||
},
|
||
data: [],
|
||
rules: rules
|
||
},
|
||
// 新增时只显示必填字段
|
||
addColumns: [
|
||
{ title: '账户名', key: 'name', type: 'text', required: true },
|
||
{ title: '设备SN码', key: 'sn_code', type: 'text', required: true },
|
||
{
|
||
title: '平台', key: 'platform_type', com: 'Select', required: true, source: [
|
||
{ key: 'boss', value: 'Boss直聘' },
|
||
{ key: 'liepin', value: '猎聘' }
|
||
]
|
||
},
|
||
{ title: '登录名', key: 'login_name', com: 'Input', required: true },
|
||
{ title: '密码', key: 'pwd', com: 'Password', required: true },
|
||
],
|
||
listColumns: [
|
||
{ title: '账户名', key: 'name', minWidth: 120 },
|
||
{ title: '设备SN码', key: 'sn_code', minWidth: 150 },
|
||
{
|
||
title: '平台',
|
||
key: 'platform_type',
|
||
minWidth: 100,
|
||
render: (h, params) => {
|
||
const platformMap = {
|
||
'boss': { text: 'Boss直聘', color: 'blue' },
|
||
'liepin': { text: '猎聘', color: 'green' }
|
||
}
|
||
const platform = platformMap[params.row.platform_type] || { text: params.row.platform_type, color: 'default' }
|
||
return h('Tag', { props: { color: platform.color } }, platform.text)
|
||
}
|
||
},
|
||
{
|
||
title: '在线状态',
|
||
key: 'is_online',
|
||
minWidth: 90,
|
||
render: (h, params) => {
|
||
return h('Tag', {
|
||
props: { color: params.row.is_online ? 'success' : 'default' }
|
||
}, params.row.is_online ? '在线' : '离线')
|
||
}
|
||
},
|
||
{
|
||
title: '自动投递',
|
||
key: 'auto_deliver',
|
||
minWidth: 90,
|
||
render: (h, params) => {
|
||
return h('Tag', {
|
||
props: { color: params.row.auto_deliver ? 'success' : 'default' }
|
||
}, params.row.auto_deliver ? '开启' : '关闭')
|
||
}
|
||
},
|
||
{
|
||
title: '自动沟通',
|
||
key: 'auto_chat',
|
||
minWidth: 90,
|
||
render: (h, params) => {
|
||
return h('Tag', {
|
||
props: { color: params.row.auto_chat ? 'success' : 'default' }
|
||
}, params.row.auto_chat ? '开启' : '关闭')
|
||
}
|
||
},
|
||
{
|
||
title: '剩余天数',
|
||
key: 'remaining_days',
|
||
minWidth: 100,
|
||
render: (h, params) => {
|
||
const remainingDays = params.row.remaining_days || 0
|
||
let color = 'success'
|
||
if (remainingDays <= 0) {
|
||
color = 'error'
|
||
} else if (remainingDays <= 7) {
|
||
color = 'warning'
|
||
}
|
||
return h('Tag', {
|
||
props: { color: color }
|
||
}, remainingDays > 0 ? `${remainingDays} 天` : '已过期')
|
||
}
|
||
},
|
||
{
|
||
title: '启用状态',
|
||
key: 'is_enabled',
|
||
minWidth: 100,
|
||
render: (h, params) => {
|
||
return h('i-switch', {
|
||
props: {
|
||
value: Boolean(params.row.is_enabled),
|
||
size: 'large'
|
||
},
|
||
on: {
|
||
'on-change': (value) => {
|
||
this.toggleEnabled(params.row, value)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
},
|
||
{
|
||
title: '操作',
|
||
key: 'action',
|
||
width: 500,
|
||
type: 'template',
|
||
render: (h, params) => {
|
||
let btns = [
|
||
{
|
||
title: '详情',
|
||
type: 'info',
|
||
click: () => {
|
||
this.showAccountDetails(params.row)
|
||
},
|
||
},
|
||
{
|
||
title: '查看简历',
|
||
type: 'success',
|
||
click: () => {
|
||
this.showResume(params.row)
|
||
},
|
||
},
|
||
{
|
||
title: '编辑',
|
||
type: 'primary',
|
||
click: () => {
|
||
this.showEditWarp(params.row)
|
||
},
|
||
},
|
||
{
|
||
title: '设置授权',
|
||
type: 'warning',
|
||
click: () => {
|
||
this.showAuthorizationModal(params.row)
|
||
},
|
||
},
|
||
{
|
||
title: '解析位置',
|
||
type: 'success',
|
||
click: () => {
|
||
this.parseLocation(params.row)
|
||
},
|
||
},
|
||
{
|
||
title: '停止任务',
|
||
type: 'warning',
|
||
click: () => {
|
||
this.stopTasks(params.row)
|
||
},
|
||
},
|
||
{
|
||
title: '删除',
|
||
type: 'error',
|
||
click: () => {
|
||
this.delConfirm(params.row)
|
||
},
|
||
},
|
||
]
|
||
return window.framework.uiTool.getBtn(h, btns)
|
||
},
|
||
}
|
||
]
|
||
}
|
||
},
|
||
mounted() {
|
||
this.query(1)
|
||
this.loadJobTypes()
|
||
},
|
||
methods: {
|
||
query(page) {
|
||
this.gridOption.param.pageOption.page = page
|
||
plaAccountServer.page(this.gridOption.param).then(res => {
|
||
this.gridOption.data = res.data.rows
|
||
this.gridOption.param.pageOption.total = res.data.count
|
||
})
|
||
},
|
||
showAddWarp() {
|
||
// 新增使用简单的 editModal,只包含必填字段
|
||
this.$refs.editModal.showModal()
|
||
},
|
||
showEditWarp(row) {
|
||
this.$refs.accountEdit.showEdit(row)
|
||
},
|
||
toggleEnabled(row, value) {
|
||
const action = value ? '启用' : '禁用'
|
||
this.$Modal.confirm({
|
||
title: `确认${action}`,
|
||
content: `确定要${action}账号 "${row.name}" 吗?${!value ? '禁用后该账号将不会执行自动任务。' : ''}`,
|
||
onOk: async () => {
|
||
try {
|
||
await plaAccountServer.update({ ...row, is_enabled: value ? 1 : 0 })
|
||
this.$Message.success(`${action}成功!`)
|
||
this.query(this.gridOption.param.pageOption.page)
|
||
} catch (error) {
|
||
this.$Message.error(`${action}失败`)
|
||
// 恢复开关状态
|
||
row.is_enabled = value ? 0 : 1
|
||
}
|
||
},
|
||
onCancel: () => {
|
||
// 取消时恢复开关状态
|
||
row.is_enabled = value ? 0 : 1
|
||
}
|
||
})
|
||
},
|
||
loadJobTypes() {
|
||
// 职位类型选项已移到编辑组件中加载
|
||
},
|
||
stopTasks(row) {
|
||
this.$Modal.confirm({
|
||
title: '确认停止',
|
||
content: `确定要停止账号 "${row.name}" 的所有任务吗?`,
|
||
onOk: async () => {
|
||
try {
|
||
await plaAccountServer.stopTasks(row)
|
||
this.$Message.success('停止任务成功!')
|
||
this.query(this.gridOption.param.pageOption.page)
|
||
} catch (error) {
|
||
this.$Message.error('停止任务失败:' + (error.message || '请稍后重试'))
|
||
}
|
||
}
|
||
})
|
||
},
|
||
delConfirm(row) {
|
||
window.framework.uiTool.delConfirm(async () => {
|
||
await plaAccountServer.del(row)
|
||
this.$Message.success('删除成功!')
|
||
this.query(1)
|
||
})
|
||
},
|
||
exportCsv() {
|
||
plaAccountServer.exportCsv(this.gridOption.param).then(res => {
|
||
window.framework.funTool.downloadFile(res, '平台账号.csv')
|
||
})
|
||
},
|
||
resetQuery() {
|
||
this.gridOption.param.seachOption = {
|
||
key: 'name',
|
||
value: '',
|
||
platform_type: null,
|
||
is_online: null
|
||
}
|
||
this.query(1)
|
||
},
|
||
async handleSaveSuccess({ data, isEdit } = {}) {
|
||
try {
|
||
// 如果是新增(来自 editModal),data 只包含必填字段,直接保存
|
||
if (data && !data.id && !isEdit) {
|
||
await plaAccountServer.add(data)
|
||
this.$Message.success('保存成功!')
|
||
}
|
||
// 编辑时由 FloatPanel 组件(PlaAccountEdit)处理保存,这里只刷新列表
|
||
// 刷新列表,保持当前页码
|
||
this.query(this.gridOption.param.pageOption.page || 1)
|
||
} catch (error) {
|
||
console.error('保存失败:', error)
|
||
// 优先从 error.response.data.message 获取,然后是 error.message
|
||
const errorMsg = error.response?.data?.message || error.message || '请稍后重试'
|
||
this.$Message.error('保存失败:' + errorMsg)
|
||
}
|
||
},
|
||
// 显示账号详情
|
||
showAccountDetails(row) {
|
||
this.$router.push({
|
||
path: '/pla_account/pla_account_detail',
|
||
query: { id: row.id }
|
||
})
|
||
},
|
||
// 查看简历
|
||
async showResume(row) {
|
||
// 显示加载提示
|
||
const loadingMsg = this.$Message.loading({
|
||
content: '正在加载简历数据...',
|
||
duration: 0
|
||
})
|
||
|
||
try {
|
||
// 根据 sn_code 和 platform 获取简历
|
||
const platformMap = {
|
||
'1': 'boss',
|
||
'2': 'liepin'
|
||
}
|
||
const platform = platformMap[row.platform_type] || 'boss'
|
||
|
||
// admin 会自动加 /admin_api 前缀
|
||
const res = await window.framework.http.get(`/resume/get-by-device?sn_code=${row.sn_code}&platform=${platform}`)
|
||
|
||
loadingMsg()
|
||
|
||
if (res.code === 0 && res.data && res.data.resumeId) {
|
||
// 使用 ResumeInfoDetail 组件显示简历
|
||
this.$refs.resumeDetail.show(res.data.resumeId)
|
||
} else {
|
||
this.$Message.warning(res.message || '未找到简历数据')
|
||
}
|
||
} catch (error) {
|
||
loadingMsg()
|
||
console.error('获取简历失败:', error)
|
||
this.$Message.error('获取简历失败:' + (error.message || '请稍后重试'))
|
||
}
|
||
},
|
||
// 关闭简历详情
|
||
handleResumeDetailClose() {
|
||
// 可以在这里添加关闭后的逻辑
|
||
},
|
||
// 解析技能标签
|
||
parseSkills(skills) {
|
||
if (!skills) return []
|
||
if (Array.isArray(skills)) return skills
|
||
try {
|
||
return JSON.parse(skills)
|
||
} catch (e) {
|
||
return skills.split(',').map(s => s.trim()).filter(s => s)
|
||
}
|
||
},
|
||
// 解析项目经验
|
||
parseProjectExp(projectExp) {
|
||
if (!projectExp) return []
|
||
if (Array.isArray(projectExp)) return projectExp
|
||
try {
|
||
return JSON.parse(projectExp)
|
||
} catch (e) {
|
||
return []
|
||
}
|
||
},
|
||
// 格式化日期
|
||
formatDate(date) {
|
||
if (!date) return '-'
|
||
const d = new Date(date)
|
||
const year = d.getFullYear()
|
||
const month = String(d.getMonth() + 1).padStart(2, '0')
|
||
const day = String(d.getDate()).padStart(2, '0')
|
||
return `${year}-${month}-${day}`
|
||
},
|
||
// 显示授权设置弹窗
|
||
showAuthorizationModal(row) {
|
||
this.authorizationModal.formData = {
|
||
id: row.id,
|
||
accountName: row.name,
|
||
sn_code: row.sn_code,
|
||
// 如果有授权日期则使用,否则默认使用当天
|
||
authorization_date: row.authorization_date ? new Date(row.authorization_date) : new Date(),
|
||
authorization_days: row.authorization_days || 0
|
||
}
|
||
this.authorizationModal.visible = true
|
||
},
|
||
// 设置快捷授权天数
|
||
setQuickAuthorizationDays(days) {
|
||
this.authorizationModal.formData.authorization_days = days
|
||
// 如果没有设置授权日期,自动设置为当前日期
|
||
if (!this.authorizationModal.formData.authorization_date) {
|
||
this.authorizationModal.formData.authorization_date = new Date()
|
||
}
|
||
},
|
||
// 保存授权信息
|
||
async handleSaveAuthorization() {
|
||
if (!this.authorizationModal.formData.id) {
|
||
this.$Message.error('账号ID不能为空')
|
||
return
|
||
}
|
||
|
||
if (!this.authorizationModal.formData.authorization_days || this.authorizationModal.formData.authorization_days <= 0) {
|
||
this.$Message.error('请输入授权天数')
|
||
return
|
||
}
|
||
|
||
try {
|
||
const param = {
|
||
id: this.authorizationModal.formData.id,
|
||
authorization_days: this.authorizationModal.formData.authorization_days
|
||
}
|
||
|
||
// 如果设置了授权日期,添加到参数中
|
||
if (this.authorizationModal.formData.authorization_date) {
|
||
param.authorization_date = this.authorizationModal.formData.authorization_date
|
||
}
|
||
|
||
await plaAccountServer.updateAuthorization(param)
|
||
this.$Message.success('授权设置成功!')
|
||
this.authorizationModal.visible = false
|
||
// 刷新列表
|
||
this.query(this.gridOption.param.pageOption.page || 1)
|
||
} catch (error) {
|
||
console.error('设置授权失败:', error)
|
||
this.$Message.error('设置授权失败:' + (error.message || '请稍后重试'))
|
||
}
|
||
},
|
||
// 取消授权设置
|
||
handleCancelAuthorization() {
|
||
this.authorizationModal.visible = false
|
||
this.authorizationModal.formData = {
|
||
id: null,
|
||
accountName: '',
|
||
sn_code: '',
|
||
authorization_date: null,
|
||
authorization_days: 0
|
||
}
|
||
},
|
||
// 获取过期时间
|
||
getExpireDate(formData) {
|
||
if (!formData.authorization_date || !formData.authorization_days) {
|
||
return '未授权'
|
||
}
|
||
const authDate = new Date(formData.authorization_date)
|
||
const expireDate = new Date(authDate.getTime() + formData.authorization_days * 24 * 60 * 60 * 1000)
|
||
const year = expireDate.getFullYear()
|
||
const month = String(expireDate.getMonth() + 1).padStart(2, '0')
|
||
const day = String(expireDate.getDate()).padStart(2, '0')
|
||
return `${year}-${month}-${day}`
|
||
},
|
||
// 获取过期时间颜色
|
||
getExpireDateColor(formData) {
|
||
if (!formData.authorization_date || !formData.authorization_days) {
|
||
return '#999'
|
||
}
|
||
const authDate = new Date(formData.authorization_date)
|
||
const expireDate = new Date(authDate.getTime() + formData.authorization_days * 24 * 60 * 60 * 1000)
|
||
const now = new Date()
|
||
const remaining = Math.ceil((expireDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000))
|
||
if (remaining <= 0) {
|
||
return '#ed4014'
|
||
} else if (remaining <= 7) {
|
||
return '#ff9900'
|
||
} else {
|
||
return '#515a6e'
|
||
}
|
||
},
|
||
// 解析工作经历
|
||
parseWorkExp(workExp) {
|
||
if (!workExp) return []
|
||
if (Array.isArray(workExp)) return workExp
|
||
try {
|
||
return JSON.parse(workExp)
|
||
} catch (e) {
|
||
return []
|
||
}
|
||
},
|
||
// 获取评分颜色
|
||
getScoreColor(score) {
|
||
if (score >= 80) return 'success'
|
||
if (score >= 60) return 'warning'
|
||
return 'error'
|
||
},
|
||
// 解析单个账号的位置
|
||
async parseLocation(row) {
|
||
if (!row.user_address || row.user_address.trim() === '') {
|
||
this.$Message.warning('请先设置用户地址')
|
||
return
|
||
}
|
||
|
||
this.$Modal.confirm({
|
||
title: '确认解析位置',
|
||
content: `确定要解析账号 "${row.name}" 的地址 "${row.user_address}" 吗?`,
|
||
onOk: async () => {
|
||
try {
|
||
const res = await window.framework.http.post('/pla_account/parseLocation', {
|
||
id: row.id,
|
||
address: row.user_address
|
||
})
|
||
|
||
if (res.code === 0) {
|
||
this.$Message.success('位置解析成功!')
|
||
this.query(this.gridOption.param.pageOption.page)
|
||
} else {
|
||
this.$Message.error(res.message || '位置解析失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('位置解析失败:', error)
|
||
// 优先从 error.response.data.message 获取,然后是 error.message
|
||
const errorMsg = error.response?.data?.message || error.message || '请稍后重试'
|
||
this.$Message.error(errorMsg)
|
||
}
|
||
}
|
||
})
|
||
},
|
||
// 批量解析位置
|
||
async batchParseLocation() {
|
||
const selectedRows = this.gridOption.data.filter(row => row.user_address && row.user_address.trim() !== '')
|
||
|
||
if (selectedRows.length === 0) {
|
||
this.$Message.warning('当前页面没有已设置地址的账号')
|
||
return
|
||
}
|
||
|
||
this.$Modal.confirm({
|
||
title: '确认批量解析位置',
|
||
content: `确定要批量解析 ${selectedRows.length} 个账号的位置吗?`,
|
||
onOk: async () => {
|
||
this.batchParseLoading = true
|
||
try {
|
||
const ids = selectedRows.map(row => row.id)
|
||
const res = await window.framework.http.post('/pla_account/batchParseLocation', { ids })
|
||
|
||
if (res.code === 0) {
|
||
const result = res.data
|
||
const successCount = result.success || 0
|
||
const failedCount = result.failed || 0
|
||
|
||
let message = `批量解析完成:成功 ${successCount} 个`
|
||
if (failedCount > 0) {
|
||
message += `,失败 ${failedCount} 个`
|
||
}
|
||
|
||
this.$Message.success(message)
|
||
|
||
// 如果有失败的,显示详细信息
|
||
if (failedCount > 0 && result.details) {
|
||
const failedDetails = result.details.filter(d => !d.success)
|
||
console.log('解析失败的账号:', failedDetails)
|
||
}
|
||
|
||
this.query(this.gridOption.param.pageOption.page)
|
||
} else {
|
||
this.$Message.error(res.message || '批量解析失败')
|
||
}
|
||
} catch (error) {
|
||
console.error('批量位置解析失败:', error)
|
||
// 优先从 error.response.data.message 获取,然后是 error.message
|
||
const errorMsg = error.response?.data?.message || error.message || '请稍后重试'
|
||
this.$Message.error(errorMsg)
|
||
} finally {
|
||
this.batchParseLoading = false
|
||
}
|
||
}
|
||
})
|
||
}
|
||
},
|
||
computed: {
|
||
seachTypePlaceholder() {
|
||
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key)
|
||
return selected ? selected.value : '请选择搜索类型'
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.ml10 {
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.flex {
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.resume-detail {
|
||
max-height: 600px;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
.resume-detail p {
|
||
margin: 8px 0;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
.resume-detail strong {
|
||
color: #333;
|
||
}
|
||
</style>
|