feat(Workflow): 添加工作流功能,包含工作流列表和相关接口定义

This commit is contained in:
jxxghp
2025-02-22 12:11:38 +08:00
parent 32621ee299
commit c056ec9377
5 changed files with 217 additions and 0 deletions

View File

@@ -1261,3 +1261,45 @@ export interface SiteCategory {
cat: string
desc: string
}
// 动作
export interface Action {
// 动作ID (类名)
id?: string;
// 动作名称
name?: string;
// 动作描述
description?: string;
// 是否需要循环
loop?: boolean;
// 循环间隔 (秒)
loop_interval?: number;
// 参数
params?: { [key: string]: any };
}
// 工作流
export interface Workflow {
// 工作流ID
id?: string;
// 工作流名称
name?: string;
// 工作流描述
description?: string;
// 定时器
timer?: string;
// 状态
state?: string;
// 当前执行动作
current_action?: string;
// 任务执行结果
result?: string;
// 已执行次数
run_count?: number;
// 动作列表
actions?: Action[];
// 创建时间
add_time?: string;
// 最后执行时间
last_time?: string;
}

7
src/pages/workflow.vue Normal file
View File

@@ -0,0 +1,7 @@
<script setup lang="ts">
import WorkflowListView from '@/views/workflow/WorkflowListView.vue'
</script>
<template>
<WorkflowListView />
</template>

View File

@@ -68,6 +68,14 @@ const router = createRouter({
subType: '电视剧',
},
},
{
path: '/workflow',
component: () => import('../pages/workflow.vue'),
meta: {
keepAlive: true,
requiresAuth: true,
},
},
{
path: '/calendar',
component: () => import('../pages/calendar.vue'),

View File

@@ -49,6 +49,16 @@ export const SystemNavMenus = [
admin: false,
footer: true,
},
{
title: '工作流',
full_title: '自定义工作流',
icon: 'mdi-state-machine',
to: '/workflow',
header: '订阅',
admin: false,
footer: false,
},
{
title: '日历',
full_title: '订阅日历',

View File

@@ -0,0 +1,150 @@
<script setup lang="ts">
import api from '@/api'
import { Workflow } from '@/api/types'
import { useDisplay } from 'vuetify'
// APP
const display = useDisplay()
const appMode = inject('pwaMode') && display.mdAndDown.value
// 是否刷新
const isRefreshed = ref(false)
// 所有工作流
const workflowList = ref<Workflow[]>([])
const options = ref({ page: 1, itemsPerPage: 25, sortBy: [''], sortDesc: [false] })
// headers
const headers = [
{ title: '名称', key: 'name' },
{ title: '定时', key: 'timer' },
{ title: '当前任务', key: 'current_action' },
{ title: '状态', key: 'state' },
{ title: '进度', key: 'progress' },
{ title: '创建时间', key: 'add_time' },
]
// 加载数据
async function fetchData() {
try {
workflowList.value = await api.get('workflow/')
isRefreshed.value = true
} catch (error) {
console.error(error)
}
}
// 计算状态颜色
const resolveStatusVariant = (status: string | undefined) => {
if (status === 'S') return { color: 'success', text: '成功' }
else if (status === 'R') return { color: 'primary', text: '运行中' }
else if (status === 'F') return { color: 'error', text: '失败' }
else if (status === 'P') return { color: 'warning', text: '暂停' }
else return { color: '', text: '等待' }
}
// 计算当前动作占比
const resolveProgress = (item: Workflow) => {
const current_action_index = item.actions?.findIndex(action => action.id === item.current_action) ?? 0
return item.actions?.length ? Math.round((current_action_index / item.actions.length) * 100) : 0
}
onMounted(() => {
fetchData()
})
</script>
<template>
<VCard>
<VCardItem>
<div class="flex">
<VCardTitle> 工作流 </VCardTitle>
<VSpacer />
<VCombobox
max-width="300"
key="search_navbar"
class="text-disabled"
density="compact"
label="搜索"
prepend-inner-icon="mdi-magnify"
variant="solo-filled"
single-line
hide-details
flat
rounded
clearable
/>
</div>
</VCardItem>
<VDataTable
:headers="headers"
:items="workflowList"
:items-per-page="options.itemsPerPage"
:page="options.page"
:options="options"
loading-text="加载中..."
class="text-no-wrap"
hover
>
<!-- name -->
<template #item.name="{ item }">
<div class="d-flex align-center">
<VAvatar size="32" color="primary" class="v-avatar-light-bg primary--text" variant="tonal">
<span class="text-sm">{{ item.actions?.length }}</span>
</VAvatar>
<div class="d-flex flex-column ms-3">
<span class="d-block font-weight-medium text-high-emphasis text-truncate">{{ item.name }}</span>
<small>{{ item.description }}</small>
</div>
</div>
</template>
<!-- state -->
<template #item.state="{ item }">
<VChip :color="resolveStatusVariant(item.state).color" class="font-weight-medium" size="small">
{{ resolveStatusVariant(item.state).text }}
</VChip>
</template>
<!-- progress -->
<template #item.progress="{ item }">
<div class="d-flex align-center gap-x-4">
<div class="w-100">
<VProgressLinear rounded :value="resolveProgress" color="primary" height="8" />
</div>
<div>{{ resolveProgress }}%</div>
</div>
</template>
<template #bottom>
<VCardText class="pt-2">
<div class="d-flex flex-wrap justify-space-between gap-y-2 mt-2">
<VSelect
v-model="options.itemsPerPage"
:items="[10, 25, 50, 100]"
label="每页记录数:"
variant="underlined"
style="max-inline-size: 8rem; min-inline-size: 5rem"
/>
<VPagination
v-model="options.page"
:total-visible="$vuetify.display.smAndDown ? 2 : 3"
:length="Math.ceil(workflowList.length / options.itemsPerPage)"
/>
</div>
</VCardText>
</template>
<template #no-data> 没有数据 </template>
</VDataTable>
</VCard>
<!-- 新增按钮 -->
<VFab
v-if="isRefreshed"
icon="mdi-plus"
location="bottom"
size="x-large"
fixed
app
appear
:class="{ 'mb-12': appMode }"
/>
</template>