1
This commit is contained in:
@@ -132,6 +132,17 @@ class PlaAccountServer {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重试指令
|
||||||
|
* @param {Number|String} commandId - 指令ID
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
retryCommand(commandId) {
|
||||||
|
return window.framework.http.post(`pla_account/retryCommand`, {
|
||||||
|
commandId
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 停止账号的所有任务
|
* 停止账号的所有任务
|
||||||
* @param {Object} row - 账号数据(包含id和sn_code)
|
* @param {Object} row - 账号数据(包含id和sn_code)
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
<Col span="8">
|
<Col span="8">
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<span class="label">过期时间:</span>
|
<span class="label">过期时间:</span>
|
||||||
<span class="value" :class="{'text-danger': isExpired(accountInfo)}">
|
<span class="value" :class="{ 'text-danger': isExpired(accountInfo) }">
|
||||||
{{ getExpireDate(accountInfo) }}
|
{{ getExpireDate(accountInfo) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -176,7 +176,8 @@
|
|||||||
<span class="priority-value">{{ item.weight }}%</span>
|
<span class="priority-value">{{ item.weight }}%</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="priority-total-display">
|
<div class="priority-total-display">
|
||||||
<span>总权重:<strong :class="{'weight-warning': totalWeight !== 100}">{{ totalWeight }}%</strong></span>
|
<span>总权重:<strong :class="{ 'weight-warning': totalWeight !== 100 }">{{ totalWeight
|
||||||
|
}}%</strong></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="empty-config">暂无配置</div>
|
<div v-else class="empty-config">暂无配置</div>
|
||||||
@@ -204,13 +205,15 @@
|
|||||||
<Col span="8">
|
<Col span="8">
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<span class="label">最低薪资(元):</span>
|
<span class="label">最低薪资(元):</span>
|
||||||
<span class="value">{{ deliverConfig.min_salary || deliverConfig.min_salary === 0 ? deliverConfig.min_salary : '-' }}</span>
|
<span class="value">{{ deliverConfig.min_salary || deliverConfig.min_salary === 0 ?
|
||||||
|
deliverConfig.min_salary : '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<span class="label">最高薪资(元):</span>
|
<span class="label">最高薪资(元):</span>
|
||||||
<span class="value">{{ deliverConfig.max_salary || deliverConfig.max_salary === 0 ? deliverConfig.max_salary : '-' }}</span>
|
<span class="value">{{ deliverConfig.max_salary || deliverConfig.max_salary === 0 ?
|
||||||
|
deliverConfig.max_salary : '-' }}</span>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col span="8">
|
<Col span="8">
|
||||||
@@ -354,8 +357,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<TabPane name="tasks" label="任务列表">
|
<TabPane name="tasks" label="任务列表">
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-body">
|
<div class="tab-body" v-if="tasksLoading">
|
||||||
<tables :columns="taskColumns" :value="tasksData" :loading="tasksLoading"
|
<tables :columns="taskColumns" :value="tasksData"
|
||||||
:pageOption="tasksPageOption" @changePage="queryTasks">
|
:pageOption="tasksPageOption" @changePage="queryTasks">
|
||||||
</tables>
|
</tables>
|
||||||
</div>
|
</div>
|
||||||
@@ -364,8 +367,8 @@
|
|||||||
|
|
||||||
<TabPane name="commands" label="指令列表">
|
<TabPane name="commands" label="指令列表">
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
<div class="tab-body">
|
<div class="tab-body" v-if="commandsLoading">
|
||||||
<tables :columns="commandColumns" :value="commandsData" :loading="commandsLoading"
|
<tables :columns="commandColumns" :value="commandsData"
|
||||||
:pageOption="commandsPageOption" @changePage="queryCommands">
|
:pageOption="commandsPageOption" @changePage="queryCommands">
|
||||||
</tables>
|
</tables>
|
||||||
</div>
|
</div>
|
||||||
@@ -778,17 +781,35 @@ export default {
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'action',
|
key: 'action',
|
||||||
width: 120,
|
width: 180,
|
||||||
render: (h, params) => {
|
render: (h, params) => {
|
||||||
return h('Button', {
|
const btns = []
|
||||||
props: {
|
// 详情按钮
|
||||||
|
btns.push({
|
||||||
|
title: '详情',
|
||||||
type: 'primary',
|
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'
|
size: 'small'
|
||||||
},
|
},
|
||||||
|
style: { marginRight: '5px' },
|
||||||
on: {
|
on: {
|
||||||
click: () => this.showCommandDetail(params.row)
|
click: btn.click
|
||||||
}
|
}
|
||||||
}, '详情')
|
}, btn.title)
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -974,8 +995,16 @@ export default {
|
|||||||
pageOption: this.tasksPageOption
|
pageOption: this.tasksPageOption
|
||||||
}
|
}
|
||||||
const res = await plaAccountServer.getTasks(this.accountId, param)
|
const res = await plaAccountServer.getTasks(this.accountId, param)
|
||||||
|
|
||||||
|
|
||||||
this.tasksData = res.data.rows || []
|
this.tasksData = res.data.rows || []
|
||||||
this.tasksPageOption.total = res.data.count || 0
|
this.tasksPageOption.total = res.data.count || 0
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$forceUpdate()
|
||||||
|
}, 0)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$Message.error('加载任务列表失败')
|
this.$Message.error('加载任务列表失败')
|
||||||
this.tasksData = []
|
this.tasksData = []
|
||||||
@@ -997,8 +1026,13 @@ export default {
|
|||||||
pageOption: this.commandsPageOption
|
pageOption: this.commandsPageOption
|
||||||
}
|
}
|
||||||
const res = await plaAccountServer.getCommands(this.accountId, param)
|
const res = await plaAccountServer.getCommands(this.accountId, param)
|
||||||
|
|
||||||
this.commandsData = res.data.rows || []
|
this.commandsData = res.data.rows || []
|
||||||
this.commandsPageOption.total = res.data.count || 0
|
this.commandsPageOption.total = res.data.count || 0
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
this.$forceUpdate()
|
||||||
|
}, 0)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$Message.error('加载指令列表失败')
|
this.$Message.error('加载指令列表失败')
|
||||||
this.commandsData = []
|
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) {
|
async cancelTask(task) {
|
||||||
this.$Modal.confirm({
|
this.$Modal.confirm({
|
||||||
|
|||||||
@@ -284,6 +284,7 @@ export default {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await taskStatusServer.getCommands(row.id)
|
const res = await taskStatusServer.getCommands(row.id)
|
||||||
|
|
||||||
this.commandsModal.data = res.data || []
|
this.commandsModal.data = res.data || []
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.$Message.error('获取指令列表失败')
|
this.$Message.error('获取指令列表失败')
|
||||||
|
|||||||
@@ -445,6 +445,35 @@ module.exports = {
|
|||||||
return ctx.success(commandDetail);
|
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
|
* @swagger
|
||||||
* /admin_api/pla_account/parseLocation:
|
* /admin_api/pla_account/parseLocation:
|
||||||
|
|||||||
@@ -252,9 +252,12 @@ class PlaAccountService {
|
|||||||
order: [['id', 'DESC']]
|
order: [['id', 'DESC']]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 将 Sequelize 模型实例转换为普通对象
|
||||||
|
const rows = result.rows.map(row => row.get({ plain: true }));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
count: result.count,
|
count: result.count,
|
||||||
rows: result.rows
|
rows: rows
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,7 +269,9 @@ class PlaAccountService {
|
|||||||
async getAccountCommands(params) {
|
async getAccountCommands(params) {
|
||||||
const pla_account = db.getModel('pla_account');
|
const pla_account = db.getModel('pla_account');
|
||||||
const task_commands = db.getModel('task_commands');
|
const task_commands = db.getModel('task_commands');
|
||||||
|
const task_status = db.getModel('task_status');
|
||||||
const Sequelize = require('sequelize');
|
const Sequelize = require('sequelize');
|
||||||
|
const Op = Sequelize.Op;
|
||||||
|
|
||||||
const { id, limit, offset } = params;
|
const { id, limit, offset } = params;
|
||||||
|
|
||||||
@@ -280,51 +285,66 @@ class PlaAccountService {
|
|||||||
throw new Error('账号不存在');
|
throw new Error('账号不存在');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取 sequelize 实例
|
// 先查询所有匹配 sn_code 的任务 ID
|
||||||
const sequelize = task_commands.sequelize;
|
const tasks = await task_status.findAll({
|
||||||
|
where: {
|
||||||
// 使用原生 SQL JOIN 查询
|
sn_code: account.sn_code
|
||||||
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 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
|
|
||||||
`;
|
|
||||||
|
|
||||||
// 并行执行查询和计数
|
|
||||||
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
|
attributes: ['id']
|
||||||
})
|
});
|
||||||
]);
|
|
||||||
|
|
||||||
const count = countResult[0]?.count || 0;
|
const taskIds = tasks.map(task => task.id);
|
||||||
|
|
||||||
// 将原始数据转换为 Sequelize 模型实例
|
// 如果没有任务,直接返回空结果
|
||||||
const rows = dataResult.map(row => {
|
if (taskIds.length === 0) {
|
||||||
return task_commands.build(row, { isNewRecord: false });
|
return {
|
||||||
|
count: 0,
|
||||||
|
rows: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 Sequelize 模型查询指令列表
|
||||||
|
const result = await task_commands.findAndCountAll({
|
||||||
|
where: {
|
||||||
|
task_id: {
|
||||||
|
[Op.in]: taskIds
|
||||||
|
}
|
||||||
|
},
|
||||||
|
limit,
|
||||||
|
offset,
|
||||||
|
order: [['id', 'DESC']]
|
||||||
|
});
|
||||||
|
|
||||||
|
// 将 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 {
|
return {
|
||||||
count: parseInt(count),
|
count: result.count,
|
||||||
rows: rows
|
rows: rows
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -451,6 +471,100 @@ class PlaAccountService {
|
|||||||
return commandDetail;
|
return commandDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 重试指令
|
||||||
|
* @param {Object} params - 重试参数
|
||||||
|
* @param {number} params.commandId - 指令ID
|
||||||
|
* @returns {Promise<Object>} 重试结果
|
||||||
|
*/
|
||||||
|
async retryCommand(params) {
|
||||||
|
const task_commands = db.getModel('task_commands');
|
||||||
|
const task_status = db.getModel('task_status');
|
||||||
|
const scheduleManager = require('../middleware/schedule/index.js');
|
||||||
|
|
||||||
|
const { commandId } = params;
|
||||||
|
|
||||||
|
if (!commandId) {
|
||||||
|
throw new Error('指令ID不能为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询指令信息
|
||||||
|
const command = await task_commands.findByPk(commandId);
|
||||||
|
if (!command) {
|
||||||
|
throw new Error('指令不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查指令状态
|
||||||
|
if (command.status !== 'failed') {
|
||||||
|
throw new Error('只能重试失败的指令');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取任务信息
|
||||||
|
const task = await task_status.findByPk(command.task_id);
|
||||||
|
if (!task) {
|
||||||
|
throw new Error('任务不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取账号信息
|
||||||
|
const pla_account = db.getModel('pla_account');
|
||||||
|
const account = await pla_account.findOne({ where: { sn_code: task.sn_code } });
|
||||||
|
if (!account) {
|
||||||
|
throw new Error('账号不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查授权状态
|
||||||
|
const authCheck = await authorizationService.checkAuthorization(account.id, 'id');
|
||||||
|
if (!authCheck.is_authorized) {
|
||||||
|
throw new Error(authCheck.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查 MQTT 客户端
|
||||||
|
if (!scheduleManager.mqttClient) {
|
||||||
|
throw new Error('MQTT客户端未初始化');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置指令状态
|
||||||
|
await command.update({
|
||||||
|
status: 'pending',
|
||||||
|
error_message: null,
|
||||||
|
error_stack: null,
|
||||||
|
retry_count: (command.retry_count || 0) + 1,
|
||||||
|
start_time: null,
|
||||||
|
end_time: null,
|
||||||
|
duration: null,
|
||||||
|
result: null
|
||||||
|
});
|
||||||
|
|
||||||
|
// 解析指令参数
|
||||||
|
let commandParams = {};
|
||||||
|
if (command.command_params) {
|
||||||
|
try {
|
||||||
|
commandParams = typeof command.command_params === 'string'
|
||||||
|
? JSON.parse(command.command_params)
|
||||||
|
: command.command_params;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('解析指令参数失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建指令对象
|
||||||
|
const commandObj = {
|
||||||
|
command_type: command.command_type,
|
||||||
|
command_name: command.command_name,
|
||||||
|
command_params: JSON.stringify(commandParams)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 执行指令
|
||||||
|
const result = await scheduleManager.command.executeCommand(task.id, commandObj, scheduleManager.mqttClient);
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: '指令重试成功',
|
||||||
|
commandId: command.id,
|
||||||
|
result: result
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行账号任务(旧接口兼容)
|
* 执行账号任务(旧接口兼容)
|
||||||
* @param {Object} params - 任务参数
|
* @param {Object} params - 任务参数
|
||||||
|
|||||||
Reference in New Issue
Block a user