Merge branch 'refs/heads/dev_1.0.0_beta2' into dev

This commit is contained in:
xlsea 2024-05-20 14:47:47 +08:00
commit 60d0c218d4
17 changed files with 432 additions and 201 deletions

View File

@ -112,5 +112,6 @@
"lint-staged": { "lint-staged": {
"*": "eslint --fix" "*": "eslint --fix"
}, },
"website": "https://www.easyretry.com/pages/78ba75/" "officialWebsite": "https://snailjob.opensnail.com",
"website": "https://snailjob.opensnail.com/pages/78ba75/"
} }

View File

@ -0,0 +1,79 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import CronInput from '@sa/cron-input';
import { useAppStore } from '@/store/modules/app';
defineOptions({
name: 'JobTriggerInterval'
});
interface Props {
triggerType: Api.Common.TriggerType;
}
const model = defineModel<string>();
const props = defineProps<Props>();
const app = useAppStore();
/** 保存 `固定时间` 类型的 时间间隔 */
const interval = ref<number>(props.triggerType === 2 ? Number(model.value) : 60);
/** 保存 `CRON表达式` 类型的 表达式 */
const cron = ref<string>(props.triggerType === 3 ? model.value! : '* * * * * ?');
/** 监视 触发间隔 变化 */
watch(
interval,
val => {
if (props.triggerType === 2) {
model.value = `${val}`;
}
},
{ immediate: true }
);
/** 监视 cron 表达式变化 */
watch(
cron,
val => {
if (props.triggerType === 3) {
model.value = val;
}
},
{ immediate: true }
);
/** 根据不同 triggerType, 为model赋值 */
watch(
() => props.triggerType,
triggerType => {
if (triggerType === 2) {
model.value = `${interval.value}`;
} else if (triggerType === 3) {
model.value = cron.value;
} else {
model.value = '*';
}
},
{ immediate: true }
);
</script>
<template>
<div>
<NInputGroup v-if="triggerType === 2">
<NInputNumber v-model:value="interval" :placeholder="$t('page.jobTask.form.triggerInterval')" />
<NInputGroupLabel></NInputGroupLabel>
</NInputGroup>
<CronInput
v-else-if="triggerType === 3"
v-model="cron"
:placeholder="$t('page.jobTask.form.triggerInterval_CRON')"
:lang="app.locale"
/>
<NInput v-else-if="triggerType === 99" disabled />
</div>
</template>
<style scoped></style>

View File

@ -95,7 +95,7 @@ function timestampToDate(timestamp: string): string {
<pre <pre
v-for="(message, index) in modelValue" v-for="(message, index) in modelValue"
:key="index" :key="index"
><NDivider v-if="index !== 0" /><span class="log-hljs-time">{{timestampToDate(message.time_stamp)}}</span><span :class="`log-hljs-level-${message.level}`">{{`\t${message.level}\t`}}</span><span class="log-hljs-thread">{{`[${message.thread}]\t`}}</span><span class="log-hljs-location">{{`${message.location}: \n`}}</span> -<span class="pl-6px">{{`${message.message}\n`}}</span><ThrowableComponent :throwable="message.throwable" /></pre> ><NDivider v-if="index !== 0" /><span class="log-hljs-time inline-block">{{timestampToDate(message.time_stamp)}}</span><span :class="`log-hljs-level-${message.level}`" class="ml-12px mr-12px inline-block">{{`${message.level}`}}</span><span class="log-hljs-thread mr-12px inline-block">{{ `[${message.host}:${message.port}]` }}</span><span class="log-hljs-thread mr-12px inline-block">{{`[${message.thread}]`}}</span><span class="log-hljs-location">{{`${message.location}: \n`}}</span> -<span class="pl-6px">{{`${message.message}`}}</span><ThrowableComponent :throwable="message.throwable" /></pre>
</code> </code>
</div> </div>
</div> </div>
@ -107,11 +107,10 @@ function timestampToDate(timestamp: string): string {
<pre <pre
v-for="(message, index) in modelValue" v-for="(message, index) in modelValue"
:key="index" :key="index"
><NDivider v-if="index !== 0" /><span class="log-hljs-time">{{timestampToDate(message.time_stamp)}}</span><span :class="`log-hljs-level-${message.level}`">{{`\t${message.level}\t`}}</span><span class="log-hljs-thread">{{`[${message.thread}]\t`}}</span><span class="log-hljs-location">{{`${message.location}: \n`}}</span> -<span class="pl-6px">{{`${message.message}\n`}}</span><ThrowableComponent :throwable="message.throwable" /></pre> ><NDivider v-if="index !== 0" /><span class="log-hljs-time">{{timestampToDate(message.time_stamp)}}</span><span :class="`log-hljs-level-${message.level}`">{{`\t${message.level}\t`}}</span><span class="log-hljs-thread">{{ `[${message.host}:${message.port}]\t` }}</span><span class="log-hljs-thread">{{`[${message.thread}]\t`}}</span><span class="log-hljs-location">{{`${message.location}: \n`}}</span> -<span class="pl-6px">{{`${message.message}\n`}}</span><ThrowableComponent :throwable="message.throwable" /></pre>
</code> </code>
</div> </div>
</div> </div>
<NEmpty v-else class="h-full" />
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -7,9 +7,10 @@ defineOptions({
}); });
interface Props { interface Props {
title?: string; title: string;
minSize?: number;
} }
const props = defineProps<Props>(); const props = withDefaults(defineProps<Props>(), { minSize: 360 });
interface Emits { interface Emits {
(e: 'update:modelValue', modelValue: boolean): void; (e: 'update:modelValue', modelValue: boolean): void;
@ -25,8 +26,8 @@ const appStore = useAppStore();
const state = reactive({ width: 0 }); const state = reactive({ width: 0 });
const isFullscreen = ref(false); const isFullscreen = ref(false);
const drawerWidth = computed(() => { const drawerWidth = computed(() => {
const maxMinWidth = 360; const maxMinWidth = props.minSize;
const maxMaxWidth = 600; const maxMaxWidth = Math.max(props.minSize, 600);
if (appStore.isMobile) { if (appStore.isMobile) {
return state.width * 0.9 >= maxMinWidth ? `${maxMinWidth}px` : '90%'; return state.width * 0.9 >= maxMinWidth ? `${maxMinWidth}px` : '90%';
} }

View File

@ -0,0 +1,76 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import CronInput from '@sa/cron-input';
import { useAppStore } from '@/store/modules/app';
defineOptions({
name: 'SceneTriggerInterval'
});
interface Props {
backOff: Api.RetryScene.BackOff;
}
const model = defineModel<string>();
const props = defineProps<Props>();
const app = useAppStore();
/** 保存 `固定时间` 类型的 时间间隔 */
const interval = ref<number>(props.backOff === 2 || props.backOff === 4 ? Number(model.value) : 60);
/** 保存 `CRON表达式` 类型的 表达式 */
const cron = ref<string>(props.backOff === 3 ? model.value! : '* * * * * ?');
const delayLevelDesc = ref('10s,15s,30s,35s,40s,50s,1m,2m,4m,6m,8m,10m,20m,40m,1h,2h,3h,4h,5h,6h,7h,8h,9h,10h,11h,12h');
/** 监视 触发间隔 变化 */
watch(
interval,
val => {
if (props.backOff === 2 || props.backOff === 4) {
model.value = `${val}`;
}
},
{ immediate: true }
);
/** 监视 cron表达式 变化 */
watch(
cron,
val => {
if (props.backOff === 3) {
model.value = val;
}
},
{ immediate: true }
);
/** 根据不同 backOff 对model赋值 */
watch(
() => props.backOff,
backOff => {
if (backOff === 2 || backOff === 4) {
model.value = `${interval.value}`;
} else if (backOff === 3) {
model.value = cron.value;
} else {
model.value = '*';
}
},
{ immediate: true }
);
</script>
<template>
<CronInput v-if="backOff === 3" v-model="cron" :lang="app.locale" />
<NInputNumber
v-else-if="backOff === 2 || backOff === 4"
v-model:value="interval"
:placeholder="$t('page.retryScene.form.triggerInterval')"
clearable
/>
<NInput v-else v-model:value="delayLevelDesc" type="textarea" :autosize="{ minRows: 1, maxRows: 3 }" readonly />
</template>
<style scoped></style>

View File

@ -45,6 +45,10 @@ const headerMenus = computed(() => {
return []; return [];
}); });
const href = (url: string) => {
window.open(url, '_blank');
};
</script> </script>
<template> <template>
@ -58,6 +62,27 @@ const headerMenus = computed(() => {
<div class="h-full flex-y-center justify-end"> <div class="h-full flex-y-center justify-end">
<NamespaceSelect /> <NamespaceSelect />
<GlobalSearch /> <GlobalSearch />
<ButtonIcon
v-if="!appStore.isMobile"
class="color-#c71d23"
tooltip-content="Gitee"
icon="simple-icons:gitee"
@click="href('https://gitee.com/aizuda/snail-job')"
/>
<ButtonIcon
v-if="!appStore.isMobile"
tooltip-content="Github"
class="color-#010409 dark:color-#e6edf3"
icon="simple-icons:github"
@click="href('https://github.com/aizuda/snail-job')"
/>
<ButtonIcon
v-if="!appStore.isMobile"
tooltip-content="Document"
class="color-#272636 dark:color-#f0f2f5"
icon="material-symbols:unknown-document-outline"
@click="href('https://snailjob.opensnail.com/')"
/>
<FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" /> <FullScreen v-if="!appStore.isMobile" :full="isFullscreen" @click="toggle" />
<LangSwitch :lang="appStore.locale" :lang-options="appStore.localeOptions" @change-lang="appStore.changeLocale" /> <LangSwitch :lang="appStore.locale" :lang-options="appStore.localeOptions" @change-lang="appStore.changeLocale" />
<ThemeSchemaSwitch <ThemeSchemaSwitch

View File

@ -17,8 +17,8 @@ withDefaults(defineProps<Props>(), {
<template> <template>
<RouterLink to="/" class="w-full flex-center nowrap-hidden"> <RouterLink to="/" class="w-full flex-center nowrap-hidden">
<SystemLogo class="text-32px text-primary" /> <SystemLogo class="text-36px text-primary" />
<h2 v-show="showTitle" class="pl-8px text-16px text-primary font-bold transition duration-300 ease-in-out"> <h2 v-show="showTitle" class="pl-8px text-27px text-primary font-bold transition duration-300 ease-in-out">
{{ $t('system.title') }} {{ $t('system.title') }}
</h2> </h2>
</RouterLink> </RouterLink>

View File

@ -946,7 +946,7 @@ const local: App.I18n.Schema = {
triggerType: 'Trigger type', triggerType: 'Trigger type',
triggerInterval: 'Interval duration', triggerInterval: 'Interval duration',
blockStrategy: 'Blocking strategy', blockStrategy: 'Blocking strategy',
executorTimeout: 'Overtime time', executorTimeout: 'Overtime time(s)',
maxRetryTimes: 'Maximum number of retries', maxRetryTimes: 'Maximum number of retries',
retryInterval: 'Retry interval', retryInterval: 'Retry interval',
taskType: 'Task type', taskType: 'Task type',

View File

@ -953,7 +953,7 @@ const local: App.I18n.Schema = {
triggerType: '触发类型', triggerType: '触发类型',
triggerInterval: '间隔时长', triggerInterval: '间隔时长',
blockStrategy: '阻塞策略', blockStrategy: '阻塞策略',
executorTimeout: '超时时间', executorTimeout: '超时时间(秒)',
maxRetryTimes: '最大重试次数', maxRetryTimes: '最大重试次数',
retryInterval: '重试间隔', retryInterval: '重试间隔',
taskType: '任务类型', taskType: '任务类型',

View File

@ -812,7 +812,7 @@ declare namespace Api {
/** 最大重试次数 */ /** 最大重试次数 */
maxRetryCount: number; maxRetryCount: number;
/** 间隔时间 */ /** 间隔时间 */
triggerInterval: number; triggerInterval: string;
/** 调用链超时时间 */ /** 调用链超时时间 */
deadlineRequest: number; deadlineRequest: number;
/** 超时时间 */ /** 超时时间 */
@ -1212,6 +1212,8 @@ declare namespace Api {
type JobMessage = { type JobMessage = {
level: JobLevel; level: JobLevel;
host: string;
port: string;
location: string; location: string;
message: string; message: string;
thread: string; thread: string;

View File

@ -20,7 +20,7 @@ interface FormModel {
const model: FormModel = reactive({ const model: FormModel = reactive({
userName: 'admin', userName: 'admin',
password: '654321' password: 'admin'
}); });
type RuleKey = Extract<keyof FormModel, 'userName' | 'password'>; type RuleKey = Extract<keyof FormModel, 'userName' | 'password'>;

View File

@ -48,7 +48,7 @@ const latestBuildTime = BUILD_TIME;
<NCard :title="$t('page.about.projectInfo.title')" :bordered="false" size="small" segmented class="card-wrapper"> <NCard :title="$t('page.about.projectInfo.title')" :bordered="false" size="small" segmented class="card-wrapper">
<NDescriptions label-placement="left" bordered size="small" :column="column"> <NDescriptions label-placement="left" bordered size="small" :column="column">
<NDescriptionsItem :label="$t('page.about.projectInfo.officialWebsite')"> <NDescriptionsItem :label="$t('page.about.projectInfo.officialWebsite')">
<a class="text-primary" :href="pkg.website" target="_blank" rel="noopener noreferrer"> <a class="text-primary" :href="pkg.officialWebsite" target="_blank" rel="noopener noreferrer">
{{ $t('page.about.projectInfo.officialWebsite') }} {{ $t('page.about.projectInfo.officialWebsite') }}
</a> </a>
</NDescriptionsItem> </NDescriptionsItem>

View File

@ -139,7 +139,7 @@ const createColumns = (): DataTableColumns<Api.Dashboard.Task> => [
title: $t('page.home.retryTab.task.total'), title: $t('page.home.retryTab.task.total'),
key: 'total', key: 'total',
align: 'center', align: 'center',
render: row => <span class="retry-table-number">{row.run}</span> render: row => <span class="retry-table-number">{row.total}</span>
} }
]; ];

View File

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, reactive, watch } from 'vue'; import { computed, reactive, watch } from 'vue';
import CronInput from '@sa/cron-input';
import { useFormRules, useNaiveForm } from '@/hooks/common/form'; import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import OperateDrawer from '@/components/common/operate-drawer.vue'; import OperateDrawer from '@/components/common/operate-drawer.vue';
import { $t } from '@/locales'; import { $t } from '@/locales';
@ -10,6 +9,7 @@ import RouteKey from '@/components/common/route-key.vue';
import ExecutorType from '@/components/common/executor-type.vue'; import ExecutorType from '@/components/common/executor-type.vue';
import TaskType from '@/components/common/task-type.vue'; import TaskType from '@/components/common/task-type.vue';
import CodeMirror from '@/components/common/code-mirror.vue'; import CodeMirror from '@/components/common/code-mirror.vue';
import JobTriggerInterval from '@/components/common/job-trigger-interval.vue';
defineOptions({ defineOptions({
name: 'JobTaskOperateDrawer' name: 'JobTaskOperateDrawer'
@ -271,7 +271,7 @@ watch(visible, () => {
</script> </script>
<template> <template>
<OperateDrawer v-model="visible" :title="title" @handle-submit="handleSubmit"> <OperateDrawer v-model="visible" :title="title" :min-size="480" @handle-submit="handleSubmit">
<NForm ref="formRef" :model="model" :rules="rules"> <NForm ref="formRef" :model="model" :rules="rules">
<NFormItem :label="$t('page.jobTask.jobName')" path="jobName"> <NFormItem :label="$t('page.jobTask.jobName')" path="jobName">
<NInput <NInput
@ -281,27 +281,39 @@ watch(visible, () => {
:placeholder="$t('page.jobTask.form.jobName')" :placeholder="$t('page.jobTask.form.jobName')"
/> />
</NFormItem> </NFormItem>
<NFormItem :label="$t('page.jobTask.groupName')" path="groupName"> <NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20">
<SelectGroup v-model:value="model.groupName" /> <NGi>
</NFormItem> <NFormItem :label="$t('page.jobTask.groupName')" path="groupName">
<NFormItem :label="$t('page.jobTask.jobStatus')" path="jobStatus"> <SelectGroup v-model:value="model.groupName" />
<NRadioGroup v-model:value="model.jobStatus" name="jobStatus"> </NFormItem>
<NSpace> </NGi>
<NRadio <NGi>
v-for="item in enableStatusNumberOptions" <NFormItem :label="$t('page.jobTask.jobStatus')" path="jobStatus">
:key="item.value" <NRadioGroup v-model:value="model.jobStatus" name="jobStatus">
:value="item.value" <NSpace>
:label="$t(item.label)" <NRadio
/> v-for="item in enableStatusNumberOptions"
</NSpace> :key="item.value"
</NRadioGroup> :value="item.value"
</NFormItem> :label="$t(item.label)"
<NFormItem :label="$t('page.jobTask.executorType')" path="executorType"> />
<ExecutorType v-model:value="model.executorType" /> </NSpace>
</NFormItem> </NRadioGroup>
<NFormItem :label="$t('page.jobTask.executorInfo')" path="executorInfo"> </NFormItem>
<NInput v-model:value="model.executorInfo" :placeholder="$t('page.jobTask.form.executorInfo')" /> </NGi>
</NFormItem> </NGrid>
<NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20">
<NGi>
<NFormItem :label="$t('page.jobTask.executorType')" path="executorType">
<ExecutorType v-model:value="model.executorType" />
</NFormItem>
</NGi>
<NGi>
<NFormItem :label="$t('page.jobTask.executorInfo')" path="executorInfo">
<NInput v-model:value="model.executorInfo" :placeholder="$t('page.jobTask.form.executorInfo')" />
</NFormItem>
</NGi>
</NGrid>
<NFormItem :label="$t('page.jobTask.taskType')" path="taskType"> <NFormItem :label="$t('page.jobTask.taskType')" path="taskType">
<TaskType v-model:value="model.taskType" :placeholder="$t('page.jobTask.form.taskType')" /> <TaskType v-model:value="model.taskType" :placeholder="$t('page.jobTask.form.taskType')" />
</NFormItem> </NFormItem>
@ -336,68 +348,84 @@ watch(visible, () => {
</NCard> </NCard>
<CodeMirror v-else v-model="model.argsStr" lang="json" :placeholder="$t('page.jobTask.form.argsStr')" /> <CodeMirror v-else v-model="model.argsStr" lang="json" :placeholder="$t('page.jobTask.form.argsStr')" />
</NFormItem> </NFormItem>
<NFormItem :label="$t('page.jobTask.routeKey')" path="routeKey"> <NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20">
<RouteKey v-model:value="model.routeKey" :task-type="model.taskType" /> <NGi>
</NFormItem> <NFormItem :label="$t('page.jobTask.routeKey')" path="routeKey">
<NFormItem :label="$t('page.jobTask.blockStrategy')" path="blockStrategy"> <RouteKey v-model:value="model.routeKey" :task-type="model.taskType" />
<BlockStrategy v-model:value="model.blockStrategy" /> </NFormItem>
</NFormItem> </NGi>
<NFormItem :label="$t('page.jobTask.triggerType')" path="triggerType"> <NGi>
<TriggerType <NFormItem :label="$t('page.jobTask.blockStrategy')" path="blockStrategy">
v-model:value="model.triggerType" <BlockStrategy v-model:value="model.blockStrategy" />
:placeholder="$t('page.jobTask.form.triggerType')" </NFormItem>
@update:value="model.triggerInterval = ''" </NGi>
/> </NGrid>
</NFormItem> <NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20">
<NFormItem :label="$t('page.jobTask.triggerInterval')" path="triggerInterval"> <NGi>
<NInput <NFormItem :label="$t('page.jobTask.triggerType')" path="triggerType">
v-if="model.triggerType === 2" <TriggerType
v-model:value="model.triggerInterval" v-model:value="model.triggerType"
:placeholder="$t('page.jobTask.form.triggerInterval')" :placeholder="$t('page.jobTask.form.triggerType')"
/> @update:value="model.triggerInterval = ''"
<CronInput />
v-else-if="model.triggerType === 3" </NFormItem>
v-model:value="model.triggerInterval" </NGi>
:placeholder="$t('page.jobTask.form.triggerInterval_CRON')" <NGi>
/> <NFormItem :label="$t('page.jobTask.triggerInterval')" path="triggerInterval">
<NInput v-else-if="model.triggerType === 99" v-model:value="model.triggerInterval" disabled /> <JobTriggerInterval v-model="model.triggerInterval" :trigger-type="model.triggerType" />
</NFormItem> </NFormItem>
<NFormItem :label="$t('page.jobTask.executorTimeout')" path="executorTimeout"> </NGi>
<NInputNumber </NGrid>
v-model:value="model.executorTimeout" <NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20">
:min="1" <NGi>
:max="60" <NFormItem :label="$t('page.jobTask.executorTimeout')" path="executorTimeout">
:placeholder="$t('page.jobTask.form.executorTimeout')" <NInputGroup>
clearable <NInputNumber
/> v-model:value="model.executorTimeout"
</NFormItem> :min="1"
<NFormItem :label="$t('page.jobTask.maxRetryTimes')" path="maxRetryTimes"> :max="99999999"
<NInputNumber :placeholder="$t('page.jobTask.form.executorTimeout')"
v-model:value="model.maxRetryTimes" clearable
:min="1" />
:max="60" </NInputGroup>
:placeholder="$t('page.jobTask.form.maxRetryTimes')" </NFormItem>
clearable </NGi>
/> <NGi>
</NFormItem> <NFormItem :label="$t('page.jobTask.maxRetryTimes')" path="maxRetryTimes">
<NFormItem :label="$t('page.jobTask.retryInterval')" path="retryInterval"> <NInputNumber
<NInputNumber v-model:value="model.maxRetryTimes"
v-model:value="model.retryInterval" :min="1"
:min="1" :max="999"
:max="60" :placeholder="$t('page.jobTask.form.maxRetryTimes')"
:placeholder="$t('page.jobTask.form.retryInterval')" clearable
clearable />
/> </NFormItem>
</NFormItem> </NGi>
<NFormItem :label="$t('page.jobTask.parallelNum')" path="parallelNum"> </NGrid>
<NInputNumber <NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20">
v-model:value="model.parallelNum" <NGi>
:min="1" <NFormItem :label="$t('page.jobTask.retryInterval')" path="retryInterval">
:max="60" <NInputNumber
:placeholder="$t('page.jobTask.form.parallelNum')" v-model:value="model.retryInterval"
clearable :min="1"
/> :max="99999999"
</NFormItem> :placeholder="$t('page.jobTask.form.retryInterval')"
clearable
/>
</NFormItem>
</NGi>
<NGi>
<NFormItem :label="$t('page.jobTask.parallelNum')" path="parallelNum">
<NInputNumber
v-model:value="model.parallelNum"
:min="1"
:max="999"
:placeholder="$t('page.jobTask.form.parallelNum')"
clearable
/>
</NFormItem>
</NGi>
</NGrid>
<NFormItem :label="$t('page.jobTask.description')" path="description"> <NFormItem :label="$t('page.jobTask.description')" path="description">
<NInput v-model:value="model.description" type="textarea" :placeholder="$t('page.jobTask.form.description')" /> <NInput v-model:value="model.description" type="textarea" :placeholder="$t('page.jobTask.form.description')" />
</NFormItem> </NFormItem>

View File

@ -102,7 +102,8 @@ onBeforeUnmount(() => {
</NDescriptions> </NDescriptions>
</NTabPane> </NTabPane>
<NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if"> <NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if">
<LogDrawer v-model="logList" :drawer="false" /> <LogDrawer v-if="logList.length > 0" v-model="logList" :drawer="false" />
<NEmpty v-else class="h-full" />
</NTabPane> </NTabPane>
</NTabs> </NTabs>
</DetailDrawer> </DetailDrawer>

View File

@ -1,6 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'; import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue';
import CronInput from '@sa/cron-input';
import { useFormRules, useNaiveForm } from '@/hooks/common/form'; import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import OperateDrawer from '@/components/common/operate-drawer.vue'; import OperateDrawer from '@/components/common/operate-drawer.vue';
import RouteKey from '@/components/common/route-key.vue'; import RouteKey from '@/components/common/route-key.vue';
@ -8,7 +7,6 @@ import { $t } from '@/locales';
import { fetchAddRetryScene, fetchEditRetryScene, fetchGetAllGroupNameList } from '@/service/api'; import { fetchAddRetryScene, fetchEditRetryScene, fetchGetAllGroupNameList } from '@/service/api';
import { DelayLevel, backOffRecordOptions, enableStatusNumberOptions } from '@/constants/business'; import { DelayLevel, backOffRecordOptions, enableStatusNumberOptions } from '@/constants/business';
import { translateOptions, translateOptions2 } from '@/utils/common'; import { translateOptions, translateOptions2 } from '@/utils/common';
import { useAppStore } from '@/store/modules/app';
defineOptions({ defineOptions({
name: 'SceneOperateDrawer' name: 'SceneOperateDrawer'
@ -21,7 +19,6 @@ interface Props {
rowData?: Api.RetryScene.Scene | null; rowData?: Api.RetryScene.Scene | null;
} }
const app = useAppStore();
const groupNameList = ref<string[]>([]); const groupNameList = ref<string[]>([]);
const delayLevelDesc = ref<string>('10s'); const delayLevelDesc = ref<string>('10s');
@ -72,7 +69,7 @@ function createDefaultModel(): Model {
sceneStatus: 1, sceneStatus: 1,
backOff: 2, backOff: 2,
maxRetryCount: 1, maxRetryCount: 1,
triggerInterval: 60, triggerInterval: '60',
deadlineRequest: 60000, deadlineRequest: 60000,
executorTimeout: 60, executorTimeout: 60,
description: '', description: '',
@ -155,7 +152,7 @@ async function handleSubmit() {
routeKey, routeKey,
description description
} = model; } = model;
fetchAddRetryScene({ const { error } = await fetchAddRetryScene({
groupName, groupName,
sceneName, sceneName,
sceneStatus, sceneStatus,
@ -167,6 +164,8 @@ async function handleSubmit() {
routeKey, routeKey,
description description
}); });
if (error) return;
window.$message?.success($t('common.addSuccess'));
} }
if (props.operateType === 'edit') { if (props.operateType === 'edit') {
@ -183,7 +182,7 @@ async function handleSubmit() {
routeKey, routeKey,
description description
} = model; } = model;
fetchEditRetryScene({ const { error } = await fetchEditRetryScene({
id, id,
groupName, groupName,
sceneName, sceneName,
@ -196,8 +195,9 @@ async function handleSubmit() {
routeKey, routeKey,
description description
}); });
if (error) return;
window.$message?.success($t('common.updateSuccess'));
} }
window.$message?.success($t('common.updateSuccess'));
closeDrawer(); closeDrawer();
emit('submitted'); emit('submitted');
} }
@ -229,7 +229,7 @@ watch(
</script> </script>
<template> <template>
<OperateDrawer v-model="visible" :title="title" @handle-submit="handleSubmit"> <OperateDrawer v-model="visible" :title="title" :min-size="480" @handle-submit="handleSubmit">
<NForm ref="formRef" :model="model" :rules="rules"> <NForm ref="formRef" :model="model" :rules="rules">
<NFormItem :label="$t('page.retryScene.sceneName')" path="sceneName"> <NFormItem :label="$t('page.retryScene.sceneName')" path="sceneName">
<NInput <NInput
@ -240,97 +240,115 @@ watch(
:placeholder="$t('page.retryScene.form.sceneName')" :placeholder="$t('page.retryScene.form.sceneName')"
/> />
</NFormItem> </NFormItem>
<NFormItem :label="$t('page.retryScene.groupName')" path="groupName"> <NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20">
<NSelect <NGi>
v-model:value="model.groupName" <NFormItem :label="$t('page.retryScene.groupName')" path="groupName">
:disabled="props.operateType === 'edit'" <NSelect
:placeholder="$t('page.retryScene.form.groupName')" v-model:value="model.groupName"
:options="translateOptions2(groupNameList)" :disabled="props.operateType === 'edit'"
clearable :placeholder="$t('page.retryScene.form.groupName')"
/> :options="translateOptions2(groupNameList)"
</NFormItem> clearable
<NFormItem :label="$t('common.routeKey.routeLabel')" path="routeKey">
<RouteKey v-model:value="model.routeKey" />
</NFormItem>
<NFormItem :label="$t('page.retryScene.maxRetryCount')" path="maxRetryCount">
<NInputNumber
v-model:value="model.maxRetryCount"
:min="1"
:max="model.backOff === 1 ? 26 : 9999999"
:placeholder="$t('page.retryScene.form.maxRetryCount')"
clearable
/>
</NFormItem>
<NFormItem :label="$t('page.retryScene.executorTimeout')" path="executorTimeout">
<NInputNumber
v-model:value="model.executorTimeout"
:min="1"
:max="60"
:placeholder="$t('page.retryScene.form.executorTimeout')"
clearable
/>
</NFormItem>
<NFormItem :label="$t('page.retryScene.deadlineRequest')" path="deadlineRequest">
<NInputNumber
v-model:value="model.deadlineRequest"
:min="100"
:max="60000"
:placeholder="$t('page.retryScene.form.deadlineRequest')"
clearable
/>
</NFormItem>
<NFormItem :label="$t('page.retryScene.backOff')" path="backOff">
<NSelect
v-model:value="model.backOff"
:placeholder="$t('page.retryScene.form.backOff')"
:options="translateOptions(backOffRecordOptions)"
clearable
/>
</NFormItem>
<NFormItem path="triggerInterval">
<CronInput v-if="model.backOff === 3" v-model:value="model.triggerInterval as any" :lang="app.locale" />
<NInputNumber
v-else-if="model.backOff === 2 || model.backOff === 4"
v-model:value="model.triggerInterval as any"
:placeholder="$t('page.retryScene.form.triggerInterval')"
clearable
/>
<NInput v-else v-model:value="delayLevelDesc" type="textarea" :autosize="{ minRows: 1, maxRows: 3 }" readonly />
<template #label>
<div class="flex-center">
{{ $t('page.retryScene.triggerInterval') }}
<NTooltip v-if="model.backOff === 1" trigger="hover">
<template #trigger>
<NButton text class="ml-6px">
<SvgIcon icon="ant-design:info-circle-outlined" class="mb-1px text-16px" />
</NButton>
</template>
延迟等级是参考RocketMQ的messageDelayLevel设计实现具体延迟时间如下:
10s,15s,30s,35s,40s,50s,1m,2m,4m,6m,8m,10m,20m,40m,1h,2h,3h,4h,5h,6h,7h,8h,9h,10h,11h,12h
<br />
<NText strong>执行逻辑:</NText>
<NUl align-text>
<NLi>第一次执行间隔10s</NLi>
<NLi>第二次执行间隔15s</NLi>
<NLi>l第二次执行间隔30s</NLi>
<NLi>........... 依次类推</NLi>
</NUl>
</NTooltip>
</div>
</template>
</NFormItem>
<NFormItem :label="$t('page.retryScene.sceneStatus')" path="sceneStatus">
<NRadioGroup v-model:value="model.sceneStatus" name="sceneStatus">
<NSpace>
<NRadio
v-for="item in enableStatusNumberOptions"
:key="item.value"
:value="item.value"
:label="$t(item.label)"
/> />
</NSpace> </NFormItem>
</NRadioGroup> </NGi>
</NFormItem> <NGi>
<NFormItem :label="$t('page.retryScene.sceneStatus')" path="sceneStatus">
<NRadioGroup v-model:value="model.sceneStatus" name="sceneStatus">
<NSpace>
<NRadio
v-for="item in enableStatusNumberOptions"
:key="item.value"
:value="item.value"
:label="$t(item.label)"
/>
</NSpace>
</NRadioGroup>
</NFormItem>
</NGi>
</NGrid>
<NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20">
<NGi>
<NFormItem :label="$t('common.routeKey.routeLabel')" path="routeKey">
<RouteKey v-model:value="model.routeKey" />
</NFormItem>
</NGi>
<NGi>
<NFormItem :label="$t('page.retryScene.maxRetryCount')" path="maxRetryCount">
<NInputNumber
v-model:value="model.maxRetryCount"
:min="1"
:max="model.backOff === 1 ? 26 : 9999999"
:placeholder="$t('page.retryScene.form.maxRetryCount')"
clearable
/>
</NFormItem>
</NGi>
</NGrid>
<NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20">
<NGi>
<NFormItem :label="$t('page.retryScene.backOff')" path="backOff">
<NSelect
v-model:value="model.backOff"
:placeholder="$t('page.retryScene.form.backOff')"
:options="translateOptions(backOffRecordOptions)"
clearable
/>
</NFormItem>
</NGi>
<NGi>
<NFormItem path="triggerInterval">
<SceneTriggerInterval v-model="model.triggerInterval" :back-off="model.backOff" />
<template #label>
<div class="flex-center">
{{ $t('page.retryScene.triggerInterval') }}
<NTooltip v-if="model.backOff === 1" trigger="hover">
<template #trigger>
<NButton text class="ml-6px">
<SvgIcon icon="ant-design:info-circle-outlined" class="mb-1px text-16px" />
</NButton>
</template>
延迟等级是参考RocketMQ的messageDelayLevel设计实现具体延迟时间如下:
10s,15s,30s,35s,40s,50s,1m,2m,4m,6m,8m,10m,20m,40m,1h,2h,3h,4h,5h,6h,7h,8h,9h,10h,11h,12h
<br />
<NText strong>执行逻辑:</NText>
<NUl align-text>
<NLi>第一次执行间隔10s</NLi>
<NLi>第二次执行间隔15s</NLi>
<NLi>l第二次执行间隔30s</NLi>
<NLi>........... 依次类推</NLi>
</NUl>
</NTooltip>
</div>
</template>
</NFormItem>
</NGi>
</NGrid>
<NGrid cols="2 s:1 m:2" responsive="screen" x-gap="20">
<NGi>
<NFormItem :label="$t('page.retryScene.executorTimeout')" path="executorTimeout">
<NInputNumber
v-model:value="model.executorTimeout"
:min="1"
:max="60"
:placeholder="$t('page.retryScene.form.executorTimeout')"
clearable
/>
</NFormItem>
</NGi>
<NGi>
<NFormItem :label="$t('page.retryScene.deadlineRequest')" path="deadlineRequest">
<NInputNumber
v-model:value="model.deadlineRequest"
:min="100"
:max="60000"
:placeholder="$t('page.retryScene.form.deadlineRequest')"
clearable
/>
</NFormItem>
</NGi>
</NGrid>
<NFormItem :label="$t('page.retryScene.description')" path="description"> <NFormItem :label="$t('page.retryScene.description')" path="description">
<NInput <NInput
v-model:value="model.description" v-model:value="model.description"

View File

@ -109,7 +109,8 @@ onBeforeUnmount(() => {
</NDescriptions> </NDescriptions>
</NTabPane> </NTabPane>
<NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if"> <NTabPane :name="1" :tab="$t('page.log.title')" display-directive="if">
<LogDrawer v-model="logList" :drawer="false" /> <LogDrawer v-if="logList.length > 0" v-model="logList" :drawer="false" />
<NEmpty v-else class="h-full" />
</NTabPane> </NTabPane>
</NTabs> </NTabs>
</OperateDrawer> </OperateDrawer>