merge(sj_map_reduce): Merge branch dev_1.1.0_beta1 into dev_map_reduce

This commit is contained in:
xlsea 2024-06-24 16:07:22 +08:00
commit 61b0d80a77
72 changed files with 671 additions and 303 deletions

2
.env
View File

@ -2,7 +2,7 @@ VITE_APP_TITLE=Snail Job
VITE_APP_DESC=A flexible, reliable, and fast platform for distributed task retry and distributed task scheduling.
VITE_APP_VERSION=1.0.0
VITE_APP_VERSION=1.1.0-beta1
VITE_APP_DEFAULT_TOKEN=SJ_Wyz3dmsdbDOkDujOTSSoBjGQP1BMsVnj

View File

@ -15,7 +15,7 @@ interface Props {
min: number;
max: number;
};
locale: I18n.LocaleType;
locale?: I18n.LocaleType;
}
const props = withDefaults(defineProps<Props>(), {
@ -106,7 +106,9 @@ const value = computed(() => {
return props.field.value === DATE ? type.value : `${lastDayOfWeek.value}${type.value}`;
case TYPE.SPECIFY: {
const specifyValue = specify.value;
return specifyValue.length ? specifyValue.sort((a, b) => a - b).join(type.value) : `${specifyValue[0] || 0}`;
return specifyValue.length
? specifyValue.sort((a, b) => a - b).join(type.value)
: `${specifyValue[0] || specifies.value[0].value}`;
}
default:
return '';
@ -144,7 +146,8 @@ watch(
specify.value =
data !== 'undefined' && data !== 'NaN' ? data.split(TYPE.SPECIFY).map(i => Number.parseInt(i, 10)) : [];
}
}
},
{ immediate: true }
);
watch(

View File

@ -49,7 +49,7 @@ const fields = computed(() => {
// });
const previewLabel = computed(() => {
return Locales[props.lang].preview.join(previewTime.value.toString());
return Locales[props.lang].preview.join(previewTime.value?.toString());
});
const expression = computed(() => {
@ -81,7 +81,7 @@ const previews = computed(() => {
try {
previewList = parserCron(expression.value);
} catch (error) {
previewList = ['此表达式暂时无法解析!'];
previewList = [Locales[props.lang].previewError];
}
return previewList;

View File

@ -31,7 +31,7 @@ const value = computed({
});
const formatter = (val: number) => {
return props.fieldValue === WEEK ? formatterWeek(val.toString(), props.locale!) : null;
return props.fieldValue === WEEK ? formatterWeek(val?.toString(), props.locale!) : null;
};
const parser = (val: string) => {

View File

@ -19,7 +19,7 @@ export function generateSpecifies(
let index = 0;
for (let specify = min; specify <= max; specify += 1) {
specifies.push({ value: specify, label: labels ? labels[index] : specify.toString() });
specifies.push({ value: specify, label: labels ? labels[index] : specify?.toString() });
index += 1;
}

View File

@ -29,6 +29,8 @@ export type TableConfig<A extends ApiFn, T, C> = {
apiFn: A;
/** api params */
apiParams?: Parameters<A>[0];
/** search params */
searchParams?: Parameters<A>[0];
/** transform api response to table data */
transformer: Transformer<T, Awaited<ReturnType<A>>>;
/** columns factory */
@ -133,6 +135,9 @@ export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<
}
if (immediate) {
if (config.searchParams) {
updateSearchParams(config.searchParams);
}
getData();
}

View File

@ -11,7 +11,7 @@ export class Crypto<T extends object> {
encrypt(data: T): string {
const dataString = JSON.stringify(data);
const encrypted = CryptoJS.AES.encrypt(dataString, this.secret);
return encrypted.toString();
return encrypted?.toString();
}
decrypt(encrypted: string) {

View File

@ -29,6 +29,9 @@
"warning-outlined": {
"body": "<path fill=\"currentColor\" d=\"M464 720a48 48 0 1 0 96 0a48 48 0 1 0-96 0m16-304v184c0 4.4 3.6 8 8 8h48c4.4 0 8-3.6 8-8V416c0-4.4-3.6-8-8-8h-48c-4.4 0-8 3.6-8 8m475.7 440l-416-720c-6.2-10.7-16.9-16-27.7-16s-21.6 5.3-27.7 16l-416 720C56 877.4 71.4 904 96 904h832c24.6 0 40-26.6 27.7-48m-783.5-27.9L512 239.9l339.8 588.2z\"/>"
},
"check-outlined": {
"body": "<path fill=\"currentColor\" d=\"M912 190h-69.9c-9.8 0-19.1 4.5-25.1 12.2L404.7 724.5L207 474a32 32 0 0 0-25.1-12.2H112c-6.7 0-10.4 7.7-6.3 12.9l273.9 347c12.8 16.2 37.4 16.2 50.3 0l488.4-618.9c4.1-5.1.4-12.8-6.3-12.8\"/>"
},
"clock-circle-outlined": {
"body": "<path fill=\"currentColor\" d=\"M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448s448-200.6 448-448S759.4 64 512 64m0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372s372 166.6 372 372s-166.6 372-372 372\"/><path fill=\"currentColor\" d=\"M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2\"/>"
},

View File

@ -8,6 +8,10 @@
"dashboard-outline-rounded": {
"body": "<path fill=\"currentColor\" d=\"M13 8V4q0-.425.288-.712T14 3h6q.425 0 .713.288T21 4v4q0 .425-.288.713T20 9h-6q-.425 0-.712-.288T13 8M3 12V4q0-.425.288-.712T4 3h6q.425 0 .713.288T11 4v8q0 .425-.288.713T10 13H4q-.425 0-.712-.288T3 12m10 8v-8q0-.425.288-.712T14 11h6q.425 0 .713.288T21 12v8q0 .425-.288.713T20 21h-6q-.425 0-.712-.288T13 20M3 20v-4q0-.425.288-.712T4 15h6q.425 0 .713.288T11 16v4q0 .425-.288.713T10 21H4q-.425 0-.712-.288T3 20m2-9h4V5H5zm10 8h4v-6h-4zm0-12h4V5h-4zM5 19h4v-2H5zm4-2\"/>"
},
"expand-more-rounded": {
"body": "<path fill=\"currentColor\" d=\"M12 14.95q-.2 0-.375-.062t-.325-.213l-4.6-4.6q-.275-.275-.275-.7t.275-.7t.7-.275t.7.275l3.9 3.9l3.9-3.9q.275-.275.7-.275t.7.275t.275.7t-.275.7l-4.6 4.6q-.15.15-.325.213T12 14.95\"/>",
"hidden": true
},
"group-work-outline": {
"body": "<path fill=\"currentColor\" d=\"M8 16q.825 0 1.413-.587T10 14t-.587-1.412T8 12t-1.412.588T6 14t.588 1.413T8 16m8 0q.825 0 1.413-.587T18 14t-.587-1.412T16 12t-1.412.588T14 14t.588 1.413T16 16m-4-6q.825 0 1.413-.587T14 8t-.587-1.412T12 6t-1.412.588T10 8t.588 1.413T12 10m0 12q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22m0-2q3.35 0 5.675-2.325T20 12t-2.325-5.675T12 4T6.325 6.325T4 12t2.325 5.675T12 20m0-8\"/>"
},
@ -31,6 +35,12 @@
},
"sunny": {
"body": "<path fill=\"currentColor\" d=\"M11 5V1h2v4zm6.65 2.75l-1.375-1.375l2.8-2.875l1.4 1.425zM19 13v-2h4v2zm-8 10v-4h2v4zM6.35 7.7L3.5 4.925l1.425-1.4L7.75 6.35zm12.7 12.8l-2.775-2.875l1.35-1.35l2.85 2.75zM1 13v-2h4v2zm3.925 7.5l-1.4-1.425l2.8-2.8l.725.675l.725.7zM12 18q-2.5 0-4.25-1.75T6 12t1.75-4.25T12 6t4.25 1.75T18 12t-1.75 4.25T12 18\"/>"
},
"check-circle": {
"body": "<path fill=\"currentColor\" d=\"m10.6 16.6l7.05-7.05l-1.4-1.4l-5.65 5.65l-2.85-2.85l-1.4 1.4zM12 22q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
},
"cancel": {
"body": "<path fill=\"currentColor\" d=\"m8.4 17l3.6-3.6l3.6 3.6l1.4-1.4l-3.6-3.6L17 8.4L15.6 7L12 10.6L8.4 7L7 8.4l3.6 3.6L7 15.6zm3.6 5q-2.075 0-3.9-.788t-3.175-2.137T2.788 15.9T2 12t.788-3.9t2.137-3.175T8.1 2.788T12 2t3.9.788t3.175 2.137T21.213 8.1T22 12t-.788 3.9t-2.137 3.175t-3.175 2.138T12 22\"/>"
}
}
}

View File

@ -1,4 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="snail-job" data-name="snail-job" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 709.6845005488 690.7728719041">
<path fill="#1366ff" d="M338.3025810878.1920476727c-24.3000003621,2.1000000313-31.0000004619,2.8000000417-41.0000006109,4.5000000671C156.7025783818,28.3920480929,44.9025767158,131.4920496292,10.4025762017,269.0920516796,2.8025760885,299.5920521341.0025760468,322.4920524753.0025760468,354.0920529462c-.1000000015,33.1000004932,2.7000000402,55.8000008315,10.400000155,86.7000012919,9.9000001475,39.4000005871,26.0000003874,76.0000011325,47.9000007138,109.1000016257,13.9000002071,20.9000003114,25.100000374,34.7000005171,42.5000006333,52.6000007838,29.8000004441,30.5000004545,62.0000009239,53.8000008017,99.2000014782,71.8000010699,8.3000001237,4.0000000596,15.7000002339,7.3000001088,16.4000002444,7.3000001088.6000000089,0,5.4000000805,1.3000000194,10.5000001565,3.0000000447,44.7000006661,14.3000002131,92.2000013739,3.7000000551,138.4000020623-30.9000004604,4.1000000611-3.1000000462,12.9000001922-10.8000001609,19.4000002891-17.1000002548,10.1000001505-9.7000001445,12.4000001848-12.6000001878,15.2000002265-18.5000002757,15.6000002325-33.9000005051,14.6000002176-69.0000010282-3.2000000477-110.9000016525-8.6000001281-20.1000002995-25.9000003859-46.1000006869-43.3000006452-65.0000009686-3.0000000447-3.4000000507-10.7000001594-9.6000001431-17.1000002548-14.0000002086-24.5000003651-16.7000002488-43.7000006512-32.1000004783-54.3000008091-43.4000006467-6.3000000939-6.8000001013-16.1000002399-21.9000003263-18.7000002787-28.9000004306-2.8000000417-7.5000001118-3.6000000536-17.5000002608-2.0000000298-24.4000003636,1.8000000268-7.8000001162,7.2000001073-18.7000002787,12.0000001788-24.2000003606,4.1000000611-4.6000000685,4.2000000626-4.8000000715,3.5000000522-10.000000149-1.6000000238-11.7000001743-17.8000002652-62.0000009239-20.600000307-63.8000009507-.6000000089-.3000000045-3.3000000492-.9000000134-6.0000000894-1.2000000179-10.5000001565-1.3000000194-18.3000002727-6.9000001028-22.7000003383-16.6000002474-2.1000000313-4.5000000671-2.4000000358-6.4000000954-2.0000000298-12.1000001803,1.1000000164-15.500000231,13.5000002012-26.0000003874,29.2000004351-24.8000003695,18.3000002727,1.5000000224,30.1000004485,19.2000002861,23.7000003532,35.8000005335-1.2000000179,3.0000000447-2.1000000313,6.5000000969-2.1000000313,7.7000001147,0,3.1000000462,8.2000001222,18.5000002757,14.2000002116,26.6000003964,13.9000002071,18.8000002801,30.000000447,27.6000004113,48.4000007212,26.4000003934,10.600000158-.6000000089,13.3000001982-2.0000000298,16.4000002444-8.2000001222,2.3000000343-4.5000000671,2.5000000373-6.2000000924,2.5000000373-17.0000002533,0-7.7000001147-.7000000104-15.0000002235-1.9000000283-20.5000003055-2.5000000373-11.6000001729-11.3000001684-38.4000005722-13.7000002041-41.4000006169-1.1000000164-1.3000000194-4.1000000611-3.2000000477-6.8000001013-4.1000000611-14.700000219-4.900000073-23.3000003472-16.7000002488-23.3000003472-32.1000004783,0-13.1000001952,7.2000001073-24.3000003621,19.5000002906-30.000000447,7.8000001162-3.6000000536,19.0000002831-3.4000000507,27.2000004053.5000000075,3.5000000522,1.6000000238,7.8000001162,4.8000000715,10.3000001535,7.5000001118,10.3000001535,11.2000001669,11.8000001758,24.4000003636,4.5000000671,39.4000005871-3.3000000492,6.7000000998-3.0000000447,8.1000001207,5.100000076,24.6000003666,18.2000002712,36.8000005484,52.2000007778,76.8000011444,101.1000015065,118.7000017688,39.2000005841,33.7000005022,64.9000009671,65.4000009745,84.9000012651,105.1000015661,15.6000002325,31.0000004619,24.1000003591,57.9000008628,30.20000045,96.300001435,1.0000000149,6.1000000909,1.8000000268,19.7000002936,2.1000000313,34.6000005156.6000000089,26.0000003874-.3000000045,39.5000005886-3.8000000566,59.3000008836-1.1000000164,6.0000000894-1.8000000268,11.0000001639-1.6000000238,11.2000001669.8000000119.8000000119,31.7000004724-31.7000004724,40.1000005975-42.1000006273,18.9000002816-23.5000003502,38.6000005752-56.6000008434,50.400000751-84.5000012591,13.0000001937-30.800000459,22.7000003383-68.9000010267,26.4000003934-104.0000015497,1.8000000268-17.3000002578,1.5000000224-54.7000008151-.6000000089-72.0000010729-13.2000001967-111.000001654-73.5000010952-206.1000030711-167.300002493-264.0000039339-13.3000001982-8.2000001222-37.4000005573-20.5000003055-52.2000007778-26.5000003949-29.7000004426-12.1000001803-67.1000009999-21.3000003174-99.1000014767-24.5000003651-11.4000001699-1.1000000164-43.8000006527-2.0000000298-51.00000076-1.4000000209Z"/>
<path d="M338.3025810878.1920476727c-24.3000003621,2.1000000313-31.0000004619,2.8000000417-41.0000006109,4.5000000671C156.7025783818,28.3920480929,44.9025767158,131.4920496292,10.4025762017,269.0920516796,2.8025760885,299.5920521341.0025760468,322.4920524753.0025760468,354.0920529462c-.1000000015,33.1000004932,2.7000000402,55.8000008315,10.400000155,86.7000012919,9.9000001475,39.4000005871,26.0000003874,76.0000011325,47.9000007138,109.1000016257,13.9000002071,20.9000003114,25.100000374,34.7000005171,42.5000006333,52.6000007838,29.8000004441,30.5000004545,62.0000009239,53.8000008017,99.2000014782,71.8000010699,8.3000001237,4.0000000596,15.7000002339,7.3000001088,16.4000002444,7.3000001088.6000000089,0,5.4000000805,1.3000000194,10.5000001565,3.0000000447,44.7000006661,14.3000002131,92.2000013739,3.7000000551,138.4000020623-30.9000004604,4.1000000611-3.1000000462,12.9000001922-10.8000001609,19.4000002891-17.1000002548,10.1000001505-9.7000001445,12.4000001848-12.6000001878,15.2000002265-18.5000002757,15.6000002325-33.9000005051,14.6000002176-69.0000010282-3.2000000477-110.9000016525-8.6000001281-20.1000002995-25.9000003859-46.1000006869-43.3000006452-65.0000009686-3.0000000447-3.4000000507-10.7000001594-9.6000001431-17.1000002548-14.0000002086-24.5000003651-16.7000002488-43.7000006512-32.1000004783-54.3000008091-43.4000006467-6.3000000939-6.8000001013-16.1000002399-21.9000003263-18.7000002787-28.9000004306-2.8000000417-7.5000001118-3.6000000536-17.5000002608-2.0000000298-24.4000003636,1.8000000268-7.8000001162,7.2000001073-18.7000002787,12.0000001788-24.2000003606,4.1000000611-4.6000000685,4.2000000626-4.8000000715,3.5000000522-10.000000149-1.6000000238-11.7000001743-17.8000002652-62.0000009239-20.600000307-63.8000009507-.6000000089-.3000000045-3.3000000492-.9000000134-6.0000000894-1.2000000179-10.5000001565-1.3000000194-18.3000002727-6.9000001028-22.7000003383-16.6000002474-2.1000000313-4.5000000671-2.4000000358-6.4000000954-2.0000000298-12.1000001803,1.1000000164-15.500000231,13.5000002012-26.0000003874,29.2000004351-24.8000003695,18.3000002727,1.5000000224,30.1000004485,19.2000002861,23.7000003532,35.8000005335-1.2000000179,3.0000000447-2.1000000313,6.5000000969-2.1000000313,7.7000001147,0,3.1000000462,8.2000001222,18.5000002757,14.2000002116,26.6000003964,13.9000002071,18.8000002801,30.000000447,27.6000004113,48.4000007212,26.4000003934,10.600000158-.6000000089,13.3000001982-2.0000000298,16.4000002444-8.2000001222,2.3000000343-4.5000000671,2.5000000373-6.2000000924,2.5000000373-17.0000002533,0-7.7000001147-.7000000104-15.0000002235-1.9000000283-20.5000003055-2.5000000373-11.6000001729-11.3000001684-38.4000005722-13.7000002041-41.4000006169-1.1000000164-1.3000000194-4.1000000611-3.2000000477-6.8000001013-4.1000000611-14.700000219-4.900000073-23.3000003472-16.7000002488-23.3000003472-32.1000004783,0-13.1000001952,7.2000001073-24.3000003621,19.5000002906-30.000000447,7.8000001162-3.6000000536,19.0000002831-3.4000000507,27.2000004053.5000000075,3.5000000522,1.6000000238,7.8000001162,4.8000000715,10.3000001535,7.5000001118,10.3000001535,11.2000001669,11.8000001758,24.4000003636,4.5000000671,39.4000005871-3.3000000492,6.7000000998-3.0000000447,8.1000001207,5.100000076,24.6000003666,18.2000002712,36.8000005484,52.2000007778,76.8000011444,101.1000015065,118.7000017688,39.2000005841,33.7000005022,64.9000009671,65.4000009745,84.9000012651,105.1000015661,15.6000002325,31.0000004619,24.1000003591,57.9000008628,30.20000045,96.300001435,1.0000000149,6.1000000909,1.8000000268,19.7000002936,2.1000000313,34.6000005156.6000000089,26.0000003874-.3000000045,39.5000005886-3.8000000566,59.3000008836-1.1000000164,6.0000000894-1.8000000268,11.0000001639-1.6000000238,11.2000001669.8000000119.8000000119,31.7000004724-31.7000004724,40.1000005975-42.1000006273,18.9000002816-23.5000003502,38.6000005752-56.6000008434,50.400000751-84.5000012591,13.0000001937-30.800000459,22.7000003383-68.9000010267,26.4000003934-104.0000015497,1.8000000268-17.3000002578,1.5000000224-54.7000008151-.6000000089-72.0000010729-13.2000001967-111.000001654-73.5000010952-206.1000030711-167.300002493-264.0000039339-13.3000001982-8.2000001222-37.4000005573-20.5000003055-52.2000007778-26.5000003949-29.7000004426-12.1000001803-67.1000009999-21.3000003174-99.1000014767-24.5000003651-11.4000001699-1.1000000164-43.8000006527-2.0000000298-51.00000076-1.4000000209Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@ -25,7 +25,6 @@ const handleUpdate = (value: Api.Common.BlockStrategy) => {
v-model:value="valueRef"
:placeholder="$t('common.blockStrategy.form')"
:options="translateOptions(blockStrategyRecordOptions)"
clearable
@update:value="handleUpdate"
/>
</template>

View File

@ -0,0 +1,35 @@
<script setup lang="ts">
import { $t } from '@/locales';
import { monthRange } from '@/utils/common';
defineOptions({
name: 'DatetimeRange'
});
// day.js
const DATETIME_FORMAT_ISO8601 = "yyyy-MM-dd'T'HH:mm:ss";
const modelValue = defineModel<[string, string] | null>('value');
const createShortcuts = () => {
const shortcuts: any = {};
shortcuts[$t('common.currentMonth')] = monthRange(0, 'month');
shortcuts[$t('common.lastMonth')] = monthRange(1, 'month');
shortcuts[$t('common.lastTwoMonth')] = monthRange(2, 'month');
return shortcuts;
};
</script>
<template>
<NDatePicker
v-model:formatted-value="modelValue"
type="datetimerange"
:value-format="DATETIME_FORMAT_ISO8601"
clearable
:default-time="['00:00:00', '23:56:56']"
:shortcuts="createShortcuts()"
:actions="['clear', 'confirm']"
/>
</template>
<style scoped lang="scss"></style>

View File

@ -25,7 +25,6 @@ const handleUpdate = (value: Api.Common.TriggerType) => {
v-model:value="valueRef"
:placeholder="$t('common.executorType.form')"
:options="translateOptions(executorTypeRecordOptions)"
clearable
@update:value="handleUpdate"
/>
</template>

View File

@ -78,14 +78,14 @@ const onUpdateShow = (value: boolean) => {
};
function timestampToDate(timestamp: string): string {
const date = new Date(Number.parseInt(timestamp.toString(), 10));
const date = new Date(Number.parseInt(timestamp?.toString(), 10));
const year = date.getFullYear();
const month =
(date.getMonth() + 1).toString().length === 1 ? `0${date.getMonth() + 1}` : (date.getMonth() + 1).toString();
const day = date.getDate().toString().length === 1 ? `0${date.getDate()}` : date.getDate().toString();
const hours = date.getHours().toString().length === 1 ? `0${date.getHours()}` : date.getHours().toString();
const minutes = date.getMinutes().toString().length === 1 ? `0${date.getMinutes()}` : date.getMinutes().toString();
const seconds = date.getSeconds().toString().length === 1 ? `0${date.getSeconds()}` : date.getSeconds().toString();
(date.getMonth() + 1)?.toString().length === 1 ? `0${date.getMonth() + 1}` : (date.getMonth() + 1)?.toString();
const day = date.getDate()?.toString().length === 1 ? `0${date.getDate()}` : date.getDate()?.toString();
const hours = date.getHours()?.toString().length === 1 ? `0${date.getHours()}` : date.getHours()?.toString();
const minutes = date.getMinutes()?.toString().length === 1 ? `0${date.getMinutes()}` : date.getMinutes()?.toString();
const seconds = date.getSeconds()?.toString().length === 1 ? `0${date.getSeconds()}` : date.getSeconds()?.toString();
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${date.getMilliseconds()}`;
}

View File

@ -1,29 +0,0 @@
<script setup lang="ts">
import { ref } from 'vue';
import { $t } from '@/locales';
import { translateOptions } from '@/utils/common';
import { operationReasonOptions } from '@/constants/business';
interface Emits {
(e: 'update:value', value: Api.Common.OperationReason): void;
}
const emit = defineEmits<Emits>();
const handleUpdate = (operationReason: Api.Common.OperationReason) => {
emit('update:value', operationReason);
};
const operationReasonRef = ref<Api.Common.OperationReason>();
</script>
<template>
<NSelect
v-model:value="operationReasonRef"
:placeholder="$t('common.jobOperationReason.form')"
:options="translateOptions(operationReasonOptions)"
clearable
@update:value="handleUpdate"
/>
</template>
<style scoped></style>

View File

@ -36,12 +36,7 @@ const selectOptions = computed(() => {
</script>
<template>
<NSelect
v-model:value="modelValue"
:placeholder="$t('common.routeKey.routeForm')"
:options="selectOptions"
clearable
/>
<NSelect v-model:value="modelValue" :placeholder="$t('common.routeKey.routeForm')" :options="selectOptions" />
</template>
<style scoped></style>

View File

@ -10,6 +10,7 @@ defineOptions({
interface Props {
model: Record<string, any>;
btnSpan?: string;
}
const props = defineProps<Props>();
@ -40,15 +41,16 @@ async function search() {
}
const btnSpan = computed(() => {
const keyNum = Object.keys(props.model).length - 2;
return `24 m:12 m:${(4 - (keyNum % 4)) * 6}`;
const keyNum = Object.keys(props.model).length - 1;
return props.btnSpan || keyNum % 4 !== 0 ? `24 m:12 m:${(4 - ((keyNum - 1) % 4)) * 6}` : '24';
});
</script>
<template>
<NCard :title="title" :bordered="false" size="small" class="card-wrapper">
<NForm ref="formRef" :model="model" label-placement="left" :label-width="80" :show-feedback="appStore.isMobile">
<NGrid responsive="screen" item-responsive :y-gap="5">
<NGrid responsive="screen" item-responsive :y-gap="12">
<slot></slot>
<NFormItemGi :y-gap="8" :span="btnSpan" class="pr-24px lg:p-t-0 md:p-t-16px">
<NSpace class="min-w-172px w-full" justify="end">

View File

@ -10,10 +10,12 @@ defineOptions({
interface Props {
disabled?: boolean;
clearable?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
disabled: false
disabled: false,
clearable: false
});
const model = defineModel<string | null>();
@ -45,6 +47,7 @@ getGroupNameList();
:placeholder="$t('page.retryTask.form.groupName')"
:options="translateOptions2(groupNameList)"
:disabled="props.disabled"
:clearable="props.clearable"
filterable
@update:value="handleUpdate"
/>

View File

@ -12,9 +12,12 @@ const emit = defineEmits<Emits>();
interface Props {
groupName: string | null;
clearable?: boolean;
}
const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
clearable: false
});
/** 场景列表 */
const sceneNameList = ref<string[]>([]);
@ -55,7 +58,7 @@ watch(
v-model:value="sceneNameRef"
:placeholder="$t('page.retryTask.form.sceneName')"
:options="translateOptions2(sceneNameList)"
clearable
:clearable="props.clearable"
/>
</template>

View File

@ -1,28 +1,31 @@
<script setup lang="ts">
import { ref } from 'vue';
import { $t } from '@/locales';
import { translateOptions } from '@/utils/common';
import { taskBatchStatusRecordOptions } from '@/constants/business';
interface Emits {
(e: 'update:value', value: Api.Common.TaskBatchStatus): void;
defineOptions({
name: 'TaskBatchStatus'
});
interface Props {
disabled?: boolean;
clearable?: boolean;
}
const emit = defineEmits<Emits>();
const taskBatchStatusRef = ref<Api.Common.TaskBatchStatus>();
const handleUpdate = (taskBatchStatus: Api.Common.TaskBatchStatus) => {
emit('update:value', taskBatchStatus);
};
const props = withDefaults(defineProps<Props>(), {
disabled: false,
clearable: false
});
const modelValue = defineModel<Api.Common.TaskBatchStatus>();
</script>
<template>
<NSelect
v-model:value="taskBatchStatusRef"
v-model:value="modelValue"
:placeholder="$t('common.taskBatchStatus.form')"
:options="translateOptions(taskBatchStatusRecordOptions)"
clearable
@update:value="handleUpdate"
:disabled="props.disabled"
:clearable="props.clearable"
/>
</template>

View File

@ -25,7 +25,6 @@ const handleUpdate = (value: Api.Common.BlockStrategy) => {
v-model:value="valueRef"
:placeholder="$t('common.routeKey.routeForm')"
:options="translateOptions(taskTypeRecordRecordOptions)"
clearable
@update:value="handleUpdate"
/>
</template>

View File

@ -25,7 +25,6 @@ const handleUpdate = (value: Api.Common.TriggerType) => {
v-model:value="valueRef"
:placeholder="$t('common.triggerType.form')"
:options="translateOptions(triggerTypeOptions)"
clearable
@update:value="handleUpdate"
/>
</template>

View File

@ -7,9 +7,9 @@ export const REG_PHONE =
/**
* Password reg
*
* 6-18 characters, including letters, numbers, and underscores
* 26-20
*/
export const REG_PWD = /^\w{6,18}$/;
export const REG_PWD = /^(?![a-zA-Z]+$)(?!\d+$)(?![^\da-zA-Z\s]+$).{6,20}$/;
/** Email reg */
export const REG_EMAIL = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;

View File

@ -36,6 +36,7 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
} = useHookTable<A, GetTableData<A>, TableColumn<NaiveUI.TableDataWithIndex<GetTableData<A>>>>({
apiFn,
apiParams,
searchParams: config.searchParams,
columns: config.columns,
transformer: res => {
const { data: records = [], page: current = 1, size = 10, total = 0 } = res.data || {};

View File

@ -34,10 +34,10 @@ const { formRef, validate } = useNaiveForm();
type RuleRecord = Partial<Record<keyof Model, App.Global.FormRule[]>>;
const rules = computed<RuleRecord>(() => {
const { formRules, createConfirmPwdRule } = useFormRules();
const { formRules, createConfirmPwdRule, defaultRequiredRule } = useFormRules();
return {
oldPassword: formRules.pwd,
oldPassword: [defaultRequiredRule],
newPassword: formRules.pwd,
checkPassword: createConfirmPwdRule(model.newPassword!)
};

View File

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

View File

@ -1,24 +1,60 @@
<script lang="ts" setup>
import { computed, ref } from 'vue';
<script lang="tsx" setup>
import { computed, ref, watch } from 'vue';
import { useRouter } from 'vue-router';
import { NEllipsis } from 'naive-ui';
import { $t } from '@/locales';
import { localStg } from '@/utils/storage';
import { useAppStore } from '@/store/modules/app';
import { useAuthStore } from '@/store/modules/auth';
import ButtonIcon from '@/components/custom/button-icon.vue';
import SvgIcon from '@/components/custom/svg-icon.vue';
defineOptions({
name: 'NamespaceSelect'
});
const router = useRouter();
const appStore = useAppStore();
const authStore = useAuthStore();
const namespaceId = ref<string>(localStg.get('namespaceId')!);
const userInfo = localStg.get('userInfo');
const selectOptions = computed(() =>
userInfo?.namespaceIds.map(item => {
return { label: item.name, value: item.uniqueId };
})
const namespaceIds = ref(localStg.get('userInfo')?.namespaceIds || []);
watch(
() => authStore.namespaceUniqueId,
val => {
namespaceId.value = val;
authStore.setNamespaceId(val);
}
);
watch(
() => authStore.userInfo.namespaceIds,
val => {
namespaceIds.value = val;
},
{ deep: true }
);
const dropOptions = computed(() =>
userInfo?.namespaceIds.map(item => {
return { label: item.name, key: item.uniqueId };
namespaceIds.value.map(item => {
return {
label: () => {
if (item.uniqueId === namespaceId.value) {
return (
<div class="max-w-130px flex items-center justify-between">
<NEllipsis tooltip={{ placement: 'left' }}>{item.name}</NEllipsis>
<SvgIcon class="ml-6px" icon="ant-design:check-outlined" />
</div>
);
}
return (
<div class="max-w-130px flex items-center justify-between">
<NEllipsis tooltip={{ placement: 'left' }}>{item.name}</NEllipsis>
</div>
);
},
key: item.uniqueId
};
})
);
@ -27,31 +63,80 @@ const onChange = (value: string) => {
authStore.setNamespaceId(value);
router.go(0);
};
const namespaceName = computed(() => {
return namespaceIds.value.filter(item => item.uniqueId === namespaceId.value)[0]?.name || 'Default';
});
</script>
<template>
<NDropdown v-if="appStore.isMobile" :value="namespaceId" :options="dropOptions" trigger="hover" @select="onChange">
<div>
<ButtonIcon :tooltip-content="$t('icon.namespace')" tooltip-placement="left">
<SvgIcon icon="oui:app-spaces" />
<SvgIcon icon="eos-icons:namespace" />
</ButtonIcon>
</div>
</NDropdown>
<NDropdown v-else :value="namespaceId" :options="dropOptions" trigger="click" @select="onChange">
<div class="namespace-select">
<ButtonIcon class="w-full" :tooltip-content="$t('icon.namespace')" tooltip-placement="left">
<SvgIcon icon="eos-icons:namespace" />
<NEllipsis class="text-14px">{{ namespaceName }}</NEllipsis>
<SvgIcon icon="material-symbols:expand-more-rounded" />
</ButtonIcon>
</div>
</NDropdown>
<NSelect
v-else
v-model:value="namespaceId"
class="namespace-select"
:options="selectOptions"
@update:value="onChange"
/>
</template>
<style lang="scss" scoped>
.namespace-select {
width: 150px;
width: 158px;
border: 1px solid rgb(224, 224, 230);
border-radius: 35px;
:deep(.n-base-selection) {
border-radius: 32px !important;
:deep(.n-button):hover,
:deep(.n-button):focus {
background-color: var(--n-color);
}
:deep(.n-ellipsis) {
width: 100%;
text-align: left;
max-width: 88px;
}
:deep(.n-button) {
width: 100% !important;
padding: 0 5px 0 10px !important;
}
:deep(.n-button__content) {
width: 100% !important;
}
:deep(.gap-8px) {
gap: 0 !important;
}
:deep(.flex-center) {
width: 100% !important;
justify-content: space-between !important;
}
}
.namespace-select:hover {
border-color: rgb(var(--nprogress-color));
}
.namespace-select-option {
display: flex;
justify-content: space-between;
align-items: center;
:deep(.n-ellipsis) {
width: 100%;
max-width: 113px;
}
}
</style>

View File

@ -82,6 +82,8 @@ const local: App.I18n.Schema = {
confirmRetry: 'Are you sure you want to retry?',
idDetailTip: 'Click on ID for details',
log: 'Log',
generateRandomly: 'Generate randomly',
active: 'Active',
yesOrNo: {
yes: 'Yes',
no: 'No'
@ -201,7 +203,10 @@ const local: App.I18n.Schema = {
}
},
updateDt: 'Updated Time',
createDt: 'Created Time'
createDt: 'Created Time',
currentMonth: 'Current Month',
lastMonth: 'Last Month',
lastTwoMonth: 'Last 2 Month'
},
request: {
logout: 'Logout user after request failed',
@ -309,7 +314,7 @@ const local: App.I18n.Schema = {
namespace: 'Namespace',
notify: 'Notify',
notify_recipient: 'Notify Recipient',
notify_scene: 'Notify Scene',
notify_config: 'Notify Config',
retry: 'Retry Task',
retry_task: 'Retry Task',
retry_scene: 'Retry Scene',
@ -909,7 +914,7 @@ const local: App.I18n.Schema = {
},
pwd: {
required: 'Please enter password',
invalid: '6-18 characters, including letters, numbers, and underscores'
invalid: 'Letters, numbers, and special characters, combination of two, 6 to 20 characters'
},
confirmPwd: {
required: 'Please enter password again',

View File

@ -82,6 +82,8 @@ const local: App.I18n.Schema = {
confirmRetry: '确认重试吗?',
log: '日志',
idDetailTip: '点击 ID 查看详情',
generateRandomly: '随机生成',
active: '活跃',
yesOrNo: {
yes: '是',
no: '否'
@ -201,7 +203,10 @@ const local: App.I18n.Schema = {
}
},
updateDt: '更新时间',
createDt: '创建时间'
createDt: '创建时间',
currentMonth: '当月',
lastMonth: '最近一月',
lastTwoMonth: '最近两月'
},
request: {
logout: '请求失败后登出用户',
@ -310,7 +315,7 @@ const local: App.I18n.Schema = {
group: '组管理',
notify: '告警通知',
notify_recipient: '通知人',
notify_scene: '通知场景',
notify_config: '通知配置',
retry: '重试任务',
retry_task: '任务管理',
'retry_dead-letter': '死信任务',
@ -915,7 +920,7 @@ const local: App.I18n.Schema = {
},
pwd: {
required: '请输入密码',
invalid: '密码格式不正确6-18位字符包含字母、数字、下划线'
invalid: '由字母、数字、特殊字符任意2种组成6-20位'
},
confirmPwd: {
required: '请输入确认密码',
@ -950,7 +955,7 @@ const local: App.I18n.Schema = {
expand: '展开菜单',
pin: '固定',
unpin: '取消固定',
namespace: '切换空间'
namespace: '切换命名空间'
},
datatable: {
itemCount: '共 {total} 条'

View File

@ -18,7 +18,7 @@ export function setupLoading() {
'right-0 bottom-0 animate-delay-1500'
];
const logoWithClass = systemLogo.replace('<svg', `<svg class="size-158px text-primary"`);
const logoWithClass = systemLogo.replace('<svg', `<svg class="size-158px fill-primary"`);
const dot = loadingClasses
.map(item => {

View File

@ -27,8 +27,8 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
job_batch: () => import("@/views/job/batch/index.vue"),
job_task: () => import("@/views/job/task/index.vue"),
namespace: () => import("@/views/namespace/index.vue"),
notify_config: () => import("@/views/notify/config/index.vue"),
notify_recipient: () => import("@/views/notify/recipient/index.vue"),
notify_scene: () => import("@/views/notify/scene/index.vue"),
pods: () => import("@/views/pods/index.vue"),
"retry_dead-letter": () => import("@/views/retry/dead-letter/index.vue"),
retry_log: () => import("@/views/retry/log/index.vue"),

View File

@ -166,6 +166,16 @@ export const generatedRoutes: GeneratedRoute[] = [
icon: 'material-symbols:notifications-active-outline-rounded'
},
children: [
{
name: 'notify_config',
path: '/notify/config',
component: 'view.notify_config',
meta: {
title: 'notify_config',
i18nKey: 'route.notify_config',
icon: 'cbi:scene-dynamic'
}
},
{
name: 'notify_recipient',
path: '/notify/recipient',
@ -175,16 +185,6 @@ export const generatedRoutes: GeneratedRoute[] = [
i18nKey: 'route.notify_recipient',
icon: 'fluent:people-call-20-filled'
}
},
{
name: 'notify_scene',
path: '/notify/scene',
component: 'view.notify_scene',
meta: {
title: 'notify_scene',
i18nKey: 'route.notify_scene',
icon: 'cbi:scene-dynamic'
}
}
]
},

View File

@ -18,7 +18,7 @@ export function fetchTriggerWorkflow(id: string) {
}
/** get namespace list */
export function fetchGetWorkflowNameList(params?: Api.WorkflowBatch.WorkflowBatchSearchParams) {
export function fetchGetWorkflowNameList(params?: Api.Workflow.WorkflowNameSearchParams) {
return request<Api.Workflow.Workflow[]>({
url: '/workflow/workflow-name/list',
method: 'get',

View File

@ -13,6 +13,9 @@ const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
export const request = createFlatRequest<App.Service.Response, RequestInstanceState>(
{
baseURL,
'axios-retry': {
retries: 0
},
headers: {
timeout: 6000
}
@ -52,14 +55,14 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
// when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
if (logoutCodes.includes(response.data.status.toString())) {
if (logoutCodes.includes(response.data.status?.toString())) {
handleLogout();
return null;
}
// when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
if (modalLogoutCodes.includes(response.data.status.toString())) {
if (modalLogoutCodes.includes(response.data.status?.toString())) {
request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.message];
// prevent the user from refreshing the page
@ -116,7 +119,7 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
// get backend error message and code
message = error.response?.data?.message || message;
backendErrorCode = error.response?.data?.status.toString() || '';
backendErrorCode = error.response?.data?.status?.toString() || '';
// the error message is displayed in the modal
const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];

View File

@ -19,6 +19,8 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const token = ref(getToken());
const namespaceUniqueId = ref('');
const userInfo: Api.Auth.UserInfo = reactive({
id: '',
userId: '',
@ -35,7 +37,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const isStaticSuper = computed(() => {
const { VITE_AUTH_ROUTE_MODE, VITE_STATIC_SUPER_ROLE } = import.meta.env;
return (
VITE_AUTH_ROUTE_MODE === 'static' && userInfo.roles.map(role => role.toString()).includes(VITE_STATIC_SUPER_ROLE)
VITE_AUTH_ROUTE_MODE === 'static' && userInfo.roles.map(role => role?.toString()).includes(VITE_STATIC_SUPER_ROLE)
);
});
@ -160,6 +162,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
}
function setNamespaceId(namespaceId: string) {
namespaceUniqueId.value = namespaceId;
const userNamespace = localStg.get('userNamespace') || {};
userNamespace[userInfo.userId] = namespaceId;
localStg.set('userNamespace', userNamespace);
@ -169,11 +172,13 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
return {
token,
userInfo,
namespaceUniqueId,
isStaticSuper,
isLogin,
loginLoading,
resetStore,
login,
getUserInfo,
initUserInfo,
initAppVersion,
setNamespaceId

25
src/typings/api.d.ts vendored
View File

@ -287,8 +287,7 @@ declare namespace Api {
groupName?: string;
type: DashboardLineType;
mode?: DashboardLineMode;
startTime?: string;
endTime?: string;
datetimeRange?: [string, string] | null;
} & CommonSearchParams;
/**
@ -718,7 +717,7 @@ declare namespace Api {
Api.RetryDeadLetter.DeadLetter,
'id' | 'uniqueId' | 'groupName' | 'sceneName' | 'idempotentId' | 'bizNo' | 'taskType' | 'createDt'
> &
CommonSearchParams
CommonSearchParams & { datetimeRange?: [string, string] }
>;
/** DeadLetter list */
@ -914,6 +913,18 @@ declare namespace Api {
Pick<Api.Workflow.Workflow, 'workflowName' | 'groupName' | 'workflowStatus'> & CommonSearchParams
>;
/** workflow name search params */
type WorkflowNameSearchParams = CommonType.RecordNullable<
Pick<
Common.CommonRecord<{
keywords: string;
workflowId: number;
groupName: string;
}>,
'keywords' | 'workflowId' | 'groupName'
>
>;
type ExportWorkflow = Common.CommonRecord<{
workflowIds: String[];
}> &
@ -1113,7 +1124,8 @@ declare namespace Api {
/** JobBatch search params */
type JobBatchSearchParams = CommonType.RecordNullable<
Pick<Api.JobBatch.JobBatch, 'groupName' | 'jobName' | 'taskBatchStatus' | 'taskType'> & CommonSearchParams
Pick<Api.JobBatch.JobBatch, 'groupName' | 'jobName' | 'taskBatchStatus' | 'jobId' | 'taskType'> &
CommonSearchParams & { datetimeRange?: [string, string] }
>;
/** JobBatch list */
@ -1148,7 +1160,8 @@ declare namespace Api {
/** workflowBatch search params */
type WorkflowBatchSearchParams = CommonType.RecordNullable<
Pick<Api.WorkflowBatch.WorkflowBatch, 'workflowId' | 'groupName' | 'taskBatchStatus'> & CommonSearchParams
Pick<Api.WorkflowBatch.WorkflowBatch, 'workflowId' | 'groupName' | 'workflowName' | 'taskBatchStatus'> &
CommonSearchParams & { datetimeRange?: [string, string] }
>;
/** workflowBatch list */
@ -1198,7 +1211,7 @@ declare namespace Api {
/** retryLog search params */
type RetryLogSearchParams = CommonType.RecordNullable<
Pick<Api.RetryLog.RetryLog, 'uniqueId' | 'groupName' | 'sceneName' | 'idempotentId' | 'bizNo' | 'retryStatus'> &
CommonSearchParams
CommonSearchParams & { datetimeRange?: [string, string] }
>;
/** retryLog list */

View File

@ -332,6 +332,8 @@ declare namespace App {
confirmPause: string;
confirmFinish: string;
confirmRetry: string;
generateRandomly: string;
active: string;
log: string;
idDetailTip: string;
yesOrNo: {
@ -454,6 +456,9 @@ declare namespace App {
};
updateDt: string;
createDt: string;
currentMonth: string;
lastMonth: string;
lastTwoMonth: string;
};
request: {
logout: string;

View File

@ -42,7 +42,7 @@ declare namespace NaiveUI {
type NaiveTableConfig<A extends TableApiFn> = Pick<
import('@sa/hooks').TableConfig<A, GetTableData<A>, TableColumn<TableDataWithIndex<GetTableData<A>>>>,
'apiFn' | 'apiParams' | 'columns' | 'immediate'
'apiFn' | 'apiParams' | 'columns' | 'immediate' | 'searchParams'
> & {
/**
* whether to display the total items count

View File

@ -1,4 +1,5 @@
import { Md5 } from 'ts-md5';
import dayjs from 'dayjs';
import { $t } from '@/locales';
/**
@ -118,6 +119,34 @@ export function toggleHtmlClass(className: string) {
};
}
/**
* `最近n个自然月` timestamp时间区间
*
* @param months
* @param startOf
* @returns timestamp时间区间
*/
export function monthRange(months: number = 1, startOf: dayjs.OpUnitType = 'day') {
return [dayjs().subtract(months, 'month').startOf(startOf).valueOf(), dayjs().endOf('day').valueOf()] as [
number,
number
];
}
/**
* `最近n个自然月`
*
* @param months
* @param startOf
* @returns
*/
export function monthRangeISO8601(months: number = 1, startOf: dayjs.OpUnitType = 'day') {
return [
dayjs().subtract(months, 'month').startOf(startOf).format('YYYY-MM-DDTHH:mm:ss'),
dayjs().endOf('day').format('YYYY-MM-DDTHH:mm:ss')
] as [string, string];
}
export function isNotNull(value: any) {
return value !== undefined && value !== null && value !== '' && value !== 'undefined';
}

View File

@ -70,7 +70,7 @@ const href = (url: string) => {
<NCard :bordered="false" class="relative z-4 w-auto rd-12px">
<div class="w-400px lt-sm:w-300px">
<header class="flex-y-center justify-between">
<SystemLogo class="text-64px text-primary lt-sm:text-48px" />
<SystemLogo class="fill-primary text-64px lt-sm:text-48px" />
<h3 class="flex text-28px text-primary font-500 lt-sm:text-22px">
{{ $t('system.title') }}
<span class="mt-3px pl-12px text-16px color-#00000072 font-600">v{{ version }}</span>

View File

@ -37,7 +37,7 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
width: 48
},
{
key: 'index',
key: 'id',
title: $t('common.index'),
align: 'center',
width: 64

View File

@ -28,7 +28,7 @@ function search() {
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.groupConfig.groupName')" path="groupName" class="pr-24px">
<NInput v-model:value="model.groupName" :placeholder="$t('page.groupConfig.form.groupName')" />
<NInput v-model:value="model.groupName" :placeholder="$t('page.groupConfig.form.groupName')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.groupConfig.groupStatus')" path="groupStatus" class="pr-24px">
<NSelect

View File

@ -4,6 +4,7 @@ import type { DataTableColumns } from 'naive-ui';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { fetchAllGroupName, fetchJobLine, fetchRetryLine } from '@/service/api';
import DatetimeRange from '@/components/common/datetime-range.vue';
import TaskLineChart from './task-line-chart.vue';
import TaskPieChart from './task-pie-chart.vue';
@ -26,12 +27,9 @@ const tabParams = ref<Api.Dashboard.DashboardLineParams>({
type: 'WEEK',
page: 1,
size: 6,
mode: 'JOB'
mode: 'JOB',
datetimeRange: null
});
const dateRange = ref<[number, number] | null>();
const formattedValue = ref<[string, string] | null>(
tabParams.value.startTime && tabParams.value.endTime ? [tabParams.value.startTime, tabParams.value.endTime] : null
);
const getData = async () => {
const { data: lineData, error } =
@ -67,26 +65,19 @@ const onUpdateTab = (value: string) => {
}
};
const onUpdateDate = (value: [string, string]) => {
const onUpdateDate = (value: [string, string] | null) => {
if (value) {
tabParams.value.type = 'OTHERS';
tabParams.value.startTime = value[0];
tabParams.value.endTime = value[1];
}
};
const onClearDate = () => {
tabParams.value.type = 'WEEK';
tabParams.value.startTime = undefined;
tabParams.value.endTime = undefined;
};
const onUpdateType = (value: string) => {
if (value !== 'OTHERS') {
dateRange.value = null;
formattedValue.value = null;
tabParams.value.startTime = undefined;
tabParams.value.endTime = undefined;
tabParams.value.datetimeRange = null;
}
};
@ -213,16 +204,7 @@ getGroupNames();
<NRadioButton value="MONTH" :label="$t('page.home.retryTab.params.month')" />
<NRadioButton value="YEAR" :label="$t('page.home.retryTab.params.year')" />
</NRadioGroup>
<NDatePicker
v-model:value="dateRange"
v-model:formatted-value="formattedValue"
value-format="yyyy-MM-dd HH:mm:ss"
type="daterange"
class="w-300px lg:w-230px md:w-270px"
clearable
@update:formatted-value="onUpdateDate"
@clear="onClearDate"
/>
<DatetimeRange v-model:value="tabParams.datetimeRange" @update:value="onUpdateDate" @clear="onClearDate" />
<NSelect v-model:value="tabParams.groupName" :options="groupOptions" class="w-200px lg:w-150px md:w-170px" />
</div>
</div>

View File

@ -1,26 +1,26 @@
<script setup lang="tsx">
import { NButton, NPopconfirm, NTag, NTooltip } from 'naive-ui';
import { useBoolean } from '@sa/hooks';
import { ref, watch } from 'vue';
import { useRoute } from 'vue-router';
import { fetchGetJobBatchList, fetchGetJobNameList, fetchJobBatchRetry, fetchJobBatchStop } from '@/service/api';
import { ref } from 'vue';
import { fetchGetJobBatchList, fetchJobBatchRetry, fetchJobBatchStop } from '@/service/api';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useTable } from '@/hooks/common/table';
import { operationReasonRecord, taskBatchStatusRecord, taskTypeRecord } from '@/constants/business';
import { tagColor } from '@/utils/common';
import { monthRangeISO8601, tagColor } from '@/utils/common';
import SvgIcon from '@/components/custom/svg-icon.vue';
import JobBatchSearch from './modules/job-batch-search.vue';
import JobBatchDetailDrawer from './modules/job-batch-detail-drawer.vue';
const appStore = useAppStore();
const route = useRoute();
/** 详情页属性数据 */
const detailData = ref<Api.JobBatch.JobBatch | null>();
/** 详情页可见状态 */
const { bool: detailVisible, setTrue: openDetail } = useBoolean(false);
const { bool: detailLog, setBool: setDetailLog } = useBoolean(false);
const jobName = history.state.jobName;
const jobId = history.state.jobId;
const taskBatchStatus = history.state.taskBatchStatus;
const { columnChecks, columns, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
apiFn: fetchGetJobBatchList,
@ -29,7 +29,14 @@ const { columnChecks, columns, data, getData, loading, mobilePagination, searchP
size: 10,
groupName: null,
jobName: null,
taskBatchStatus: null
taskBatchStatus: null,
jobId: null,
datetimeRange: monthRangeISO8601()
},
searchParams: {
jobId,
jobName,
taskBatchStatus
},
columns: () => [
{
@ -211,41 +218,6 @@ async function handleStopJob(id: string) {
getData();
}
}
/** 处理路由 query 参数变化 */
async function handleQueryChanged(jobId: number) {
if (!jobId) {
searchParams.jobName = null;
} else {
const { data: jobList, error } = await fetchGetJobNameList({ jobId });
if (!error && jobList.length > 0) {
const jobName = jobList[0].jobName;
searchParams.jobName = jobName;
}
}
getData();
}
watch(
() => route.query,
() => {
if (route.name === 'job_batch' && route.query.jobId) {
const jobId = Number(route.query.jobId);
handleQueryChanged(jobId);
}
},
{ immediate: true }
);
function initParams() {
const taskBatchStatus = history.state.taskBatchStatus;
if (taskBatchStatus) {
searchParams.taskBatchStatus = taskBatchStatus;
getData();
}
}
initParams();
</script>
<template>

View File

@ -1,7 +1,11 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import type { SelectOption } from 'naive-ui';
import SelectGroup from '@/components/common/select-group.vue';
import TaskBatchStatus from '@/components/common/task-batch-status.vue';
import DatetimeRange from '@/components/common/datetime-range.vue';
import { $t } from '@/locales';
import { fetchGetJobNameList } from '@/service/api';
defineOptions({
name: 'JobBatchSearch'
@ -12,29 +16,84 @@ interface Emits {
(e: 'search'): void;
}
const noSearchFlag = ref(false);
const emit = defineEmits<Emits>();
/** 定时任务列表 */
const jobList = ref<Api.Job.Job[]>([]);
const model = defineModel<Api.JobBatch.JobBatchSearchParams>('model', { required: true });
const keywords = ref<string>(model.value.jobName as string);
function reset() {
keywords.value = '';
emit('reset');
}
function search() {
emit('search');
}
async function keywordsUpdate() {
const res = await fetchGetJobNameList({ keywords: keywords.value, groupName: model.value.groupName });
jobList.value = res.data as Api.Job.Job[];
}
function handleSelect(value: string) {
model.value.jobId = value;
}
watch(
() => keywords.value,
(value: string) => {
if (value.length !== 0) {
keywordsUpdate();
} else {
noSearchFlag.value = false;
}
}
);
function translateOptions(options: Api.Job.Job[]) {
return options.map(option => ({
value: option.id,
label: option.jobName
}));
}
function renderLabel(option: SelectOption) {
return [option.label as string, `(${option.value})`];
}
</script>
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<SearchForm btn-span="24 s:24 m:9 l:12 xl:15" :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.jobBatch.groupName')" path="groupName" class="pr-24px">
<SelectGroup v-model:value="model.groupName" />
<SelectGroup v-model:value="model.groupName" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.jobBatch.jobName')" path="jobName" class="pr-24px">
<NInput v-model:value="model.jobName" :placeholder="$t('page.jobBatch.form.jobName')" />
<NAutoComplete
v-model:value="keywords"
:placeholder="$t('page.jobBatch.form.jobName')"
:options="translateOptions(jobList)"
:empty-visible="noSearchFlag"
clearable
filterable
:render-label="renderLabel"
@select="handleSelect"
/>
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.jobBatch.taskBatchStatus')" path="taskBatchStatus" class="pr-24px">
<TaskBatchStatus v-model:value="model.taskBatchStatus" />
<TaskBatchStatus v-model:value="model.taskBatchStatus" clearable />
</NFormItemGi>
<NFormItemGi
span="24 s:24 m:15 l:12 xl:9"
:label="$t('page.common.createTime')"
path="datetimeRange"
class="pr-24px"
>
<DatetimeRange v-model:value="model.datetimeRange!" />
</NFormItemGi>
</SearchForm>
</template>

View File

@ -41,7 +41,7 @@ const { columnChecks, columns, data, getData, loading, mobilePagination, searchP
width: 48
},
{
key: 'index',
key: 'id',
title: $t('common.index'),
align: 'center',
width: 48
@ -252,7 +252,8 @@ async function handleTriggerJob(id: string) {
}
function goToBatch(jobId: string) {
routerPushByKey('job_batch', { query: { jobId } });
const findItem = data.value.find(item => item.id === jobId)!;
routerPushByKey('job_batch', { state: { jobId, jobName: findItem.jobName } });
}
function body(): Api.Job.ExportJob {

View File

@ -162,6 +162,7 @@ async function handleSubmit() {
groupName,
jobName,
argsType,
argsStr,
jobStatus,
routeKey,
executorType,
@ -179,7 +180,7 @@ async function handleSubmit() {
const { error } = await fetchAddJob({
groupName,
jobName,
argsStr: parseArgsStr(),
argsStr,
argsType,
jobStatus,
routeKey,
@ -204,6 +205,7 @@ async function handleSubmit() {
id,
groupName,
jobName,
argsStr,
argsType,
jobStatus,
routeKey,
@ -223,7 +225,7 @@ async function handleSubmit() {
id,
groupName,
jobName,
argsStr: parseArgsStr(),
argsStr,
argsType,
jobStatus,
routeKey,
@ -249,7 +251,8 @@ async function handleSubmit() {
function parseArgsStr() {
if (model.taskType === 3 && dynamicForm.args) {
return JSON.stringify(dynamicForm.args.map(item => item.arg));
const slices = dynamicForm.args.map(item => item.arg.trim()).filter(item => Boolean(item));
model.argsStr = slices.length > 0 ? JSON.stringify(slices) : '';
}
return model.argsStr;
}
@ -268,6 +271,26 @@ watch(visible, () => {
restoreValidation();
}
});
/** 分片参数变化, 解析并序列化到model.argsStr */
watch(dynamicForm, () => {
if (visible.value && model.taskType === 3) {
parseArgsStr();
}
});
/** 任务类型变化, 清理分片参数/方法参数 */
watch(
() => model.taskType,
taskType => {
if (visible.value) {
if (taskType !== 3) {
dynamicForm.args = [];
}
model.argsStr = '';
}
}
);
</script>
<template>

View File

@ -29,16 +29,17 @@ function search() {
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.jobTask.groupName')" path="groupName" class="pr-24px">
<SelectGroup v-model:value="model.groupName" />
<SelectGroup v-model:value="model.groupName" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.jobTask.jobName')" path="jobName" class="pr-24px">
<NInput v-model:value="model.jobName" :placeholder="$t('page.jobTask.form.jobName')" />
<NInput v-model:value="model.jobName" :placeholder="$t('page.jobTask.form.jobName')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.jobTask.jobStatus')" path="jobStatus" class="pr-24px">
<NSelect
v-model:value="model.jobStatus"
:placeholder="$t('page.jobTask.form.jobStatus')"
:options="translateOptions(enableStatusNumberOptions)"
clearable
/>
</NFormItemGi>
</SearchForm>

View File

@ -1,13 +1,24 @@
<script setup lang="tsx">
import { NButton } from 'naive-ui';
import { ref } from 'vue';
import { fetchGetNamespaceList } from '@/service/api';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { localStg } from '@/utils/storage';
import { useAuthStore } from '@/store/modules/auth';
import SvgIcon from '@/components/custom/svg-icon.vue';
import NamespaceOperateDrawer from './modules/namespace-operate-drawer.vue';
import NamespaceSearch from './modules/namespace-search.vue';
const appStore = useAppStore();
const authStore = useAuthStore();
const namespaceId = ref<string>(localStg.get('namespaceId')!);
const handleChange = (uniqueId: string) => {
namespaceId.value = uniqueId;
authStore.setNamespaceId(uniqueId);
};
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
apiFn: fetchGetNamespaceList,
@ -18,7 +29,7 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
},
columns: () => [
// {
// key: 'index',
// key: 'id',
// title: $t('common.index'),
// align: 'center',
// width: 64
@ -29,6 +40,21 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
align: 'left',
width: 120
},
{
key: 'status',
title: $t('common.active'),
align: 'center',
width: 40,
render: row => (
<div class="flex justify-center">
{namespaceId.value === row.uniqueId! ? (
<SvgIcon icon="material-symbols:check-circle" class="text-20px color-success" />
) : (
<SvgIcon icon="material-symbols:cancel" class="text-20px color-gray400" />
)}
</div>
)
},
{
key: 'uniqueId',
title: $t('page.namespace.uniqueId'),
@ -57,6 +83,16 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
<NButton type="primary" text ghost size="small" onClick={() => edit(row.id!)}>
{$t('common.edit')}
</NButton>
{namespaceId.value !== row.uniqueId! ? (
<>
<n-divider vertical />
<NButton type="warning" text ghost size="small" onClick={() => handleChange(row.uniqueId!)}>
{$t('common.switch')}
</NButton>
</>
) : (
''
)}
</div>
)
}

View File

@ -1,9 +1,11 @@
<script setup lang="ts">
import { computed, reactive, watch } from 'vue';
import { nanoid } from '@sa/utils';
import { useFormRules, useNaiveForm } from '@/hooks/common/form';
import OperateDrawer from '@/components/common/operate-drawer.vue';
import { $t } from '@/locales';
import { fetchAddNamespace, fetchEditNamespace } from '@/service/api';
import { useAuthStore } from '@/store/modules/auth';
defineOptions({
name: 'NamespaceOperateDrawer'
@ -24,6 +26,7 @@ interface Emits {
const emit = defineEmits<Emits>();
const authStore = useAuthStore();
const visible = defineModel<boolean>('visible', {
default: false
});
@ -61,6 +64,10 @@ const rules: Record<RuleKey, App.Global.FormRule> = {
}
};
function setUniqueId() {
model.uniqueId = nanoid(32);
}
function handleUpdateModelWhenEdit() {
if (props.operateType === 'add') {
Object.assign(model, createDefaultModel());
@ -93,6 +100,7 @@ async function handleSubmit() {
window.$message?.success($t('common.updateSuccess'));
}
await authStore.getUserInfo();
closeDrawer();
emit('submitted');
}
@ -109,11 +117,21 @@ watch(visible, () => {
<OperateDrawer v-model="visible" :title="title" @submitted="handleSubmit">
<NForm ref="formRef" :model="model" :rules="rules">
<NFormItem :label="$t('page.namespace.uniqueId')" path="uniqueId">
<NInput
v-model:value="model.uniqueId"
:disabled="props.operateType === 'edit'"
:placeholder="$t('page.namespace.form.uniqueId')"
/>
<NInputGroup>
<NInput
v-model:value="model.uniqueId"
:disabled="props.operateType === 'edit'"
:placeholder="$t('page.namespace.form.uniqueId')"
/>
<NTooltip trigger="hover">
<template #trigger>
<NButton type="default" ghost :disabled="props.operateType === 'edit'" @click="setUniqueId">
<icon-ic-round-refresh class="text-icon" />
</NButton>
</template>
{{ $t('common.generateRandomly') }}
</NTooltip>
</NInputGroup>
</NFormItem>
<NFormItem :label="$t('page.namespace.name')" path="name">
<NInput v-model:value="model.name" :placeholder="$t('page.namespace.form.name')" />

View File

@ -26,7 +26,7 @@ function search() {
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.namespace.keyword')" path="keyword" class="pr-24px">
<NInput v-model:value="model.keyword" :placeholder="$t('page.namespace.form.keyword')" />
<NInput v-model:value="model.keyword" :placeholder="$t('page.namespace.form.keyword')" clearable />
</NFormItemGi>
</SearchForm>
</template>

View File

@ -6,9 +6,9 @@ import { fetchBatchDeleteNotify, fetchGetNotifyConfigList, fetchUpdateNotifyStat
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import NotifyConfigOperateDrawer from '@/views/notify/scene/modules/notify-config-operate-drawer.vue';
import NotifyConfigSearch from '@/views/notify/scene/modules/notify-config-search.vue';
import NotifyConfigDetailDrawer from '@/views/notify/scene/modules/notify-config-detail-drawer.vue';
import NotifyConfigOperateDrawer from '@/views/notify/config/modules/notify-config-operate-drawer.vue';
import NotifyConfigSearch from '@/views/notify/config/modules/notify-config-search.vue';
import NotifyConfigDetailDrawer from '@/views/notify/config/modules/notify-config-detail-drawer.vue';
import StatusSwitch from '@/components/common/status-switch.vue';
import { jobNotifyScene, retryNotifyScene, systemTaskType, workflowNotifyScene } from '@/constants/business';
import { tagColor } from '@/utils/common';
@ -40,7 +40,7 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
width: 48
},
{
key: 'index',
key: 'id',
title: $t('common.index'),
align: 'center',
width: 64

View File

@ -115,12 +115,20 @@ function createDefaultModel(): Model {
type RuleKey = Extract<
keyof Model,
'groupName' | 'businessId' | 'recipientIds' | 'notifyStatus' | 'notifyScene' | 'rateLimiterStatus' | 'notifyThreshold'
| 'groupName'
| 'businessId'
| 'systemTaskType'
| 'recipientIds'
| 'notifyStatus'
| 'notifyScene'
| 'rateLimiterStatus'
| 'notifyThreshold'
>;
const rules: Record<RuleKey, App.Global.FormRule> = {
groupName: defaultRequiredRule,
businessId: defaultRequiredRule,
systemTaskType: defaultRequiredRule,
notifyStatus: defaultRequiredRule,
notifyScene: defaultRequiredRule,
recipientIds: defaultRequiredRule,
@ -277,7 +285,6 @@ watch(visible, () => {
v-model:value="model.systemTaskType"
:placeholder="$t('page.notifyConfig.form.systemTaskType')"
:options="translateOptions(systemTaskTypeOptions)"
clearable
@update:value="systemTaskTypeChange"
/>
</NFormItem>
@ -288,7 +295,6 @@ watch(visible, () => {
:options="retryScenes"
label-field="sceneName"
value-field="sceneName"
clearable
/>
</NFormItem>
<NFormItem v-if="model.systemTaskType === 3" :label="$t('page.notifyConfig.job')" path="businessId">
@ -298,7 +304,6 @@ watch(visible, () => {
:options="jobs"
label-field="jobName"
value-field="id"
clearable
/>
</NFormItem>
<NFormItem v-if="model.systemTaskType === 4" :label="$t('page.notifyConfig.workflow')" path="businessId">
@ -308,7 +313,6 @@ watch(visible, () => {
:options="workflows"
label-field="workflowName"
value-field="id"
clearable
/>
</NFormItem>
<NFormItem :label="$t('page.notifyConfig.notifyScene')" path="notifyScene">
@ -316,7 +320,6 @@ watch(visible, () => {
v-model:value="model.notifyScene"
:placeholder="$t('page.notifyConfig.form.notifyScene')"
:options="notifySceneOptions"
clearable
@update:value="retrySceneChange"
/>
</NFormItem>

View File

@ -1,5 +1,7 @@
<script setup lang="ts">
import { $t } from '@/locales';
import { translateOptions } from '@/utils/common';
import { enableStatusNumberOptions } from '@/constants/business';
defineOptions({
name: 'NotifyConfigSearch'
@ -26,13 +28,18 @@ function search() {
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.notifyConfig.groupName')" path="groupName" class="pr-24px">
<NSelect v-model:value="model.groupName" :placeholder="$t('page.notifyConfig.groupName')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.notifyConfig.notifyStatus')" path="notifyStatus" class="pr-24px">
<NSelect v-model:value="model.notifyStatus" :placeholder="$t('page.notifyConfig.notifyStatus')" clearable />
<SelectGroup v-model:value="model.groupName" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.notifyConfig.notifyScene')" path="notifyScene" class="pr-24px">
<NSelect v-model:value="model.notifyScene" :placeholder="$t('page.notifyConfig.notifyScene')" clearable />
<SelectScene v-model:value="model.notifyScene" :group-name="model.groupName as string" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.notifyConfig.notifyStatus')" path="notifyStatus" class="pr-24px">
<NSelect
v-model:value="model.notifyStatus"
:placeholder="$t('page.notifyConfig.notifyStatus')"
:options="translateOptions(enableStatusNumberOptions)"
clearable
/>
</NFormItemGi>
</SearchForm>
</template>

View File

@ -39,7 +39,7 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
width: 48
},
{
key: 'index',
key: 'id',
title: $t('common.index'),
align: 'center',
width: 64

View File

@ -35,7 +35,11 @@ function search() {
path="recipientName"
class="pr-24px"
>
<NInput v-model:value="model.recipientName" :placeholder="$t('page.notifyRecipient.form.recipientName')" />
<NInput
v-model:value="model.recipientName"
:placeholder="$t('page.notifyRecipient.form.recipientName')"
clearable
/>
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.notifyRecipient.notifyType')" path="notifyType" class="pr-24px">
<NSelect

View File

@ -27,7 +27,7 @@ function search() {
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.pods.groupName')" path="groupName" class="pr-24px">
<SelectGroup v-model:value="model.groupName" :placeholder="$t('page.pods.form.groupName')" />
<SelectGroup v-model:value="model.groupName" :placeholder="$t('page.pods.form.groupName')" clearable />
</NFormItemGi>
</SearchForm>
</template>

View File

@ -13,7 +13,7 @@ import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { retryTaskTypeRecord } from '@/constants/business';
import { tagColor } from '@/utils/common';
import { monthRangeISO8601, tagColor } from '@/utils/common';
import RetryDeadLetterSearch from './modules/dead-letter-search.vue';
import RetryDeadLetterDetailDrawer from './modules/retry-letter-detail-drawer.vue';
@ -30,7 +30,8 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
page: 1,
size: 10,
groupName: null,
sceneName: null
sceneName: null,
datetimeRange: monthRangeISO8601()
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
},
@ -41,7 +42,7 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
width: 48
},
{
key: 'index',
key: 'id',
title: $t('common.index'),
align: 'center',
width: 64

View File

@ -2,6 +2,7 @@
import { $t } from '@/locales';
import SelectGroup from '@/components/common/select-group.vue';
import SelectScene from '@/components/common/select-scene.vue';
import DatetimeRange from '@/components/common/datetime-range.vue';
defineOptions({
name: 'RetryDeadLetterSearch'
@ -28,10 +29,18 @@ function search() {
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.groupName')" path="groupName" class="pr-24px">
<SelectGroup v-model:value="model.groupName" />
<SelectGroup v-model:value="model.groupName" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.sceneName')" path="sceneName" class="pr-24px">
<SelectScene v-model:value="model.sceneName" :group-name="model.groupName as string" />
<SelectScene v-model:value="model.sceneName" :group-name="model.groupName as string" clearable />
</NFormItemGi>
<NFormItemGi
span="24 s:24 m:15 l:12 xl:9"
:label="$t('page.common.createTime')"
path="datetimeRange"
class="pr-24px"
>
<DatetimeRange v-model:value="model.datetimeRange!" />
</NFormItemGi>
</SearchForm>
</template>

View File

@ -7,7 +7,7 @@ import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { retryTaskStatusTypeRecord, retryTaskTypeRecord } from '@/constants/business';
import { tagColor } from '@/utils/common';
import { monthRangeISO8601, tagColor } from '@/utils/common';
import RetryLogSearch from './modules/retry-log-search.vue';
import RetryLogDetailDrawer from './modules/retry-log-detail-drawer.vue';
@ -17,6 +17,7 @@ const appStore = useAppStore();
const detailData = ref<Api.RetryLog.RetryLog | null>();
/** 详情页可见状态 */
const { bool: detailVisible, setTrue: openDetail } = useBoolean(false);
const retryStatus = history.state.retryStatus;
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
apiFn: fetchRetryLogPageList,
@ -30,7 +31,11 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
sceneName: null,
idempotentId: null,
bizNo: null,
retryStatus: null
retryStatus: null,
datetimeRange: monthRangeISO8601()
},
searchParams: {
retryStatus
},
columns: () => [
{
@ -40,7 +45,7 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
disabled: row => row.retryStatus !== 1
},
{
key: 'index',
key: 'id',
title: $t('common.index'),
align: 'center',
width: 64
@ -168,16 +173,6 @@ async function loadRetryInfo(row: Api.RetryLog.RetryLog) {
const res = await fetchRetryLogById(row.id!);
detailData.value = (res.data as Api.RetryLog.RetryLog) || null;
}
function initParams() {
const retryStatus = history.state.retryStatus;
if (retryStatus) {
searchParams.retryStatus = retryStatus;
getData();
}
}
initParams();
</script>
<template>

View File

@ -4,6 +4,7 @@ import { translateOptions } from '@/utils/common';
import { retryTaskStatusTypeOptions } from '@/constants/business';
import SelectGroup from '@/components/common/select-group.vue';
import SelectScene from '@/components/common/select-scene.vue';
import DatetimeRange from '@/components/common/datetime-range.vue';
defineOptions({
name: 'RetryLogSearch'
@ -30,19 +31,19 @@ function search() {
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.groupName')" path="groupName" class="pr-24px">
<SelectGroup v-model:value="model.groupName" />
<SelectGroup v-model:value="model.groupName" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.sceneName')" path="sceneName" class="pr-24px">
<SelectScene v-model:value="model.sceneName" :group-name="model.groupName as string" />
<SelectScene v-model:value="model.sceneName" :group-name="model.groupName as string" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.UniqueId')" path="UniqueId" class="pr-24px">
<NInput v-model:value="model.uniqueId" :placeholder="$t('page.retryLog.form.UniqueId')" />
<NInput v-model:value="model.uniqueId" :placeholder="$t('page.retryLog.form.UniqueId')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.idempotentId')" path="idempotentId" class="pr-24px">
<NInput v-model:value="model.idempotentId" :placeholder="$t('page.retryLog.form.idempotentId')" />
<NInput v-model:value="model.idempotentId" :placeholder="$t('page.retryLog.form.idempotentId')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.bizNo')" path="bizNo" class="pr-24px">
<NInput v-model:value="model.bizNo" :placeholder="$t('page.retryLog.form.bizNo')" />
<NInput v-model:value="model.bizNo" :placeholder="$t('page.retryLog.form.bizNo')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryLog.retryStatus')" path="taskBatchStatus" class="pr-24px">
<NSelect
@ -52,6 +53,14 @@ function search() {
clearable
/>
</NFormItemGi>
<NFormItemGi
span="24 s:24 m:15 l:12 xl:9"
:label="$t('page.common.createTime')"
path="datetimeRange"
class="pr-24px"
>
<DatetimeRange v-model:value="model.datetimeRange!" />
</NFormItemGi>
</SearchForm>
</template>

View File

@ -39,6 +39,12 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
align: 'center',
width: 48
},
{
key: 'id',
title: $t('common.index'),
align: 'center',
width: 64
},
{
key: 'sceneName',
title: $t('page.retryScene.sceneName'),

View File

@ -29,10 +29,10 @@ function search() {
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryScene.groupName')" path="groupName" class="pr-24px">
<SelectGroup v-model:value="model.groupName" />
<SelectGroup v-model:value="model.groupName" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryScene.sceneName')" path="sceneName" class="pr-24px">
<NInput v-model:value="model.sceneName" :placeholder="$t('page.retryScene.form.sceneName')" />
<SelectScene v-model:value="model.sceneName" :group-name="model.groupName as string" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryScene.sceneStatus')" path="sceneStatus" class="pr-24px">
<NSelect

View File

@ -45,6 +45,12 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
align: 'center',
width: 48
},
{
key: 'id',
title: $t('common.index'),
align: 'center',
width: 64
},
{
key: 'uniqueId',
title: $t('page.retryTask.uniqueId'),

View File

@ -30,25 +30,26 @@ function search() {
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.groupName')" path="groupName" class="pr-24px">
<SelectGroup v-model:value="model.groupName" />
<SelectGroup v-model:value="model.groupName" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.sceneName')" path="sceneName" class="pr-24px">
<SelectScene v-model:value="model.sceneName" :group-name="model.groupName as string" />
<SelectScene v-model:value="model.sceneName" :group-name="model.groupName as string" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.uniqueId')" path="uniqueId" class="pr-24px">
<NInput v-model:value="model.uniqueId" :placeholder="$t('page.retryTask.form.uniqueId')" />
<NInput v-model:value="model.uniqueId" :placeholder="$t('page.retryTask.form.uniqueId')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.idempotentId')" path="idempotentId" class="pr-24px">
<NInput v-model:value="model.idempotentId" :placeholder="$t('page.retryTask.form.idempotentId')" />
<NInput v-model:value="model.idempotentId" :placeholder="$t('page.retryTask.form.idempotentId')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.bizNo')" path="bizNo" class="pr-24px">
<NInput v-model:value="model.bizNo" :placeholder="$t('page.retryTask.form.bizNo')" />
<NInput v-model:value="model.bizNo" :placeholder="$t('page.retryTask.form.bizNo')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.retryTask.retryStatus')" path="retryStatus" class="pr-24px">
<NSelect
v-model:value="model.retryStatus"
:placeholder="$t('page.retryTask.form.retryStatus')"
:options="translateOptions(retryTaskStatusTypeOptions)"
clearable
/>
</NFormItemGi>
</SearchForm>

View File

@ -57,6 +57,12 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
);
}
},
{
key: 'id',
title: $t('common.index'),
align: 'left',
minWidth: 50
},
{
key: 'username',
title: $t('page.userManager.username'),
@ -93,6 +99,12 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
return <NTag type={tagMap[row.role!]}>{label}</NTag>;
}
},
{
key: 'createDt',
title: $t('common.createDt'),
align: 'left',
minWidth: 50
},
{
key: 'updateDt',
title: $t('common.updateDt'),

View File

@ -26,7 +26,7 @@ function search() {
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.userManager.username')" path="username" class="pr-24px">
<NInput v-model:value="model.username" :placeholder="$t('page.userManager.form.username')" />
<NInput v-model:value="model.username" :placeholder="$t('page.userManager.form.username')" clearable />
</NFormItemGi>
</SearchForm>
</template>

View File

@ -1,20 +1,20 @@
<script setup lang="tsx">
import { NButton, NPopconfirm, NTag } from 'naive-ui';
import { useRoute, useRouter } from 'vue-router';
import { useRouter } from 'vue-router';
import { fetchGetWorkflowBatchList, fetchStopWorkflowBatch } from '@/service/api';
import { $t } from '@/locales';
import { useAppStore } from '@/store/modules/app';
import { useTable, useTableOperate } from '@/hooks/common/table';
import { operationReasonRecord, taskBatchStatusRecord } from '@/constants/business';
import { monthRangeISO8601 } from '@/utils/common';
import WorkflowBatchSearch from './modules/workflow-batch-search.vue';
const router = useRouter();
const route = useRoute();
//
const workflowId =
route.query?.workflowId === undefined ? null : Number.parseInt(route.query?.workflowId as string, 10);
const router = useRouter();
const appStore = useAppStore();
const workflowId = history.state.workflowId;
const workflowName = history.state.workflowName;
const taskBatchStatus = history.state.taskBatchStatus;
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
apiFn: fetchGetWorkflowBatchList,
@ -23,9 +23,16 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
size: 10,
// if you want to use the searchParams in Form, you need to define the following properties, and the value is null
// the value can not be undefined, otherwise the property in Form will not be reactive
workflowId,
workflowId: null,
workflowName: null,
groupName: null,
taskBatchStatus: null
taskBatchStatus: null,
datetimeRange: monthRangeISO8601()
},
searchParams: {
workflowId,
workflowName,
taskBatchStatus
},
columns: () => [
{
@ -155,16 +162,6 @@ async function handleStop(id: string) {
function detail(id: string) {
router.push({ path: '/workflow/form/batch', query: { id } });
}
function initParams() {
const taskBatchStatus = history.state.taskBatchStatus;
if (taskBatchStatus) {
searchParams.taskBatchStatus = taskBatchStatus;
getData();
}
}
initParams();
</script>
<template>

View File

@ -1,8 +1,10 @@
<script setup lang="ts">
import { ref } from 'vue';
import { ref, watch } from 'vue';
import type { SelectOption } from 'naive-ui';
import { $t } from '@/locales';
import SelectGroup from '@/components/common/select-group.vue';
import TaskBatchStatus from '@/components/common/task-batch-status.vue';
import DatetimeRange from '@/components/common/datetime-range.vue';
import { fetchGetWorkflowNameList } from '@/service/api';
@ -14,14 +16,17 @@ interface Emits {
(e: 'reset'): void;
(e: 'search'): void;
}
const noSearchFlag = ref(false);
const emit = defineEmits<Emits>();
/** 列表 */
/** 工作流列表 */
const workflowList = ref<Api.Workflow.Workflow[]>([]);
const model = defineModel<Api.WorkflowBatch.WorkflowBatchSearchParams>('model', { required: true });
const keywords = ref<string>(model.value.workflowName as any);
function reset() {
keywords.value = '';
emit('reset');
}
@ -29,18 +34,42 @@ function search() {
emit('search');
}
async function groupNameUpdate(groupName: string) {
const res = await fetchGetWorkflowNameList({ groupName });
async function keywordsUpdate() {
const res = await fetchGetWorkflowNameList({ keywords: keywords.value, groupName: model.value.groupName });
workflowList.value = res.data as Api.Workflow.Workflow[];
}
groupNameUpdate('');
function handleSelect(value: number) {
model.value.workflowId = value;
}
watch(
() => keywords.value,
(value: string) => {
if (value.length !== 0) {
keywordsUpdate();
} else {
noSearchFlag.value = false;
}
}
);
function translateOptions(options: Api.Workflow.Workflow[]) {
return options.map(option => ({
value: option.id,
label: option.workflowName
}));
}
function renderLabel(option: SelectOption) {
return [option.label as string, `(${option.value})`];
}
</script>
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<SearchForm btn-span="24 s:24 m:9 l:12 xl:15" :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.workflowBatch.groupName')" path="groupName" class="pr-24px">
<SelectGroup v-model:value="model.groupName" @update:value="groupNameUpdate" />
<SelectGroup v-model:value="model.groupName" clearable />
</NFormItemGi>
<NFormItemGi
span="24 s:12 m:6"
@ -49,14 +78,15 @@ groupNameUpdate('');
path="workflowName"
class="pr-24px"
>
<NSelect
v-model:value="model.workflowId"
<NAutoComplete
v-model:value="keywords"
:placeholder="$t('page.workflowBatch.form.workflowName')"
value-field="id"
label-field="workflowName"
:options="workflowList"
:options="translateOptions(workflowList)"
:empty-visible="noSearchFlag"
clearable
filterable
:render-label="renderLabel"
@select="handleSelect"
/>
</NFormItemGi>
<NFormItemGi
@ -65,7 +95,15 @@ groupNameUpdate('');
path="taskBatchStatus"
class="pr-24px"
>
<TaskBatchStatus v-model:value="model.taskBatchStatus" />
<TaskBatchStatus v-model:value="model.taskBatchStatus" clearable />
</NFormItemGi>
<NFormItemGi
span="24 s:24 m:15 l:12 xl:9"
:label="$t('page.common.createTime')"
path="datetimeRange"
class="pr-24px"
>
<DatetimeRange v-model:value="model.datetimeRange!" />
</NFormItemGi>
</SearchForm>
</template>

View File

@ -15,11 +15,13 @@ import StatusSwitch from '@/components/common/status-switch.vue';
import { tagColor } from '@/utils/common';
import { useAuth } from '@/hooks/business/auth';
import { downloadFetch } from '@/utils/download';
import { useRouterPush } from '@/hooks/common/router';
import WorkflowSearch from './modules/workflow-search.vue';
const { hasAuth } = useAuth();
const router = useRouter();
const appStore = useAppStore();
const { routerPushByKey } = useRouterPush();
const { columns, columnChecks, data, getData, loading, mobilePagination, searchParams, resetSearchParams } = useTable({
apiFn: fetchGetWorkflowPageList,
@ -143,7 +145,7 @@ const { columns, columnChecks, data, getData, loading, mobilePagination, searchP
{
label: $t('common.batchList'),
key: 'batchList',
click: () => batch(row.id!)
click: () => goToBatch(row.id!)
},
{
type: 'divider',
@ -241,9 +243,9 @@ function copy(id: string) {
router.push({ path: '/workflow/form/copy', query: { id } });
}
function batch(id: string) {
router.push({ path: '/workflow/batch', query: { workflowId: id } });
}
// function batch(id: string) {
// router.push({ path: '/workflow/batch', state: { workflowId: id } });
// }
async function execute(id: string) {
const { error } = await fetchTriggerWorkflow(id);
@ -265,6 +267,10 @@ function body(): Api.Workflow.ExportWorkflow {
function handleExport() {
downloadFetch('/workflow/export', body(), $t('page.workflow.title'));
}
function goToBatch(workflowId: string) {
const findItem = data.value.find(item => item.id === workflowId)!;
routerPushByKey('workflow_batch', { state: { workflowId, workflowName: findItem.workflowName } });
}
</script>
<template>
@ -324,3 +330,5 @@ function handleExport() {
</NCard>
</div>
</template>
<style scoped></style>

View File

@ -29,7 +29,7 @@ function search() {
<template>
<SearchForm :model="model" @search="search" @reset="reset">
<NFormItemGi span="24 s:12 m:6" :label="$t('page.workflow.groupName')" path="groupName" class="pr-24px">
<SelectGroup v-model:value="model.groupName" />
<SelectGroup v-model:value="model.groupName" clearable />
</NFormItemGi>
<NFormItemGi
span="24 s:12 m:6"
@ -38,13 +38,14 @@ function search() {
class="pr-24px"
:label-width="100"
>
<NInput v-model:value="model.workflowName" :placeholder="$t('page.workflow.form.workflowName')" />
<NInput v-model:value="model.workflowName" :placeholder="$t('page.workflow.form.workflowName')" clearable />
</NFormItemGi>
<NFormItemGi span="24 s:12 m:6" :label="$t('page.workflow.workflowStatus')" path="workflowStatus" class="pr-24px">
<NSelect
v-model:value="model.workflowStatus"
:placeholder="$t('page.workflow.form.workflowStatus')"
:options="translateOptions(enableStatusNumberOptions)"
clearable
/>
</NFormItemGi>
</SearchForm>