From 570f284230cd618eb148fc365f2b467418cc2579 Mon Sep 17 00:00:00 2001 From: dk <1260500659@qq.com> Date: Thu, 25 Jun 2026 21:34:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E6=97=A5=E5=BF=97=E7=AE=A1=E7=90=86):=20?= =?UTF-8?q?=E5=BC=80=E5=8F=91=E6=97=A5=E5=BF=97=E7=AE=A1=E7=90=86=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E3=80=82=20fix(=E9=A1=B9=E7=9B=AE=E4=BB=BB=E5=8A=A1):?= =?UTF-8?q?=201=E3=80=81=E4=BB=BB=E5=8A=A1=E5=AE=8C=E6=88=90=E5=90=8E?= =?UTF-8?q?=E9=9C=80=E8=A6=81=E4=BE=9D=E7=84=B6=E8=83=BD=E5=A4=9F=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E5=B7=A5=E4=BD=9C=E6=97=A5=E5=BF=97=EF=BC=8C=E4=BD=86?= =?UTF-8?q?=E6=98=AF=E5=8F=AA=E8=83=BD=E4=BF=AE=E6=94=B9=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E5=92=8C=E4=B8=8A=E4=BC=A0=E9=99=84=E4=BB=B6?= =?UTF-8?q?=E3=80=822=E3=80=81=E4=BB=BB=E5=8A=A1=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E5=90=8E=EF=BC=8C=E5=8D=8F=E5=8A=9E=E4=BA=BA=E7=9A=84=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=97=A5=E5=BF=97=E4=B8=8D=E5=BA=94=E8=AF=A5=E8=83=BD?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E3=80=81=E6=89=80=E6=9C=89=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E9=87=8C=E7=9A=84=E6=88=90=E5=91=98=E4=B8=8D=E8=83=BD=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E5=B7=A5=E4=BD=9C=E6=97=A5=E5=BF=97=EF=BC=8C=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E4=B8=8D=E6=98=BE=E7=A4=BA=E6=96=B0=E5=A2=9E=E3=80=81?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=8C=89=E9=92=AE=E3=80=823=E3=80=81?= =?UTF-8?q?=E5=9B=A2=E9=98=9F=E6=88=90=E5=91=98=E7=9A=84=E9=9D=A2=E6=9D=BF?= =?UTF-8?q?=EF=BC=8C=E5=9C=A8=E6=88=90=E5=91=98=E6=8E=92=E5=BA=8F=E6=97=B6?= =?UTF-8?q?=EF=BC=8C=E8=AE=A9=E6=9C=89=E4=B8=8B=E5=B1=9E=E7=9A=84=E6=88=90?= =?UTF-8?q?=E5=91=98=E6=8F=90=E5=89=8D=E3=80=824=E3=80=81=E5=9C=A8?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1=E5=BC=B9=E5=87=BA=E6=A1=86=E6=9C=89=E4=B8=AA?= =?UTF-8?q?=E5=BF=AB=E9=80=9F=E7=94=A8=E6=89=A7=E8=A1=8C=E7=9A=84=E4=BF=A1?= =?UTF-8?q?=E6=81=AF=E5=A1=AB=E5=85=85=E7=9A=84icon=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build/plugins/router.ts | 23 +- docs/frontend-page-resource-manifest.json | 131 +++++---- .../custom/business-form-dialog.vue | 6 + .../custom/business-form-section.vue | 4 +- .../custom/subordinate-selector.vue | 39 ++- src/constants/dict.ts | 48 ++++ src/locales/langs/en-us.ts | 5 + src/locales/langs/zh-cn.ts | 5 + src/router/elegant/imports.ts | 5 + src/router/elegant/routes.ts | 60 +++- src/router/elegant/transform.ts | 5 + src/router/guard/title.ts | 2 +- src/service/api/index.ts | 1 + src/service/api/system-log.ts | 260 ++++++++++++++++++ src/store/modules/app/index.ts | 2 +- src/typings/api/system-log.d.ts | 151 ++++++++++ src/typings/elegant-router.d.ts | 10 + .../log-management/api-access-log/index.vue | 238 ++++++++++++++++ .../api-access-log/modules/search.vue | 81 ++++++ .../log-management/api-error-log/index.vue | 230 ++++++++++++++++ .../api-error-log/modules/search.vue | 63 +++++ src/views/infra/log-management/index.vue | 177 ++++++++++++ .../infra/log-management/login-log/index.vue | 235 ++++++++++++++++ .../login-log/modules/search.vue | 60 ++++ .../log-management/operate-log/index.vue | 244 ++++++++++++++++ .../operate-log/modules/search.vue | 61 ++++ src/views/infra/log-management/shared.ts | 123 +++++++++ .../shared/log-detail-dialog.vue | 137 +++++++++ .../modules/performance-action-dialog.vue | 2 +- .../use-task-completion-cascade.ts | 2 +- .../execution/modules/task-operate-dialog.vue | 72 ++++- .../modules/task-worklog-content.vue | 4 +- .../modules/task-worklog-form-dialog.vue | 17 +- .../execution/modules/task-worklog-panel.vue | 2 +- .../execution/modules/task-workspace.vue | 1 + 35 files changed, 2434 insertions(+), 72 deletions(-) create mode 100644 src/service/api/system-log.ts create mode 100644 src/typings/api/system-log.d.ts create mode 100644 src/views/infra/log-management/api-access-log/index.vue create mode 100644 src/views/infra/log-management/api-access-log/modules/search.vue create mode 100644 src/views/infra/log-management/api-error-log/index.vue create mode 100644 src/views/infra/log-management/api-error-log/modules/search.vue create mode 100644 src/views/infra/log-management/index.vue create mode 100644 src/views/infra/log-management/login-log/index.vue create mode 100644 src/views/infra/log-management/login-log/modules/search.vue create mode 100644 src/views/infra/log-management/operate-log/index.vue create mode 100644 src/views/infra/log-management/operate-log/modules/search.vue create mode 100644 src/views/infra/log-management/shared.ts create mode 100644 src/views/infra/log-management/shared/log-detail-dialog.vue diff --git a/build/plugins/router.ts b/build/plugins/router.ts index 8ec0d87..5fd990e 100644 --- a/build/plugins/router.ts +++ b/build/plugins/router.ts @@ -209,9 +209,30 @@ export function setupElegantRouter() { order: 1, keepAlive: true }, + 'infra_log-management': { + icon: 'mdi:text-box-search-outline', + order: 2, + keepAlive: true + }, + 'infra_log-management_login-log': { + hideInMenu: true, + activeMenu: 'infra_log-management' + }, + 'infra_log-management_operate-log': { + hideInMenu: true, + activeMenu: 'infra_log-management' + }, + 'infra_log-management_api-access-log': { + hideInMenu: true, + activeMenu: 'infra_log-management' + }, + 'infra_log-management_api-error-log': { + hideInMenu: true, + activeMenu: 'infra_log-management' + }, 'infra_rd-code': { icon: 'mdi:identifier', - order: 2, + order: 3, keepAlive: true } }; diff --git a/docs/frontend-page-resource-manifest.json b/docs/frontend-page-resource-manifest.json index 6c2877a..706ece0 100644 --- a/docs/frontend-page-resource-manifest.json +++ b/docs/frontend-page-resource-manifest.json @@ -1,12 +1,12 @@ { - "generatedAt": "2026-06-05T03:08:01.803Z", + "generatedAt": "2026-06-25T05:22:43.905Z", "description": "Frontend visible page resource whitelist for backend route/menu configuration.", "rules": { "directoryComponent": "layout.base", "pageComponentPattern": "view.", "singlePageComponentPattern": "layout.$view." }, - "total": 22, + "total": 23, "items": [ { "name": "product_list", @@ -338,6 +338,39 @@ "pageType": "leaf", "source": "generated" }, + { + "name": "personal-center_my-application", + "path": "/personal-center/my-application", + "component": "view.personal-center_my-application", + "title": "我的申请", + "routeTitle": "personal-center_my-application", + "i18nKey": "route.personal-center_my-application", + "icon": "mdi:file-document-outline", + "localIcon": null, + "order": 4, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "我的申请", + "i18nKey": "route.personal-center_my-application", + "icon": "mdi:file-document-outline", + "localIcon": null, + "order": 4, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "personal-center", + "pageType": "leaf", + "source": "generated" + }, { "name": "personal-center_my-performance", "path": "/personal-center/my-performance", @@ -372,15 +405,15 @@ "source": "generated" }, { - "name": "personal-center_my-application", - "path": "/personal-center/my-application", - "component": "view.personal-center_my-application", - "title": "我的申请", - "routeTitle": "personal-center_my-application", - "i18nKey": "route.personal-center_my-application", - "icon": "mdi:file-document-outline", + "name": "personal-center_overtime-application", + "path": "/personal-center/overtime-application", + "component": "view.personal-center_overtime-application", + "title": "加班申请", + "routeTitle": "personal-center_overtime-application", + "i18nKey": "route.personal-center_overtime-application", + "icon": "mdi:clock-plus-outline", "localIcon": null, - "order": 5, + "order": 6, "hideInMenu": false, "keepAlive": true, "activeMenu": null, @@ -389,11 +422,11 @@ "redirect": null, "props": null, "meta": { - "title": "我的申请", - "i18nKey": "route.personal-center_my-application", - "icon": "mdi:file-document-outline", + "title": "加班申请", + "i18nKey": "route.personal-center_overtime-application", + "icon": "mdi:clock-plus-outline", "localIcon": null, - "order": 5, + "order": 6, "keepAlive": true, "hideInMenu": false, "activeMenu": null, @@ -437,39 +470,6 @@ "pageType": "leaf", "source": "generated" }, - { - "name": "personal-center_overtime-application", - "path": "/personal-center/overtime-application", - "component": "view.personal-center_overtime-application", - "title": "加班申请", - "routeTitle": "personal-center_overtime-application", - "i18nKey": "route.personal-center_overtime-application", - "icon": "mdi:clock-plus-outline", - "localIcon": null, - "order": 6, - "hideInMenu": false, - "keepAlive": true, - "activeMenu": null, - "multiTab": false, - "fixedIndexInTab": null, - "redirect": null, - "props": null, - "meta": { - "title": "加班申请", - "i18nKey": "route.personal-center_overtime-application", - "icon": "mdi:clock-plus-outline", - "localIcon": null, - "order": 6, - "keepAlive": true, - "hideInMenu": false, - "activeMenu": null, - "multiTab": false, - "fixedIndexInTab": null - }, - "parentName": "personal-center", - "pageType": "leaf", - "source": "generated" - }, { "name": "system_user", "path": "/system/user", @@ -701,6 +701,39 @@ "pageType": "leaf", "source": "generated" }, + { + "name": "infra_log-management", + "path": "/infra/log-management", + "component": "view.infra_log-management", + "title": "日志管理", + "routeTitle": "infra_log-management", + "i18nKey": "route.infra_log-management", + "icon": "mdi:text-box-search-outline", + "localIcon": null, + "order": 2, + "hideInMenu": false, + "keepAlive": true, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null, + "redirect": null, + "props": null, + "meta": { + "title": "日志管理", + "i18nKey": "route.infra_log-management", + "icon": "mdi:text-box-search-outline", + "localIcon": null, + "order": 2, + "keepAlive": true, + "hideInMenu": false, + "activeMenu": null, + "multiTab": false, + "fixedIndexInTab": null + }, + "parentName": "infra", + "pageType": "leaf", + "source": "generated" + }, { "name": "infra_rd-code", "path": "/infra/rd-code", @@ -710,7 +743,7 @@ "i18nKey": "route.infra_rd-code", "icon": "mdi:identifier", "localIcon": null, - "order": 2, + "order": 3, "hideInMenu": false, "keepAlive": true, "activeMenu": null, @@ -723,7 +756,7 @@ "i18nKey": "route.infra_rd-code", "icon": "mdi:identifier", "localIcon": null, - "order": 2, + "order": 3, "keepAlive": true, "hideInMenu": false, "activeMenu": null, diff --git a/src/components/custom/business-form-dialog.vue b/src/components/custom/business-form-dialog.vue index 8b175ec..9cc58c3 100644 --- a/src/components/custom/business-form-dialog.vue +++ b/src/components/custom/business-form-dialog.vue @@ -82,6 +82,12 @@ function handleConfirm() { footer-class="business-form-dialog__footer" v-bind="$attrs" > + + (); diff --git a/src/components/custom/subordinate-selector.vue b/src/components/custom/subordinate-selector.vue index b8845de..50b62f5 100644 --- a/src/components/custom/subordinate-selector.vue +++ b/src/components/custom/subordinate-selector.vue @@ -1,4 +1,6 @@ + + diff --git a/src/views/infra/log-management/api-access-log/modules/search.vue b/src/views/infra/log-management/api-access-log/modules/search.vue new file mode 100644 index 0000000..335a7fe --- /dev/null +++ b/src/views/infra/log-management/api-access-log/modules/search.vue @@ -0,0 +1,81 @@ + + + diff --git a/src/views/infra/log-management/api-error-log/index.vue b/src/views/infra/log-management/api-error-log/index.vue new file mode 100644 index 0000000..f778f2f --- /dev/null +++ b/src/views/infra/log-management/api-error-log/index.vue @@ -0,0 +1,230 @@ + + + diff --git a/src/views/infra/log-management/api-error-log/modules/search.vue b/src/views/infra/log-management/api-error-log/modules/search.vue new file mode 100644 index 0000000..c74ecb4 --- /dev/null +++ b/src/views/infra/log-management/api-error-log/modules/search.vue @@ -0,0 +1,63 @@ + + + diff --git a/src/views/infra/log-management/index.vue b/src/views/infra/log-management/index.vue new file mode 100644 index 0000000..ef7435e --- /dev/null +++ b/src/views/infra/log-management/index.vue @@ -0,0 +1,177 @@ + + + + + diff --git a/src/views/infra/log-management/login-log/index.vue b/src/views/infra/log-management/login-log/index.vue new file mode 100644 index 0000000..49f2c17 --- /dev/null +++ b/src/views/infra/log-management/login-log/index.vue @@ -0,0 +1,235 @@ + + + diff --git a/src/views/infra/log-management/login-log/modules/search.vue b/src/views/infra/log-management/login-log/modules/search.vue new file mode 100644 index 0000000..a1861e2 --- /dev/null +++ b/src/views/infra/log-management/login-log/modules/search.vue @@ -0,0 +1,60 @@ + + + diff --git a/src/views/infra/log-management/operate-log/index.vue b/src/views/infra/log-management/operate-log/index.vue new file mode 100644 index 0000000..287ccc8 --- /dev/null +++ b/src/views/infra/log-management/operate-log/index.vue @@ -0,0 +1,244 @@ + + + diff --git a/src/views/infra/log-management/operate-log/modules/search.vue b/src/views/infra/log-management/operate-log/modules/search.vue new file mode 100644 index 0000000..c97221c --- /dev/null +++ b/src/views/infra/log-management/operate-log/modules/search.vue @@ -0,0 +1,61 @@ + + + diff --git a/src/views/infra/log-management/shared.ts b/src/views/infra/log-management/shared.ts new file mode 100644 index 0000000..4b520ee --- /dev/null +++ b/src/views/infra/log-management/shared.ts @@ -0,0 +1,123 @@ +import dayjs from 'dayjs'; + +export type LogTabKey = 'login-log' | 'operate-log' | 'api-access-log' | 'api-error-log'; + +export interface LogTabOption { + name: LogTabKey; + label: string; + description: string; + queryPermission: string; + exportPermission: string; +} + +export interface LogDetailField { + label: string; + key?: string; + span?: number; + type?: 'text' | 'datetime' | 'dict' | 'multiline'; + dictCode?: string; + formatter?: (detail: Record) => string; +} + +export interface LogDetailSection { + title: string; + fields: LogDetailField[]; +} + +export const LogPermission = { + LoginQuery: 'system:login-log:query', + LoginExport: 'system:login-log:export', + OperateQuery: 'system:operate-log:query', + OperateExport: 'system:operate-log:export', + ApiAccessQuery: 'system:api-access-log:query', + ApiAccessExport: 'system:api-access-log:export', + ApiErrorQuery: 'system:api-error-log:query', + ApiErrorExport: 'system:api-error-log:export' +} as const; + +export const LOG_TABS: LogTabOption[] = [ + { + name: 'login-log', + label: '登录日志', + description: '查看系统登录行为、登录结果与登录时间。', + queryPermission: LogPermission.LoginQuery, + exportPermission: LogPermission.LoginExport + }, + { + name: 'operate-log', + label: '操作日志', + description: '查看系统操作轨迹、请求地址与业务编号。', + queryPermission: LogPermission.OperateQuery, + exportPermission: LogPermission.OperateExport + }, + { + name: 'api-access-log', + label: 'API访问日志', + description: '查看接口访问结果、执行时长与请求链路。', + queryPermission: LogPermission.ApiAccessQuery, + exportPermission: LogPermission.ApiAccessExport + }, + { + name: 'api-error-log', + label: 'API错误日志', + description: '查看接口异常、处理状态与错误上下文。', + queryPermission: LogPermission.ApiErrorQuery, + exportPermission: LogPermission.ApiErrorExport + } +]; + +export function formatDateTime(value?: string | null) { + if (!value) return '--'; + + const target = dayjs(value); + return target.isValid() ? target.format('YYYY-MM-DD HH:mm:ss') : value; +} + +export function formatText(value: unknown) { + if (value === null || value === undefined || value === '') return '--'; + return String(value); +} + +export function formatDuration(value: unknown) { + if (value === null || value === undefined || value === '') return '--'; + + const duration = Number(value); + return Number.isFinite(duration) ? `${duration} ms` : String(value); +} + +export function formatMultilineText(value: unknown) { + if (value === null || value === undefined || value === '') return '--'; + + const text = String(value); + const normalized = text.trim(); + + if (!normalized) return '--'; + + if ( + (normalized.startsWith('{') && normalized.endsWith('}')) || + (normalized.startsWith('[') && normalized.endsWith(']')) + ) { + try { + return JSON.stringify(JSON.parse(normalized), null, 2); + } catch { + return text; + } + } + + return text; +} + +export function downloadBlob(blob: Blob, fileName: string) { + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + + link.href = url; + link.download = fileName; + link.click(); + + URL.revokeObjectURL(url); +} + +export function getLogExportFileName(label: string) { + return `${label}_${dayjs().format('YYYY-MM-DD')}.xls`; +} diff --git a/src/views/infra/log-management/shared/log-detail-dialog.vue b/src/views/infra/log-management/shared/log-detail-dialog.vue new file mode 100644 index 0000000..fd4a3af --- /dev/null +++ b/src/views/infra/log-management/shared/log-detail-dialog.vue @@ -0,0 +1,137 @@ + + + + + diff --git a/src/views/personal-center/my-performance/modules/performance-action-dialog.vue b/src/views/personal-center/my-performance/modules/performance-action-dialog.vue index d7d4ab2..2cfb31d 100644 --- a/src/views/personal-center/my-performance/modules/performance-action-dialog.vue +++ b/src/views/personal-center/my-performance/modules/performance-action-dialog.vue @@ -98,7 +98,7 @@ watch(visible, isVisible => { {{ props.rowData?.periodMonth || '--' }} - {{ props.rowData?.employeeName || '--' }} + {{ props.rowData?.employeeName || '--' }} {{ props.rowData?.actualScoreTotal ?? '--' }} diff --git a/src/views/project/project/execution/composables/use-task-completion-cascade.ts b/src/views/project/project/execution/composables/use-task-completion-cascade.ts index 36896ad..1ba7d8a 100644 --- a/src/views/project/project/execution/composables/use-task-completion-cascade.ts +++ b/src/views/project/project/execution/composables/use-task-completion-cascade.ts @@ -44,7 +44,7 @@ export function useTaskCompletionCascade(options: UseTaskCompletionCascadeOption const { task } = payload; const completeAction = options.resolveCompleteAction(task); if (!completeAction) { - window.$message?.warning('当前任务暂无可用完成动作'); + // window.$message?.warning('当前任务暂无可用完成动作'); return; } diff --git a/src/views/project/project/execution/modules/task-operate-dialog.vue b/src/views/project/project/execution/modules/task-operate-dialog.vue index 60a93d3..c9cc5f7 100644 --- a/src/views/project/project/execution/modules/task-operate-dialog.vue +++ b/src/views/project/project/execution/modules/task-operate-dialog.vue @@ -11,6 +11,7 @@ import BusinessRichTextEditor from '@/components/custom/business-rich-text-edito import BusinessUserSelect from '@/components/custom/business-user-select.vue'; import DictSelect from '@/components/custom/dict-select.vue'; import { SHOW_TASK_PARENT_FIELD } from '../shared'; +import IconMdiAutoFix from '~icons/mdi/auto-fix'; defineOptions({ name: 'ProjectExecutionTaskOperateDialog' }); @@ -26,6 +27,7 @@ export interface PlannedEndShortcutOffset { interface Props { mode: OperateMode; rowData: Api.Project.ProjectTask | null; + executionData?: Api.Project.ProjectExecution | null; /** 创建模式下的父任务预填;编辑/查看模式忽略 */ defaultParentTaskId?: string | null; userOptions: Api.SystemManage.UserSimple[]; @@ -40,6 +42,7 @@ interface Emits { } const props = withDefaults(defineProps(), { + executionData: null, defaultParentTaskId: null, canManageAssignee: false, plannedEndShortcuts: () => [ @@ -95,6 +98,8 @@ const dialogTitle = computed(() => { return props.rowData?.taskTitle ? `编辑任务:${props.rowData.taskTitle}` : '编辑任务'; }); +const canFillFromExecution = computed(() => props.mode === 'create' && Boolean(props.executionData)); + const selectableParentTasks = computed(() => props.taskOptions.filter(item => item.id !== props.rowData?.id)); /** 编辑态 + 任务已开始(statusCode 离开 pending)→ 负责人锁定不可切换 */ @@ -108,11 +113,17 @@ const ownerLocked = computed(() => { * owner 可能已不在执行协办人池里,用任务自带 ownerNickname 兜底回显,避免锁定态显示成裸 userId。 */ const ownerSelectOptions = computed(() => { - const ownerId = props.rowData?.ownerId; - if (props.mode === 'create' || !ownerId || props.userOptions.some(item => item.id === ownerId)) { + const currentOwnerId = props.mode === 'create' ? model.ownerId : (props.rowData?.ownerId ?? null); + if (!currentOwnerId || props.userOptions.some(item => item.id === currentOwnerId)) { return props.userOptions; } - return [...props.userOptions, { id: ownerId, nickname: props.rowData?.ownerNickname || ownerId }]; + + const fallbackNickname = + props.mode === 'create' + ? props.executionData?.ownerNickname || currentOwnerId + : props.rowData?.ownerNickname || currentOwnerId; + + return [...props.userOptions, { id: currentOwnerId, nickname: fallbackNickname }]; }); /** 编辑态无协办人管理权限时只读回显(create 恒可交互) */ @@ -129,7 +140,13 @@ const assigneeSelectOptions = computed(() => { const options = props.userOptions.map(item => ({ id: item.id, nickname: item.nickname })); const known = new Set(options.map(item => item.id)); - if (props.mode !== 'create' && props.rowData) { + if (props.mode === 'create') { + const ownerId = model.ownerId; + if (ownerId && !known.has(ownerId)) { + options.push({ id: ownerId, nickname: props.executionData?.ownerNickname || ownerId }); + known.add(ownerId); + } + } else if (props.rowData) { props.rowData.assignees?.forEach(assignee => { if (assignee.userId && !known.has(assignee.userId)) { options.push({ id: assignee.userId, nickname: assignee.nickname || assignee.userId }); @@ -311,6 +328,23 @@ function handleAssigneeChange(value: string[]) { model.assigneeUserIds = cleaned; } +function fillFromExecution() { + const execution = props.executionData; + if (!execution) return; + + model.taskTitle = execution.executionName || ''; + model.ownerId = execution.ownerId || null; + model.plannedStartDate = execution.plannedStartDate || null; + model.plannedEndDate = execution.plannedEndDate || null; + model.priority = execution.priority || '3'; + model.taskDesc = execution.executionDesc || null; + model.assigneeUserIds = props.userOptions.map(item => item.id); + + nextTick(() => { + formRef.value?.clearValidate(['taskTitle', 'ownerId', 'plannedStartDate', 'plannedEndDate', 'priority']); + }); +} + function applyBasicFieldsFromRow(row: Api.Project.ProjectTask | null) { model.taskTitle = row?.taskTitle || ''; model.type = row?.type || ''; @@ -386,6 +420,19 @@ defineExpose({
+ + @@ -526,6 +573,23 @@ defineExpose({