1
This commit is contained in:
0
admin/src/views/task/chat_records.vue
Normal file
0
admin/src/views/task/chat_records.vue
Normal file
415
admin/src/views/task/components/CommandsList.vue
Normal file
415
admin/src/views/task/components/CommandsList.vue
Normal file
@@ -0,0 +1,415 @@
|
||||
<template>
|
||||
<div class="commands-list-container commands-body">
|
||||
<!-- 头部导航 -->
|
||||
<div class="commands-header">
|
||||
<Button type="default" icon="ios-arrow-back" class="back-btn" @click="handleBack">
|
||||
返回任务列表
|
||||
</Button>
|
||||
<div class="commands-title">
|
||||
<Icon type="ios-list-box" size="20" />
|
||||
<span class="title-text">{{ title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<tables :columns="columns" :value="data" :loading="loading"></tables>
|
||||
|
||||
<!-- 查看完整内容弹窗 -->
|
||||
<Modal
|
||||
v-model="showDetailModal"
|
||||
title="查看完整内容"
|
||||
width="800"
|
||||
:mask-closable="true"
|
||||
>
|
||||
<div class="detail-content">
|
||||
<pre class="detail-pre">{{ detailContent }}</pre>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<Button @click="showDetailModal = false">关闭</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CommandsList',
|
||||
props: {
|
||||
// 是否显示
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: '指令列表'
|
||||
},
|
||||
// 加载状态
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 数据
|
||||
data: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showDetailModal: false,
|
||||
detailContent: '',
|
||||
columns: [
|
||||
{ title: 'ID', key: 'id', align: 'center' },
|
||||
{ title: '序号', key: 'sequence', align: 'center' },
|
||||
{
|
||||
title: '指令类型',
|
||||
key: 'command_type',
|
||||
align: 'center',
|
||||
type: 'template',
|
||||
render: (h, params) => {
|
||||
return h('Tag', {
|
||||
props: {
|
||||
color: this.getCommandTypeColor(params.row.command_type)
|
||||
}
|
||||
}, params.row.command_type)
|
||||
}
|
||||
},
|
||||
{ title: '指令名称', key: 'command_name' },
|
||||
{
|
||||
title: '指令参数',
|
||||
key: 'command_params',
|
||||
type: 'template',
|
||||
width: 250,
|
||||
render: (h, params) => {
|
||||
if (!params.row.command_params) {
|
||||
return h('span', '-')
|
||||
}
|
||||
return h('div', {
|
||||
class: 'json-content',
|
||||
style: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
on: {
|
||||
dblclick: () => {
|
||||
this.showDetail('指令参数', params.row.command_params)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
h('pre', this.formatJson(params.row.command_params))
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
type: 'template',
|
||||
render: (h, params) => {
|
||||
return h('Tag', {
|
||||
props: {
|
||||
color: this.getStatusColor(params.row.status)
|
||||
}
|
||||
}, this.getStatusText(params.row.status))
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '执行结果',
|
||||
key: 'result',
|
||||
type: 'template',
|
||||
width: 300,
|
||||
render: (h, params) => {
|
||||
if (!params.row.result) {
|
||||
return h('span', '-')
|
||||
}
|
||||
return h('div', {
|
||||
class: 'json-content',
|
||||
style: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
on: {
|
||||
dblclick: () => {
|
||||
this.showDetail('执行结果', params.row.result)
|
||||
}
|
||||
}
|
||||
}, [
|
||||
h('pre', this.formatJson(params.row.result))
|
||||
])
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '错误信息',
|
||||
key: 'error_message',
|
||||
type: 'template',
|
||||
render: (h, params) => {
|
||||
if (!params.row.error_message) {
|
||||
return h('span', '-')
|
||||
}
|
||||
return h('div', {
|
||||
class: 'error-content'
|
||||
}, params.row.error_message)
|
||||
}
|
||||
},
|
||||
{ title: '重试次数', key: 'retry_count', align: 'center' },
|
||||
{ title: '执行时长(s)', key: 'execution_time', align: 'center' },
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'create_time',
|
||||
type: 'template',
|
||||
render: (h, params) => {
|
||||
return h('span', this.formatDateTime(params.row.create_time))
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
key: 'last_modify_time',
|
||||
type: 'template',
|
||||
render: (h, params) => {
|
||||
return h('span', this.formatDateTime(params.row.last_modify_time))
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 返回按钮点击
|
||||
handleBack() {
|
||||
this.$emit('back')
|
||||
},
|
||||
|
||||
// 获取指令类型颜色
|
||||
getCommandTypeColor(type) {
|
||||
const colorMap = {
|
||||
'browser': 'blue',
|
||||
'search': 'cyan',
|
||||
'fill': 'geekblue',
|
||||
'click': 'purple',
|
||||
'wait': 'orange',
|
||||
'screenshot': 'magenta',
|
||||
'extract': 'green'
|
||||
}
|
||||
return colorMap[type] || 'default'
|
||||
},
|
||||
|
||||
// 获取状态颜色
|
||||
getStatusColor(status) {
|
||||
const colorMap = {
|
||||
'pending': 'default',
|
||||
'running': 'blue',
|
||||
'success': 'success',
|
||||
'failed': 'error',
|
||||
'skipped': 'warning'
|
||||
}
|
||||
return colorMap[status] || 'default'
|
||||
},
|
||||
|
||||
// 获取状态文本
|
||||
getStatusText(status) {
|
||||
const textMap = {
|
||||
'pending': '待执行',
|
||||
'running': '执行中',
|
||||
'success': '成功',
|
||||
'failed': '失败',
|
||||
'skipped': '已跳过'
|
||||
}
|
||||
return textMap[status] || status
|
||||
},
|
||||
|
||||
// 格式化JSON
|
||||
formatJson(str) {
|
||||
if (!str) return ''
|
||||
try {
|
||||
const obj = typeof str === 'string' ? JSON.parse(str) : str
|
||||
return JSON.stringify(obj, null, 2)
|
||||
} catch {
|
||||
return str
|
||||
}
|
||||
},
|
||||
|
||||
// 格式化日期时间
|
||||
formatDateTime(datetime) {
|
||||
if (!datetime) return '-'
|
||||
const date = new Date(datetime)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
},
|
||||
|
||||
// 显示完整内容
|
||||
showDetail(title, content) {
|
||||
this.detailContent = this.formatJson(content)
|
||||
this.showDetailModal = true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
/* 容器 - 撑满父元素 */
|
||||
.commands-list-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* 头部导航 */
|
||||
.commands-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 16px 20px;
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e8eaec;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.back-btn {
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
.commands-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.commands-title>>>.ivu-icon {
|
||||
margin-right: 8px;
|
||||
color: #2d8cf0;
|
||||
}
|
||||
|
||||
.title-text {
|
||||
color: #17233d;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* JSON内容显示 */
|
||||
.commands-body>>>.json-content {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
height: 120px;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
position: relative;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.commands-body>>>.json-content:hover {
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.commands-body>>>.json-content pre {
|
||||
margin: 0;
|
||||
padding: 8px;
|
||||
background: #f8f8f9;
|
||||
border-radius: 4px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
|
||||
color: #515a6e;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
max-height: 120px;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 错误信息显示 */
|
||||
.error-content {
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #ed4014;
|
||||
padding: 8px;
|
||||
background: #fff1f0;
|
||||
border-radius: 4px;
|
||||
border-left: 3px solid #ed4014;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* 表格样式优化 */
|
||||
.commands-body>>>.ivu-table {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.commands-body>>>.ivu-table-wrapper {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.commands-body>>>.ivu-table-body {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.commands-body>>>.ivu-table th {
|
||||
background: #f8f8f9;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.commands-body>>>.ivu-table-stripe .ivu-table-body tr:nth-child(2n) td {
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.commands-body>>>.ivu-table td {
|
||||
padding: 12px 8px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
/* 确保表格单元格内容不会撑高 */
|
||||
.commands-body>>>.ivu-table td .json-content {
|
||||
max-height: 120px;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.commands-body>>>.ivu-table td .json-content pre {
|
||||
max-height: 120px;
|
||||
overflow: hidden;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 滚动条美化 - 仅作用于外层容器 */
|
||||
.commands-body::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.commands-body::-webkit-scrollbar-thumb {
|
||||
background: #dcdee2;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.commands-body::-webkit-scrollbar-thumb:hover {
|
||||
background: #b8bbbf;
|
||||
}
|
||||
|
||||
.commands-body::-webkit-scrollbar-track {
|
||||
background: #f8f8f9;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 弹窗内容样式 */
|
||||
.detail-content {
|
||||
max-height: 60vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.detail-pre {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
background: #f8f8f9;
|
||||
border-radius: 4px;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', 'source-code-pro', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.6;
|
||||
color: #515a6e;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
</style>
|
||||
279
admin/src/views/task/components/README.md
Normal file
279
admin/src/views/task/components/README.md
Normal file
@@ -0,0 +1,279 @@
|
||||
# CommandsList 组件
|
||||
|
||||
## 📦 组件说明
|
||||
|
||||
`CommandsList.vue` 是一个通用的指令列表展示组件,专门用于在任务状态页面中展示任务的执行指令详情。
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
- ✅ **撑满父元素** - 组件宽高自动撑满父容器(100% width & height)
|
||||
- ✅ **自适应表格** - 表格列宽自动分配,无需手动设置每列宽度
|
||||
- ✅ **返回导航** - 内置返回按钮,支持返回上级页面
|
||||
- ✅ **加载状态** - 支持loading状态显示
|
||||
- ✅ **美观样式** - 现代化UI设计,带有斑马纹、边框等
|
||||
- ✅ **数据格式化** - 自动格式化JSON、时间等数据
|
||||
- ✅ **状态标签** - 彩色标签区分不同状态和类型
|
||||
- ✅ **独立封装** - 完全独立的组件,可在其他页面复用
|
||||
|
||||
## 📋 Props 参数
|
||||
|
||||
| 参数名 | 类型 | 默认值 | 必填 | 说明 |
|
||||
|--------|------|--------|------|------|
|
||||
| visible | Boolean | false | 否 | 是否显示组件 |
|
||||
| title | String | '指令列表' | 否 | 页面标题 |
|
||||
| loading | Boolean | false | 否 | 数据加载状态 |
|
||||
| data | Array | [] | 否 | 指令列表数据 |
|
||||
|
||||
## 🎯 Events 事件
|
||||
|
||||
| 事件名 | 参数 | 说明 |
|
||||
|--------|------|------|
|
||||
| back | 无 | 点击返回按钮时触发 |
|
||||
|
||||
## 💻 使用示例
|
||||
|
||||
### 基础用法
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="page-container">
|
||||
<!-- 列表页面 -->
|
||||
<div v-show="!showDetail">
|
||||
<Button @click="handleShowDetail">查看详情</Button>
|
||||
</div>
|
||||
|
||||
<!-- 详情页面 - 使用 CommandsList 组件 -->
|
||||
<CommandsList
|
||||
v-show="showDetail"
|
||||
:visible="showDetail"
|
||||
:title="detailTitle"
|
||||
:loading="detailLoading"
|
||||
:data="detailData"
|
||||
@back="handleBack" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommandsList from './components/CommandsList.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CommandsList
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showDetail: false,
|
||||
detailTitle: '指令列表',
|
||||
detailLoading: false,
|
||||
detailData: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async handleShowDetail() {
|
||||
this.showDetail = true
|
||||
this.detailLoading = true
|
||||
|
||||
try {
|
||||
// 获取数据
|
||||
const res = await this.fetchData()
|
||||
this.detailData = res.data
|
||||
} catch (error) {
|
||||
this.$Message.error('获取数据失败')
|
||||
} finally {
|
||||
this.detailLoading = false
|
||||
}
|
||||
},
|
||||
handleBack() {
|
||||
this.showDetail = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 在 task_status.vue 中的实际应用
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="content-view">
|
||||
<!-- 任务列表主页面 -->
|
||||
<div v-show="!showCommandsView" class="task-list-view">
|
||||
<!-- 任务列表内容 -->
|
||||
</div>
|
||||
|
||||
<!-- 指令列表详情页面 -->
|
||||
<CommandsList
|
||||
v-show="showCommandsView"
|
||||
:visible="showCommandsView"
|
||||
:title="commandsModal.title"
|
||||
:loading="commandsModal.loading"
|
||||
:data="commandsModal.data"
|
||||
@back="backToTaskList" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CommandsList from './components/CommandsList.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CommandsList
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showCommandsView: false,
|
||||
commandsModal: {
|
||||
loading: false,
|
||||
title: '指令列表',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async showCommands(row) {
|
||||
this.showCommandsView = true
|
||||
this.commandsModal.loading = true
|
||||
this.commandsModal.title = `指令列表 - 任务ID: ${row.id} (${row.taskName})`
|
||||
|
||||
try {
|
||||
const res = await taskStatusServer.getCommands(row.id)
|
||||
this.commandsModal.data = res.data || []
|
||||
} catch (error) {
|
||||
this.$Message.error('获取指令列表失败')
|
||||
this.commandsModal.data = []
|
||||
} finally {
|
||||
this.commandsModal.loading = false
|
||||
}
|
||||
},
|
||||
backToTaskList() {
|
||||
this.showCommandsView = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
## 📊 数据格式
|
||||
|
||||
### 指令数据结构(data 数组元素)
|
||||
|
||||
```javascript
|
||||
{
|
||||
id: 123, // 指令ID
|
||||
sequence: 1, // 序号
|
||||
command_type: 'browser', // 指令类型:browser/search/fill/click/wait/screenshot/extract
|
||||
command_name: '打开浏览器', // 指令名称
|
||||
command_params: '{"url":"..."}', // 指令参数(JSON字符串)
|
||||
status: 'success', // 状态:pending/running/success/failed/skipped
|
||||
result: '{"data":"..."}', // 执行结果(JSON字符串)
|
||||
error_message: null, // 错误信息
|
||||
retry_count: 0, // 重试次数
|
||||
execution_time: 1.23, // 执行时长(秒)
|
||||
create_time: '2025-10-29 10:00:00', // 创建时间
|
||||
}
|
||||
```
|
||||
|
||||
## 🎨 样式特性
|
||||
|
||||
### 布局特性
|
||||
- **弹性布局** - 使用 flexbox 实现头部+内容区域布局
|
||||
- **100%撑满** - 宽高100%,自动撑满父容器
|
||||
- **自适应列宽** - 表格列宽自动分配,无需手动设置
|
||||
|
||||
### 视觉效果
|
||||
- **现代化头部** - 带返回按钮和标题的导航栏
|
||||
- **美观表格** - 斑马纹、边框、悬停效果
|
||||
- **彩色标签** - 不同状态使用不同颜色区分
|
||||
- **格式化显示** - JSON自动格式化,代码高亮
|
||||
- **错误高亮** - 错误信息红色背景突出显示
|
||||
- **滚动优化** - 美化的滚动条样式
|
||||
|
||||
### 响应式
|
||||
- 表格自动适应父容器宽度
|
||||
- 内容区域独立滚动
|
||||
- 头部固定不滚动
|
||||
|
||||
## 🔧 自定义样式
|
||||
|
||||
如果需要自定义样式,可以在父组件中使用深度选择器:
|
||||
|
||||
```vue
|
||||
<style scoped>
|
||||
/* 修改组件内部样式 */
|
||||
.my-page >>> .commands-list-container {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
/* 修改表格样式 */
|
||||
.my-page >>> .commands-body .ivu-table {
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
## 📌 注意事项
|
||||
|
||||
1. **父容器要求** - 父容器必须有明确的宽高,否则组件无法正确撑满
|
||||
2. **v-show控制** - 建议使用 `v-show` 而不是 `v-if`,以保持状态
|
||||
3. **数据格式** - JSON字段支持字符串和对象两种格式,组件会自动处理
|
||||
4. **时间格式** - 时间字段应为标准的日期时间字符串
|
||||
5. **状态映射** - 组件内部已定义状态和类型的颜色映射,可根据需要修改
|
||||
|
||||
## 🚀 特色功能
|
||||
|
||||
### 1. 自动JSON格式化
|
||||
组件会自动格式化 `command_params` 和 `result` 字段的JSON数据,提供更好的可读性。
|
||||
|
||||
### 2. 智能状态标签
|
||||
根据不同的指令类型和状态,自动显示对应颜色的标签:
|
||||
|
||||
**指令类型颜色**:
|
||||
- browser - 蓝色
|
||||
- search - 青色
|
||||
- fill - 深蓝色
|
||||
- click - 紫色
|
||||
- wait - 橙色
|
||||
- screenshot - 洋红色
|
||||
- extract - 绿色
|
||||
|
||||
**状态颜色**:
|
||||
- pending(待执行) - 灰色
|
||||
- running(执行中) - 蓝色
|
||||
- success(成功) - 绿色
|
||||
- failed(失败) - 红色
|
||||
- skipped(已跳过) - 橙色
|
||||
|
||||
### 3. 错误信息高亮
|
||||
错误信息使用红色背景和左边框高亮显示,便于快速定位问题。
|
||||
|
||||
### 4. 时间格式化
|
||||
自动格式化时间为 `YYYY-MM-DD HH:mm:ss` 格式,统一时间显示样式。
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
### v1.0.0 (2025-10-29)
|
||||
- ✅ 初始版本发布
|
||||
- ✅ 实现基础功能
|
||||
- ✅ 移除所有列宽限制,实现表格自动撑满
|
||||
- ✅ 优化样式和交互
|
||||
- ✅ 完善组件文档
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
如需改进此组件,请遵循以下原则:
|
||||
1. 保持组件的通用性和独立性
|
||||
2. 确保样式不依赖外部全局样式
|
||||
3. 维护良好的代码注释
|
||||
4. 更新此文档说明
|
||||
|
||||
## 📄 许可
|
||||
|
||||
此组件为项目内部组件,仅供项目内使用。
|
||||
|
||||
324
admin/src/views/task/task_status.vue
Normal file
324
admin/src/views/task/task_status.vue
Normal file
@@ -0,0 +1,324 @@
|
||||
<template>
|
||||
<div class="content-view">
|
||||
<!-- 任务列表主页面 -->
|
||||
<div v-show="!showCommandsView" class="task-list-view">
|
||||
<div class="table-head-tool">
|
||||
<Button type="primary" @click="showAddWarp">新增任务</Button>
|
||||
<Form ref="formInline" :model="gridOption.param.seachOption" inline :label-width="80">
|
||||
<FormItem :label-width="20" class="flex">
|
||||
<Select v-model="gridOption.param.seachOption.key" style="width: 120px"
|
||||
:placeholder="seachTypePlaceholder">
|
||||
<Option v-for="item in seachTypes" :value="item.key" :key="item.key">{{ item.value }}</Option>
|
||||
</Select>
|
||||
<Input class="ml10" v-model="gridOption.param.seachOption.value" style="width: 200px" search
|
||||
placeholder="请输入关键字" @on-search="query(1)" />
|
||||
</FormItem>
|
||||
<FormItem label="任务类型">
|
||||
<Select v-model="gridOption.param.seachOption.taskType" style="width: 120px" clearable @on-change="query(1)">
|
||||
<Option value="search">搜索岗位</Option>
|
||||
<Option value="apply">投递简历</Option>
|
||||
<Option value="chat">聊天回复</Option>
|
||||
<Option value="follow">跟进</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem label="任务状态">
|
||||
<Select v-model="gridOption.param.seachOption.status" style="width: 120px" clearable @on-change="query(1)">
|
||||
<Option value="pending">待执行</Option>
|
||||
<Option value="running">执行中</Option>
|
||||
<Option value="success">成功</Option>
|
||||
<Option value="failed">失败</Option>
|
||||
<Option value="cancelled">已取消</Option>
|
||||
</Select>
|
||||
</FormItem>
|
||||
<FormItem>
|
||||
<Button type="primary" @click="query(1)">查询</Button>
|
||||
<Button type="default" @click="resetQuery" class="ml10">重置</Button>
|
||||
<Button type="default" @click="exportCsv">导出</Button>
|
||||
</FormItem>
|
||||
</Form>
|
||||
</div>
|
||||
<div class="table-body">
|
||||
<tables :columns="listColumns" :value="gridOption.data" :pageOption="gridOption.param.pageOption"
|
||||
@changePage="query"></tables>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 指令列表详情页面 -->
|
||||
<CommandsList
|
||||
v-show="showCommandsView"
|
||||
:visible="showCommandsView"
|
||||
:title="commandsModal.title"
|
||||
:loading="commandsModal.loading"
|
||||
:data="commandsModal.data"
|
||||
@back="backToTaskList" />
|
||||
|
||||
|
||||
<editModal ref="editModal" :columns="editColumns" :rules="gridOption.rules"></editModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import taskStatusServer from '@/api/operation/task_status_server.js'
|
||||
import CommandsList from './components/CommandsList.vue'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CommandsList
|
||||
},
|
||||
data() {
|
||||
let rules = {}
|
||||
rules["taskType"] = [{ required: true, message: '请选择任务类型', trigger: 'change' }]
|
||||
rules["sn_code"] = [{ required: true, message: '请填写设备SN码', trigger: 'blur' }]
|
||||
|
||||
return {
|
||||
seachTypes: [
|
||||
{ key: 'taskName', value: '任务名称' },
|
||||
{ key: 'sn_code', value: '设备SN码' },
|
||||
{ key: 'id', value: '任务ID' }
|
||||
],
|
||||
showCommandsView: false, // 是否显示指令列表视图
|
||||
gridOption: {
|
||||
param: {
|
||||
seachOption: {
|
||||
key: 'taskName',
|
||||
value: '',
|
||||
taskType: null,
|
||||
status: null
|
||||
},
|
||||
pageOption: {
|
||||
page: 1,
|
||||
pageSize: 20
|
||||
}
|
||||
},
|
||||
data: [],
|
||||
rules: rules
|
||||
},
|
||||
listColumns: [
|
||||
{ title: '任务ID', key: 'id', minWidth: 180 },
|
||||
{ title: '设备SN码', key: 'sn_code', minWidth: 120 },
|
||||
{
|
||||
title: '任务类型',
|
||||
key: 'taskType',
|
||||
minWidth: 100,
|
||||
render: (h, params) => {
|
||||
const typeMap = {
|
||||
'search': { text: '搜索岗位', color: 'blue' },
|
||||
'apply': { text: '投递简历', color: 'success' },
|
||||
'chat': { text: '聊天回复', color: 'purple' },
|
||||
'follow': { text: '跟进', color: 'orange' }
|
||||
}
|
||||
const type = typeMap[params.row.taskType] || { text: params.row.taskType, color: 'default' }
|
||||
return h('Tag', { props: { color: type.color } }, type.text)
|
||||
}
|
||||
},
|
||||
{ title: '任务名称', key: 'taskName', minWidth: 150 },
|
||||
{
|
||||
title: '任务状态',
|
||||
key: 'status',
|
||||
minWidth: 100,
|
||||
render: (h, params) => {
|
||||
const statusMap = {
|
||||
'pending': { text: '待执行', color: 'default' },
|
||||
'running': { text: '执行中', color: 'blue' },
|
||||
'success': { text: '成功', color: 'success' },
|
||||
'failed': { text: '失败', color: 'error' },
|
||||
'cancelled': { text: '已取消', color: 'warning' }
|
||||
}
|
||||
const status = statusMap[params.row.status] || { text: params.row.status, color: 'default' }
|
||||
return h('Tag', { props: { color: status.color } }, status.text)
|
||||
}
|
||||
},
|
||||
{ title: '进度', key: 'progress', minWidth: 100 },
|
||||
{ title: '开始时间', key: 'startTime', minWidth: 150 },
|
||||
{ title: '结束时间', key: 'endTime', minWidth: 150 },
|
||||
{ title: '创建时间', key: 'create_time', minWidth: 150 },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 360,
|
||||
type: 'template',
|
||||
render: (h, params) => {
|
||||
let btns = [
|
||||
{
|
||||
title: '指令列表',
|
||||
type: 'info',
|
||||
click: () => {
|
||||
this.showCommands(params.row)
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '编辑',
|
||||
type: 'primary',
|
||||
click: () => {
|
||||
this.showEditWarp(params.row)
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
if (params.row.status === 'failed') {
|
||||
btns.push({
|
||||
title: '重试',
|
||||
type: 'success',
|
||||
click: () => {
|
||||
this.retryTask(params.row)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
if (params.row.status === 'pending' || params.row.status === 'running') {
|
||||
btns.push({
|
||||
title: '取消',
|
||||
type: 'warning',
|
||||
click: () => {
|
||||
this.cancelTask(params.row)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
btns.push({
|
||||
title: '删除',
|
||||
type: 'error',
|
||||
click: () => {
|
||||
this.delConfirm(params.row)
|
||||
},
|
||||
})
|
||||
|
||||
return window.framework.uiTool.getBtn(h, btns)
|
||||
},
|
||||
}
|
||||
],
|
||||
editColumns: [
|
||||
{ title: '设备SN码', key: 'sn_code', type: 'text', required: true },
|
||||
{ title: '任务类型', key: 'taskType', type: 'select', required: true, options: [
|
||||
{ value: 'search', label: '搜索岗位' },
|
||||
{ value: 'apply', label: '投递简历' },
|
||||
{ value: 'chat', label: '聊天回复' },
|
||||
{ value: 'follow', label: '跟进' }
|
||||
]},
|
||||
{ title: '任务名称', key: 'taskName', type: 'text' },
|
||||
{ title: '任务描述', key: 'taskDescription', type: 'textarea' },
|
||||
{ title: '任务参数', key: 'taskParams', type: 'textarea' },
|
||||
{ title: '优先级', key: 'priority', type: 'number' },
|
||||
{ title: '最大重试次数', key: 'maxRetries', type: 'number' },
|
||||
{ title: '计划执行时间', key: 'scheduledTime', type: 'datetime' }
|
||||
],
|
||||
commandsModal: {
|
||||
loading: false,
|
||||
title: '指令列表',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.query(1)
|
||||
},
|
||||
methods: {
|
||||
query(page) {
|
||||
this.gridOption.param.pageOption.page = page
|
||||
taskStatusServer.page(this.gridOption.param).then(res => {
|
||||
this.gridOption.data = res.data.rows
|
||||
this.gridOption.param.pageOption.total = res.data.count
|
||||
})
|
||||
},
|
||||
showAddWarp() {
|
||||
this.$refs.editModal.showModal()
|
||||
},
|
||||
showEditWarp(row) {
|
||||
this.$refs.editModal.showModal(row)
|
||||
},
|
||||
cancelTask(row) {
|
||||
this.$Modal.confirm({
|
||||
title: '确认取消任务',
|
||||
content: `确定要取消任务 "${row.taskName}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await taskStatusServer.cancel(row)
|
||||
this.$Message.success('任务取消成功!')
|
||||
this.query(this.gridOption.param.pageOption.page)
|
||||
} catch (error) {
|
||||
this.$Message.error('任务取消失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
retryTask(row) {
|
||||
this.$Modal.confirm({
|
||||
title: '确认重试任务',
|
||||
content: `确定要重试任务 "${row.taskName}" 吗?`,
|
||||
onOk: async () => {
|
||||
try {
|
||||
await taskStatusServer.retry(row)
|
||||
this.$Message.success('任务重试成功!')
|
||||
this.query(this.gridOption.param.pageOption.page)
|
||||
} catch (error) {
|
||||
this.$Message.error('任务重试失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
delConfirm(row) {
|
||||
window.framework.uiTool.delConfirm(async () => {
|
||||
await taskStatusServer.del(row)
|
||||
this.$Message.success('删除成功!')
|
||||
this.query(1)
|
||||
})
|
||||
},
|
||||
exportCsv() {
|
||||
taskStatusServer.exportCsv(this.gridOption.param).then(res => {
|
||||
window.framework.funTool.downloadFile(res, '任务状态.csv')
|
||||
})
|
||||
},
|
||||
resetQuery() {
|
||||
this.gridOption.param.seachOption = {
|
||||
key: 'taskName',
|
||||
value: '',
|
||||
taskType: null,
|
||||
status: null
|
||||
}
|
||||
this.query(1)
|
||||
},
|
||||
async showCommands(row) {
|
||||
this.showCommandsView = true
|
||||
this.commandsModal.loading = true
|
||||
this.commandsModal.title = `指令列表 - 任务ID: ${row.id} (${row.taskName})`
|
||||
|
||||
try {
|
||||
const res = await taskStatusServer.getCommands(row.id)
|
||||
this.commandsModal.data = res.data || []
|
||||
} catch (error) {
|
||||
this.$Message.error('获取指令列表失败')
|
||||
this.commandsModal.data = []
|
||||
} finally {
|
||||
this.commandsModal.loading = false
|
||||
}
|
||||
},
|
||||
backToTaskList() {
|
||||
this.showCommandsView = false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
seachTypePlaceholder() {
|
||||
const selected = this.seachTypes.find(item => item.key === this.gridOption.param.seachOption.key)
|
||||
return selected ? selected.value : '请选择搜索类型'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.ml10 {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* 任务列表视图 */
|
||||
.task-list-view {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user