-
@@ -524,10 +527,10 @@ export default {
return {
accountInfo: {},
activeTab: 'tasks',
-
+
// 职位类型选项
jobTypeOptions: [],
-
+
// 配置数据
priorityList: [],
deliverConfig: {
@@ -778,17 +781,35 @@ export default {
{
title: '操作',
key: 'action',
- width: 120,
+ width: 180,
render: (h, params) => {
- return h('Button', {
- props: {
- type: 'primary',
- size: 'small'
- },
- on: {
- click: () => this.showCommandDetail(params.row)
- }
- }, '详情')
+ const btns = []
+ // 详情按钮
+ btns.push({
+ title: '详情',
+ type: 'primary',
+ click: () => this.showCommandDetail(params.row)
+ })
+ // 重试按钮(只在失败状态时显示)
+ if (params.row.status === 'failed') {
+ btns.push({
+ title: '重试',
+ type: 'warning',
+ click: () => this.retryCommand(params.row)
+ })
+ }
+ return h('div', btns.map(btn =>
+ h('Button', {
+ props: {
+ type: btn.type,
+ size: 'small'
+ },
+ style: { marginRight: '5px' },
+ on: {
+ click: btn.click
+ }
+ }, btn.title)
+ ))
}
}
]
@@ -828,7 +849,7 @@ export default {
this.accountInfo = {}
}
},
-
+
// 解析配置数据
parseConfigData(accountInfo) {
// 解析排序优先级配置
@@ -847,7 +868,7 @@ export default {
} else {
this.priorityList = []
}
-
+
// 解析自动投递配置
if (accountInfo.deliver_config) {
const deliverConfig = typeof accountInfo.deliver_config === 'string'
@@ -885,7 +906,7 @@ export default {
deliver_workdays_only: 1
}
}
-
+
// 解析自动沟通配置
if (accountInfo.chat_strategy) {
const chatStrategy = typeof accountInfo.chat_strategy === 'string'
@@ -909,7 +930,7 @@ export default {
chat_workdays_only: 1
}
}
-
+
// 解析自动活跃配置
if (accountInfo.active_actions) {
const activeActions = typeof accountInfo.active_actions === 'string'
@@ -928,7 +949,7 @@ export default {
}
}
},
-
+
// 加载职位类型
async loadJobTypes() {
try {
@@ -943,14 +964,14 @@ export default {
console.error('加载职位类型失败:', err)
}
},
-
+
// 获取职位类型名称
getJobTypeName(jobTypeId) {
if (!jobTypeId) return ''
const jobType = this.jobTypeOptions.find(item => item.value === jobTypeId)
return jobType ? jobType.label : ''
},
-
+
// 获取优先级标签
getPriorityLabel(key) {
const labelMap = {
@@ -974,8 +995,16 @@ export default {
pageOption: this.tasksPageOption
}
const res = await plaAccountServer.getTasks(this.accountId, param)
+
+
this.tasksData = res.data.rows || []
this.tasksPageOption.total = res.data.count || 0
+
+
+
+ setTimeout(() => {
+ this.$forceUpdate()
+ }, 0)
} catch (error) {
this.$Message.error('加载任务列表失败')
this.tasksData = []
@@ -997,8 +1026,13 @@ export default {
pageOption: this.commandsPageOption
}
const res = await plaAccountServer.getCommands(this.accountId, param)
+
this.commandsData = res.data.rows || []
this.commandsPageOption.total = res.data.count || 0
+
+ setTimeout(() => {
+ this.$forceUpdate()
+ }, 0)
} catch (error) {
this.$Message.error('加载指令列表失败')
this.commandsData = []
@@ -1020,6 +1054,27 @@ export default {
}
},
+ // 重试指令
+ async retryCommand(command) {
+ this.$Modal.confirm({
+ title: '确认重试',
+ content: `确定要重试指令"${command.command_name}"吗?`,
+ onOk: async () => {
+ try {
+ await plaAccountServer.retryCommand(command.id)
+ this.$Message.success('重试指令成功')
+ // 刷新指令列表
+ setTimeout(() => {
+ this.queryCommands(this.commandsPageOption.page)
+ }, 1000)
+ } catch (error) {
+ console.error('重试指令失败:', error)
+ this.$Message.error(error.message || '重试指令失败')
+ }
+ }
+ })
+ },
+
// 取消任务
async cancelTask(task) {
this.$Modal.confirm({
diff --git a/admin/src/views/task/task_status.vue b/admin/src/views/task/task_status.vue
index 4de7a03..9a57a5d 100644
--- a/admin/src/views/task/task_status.vue
+++ b/admin/src/views/task/task_status.vue
@@ -284,6 +284,7 @@ export default {
try {
const res = await taskStatusServer.getCommands(row.id)
+
this.commandsModal.data = res.data || []
} catch (error) {
this.$Message.error('获取指令列表失败')
diff --git a/api/controller_admin/pla_account.js b/api/controller_admin/pla_account.js
index 371f13d..2ea882a 100644
--- a/api/controller_admin/pla_account.js
+++ b/api/controller_admin/pla_account.js
@@ -445,6 +445,35 @@ module.exports = {
return ctx.success(commandDetail);
},
+ /**
+ * @swagger
+ * /admin_api/pla_account/retryCommand:
+ * post:
+ * summary: 重试指令
+ * description: 重新执行失败的指令
+ * tags: [后台-账号管理]
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * type: object
+ * required:
+ * - commandId
+ * properties:
+ * commandId:
+ * type: integer
+ * description: 指令ID
+ * responses:
+ * 200:
+ * description: 重试成功
+ */
+ 'POST /pla_account/retryCommand': async (ctx) => {
+ const body = ctx.getBody();
+ const result = await plaAccountService.retryCommand(body);
+ return ctx.success(result);
+ },
+
/**
* @swagger
* /admin_api/pla_account/parseLocation:
diff --git a/api/services/pla_account_service.js b/api/services/pla_account_service.js
index 919c6b3..2ba5980 100644
--- a/api/services/pla_account_service.js
+++ b/api/services/pla_account_service.js
@@ -252,9 +252,12 @@ class PlaAccountService {
order: [['id', 'DESC']]
});
+ // 将 Sequelize 模型实例转换为普通对象
+ const rows = result.rows.map(row => row.get({ plain: true }));
+
return {
count: result.count,
- rows: result.rows
+ rows: rows
};
}
@@ -266,7 +269,9 @@ class PlaAccountService {
async getAccountCommands(params) {
const pla_account = db.getModel('pla_account');
const task_commands = db.getModel('task_commands');
+ const task_status = db.getModel('task_status');
const Sequelize = require('sequelize');
+ const Op = Sequelize.Op;
const { id, limit, offset } = params;
@@ -280,51 +285,66 @@ class PlaAccountService {
throw new Error('账号不存在');
}
- // 获取 sequelize 实例
- const sequelize = task_commands.sequelize;
+ // 先查询所有匹配 sn_code 的任务 ID
+ const tasks = await task_status.findAll({
+ where: {
+ sn_code: account.sn_code
+ },
+ attributes: ['id']
+ });
- // 使用原生 SQL JOIN 查询
- const countSql = `
- SELECT COUNT(DISTINCT tc.id) as count
- FROM task_commands tc
- INNER JOIN task_status ts ON tc.task_id = ts.id
- WHERE ts.sn_code = :sn_code
- `;
+ const taskIds = tasks.map(task => task.id);
- const dataSql = `
- SELECT tc.*
- FROM task_commands tc
- INNER JOIN task_status ts ON tc.task_id = ts.id
- WHERE ts.sn_code = :sn_code
- ORDER BY tc.id DESC
- LIMIT :limit OFFSET :offset
- `;
+ // 如果没有任务,直接返回空结果
+ if (taskIds.length === 0) {
+ return {
+ count: 0,
+ rows: []
+ };
+ }
- // 并行执行查询和计数
- const [countResult, dataResult] = await Promise.all([
- sequelize.query(countSql, {
- replacements: { sn_code: account.sn_code },
- type: Sequelize.QueryTypes.SELECT
- }),
- sequelize.query(dataSql, {
- replacements: {
- sn_code: account.sn_code,
- limit: limit,
- offset: offset
- },
- type: Sequelize.QueryTypes.SELECT
- })
- ]);
+ // 使用 Sequelize 模型查询指令列表
+ const result = await task_commands.findAndCountAll({
+ where: {
+ task_id: {
+ [Op.in]: taskIds
+ }
+ },
+ limit,
+ offset,
+ order: [['id', 'DESC']]
+ });
- const count = countResult[0]?.count || 0;
-
- // 将原始数据转换为 Sequelize 模型实例
- const rows = dataResult.map(row => {
- return task_commands.build(row, { isNewRecord: false });
+ // 将 Sequelize 模型实例转换为普通对象
+ const rows = result.rows.map(row => {
+ const plainRow = row.get({ plain: true });
+
+ // 添加 create_time 字段(使用 start_time 或 createdAt)
+ if (!plainRow.create_time) {
+ plainRow.create_time = plainRow.start_time || plainRow.createdAt || null;
+ }
+
+ // 解析 JSON 字段
+ if (plainRow.command_params && typeof plainRow.command_params === 'string') {
+ try {
+ plainRow.command_params = JSON.parse(plainRow.command_params);
+ } catch (e) {
+ // 解析失败保持原样
+ }
+ }
+ if (plainRow.result && typeof plainRow.result === 'string') {
+ try {
+ plainRow.result = JSON.parse(plainRow.result);
+ } catch (e) {
+ // 解析失败保持原样
+ }
+ }
+
+ return plainRow;
});
return {
- count: parseInt(count),
+ count: result.count,
rows: rows
};
}
@@ -451,6 +471,100 @@ class PlaAccountService {
return commandDetail;
}
+ /**
+ * 重试指令
+ * @param {Object} params - 重试参数
+ * @param {number} params.commandId - 指令ID
+ * @returns {Promise