Browse Source

feat(tasks): 增加作业详情页.

pull/482/head
cKey 4 years ago
parent
commit
ee1aa235b2
  1. 2
      apps/vue/src/locales/lang/en/routes/basic.ts
  2. 2
      apps/vue/src/locales/lang/zh-CN/routes/basic.ts
  3. 35
      apps/vue/src/router/routes/basic.ts
  4. 3
      apps/vue/src/router/routes/index.ts
  5. 2
      apps/vue/src/views/platform/dataDic/components/DataItemTable.vue
  6. 102
      apps/vue/src/views/platform/dataDic/components/DataTree.vue
  7. 10
      apps/vue/src/views/platform/menu/hooks/useMenuFormContext.ts
  8. 120
      apps/vue/src/views/task-management/background-jobs/components/BackgroundJobInfoDetail.vue
  9. 9
      apps/vue/src/views/task-management/background-jobs/components/BackgroundJobInfoTable.vue
  10. 88
      apps/vue/src/views/task-management/background-jobs/datas/DescriptionData.ts
  11. 56
      apps/vue/src/views/task-management/background-jobs/datas/TableData.ts
  12. 2
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/en.json
  13. 2
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/zh-Hans.json
  14. 2
      aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobLogRepository.cs

2
apps/vue/src/locales/lang/en/routes/basic.ts

@ -1,4 +1,6 @@
export default { export default {
login: 'Login', login: 'Login',
errorLogList: 'Error Log', errorLogList: 'Error Log',
accountSetting: 'Account Setting',
accountCenter: 'Account Center'
}; };

2
apps/vue/src/locales/lang/zh-CN/routes/basic.ts

@ -1,4 +1,6 @@
export default { export default {
login: '登录', login: '登录',
errorLogList: '错误日志列表', errorLogList: '错误日志列表',
accountSetting: '个人设置',
accountCenter: '个人中心'
}; };

35
apps/vue/src/router/routes/basic.ts

@ -76,3 +76,38 @@ export const ERROR_LOG_ROUTE: AppRouteRecordRaw = {
}, },
], ],
}; };
export const ACCOUNT_CENTER_ROUTE: AppRouteRecordRaw = {
path: '/account',
name: 'Account',
component: LAYOUT,
redirect: '/account/center',
meta: {
title: 'Account',
hideMenu: true,
ignoreAuth: true,
},
children: [
{
path: 'settings',
name: 'ASettings',
component: () => import('/@/views/account/setting/index.vue'),
meta: {
title: t('routes.basic.accountSetting'),
hideMenu: true,
ignoreAuth: true,
},
},
{
path: 'center',
name: 'ACenter',
component: () => import('/@/views/account/center/index.vue'),
meta: {
title: t('routes.basic.accountCenter'),
hideMenu: true,
ignoreAuth: true,
},
},
],
};

3
apps/vue/src/router/routes/index.ts

@ -1,6 +1,6 @@
import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types'; import type { AppRouteRecordRaw, AppRouteModule } from '/@/router/types';
import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE } from '/@/router/routes/basic'; import { PAGE_NOT_FOUND_ROUTE, REDIRECT_ROUTE, ACCOUNT_CENTER_ROUTE } from '/@/router/routes/basic';
import { mainOutRoutes } from './mainOut'; import { mainOutRoutes } from './mainOut';
import { PageEnum } from '/@/enums/pageEnum'; import { PageEnum } from '/@/enums/pageEnum';
@ -40,6 +40,7 @@ export const LoginRoute: AppRouteRecordRaw = {
export const basicRoutes = [ export const basicRoutes = [
LoginRoute, LoginRoute,
RootRoute, RootRoute,
ACCOUNT_CENTER_ROUTE,
...mainOutRoutes, ...mainOutRoutes,
REDIRECT_ROUTE, REDIRECT_ROUTE,
PAGE_NOT_FOUND_ROUTE, PAGE_NOT_FOUND_ROUTE,

2
apps/vue/src/views/platform/dataDic/components/DataItemTable.vue

@ -104,7 +104,7 @@
}); });
}, },
handleAppendItem() { handleAppendItem() {
this.openModal(true, {} as DataItem, true); this.openModal(true, {}, true);
}, },
handleEdit(record: Recordable) { handleEdit(record: Recordable) {
// , // ,

102
apps/vue/src/views/platform/dataDic/components/DataTree.vue

@ -23,7 +23,7 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, ref } from 'vue'; import { defineComponent, ref, onMounted } from 'vue';
import { useLocalization } from '/@/hooks/abp/useLocalization'; import { useLocalization } from '/@/hooks/abp/useLocalization';
import { Modal, Card } from 'ant-design-vue'; import { Modal, Card } from 'ant-design-vue';
@ -47,7 +47,7 @@
DataItemModal, DataItemModal,
}, },
emits: ['change', 'append-item'], emits: ['change', 'append-item'],
setup() { setup(_, { emit }) {
const { L } = useLocalization('AppPlatform'); const { L } = useLocalization('AppPlatform');
const title = L('DisplayName:DataDictionary'); const title = L('DisplayName:DataDictionary');
const treeData = ref<Data[]>(); const treeData = ref<Data[]>();
@ -62,62 +62,45 @@
const formTitle = ref(''); const formTitle = ref('');
const getDataId = ref(''); const getDataId = ref('');
return { onMounted(onLoadAllDataDic);
L,
formItems, function getContentMenus(node: any): ContextMenuItem[] {
formTitle,
title,
getDataId,
treeData,
replaceFields,
registerDataModal,
openDataModal,
closeDataModal,
registerItemModal,
openItemModal,
};
},
mounted() {
this.onLoadAllDataDic();
},
methods: {
getContentMenus(node: any): ContextMenuItem[] {
return [ return [
{ {
label: this.L('Data:Edit'), label: L('Data:Edit'),
handler: () => { handler: () => {
get(node.eventKey).then((res) => { get(node.eventKey).then((res) => {
this.formTitle = this.L('Data:Edit'); formTitle.value = L('Data:Edit');
this.openDataModal(true, res, true); openDataModal(true, res, true);
}); });
}, },
icon: 'ant-design:edit-outlined', icon: 'ant-design:edit-outlined',
}, },
{ {
label: this.L('Data:AddNew'), label: L('Data:AddNew'),
handler: () => { handler: () => {
this.handleNewData(node.eventKey); handleNewData(node.eventKey);
}, },
icon: 'ant-design:plus-outlined', icon: 'ant-design:plus-outlined',
}, },
{ {
label: this.L('Data:AppendItem'), label: L('Data:AppendItem'),
handler: () => { handler: () => {
this.getDataId = node.eventKey; getDataId.value = node.eventKey;
this.openItemModal(true, {}, true); openItemModal(true, {}, true);
}, },
icon: 'ant-design:plus-square-outlined', icon: 'ant-design:plus-square-outlined',
}, },
{ {
label: this.L('Data:Delete'), label: L('Data:Delete'),
handler: () => { handler: () => {
Modal.warning({ Modal.warning({
title: this.t('AbpUi.AreYouSure'), title: L('AbpUi.AreYouSure'),
content: this.t('AbpUi.ItemWillBeDeletedMessage'), content: L('AbpUi.ItemWillBeDeletedMessage'),
okCancel: true, okCancel: true,
onOk: () => { onOk: () => {
remove(node.eventKey).then(() => { remove(node.eventKey).then(() => {
this.onLoadAllDataDic(); onLoadAllDataDic();
}); });
}, },
}); });
@ -125,30 +108,34 @@
icon: 'ant-design:delete-outlined', icon: 'ant-design:delete-outlined',
}, },
]; ];
}, }
onLoadAllDataDic() {
function onLoadAllDataDic() {
getAll().then((res) => { getAll().then((res) => {
this.treeData = listToTree(res.items, { treeData.value = listToTree(res.items, {
pid: 'parentId', pid: 'parentId',
}); });
}); });
}, }
handleNodeChange(selectKeys: String[]) {
function handleNodeChange(selectKeys: String[]) {
if (selectKeys.length > 0) { if (selectKeys.length > 0) {
this.$emit('change', selectKeys[0]); emit('change', selectKeys[0]);
} }
}, }
handleNewData(parentId: string | null) {
this.formTitle = this.L('Data:AddNew'); function handleNewData(parentId: string | null) {
this.openDataModal( formTitle.value = L('Data:AddNew');
openDataModal(
true, true,
{ {
parentId: parentId, parentId: parentId,
} as Data, } as Data,
true, true,
); );
}, }
handleSaveChanges(val) {
function handleSaveChanges(val) {
const api: Promise<Data> = val.id const api: Promise<Data> = val.id
? update(val.id, { ? update(val.id, {
name: val.name, name: val.name,
@ -162,9 +149,28 @@
parentId: val.parentId, parentId: val.parentId,
}); });
return api.then(() => { return api.then(() => {
this.onLoadAllDataDic(); onLoadAllDataDic();
}); });
}, }
return {
L,
formItems,
formTitle,
title,
getDataId,
treeData,
replaceFields,
registerDataModal,
openDataModal,
closeDataModal,
registerItemModal,
openItemModal,
getContentMenus,
handleNewData,
handleNodeChange,
handleSaveChanges,
};
}, },
}); });
</script> </script>

10
apps/vue/src/views/platform/menu/hooks/useMenuFormContext.ts

@ -23,7 +23,8 @@ export function useMenuFormContext({ menuModel, formElRef }: UseMenuFormContext)
const { L } = useLocalization('AppPlatform'); const { L } = useLocalization('AppPlatform');
function getMetaFormSchemas(meta: DataItem[]): TabFormSchema[] { function getMetaFormSchemas(meta: DataItem[]): TabFormSchema[] {
return meta.map((item) => { return meta.sort((pre, next) => pre.name.localeCompare(next.name))
.map((item) => {
const schema: TabFormSchema = { const schema: TabFormSchema = {
tab: L('DisplayName:Meta'), tab: L('DisplayName:Meta'),
field: 'meta.'.concat(item.name), field: 'meta.'.concat(item.name),
@ -31,6 +32,11 @@ export function useMenuFormContext({ menuModel, formElRef }: UseMenuFormContext)
colProps: { span: 24 }, colProps: { span: 24 },
required: !item.allowBeNull, required: !item.allowBeNull,
component: 'Input', component: 'Input',
componentProps: {
style: {
width: '100%',
},
}
}; };
switch (item.valueType) { switch (item.valueType) {
case ValueType.Boolean: case ValueType.Boolean:
@ -46,7 +52,7 @@ export function useMenuFormContext({ menuModel, formElRef }: UseMenuFormContext)
onChange: (e: ChangeEvent) => { onChange: (e: ChangeEvent) => {
model[field] = e.target.checked.toString(); model[field] = e.target.checked.toString();
}, },
}); }, () => item.displayName);
}; };
break; break;
case ValueType.Date: case ValueType.Date:

120
apps/vue/src/views/task-management/background-jobs/components/BackgroundJobInfoDetail.vue

@ -0,0 +1,120 @@
<template>
<PageWrapper>
<template #title>
<ArrowLeftOutlined @click="handleBack" />
作业详情
</template>
<Skeleton :loading="jobInfo === undefined">
<Card class="mt-4" :title="L('BasicInfo')">
<Description @register="registerDescription" :data="jobInfo" />
</Card>
<Card class="mt-4" :title="L('BackgroundJobLogs')">
<List
ref="logListElRef"
item-layout="vertical"
size="default"
bordered
:loading="fetchingLog"
:pagination="{
pageSize: fetchLogCount,
total: maxLogCount,
showSizeChanger: true,
onChange: fetchJobLogs,
onShowSizeChange: handleSizeChange,
}"
:data-source="jobLogs"
>
<template #renderItem="{ item }">
<ListItem :key="item.id">
<ListItemMeta :description="item.message">
<template #avatar>
<Icon v-if="!item.exception" :size="40" icon="grommet-icons:status-good" color="seagreen" />
<Icon v-else :size="40" icon="grommet-icons:status-warning" color="orangered" />
</template>
<template #title>
<span>{{ item.runTime }}</span>
</template>
</ListItemMeta>
{{ item.exception ?? item.message }}
</ListItem>
</template>
</List>
</Card>
</Skeleton>
</PageWrapper>
</template>
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { Card, List, Skeleton } from 'ant-design-vue';
import { ArrowLeftOutlined } from '@ant-design/icons-vue';
import { Icon } from '/@/components/Icon';
import { PageWrapper } from '/@/components/Page';
import { Description, useDescription } from '/@/components/Description';
import { useRoute, useRouter } from 'vue-router';
import { useLocalization } from '/@/hooks/abp/useLocalization';
import { formatPagedRequest } from '/@/utils/http/abp/helper';
import { getById } from '/@/api/task-management/backgroundJobInfo';
import { getList as getJobLogs } from '/@/api/task-management/backgroundJobLog';
import { BackgroundJobInfo } from '/@/api/task-management/model/backgroundJobInfoModel';
import { BackgroundJobLog } from '/@/api/task-management/model/backgroundJobLogModel';
import { getDescriptionSchemas } from '../datas/DescriptionData';
const ListItem = List.Item;
const ListItemMeta = List.Item.Meta;
const { L } = useLocalization('TaskManagement');
const { back } = useRouter();
const route = useRoute();
const maxLogCount = ref(0);
const fetchLogCount = ref(10);
const fetchingLog = ref(false);
const logListElRef = ref<any>();
const jobInfo = ref<BackgroundJobInfo>();
const jobLogs = ref<BackgroundJobLog[]>([]);
const [registerDescription] = useDescription({
bordered: true,
column: 3,
schema: getDescriptionSchemas(),
});
onMounted(fetchJob);
function fetchJob() {
getById(String(route.params.id)).then((res) => {
jobInfo.value = res;
jobLogs.value = [];
fetchJobLogs(1);
});
}
function fetchJobLogs(page: number) {
const request = {
skipCount: page,
maxResultCount: fetchLogCount.value,
};
formatPagedRequest(request);
fetchingLog.value = true;
getJobLogs({
jobId: String(route.params.id),
sorting: '',
skipCount: request.skipCount,
maxResultCount: request.maxResultCount,
}).then((res) => {
jobLogs.value = res.items;
maxLogCount.value = res.totalCount;
}).finally(() => {
fetchingLog.value = false;
});
}
function handleSizeChange(current: number, size: number) {
fetchLogCount.value = size;
fetchJobLogs(current);
}
function handleBack() {
back();
}
</script>

9
apps/vue/src/views/task-management/background-jobs/components/BackgroundJobInfoTable.vue

@ -26,6 +26,9 @@
<template #enable="{ record }"> <template #enable="{ record }">
<Switch :checked="record.isEnabled" disabled /> <Switch :checked="record.isEnabled" disabled />
</template> </template>
<template #name="{ record }">
<a href="javascript:(0);" @click="handleDetail(record)">{{ record.name }}</a>
</template>
<template #status="{ record }"> <template #status="{ record }">
<Tooltip v-if="record.isAbandoned" color="orange" :title="L('Description:IsAbandoned')"> <Tooltip v-if="record.isAbandoned" color="orange" :title="L('Description:IsAbandoned')">
<Tag :color="JobStatusColor[record.status]">{{ JobStatusMap[record.status] }}</Tag> <Tag :color="JobStatusColor[record.status]">{{ JobStatusMap[record.status] }}</Tag>
@ -88,6 +91,7 @@
import { Switch, Modal, Tag, Tooltip, message } from 'ant-design-vue'; import { Switch, Modal, Tag, Tooltip, message } from 'ant-design-vue';
import { useLocalization } from '/@/hooks/abp/useLocalization'; import { useLocalization } from '/@/hooks/abp/useLocalization';
import { usePermission } from '/@/hooks/web/usePermission'; import { usePermission } from '/@/hooks/web/usePermission';
import { useGo } from '/@/hooks/web/usePage';
import { useModal } from '/@/components/Modal'; import { useModal } from '/@/components/Modal';
import { BasicTable, TableAction, useTable } from '/@/components/Table'; import { BasicTable, TableAction, useTable } from '/@/components/Table';
import { formatPagedRequest } from '/@/utils/http/abp/helper'; import { formatPagedRequest } from '/@/utils/http/abp/helper';
@ -98,6 +102,7 @@
import { JobStatusMap, JobStatusColor, JobTypeMap, JobPriorityMap, JobPriorityColor } from '../datas/typing'; import { JobStatusMap, JobStatusColor, JobTypeMap, JobPriorityMap, JobPriorityColor } from '../datas/typing';
import BackgroundJobInfoModal from './BackgroundJobInfoModal.vue'; import BackgroundJobInfoModal from './BackgroundJobInfoModal.vue';
const go = useGo();
const { L } = useLocalization('TaskManagement'); const { L } = useLocalization('TaskManagement');
const { hasPermission } = usePermission(); const { hasPermission } = usePermission();
const [registerModal, { openModal }] = useModal(); const [registerModal, { openModal }] = useModal();
@ -149,6 +154,10 @@
openModal(true, record); openModal(true, record);
} }
function handleDetail(record) {
go(`/task-management/background-jobs/${record.id}`);
}
function handlePause(record) { function handlePause(record) {
pause(record.id).then(() => { pause(record.id).then(() => {
message.success(L('Successful')); message.success(L('Successful'));

88
apps/vue/src/views/task-management/background-jobs/datas/DescriptionData.ts

@ -0,0 +1,88 @@
import { DescItem } from "/@/components/Description";
import { useLocalization } from '/@/hooks/abp/useLocalization';
const { L } = useLocalization('TaskManagement');
export function getDescriptionSchemas() : DescItem[] {
return [
{
label: L('DisplayName:Group'),
field: 'group',
},
{
label: L('DisplayName:Name'),
field: 'name',
},
{
label: L('DisplayName:Type'),
field: 'type',
},
{
label: L('DisplayName:CreationTime'),
field: 'creationTime',
},
{
label: L('DisplayName:BeginTime'),
field: 'beginTime',
},
{
label: L('DisplayName:EndTime'),
field: 'endTime',
},
{
label: L('DisplayName:LockTimeOut'),
field: 'lockTimeOut',
span: 1,
},
{
label: L('DisplayName:Description'),
field: 'description',
span: 2,
},
{
label: L('DisplayName:LastRunTime'),
field: 'lastRunTime',
span: 1.5,
},
{
label: L('DisplayName:NextRunTime'),
field: 'nextRunTime',
span: 1.5,
},
{
label: L('DisplayName:Status'),
field: 'status',
span: 1,
},
{
label: L('DisplayName:JobType'),
field: 'jobType',
span: 1,
},
{
label: L('DisplayName:Priority'),
field: 'priority',
span: 1,
},
{
label: L('DisplayName:TriggerCount'),
field: 'triggerCount',
span: 0.75,
},
{
label: L('DisplayName:MaxCount'),
field: 'maxCount',
span: 0.75,
},
{
label: L('DisplayName:TryCount'),
field: 'tryCount',
span: 0.75,
},
{
label: L('DisplayName:MaxTryCount'),
field: 'maxTryCount',
span: 0.75,
},
];
}

56
apps/vue/src/views/task-management/background-jobs/datas/TableData.ts

@ -27,6 +27,9 @@ export function getDataColumns(): BasicColumn[] {
width: 300, width: 300,
sorter: true, sorter: true,
fixed: 'left', fixed: 'left',
slots: {
customRender: 'name',
}
}, },
{ {
title: L('DisplayName:Description'), title: L('DisplayName:Description'),
@ -125,3 +128,56 @@ export function getDataColumns(): BasicColumn[] {
}, },
]; ];
} }
export function getLogDataColumns(): BasicColumn[] {
return [
{
title: 'id',
dataIndex: 'id',
width: 1,
ifShow: false,
},
{
title: L('DisplayName:Group'),
dataIndex: 'jobGroup',
align: 'left',
width: 150,
sorter: true,
},
{
title: L('DisplayName:Name'),
dataIndex: 'jobName',
align: 'left',
width: 150,
sorter: true,
},
{
title: L('DisplayName:Type'),
dataIndex: 'jobType',
align: 'left',
width: 150,
sorter: true,
},
{
title: L('DisplayName:LastRunTime'),
dataIndex: 'runTime',
align: 'left',
width: 150,
sorter: true,
},
{
title: L('DisplayName:Result'),
dataIndex: 'message',
align: 'left',
width: 150,
sorter: true,
},
{
title: L('DisplayName:Exception'),
dataIndex: 'exception',
align: 'left',
width: 150,
sorter: true,
},
];
}

2
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/en.json

@ -67,6 +67,8 @@
"DisplayName:AboveNormal": "Above Normal", "DisplayName:AboveNormal": "Above Normal",
"DisplayName:High": "High", "DisplayName:High": "High",
"BackgroundJobs": "Jobs", "BackgroundJobs": "Jobs",
"BackgroundJobDetail": "Job Detail",
"BackgroundJobLogs": "Job Logs",
"BackgroundJobs:AddNew": "New Job", "BackgroundJobs:AddNew": "New Job",
"BackgroundJobs:Edit": "Edit Job", "BackgroundJobs:Edit": "Edit Job",
"BackgroundJobs:AddNewArg": "New Args", "BackgroundJobs:AddNewArg": "New Args",

2
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.Domain.Shared/LINGYUN/Abp/TaskManagement/Localization/Resources/zh-Hans.json

@ -67,6 +67,8 @@
"DisplayName:AboveNormal": "高于正常", "DisplayName:AboveNormal": "高于正常",
"DisplayName:High": "高", "DisplayName:High": "高",
"BackgroundJobs": "作业列表", "BackgroundJobs": "作业列表",
"BackgroundJobDetail": "作业详情",
"BackgroundJobLogs": "作业日志",
"BackgroundJobs:AddNew": "新建作业", "BackgroundJobs:AddNew": "新建作业",
"BackgroundJobs:Edit": "编辑作业", "BackgroundJobs:Edit": "编辑作业",
"BackgroundJobs:AddNewArg": "添加参数", "BackgroundJobs:AddNewArg": "添加参数",

2
aspnet-core/modules/task-management/LINGYUN.Abp.TaskManagement.EntityFrameworkCore/LINGYUN/Abp/TaskManagement/EntityFrameworkCore/EfCoreBackgroundJobLogRepository.cs

@ -56,7 +56,7 @@ public class EfCoreBackgroundJobLogRepository :
.WhereIf(filter.HasExceptions.HasValue, x => !string.IsNullOrWhiteSpace(x.Exception)) .WhereIf(filter.HasExceptions.HasValue, x => !string.IsNullOrWhiteSpace(x.Exception))
.WhereIf(filter.BeginRunTime.HasValue, x => x.RunTime.CompareTo(filter.BeginRunTime.Value) >= 0) .WhereIf(filter.BeginRunTime.HasValue, x => x.RunTime.CompareTo(filter.BeginRunTime.Value) >= 0)
.WhereIf(filter.EndRunTime.HasValue, x => x.RunTime.CompareTo(filter.EndRunTime.Value) <= 0) .WhereIf(filter.EndRunTime.HasValue, x => x.RunTime.CompareTo(filter.EndRunTime.Value) <= 0)
.OrderBy(sorting ?? nameof(BackgroundJobInfo.Name)) .OrderBy(sorting ?? $"{nameof(BackgroundJobLog.RunTime)} DESC")
.PageBy(skipCount, maxResultCount) .PageBy(skipCount, maxResultCount)
.ToListAsync(GetCancellationToken(cancellationToken)); .ToListAsync(GetCancellationToken(cancellationToken));
} }

Loading…
Cancel
Save