Browse Source

feat: 任务页签表格与分段切换到 TDesign

main
xiaosi 3 weeks ago
parent
commit
682edb2b77
  1. 203
      src/views/TasksView/TasksView.vue

203
src/views/TasksView/TasksView.vue

@ -16,11 +16,11 @@
</div> </div>
<div class="business-workspace"> <div class="business-workspace">
<div class="business-tabs">
<button :class="{ active: view === 'plans' }" @click="setView('plans')">任务计划<b>{{ plans.length }}</b></button>
<button :class="{ active: view === 'records' }" @click="setView('records')">执行记录<b>{{ records.length }}</b></button>
<button :class="{ active: view === 'routes' }" @click="setView('routes')">航线库<b>{{ routes.length }}</b></button>
</div>
<t-tabs v-model="view" class="business-tdesign-tabs" @change="setView">
<t-tab-panel value="plans" :label="plansTabLabel" :destroy-on-hide="false" />
<t-tab-panel value="records" :label="recordsTabLabel" :destroy-on-hide="false" />
<t-tab-panel value="routes" :label="routesTabLabel" :destroy-on-hide="false" />
</t-tabs>
<!-- 任务计划 --> <!-- 任务计划 -->
<div class="business-panel" :class="{ active: view === 'plans' }"> <div class="business-panel" :class="{ active: view === 'plans' }">
@ -36,33 +36,34 @@
</div> </div>
</div> </div>
<div class="business-table-wrap"> <div class="business-table-wrap">
<table class="business-table task-plan-table">
<thead>
<tr>
<th>任务名称</th>
<th>执行设备</th>
<th>执行方式</th>
<th>下次执行</th>
<th>视频策略</th>
<th>任务状态</th>
<th>最近结果</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="t in filteredPlans" :key="t.id">
<td><div class="primary-table-cell"><span class="task-table-icon" :class="{ muted: t.statusClass === 'offline' }"><svg><use href="#i-route" /></svg></span><span><strong>{{ t.name }}</strong><small>{{ t.code }} · {{ t.route }}</small></span></div></td>
<td>{{ t.device }}</td>
<td>{{ t.cycle }}</td>
<td :class="{ 'blue-text': t.statusClass === 'mission' }">{{ t.next }}</td>
<td><span class="media-policy" :class="t.videoPolicyClass">{{ t.videoPolicy }}</span></td>
<td><span class="table-status" :class="t.statusClass"><i></i>{{ t.status }}</span></td>
<td>{{ t.lastResult }}</td>
<td><div class="row-actions"><t-button v-if="canExecuteTask(t)" variant="text" theme="primary" @click="executeTask(t)">立即执行</t-button><t-button variant="text" theme="danger" @click="deleteTask(t)">删除</t-button></div></td>
</tr>
</tbody>
</table>
<div v-if="!filteredPlans.length" class="table-empty">暂无任务计划</div>
<t-table
row-key="id"
:data="filteredPlans"
:columns="planColumns"
empty="暂无任务计划"
>
<template #name="{ row }">
<div class="primary-table-cell">
<span class="task-table-icon" :class="{ muted: row.statusClass === 'offline' }"><svg><use href="#i-route" /></svg></span>
<span><strong>{{ row.name }}</strong><small>{{ row.code }} · {{ row.route }}</small></span>
</div>
</template>
<template #next="{ row }">
<span :class="{ 'blue-text': row.statusClass === 'mission' }">{{ row.next }}</span>
</template>
<template #videoPolicy="{ row }">
<span class="media-policy" :class="row.videoPolicyClass">{{ row.videoPolicy }}</span>
</template>
<template #status="{ row }">
<span class="table-status" :class="row.statusClass"><i></i>{{ row.status }}</span>
</template>
<template #actions="{ row }">
<div class="row-actions">
<t-button v-if="canExecuteTask(row)" variant="text" theme="primary" @click="executeTask(row)">立即执行</t-button>
<t-button variant="text" theme="danger" @click="deleteTask(row)">删除</t-button>
</div>
</template>
</t-table>
</div> </div>
</div> </div>
@ -78,38 +79,34 @@
</div> </div>
</div> </div>
</div> </div>
<div class="record-header"><div><h2>执行记录</h2><span>最近 30 天执行记录</span></div><t-select v-model="execResultFilter" :options="execResultFilterOptions" placeholder="全部执行结果" style="width: 160px" /></div>
<div class="business-table-wrap">
<table class="business-table execution-record-table">
<thead>
<tr>
<th>执行编号</th>
<th>任务名称</th>
<th>执行设备</th>
<th>开始时间</th>
<th>飞行时长</th>
<th>采集成果</th>
<th>执行结果</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="r in filteredRecords" :key="r.id">
<td>{{ r.id }}</td>
<td><div class="primary-table-cell"><span class="task-table-icon"><svg><use href="#i-route" /></svg></span><span><strong>{{ r.name }}</strong><small>{{ r.type }}</small></span></div></td>
<td>{{ r.device }}</td>
<td>{{ r.start }}</td>
<td>{{ r.duration }}</td>
<td>{{ r.collect }}</td>
<td><span class="table-status" :class="r.statusClass"><i></i>{{ r.result }}</span></td>
<td><div class="row-actions"><t-button variant="text" theme="primary" :disabled="r.statusClass === 'mission'" @click="goReplay(r)">{{ r.statusClass === 'mission' ? '执行中' : '轨迹回放' }}</t-button><t-button v-if="r.rawVideoId" variant="text" theme="primary" :disabled="r.rawVideoStatus !== 'ready'" @click="downloadRawVideo(r)">{{ r.rawVideoStatus === 'ready' ? '下载原视频' : '视频处理中' }}</t-button></div></td>
</tr>
</tbody>
</table>
<div v-if="!filteredRecords.length" class="table-empty">暂无执行记录</div>
<div class="record-header"><div><h2>执行记录</h2><span>最近 30 天执行记录</span></div><t-select v-model="execResultFilter" :options="execResultFilterOptions" placeholder="全部执行结果" style="width: 160px" /></div>
<div class="business-table-wrap">
<t-table
row-key="id"
:data="filteredRecords"
:columns="recordColumns"
empty="暂无执行记录"
>
<template #name="{ row }">
<div class="primary-table-cell">
<span class="task-table-icon"><svg><use href="#i-route" /></svg></span>
<span><strong>{{ row.name }}</strong><small>{{ row.type }}</small></span>
</div>
</template>
<template #result="{ row }">
<span class="table-status" :class="row.statusClass"><i></i>{{ row.result }}</span>
</template>
<template #actions="{ row }">
<div class="row-actions">
<t-button variant="text" theme="primary" :disabled="row.statusClass === 'mission'" @click="goReplay(row)">{{ row.statusClass === 'mission' ? '执行中' : '轨迹回放' }}</t-button>
<t-button v-if="row.rawVideoId" variant="text" theme="primary" :disabled="row.rawVideoStatus !== 'ready'" @click="downloadRawVideo(row)">{{ row.rawVideoStatus === 'ready' ? '下载原视频' : '视频处理中' }}</t-button>
</div>
</template>
</t-table>
</div> </div>
</div> </div>
<!-- 航线库 --> <!-- 航线库 -->
<div class="business-panel route-library" :class="{ active: view === 'routes' }"> <div class="business-panel route-library" :class="{ active: view === 'routes' }">
<div class="route-library-view" :hidden="routeEditorOpen"> <div class="route-library-view" :hidden="routeEditorOpen">
@ -150,7 +147,7 @@
<label><span>高度</span><div class="number-input"><t-input-number v-model="routeForm.altitude" theme="normal" :min="20" :max="120" style="width: 100%" /><em>m</em></div></label> <label><span>高度</span><div class="number-input"><t-input-number v-model="routeForm.altitude" theme="normal" :min="20" :max="120" style="width: 100%" /><em>m</em></div></label>
<label><span>速度</span><div class="number-input"><t-input-number v-model="routeForm.speed" theme="normal" :min="1" :max="20" :step="0.1" style="width: 100%" /><em>m/s</em></div></label> <label><span>速度</span><div class="number-input"><t-input-number v-model="routeForm.speed" theme="normal" :min="1" :max="20" :step="0.1" style="width: 100%" /><em>m/s</em></div></label>
</div> </div>
<label><span>转弯方式</span><div class="mode-segment route-turn-segment"><button type="button" :class="{ active: routeForm.turnMode === 'auto' }" @click="routeForm.turnMode = 'auto'">自动转弯</button><button type="button" :class="{ active: routeForm.turnMode === 'stop' }" @click="routeForm.turnMode = 'stop'">定点转弯</button></div></label>
<label><span>转弯方式</span><t-radio-group v-model="routeForm.turnMode" class="task-mode-radio" theme="button" variant="default-filled" :options="turnModeOptions" /></label>
<t-button class="apply-all-waypoints" variant="outline" type="button" :disabled="!routePoints.length" @click="applyRouteDefaults"><svg><use href="#i-settings" /></svg>应用到全部航点</t-button> <t-button class="apply-all-waypoints" variant="outline" type="button" :disabled="!routePoints.length" @click="applyRouteDefaults"><svg><use href="#i-settings" /></svg>应用到全部航点</t-button>
<div class="waypoint-heading"><strong>航点列表</strong><span>{{ routePoints.length }}</span></div> <div class="waypoint-heading"><strong>航点列表</strong><span>{{ routePoints.length }}</span></div>
<div class="waypoint-list"> <div class="waypoint-list">
@ -187,12 +184,14 @@
</div> </div>
<div class="task-schedule-settings"> <div class="task-schedule-settings">
<div class="task-video-title"><strong>执行方式</strong><span>按计划自动执行</span></div> <div class="task-video-title"><strong>执行方式</strong><span>按计划自动执行</span></div>
<div class="mode-segment">
<button type="button" :class="{ active: taskForm.scheduleType === 'once' }" @click="setScheduleType('once')">手动执行</button>
<button type="button" :class="{ active: taskForm.scheduleType === 'cron' && taskForm.schedulePeriod === 'day' }" @click="setScheduleType('day')">每天</button>
<button type="button" :class="{ active: taskForm.scheduleType === 'cron' && taskForm.schedulePeriod === 'week' }" @click="setScheduleType('week')">每周</button>
<button type="button" :class="{ active: taskForm.scheduleType === 'cron' && taskForm.schedulePeriod === 'custom' }" @click="setScheduleType('custom')">自定义 Cron</button>
</div>
<t-radio-group
class="task-mode-radio task-schedule-radio"
:value="scheduleMode"
theme="button"
variant="default-filled"
:options="scheduleModeOptions"
@change="setScheduleType"
/>
<div v-if="taskForm.scheduleType === 'cron' && taskForm.schedulePeriod !== 'custom'" class="form-row"> <div v-if="taskForm.scheduleType === 'cron' && taskForm.schedulePeriod !== 'custom'" class="form-row">
<label v-if="taskForm.schedulePeriod === 'week'"><span>星期</span><t-select v-model="taskForm.scheduleWeekday" :options="weekdayOptions" style="width: 100%" /></label> <label v-if="taskForm.schedulePeriod === 'week'"><span>星期</span><t-select v-model="taskForm.scheduleWeekday" :options="weekdayOptions" style="width: 100%" /></label>
<label><span>执行时间</span><t-time-picker v-model="taskForm.scheduleTime" format="HH:mm" allow-input style="width: 100%" /></label> <label><span>执行时间</span><t-time-picker v-model="taskForm.scheduleTime" format="HH:mm" allow-input style="width: 100%" /></label>
@ -222,7 +221,7 @@
<div class="waypoint-id-row"><span>航点 ID</span><strong>{{ editingWaypointIndex + 1 }}</strong></div> <div class="waypoint-id-row"><span>航点 ID</span><strong>{{ editingWaypointIndex + 1 }}</strong></div>
<div class="form-row"><label><span>经度 <b>*</b></span><t-input-number v-model="waypointForm.longitude" theme="normal" :min="-180" :max="180" :step="0.000001" style="width: 100%" /></label><label><span>纬度 <b>*</b></span><t-input-number v-model="waypointForm.latitude" theme="normal" :min="-90" :max="90" :step="0.000001" style="width: 100%" /></label></div> <div class="form-row"><label><span>经度 <b>*</b></span><t-input-number v-model="waypointForm.longitude" theme="normal" :min="-180" :max="180" :step="0.000001" style="width: 100%" /></label><label><span>纬度 <b>*</b></span><t-input-number v-model="waypointForm.latitude" theme="normal" :min="-90" :max="90" :step="0.000001" style="width: 100%" /></label></div>
<div class="form-row"><label><span>高度 <b>*</b></span><div class="number-input"><t-input-number v-model="waypointForm.altitude" theme="normal" :min="20" :max="120" style="width: 100%" /><em>m</em></div></label><label><span>速度 <b>*</b></span><div class="number-input"><t-input-number v-model="waypointForm.speed" theme="normal" :min="1" :max="20" :step="0.1" style="width: 100%" /><em>m/s</em></div></label></div> <div class="form-row"><label><span>高度 <b>*</b></span><div class="number-input"><t-input-number v-model="waypointForm.altitude" theme="normal" :min="20" :max="120" style="width: 100%" /><em>m</em></div></label><label><span>速度 <b>*</b></span><div class="number-input"><t-input-number v-model="waypointForm.speed" theme="normal" :min="1" :max="20" :step="0.1" style="width: 100%" /><em>m/s</em></div></label></div>
<label><span>转弯方式</span><div class="mode-segment waypoint-turn-segment"><button type="button" :class="{ active: waypointForm.turnMode === 'auto' }" @click="waypointForm.turnMode = 'auto'">自动转弯</button><button type="button" :class="{ active: waypointForm.turnMode === 'stop' }" @click="waypointForm.turnMode = 'stop'">定点转弯</button></div></label>
<label><span>转弯方式</span><t-radio-group v-model="waypointForm.turnMode" class="task-mode-radio" theme="button" variant="default-filled" :options="turnModeOptions" /></label>
<div class="route-form-error">{{ waypointFormError }}</div> <div class="route-form-error">{{ waypointFormError }}</div>
</form> </form>
<template #footer> <template #footer>
@ -337,6 +336,50 @@ const weekdayOptions = [
{ value: 0, label: '星期日' } { value: 0, label: '星期日' }
] ]
const turnModeOptions = [
{ label: '自动转弯', value: 'auto' },
{ label: '定点转弯', value: 'stop' }
]
const scheduleModeOptions = [
{ label: '手动执行', value: 'once' },
{ label: '每天', value: 'day' },
{ label: '每周', value: 'week' },
{ label: '自定义 Cron', value: 'custom' }
]
const scheduleMode = computed(() => {
if (taskForm.scheduleType === 'once') return 'once'
return taskForm.schedulePeriod || 'day'
})
const plansTabLabel = computed(() => `任务计划 ${plans.value.length}`)
const recordsTabLabel = computed(() => `执行记录 ${records.value.length}`)
const routesTabLabel = computed(() => `航线库 ${routes.value.length}`)
const planColumns = [
{ colKey: 'name', title: '任务名称' },
{ colKey: 'device', title: '执行设备' },
{ colKey: 'cycle', title: '执行方式' },
{ colKey: 'next', title: '下次执行' },
{ colKey: 'videoPolicy', title: '视频策略' },
{ colKey: 'status', title: '任务状态' },
{ colKey: 'lastResult', title: '最近结果' },
{ colKey: 'actions', title: '操作', width: '16%' }
]
const recordColumns = [
{ colKey: 'id', title: '执行编号' },
{ colKey: 'name', title: '任务名称' },
{ colKey: 'device', title: '执行设备' },
{ colKey: 'start', title: '开始时间' },
{ colKey: 'duration', title: '飞行时长' },
{ colKey: 'collect', title: '采集成果' },
{ colKey: 'result', title: '执行结果' },
{ colKey: 'actions', title: '操作', width: '18%' }
]
watch( watch(
() => route.query.view, () => route.query.view,
(v) => { if (v) view.value = v } (v) => { if (v) view.value = v }
@ -985,6 +1028,28 @@ async function downloadRawVideo(record) {
.toolbar-group :deep(.t-select) { .toolbar-group :deep(.t-select) {
flex-shrink: 0; flex-shrink: 0;
} }
.business-tdesign-tabs :deep(.t-tabs__nav-container) {
padding: 0 18px;
}
.business-tdesign-tabs :deep(.t-tabs__nav-wrap) {
margin-left: 0;
}
.business-table-wrap :deep(.t-table) {
background: transparent;
}
.task-mode-radio {
width: 100%;
display: flex;
}
.task-mode-radio :deep(.t-radio-button) {
flex: 1;
justify-content: center;
}
.task-schedule-radio :deep(.t-radio-button) {
min-width: 0;
padding: 0 4px;
white-space: nowrap;
}
.route-config-panel :deep(.t-input), .route-config-panel :deep(.t-input),
.route-config-panel :deep(.t-input-number) { .route-config-panel :deep(.t-input-number) {
width: 100%; width: 100%;

Loading…
Cancel
Save