feat: 机器任务优化
1.从楼上下来的托盘需要缓存站资源 2.取消任务时可以选择是否解绑 3.对货架托盘混下的任务按比例进行组合
This commit is contained in:
parent
d98c8d17d9
commit
19ffe84785
|
|
@ -0,0 +1,18 @@
|
|||
import request from '@/utils/request'
|
||||
|
||||
export interface PalletCacheStationVO {
|
||||
warehouse: string
|
||||
capacity: number
|
||||
boundCount: number
|
||||
reservedCount: number
|
||||
totalCount: number
|
||||
remainingCapacity: number
|
||||
reservedChainCodes: string[]
|
||||
}
|
||||
|
||||
export function getPalletCacheStationSnapshot(warehouse: string) {
|
||||
return request.get<never, { code: number; data: PalletCacheStationVO }>(
|
||||
'/api/robot/external/admin/pallet-cache-station/snapshot',
|
||||
{ params: { warehouse } },
|
||||
)
|
||||
}
|
||||
|
|
@ -129,11 +129,11 @@ export const taskStatusOptions = [
|
|||
{ value: 'FAILED', label: '失败' },
|
||||
]
|
||||
|
||||
export function cancelChain(chainCode: string, reason?: string) {
|
||||
export function cancelChain(chainCode: string, reason?: string, moveOut = false) {
|
||||
return request.post<never, { code: number; data: boolean }>(
|
||||
'/api/robot/external/admin/machine-task/chain/cancel',
|
||||
null,
|
||||
{ params: { chainCode, reason } },
|
||||
{ params: { chainCode, reason, moveOut } },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@
|
|||
<el-icon><Tickets /></el-icon>
|
||||
<template #title>站点排队</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/station/pallet-cache">
|
||||
<el-icon><Box /></el-icon>
|
||||
<template #title>托盘缓存站</template>
|
||||
</el-menu-item>
|
||||
<el-menu-item v-if="!isMonitorMode" index="/vendor/load">
|
||||
<el-icon><DataLine /></el-icon>
|
||||
<template #title>厂商负载</template>
|
||||
|
|
@ -168,7 +172,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
|||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useDevice } from '@/composables/useDevice'
|
||||
import { Monitor, List, SetUp, Plus, DataLine, Fold, Expand, User, ArrowDown, Refresh, Tickets } from '@element-plus/icons-vue'
|
||||
import { Monitor, List, SetUp, Plus, DataLine, Fold, Expand, User, ArrowDown, Refresh, Tickets, Box } from '@element-plus/icons-vue'
|
||||
import { useTabsStore } from '@/stores/tabs'
|
||||
|
||||
const { isMobile } = useDevice()
|
||||
|
|
@ -199,7 +203,7 @@ const userStore = useUserStore()
|
|||
const isMonitorMode = computed(() => userStore.isMonitorMode())
|
||||
const keepAliveNames = computed(() => {
|
||||
if (isMonitorMode.value) {
|
||||
return ['TaskChainList', 'TaskChainDetail', 'ElevatorList', 'StationQueueView']
|
||||
return ['TaskChainList', 'TaskChainDetail', 'ElevatorList', 'StationQueueView', 'PalletCacheStationView']
|
||||
}
|
||||
return [
|
||||
'DashboardView',
|
||||
|
|
@ -207,6 +211,7 @@ const keepAliveNames = computed(() => {
|
|||
'TaskChainDetail',
|
||||
'ElevatorList',
|
||||
'StationQueueView',
|
||||
'PalletCacheStationView',
|
||||
'VendorLoadView',
|
||||
'TaskGenerateView',
|
||||
]
|
||||
|
|
@ -330,6 +335,7 @@ const tabs = computed(() => [
|
|||
{ path: '/task/chain', label: '任务链', icon: List },
|
||||
{ path: '/elevator', label: '提升机', icon: SetUp },
|
||||
{ path: '/station/queue', label: '排队', icon: Tickets },
|
||||
{ path: '/station/pallet-cache', label: '缓存站', icon: Box },
|
||||
...(!isMonitorMode.value ? [{ path: '/vendor/load', label: '厂商负载', icon: DataLine }] : []),
|
||||
...(!isMonitorMode.value ? [{ path: '/generate', label: '生成', icon: Plus }] : []),
|
||||
])
|
||||
|
|
@ -338,6 +344,7 @@ const activeMenu = computed(() => {
|
|||
const path = route.path
|
||||
if (path.startsWith('/task/chain')) return '/task/chain'
|
||||
if (path.startsWith('/station/queue')) return '/station/queue'
|
||||
if (path.startsWith('/station/pallet-cache')) return '/station/pallet-cache'
|
||||
return path
|
||||
})
|
||||
|
||||
|
|
@ -345,6 +352,7 @@ const activeTab = computed(() => {
|
|||
const path = route.path
|
||||
if (path.startsWith('/task/chain')) return '/task/chain'
|
||||
if (path.startsWith('/station/queue')) return '/station/queue'
|
||||
if (path.startsWith('/station/pallet-cache')) return '/station/pallet-cache'
|
||||
return path
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,12 @@ const router = createRouter({
|
|||
component: () => import('@/views/station/StationQueueView.vue'),
|
||||
meta: { title: '站点排队' },
|
||||
},
|
||||
{
|
||||
path: 'station/pallet-cache',
|
||||
name: 'PalletCacheStationView',
|
||||
component: () => import('@/views/station/PalletCacheStationView.vue'),
|
||||
meta: { title: '托盘缓存站' },
|
||||
},
|
||||
{
|
||||
path: 'vendor/load',
|
||||
name: 'VendorLoadView',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,461 @@
|
|||
<template>
|
||||
<div class="page-container">
|
||||
<el-card shadow="hover" class="filter-card">
|
||||
<div class="filter-actions">
|
||||
<div class="filter-warehouse">
|
||||
<span class="filter-label">仓库</span>
|
||||
<el-input
|
||||
v-model="warehouse"
|
||||
placeholder="如 WH01"
|
||||
clearable
|
||||
@keyup.enter="loadData"
|
||||
/>
|
||||
</div>
|
||||
<el-button type="primary" :loading="loading" @click="loadData">查询</el-button>
|
||||
</div>
|
||||
<el-alert type="info" show-icon :closable="false" class="hint">
|
||||
缓存站总占用 = 已绑定缓存站库位的托盘数量 + Redis 中在途预占缓存站名额的任务链数量。
|
||||
</el-alert>
|
||||
</el-card>
|
||||
|
||||
<div class="board-toolbar">
|
||||
<div class="board-titles">
|
||||
<span class="board-title">托盘缓存站</span>
|
||||
<span v-if="snapshot" class="board-sub">{{ snapshot.warehouse }}</span>
|
||||
</div>
|
||||
<div class="board-actions">
|
||||
<span class="board-hint">点击预占任务链查看详情 · 预占数据来自 Redis Set</span>
|
||||
<el-button type="primary" size="small" :loading="loading" @click="loadData">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="board-body" v-loading="loading">
|
||||
<el-empty v-if="!snapshot && !loading" description="暂无数据,请确认仓库编码" />
|
||||
<div v-else-if="snapshot" class="cache-layout">
|
||||
<el-card shadow="hover" class="summary-card">
|
||||
<template #header>
|
||||
<div class="summary-head">
|
||||
<span class="summary-title">容量状态</span>
|
||||
<el-tag :type="capacityStressType" size="small" effect="plain">
|
||||
占用 {{ snapshot.totalCount }} / 容量 {{ snapshot.capacity }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="capacity-bar">
|
||||
<el-progress
|
||||
:percentage="capacityPercentage"
|
||||
:status="capacityProgressStatus"
|
||||
:stroke-width="12"
|
||||
striped
|
||||
striped-flow
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-block">
|
||||
<span class="stat-k">已绑定</span>
|
||||
<span class="stat-v">{{ snapshot.boundCount }}</span>
|
||||
</div>
|
||||
<div class="stat-block">
|
||||
<span class="stat-k">在途预占</span>
|
||||
<span class="stat-v">{{ snapshot.reservedCount }}</span>
|
||||
</div>
|
||||
<div class="stat-block">
|
||||
<span class="stat-k">总占用</span>
|
||||
<span class="stat-v">{{ snapshot.totalCount }}</span>
|
||||
</div>
|
||||
<div class="stat-block">
|
||||
<span class="stat-k">剩余容量</span>
|
||||
<span class="stat-v">{{ snapshot.remainingCapacity }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="hover" class="chains-card">
|
||||
<template #header>
|
||||
<div class="chains-head">
|
||||
<span class="chains-title">预占任务链</span>
|
||||
<span v-if="snapshot.reservedChainCodes?.length" class="chains-n">
|
||||
{{ snapshot.reservedChainCodes.length }} 条
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="snapshot.reservedChainCodes?.length" class="chains-scroll">
|
||||
<button
|
||||
v-for="c in snapshot.reservedChainCodes"
|
||||
:key="c"
|
||||
type="button"
|
||||
class="chain-row"
|
||||
@click="goChainDetail(c)"
|
||||
>
|
||||
<span class="chain-row__code">{{ c }}</span>
|
||||
<span class="chain-row__sep" aria-hidden="true">:</span>
|
||||
<span class="chain-row__containers">{{ chainContainersText(c) }}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="chains-empty">无</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'PalletCacheStationView' })
|
||||
import {
|
||||
getPalletCacheStationSnapshot,
|
||||
type PalletCacheStationVO,
|
||||
} from '@/api/palletCacheStation'
|
||||
import { getChainDetail, type TaskChainDetailVO } from '@/api/task'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const warehouse = ref('WH01')
|
||||
const snapshot = ref<PalletCacheStationVO | null>(null)
|
||||
const loading = ref(false)
|
||||
|
||||
/** 任务链号 -> 容器串(冒号拼接);载入中 / 失败为占位标记 */
|
||||
const chainContainersLabel = ref<Record<string, string>>({})
|
||||
|
||||
const capacityPercentage = computed(() => {
|
||||
const s = snapshot.value
|
||||
if (!s || s.capacity <= 0) return 0
|
||||
return Math.min(100, Math.round((s.totalCount / s.capacity) * 100))
|
||||
})
|
||||
|
||||
const capacityStressType = computed<'success' | 'warning' | 'danger' | 'info'>(() => {
|
||||
const s = snapshot.value
|
||||
if (!s || s.capacity <= 0) return s?.totalCount ? 'danger' : 'info'
|
||||
if (s.totalCount >= s.capacity) return 'danger'
|
||||
if (s.totalCount / s.capacity >= 0.8) return 'warning'
|
||||
return 'success'
|
||||
})
|
||||
|
||||
const capacityProgressStatus = computed<'success' | 'warning' | 'exception' | undefined>(() => {
|
||||
if (capacityStressType.value === 'danger') return 'exception'
|
||||
if (capacityStressType.value === 'warning') return 'warning'
|
||||
if (capacityStressType.value === 'success') return 'success'
|
||||
return undefined
|
||||
})
|
||||
|
||||
function collectContainersFromDetail(d: TaskChainDetailVO): string[] {
|
||||
const out: string[] = []
|
||||
const push = (v: string | undefined) => {
|
||||
const t = v?.trim()
|
||||
if (t) out.push(t)
|
||||
}
|
||||
push(d.sourceContainer)
|
||||
for (const task of d.tasks ?? []) {
|
||||
push(task.sourceContainer)
|
||||
}
|
||||
return [...new Set(out)]
|
||||
}
|
||||
|
||||
function chainContainersText(code: string): string {
|
||||
const v = chainContainersLabel.value[code]
|
||||
if (v === '__loading__') return '容器载入中...'
|
||||
if (v === '__error__') return '容器加载失败'
|
||||
return v ?? '-'
|
||||
}
|
||||
|
||||
async function loadData() {
|
||||
const wh = warehouse.value?.trim()
|
||||
if (!wh) {
|
||||
ElMessage.warning('请输入仓库编码')
|
||||
return
|
||||
}
|
||||
loading.value = true
|
||||
chainContainersLabel.value = {}
|
||||
try {
|
||||
const res = await getPalletCacheStationSnapshot(wh)
|
||||
snapshot.value = res.data || null
|
||||
const codes = [
|
||||
...new Set((snapshot.value?.reservedChainCodes ?? []).filter((c) => !!c?.trim())),
|
||||
]
|
||||
for (const c of codes) {
|
||||
chainContainersLabel.value[c] = '__loading__'
|
||||
}
|
||||
await Promise.all(
|
||||
codes.map(async (code) => {
|
||||
try {
|
||||
const detailRes = await getChainDetail(code)
|
||||
const list = collectContainersFromDetail(detailRes.data)
|
||||
chainContainersLabel.value[code] = list.length ? list.join(':') : '-'
|
||||
} catch {
|
||||
chainContainersLabel.value[code] = '__error__'
|
||||
}
|
||||
}),
|
||||
)
|
||||
} catch {
|
||||
snapshot.value = null
|
||||
chainContainersLabel.value = {}
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function goChainDetail(code: string) {
|
||||
if (!code) return
|
||||
router.push(`/task/chain/${encodeURIComponent(code)}`)
|
||||
}
|
||||
|
||||
onMounted(loadData)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.page-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.filter-card .hint {
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.filter-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
gap: 12px 16px;
|
||||
}
|
||||
|
||||
.filter-warehouse {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 200px;
|
||||
flex: 1;
|
||||
max-width: 280px;
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-regular);
|
||||
}
|
||||
|
||||
.board-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.board-titles {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 8px 12px;
|
||||
}
|
||||
|
||||
.board-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.board-sub {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.board-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 10px 12px;
|
||||
}
|
||||
|
||||
.board-hint {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
max-width: min(420px, 100%);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.board-body {
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.cache-layout {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(280px, 420px) minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.summary-card :deep(.el-card__header),
|
||||
.chains-card :deep(.el-card__header) {
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
}
|
||||
|
||||
.summary-card :deep(.el-card__body),
|
||||
.chains-card :deep(.el-card__body) {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.summary-head,
|
||||
.chains-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.summary-title,
|
||||
.chains-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
|
||||
.capacity-bar {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.capacity-bar :deep(.el-progress-bar__inner--striped.el-progress-bar__inner--striped-flow) {
|
||||
animation-duration: 12s !important;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.stat-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: var(--el-fill-color-lighter);
|
||||
}
|
||||
|
||||
.stat-k {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.stat-v {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
color: var(--el-text-color-primary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.chains-n {
|
||||
font-size: 12px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.chains-scroll {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 6px;
|
||||
max-height: 360px;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
padding: 2px;
|
||||
margin: 0 -2px;
|
||||
scrollbar-gutter: stable;
|
||||
}
|
||||
|
||||
.chain-row {
|
||||
display: inline-flex;
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
gap: 0 4px;
|
||||
width: max-content;
|
||||
max-width: none;
|
||||
margin: 0;
|
||||
padding: 6px 10px;
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
font: inherit;
|
||||
line-height: 1.45;
|
||||
white-space: nowrap;
|
||||
color: var(--el-text-color-primary);
|
||||
background: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 0.15s ease,
|
||||
border-color 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.chain-row:hover {
|
||||
background: var(--el-fill-color-blank);
|
||||
border-color: var(--el-color-primary-light-7);
|
||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.chain-row:active {
|
||||
background: var(--el-fill-color-light);
|
||||
}
|
||||
|
||||
.chain-row:focus-visible {
|
||||
outline: 2px solid var(--el-color-primary-light-5);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.chain-row__code {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--el-color-primary);
|
||||
letter-spacing: 0.02em;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chain-row__sep {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.chain-row__containers {
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: var(--el-text-color-regular);
|
||||
flex-shrink: 0;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.chains-empty {
|
||||
font-size: 13px;
|
||||
color: var(--el-text-color-placeholder);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.cache-layout {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.filter-warehouse {
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.board-actions {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -390,6 +390,42 @@
|
|||
</template>
|
||||
|
||||
<!-- ========== 共用弹窗 ========== -->
|
||||
<!-- 取消任务链 -->
|
||||
<el-dialog
|
||||
v-model="cancelDialogVisible"
|
||||
title="取消任务链"
|
||||
:width="isMobile ? '90%' : '420px'"
|
||||
destroy-on-close
|
||||
>
|
||||
<el-form :model="cancelForm" label-width="110px">
|
||||
<el-form-item label="任务链编号">
|
||||
<el-input :model-value="chain?.code || ''" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="取消原因">
|
||||
<el-input
|
||||
v-model="cancelForm.reason"
|
||||
type="textarea"
|
||||
:rows="3"
|
||||
placeholder="取消原因(可选)"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="移至库外">
|
||||
<el-switch
|
||||
v-model="cancelForm.moveOut"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="cancelDialogVisible = false">返回</el-button>
|
||||
<el-button type="danger" :loading="canceling" @click="submitCancelChain">
|
||||
确认取消
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 模拟回调:选择场景 -->
|
||||
<el-dialog
|
||||
v-model="scenarioDialogVisible"
|
||||
|
|
@ -458,7 +494,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
defineOptions({ name: 'TaskChainDetail' })
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ArrowLeft, MoreFilled, VideoPlay, Switch, Delete, House } from '@element-plus/icons-vue'
|
||||
|
|
@ -480,6 +516,11 @@ const route = useRoute()
|
|||
const loading = ref(false)
|
||||
const executing = ref(false)
|
||||
const canceling = ref(false)
|
||||
const cancelDialogVisible = ref(false)
|
||||
const cancelForm = reactive({
|
||||
reason: '',
|
||||
moveOut: false,
|
||||
})
|
||||
const returningTaskCode = ref<string | null>(null)
|
||||
const chain = ref<TaskChainDetailVO | null>(null)
|
||||
const tasks = ref<TaskVO[]>([])
|
||||
|
|
@ -566,18 +607,20 @@ function onPickStatus(targetStatus: string) {
|
|||
|
||||
// ==================== 操作逻辑(共用) ====================
|
||||
async function handleCancelChain() {
|
||||
if (!chain.value) return
|
||||
cancelForm.reason = ''
|
||||
cancelForm.moveOut = false
|
||||
cancelDialogVisible.value = true
|
||||
}
|
||||
|
||||
async function submitCancelChain() {
|
||||
if (!chain.value) return
|
||||
try {
|
||||
const result = await ElMessageBox.prompt('请输入取消原因', '取消任务链', {
|
||||
confirmButtonText: '确认取消',
|
||||
cancelButtonText: '返回',
|
||||
type: 'warning',
|
||||
inputPlaceholder: '取消原因(可选)',
|
||||
})
|
||||
const reason = (result as { value: string }).value
|
||||
canceling.value = true
|
||||
await cancelChain(chain.value.code, reason || undefined)
|
||||
const reason = cancelForm.reason.trim()
|
||||
await cancelChain(chain.value.code, reason || undefined, cancelForm.moveOut)
|
||||
ElMessage.success('任务链已取消')
|
||||
cancelDialogVisible.value = false
|
||||
await loadData()
|
||||
} catch { /* cancelled */ } finally {
|
||||
canceling.value = false
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"root":["./src/main.ts","./src/api/auth.ts","./src/api/elevator.ts","./src/api/generate.ts","./src/api/stationqueue.ts","./src/api/task.ts","./src/api/vendorload.ts","./src/composables/usedevice.ts","./src/constants/chainstatus.ts","./src/constants/taskstatus.ts","./src/router/index.ts","./src/stores/tabs.ts","./src/stores/user.ts","./src/utils/request.ts","./src/app.vue","./src/layouts/adminlayout.vue","./src/views/dashboard/dashboardview.vue","./src/views/elevator/elevatorlist.vue","./src/views/generate/taskgenerateview.vue","./src/views/login/loginview.vue","./src/views/station/stationqueueview.vue","./src/views/task/taskchaindetail.vue","./src/views/task/taskchainlist.vue","./src/views/vendor/vendorloadview.vue","./env.d.ts"],"version":"5.7.3"}
|
||||
{"root":["./src/main.ts","./src/api/auth.ts","./src/api/elevator.ts","./src/api/generate.ts","./src/api/palletcachestation.ts","./src/api/stationqueue.ts","./src/api/task.ts","./src/api/vendorload.ts","./src/composables/usedevice.ts","./src/constants/chainstatus.ts","./src/constants/taskstatus.ts","./src/router/index.ts","./src/stores/tabs.ts","./src/stores/user.ts","./src/utils/request.ts","./src/app.vue","./src/layouts/adminlayout.vue","./src/views/dashboard/dashboardview.vue","./src/views/elevator/elevatorlist.vue","./src/views/generate/taskgenerateview.vue","./src/views/login/loginview.vue","./src/views/station/palletcachestationview.vue","./src/views/station/stationqueueview.vue","./src/views/task/taskchaindetail.vue","./src/views/task/taskchainlist.vue","./src/views/vendor/vendorloadview.vue","./env.d.ts"],"version":"5.7.3"}
|
||||
Loading…
Reference in New Issue