This commit is contained in:
张成
2025-11-26 18:39:15 +08:00
parent 771dc60607
commit d5d8069573
15 changed files with 2346 additions and 1593 deletions

File diff suppressed because it is too large Load Diff

View 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 并执行

View 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 会报错,可以忽略或先删除已存在的字段

View File

@@ -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
} }

View File

@@ -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)
} }
/** /**

View File

@@ -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,8 +41,13 @@
<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">
@@ -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 // 如果是新增(来自 editModaldata 只包含必填字段,直接保存
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)
@@ -702,7 +646,7 @@ export default {
// 批量解析位置 // 批量解析位置
async batchParseLocation() { async batchParseLocation() {
const selectedRows = this.gridOption.data.filter(row => row.user_address && row.user_address.trim() !== '') const selectedRows = this.gridOption.data.filter(row => row.user_address && row.user_address.trim() !== '')
if (selectedRows.length === 0) { if (selectedRows.length === 0) {
this.$Message.warning('当前页面没有已设置地址的账号') this.$Message.warning('当前页面没有已设置地址的账号')
return return
@@ -721,20 +665,20 @@ export default {
const result = res.data const result = res.data
const successCount = result.success || 0 const successCount = result.success || 0
const failedCount = result.failed || 0 const failedCount = result.failed || 0
let message = `批量解析完成:成功 ${successCount}` let message = `批量解析完成:成功 ${successCount}`
if (failedCount > 0) { if (failedCount > 0) {
message += `,失败 ${failedCount}` message += `,失败 ${failedCount}`
} }
this.$Message.success(message) this.$Message.success(message)
// 如果有失败的,显示详细信息 // 如果有失败的,显示详细信息
if (failedCount > 0 && result.details) { if (failedCount > 0 && result.details) {
const failedDetails = result.details.filter(d => !d.success) const failedDetails = result.details.filter(d => !d.success)
console.log('解析失败的账号:', failedDetails) console.log('解析失败的账号:', failedDetails)
} }
this.query(this.gridOption.param.pageOption.page) this.query(this.gridOption.param.pageOption.page)
} else { } else {
this.$Message.error(res.message || '批量解析失败') this.$Message.error(res.message || '批量解析失败')

View File

@@ -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">

View 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:mm09:00" />
</FormItem>
<FormItem label="沟通结束时间">
<Input v-model="formData.chat_end_time" placeholder="格式HH:mm18: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>

View File

@@ -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>

View File

@@ -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: '账号信息更新成功' });
}, },

View File

@@ -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秒
}
} }
/** /**

View File

@@ -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('[自动投递] 任务添加完成');

View File

@@ -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
}
}); });
} }
} }

View File

@@ -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

2
app.js
View File

@@ -36,7 +36,7 @@ async function startApp() {
// 启动调度系统 // 启动调度系统
await schedule.init(); // await schedule.init();
// 优雅关闭处理 // 优雅关闭处理
process.on('SIGINT', async () => { process.on('SIGINT', async () => {