feat: 工作流新增任务表单抽屉

This commit is contained in:
xlsea 2024-05-25 14:15:59 +08:00
commit 9b79d41ecb
23 changed files with 406 additions and 95 deletions

View File

@ -40,7 +40,7 @@ defineExpose({
</script> </script>
<template> <template>
<NPopover trigger="click" placement="bottom-start"> <NPopover class="cron-popover" trigger="click" placement="bottom-start">
<template #trigger> <template #trigger>
<NInput v-bind="attrs" v-model:value="cron" /> <NInput v-bind="attrs" v-model:value="cron" />
</template> </template>
@ -49,10 +49,7 @@ defineExpose({
</template> </template>
<style> <style>
.n-popover { .cron-popover {
padding: 0 !important; padding: 0 !important;
} }
.n-popover .n-popover-shared .n-popover-arrow-wrapper .n-popover-arrow {
}
</style> </style>

View File

@ -39,6 +39,14 @@ export function fetchAddWorkflow(data: Flow.NodeDataType) {
}); });
} }
export function fetchUpdateWorkflow(data: Flow.NodeDataType) {
return request<null>({
url: `/workflow`,
method: 'put',
data
});
}
export function fetchWorkflowInfo(id: string) { export function fetchWorkflowInfo(id: string) {
return request<Flow.NodeDataType>({ return request<Flow.NodeDataType>({
url: `/workflow/${id}`, url: `/workflow/${id}`,

View File

@ -0,0 +1,60 @@
<script setup lang="ts">
import type { InputInst } from 'naive-ui';
import { nextTick, ref, watch } from 'vue';
defineOptions({
name: 'EditableInput'
});
interface Props {
modelValue?: string;
}
const props = defineProps<Props>();
interface Emits {
(e: 'update:modelValue', modelValue: string): void;
}
const emit = defineEmits<Emits>();
const inputRef = ref<InputInst>();
const value = ref();
const idEdit = ref<boolean>(false);
watch(
() => props.modelValue,
val => {
value.value = val;
},
{ immediate: true }
);
const edit = () => {
idEdit.value = true;
nextTick(() => {
inputRef.value?.focus();
});
};
const save = () => {
emit('update:modelValue', value.value!);
idEdit.value = false;
};
</script>
<template>
<NInput v-if="idEdit" ref="inputRef" v-model:value="value" type="text" @blur="save" />
<NEllipsis v-else>
<span class="flex items-center">
{{ value }}
<NButton text type="info" class="m-l-6px" @click="edit">
<template #icon>
<icon-ant-design:edit-outlined class="text-icon" />
</template>
</NButton>
</span>
</NEllipsis>
</template>
<style scoped lang="scss"></style>

View File

@ -156,7 +156,7 @@ const getClass = (item: Flow.ConditionNodeType) => {
<template v-if="item.callback?.webhook"> <template v-if="item.callback?.webhook">
<div class="flex justify-between"> <div class="flex justify-between">
<span class="content_label">Webhook:</span> <span class="content_label">Webhook:</span>
<NEllipsis class="w-116px">{{ item.callback.webhook }}</NEllipsis> <NEllipsis class="max-w-116px">{{ item.callback.webhook }}</NEllipsis>
</div> </div>
<div> <div>
<span class="content_label">{{ $t('node.callback.conditionNodes.contentType') }}:</span> <span class="content_label">{{ $t('node.callback.conditionNodes.contentType') }}:</span>

View File

@ -88,18 +88,18 @@ const show = () => {
</div> </div>
<div v-if="nodeData.groupName" class="content"> <div v-if="nodeData.groupName" class="content">
<div> <div>
<span class="content_label">{{ $t('snail.groupName') }}:</span> <span class="content_label">{{ $t('snail.groupName') }}:&nbsp;</span>
<NEllipsis class="w-135px"> <NEllipsis class="max-w-132px">
{{ nodeData.groupName }} {{ nodeData.groupName }}
</NEllipsis> </NEllipsis>
</div> </div>
<div> <div>
<span class="content_label">{{ $t('snail.blockStrategy') }}:</span> <span class="content_label">{{ $t('snail.blockStrategy') }}:&nbsp;</span>
{{ blockStrategyRecord[nodeData.blockStrategy!] }} {{ $t(blockStrategyRecord[nodeData.blockStrategy!]) }}
</div> </div>
<div>.........</div> <div>.........</div>
</div> </div>
<div v-else class="content"> <div v-else class="content min-h-85px">
<span class="placeholder">{{ $t('snail.form.workflowTip') }}</span> <span class="placeholder">{{ $t('snail.form.workflowTip') }}</span>
</div> </div>
<NTooltip v-if="store.type === 2" trigger="hover"> <NTooltip v-if="store.type === 2" trigger="hover">

View File

@ -5,6 +5,7 @@ import { fetchNodeRetry, fetchNodeStop } from '../api';
import { useFlowStore } from '../stores'; import { useFlowStore } from '../stores';
import { $t } from '../locales'; import { $t } from '../locales';
import { failStrategyRecord, taskBatchStatusEnum } from '../constants/business'; import { failStrategyRecord, taskBatchStatusEnum } from '../constants/business';
import TaskDrawer from '../drawer/task-drawer.vue';
import AddNode from './add-node.vue'; import AddNode from './add-node.vue';
defineOptions({ defineOptions({
@ -93,15 +94,15 @@ const index = ref<number>(0);
const drawer = ref<boolean>(false); const drawer = ref<boolean>(false);
const form = ref<Flow.ConditionNodeType>({}); const form = ref<Flow.ConditionNodeType>({});
// const save = (val: Flow.ConditionNodeType) => { const save = (val: Flow.ConditionNodeType) => {
// const oldLevel = nodeConfig.value.conditionNodes![index.value].priorityLevel; const oldLevel = nodeConfig.value.conditionNodes![index.value].priorityLevel;
// const newLevel = val.priorityLevel; const newLevel = val.priorityLevel;
// nodeConfig.value.conditionNodes![index.value] = val; nodeConfig.value.conditionNodes![index.value] = val;
// if (oldLevel !== newLevel) { if (oldLevel !== newLevel) {
// arrTransfer(index.value, newLevel! - oldLevel!); arrTransfer(index.value, newLevel! - oldLevel!);
// } }
// emit('update:modelValue', nodeConfig.value); emit('update:modelValue', nodeConfig.value);
// }; };
const show = (currentIndex: number) => { const show = (currentIndex: number) => {
if (!props.disabled) { if (!props.disabled) {
@ -214,16 +215,18 @@ const isStop = (taskBatchStatus: number) => {
<span class="priority-title">{{ $t('node.priority') }}{{ item.priorityLevel }}</span> <span class="priority-title">{{ $t('node.priority') }}{{ item.priorityLevel }}</span>
<icon-ant-design:close-outlined v-if="!disabled" class="close" @click.stop="delTerm(i)" /> <icon-ant-design:close-outlined v-if="!disabled" class="close" @click.stop="delTerm(i)" />
</div> </div>
<div class="content min-h-81px"> <div class="content min-h-72px">
<div v-if="!item.jobTask?.jobId" class="placeholder">{{ $t('snail.form.taskTip') }}</div> <div v-if="!item.jobTask?.jobId" class="placeholder">{{ $t('snail.form.taskTip') }}</div>
<template v-if="item.jobTask?.jobId"> <template v-if="item.jobTask?.jobId">
<div> <div>
<span class="content_label">{{ $t('snail.taskName') }}:</span> <span class="content_label">{{ $t('snail.taskName') }}:&nbsp;</span>
<NEllipsis class="w-126px">{{ `${item.jobTask?.jobName}(${item.jobTask?.jobId})` }}</NEllipsis> <NEllipsis class="max-w-123px">
{{ `${item.jobTask?.jobName}(${item.jobTask?.jobId})` }}
</NEllipsis>
</div> </div>
<div> <div>
<span class="content_label">{{ $t('snail.failStrategy') }} :</span> <span class="content_label">{{ $t('snail.failStrategy') }}:&nbsp;</span>
{{ failStrategyRecord[item.failStrategy!] }} {{ $t(failStrategyRecord[item.failStrategy!]) }}
</div> </div>
<div>.........</div> <div>.........</div>
</template> </template>
@ -266,16 +269,15 @@ const isStop = (taskBatchStatus: number) => {
</div> </div>
</div> </div>
<AddNode v-if="nodeConfig.conditionNodes!.length > 1" v-model="nodeConfig.childNode!" :disabled="disabled" /> <AddNode v-if="nodeConfig.conditionNodes!.length > 1" v-model="nodeConfig.childNode!" :disabled="disabled" />
<!--
<TaskDrawer <TaskDrawer
v-if="store.type === 0 && drawer" v-if="store.type === 0"
v-model:open="drawer" v-model:open="drawer"
v-model="form" v-model="form"
v-model:len="nodeConfig.conditionNodes!.length" v-model:len="nodeConfig.conditionNodes!.length"
@save="save" @save="save"
/> />
<DetailCard v-if="store.type !== 0 && cardDrawer" :id="detailId" v-model:open="cardDrawer" :ids="detailIds" /> <!-- <DetailCard v-if="store.type !== 0 && cardDrawer" :id="detailId" v-model:open="cardDrawer" :ids="detailIds" /> -->
-->
</div> </div>
</template> </template>

View File

@ -44,8 +44,8 @@ export const triggerTypeRecord: Record<Flow.TriggerType, string> = {
export const triggerTypeOptions = transformRecordToOption(triggerTypeRecord); export const triggerTypeOptions = transformRecordToOption(triggerTypeRecord);
export const workFlowNodeStatusRecord: Record<Flow.WorkFlowNodeStatus, string> = { export const workFlowNodeStatusRecord: Record<Flow.WorkFlowNodeStatus, string> = {
0: '关闭', 0: 'snail.enum.workFlowNodeStatus.close',
1: '开启' 1: 'snail.enum.workFlowNodeStatus.open'
}; };
export const workFlowNodeStatusOptions = transformRecordToOption(workFlowNodeStatusRecord); export const workFlowNodeStatusOptions = transformRecordToOption(workFlowNodeStatusRecord);

View File

@ -7,9 +7,10 @@ import { $t } from '../locales';
import { fetchGroupNameList } from '../api'; import { fetchGroupNameList } from '../api';
import { isNotNull } from '../utils/common'; import { isNotNull } from '../utils/common';
import { useFlowStore } from '../stores'; import { useFlowStore } from '../stores';
import EditableInput from '../common/editable-input.vue';
defineOptions({ defineOptions({
name: 'StartDetail' name: 'StartDrawer'
}); });
interface Props { interface Props {
@ -49,6 +50,9 @@ watch(
() => props.modelValue, () => props.modelValue,
val => { val => {
form.value = val; form.value = val;
if (val.triggerType === 2) {
form.value.triggerInterval = Number(val.triggerInterval);
}
if (val.workflowName) { if (val.workflowName) {
title = val.workflowName; title = val.workflowName;
} else if (val.groupName) { } else if (val.groupName) {
@ -92,7 +96,7 @@ const typeChange = (value: number) => {
if (value === 1) { if (value === 1) {
form.value.triggerInterval = '* * * * * ?'; form.value.triggerInterval = '* * * * * ?';
} else if (value === 2) { } else if (value === 2) {
form.value.triggerInterval = '60'; form.value.triggerInterval = 60;
} }
}; };
@ -116,10 +120,10 @@ const rules: Record<RuleKey, FormItemRule> = {
<template> <template>
<NDrawer v-model:show="drawer" display-directive="if" :width="610" @after-leave="close"> <NDrawer v-model:show="drawer" display-directive="if" :width="610" @after-leave="close">
<NDrawerContent :title="title"> <NDrawerContent :title="title">
<template #header>
<EditableInput v-model="form.workflowName" class="max-w-570px min-w-570px" />
</template>
<NForm ref="formRef" :model="form" :rules="rules" label-align="left" label-width="100px"> <NForm ref="formRef" :model="form" :rules="rules" label-align="left" label-width="100px">
<NFormItem path="workflowName" label="工作流名称">
<NInput v-model:value="form.workflowName" placeholder="请输入工作流名称" />
</NFormItem>
<NFormItem path="groupName" label="组名称"> <NFormItem path="groupName" label="组名称">
<NSelect <NSelect
v-model:value="form.groupName" v-model:value="form.groupName"
@ -177,8 +181,8 @@ const rules: Record<RuleKey, FormItemRule> = {
<NRadioGroup v-model:value="form.blockStrategy"> <NRadioGroup v-model:value="form.blockStrategy">
<NSpace> <NSpace>
<NRadio <NRadio
v-for="options in blockStrategyOptions" v-for="(options, index) in blockStrategyOptions"
:key="options.value" :key="index"
:label="$t(options.label)" :label="$t(options.label)"
:value="options.value" :value="options.value"
/> />
@ -191,8 +195,8 @@ const rules: Record<RuleKey, FormItemRule> = {
<NRadioGroup v-model:value="form.workflowStatus"> <NRadioGroup v-model:value="form.workflowStatus">
<NSpace> <NSpace>
<NRadio <NRadio
v-for="options in workFlowNodeStatusOptions" v-for="(options, index) in workFlowNodeStatusOptions"
:key="options.value" :key="index"
:label="$t(options.label)" :label="$t(options.label)"
:value="options.value" :value="options.value"
/> />

View File

@ -0,0 +1,164 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import { type FormInst, useMessage } from 'naive-ui';
import { useFlowStore } from '../stores';
import { $t } from '../locales';
import { failStrategyOptions, workFlowNodeStatusOptions } from '../constants/business';
import EditableInput from '../common/editable-input.vue';
defineOptions({
name: 'TaskDrawer'
});
interface Props {
modelValue?: Flow.ConditionNodeType;
open?: boolean;
len?: number;
}
const props = withDefaults(defineProps<Props>(), {
open: false,
len: 0,
modelValue: () => ({})
});
interface Emits {
(e: 'update:open', open: boolean): void;
(e: 'save', form: Flow.ConditionNodeType): void;
}
const emit = defineEmits<Emits>();
const store = useFlowStore();
const message = useMessage();
const drawer = ref<boolean>(false);
const form = ref<Flow.ConditionNodeType>({});
const jobList = ref<{ id: string; jobName: string }[]>([]);
watch(
() => store.jobList,
val => {
jobList.value = val;
},
{ immediate: true, deep: true }
);
watch(
() => props.open,
val => {
drawer.value = val;
},
{ immediate: true }
);
watch(
() => props.modelValue,
val => {
form.value = val;
},
{ immediate: true, deep: true }
);
const formRef = ref<FormInst>();
const close = () => {
emit('update:open', false);
drawer.value = false;
};
const save = () => {
formRef.value?.validate(errors => {
if (!errors) {
close();
emit('save', form.value);
} else {
message.warning('请检查表单信息');
}
});
};
const rules = {
failStrategy: [{ required: true, message: '请选择失败策略' }],
workflowNodeStatus: [{ required: true, message: '请选择工作流状态' }],
jobTask: {
jobId: [{ required: true, message: '请选择任务' }]
}
};
const jobTaskChange = (_: string, option: { label: string; value: number }) => {
form.value.jobTask!.jobName = option.label;
};
</script>
<template>
<NDrawer v-model:show="drawer" display-directive="if" :width="500" @after-leave="close">
<NDrawerContent>
<template #header>
<div class="w-460px flex-center">
<EditableInput v-model="form.nodeName" class="mr-16px max-w-320px min-w-320px" />
<NSelect
v-model:value="form.priorityLevel"
class="max-w-110px"
:options="
Array(len)
.fill(0)
.map((_, index) => {
return {
label: '优先级 ' + (index + 1),
value: index + 1
};
})
"
/>
</div>
</template>
<NForm ref="formRef" :model="form" :rules="rules" label-align="left" label-width="100px">
<NFormItem path="jobTask.jobId" label="所属任务" placeholder="请选择任务">
<NSelect
v-model:value="form.jobTask!.jobId"
:options="
jobList.map(job => {
return {
label: job.jobName,
value: job.id
};
})
"
@update:value="jobTaskChange"
/>
</NFormItem>
<NFormItem path="failStrategy" label="失败策略">
<NRadioGroup v-model:value="form.failStrategy">
<NSpace>
<NRadio
v-for="(options, index) in failStrategyOptions"
:key="index"
:label="$t(options.label)"
:value="options.value"
/>
</NSpace>
</NRadioGroup>
</NFormItem>
<NFormItem path="workflowNodeStatus" label="节点状态">
<NRadioGroup v-model:value="form.workflowNodeStatus">
<NSpace>
<NRadio
v-for="(options, index) in workFlowNodeStatusOptions"
:key="index"
:label="$t(options.label)"
:value="options.value"
/>
</NSpace>
</NRadioGroup>
</NFormItem>
</NForm>
<template #footer>
<NButton type="primary" @click="save">保存</NButton>
<NButton class="ml-12px" @click="close">取消</NButton>
</template>
</NDrawerContent>
</NDrawer>
</template>

View File

@ -33,6 +33,14 @@ const local: FlowI18n.Schema = {
cancel: 'Cancel', cancel: 'Cancel',
decisionFailed: 'Decision Failed', decisionFailed: 'Decision Failed',
skip: 'Skip' skip: 'Skip'
},
workFlowNodeStatus: {
open: 'Open',
close: 'Close'
},
triggerType: {
time: 'Fixed Time',
cron: 'CRON Expressions'
} }
} }
}, },

View File

@ -33,6 +33,14 @@ const local: FlowI18n.Schema = {
cancel: '取消', cancel: '取消',
decisionFailed: '判定未通过', decisionFailed: '判定未通过',
skip: '跳过' skip: '跳过'
},
workFlowNodeStatus: {
open: '开启',
close: '关闭'
},
triggerType: {
time: '固定时间',
cron: 'CRON 表达式'
} }
} }
}, },

View File

@ -595,7 +595,7 @@
} }
.end-node .end-node-text { .end-node .end-node-text {
color: #ccc; color: #d6d6d6;
} }
.auto-judge .sort-left:hover, .auto-judge .sort-left:hover,
@ -608,4 +608,8 @@
.add-branch { .add-branch {
background-color: #3e5a2d; background-color: #3e5a2d;
} }
.content {
color: #d6d6d6;
}
} }

View File

@ -37,6 +37,14 @@ declare namespace FlowI18n {
decisionFailed: string; decisionFailed: string;
skip: string; skip: string;
}; };
workFlowNodeStatus: {
open: string;
close: string;
};
triggerType: {
time: string;
cron: string;
};
}; };
}; };
node: { node: {

View File

@ -1,7 +1,7 @@
import { BACKEND_ERROR_CODE, createFlatRequest } from '@sa/axios'; import { BACKEND_ERROR_CODE, createFlatRequest } from '@sa/axios';
import { localStg } from './storage'; import { localStg } from './storage';
const baseURL = '/proxy-default'; const baseURL = '/snail-job';
export const request = createFlatRequest<Service.Response>( export const request = createFlatRequest<Service.Response>(
{ {

View File

@ -66,7 +66,7 @@ export function getServiceBaseURL(env: Env.ImportMeta, isProxy: boolean) {
*/ */
function createProxyPattern(key?: App.Service.OtherBaseURLKey) { function createProxyPattern(key?: App.Service.OtherBaseURLKey) {
if (!key) { if (!key) {
return '/proxy-default'; return '/snail-job';
} }
return `/proxy-${key}`; return `/proxy-${key}`;

View File

@ -9,6 +9,8 @@ defineOptions({
name: 'PwdLogin' name: 'PwdLogin'
}); });
const devMode = import.meta.env.DEV;
const authStore = useAuthStore(); const authStore = useAuthStore();
const { formRef, validate } = useNaiveForm(); const { formRef, validate } = useNaiveForm();
const { defaultRequiredRule } = useFormRules(); const { defaultRequiredRule } = useFormRules();
@ -19,8 +21,8 @@ interface FormModel {
} }
const model: FormModel = reactive({ const model: FormModel = reactive({
userName: 'admin', userName: devMode ? 'admin' : '',
password: 'admin' password: devMode ? 'admin' : ''
}); });
type RuleKey = Extract<keyof FormModel, 'userName' | 'password'>; type RuleKey = Extract<keyof FormModel, 'userName' | 'password'>;

View File

@ -52,7 +52,7 @@ const model: Model = reactive(createDefaultModel());
function createDefaultModel(): Model { function createDefaultModel(): Model {
return { return {
groupName: '', groupName: '',
token: generateToken(32), token: import.meta.env.VITE_APP_DEFAULT_TOKEN || '',
groupStatus: 1, groupStatus: 1,
description: '', description: '',
idGeneratorMode: 2, idGeneratorMode: 2,

View File

@ -351,11 +351,7 @@ watch(visible, () => {
<NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20"> <NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20">
<NGi> <NGi>
<NFormItem :label="$t('page.jobTask.triggerType')" path="triggerType"> <NFormItem :label="$t('page.jobTask.triggerType')" path="triggerType">
<TriggerType <TriggerType v-model:value="model.triggerType" :placeholder="$t('page.jobTask.form.triggerType')" />
v-model:value="model.triggerType"
:placeholder="$t('page.jobTask.form.triggerType')"
@update:value="model.triggerInterval = ''"
/>
</NFormItem> </NFormItem>
</NGi> </NGi>
<NGi> <NGi>

View File

@ -28,13 +28,8 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
taskBatchStatus: null taskBatchStatus: null
}, },
columns: () => [ columns: () => [
// {
// type: 'selection',
// align: 'center',
// width: 48
// },
{ {
key: 'index', key: 'id',
title: $t('common.index'), title: $t('common.index'),
align: 'center', align: 'center',
width: 120 width: 120

View File

@ -2,6 +2,7 @@
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import Workflow, { flowFetch, flowStores } from '@sa/workflow'; import Workflow, { flowFetch, flowStores } from '@sa/workflow';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { $t } from '@/locales';
const store = flowStores.useFlowStore(); const store = flowStores.useFlowStore();
const router = useRouter(); const router = useRouter();
@ -16,6 +17,7 @@ onMounted(() => {
}); });
const node = ref<Flow.NodeDataType>({ const node = ref<Flow.NodeDataType>({
workflowName: `Workflow ${new Date().getTime()}`,
workflowStatus: 1, workflowStatus: 1,
blockStrategy: 1, blockStrategy: 1,
description: undefined, description: undefined,
@ -25,7 +27,7 @@ const node = ref<Flow.NodeDataType>({
const save = async () => { const save = async () => {
const { error } = await flowFetch.fetchAddWorkflow(node.value); const { error } = await flowFetch.fetchAddWorkflow(node.value);
if (!error) { if (!error) {
window.$message?.success('工作流新增成功'); window.$message?.info($t('common.addSuccess'));
router.push('/workflow/task'); router.push('/workflow/task');
} }
}; };

View File

@ -1,17 +1,52 @@
<script setup lang="ts"> <script setup lang="ts">
import WorkFlowIframe from '../modules/workflow-iframe.vue'; import { onMounted, ref } from 'vue';
import Workflow, { flowFetch, flowStores } from '@sa/workflow';
import { useRoute, useRouter } from 'vue-router';
import { $t } from '@/locales';
defineOptions({ const store = flowStores.useFlowStore();
name: 'WorkFlowEdit' const route = useRoute();
const router = useRouter();
const spinning = ref(false);
const disabled = ref(false);
const id: string = String(route.query.id);
const node = ref<Flow.NodeDataType>({});
const getDetail = async () => {
spinning.value = true;
const { data, error } = await flowFetch.fetchWorkflowInfo(id);
if (!error) {
node.value = data;
}
spinning.value = false;
};
onMounted(() => {
store.clear();
store.setType(0);
store.setId(id);
getDetail();
disabled.value = false;
}); });
const update = async () => {
const { error } = await flowFetch.fetchUpdateWorkflow(node.value);
if (!error) {
window.$message?.info($t('common.updateSuccess'));
router.push({ path: '/workflow/task' });
}
};
const cancel = () => {
router.push('/workflow/task');
};
</script> </script>
<template> <template>
<div class="iframe"><WorkFlowIframe value="D7Rzd7Oe" /></div> <Workflow v-model="node" :spinning="spinning" :disabled="disabled" @save="update" @cancel="cancel" />
</template> </template>
<style scoped> <style scoped></style>
.iframe {
padding: 0 !important;
}
</style>

View File

@ -81,7 +81,7 @@ onActivated(() => {
<iframe <iframe
ref="iframeRef" ref="iframeRef"
class="size-full" class="size-full"
:src="`${mode === 'prod' ? baseUrl : ''}/lib/index.html?id=${id}&mode=${mode}&x1c2Hdd6=${value}`" :src="`${mode === 'prod' ? baseUrl + '/' : '/'}lib/index.html?id=${id}&mode=${mode}&x1c2Hdd6=${value}`"
/> />
</NSpin> </NSpin>
</template> </template>

View File

@ -1,5 +1,5 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { NButton, NButtonGroup, NPopconfirm, NPopover, NTag } from 'naive-ui'; import { NButton, NDropdown, NPopconfirm, NTag } from 'naive-ui';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { import {
fetchDelWorkflow, fetchDelWorkflow,
@ -38,7 +38,7 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
// width: 48 // width: 48
// }, // },
{ {
key: 'index', key: 'id',
title: $t('common.index'), title: $t('common.index'),
align: 'center', align: 'center',
width: 120 width: 120
@ -129,17 +129,50 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
fixed: 'right', fixed: 'right',
width: 200, width: 200,
render: row => { render: row => {
const options = [
{
label: $t('common.execute'),
key: 'execute',
click: () => execute(row.id!)
},
{
type: 'divider',
key: 'd1'
},
{
label: $t('common.copy'),
key: 'copy',
click: () => copy(row.id!)
},
{
type: 'divider',
key: 'd2'
},
{
label: $t('common.batchList'),
key: 'batchList',
click: () => batch(row.id!)
}
];
function onSelect(key: string) {
options.filter(o => o.key === key)[0].click!();
}
return ( return (
<div class="flex-center gap-8px"> <div class="flex-center gap-8px">
<NButton type="warning" ghost size="small" onClick={() => edit(row.id!)}> <NButton text type="warning" ghost size="small" onClick={() => edit(row.id!)}>
{$t('common.edit')} {$t('common.edit')}
</NButton> </NButton>
<n-divider vertical />
{hasAuth('R_ADMIN') ? ( {hasAuth('R_ADMIN') ? (
<NPopconfirm onPositiveClick={() => handleDelete(row.id!)}> <NPopconfirm onPositiveClick={() => handleDelete(row.id!)}>
{{ {{
default: () => $t('common.confirmDelete'), default: () => $t('common.confirmDelete'),
trigger: () => ( trigger: () => (
<NButton type="error" ghost size="small"> <NButton text type="error" ghost size="small">
{$t('common.delete')} {$t('common.delete')}
</NButton> </NButton>
) )
@ -149,30 +182,17 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
'' ''
)} )}
<NPopover trigger="click" placement="bottom" raw show-arrow={false} class="b-rd-6px bg-#fff dark:bg-#000"> <n-divider vertical />
<NDropdown trigger="click" show-arrow={false} options={options} size="small" on-select={onSelect}>
{{ {{
trigger: () => ( default: () => (
<NButton type="primary" ghost size="small"> <NButton text type="primary" ghost size="small">
更多 更多
</NButton> </NButton>
),
default: () => (
<div>
<NButtonGroup vertical>
<NButton type="primary" ghost size="small" onClick={() => execute(row.id!)}>
{$t('common.execute')}
</NButton>
<NButton type="primary" ghost size="small" onClick={() => copy(row.id!)}>
{$t('common.copy')}
</NButton>
<NButton type="success" ghost size="small" onClick={() => batch(row.id!)}>
{$t('common.batchList')}
</NButton>
</NButtonGroup>
</div>
) )
}} }}
</NPopover> </NDropdown>
</div> </div>
); );
} }
@ -188,8 +208,6 @@ const {
async function handleBatchDelete() { async function handleBatchDelete() {
// request // request
console.log(checkedRowKeys.value);
onBatchDeleted(); onBatchDeleted();
} }
@ -207,7 +225,7 @@ function edit(id: string) {
} }
function handleAdd() { function handleAdd() {
router.push({ path: '/workflow/form/edit' }); router.push({ path: '/workflow/form/add' });
} }
function detail(id: string) { function detail(id: string) {