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 = {
|
const developmentConfig = {
|
||||||
...baseConfig,
|
...baseConfig,
|
||||||
// apiUrl: 'http://localhost:9097/admin_api/',
|
apiUrl: 'http://localhost:9097/admin_api/',
|
||||||
// uploadUrl: 'http://localhost:9097/admin_api/upload',
|
uploadUrl: 'http://localhost:9097/admin_api/upload',
|
||||||
|
|
||||||
apiUrl: 'http://work.light120.com/admin_api/',
|
// apiUrl: 'http://work.light120.com/admin_api/',
|
||||||
uploadUrl: 'http://work.light120.com/admin_api/upload',
|
// uploadUrl: 'http://work.light120.com/admin_api/upload',
|
||||||
// 开发环境显示更多调试信息
|
// 开发环境显示更多调试信息
|
||||||
debug: true
|
debug: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ class PlaAccountServer {
|
|||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
add(row) {
|
add(row) {
|
||||||
return window.framework.http.post('/account/create', row)
|
return window.framework.http.post('/account/add', row)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content-view">
|
<div class="content-view">
|
||||||
<div class="table-head-tool">
|
<div class="table-head-tool">
|
||||||
<Button type="primary" @click="showAddWarp">新增账号</Button>
|
<div>
|
||||||
<Button type="success" @click="batchParseLocation" :loading="batchParseLoading" class="ml10">批量解析位置</Button>
|
<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">
|
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
|
||||||
<FormItem :label-width="20" class="flex">
|
<FormItem :label-width="20" class="flex">
|
||||||
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
|
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
|
||||||
@@ -37,9 +41,14 @@
|
|||||||
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
|
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
|
||||||
@changePage="query"></tables>
|
@changePage="query"></tables>
|
||||||
</div>
|
</div>
|
||||||
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules" @on-save="handleSaveSuccess">
|
|
||||||
|
<!-- 新增弹窗(只包含必填字段) -->
|
||||||
|
<editModal ref="editModal" :columns="addColumns" :rules="gridOption.rules" @on-save="handleSaveSuccess">
|
||||||
</editModal>
|
</editModal>
|
||||||
|
|
||||||
|
<!-- 编辑组件(使用 FloatPanel,包含所有字段) -->
|
||||||
|
<PlaAccountEdit ref="accountEdit" @on-save="handleSaveSuccess" />
|
||||||
|
|
||||||
<!-- 简历详情弹窗 -->
|
<!-- 简历详情弹窗 -->
|
||||||
<Modal v-model="resumeModal.visible" :title="resumeModal.title" width="900" :footer-hide="true">
|
<Modal v-model="resumeModal.visible" :title="resumeModal.title" width="900" :footer-hide="true">
|
||||||
<div v-if="resumeModal.loading" style="text-align: center; padding: 40px;">
|
<div v-if="resumeModal.loading" style="text-align: center; padding: 40px;">
|
||||||
@@ -216,8 +225,12 @@
|
|||||||
<script>
|
<script>
|
||||||
import plaAccountServer from '@/api/profile/pla_account_server.js'
|
import plaAccountServer from '@/api/profile/pla_account_server.js'
|
||||||
import jobTypesServer from '@/api/work/job_types_server.js'
|
import jobTypesServer from '@/api/work/job_types_server.js'
|
||||||
|
import PlaAccountEdit from './pla_account_edit.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {
|
||||||
|
PlaAccountEdit
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
let rules = {}
|
let rules = {}
|
||||||
rules["name"] = [{ required: true, message: '请填写账户名', trigger: 'blur' }]
|
rules["name"] = [{ required: true, message: '请填写账户名', trigger: 'blur' }]
|
||||||
@@ -257,6 +270,19 @@ export default {
|
|||||||
data: [],
|
data: [],
|
||||||
rules: rules
|
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: [
|
listColumns: [
|
||||||
{ title: 'ID', key: 'id', minWidth: 80 },
|
{ title: 'ID', key: 'id', minWidth: 80 },
|
||||||
{ title: '账户名', key: 'name', minWidth: 150 },
|
{ title: '账户名', key: 'name', minWidth: 150 },
|
||||||
@@ -417,54 +443,6 @@ export default {
|
|||||||
return window.framework.uiTool.getBtn(h, btns)
|
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() {
|
showAddWarp() {
|
||||||
|
// 新增使用简单的 editModal,只包含必填字段
|
||||||
this.$refs.editModal.showModal()
|
this.$refs.editModal.showModal()
|
||||||
},
|
},
|
||||||
showEditWarp(row) {
|
showEditWarp(row) {
|
||||||
// 将布尔字段从 0/1 转换为 true/false,以便开关组件正确显示
|
this.$refs.accountEdit.showEdit(row)
|
||||||
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)
|
|
||||||
},
|
},
|
||||||
toggleEnabled(row, value) {
|
toggleEnabled(row, value) {
|
||||||
const action = value ? '启用' : '禁用'
|
const action = value ? '启用' : '禁用'
|
||||||
@@ -517,21 +488,7 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
loadJobTypes() {
|
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) {
|
stopTasks(row) {
|
||||||
this.$Modal.confirm({
|
this.$Modal.confirm({
|
||||||
@@ -571,25 +528,12 @@ export default {
|
|||||||
},
|
},
|
||||||
async handleSaveSuccess({ data }) {
|
async handleSaveSuccess({ data }) {
|
||||||
try {
|
try {
|
||||||
// 将布尔字段从 true/false 转换为 1/0
|
// 如果是新增(来自 editModal),data 只包含必填字段,直接保存
|
||||||
const saveData = { ...data }
|
if (data && !data.id) {
|
||||||
const booleanFields = ['is_enabled', 'is_online', 'auto_deliver', 'auto_chat', 'auto_reply', 'auto_active']
|
await plaAccountServer.add(data)
|
||||||
booleanFields.forEach(field => {
|
this.$Message.success('保存成功!')
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
// 编辑时由 FloatPanel 组件(PlaAccountEdit)处理保存,这里只刷新列表
|
||||||
this.$Message.success('保存成功!')
|
|
||||||
this.query(this.gridOption.param.pageOption.page)
|
this.query(this.gridOption.param.pageOption.page)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存失败:', error)
|
console.error('保存失败:', error)
|
||||||
|
|||||||
@@ -86,6 +86,190 @@
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</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 切换区域 -->
|
<!-- Tab 切换区域 -->
|
||||||
<Card class="tabs-card" :bordered="false">
|
<Card class="tabs-card" :bordered="false">
|
||||||
<Tabs v-model="activeTab" @on-click="handleTabChange">
|
<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>
|
</Select>
|
||||||
</Card>
|
</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;">
|
<Row :gutter="16" style="margin-bottom: 16px;">
|
||||||
<Col span="6">
|
<Col span="6">
|
||||||
@@ -56,9 +171,22 @@
|
|||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
<!-- 当前执行任务的命令区块(指令列表) -->
|
<!-- 当前执行任务的命令区块(指令列表) -->
|
||||||
<Card v-if="runningTasks.length > 0" style="margin-bottom: 16px;">
|
<Card style="margin-bottom: 16px;">
|
||||||
<p slot="title">当前执行中的任务</p>
|
<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>
|
</Card>
|
||||||
|
|
||||||
<!-- 趋势图表(7天趋势) -->
|
<!-- 趋势图表(7天趋势) -->
|
||||||
@@ -73,6 +201,7 @@
|
|||||||
import * as echarts from 'echarts'
|
import * as echarts from 'echarts'
|
||||||
import DeviceStatusServer from '../../api/device/device_status_server.js'
|
import DeviceStatusServer from '../../api/device/device_status_server.js'
|
||||||
import StatisticsServer from '../../api/statistics/statistics_server.js'
|
import StatisticsServer from '../../api/statistics/statistics_server.js'
|
||||||
|
import PlaAccountServer from '../../api/profile/pla_account_server.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HomePage',
|
name: 'HomePage',
|
||||||
@@ -98,7 +227,16 @@ export default {
|
|||||||
taskLoading: false,
|
taskLoading: false,
|
||||||
chartInstance: null, // ECharts实例
|
chartInstance: null, // ECharts实例
|
||||||
taskColumns: [],
|
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([
|
await Promise.all([
|
||||||
this.loadTodayStats(),
|
this.loadTodayStats(),
|
||||||
this.loadChartData(),
|
this.loadChartData(),
|
||||||
this.loadRunningTasks()
|
this.loadRunningTasks(),
|
||||||
|
this.loadAccountInfo()
|
||||||
])
|
])
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -567,6 +706,155 @@ export default {
|
|||||||
// 计算进度百分比
|
// 计算进度百分比
|
||||||
const progress = Math.round((completedCount / commands.length) * 100)
|
const progress = Math.round((completedCount / commands.length) * 100)
|
||||||
return progress
|
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;
|
font-weight: bold;
|
||||||
color: #2d8cf0;
|
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>
|
</style>
|
||||||
|
|||||||
@@ -147,6 +147,8 @@ module.exports = {
|
|||||||
'POST /account/update': async (ctx) => {
|
'POST /account/update': async (ctx) => {
|
||||||
const body = ctx.getBody();
|
const body = ctx.getBody();
|
||||||
const { id, ...updateData } = body;
|
const { id, ...updateData } = body;
|
||||||
|
|
||||||
|
|
||||||
await plaAccountService.updateAccount(id, updateData);
|
await plaAccountService.updateAccount(id, updateData);
|
||||||
return ctx.success({ message: '账号信息更新成功' });
|
return ctx.success({ message: '账号信息更新成功' });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const dayjs = require('dayjs');
|
const dayjs = require('dayjs');
|
||||||
const config = require('../../../config/config');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调度系统配置中心
|
* 调度系统配置中心
|
||||||
@@ -44,46 +43,16 @@ class ScheduleConfig {
|
|||||||
|
|
||||||
// 监控配置
|
// 监控配置
|
||||||
this.monitoring = {
|
this.monitoring = {
|
||||||
heartbeatTimeout: 3 * 60 * 1000, // 心跳超时:5分钟
|
heartbeatTimeout: 3 * 60 * 1000, // 心跳超时:3分钟
|
||||||
taskFailureRate: 0.5, // 任务失败率:50%
|
|
||||||
consecutiveFailures: 3, // 连续失败次数:3次
|
|
||||||
alertCooldown: 5 * 60 * 1000, // 告警冷却:5分钟
|
|
||||||
offlineThreshold: 24 * 60 * 60 * 1000 // 离线设备清理:24小时
|
offlineThreshold: 24 * 60 * 60 * 1000 // 离线设备清理:24小时
|
||||||
};
|
};
|
||||||
|
|
||||||
// 定时任务配置
|
// 定时任务配置
|
||||||
this.schedules = {
|
this.schedules = {
|
||||||
dailyReset: '0 0 * * *', // 每天凌晨重置统计
|
dailyReset: '0 0 * * *', // 每天凌晨重置统计
|
||||||
jobFlowInterval: '0 */5 * * * *', // 每10秒执行一次找工作流程
|
|
||||||
monitoringInterval: '*/1 * * * *', // 监控检查间隔:1分钟
|
monitoringInterval: '*/1 * * * *', // 监控检查间隔:1分钟
|
||||||
autoDeliver: '0 */5 * * * *', // 自动投递任务:每5分钟执行一次
|
autoDeliver: '0 */1 * * * *' // 自动投递任务:每5分钟执行一次
|
||||||
// 监控检查间隔:1分钟
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 测试配置覆盖
|
|
||||||
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 node_schedule = require("node-schedule");
|
||||||
|
const dayjs = require('dayjs');
|
||||||
const config = require('./config.js');
|
const config = require('./config.js');
|
||||||
const deviceManager = require('./deviceManager.js');
|
const deviceManager = require('./deviceManager.js');
|
||||||
const command = require('./command.js');
|
const command = require('./command.js');
|
||||||
@@ -331,6 +332,8 @@ class ScheduledJobs {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (!pla_users || pla_users.length === 0) {
|
if (!pla_users || pla_users.length === 0) {
|
||||||
console.log('[自动投递] 没有启用且开启自动投递的账号');
|
console.log('[自动投递] 没有启用且开启自动投递的账号');
|
||||||
return;
|
return;
|
||||||
@@ -338,11 +341,13 @@ class ScheduledJobs {
|
|||||||
|
|
||||||
console.log(`[自动投递] 找到 ${pla_users.length} 个可用账号`);
|
console.log(`[自动投递] 找到 ${pla_users.length} 个可用账号`);
|
||||||
|
|
||||||
|
// 获取 task_status 模型用于查询上次投递时间
|
||||||
|
const { task_status } = models;
|
||||||
|
|
||||||
// 为每个设备添加自动投递任务到队列
|
// 为每个设备添加自动投递任务到队列
|
||||||
for (const pl_user of pla_users) {
|
for (const pl_user of pla_users) {
|
||||||
const userData = pl_user.toJSON();
|
const userData = pl_user.toJSON();
|
||||||
|
|
||||||
|
|
||||||
// 检查设备调度策略
|
// 检查设备调度策略
|
||||||
const canExecute = deviceManager.canExecuteOperation(userData.sn_code, 'deliver');
|
const canExecute = deviceManager.canExecuteOperation(userData.sn_code, 'deliver');
|
||||||
if (!canExecute.allowed) {
|
if (!canExecute.allowed) {
|
||||||
@@ -350,6 +355,44 @@ class ScheduledJobs {
|
|||||||
continue;
|
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, {
|
await this.taskQueue.addTask(userData.sn_code, {
|
||||||
taskType: 'auto_deliver',
|
taskType: 'auto_deliver',
|
||||||
@@ -357,19 +400,19 @@ class ScheduledJobs {
|
|||||||
taskParams: {
|
taskParams: {
|
||||||
keyword: userData.keyword || '',
|
keyword: userData.keyword || '',
|
||||||
platform: userData.platform_type || 'boss',
|
platform: userData.platform_type || 'boss',
|
||||||
pageCount: 3, // 默认值
|
pageCount: deliver_config.page_count || 3,
|
||||||
maxCount: 10, // 默认值
|
maxCount: deliver_config.max_deliver || 10,
|
||||||
filterRules: {
|
filterRules: {
|
||||||
minSalary: userData.min_salary || 0,
|
minSalary: deliver_config.min_salary || 0,
|
||||||
maxSalary: userData.max_salary || 0,
|
maxSalary: deliver_config.max_salary || 0,
|
||||||
keywords: [],
|
keywords: deliver_config.filter_keywords || [],
|
||||||
excludeKeywords: []
|
excludeKeywords: deliver_config.exclude_keywords || []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
priority: config.getTaskPriority('auto_deliver') || 6
|
priority: config.getTaskPriority('auto_deliver') || 6
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`[自动投递] 已为设备 ${userData.sn_code} 添加自动投递任务,关键词: ${userData.keyword || '默认'}`);
|
console.log(`[自动投递] 已为设备 ${userData.sn_code} 添加自动投递任务,关键词: ${userData.keyword || '默认'},投递间隔: ${deliver_interval} 分钟`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[自动投递] 任务添加完成');
|
console.log('[自动投递] 任务添加完成');
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class TaskHandlers {
|
|||||||
|
|
||||||
async handleAutoDeliverTask(task) {
|
async handleAutoDeliverTask(task) {
|
||||||
const { sn_code, taskParams } = task;
|
const { sn_code, taskParams } = task;
|
||||||
const { keyword, platform, pageCount, maxCount } = taskParams;
|
const { keyword, platform, pageCount, maxCount, filterRules = {} } = taskParams;
|
||||||
|
|
||||||
console.log(`[任务处理器] 自动投递任务 - 设备: ${sn_code}, 关键词: ${keyword}`);
|
console.log(`[任务处理器] 自动投递任务 - 设备: ${sn_code}, 关键词: ${keyword}`);
|
||||||
|
|
||||||
@@ -181,11 +181,22 @@ class TaskHandlers {
|
|||||||
|
|
||||||
// 5. 根据简历信息、职位类型配置和权重配置进行评分和过滤
|
// 5. 根据简历信息、职位类型配置和权重配置进行评分和过滤
|
||||||
const scoredJobs = [];
|
const scoredJobs = [];
|
||||||
const excludeKeywords = jobTypeConfig && jobTypeConfig.excludeKeywords
|
|
||||||
|
// 合并排除关键词:从职位类型配置和任务参数中获取
|
||||||
|
const jobTypeExcludeKeywords = jobTypeConfig && jobTypeConfig.excludeKeywords
|
||||||
? (typeof jobTypeConfig.excludeKeywords === 'string'
|
? (typeof jobTypeConfig.excludeKeywords === 'string'
|
||||||
? JSON.parse(jobTypeConfig.excludeKeywords)
|
? JSON.parse(jobTypeConfig.excludeKeywords)
|
||||||
: 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');
|
const apply_records = db.getModel('apply_records');
|
||||||
@@ -209,6 +220,20 @@ class TaskHandlers {
|
|||||||
for (const job of pendingJobs) {
|
for (const job of pendingJobs) {
|
||||||
const jobData = job.toJSON ? job.toJSON() : job;
|
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) {
|
if (Array.isArray(excludeKeywords) && excludeKeywords.length > 0) {
|
||||||
const jobText = `${jobData.jobTitle} ${jobData.companyName} ${jobData.jobDescription || ''}`.toLowerCase();
|
const jobText = `${jobData.jobTitle} ${jobData.companyName} ${jobData.jobDescription || ''}`.toLowerCase();
|
||||||
@@ -233,12 +258,28 @@ class TaskHandlers {
|
|||||||
priorityWeights
|
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 的职位
|
// 只保留总分 >= 60 的职位
|
||||||
if (scoreResult.totalScore >= 60) {
|
if (finalScore >= 60) {
|
||||||
scoredJobs.push({
|
scoredJobs.push({
|
||||||
...jobData,
|
...jobData,
|
||||||
matchScore: scoreResult.totalScore,
|
matchScore: finalScore,
|
||||||
scoreDetails: scoreResult.scores
|
scoreDetails: {
|
||||||
|
...scoreResult.scores,
|
||||||
|
keywordBonus: keywordBonus
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,12 +59,31 @@ module.exports = (db) => {
|
|||||||
type: Sequelize.JSON(),
|
type: Sequelize.JSON(),
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
get: function () {
|
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) {
|
set: function (value) {
|
||||||
this.setDataValue('is_salary_priority', JSON.stringify(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,
|
allowNull: false,
|
||||||
defaultValue: 0
|
defaultValue: 0
|
||||||
},
|
},
|
||||||
min_salary: {
|
// 自动投递配置(JSON格式,包含:deliver_interval-投递间隔分钟数, min_salary-最低薪资, max_salary-最高薪资, page_count-滚动获取职位列表次数, max_deliver-每次最多投递数量, filter_keywords-过滤关键词, exclude_keywords-排除关键词)
|
||||||
comment: '最低薪资(单位:元)',
|
deliver_config: {
|
||||||
type: Sequelize.INTEGER,
|
comment: '自动投递配置(JSON对象)',
|
||||||
allowNull: false,
|
type: Sequelize.JSON(),
|
||||||
defaultValue: 0
|
allowNull: true,
|
||||||
},
|
get: function () {
|
||||||
max_salary: {
|
const value = this.getDataValue('deliver_config');
|
||||||
comment: '最高薪资(单位:元)',
|
if (!value) return null;
|
||||||
type: Sequelize.INTEGER,
|
if (typeof value === 'string') {
|
||||||
allowNull: false,
|
try {
|
||||||
defaultValue: 0
|
return JSON.parse(value);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
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: {
|
auto_chat: {
|
||||||
@@ -116,18 +174,52 @@ module.exports = (db) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0
|
defaultValue: 0
|
||||||
},
|
},
|
||||||
chat_interval: {
|
// 自动沟通配置(JSON格式,包含:chat_interval-沟通间隔分钟数, is_chat_outsourcing-是否沟通外包岗位, time_range-沟通时间段)
|
||||||
comment: '沟通间隔(单位:分钟)',
|
chat_strategy: {
|
||||||
type: Sequelize.INTEGER,
|
comment: '自动沟通策略配置(JSON对象)',
|
||||||
allowNull: false,
|
type: Sequelize.JSON(),
|
||||||
defaultValue: 30
|
allowNull: true,
|
||||||
},
|
get: function () {
|
||||||
auto_reply: {
|
const value = this.getDataValue('chat_strategy');
|
||||||
comment: '自动回复开关',
|
if (!value) return null;
|
||||||
type: Sequelize.TINYINT(1),
|
if (typeof value === 'string') {
|
||||||
allowNull: false,
|
try {
|
||||||
defaultValue: 0
|
return JSON.parse(value);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
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: {
|
auto_active: {
|
||||||
comment: '自动活跃开关',
|
comment: '自动活跃开关',
|
||||||
@@ -135,18 +227,47 @@ module.exports = (db) => {
|
|||||||
allowNull: false,
|
allowNull: false,
|
||||||
defaultValue: 0
|
defaultValue: 0
|
||||||
},
|
},
|
||||||
// 是否沟通外包岗位
|
// 自动活跃配置(JSON格式,包含:active_interval-活跃间隔分钟数, actions-活跃动作列表)
|
||||||
is_chat_outsourcing: {
|
active_actions: {
|
||||||
comment: '是否沟通外包岗位',
|
comment: '自动活跃动作配置(JSON对象)',
|
||||||
type: Sequelize.TINYINT(1),
|
type: Sequelize.JSON(),
|
||||||
allowNull: false,
|
allowNull: true,
|
||||||
defaultValue: 0
|
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: []
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//pla_account.sync({ force: true });
|
// pla_account.sync({ force: true });
|
||||||
return pla_account
|
return pla_account
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user