1
This commit is contained in:
File diff suppressed because it is too large
Load Diff
139
_sql/add_pla_account_json_config_fields.sql
Normal file
139
_sql/add_pla_account_json_config_fields.sql
Normal file
@@ -0,0 +1,139 @@
|
||||
-- 为 pla_account 表添加 JSON 配置字段
|
||||
-- 执行时间:2025-01-XX
|
||||
-- 说明:将自动投递、自动沟通、自动回复、自动活跃的配置项统一到 JSON 配置字段中
|
||||
|
||||
-- ============================================
|
||||
-- 添加自动投递配置字段(deliver_config)
|
||||
-- ============================================
|
||||
-- 如果字段已存在则跳过,否则添加
|
||||
SET @exist_deliver_config = (
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'pla_account'
|
||||
AND COLUMN_NAME = 'deliver_config'
|
||||
);
|
||||
|
||||
SET @sql_deliver_config = IF(@exist_deliver_config = 0,
|
||||
'ALTER TABLE `pla_account`
|
||||
ADD COLUMN `deliver_config` JSON COMMENT ''自动投递配置(JSON对象,包含:deliver_interval-投递间隔分钟数, min_salary-最低薪资, max_salary-最高薪资, page_count-滚动获取职位列表次数, max_deliver-每次最多投递数量, filter_keywords-过滤关键词, exclude_keywords-排除关键词)''
|
||||
AFTER `auto_deliver`',
|
||||
'SELECT ''字段 deliver_config 已存在,跳过添加'' AS message'
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @sql_deliver_config;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ============================================
|
||||
-- 添加自动沟通策略配置字段(chat_strategy)
|
||||
-- ============================================
|
||||
SET @exist_chat_strategy = (
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'pla_account'
|
||||
AND COLUMN_NAME = 'chat_strategy'
|
||||
);
|
||||
|
||||
SET @sql_chat_strategy = IF(@exist_chat_strategy = 0,
|
||||
'ALTER TABLE `pla_account`
|
||||
ADD COLUMN `chat_strategy` JSON COMMENT ''自动沟通策略配置(JSON对象,包含:chat_interval-沟通间隔分钟数, is_chat_outsourcing-是否沟通外包岗位, time_range-沟通时间段配置)''
|
||||
AFTER `auto_chat`',
|
||||
'SELECT ''字段 chat_strategy 已存在,跳过添加'' AS message'
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @sql_chat_strategy;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ============================================
|
||||
-- 添加自动回复配置字段(reply_config)
|
||||
-- ============================================
|
||||
SET @exist_reply_config = (
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'pla_account'
|
||||
AND COLUMN_NAME = 'reply_config'
|
||||
);
|
||||
|
||||
SET @sql_reply_config = IF(@exist_reply_config = 0,
|
||||
'ALTER TABLE `pla_account`
|
||||
ADD COLUMN `reply_config` JSON COMMENT ''自动回复配置(JSON对象)''
|
||||
AFTER `auto_reply`',
|
||||
'SELECT ''字段 reply_config 已存在,跳过添加'' AS message'
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @sql_reply_config;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ============================================
|
||||
-- 添加自动活跃动作配置字段(active_actions)
|
||||
-- ============================================
|
||||
SET @exist_active_actions = (
|
||||
SELECT COUNT(*)
|
||||
FROM INFORMATION_SCHEMA.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'pla_account'
|
||||
AND COLUMN_NAME = 'active_actions'
|
||||
);
|
||||
|
||||
SET @sql_active_actions = IF(@exist_active_actions = 0,
|
||||
'ALTER TABLE `pla_account`
|
||||
ADD COLUMN `active_actions` TEXT COMMENT ''自动活跃动作配置(JSON对象,包含:active_interval-活跃间隔分钟数, actions-活跃动作列表)''
|
||||
AFTER `auto_active`',
|
||||
'SELECT ''字段 active_actions 已存在,跳过添加'' AS message'
|
||||
);
|
||||
|
||||
PREPARE stmt FROM @sql_active_actions;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- ============================================
|
||||
-- 数据迁移说明
|
||||
-- ============================================
|
||||
-- 如果需要将旧字段数据迁移到新 JSON 配置字段,可以执行以下 SQL:
|
||||
--
|
||||
-- 1. 迁移自动投递配置(如果存在旧字段)
|
||||
-- UPDATE pla_account
|
||||
-- SET deliver_config = JSON_OBJECT(
|
||||
-- 'deliver_interval', 30,
|
||||
-- 'min_salary', COALESCE(min_salary, 0),
|
||||
-- 'max_salary', COALESCE(max_salary, 0),
|
||||
-- 'page_count', COALESCE(page_count, 3),
|
||||
-- 'max_deliver', COALESCE(max_deliver, 10),
|
||||
-- 'filter_keywords', COALESCE(filter_keywords, JSON_ARRAY()),
|
||||
-- 'exclude_keywords', COALESCE(exclude_keywords, JSON_ARRAY())
|
||||
-- )
|
||||
-- WHERE deliver_config IS NULL AND (min_salary IS NOT NULL OR max_salary IS NOT NULL);
|
||||
--
|
||||
-- 2. 迁移自动沟通配置(如果存在旧字段)
|
||||
-- UPDATE pla_account
|
||||
-- SET chat_strategy = JSON_OBJECT(
|
||||
-- 'chat_interval', COALESCE(chat_interval, 30),
|
||||
-- 'is_chat_outsourcing', COALESCE(is_chat_outsourcing, 0),
|
||||
-- 'time_range', JSON_OBJECT(
|
||||
-- 'start_time', '09:00',
|
||||
-- 'end_time', '18:00',
|
||||
-- 'workdays_only', 1
|
||||
-- )
|
||||
-- )
|
||||
-- WHERE chat_strategy IS NULL AND (chat_interval IS NOT NULL OR is_chat_outsourcing IS NOT NULL);
|
||||
--
|
||||
-- 3. 迁移自动活跃配置(如果存在旧字段)
|
||||
-- UPDATE pla_account
|
||||
-- SET active_actions = CONCAT('{"active_interval":', COALESCE(active_interval, 60), ',"actions":[]}')
|
||||
-- WHERE active_actions IS NULL OR active_actions = '' AND active_interval IS NOT NULL;
|
||||
|
||||
-- ============================================
|
||||
-- 注意事项
|
||||
-- ============================================
|
||||
-- 1. deliver_config、chat_strategy、reply_config 使用 JSON 类型(MySQL 5.7+)
|
||||
-- 2. active_actions 使用 TEXT 类型(兼容已有数据库结构)
|
||||
-- 3. 如果 MySQL 版本低于 5.7,请将其他 JSON 类型改为 TEXT 类型
|
||||
-- 4. 执行前建议先备份数据库
|
||||
-- 5. 字段已存在时会自动跳过,不会报错
|
||||
-- 6. 如果需要迁移旧数据,请取消注释上面的数据迁移 SQL 并执行
|
||||
|
||||
79
_sql/add_pla_account_json_config_fields_simple.sql
Normal file
79
_sql/add_pla_account_json_config_fields_simple.sql
Normal file
@@ -0,0 +1,79 @@
|
||||
-- 为 pla_account 表添加 JSON 配置字段(简化版)
|
||||
-- 执行时间:2025-01-XX
|
||||
-- 说明:将自动投递、自动沟通、自动回复、自动活跃的配置项统一到 JSON 配置字段中
|
||||
-- 注意:如果字段已存在,执行会报错,可以忽略或手动删除已存在的字段
|
||||
|
||||
-- ============================================
|
||||
-- 添加自动投递配置字段(deliver_config)
|
||||
-- ============================================
|
||||
ALTER TABLE `pla_account`
|
||||
ADD COLUMN `deliver_config` JSON COMMENT '自动投递配置(JSON对象,包含:deliver_interval-投递间隔分钟数, min_salary-最低薪资, max_salary-最高薪资, page_count-滚动获取职位列表次数, max_deliver-每次最多投递数量, filter_keywords-过滤关键词, exclude_keywords-排除关键词)'
|
||||
AFTER `auto_deliver`;
|
||||
|
||||
-- ============================================
|
||||
-- 添加自动沟通策略配置字段(chat_strategy)
|
||||
-- ============================================
|
||||
ALTER TABLE `pla_account`
|
||||
ADD COLUMN `chat_strategy` JSON COMMENT '自动沟通策略配置(JSON对象,包含:chat_interval-沟通间隔分钟数, is_chat_outsourcing-是否沟通外包岗位, time_range-沟通时间段配置)'
|
||||
AFTER `auto_chat`;
|
||||
|
||||
-- ============================================
|
||||
-- 添加自动回复配置字段(reply_config)
|
||||
-- ============================================
|
||||
ALTER TABLE `pla_account`
|
||||
ADD COLUMN `reply_config` JSON COMMENT '自动回复配置(JSON对象)'
|
||||
AFTER `auto_reply`;
|
||||
|
||||
-- ============================================
|
||||
-- 添加自动活跃动作配置字段(active_actions)
|
||||
-- ============================================
|
||||
-- 如果字段已存在且为 TEXT 类型,则修改注释;如果不存在,则添加
|
||||
ALTER TABLE `pla_account`
|
||||
ADD COLUMN `active_actions` TEXT COMMENT '自动活跃动作配置(JSON对象,包含:active_interval-活跃间隔分钟数, actions-活跃动作列表)'
|
||||
AFTER `auto_active`;
|
||||
|
||||
-- ============================================
|
||||
-- 初始化默认配置值(可选)
|
||||
-- ============================================
|
||||
-- 为已有数据初始化默认配置(如果字段为空)
|
||||
UPDATE `pla_account`
|
||||
SET `deliver_config` = JSON_OBJECT(
|
||||
'deliver_interval', 30,
|
||||
'min_salary', 0,
|
||||
'max_salary', 0,
|
||||
'page_count', 3,
|
||||
'max_deliver', 10,
|
||||
'filter_keywords', JSON_ARRAY(),
|
||||
'exclude_keywords', JSON_ARRAY()
|
||||
)
|
||||
WHERE `deliver_config` IS NULL;
|
||||
|
||||
UPDATE `pla_account`
|
||||
SET `chat_strategy` = JSON_OBJECT(
|
||||
'chat_interval', 30,
|
||||
'is_chat_outsourcing', 0,
|
||||
'time_range', JSON_OBJECT(
|
||||
'start_time', '09:00',
|
||||
'end_time', '18:00',
|
||||
'workdays_only', 1
|
||||
)
|
||||
)
|
||||
WHERE `chat_strategy` IS NULL;
|
||||
|
||||
UPDATE `pla_account`
|
||||
SET `reply_config` = JSON_OBJECT()
|
||||
WHERE `reply_config` IS NULL;
|
||||
|
||||
UPDATE `pla_account`
|
||||
SET `active_actions` = '{"active_interval": 60, "actions": []}'
|
||||
WHERE `active_actions` IS NULL OR `active_actions` = '';
|
||||
|
||||
-- ============================================
|
||||
-- 注意事项
|
||||
-- ============================================
|
||||
-- 1. deliver_config、chat_strategy、reply_config 使用 JSON 类型(MySQL 5.7+)
|
||||
-- 2. active_actions 使用 TEXT 类型(兼容已有数据库结构)
|
||||
-- 3. 如果 MySQL 版本低于 5.7,请将其他 JSON 类型改为 TEXT 类型
|
||||
-- 4. 执行前建议先备份数据库
|
||||
-- 5. 如果字段已存在,ALTER TABLE 会报错,可以忽略或先删除已存在的字段
|
||||
|
||||
@@ -26,11 +26,11 @@ const baseConfig = {
|
||||
// 开发环境配置
|
||||
const developmentConfig = {
|
||||
...baseConfig,
|
||||
// apiUrl: 'http://localhost:9097/admin_api/',
|
||||
// uploadUrl: 'http://localhost:9097/admin_api/upload',
|
||||
apiUrl: 'http://localhost:9097/admin_api/',
|
||||
uploadUrl: 'http://localhost:9097/admin_api/upload',
|
||||
|
||||
apiUrl: 'http://work.light120.com/admin_api/',
|
||||
uploadUrl: 'http://work.light120.com/admin_api/upload',
|
||||
// apiUrl: 'http://work.light120.com/admin_api/',
|
||||
// uploadUrl: 'http://work.light120.com/admin_api/upload',
|
||||
// 开发环境显示更多调试信息
|
||||
debug: true
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ class PlaAccountServer {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
add(row) {
|
||||
return window.framework.http.post('/account/create', row)
|
||||
return window.framework.http.post('/account/add', row)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
<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>
|
||||
<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"
|
||||
@@ -37,9 +41,14 @@
|
||||
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
|
||||
@changePage="query"></tables>
|
||||
</div>
|
||||
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules" @on-save="handleSaveSuccess">
|
||||
|
||||
<!-- 新增弹窗(只包含必填字段) -->
|
||||
<editModal ref="editModal" :columns="addColumns" :rules="gridOption.rules" @on-save="handleSaveSuccess">
|
||||
</editModal>
|
||||
|
||||
<!-- 编辑组件(使用 FloatPanel,包含所有字段) -->
|
||||
<PlaAccountEdit ref="accountEdit" @on-save="handleSaveSuccess" />
|
||||
|
||||
<!-- 简历详情弹窗 -->
|
||||
<Modal v-model="resumeModal.visible" :title="resumeModal.title" width="900" :footer-hide="true">
|
||||
<div v-if="resumeModal.loading" style="text-align: center; padding: 40px;">
|
||||
@@ -216,8 +225,12 @@
|
||||
<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'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
PlaAccountEdit
|
||||
},
|
||||
data() {
|
||||
let rules = {}
|
||||
rules["name"] = [{ required: true, message: '请填写账户名', trigger: 'blur' }]
|
||||
@@ -257,6 +270,19 @@ export default {
|
||||
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: '1', value: 'Boss直聘' },
|
||||
{ key: '2', value: '猎聘' }
|
||||
]
|
||||
},
|
||||
{ title: '登录名', key: 'login_name', com: 'Input', required: true },
|
||||
{ title: '密码', key: 'pwd', com: 'Password', required: true },
|
||||
],
|
||||
listColumns: [
|
||||
{ title: 'ID', key: 'id', minWidth: 80 },
|
||||
{ title: '账户名', key: 'name', minWidth: 150 },
|
||||
@@ -417,54 +443,6 @@ export default {
|
||||
return window.framework.uiTool.getBtn(h, btns)
|
||||
},
|
||||
}
|
||||
],
|
||||
editColumns: [
|
||||
{ title: '账户名', key: 'name', type: 'text', required: true },
|
||||
{ title: '设备SN码', key: 'sn_code', type: 'text', required: true },
|
||||
{
|
||||
title: '平台', key: 'platform_type', type: 'select', required: true, options: [
|
||||
{ value: '1', label: 'Boss直聘' },
|
||||
{ value: '2', label: '猎聘' }
|
||||
]
|
||||
},
|
||||
{ title: '登录名', key: 'login_name', type: 'text', required: true },
|
||||
{ title: '密码', key: 'pwd', type: 'password' },
|
||||
{ title: '搜索关键词', key: 'keyword', type: 'text' },
|
||||
{ title: '启用状态', key: 'is_enabled', type: 'switch' },
|
||||
{ title: '在线状态', key: 'is_online', type: 'switch' },
|
||||
{
|
||||
title: '职位类型',
|
||||
key: 'job_type_id',
|
||||
type: 'select',
|
||||
required: false,
|
||||
options: this.jobTypeOptions || []
|
||||
},
|
||||
{ title: '用户地址', key: 'user_address', type: 'text', placeholder: '请输入用户地址,如:北京市朝阳区' },
|
||||
// 自动投递配置
|
||||
{
|
||||
title: '自动投递', key: 'auto_deliver', "com": "Radio", options: [
|
||||
{ value: 1, label: '开启' },
|
||||
{ value: 0, label: '关闭' }
|
||||
],
|
||||
},
|
||||
{ title: '最低薪资(元)', key: 'min_salary', type: 'number', placeholder: '最低薪资,0表示不限制' },
|
||||
{ title: '最高薪资(元)', key: 'max_salary', type: 'number', placeholder: '最高薪资,0表示不限制' },
|
||||
// 自动沟通配置
|
||||
{
|
||||
title: '自动沟通', key: 'auto_chat', "com": "Radio", options: [
|
||||
{ value: 1, label: '开启' },
|
||||
{ value: 0, label: '关闭' }
|
||||
],
|
||||
},
|
||||
{ title: '沟通间隔(分钟)', key: 'chat_interval', type: 'number', placeholder: '沟通间隔时间,默认30分钟' },
|
||||
{ title: '自动回复', key: 'auto_reply', type: 'switch' },
|
||||
// 自动活跃配置
|
||||
{
|
||||
title: '自动活跃', key: 'auto_active', "com": "Radio", options: [
|
||||
{ value: 1, label: '开启' },
|
||||
{ value: 0, label: '关闭' }
|
||||
],
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
@@ -481,18 +459,11 @@ export default {
|
||||
})
|
||||
},
|
||||
showAddWarp() {
|
||||
// 新增使用简单的 editModal,只包含必填字段
|
||||
this.$refs.editModal.showModal()
|
||||
},
|
||||
showEditWarp(row) {
|
||||
// 将布尔字段从 0/1 转换为 true/false,以便开关组件正确显示
|
||||
const editData = { ...row }
|
||||
const booleanFields = ['is_enabled', 'is_online', 'auto_deliver', 'auto_chat', 'auto_reply', 'auto_active']
|
||||
booleanFields.forEach(field => {
|
||||
if (editData[field] !== undefined && editData[field] !== null) {
|
||||
editData[field] = Boolean(editData[field])
|
||||
}
|
||||
})
|
||||
this.$refs.editModal.showModal(editData)
|
||||
this.$refs.accountEdit.showEdit(row)
|
||||
},
|
||||
toggleEnabled(row, value) {
|
||||
const action = value ? '启用' : '禁用'
|
||||
@@ -517,21 +488,7 @@ export default {
|
||||
})
|
||||
},
|
||||
loadJobTypes() {
|
||||
jobTypesServer.getAll().then(res => {
|
||||
if (res.code === 0 && res.data) {
|
||||
this.jobTypeOptions = res.data.map(item => ({
|
||||
value: item.id,
|
||||
label: item.name
|
||||
}))
|
||||
// 更新 editColumns 中的选项
|
||||
const jobTypeColumn = this.gridOption.editColumns.find(col => col.key === 'job_type_id')
|
||||
if (jobTypeColumn) {
|
||||
jobTypeColumn.options = this.jobTypeOptions
|
||||
}
|
||||
}
|
||||
}).catch(err => {
|
||||
console.error('加载职位类型失败:', err)
|
||||
})
|
||||
// 职位类型选项已移到编辑组件中加载
|
||||
},
|
||||
stopTasks(row) {
|
||||
this.$Modal.confirm({
|
||||
@@ -571,25 +528,12 @@ export default {
|
||||
},
|
||||
async handleSaveSuccess({ data }) {
|
||||
try {
|
||||
// 将布尔字段从 true/false 转换为 1/0
|
||||
const saveData = { ...data }
|
||||
const booleanFields = ['is_enabled', 'is_online', 'auto_deliver', 'auto_chat', 'auto_reply', 'auto_active']
|
||||
booleanFields.forEach(field => {
|
||||
if (saveData[field] !== undefined && saveData[field] !== null) {
|
||||
saveData[field] = saveData[field] ? 1 : 0
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
// 根据是否有 id 判断是新增还是更新
|
||||
if (saveData.id) {
|
||||
await plaAccountServer.update(saveData)
|
||||
} else {
|
||||
await plaAccountServer.add(saveData)
|
||||
}
|
||||
|
||||
// 如果是新增(来自 editModal),data 只包含必填字段,直接保存
|
||||
if (data && !data.id) {
|
||||
await plaAccountServer.add(data)
|
||||
this.$Message.success('保存成功!')
|
||||
}
|
||||
// 编辑时由 FloatPanel 组件(PlaAccountEdit)处理保存,这里只刷新列表
|
||||
this.query(this.gridOption.param.pageOption.page)
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
|
||||
@@ -86,6 +86,190 @@
|
||||
</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="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 === 1 ? 'success' : 'default'">
|
||||
{{ deliverConfig.auto_deliver === 1 ? '开启' : '关闭' }}
|
||||
</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="12">
|
||||
<div class="detail-item">
|
||||
<span class="label">过滤关键词:</span>
|
||||
<span class="value">{{ deliverConfig.filter_keywords || '-' }}</span>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span="12">
|
||||
<div class="detail-item">
|
||||
<span class="label">排除关键词:</span>
|
||||
<span class="value">{{ deliverConfig.exclude_keywords || '-' }}</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">
|
||||
|
||||
633
admin/src/views/account/pla_account_edit.vue
Normal file
633
admin/src/views/account/pla_account_edit.vue
Normal file
@@ -0,0 +1,633 @@
|
||||
<template>
|
||||
<FloatPanel
|
||||
ref="floatPanel"
|
||||
:title="isEdit ? '编辑账号' : '新增账号'"
|
||||
position="right"
|
||||
:show-back="true"
|
||||
back-text="返回"
|
||||
@back="handleBack"
|
||||
>
|
||||
<template #header-right>
|
||||
<Button type="primary" @click="handleSave" :loading="saving">保存</Button>
|
||||
</template>
|
||||
|
||||
<div class="account-edit-form">
|
||||
<Form ref="form" :model="formData" :rules="rules" :label-width="120">
|
||||
<!-- 基本信息 -->
|
||||
<Card title="基本信息" style="margin-bottom: 16px;">
|
||||
<FormItem label="账户名" prop="name">
|
||||
<Input v-model="formData.name" placeholder="请输入账户名" />
|
||||
</FormItem>
|
||||
<FormItem label="设备SN码" prop="sn_code">
|
||||
<Input v-model="formData.sn_code" placeholder="请输入设备SN码" />
|
||||
</FormItem>
|
||||
<FormItem label="平台" prop="platform_type">
|
||||
<Select v-model="formData.platform_type" placeholder="请选择平台">
|
||||
<Option value="1">Boss直聘</Option>
|
||||
<Option value="2">猎聘</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="登录名" prop="login_name">
|
||||
<Input v-model="formData.login_name" placeholder="请输入登录名" />
|
||||
</FormItem>
|
||||
<FormItem label="密码" prop="pwd">
|
||||
<Input v-model="formData.pwd" type="password" placeholder="请输入密码" />
|
||||
</FormItem>
|
||||
<FormItem label="搜索关键词">
|
||||
<Input v-model="formData.keyword" placeholder="请输入搜索关键词" />
|
||||
</FormItem>
|
||||
</Card>
|
||||
|
||||
<!-- 编辑时才显示的配置项 -->
|
||||
<template v-if="isEdit">
|
||||
<Card title="基础配置" style="margin-bottom: 16px;">
|
||||
<FormItem label="启用状态">
|
||||
<i-switch v-model="formData.is_enabled" :true-value="1" :false-value="0" />
|
||||
</FormItem>
|
||||
<FormItem label="职位类型">
|
||||
<Select v-model="formData.job_type_id" placeholder="请选择职位类型" clearable>
|
||||
<Option v-for="item in jobTypeOptions" :key="item.value" :value="item.value">
|
||||
{{ item.label }}
|
||||
</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="用户地址">
|
||||
<Input v-model="formData.user_address" placeholder="请输入用户地址,如:北京市朝阳区" />
|
||||
</FormItem>
|
||||
</Card>
|
||||
|
||||
<Card title="排序优先级配置" style="margin-bottom: 16px;">
|
||||
<FormItem label="排序优先级">
|
||||
<div class="priority-config">
|
||||
<div v-for="(item, index) in priorityList" :key="item.key" class="priority-item">
|
||||
<div class="priority-label">
|
||||
<Icon type="ios-menu" class="drag-handle" />
|
||||
<span>{{ getPriorityLabel(item.key) }}</span>
|
||||
</div>
|
||||
<div class="priority-control">
|
||||
<Slider
|
||||
v-model="item.weight"
|
||||
:min="0"
|
||||
:max="100"
|
||||
:step="1"
|
||||
show-input
|
||||
:show-tooltip="true"
|
||||
style="width: 200px; margin-right: 16px;"
|
||||
/>
|
||||
<span class="priority-value">{{ item.weight }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="priority-total">
|
||||
<span>总权重:<strong :class="{'weight-warning': totalWeight !== 100}">{{ totalWeight }}%</strong></span>
|
||||
<Button v-if="totalWeight !== 100" type="text" size="small" @click="normalizeWeights">自动平衡</Button>
|
||||
</div>
|
||||
</div>
|
||||
</FormItem>
|
||||
</Card>
|
||||
|
||||
<Card title="自动投递配置" style="margin-bottom: 16px;">
|
||||
<FormItem label="自动投递">
|
||||
<RadioGroup v-model="formData.auto_deliver">
|
||||
<Radio :label="1">开启</Radio>
|
||||
<Radio :label="0">关闭</Radio>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem label="投递间隔(分钟)">
|
||||
<InputNumber v-model="formData.deliver_interval" :min="1" placeholder="默认30分钟" style="width: 100%;" />
|
||||
</FormItem>
|
||||
<FormItem label="最低薪资(元)">
|
||||
<InputNumber v-model="formData.min_salary" :min="0" placeholder="0表示不限制" style="width: 100%;" />
|
||||
</FormItem>
|
||||
<FormItem label="最高薪资(元)">
|
||||
<InputNumber v-model="formData.max_salary" :min="0" placeholder="0表示不限制" style="width: 100%;" />
|
||||
</FormItem>
|
||||
<FormItem label="滚动页数">
|
||||
<InputNumber v-model="formData.page_count" :min="1" placeholder="默认3次" style="width: 100%;" />
|
||||
</FormItem>
|
||||
<FormItem label="每次最多投递数">
|
||||
<InputNumber v-model="formData.max_deliver" :min="1" placeholder="默认10个" style="width: 100%;" />
|
||||
</FormItem>
|
||||
<FormItem label="过滤关键词">
|
||||
<Input v-model="formData.filter_keywords" placeholder="包含这些关键词的职位会被优先考虑,多个用逗号分隔" />
|
||||
</FormItem>
|
||||
<FormItem label="排除关键词">
|
||||
<Input v-model="formData.exclude_keywords" placeholder="包含这些关键词的职位会被排除,多个用逗号分隔" />
|
||||
</FormItem>
|
||||
</Card>
|
||||
|
||||
<Card title="自动沟通配置" style="margin-bottom: 16px;">
|
||||
<FormItem label="自动沟通">
|
||||
<RadioGroup v-model="formData.auto_chat">
|
||||
<Radio :label="1">开启</Radio>
|
||||
<Radio :label="0">关闭</Radio>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem label="沟通间隔(分钟)">
|
||||
<InputNumber v-model="formData.chat_interval" :min="1" placeholder="默认30分钟" style="width: 100%;" />
|
||||
</FormItem>
|
||||
<FormItem label="是否沟通外包岗位">
|
||||
<RadioGroup v-model="formData.is_chat_outsourcing">
|
||||
<Radio :label="1">是</Radio>
|
||||
<Radio :label="0">否</Radio>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem label="沟通开始时间">
|
||||
<Input v-model="formData.chat_start_time" placeholder="格式:HH:mm,如:09:00" />
|
||||
</FormItem>
|
||||
<FormItem label="沟通结束时间">
|
||||
<Input v-model="formData.chat_end_time" placeholder="格式:HH:mm,如:18:00" />
|
||||
</FormItem>
|
||||
<FormItem label="是否仅工作日">
|
||||
<RadioGroup v-model="formData.chat_workdays_only">
|
||||
<Radio :label="1">仅工作日</Radio>
|
||||
<Radio :label="0">包含周末</Radio>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
</Card>
|
||||
|
||||
<Card title="自动活跃配置" style="margin-bottom: 16px;">
|
||||
<FormItem label="自动活跃">
|
||||
<RadioGroup v-model="formData.auto_active">
|
||||
<Radio :label="1">开启</Radio>
|
||||
<Radio :label="0">关闭</Radio>
|
||||
</RadioGroup>
|
||||
</FormItem>
|
||||
<FormItem label="活跃间隔(分钟)">
|
||||
<InputNumber v-model="formData.active_interval" :min="1" placeholder="默认60分钟" style="width: 100%;" />
|
||||
</FormItem>
|
||||
<FormItem label="活跃动作配置">
|
||||
<Input
|
||||
v-model="formData.active_actions_json"
|
||||
type="textarea"
|
||||
:rows="4"
|
||||
placeholder='JSON格式的活跃动作列表,如:[{"type":"view","count":5}]'
|
||||
/>
|
||||
</FormItem>
|
||||
</Card>
|
||||
</template>
|
||||
</Form>
|
||||
</div>
|
||||
</FloatPanel>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import plaAccountServer from '@/api/profile/pla_account_server.js'
|
||||
import jobTypesServer from '@/api/work/job_types_server.js'
|
||||
|
||||
export default {
|
||||
name: 'PlaAccountEdit',
|
||||
props: {
|
||||
jobTypeOptions: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isEdit: false,
|
||||
saving: false,
|
||||
formData: {
|
||||
id: null,
|
||||
name: '',
|
||||
sn_code: '',
|
||||
platform_type: '',
|
||||
login_name: '',
|
||||
pwd: '',
|
||||
keyword: '',
|
||||
// 编辑时才有的字段
|
||||
is_enabled: 1,
|
||||
job_type_id: null,
|
||||
user_address: '',
|
||||
is_salary_priority: '',
|
||||
auto_deliver: 0,
|
||||
deliver_interval: 30,
|
||||
min_salary: 0,
|
||||
max_salary: 0,
|
||||
page_count: 3,
|
||||
max_deliver: 10,
|
||||
filter_keywords: '',
|
||||
exclude_keywords: '',
|
||||
auto_chat: 0,
|
||||
chat_interval: 30,
|
||||
is_chat_outsourcing: 0,
|
||||
chat_start_time: '09:00',
|
||||
chat_end_time: '18:00',
|
||||
chat_workdays_only: 1,
|
||||
auto_active: 0,
|
||||
active_interval: 60,
|
||||
active_actions_json: ''
|
||||
},
|
||||
// 排序优先级列表
|
||||
priorityList: [
|
||||
{ key: 'distance', weight: 50 },
|
||||
{ key: 'salary', weight: 20 },
|
||||
{ key: 'work_years', weight: 10 },
|
||||
{ key: 'education', weight: 20 }
|
||||
],
|
||||
rules: {
|
||||
name: [{ required: true, message: '请填写账户名', trigger: 'blur' }],
|
||||
sn_code: [{ required: true, message: '请填写设备SN码', trigger: 'blur' }],
|
||||
platform_type: [{ required: true, message: '请选择平台', trigger: 'change' }],
|
||||
login_name: [{ required: true, message: '请填写登录名', trigger: 'blur' }]
|
||||
},
|
||||
jobTypeOptions: []
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadJobTypes()
|
||||
},
|
||||
methods: {
|
||||
// 显示新增表单
|
||||
showAdd() {
|
||||
this.isEdit = false
|
||||
this.resetForm()
|
||||
this.$refs.floatPanel.show()
|
||||
},
|
||||
// 显示编辑表单
|
||||
showEdit(row) {
|
||||
this.isEdit = true
|
||||
this.resetForm()
|
||||
|
||||
// 复制基础字段(包含 id)
|
||||
this.formData.id = row.id
|
||||
this.formData.name = row.name || ''
|
||||
this.formData.sn_code = row.sn_code || ''
|
||||
this.formData.platform_type = row.platform_type || ''
|
||||
this.formData.login_name = row.login_name || ''
|
||||
this.formData.pwd = row.pwd || ''
|
||||
this.formData.keyword = row.keyword || ''
|
||||
|
||||
// 复制编辑字段
|
||||
this.formData.is_enabled = row.is_enabled !== undefined ? row.is_enabled : 1
|
||||
this.formData.job_type_id = row.job_type_id || null
|
||||
this.formData.user_address = row.user_address || ''
|
||||
|
||||
// 处理 JSON 配置字段
|
||||
this.processJsonFields(row)
|
||||
|
||||
this.$refs.floatPanel.show()
|
||||
},
|
||||
// 处理 JSON 配置字段
|
||||
processJsonFields(row) {
|
||||
// is_salary_priority 配置
|
||||
if (row.is_salary_priority) {
|
||||
const priorityConfig = typeof row.is_salary_priority === 'string'
|
||||
? JSON.parse(row.is_salary_priority)
|
||||
: row.is_salary_priority
|
||||
// 更新优先级列表
|
||||
if (Array.isArray(priorityConfig)) {
|
||||
this.priorityList = priorityConfig.map(item => ({
|
||||
key: item.key,
|
||||
weight: item.weight || 0
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
// 使用默认值
|
||||
this.priorityList = [
|
||||
{ key: 'distance', weight: 50 },
|
||||
{ key: 'salary', weight: 20 },
|
||||
{ key: 'work_years', weight: 10 },
|
||||
{ key: 'education', weight: 20 }
|
||||
]
|
||||
}
|
||||
|
||||
// deliver_config 配置
|
||||
if (row.deliver_config) {
|
||||
const deliverConfig = typeof row.deliver_config === 'string'
|
||||
? JSON.parse(row.deliver_config)
|
||||
: row.deliver_config
|
||||
this.formData.deliver_interval = deliverConfig.deliver_interval || 30
|
||||
this.formData.min_salary = deliverConfig.min_salary || 0
|
||||
this.formData.max_salary = deliverConfig.max_salary || 0
|
||||
this.formData.page_count = deliverConfig.page_count || 3
|
||||
this.formData.max_deliver = deliverConfig.max_deliver || 10
|
||||
this.formData.filter_keywords = Array.isArray(deliverConfig.filter_keywords)
|
||||
? deliverConfig.filter_keywords.join(',')
|
||||
: (deliverConfig.filter_keywords || '')
|
||||
this.formData.exclude_keywords = Array.isArray(deliverConfig.exclude_keywords)
|
||||
? deliverConfig.exclude_keywords.join(',')
|
||||
: (deliverConfig.exclude_keywords || '')
|
||||
}
|
||||
|
||||
// chat_strategy 配置
|
||||
if (row.chat_strategy) {
|
||||
const chatStrategy = typeof row.chat_strategy === 'string'
|
||||
? JSON.parse(row.chat_strategy)
|
||||
: row.chat_strategy
|
||||
this.formData.chat_interval = chatStrategy.chat_interval || 30
|
||||
this.formData.is_chat_outsourcing = chatStrategy.is_chat_outsourcing !== undefined ? chatStrategy.is_chat_outsourcing : 0
|
||||
if (chatStrategy.time_range) {
|
||||
this.formData.chat_start_time = chatStrategy.time_range.start_time || '09:00'
|
||||
this.formData.chat_end_time = chatStrategy.time_range.end_time || '18:00'
|
||||
this.formData.chat_workdays_only = chatStrategy.time_range.workdays_only !== undefined ? chatStrategy.time_range.workdays_only : 1
|
||||
}
|
||||
}
|
||||
|
||||
// active_actions 配置
|
||||
if (row.active_actions) {
|
||||
const activeActions = typeof row.active_actions === 'string'
|
||||
? JSON.parse(row.active_actions)
|
||||
: row.active_actions
|
||||
this.formData.active_interval = activeActions.active_interval || 60
|
||||
this.formData.active_actions_json = JSON.stringify(activeActions.actions || [], null, 2)
|
||||
} else {
|
||||
this.formData.active_actions_json = JSON.stringify([], null, 2)
|
||||
}
|
||||
},
|
||||
// 重置表单
|
||||
resetForm() {
|
||||
this.formData = {
|
||||
id: null,
|
||||
name: '',
|
||||
sn_code: '',
|
||||
platform_type: '',
|
||||
login_name: '',
|
||||
pwd: '',
|
||||
keyword: '',
|
||||
is_enabled: 1,
|
||||
job_type_id: null,
|
||||
user_address: '',
|
||||
is_salary_priority: '',
|
||||
auto_deliver: 0,
|
||||
deliver_interval: 30,
|
||||
min_salary: 0,
|
||||
max_salary: 0,
|
||||
page_count: 3,
|
||||
max_deliver: 10,
|
||||
filter_keywords: '',
|
||||
exclude_keywords: '',
|
||||
auto_chat: 0,
|
||||
chat_interval: 30,
|
||||
is_chat_outsourcing: 0,
|
||||
chat_start_time: '09:00',
|
||||
chat_end_time: '18:00',
|
||||
chat_workdays_only: 1,
|
||||
auto_active: 0,
|
||||
active_interval: 60,
|
||||
active_actions_json: JSON.stringify([], null, 2)
|
||||
}
|
||||
// 重置优先级列表
|
||||
this.priorityList = [
|
||||
{ key: 'distance', weight: 50 },
|
||||
{ key: 'salary', weight: 20 },
|
||||
{ key: 'work_years', weight: 10 },
|
||||
{ key: 'education', weight: 20 }
|
||||
]
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.resetFields()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 返回
|
||||
handleBack() {
|
||||
this.$refs.floatPanel.hide()
|
||||
},
|
||||
// 保存
|
||||
async handleSave() {
|
||||
// 验证表单
|
||||
this.$refs.form.validate(async (valid) => {
|
||||
if (!valid) {
|
||||
this.$Message.error('请填写必填项')
|
||||
return
|
||||
}
|
||||
|
||||
this.saving = true
|
||||
try {
|
||||
const saveData = this.buildSaveData()
|
||||
|
||||
if (this.isEdit) {
|
||||
await plaAccountServer.update(saveData)
|
||||
} else {
|
||||
await plaAccountServer.add(saveData)
|
||||
}
|
||||
|
||||
this.$Message.success('保存成功!')
|
||||
this.$refs.floatPanel.hide()
|
||||
this.$emit('on-save')
|
||||
} catch (error) {
|
||||
console.error('保存失败:', error)
|
||||
this.$Message.error('保存失败:' + (error.message || '请稍后重试'))
|
||||
} finally {
|
||||
this.saving = false
|
||||
}
|
||||
})
|
||||
},
|
||||
// 构建保存数据
|
||||
buildSaveData() {
|
||||
const saveData = { ...this.formData }
|
||||
|
||||
// 新增时只需要基础字段
|
||||
if (!this.isEdit) {
|
||||
return {
|
||||
name: saveData.name,
|
||||
sn_code: saveData.sn_code,
|
||||
platform_type: saveData.platform_type,
|
||||
login_name: saveData.login_name,
|
||||
pwd: saveData.pwd,
|
||||
keyword: saveData.keyword
|
||||
}
|
||||
}
|
||||
|
||||
// 编辑时需要处理所有字段
|
||||
// 处理 is_salary_priority(从 priorityList 生成)
|
||||
saveData.is_salary_priority = this.priorityList.map(item => ({
|
||||
key: item.key,
|
||||
weight: Number(item.weight) || 0
|
||||
}))
|
||||
|
||||
// 处理 deliver_config
|
||||
const deliverConfig = {}
|
||||
deliverConfig.deliver_interval = Number(saveData.deliver_interval) || 30
|
||||
deliverConfig.min_salary = Number(saveData.min_salary) || 0
|
||||
deliverConfig.max_salary = Number(saveData.max_salary) || 0
|
||||
deliverConfig.page_count = Number(saveData.page_count) || 3
|
||||
deliverConfig.max_deliver = Number(saveData.max_deliver) || 10
|
||||
deliverConfig.filter_keywords = typeof saveData.filter_keywords === 'string' && saveData.filter_keywords.trim()
|
||||
? saveData.filter_keywords.split(',').map(k => k.trim()).filter(k => k)
|
||||
: []
|
||||
deliverConfig.exclude_keywords = typeof saveData.exclude_keywords === 'string' && saveData.exclude_keywords.trim()
|
||||
? saveData.exclude_keywords.split(',').map(k => k.trim()).filter(k => k)
|
||||
: []
|
||||
saveData.deliver_config = deliverConfig
|
||||
delete saveData.deliver_interval
|
||||
delete saveData.min_salary
|
||||
delete saveData.max_salary
|
||||
delete saveData.page_count
|
||||
delete saveData.max_deliver
|
||||
delete saveData.filter_keywords
|
||||
delete saveData.exclude_keywords
|
||||
|
||||
// 处理 chat_strategy
|
||||
const chatStrategy = {}
|
||||
chatStrategy.chat_interval = Number(saveData.chat_interval) || 30
|
||||
chatStrategy.is_chat_outsourcing = saveData.is_chat_outsourcing ? 1 : 0
|
||||
chatStrategy.time_range = {
|
||||
start_time: saveData.chat_start_time || '09:00',
|
||||
end_time: saveData.chat_end_time || '18:00',
|
||||
workdays_only: saveData.chat_workdays_only ? 1 : 0
|
||||
}
|
||||
saveData.chat_strategy = chatStrategy
|
||||
delete saveData.chat_interval
|
||||
delete saveData.is_chat_outsourcing
|
||||
delete saveData.chat_start_time
|
||||
delete saveData.chat_end_time
|
||||
delete saveData.chat_workdays_only
|
||||
|
||||
// 处理 active_actions
|
||||
const activeActions = {}
|
||||
activeActions.active_interval = Number(saveData.active_interval) || 60
|
||||
if (saveData.active_actions_json) {
|
||||
try {
|
||||
activeActions.actions = typeof saveData.active_actions_json === 'string'
|
||||
? JSON.parse(saveData.active_actions_json)
|
||||
: saveData.active_actions_json
|
||||
if (!Array.isArray(activeActions.actions)) {
|
||||
activeActions.actions = []
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('解析活跃动作配置失败:', e)
|
||||
this.$Message.warning('活跃动作配置格式错误,将使用空数组')
|
||||
activeActions.actions = []
|
||||
}
|
||||
} else {
|
||||
activeActions.actions = []
|
||||
}
|
||||
saveData.active_actions = activeActions
|
||||
delete saveData.active_interval
|
||||
delete saveData.active_actions_json
|
||||
|
||||
return saveData
|
||||
},
|
||||
// 加载职位类型
|
||||
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)
|
||||
}
|
||||
},
|
||||
// 获取优先级标签
|
||||
getPriorityLabel(key) {
|
||||
const labelMap = {
|
||||
'distance': '距离',
|
||||
'salary': '薪资',
|
||||
'work_years': '工作年限',
|
||||
'education': '学历'
|
||||
}
|
||||
return labelMap[key] || key
|
||||
},
|
||||
// 自动平衡权重
|
||||
normalizeWeights() {
|
||||
const total = this.totalWeight
|
||||
if (total === 0) {
|
||||
// 如果总权重为0,平均分配
|
||||
const avgWeight = Math.floor(100 / this.priorityList.length)
|
||||
this.priorityList.forEach(item => {
|
||||
item.weight = avgWeight
|
||||
})
|
||||
// 处理余数
|
||||
const remainder = 100 - (avgWeight * this.priorityList.length)
|
||||
if (remainder > 0 && this.priorityList.length > 0) {
|
||||
this.priorityList[0].weight += remainder
|
||||
}
|
||||
} else {
|
||||
// 按比例缩放
|
||||
this.priorityList.forEach(item => {
|
||||
item.weight = Math.round((item.weight / total) * 100)
|
||||
})
|
||||
// 确保总和为100
|
||||
const newTotal = this.priorityList.reduce((sum, item) => sum + item.weight, 0)
|
||||
if (newTotal !== 100 && this.priorityList.length > 0) {
|
||||
const diff = 100 - newTotal
|
||||
this.priorityList[0].weight += diff
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算总权重
|
||||
totalWeight() {
|
||||
return this.priorityList.reduce((sum, item) => sum + (Number(item.weight) || 0), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.account-edit-form {
|
||||
padding: 16px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.priority-config {
|
||||
padding: 16px;
|
||||
background: #f8f8f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.priority-item {
|
||||
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:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.priority-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 120px;
|
||||
font-weight: 500;
|
||||
color: #515a6e;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
margin-right: 8px;
|
||||
color: #c5c8ce;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
.priority-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.priority-value {
|
||||
min-width: 50px;
|
||||
text-align: right;
|
||||
color: #2d8cf0;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.priority-total {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #e8eaec;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.weight-warning {
|
||||
color: #ed4014;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -19,6 +19,121 @@
|
||||
</Select>
|
||||
</Card>
|
||||
|
||||
<!-- 账户信息卡片 -->
|
||||
<Card v-if="accountInfo.id" style="margin-bottom: 16px;" class="account-info-card">
|
||||
<div slot="title" style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<span>账户信息</span>
|
||||
<Button type="primary" size="small" @click="viewAccountDetail">查看详情</Button>
|
||||
</div>
|
||||
<Row :gutter="16" style="margin-bottom: 16px;">
|
||||
<Col span="6">
|
||||
<div class="account-info-item">
|
||||
<div class="info-label">账户名</div>
|
||||
<div class="info-value">{{ accountInfo.name || '-' }}</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<div class="account-info-item">
|
||||
<div class="info-label">平台</div>
|
||||
<div class="info-value">
|
||||
<Tag :color="getPlatformColor(accountInfo.platform_type)">
|
||||
{{ getPlatformText(accountInfo.platform_type) }}
|
||||
</Tag>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<div class="account-info-item">
|
||||
<div class="info-label">登录名</div>
|
||||
<div class="info-value">{{ accountInfo.login_name || '-' }}</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span="6">
|
||||
<div class="account-info-item">
|
||||
<div class="info-label">设备SN码</div>
|
||||
<div class="info-value">{{ accountInfo.sn_code || '-' }}</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider style="margin: 12px 0;"></Divider>
|
||||
<Row :gutter="16">
|
||||
<Col span="4">
|
||||
<div class="account-switch-item">
|
||||
<div class="switch-label">是否启用</div>
|
||||
<i-switch
|
||||
v-model="accountInfo.is_enabled"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
@on-change="handleSwitchChange('is_enabled', $event)"
|
||||
:loading="switchLoading.is_enabled"
|
||||
></i-switch>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span="4">
|
||||
<div class="account-switch-item">
|
||||
<div class="switch-label">是否在线</div>
|
||||
<i-switch
|
||||
v-model="accountInfo.is_online"
|
||||
:true-value="true"
|
||||
:false-value="false"
|
||||
@on-change="handleSwitchChange('is_online', $event)"
|
||||
:loading="switchLoading.is_online"
|
||||
:disabled="true"
|
||||
></i-switch>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span="4">
|
||||
<div class="account-switch-item">
|
||||
<div class="switch-label">是否登录</div>
|
||||
<i-switch
|
||||
v-model="accountInfo.is_logged_in"
|
||||
:true-value="true"
|
||||
:false-value="false"
|
||||
@on-change="handleSwitchChange('is_logged_in', $event)"
|
||||
:loading="switchLoading.is_logged_in"
|
||||
:disabled="true"
|
||||
></i-switch>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span="4">
|
||||
<div class="account-switch-item">
|
||||
<div class="switch-label">自动投递</div>
|
||||
<i-switch
|
||||
v-model="accountInfo.auto_deliver"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
@on-change="handleSwitchChange('auto_deliver', $event)"
|
||||
:loading="switchLoading.auto_deliver"
|
||||
></i-switch>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span="4">
|
||||
<div class="account-switch-item">
|
||||
<div class="switch-label">自动沟通</div>
|
||||
<i-switch
|
||||
v-model="accountInfo.auto_chat"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
@on-change="handleSwitchChange('auto_chat', $event)"
|
||||
:loading="switchLoading.auto_chat"
|
||||
></i-switch>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span="4">
|
||||
<div class="account-switch-item">
|
||||
<div class="switch-label">自动活跃</div>
|
||||
<i-switch
|
||||
v-model="accountInfo.auto_active"
|
||||
:true-value="1"
|
||||
:false-value="0"
|
||||
@on-change="handleSwitchChange('auto_active', $event)"
|
||||
:loading="switchLoading.auto_active"
|
||||
></i-switch>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<!-- 统计卡片 -->
|
||||
<Row :gutter="16" style="margin-bottom: 16px;">
|
||||
<Col span="6">
|
||||
@@ -56,9 +171,22 @@
|
||||
</Row>
|
||||
|
||||
<!-- 当前执行任务的命令区块(指令列表) -->
|
||||
<Card v-if="runningTasks.length > 0" style="margin-bottom: 16px;">
|
||||
<Card style="margin-bottom: 16px;">
|
||||
<p slot="title">当前执行中的任务</p>
|
||||
<Table :columns="taskColumns" :data="commandTableData" :loading="taskLoading"></Table>
|
||||
<Table
|
||||
v-if="runningTasks.length > 0"
|
||||
:columns="taskColumns"
|
||||
:data="commandTableData"
|
||||
:loading="taskLoading"
|
||||
></Table>
|
||||
<div v-else-if="!taskLoading" style="text-align: center; padding: 40px; color: #999;">
|
||||
<Icon type="ios-list-outline" size="48" />
|
||||
<p style="margin-top: 16px; font-size: 14px;">暂无执行中的任务</p>
|
||||
</div>
|
||||
<div v-else style="text-align: center; padding: 40px;">
|
||||
<Spin size="large"></Spin>
|
||||
<p style="margin-top: 16px; color: #999;">加载中...</p>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<!-- 趋势图表(7天趋势) -->
|
||||
@@ -73,6 +201,7 @@
|
||||
import * as echarts from 'echarts'
|
||||
import DeviceStatusServer from '../../api/device/device_status_server.js'
|
||||
import StatisticsServer from '../../api/statistics/statistics_server.js'
|
||||
import PlaAccountServer from '../../api/profile/pla_account_server.js'
|
||||
|
||||
export default {
|
||||
name: 'HomePage',
|
||||
@@ -98,7 +227,16 @@ export default {
|
||||
taskLoading: false,
|
||||
chartInstance: null, // ECharts实例
|
||||
taskColumns: [],
|
||||
refreshTimer: null
|
||||
refreshTimer: null,
|
||||
accountInfo: {}, // 账户信息
|
||||
switchLoading: {
|
||||
is_enabled: false,
|
||||
is_online: false,
|
||||
is_logged_in: false,
|
||||
auto_deliver: false,
|
||||
auto_chat: false,
|
||||
auto_active: false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -220,7 +358,8 @@ export default {
|
||||
await Promise.all([
|
||||
this.loadTodayStats(),
|
||||
this.loadChartData(),
|
||||
this.loadRunningTasks()
|
||||
this.loadRunningTasks(),
|
||||
this.loadAccountInfo()
|
||||
])
|
||||
},
|
||||
|
||||
@@ -567,6 +706,155 @@ export default {
|
||||
// 计算进度百分比
|
||||
const progress = Math.round((completedCount / commands.length) * 100)
|
||||
return progress
|
||||
},
|
||||
|
||||
// 加载账户信息
|
||||
async loadAccountInfo() {
|
||||
if (!this.selectedDeviceSn) {
|
||||
this.accountInfo = {}
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
// 根据 sn_code 查询账户信息
|
||||
const res = await PlaAccountServer.page({
|
||||
seachOption: {
|
||||
key: 'sn_code',
|
||||
value: this.selectedDeviceSn
|
||||
},
|
||||
pageOption: {
|
||||
page: 1,
|
||||
pageSize: 1
|
||||
}
|
||||
})
|
||||
|
||||
if (res.code === 0 && res.data && res.data.rows && res.data.rows.length > 0) {
|
||||
const accountData = res.data.rows[0]
|
||||
|
||||
// 如果接口没有返回设备状态,则单独查询
|
||||
if (accountData.is_online === undefined || accountData.is_logged_in === undefined) {
|
||||
try {
|
||||
const deviceRes = await DeviceStatusServer.getById(this.selectedDeviceSn)
|
||||
if (deviceRes.code === 0 && deviceRes.data) {
|
||||
accountData.is_online = deviceRes.data.isOnline || false
|
||||
accountData.is_logged_in = deviceRes.data.isLoggedIn || false
|
||||
} else {
|
||||
accountData.is_online = false
|
||||
accountData.is_logged_in = false
|
||||
}
|
||||
} catch (deviceError) {
|
||||
console.error('获取设备状态失败:', deviceError)
|
||||
accountData.is_online = false
|
||||
accountData.is_logged_in = false
|
||||
}
|
||||
}
|
||||
|
||||
// 确保布尔值字段正确初始化
|
||||
accountData.is_enabled = accountData.is_enabled !== undefined ? accountData.is_enabled : 1
|
||||
accountData.auto_deliver = accountData.auto_deliver !== undefined ? accountData.auto_deliver : 0
|
||||
accountData.auto_chat = accountData.auto_chat !== undefined ? accountData.auto_chat : 0
|
||||
accountData.auto_active = accountData.auto_active !== undefined ? accountData.auto_active : 0
|
||||
|
||||
this.accountInfo = accountData
|
||||
} else {
|
||||
this.accountInfo = {}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载账户信息失败:', error)
|
||||
this.accountInfo = {}
|
||||
}
|
||||
},
|
||||
|
||||
// 处理开关变化
|
||||
async handleSwitchChange(field, value) {
|
||||
if (!this.accountInfo.id) {
|
||||
this.$Message.warning('账户信息不存在')
|
||||
return
|
||||
}
|
||||
|
||||
// is_online 和 is_logged_in 是只读的,不应该被修改
|
||||
if (field === 'is_online' || field === 'is_logged_in') {
|
||||
this.$Message.warning('该状态由设备状态决定,无法手动修改')
|
||||
// 恢复原值
|
||||
this.$nextTick(() => {
|
||||
if (field === 'is_online') {
|
||||
this.accountInfo.is_online = !value
|
||||
} else {
|
||||
this.accountInfo.is_logged_in = !value
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 设置加载状态
|
||||
this.switchLoading[field] = true
|
||||
|
||||
try {
|
||||
// 确保 value 是数字类型(0 或 1)
|
||||
const numericValue = value ? 1 : 0
|
||||
|
||||
// 构建更新数据,确保是纯对象,只包含基本类型
|
||||
const updateData = {
|
||||
id: Number(this.accountInfo.id),
|
||||
[field]: numericValue
|
||||
}
|
||||
|
||||
// 调用更新接口
|
||||
await PlaAccountServer.update(updateData)
|
||||
|
||||
// 更新本地数据
|
||||
this.accountInfo[field] = numericValue
|
||||
|
||||
this.$Message.success('更新成功')
|
||||
} catch (error) {
|
||||
console.error(`更新${field}失败:`, error)
|
||||
const errorMsg = error.response?.data?.message || error.message || '请稍后重试'
|
||||
this.$Message.error('更新失败:' + errorMsg)
|
||||
|
||||
// 恢复原值
|
||||
this.$nextTick(() => {
|
||||
const oldValue = this.accountInfo[field]
|
||||
if (field === 'is_enabled') {
|
||||
this.accountInfo.is_enabled = oldValue === 1 ? 0 : 1
|
||||
} else if (field === 'auto_deliver') {
|
||||
this.accountInfo.auto_deliver = oldValue === 1 ? 0 : 1
|
||||
} else if (field === 'auto_chat') {
|
||||
this.accountInfo.auto_chat = oldValue === 1 ? 0 : 1
|
||||
} else if (field === 'auto_active') {
|
||||
this.accountInfo.auto_active = oldValue === 1 ? 0 : 1
|
||||
}
|
||||
})
|
||||
} finally {
|
||||
this.switchLoading[field] = false
|
||||
}
|
||||
},
|
||||
|
||||
// 查看账户详情
|
||||
viewAccountDetail() {
|
||||
if (this.accountInfo.id) {
|
||||
this.$router.push({
|
||||
path: '/pla_account/pla_account_detail',
|
||||
query: { id: this.accountInfo.id }
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
// 获取平台颜色
|
||||
getPlatformColor(platformType) {
|
||||
const colorMap = {
|
||||
'1': 'blue',
|
||||
'2': 'green'
|
||||
}
|
||||
return colorMap[platformType] || 'default'
|
||||
},
|
||||
|
||||
// 获取平台文本
|
||||
getPlatformText(platformType) {
|
||||
const textMap = {
|
||||
'1': 'Boss直聘',
|
||||
'2': '猎聘'
|
||||
}
|
||||
return textMap[platformType] || platformType || '-'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -592,4 +880,43 @@ export default {
|
||||
font-weight: bold;
|
||||
color: #2d8cf0;
|
||||
}
|
||||
|
||||
.account-info-card {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.account-info-card:hover {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.account-info-item {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.account-switch-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.switch-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -147,6 +147,8 @@ module.exports = {
|
||||
'POST /account/update': async (ctx) => {
|
||||
const body = ctx.getBody();
|
||||
const { id, ...updateData } = body;
|
||||
|
||||
|
||||
await plaAccountService.updateAccount(id, updateData);
|
||||
return ctx.success({ message: '账号信息更新成功' });
|
||||
},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
const dayjs = require('dayjs');
|
||||
const config = require('../../../config/config');
|
||||
|
||||
/**
|
||||
* 调度系统配置中心
|
||||
@@ -44,46 +43,16 @@ class ScheduleConfig {
|
||||
|
||||
// 监控配置
|
||||
this.monitoring = {
|
||||
heartbeatTimeout: 3 * 60 * 1000, // 心跳超时:5分钟
|
||||
taskFailureRate: 0.5, // 任务失败率:50%
|
||||
consecutiveFailures: 3, // 连续失败次数:3次
|
||||
alertCooldown: 5 * 60 * 1000, // 告警冷却:5分钟
|
||||
heartbeatTimeout: 3 * 60 * 1000, // 心跳超时:3分钟
|
||||
offlineThreshold: 24 * 60 * 60 * 1000 // 离线设备清理:24小时
|
||||
};
|
||||
|
||||
// 定时任务配置
|
||||
this.schedules = {
|
||||
dailyReset: '0 0 * * *', // 每天凌晨重置统计
|
||||
jobFlowInterval: '0 */5 * * * *', // 每10秒执行一次找工作流程
|
||||
monitoringInterval: '*/1 * * * *', // 监控检查间隔:1分钟
|
||||
autoDeliver: '0 */5 * * * *', // 自动投递任务:每5分钟执行一次
|
||||
// 监控检查间隔:1分钟
|
||||
autoDeliver: '0 */1 * * * *' // 自动投递任务:每5分钟执行一次
|
||||
};
|
||||
|
||||
// 测试配置覆盖
|
||||
if (config.test) {
|
||||
this.applyTestConfig(config.test);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 应用测试配置
|
||||
* @param {object} testConfig - 测试配置
|
||||
*/
|
||||
applyTestConfig(testConfig) {
|
||||
if (testConfig.skipWorkStartHour) {
|
||||
this.workHours.start = 0;
|
||||
this.workHours.end = 24;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 测试模式下缩短所有间隔时间
|
||||
if (testConfig.fastMode) {
|
||||
this.rateLimits.search = 10 * 1000; // 10秒
|
||||
this.rateLimits.apply = 5 * 1000; // 5秒
|
||||
this.rateLimits.chat = 2 * 1000; // 2秒
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const node_schedule = require("node-schedule");
|
||||
const dayjs = require('dayjs');
|
||||
const config = require('./config.js');
|
||||
const deviceManager = require('./deviceManager.js');
|
||||
const command = require('./command.js');
|
||||
@@ -331,6 +332,8 @@ class ScheduledJobs {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
if (!pla_users || pla_users.length === 0) {
|
||||
console.log('[自动投递] 没有启用且开启自动投递的账号');
|
||||
return;
|
||||
@@ -338,11 +341,13 @@ class ScheduledJobs {
|
||||
|
||||
console.log(`[自动投递] 找到 ${pla_users.length} 个可用账号`);
|
||||
|
||||
// 获取 task_status 模型用于查询上次投递时间
|
||||
const { task_status } = models;
|
||||
|
||||
// 为每个设备添加自动投递任务到队列
|
||||
for (const pl_user of pla_users) {
|
||||
const userData = pl_user.toJSON();
|
||||
|
||||
|
||||
// 检查设备调度策略
|
||||
const canExecute = deviceManager.canExecuteOperation(userData.sn_code, 'deliver');
|
||||
if (!canExecute.allowed) {
|
||||
@@ -350,6 +355,44 @@ class ScheduledJobs {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 获取投递配置,如果不存在则使用默认值
|
||||
const deliver_config = userData.deliver_config || {
|
||||
deliver_interval: 30,
|
||||
min_salary: 0,
|
||||
max_salary: 0,
|
||||
page_count: 3,
|
||||
max_deliver: 10,
|
||||
filter_keywords: [],
|
||||
exclude_keywords: []
|
||||
};
|
||||
|
||||
// 检查投递间隔时间
|
||||
const deliver_interval = deliver_config.deliver_interval || 30; // 默认30分钟
|
||||
const interval_ms = deliver_interval * 60 * 1000; // 转换为毫秒
|
||||
|
||||
// 查询该账号最近一次成功完成的自动投递任务
|
||||
const lastDeliverTask = await task_status.findOne({
|
||||
where: {
|
||||
sn_code: userData.sn_code,
|
||||
taskType: 'auto_deliver',
|
||||
status: 'completed'
|
||||
},
|
||||
order: [['endTime', 'DESC']],
|
||||
attributes: ['endTime']
|
||||
});
|
||||
|
||||
// 如果存在上次投递记录,检查是否已经过了间隔时间
|
||||
if (lastDeliverTask && lastDeliverTask.endTime) {
|
||||
const lastDeliverTime = new Date(lastDeliverTask.endTime);
|
||||
const elapsedTime = now.getTime() - lastDeliverTime.getTime();
|
||||
|
||||
if (elapsedTime < interval_ms) {
|
||||
const remainingMinutes = Math.ceil((interval_ms - elapsedTime) / (60 * 1000));
|
||||
console.log(`[自动投递] 设备 ${userData.sn_code} 距离上次投递仅 ${Math.round(elapsedTime / (60 * 1000))} 分钟,还需等待 ${remainingMinutes} 分钟(间隔: ${deliver_interval} 分钟)`);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 添加自动投递任务到队列
|
||||
await this.taskQueue.addTask(userData.sn_code, {
|
||||
taskType: 'auto_deliver',
|
||||
@@ -357,19 +400,19 @@ class ScheduledJobs {
|
||||
taskParams: {
|
||||
keyword: userData.keyword || '',
|
||||
platform: userData.platform_type || 'boss',
|
||||
pageCount: 3, // 默认值
|
||||
maxCount: 10, // 默认值
|
||||
pageCount: deliver_config.page_count || 3,
|
||||
maxCount: deliver_config.max_deliver || 10,
|
||||
filterRules: {
|
||||
minSalary: userData.min_salary || 0,
|
||||
maxSalary: userData.max_salary || 0,
|
||||
keywords: [],
|
||||
excludeKeywords: []
|
||||
minSalary: deliver_config.min_salary || 0,
|
||||
maxSalary: deliver_config.max_salary || 0,
|
||||
keywords: deliver_config.filter_keywords || [],
|
||||
excludeKeywords: deliver_config.exclude_keywords || []
|
||||
}
|
||||
},
|
||||
priority: config.getTaskPriority('auto_deliver') || 6
|
||||
});
|
||||
|
||||
console.log(`[自动投递] 已为设备 ${userData.sn_code} 添加自动投递任务,关键词: ${userData.keyword || '默认'}`);
|
||||
console.log(`[自动投递] 已为设备 ${userData.sn_code} 添加自动投递任务,关键词: ${userData.keyword || '默认'},投递间隔: ${deliver_interval} 分钟`);
|
||||
}
|
||||
|
||||
console.log('[自动投递] 任务添加完成');
|
||||
|
||||
@@ -44,7 +44,7 @@ class TaskHandlers {
|
||||
|
||||
async handleAutoDeliverTask(task) {
|
||||
const { sn_code, taskParams } = task;
|
||||
const { keyword, platform, pageCount, maxCount } = taskParams;
|
||||
const { keyword, platform, pageCount, maxCount, filterRules = {} } = taskParams;
|
||||
|
||||
console.log(`[任务处理器] 自动投递任务 - 设备: ${sn_code}, 关键词: ${keyword}`);
|
||||
|
||||
@@ -181,11 +181,22 @@ class TaskHandlers {
|
||||
|
||||
// 5. 根据简历信息、职位类型配置和权重配置进行评分和过滤
|
||||
const scoredJobs = [];
|
||||
const excludeKeywords = jobTypeConfig && jobTypeConfig.excludeKeywords
|
||||
|
||||
// 合并排除关键词:从职位类型配置和任务参数中获取
|
||||
const jobTypeExcludeKeywords = jobTypeConfig && jobTypeConfig.excludeKeywords
|
||||
? (typeof jobTypeConfig.excludeKeywords === 'string'
|
||||
? JSON.parse(jobTypeConfig.excludeKeywords)
|
||||
: jobTypeConfig.excludeKeywords)
|
||||
: [];
|
||||
const taskExcludeKeywords = filterRules.excludeKeywords || [];
|
||||
const excludeKeywords = [...jobTypeExcludeKeywords, ...taskExcludeKeywords];
|
||||
|
||||
// 获取过滤关键词(用于优先匹配)
|
||||
const filterKeywords = filterRules.keywords || [];
|
||||
|
||||
// 获取薪资范围过滤
|
||||
const minSalary = filterRules.minSalary || 0;
|
||||
const maxSalary = filterRules.maxSalary || 0;
|
||||
|
||||
// 获取一个月内已投递的公司列表(用于过滤)
|
||||
const apply_records = db.getModel('apply_records');
|
||||
@@ -209,6 +220,20 @@ class TaskHandlers {
|
||||
for (const job of pendingJobs) {
|
||||
const jobData = job.toJSON ? job.toJSON() : job;
|
||||
|
||||
// 薪资范围过滤
|
||||
if (minSalary > 0 || maxSalary > 0) {
|
||||
const jobSalaryMin = jobData.salaryMin || 0;
|
||||
const jobSalaryMax = jobData.salaryMax || 0;
|
||||
|
||||
// 如果职位薪资范围与过滤范围没有交集,则跳过
|
||||
if (minSalary > 0 && jobSalaryMax > 0 && minSalary > jobSalaryMax) {
|
||||
continue;
|
||||
}
|
||||
if (maxSalary > 0 && jobSalaryMin > 0 && maxSalary < jobSalaryMin) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// 排除关键词过滤
|
||||
if (Array.isArray(excludeKeywords) && excludeKeywords.length > 0) {
|
||||
const jobText = `${jobData.jobTitle} ${jobData.companyName} ${jobData.jobDescription || ''}`.toLowerCase();
|
||||
@@ -233,12 +258,28 @@ class TaskHandlers {
|
||||
priorityWeights
|
||||
);
|
||||
|
||||
// 如果配置了过滤关键词,给包含这些关键词的职位加分
|
||||
let keywordBonus = 0;
|
||||
if (Array.isArray(filterKeywords) && filterKeywords.length > 0) {
|
||||
const jobText = `${jobData.jobTitle} ${jobData.companyName} ${jobData.jobDescription || ''}`.toLowerCase();
|
||||
const matchedKeywords = filterKeywords.filter(kw => jobText.includes(kw.toLowerCase()));
|
||||
if (matchedKeywords.length > 0) {
|
||||
// 每匹配一个关键词加5分,最多加20分
|
||||
keywordBonus = Math.min(matchedKeywords.length * 5, 20);
|
||||
}
|
||||
}
|
||||
|
||||
const finalScore = scoreResult.totalScore + keywordBonus;
|
||||
|
||||
// 只保留总分 >= 60 的职位
|
||||
if (scoreResult.totalScore >= 60) {
|
||||
if (finalScore >= 60) {
|
||||
scoredJobs.push({
|
||||
...jobData,
|
||||
matchScore: scoreResult.totalScore,
|
||||
scoreDetails: scoreResult.scores
|
||||
matchScore: finalScore,
|
||||
scoreDetails: {
|
||||
...scoreResult.scores,
|
||||
keywordBonus: keywordBonus
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,12 +59,31 @@ module.exports = (db) => {
|
||||
type: Sequelize.JSON(),
|
||||
allowNull: false,
|
||||
get: function () {
|
||||
return JSON.parse(this.getDataValue('is_salary_priority'));
|
||||
const value = this.getDataValue('is_salary_priority');
|
||||
if (!value) {
|
||||
return [{ "key": "distance", "weight": 50 }, { "key": "salary", "weight": 20 }, { "key": "work_years", "weight": 10 }, { "key": "education", "weight": 20}];
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
return [{ "key": "distance", "weight": 50 }, { "key": "salary", "weight": 20 }, { "key": "work_years", "weight": 10 }, { "key": "education", "weight": 20}];
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: function (value) {
|
||||
if (value === null || value === undefined) {
|
||||
this.setDataValue('is_salary_priority', JSON.stringify([{ "key": "distance", "weight": 50 }, { "key": "salary", "weight": 20 }, { "key": "work_years", "weight": 10 }, { "key": "education", "weight": 20}]));
|
||||
} else if (typeof value === 'string') {
|
||||
// 如果已经是字符串,直接使用
|
||||
this.setDataValue('is_salary_priority', value);
|
||||
} else {
|
||||
// 如果是对象/数组,序列化为字符串
|
||||
this.setDataValue('is_salary_priority', JSON.stringify(value));
|
||||
}
|
||||
},
|
||||
defaultValue: [ { "key": "distance", "weight": 50 }, { "key": "salary", "weight": 20 }, { "key": "work_years", "weight": 10 }, { "key": "education", "weight": 20} ]
|
||||
defaultValue: JSON.stringify([{ "key": "distance", "weight": 50 }, { "key": "salary", "weight": 20 }, { "key": "work_years", "weight": 10 }, { "key": "education", "weight": 20}])
|
||||
},
|
||||
|
||||
|
||||
@@ -97,17 +116,56 @@ module.exports = (db) => {
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
min_salary: {
|
||||
comment: '最低薪资(单位:元)',
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
// 自动投递配置(JSON格式,包含:deliver_interval-投递间隔分钟数, min_salary-最低薪资, max_salary-最高薪资, page_count-滚动获取职位列表次数, max_deliver-每次最多投递数量, filter_keywords-过滤关键词, exclude_keywords-排除关键词)
|
||||
deliver_config: {
|
||||
comment: '自动投递配置(JSON对象)',
|
||||
type: Sequelize.JSON(),
|
||||
allowNull: true,
|
||||
get: function () {
|
||||
const value = this.getDataValue('deliver_config');
|
||||
if (!value) return null;
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
max_salary: {
|
||||
comment: '最高薪资(单位:元)',
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
set: function (value) {
|
||||
if (value === null || value === undefined) {
|
||||
this.setDataValue('deliver_config', null);
|
||||
} else if (typeof value === 'string') {
|
||||
// 如果已经是字符串,直接使用
|
||||
this.setDataValue('deliver_config', value);
|
||||
} else {
|
||||
// 如果是对象,序列化为字符串
|
||||
this.setDataValue('deliver_config', JSON.stringify(value));
|
||||
}
|
||||
},
|
||||
// 默认值说明:
|
||||
// deliver_interval: 30 - 投递间隔时间,单位:分钟,默认30分钟执行一次自动投递
|
||||
// min_salary: 0 - 最低薪资(单位:元),0表示不限制
|
||||
// max_salary: 0 - 最高薪资(单位:元),0表示不限制
|
||||
// page_count: 3 - 滚动获取职位列表次数,默认3次
|
||||
// max_deliver: 10 - 每次最多投递数量,默认10个
|
||||
// filter_keywords: [] - 过滤关键词数组,包含这些关键词的职位会被优先考虑
|
||||
// exclude_keywords: [] - 排除关键词数组,包含这些关键词的职位会被排除
|
||||
defaultValue: JSON.stringify({
|
||||
deliver_interval: 30,
|
||||
min_salary: 0,
|
||||
max_salary: 0,
|
||||
page_count: 3,
|
||||
max_deliver: 10,
|
||||
time_range: {
|
||||
start_time: '09:00',
|
||||
end_time: '18:00',
|
||||
workdays_only: 1
|
||||
},
|
||||
filter_keywords: [],
|
||||
exclude_keywords: []
|
||||
})
|
||||
},
|
||||
// 自动沟通相关配置
|
||||
auto_chat: {
|
||||
@@ -116,18 +174,52 @@ module.exports = (db) => {
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
chat_interval: {
|
||||
comment: '沟通间隔(单位:分钟)',
|
||||
type: Sequelize.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 30
|
||||
// 自动沟通配置(JSON格式,包含:chat_interval-沟通间隔分钟数, is_chat_outsourcing-是否沟通外包岗位, time_range-沟通时间段)
|
||||
chat_strategy: {
|
||||
comment: '自动沟通策略配置(JSON对象)',
|
||||
type: Sequelize.JSON(),
|
||||
allowNull: true,
|
||||
get: function () {
|
||||
const value = this.getDataValue('chat_strategy');
|
||||
if (!value) return null;
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
auto_reply: {
|
||||
comment: '自动回复开关',
|
||||
type: Sequelize.TINYINT(1),
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
set: function (value) {
|
||||
if (value === null || value === undefined) {
|
||||
this.setDataValue('chat_strategy', null);
|
||||
} else if (typeof value === 'string') {
|
||||
// 如果已经是字符串,直接使用
|
||||
this.setDataValue('chat_strategy', value);
|
||||
} else {
|
||||
// 如果是对象,序列化为字符串
|
||||
this.setDataValue('chat_strategy', JSON.stringify(value));
|
||||
}
|
||||
},
|
||||
// 默认值说明:
|
||||
// chat_interval: 30 - 沟通间隔时间,单位:分钟,默认30分钟执行一次自动沟通
|
||||
// is_chat_outsourcing: 0 - 是否沟通外包岗位,0=不沟通外包岗位,1=沟通外包岗位
|
||||
// time_range: 沟通时间段配置
|
||||
// - start_time: 开始时间,格式:HH:mm,默认 "09:00"
|
||||
// - end_time: 结束时间,格式:HH:mm,默认 "18:00"
|
||||
// - workdays_only: 是否仅工作日,0=包含周末,1=仅工作日,默认1
|
||||
defaultValue: JSON.stringify({
|
||||
chat_interval: 30,
|
||||
is_chat_outsourcing: 0,
|
||||
time_range: {
|
||||
start_time: '09:00',
|
||||
end_time: '18:00',
|
||||
workdays_only: 1
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 自动活跃相关配置
|
||||
auto_active: {
|
||||
comment: '自动活跃开关',
|
||||
@@ -135,12 +227,41 @@ module.exports = (db) => {
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
// 是否沟通外包岗位
|
||||
is_chat_outsourcing: {
|
||||
comment: '是否沟通外包岗位',
|
||||
type: Sequelize.TINYINT(1),
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
// 自动活跃配置(JSON格式,包含:active_interval-活跃间隔分钟数, actions-活跃动作列表)
|
||||
active_actions: {
|
||||
comment: '自动活跃动作配置(JSON对象)',
|
||||
type: Sequelize.JSON(),
|
||||
allowNull: true,
|
||||
get: function () {
|
||||
const value = this.getDataValue('active_actions');
|
||||
if (!value) return null;
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
},
|
||||
set: function (value) {
|
||||
if (value === null || value === undefined) {
|
||||
this.setDataValue('active_actions', null);
|
||||
} else if (typeof value === 'string') {
|
||||
// 如果已经是字符串,直接使用
|
||||
this.setDataValue('active_actions', value);
|
||||
} else {
|
||||
// 如果是对象,序列化为字符串
|
||||
this.setDataValue('active_actions', JSON.stringify(value));
|
||||
}
|
||||
},
|
||||
// 默认值说明:
|
||||
// active_interval: 60 - 活跃间隔时间,单位:分钟,默认60分钟执行一次活跃动作
|
||||
// actions: [] - 活跃动作列表,数组格式,可包含多个活跃动作配置对象
|
||||
defaultValue: JSON.stringify({
|
||||
active_interval: 60,
|
||||
actions: []
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user