From 3ffdad142d5643159f6514b305a69b758237cad8 Mon Sep 17 00:00:00 2001 From: dk <1260500659@qq.com> Date: Wed, 24 Jun 2026 18:02:36 +0800 Subject: [PATCH] =?UTF-8?q?fix(=E4=BA=A7=E5=93=81=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E3=80=81=E9=A1=B9=E7=9B=AE=E9=9C=80=E6=B1=82=E3=80=81=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=8A=A5=E5=91=8A=E3=80=81=E6=88=91=E7=9A=84=E7=BB=A9?= =?UTF-8?q?=E6=95=88=E3=80=81=E5=8A=A0=E7=8F=AD=E7=94=B3=E8=AF=B7):=201?= =?UTF-8?q?=E3=80=81=E4=BF=AE=E5=A4=8D=E6=90=9C=E7=B4=A2=E5=8C=BA=E5=9F=9F?= =?UTF-8?q?=E7=9A=84=E4=B8=8B=E6=8B=89=E6=A1=86=EF=BC=8C=E6=97=A0=E6=B3=95?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=822?= =?UTF-8?q?=E3=80=81=E4=BF=AE=E5=A4=8D=E5=B7=A5=E4=BD=9C=E6=8A=A5=E5=91=8A?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E6=BA=A2=E5=87=BA=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E3=80=823=E3=80=81=E4=BF=AE=E5=A4=8D=E6=88=91=E7=9A=84?= =?UTF-8?q?=E5=BE=85=E5=8A=9E=20-=20=E5=BE=85=E5=AE=A1=E6=89=B9=EF=BC=8C?= =?UTF-8?q?=E9=87=8C=E9=9D=A2=E7=9A=84=E4=BA=8C=E7=BA=A7tabs=E6=B2=A1?= =?UTF-8?q?=E6=9C=89=E5=8A=A0=E6=9D=83=E9=99=90=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=E3=80=824=E3=80=81=E4=BC=98=E5=8C=96=E5=91=A8=E6=8A=A5?= =?UTF-8?q?=E9=87=8C=E5=B7=A5=E4=BD=9C=E6=97=A5=E5=BF=97=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E6=97=B6=E7=9A=84=E5=88=86=E9=9A=94=E7=AC=A6=E3=80=825?= =?UTF-8?q?=E3=80=81=E4=BC=98=E5=8C=96=E9=A1=B9=E7=9B=AE=E9=9C=80=E6=B1=82?= =?UTF-8?q?=E6=B1=A0=E3=80=81=E6=88=91=E7=9A=84=E7=BB=A9=E6=95=88=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E9=87=8D=E5=A4=8D=E5=8F=91=E9=80=81=E3=80=81=E5=BD=B1?= =?UTF-8?q?=E5=93=8D=E6=95=88=E7=8E=87=E7=9A=84=E9=97=AE=E9=A2=98=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit feat(我的绩效): 1、增加我的绩效在工作台可以直接处理的功能。2、增加我的绩效全部导出时,合并多个excel的sheet为一个excel的功能。 --- src/components/custom/table-search-fields.vue | 6 + .../personal-center/my-performance/index.vue | 21 ++- .../modules/performance-action-dialog.vue | 137 +++++++++++++- .../performance-excel-editor-drawer.vue | 22 ++- .../modules/performance-search.vue | 9 +- .../modules/performance-template-dialog.vue | 2 +- .../modules/overtime-application-search.vue | 1 + .../work-report/monthly/modules/fill-page.vue | 56 +++++- .../shared/components/create-dialog.vue | 17 +- .../shared/components/search-panel.vue | 5 +- .../work-report/weekly/modules/fill-page.vue | 163 ++++++++++++++--- .../list/modules/product-grouped-table.vue | 2 +- .../modules/requirement-search.vue | 1 + .../project/project/requirement/index.vue | 12 +- .../modules/requirement-search.vue | 27 +-- .../modules/workbench-todo-panel.vue | 169 +++++++++++++++++- 16 files changed, 569 insertions(+), 81 deletions(-) diff --git a/src/components/custom/table-search-fields.vue b/src/components/custom/table-search-fields.vue index 561711c..1a2fa6d 100644 --- a/src/components/custom/table-search-fields.vue +++ b/src/components/custom/table-search-fields.vue @@ -40,6 +40,8 @@ export interface SearchField { placeholder?: string; /** select 类型的自定义选项渲染函数 */ renderOption?: (option: Option) => VNode | VNode[] | string; + /** select/dict 类型是否支持搜索 */ + filterable?: boolean; /** 值写回模型前的转换函数 */ transformValue?: (value: any) => any; /** 从模型值解析展示值 */ @@ -168,6 +170,7 @@ function getFieldValue(field: SearchField) { :model-value="getFieldValue(field)" :placeholder="field.placeholder" clearable + :filterable="field.filterable" :disabled="props.disabled" @update:model-value="val => updateFieldValue(field, val)" > @@ -207,6 +210,7 @@ function getFieldValue(field: SearchField) { :model-value="getFieldValue(field)" :dict-code="field.dictCode!" :placeholder="field.placeholder" + :filterable="field.filterable" :disabled="props.disabled" :show-remark="field.showRemark" @update:model-value="val => updateFieldValue(field, val)" @@ -268,6 +272,7 @@ function getFieldValue(field: SearchField) { :model-value="getFieldValue(field)" :placeholder="field.placeholder" clearable + :filterable="field.filterable" :disabled="props.disabled" @update:model-value="val => updateFieldValue(field, val)" > @@ -307,6 +312,7 @@ function getFieldValue(field: SearchField) { :model-value="getFieldValue(field)" :dict-code="field.dictCode!" :placeholder="field.placeholder" + :filterable="field.filterable" :disabled="props.disabled" :show-remark="field.showRemark" @update:model-value="val => updateFieldValue(field, val)" diff --git a/src/views/personal-center/my-performance/index.vue b/src/views/personal-center/my-performance/index.vue index a45457a..ec9da67 100644 --- a/src/views/personal-center/my-performance/index.vue +++ b/src/views/personal-center/my-performance/index.vue @@ -118,6 +118,7 @@ const actionType = ref<'confirm' | 'reject'>('confirm'); const recordVisible = ref(false); const exporting = ref(false); +const currentUserId = computed(() => authStore.userInfo.userId || ''); const rowActionLoadingKey = ref(''); const ACTION_ICON_MAP = { @@ -326,7 +327,9 @@ function getRowActions(row: Api.Performance.Sheet.Sheet): BusinessTableAction[] } if (isTeamMode.value) { - if (canUpdate.value && ['draft', 'rejected'].includes(row.statusCode)) { + const isDirectManager = Boolean(currentUserId.value) && row.managerId === currentUserId.value; + + if (isDirectManager && canUpdate.value && ['draft', 'rejected'].includes(row.statusCode)) { actions.push({ key: 'edit', label: '编辑', @@ -344,7 +347,7 @@ function getRowActions(row: Api.Performance.Sheet.Sheet): BusinessTableAction[] }); } - if (canDelete.value && row.statusCode === 'draft') { + if (isDirectManager && canDelete.value && row.statusCode === 'draft') { actions.push({ key: 'delete', label: '删除', @@ -599,13 +602,13 @@ async function loadDeptOptions() { } async function loadDirectSubordinateOptions() { - const currentUserId = authStore.userInfo.userId; - if (!currentUserId) { + const loginUserId = authStore.userInfo.userId; + if (!loginUserId) { directSubordinateOptions.value = []; return; } - const { error, data: userList } = await fetchGetDirectSubordinates(currentUserId); + const { error, data: userList } = await fetchGetDirectSubordinates(loginUserId); if (error || !userList) { directSubordinateOptions.value = []; @@ -643,8 +646,12 @@ async function handleTeamViewModeChange(mode: TeamViewMode) { } watch( - () => [teamViewMode.value, selectedSubordinateUserId.value], - async () => { + () => [teamViewMode.value, selectedSubordinateUserId.value] as const, + async ([mode], [previousMode]) => { + if (mode === 'self' && previousMode === 'self') { + return; + } + await refreshPageData(1); } ); 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 5727ee8..d7d4ab2 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 @@ -7,13 +7,18 @@ import BusinessFormDialog from '@/components/custom/business-form-dialog.vue'; defineOptions({ name: 'PerformanceActionDialog' }); +type ActionType = 'confirm' | 'reject'; + interface Props { rowData?: Api.Performance.Sheet.Sheet | null; - actionType: 'confirm' | 'reject'; + actionType?: ActionType; + selectableActionType?: boolean; } const props = withDefaults(defineProps(), { - rowData: null + rowData: null, + actionType: 'confirm', + selectableActionType: false }); const visible = defineModel('visible', { default: false }); @@ -26,13 +31,25 @@ const { formRef, validate } = useForm(); const { createRequiredRule } = useFormRules(); const submitting = ref(false); -const form = reactive({ +const form = reactive<{ + actionType: ActionType; + reason: string; +}>({ + actionType: 'confirm', reason: '' }); -const isReject = computed(() => props.actionType === 'reject'); -const title = computed(() => (isReject.value ? '退回绩效表' : '确认绩效表')); -const confirmText = computed(() => (isReject.value ? '确认退回' : '确认')); +const isReject = computed(() => form.actionType === 'reject'); +const title = computed(() => { + if (props.selectableActionType) return '绩效审批'; + return isReject.value ? '退回绩效表' : '确认绩效表'; +}); +const confirmText = computed(() => { + if (props.selectableActionType) return '确认提交'; + return isReject.value ? '确认退回' : '确认'; +}); +const opinionLabel = computed(() => (isReject.value ? '退回原因' : '审批意见')); +const opinionPlaceholder = computed(() => (isReject.value ? `请输入${opinionLabel.value}` : '可填写审批意见')); const rules = computed(() => ({ reason: isReject.value ? [createRequiredRule('请输入退回原因')] : [] })); @@ -63,6 +80,7 @@ async function handleSubmit() { watch(visible, isVisible => { if (!isVisible) return; + form.actionType = props.actionType; form.reason = ''; }); @@ -84,16 +102,119 @@ watch(visible, isVisible => { {{ props.rowData?.actualScoreTotal ?? '--' }} - +
+
+ +
+ + +
+
+
+ + + + diff --git a/src/views/personal-center/my-performance/modules/performance-excel-editor-drawer.vue b/src/views/personal-center/my-performance/modules/performance-excel-editor-drawer.vue index cbe9ccc..b527004 100644 --- a/src/views/personal-center/my-performance/modules/performance-excel-editor-drawer.vue +++ b/src/views/personal-center/my-performance/modules/performance-excel-editor-drawer.vue @@ -26,11 +26,13 @@ interface Props { rowData?: Api.Performance.Sheet.Sheet | null; mode: 'view' | 'edit' | 'create'; subordinateOptions?: SubordinateOption[]; + showApprovalFooter?: boolean; } const props = withDefaults(defineProps(), { rowData: null, - subordinateOptions: () => [] + subordinateOptions: () => [], + showApprovalFooter: false }); const visible = defineModel('visible', { default: false }); @@ -38,6 +40,7 @@ const visible = defineModel('visible', { default: false }); const emit = defineEmits<{ saved: []; savedAndSent: []; + startApproval: []; }>(); const { formRef, validate } = useForm(); @@ -83,6 +86,7 @@ const drawerTitle = computed(() => { return `${action}绩效 Excel${name ? ` - ${name}` : ''}`; }); const canSave = computed(() => props.mode !== 'view'); +const showApprovalFooter = computed(() => props.mode === 'view' && props.showApprovalFooter); const drawerSize = computed(() => (viewportWidth.value >= 2560 ? '60%' : '88%')); function syncViewportWidth() { @@ -93,6 +97,10 @@ function handleClose() { visible.value = false; } +function handleStartApproval() { + emit('startApproval'); +} + function disposeUniver() { try { univerInstance?.dispose?.(); @@ -687,10 +695,16 @@ onMounted(() => {
-
- {{ formatWorkLogDetail(task.detail) || '暂无内容' }} + +
@@ -1337,7 +1426,20 @@ function syncRichSupport(item: PlanItem, event: Event) {
- {{ formatWorkLogDetail(task.detail) || '暂无内容' }} + +
@@ -2161,11 +2263,30 @@ function syncRichSupport(item: PlanItem, event: Event) { color: #334155; font-size: 13px; line-height: 1.6; + display: grid; + gap: 0; + white-space: normal; + overflow-wrap: anywhere; + word-break: break-word; +} + +.structured-preview__log-entry { + display: grid; + gap: 10px; +} + +.structured-preview__log-text { white-space: pre-wrap; overflow-wrap: anywhere; word-break: break-word; } +.structured-preview__log-divider { + width: 100%; + height: 1px; + background: #e2e8f0; +} + .rich-editor :deep(.rich-task) { display: grid; gap: 4px; diff --git a/src/views/product/list/modules/product-grouped-table.vue b/src/views/product/list/modules/product-grouped-table.vue index cca864b..4017dd2 100644 --- a/src/views/product/list/modules/product-grouped-table.vue +++ b/src/views/product/list/modules/product-grouped-table.vue @@ -39,7 +39,7 @@ const PAGE_SIZE = 10; /** 产品行多列数(名称/编码/经理/我的角色/状态/原因/更新),非产品行整行合并用 */ const COLUMN_COUNT = 7; /** 产品描述副行长度阈值:超过时展示「详情」入口 */ -const PRODUCT_DESC_MAX_LEN = 48; +const PRODUCT_DESC_MAX_LEN = 40; interface DirectionGroup { directionCode: string; diff --git a/src/views/product/requirement/modules/requirement-search.vue b/src/views/product/requirement/modules/requirement-search.vue index 9c77ddf..d6b48c3 100644 --- a/src/views/product/requirement/modules/requirement-search.vue +++ b/src/views/product/requirement/modules/requirement-search.vue @@ -122,6 +122,7 @@ const fields = computed(() => [ type: 'select' as const, placeholder: '筛选负责人', options: memberSelectOptions.value, + filterable: true, renderOption: renderMemberOption } ]); diff --git a/src/views/project/project/requirement/index.vue b/src/views/project/project/requirement/index.vue index 197a560..e89b56e 100644 --- a/src/views/project/project/requirement/index.vue +++ b/src/views/project/project/requirement/index.vue @@ -639,7 +639,7 @@ async function reloadTable() { try { await loadTreeData(); - await Promise.all([loadAllowedTransitionsForAll(), loadRequirementDisplayTotal()]); + await loadAllowedTransitionsForAll(); } finally { loading.value = false; } @@ -813,11 +813,13 @@ async function handleDelete(row: Api.Project.ProjectRequirement) { } window.$message?.success('需求删除成功'); + await loadRequirementDisplayTotal(); await reloadTable(); } async function handleCreateSubmitted() { createVisible.value = false; + await loadRequirementDisplayTotal(); await reloadTable(); } @@ -828,6 +830,7 @@ async function handleDetailSubmitted() { async function handleSplitSubmitted() { splitVisible.value = false; + await loadRequirementDisplayTotal(); await reloadTable(); } @@ -855,8 +858,10 @@ watch( return; } - await Promise.all([loadMembers(), loadTreeData()]); - await Promise.all([loadAllowedTransitionsForAll(), loadRequirementDisplayTotal()]); + treeData.value = []; + allowedTransitionsMap.value = new Map(); + pagination.total = 0; + await Promise.all([loadMembers(), loadRequirementDisplayTotal()]); }, { immediate: true } ); @@ -876,6 +881,7 @@ Promise.all([loadStatusOptions()]); :member-options="memberUserOptions" :category-dict-code="RDMS_REQ_CATEGORY_DICT_CODE" :priority-dict-code="RDMS_REQ_PRIORITY_DICT_CODE" + :status-options="statusOptions" @reset="handleResetSearch" @search="handleSearch" /> diff --git a/src/views/project/project/requirement/modules/requirement-search.vue b/src/views/project/project/requirement/modules/requirement-search.vue index 5905ba2..66ae584 100644 --- a/src/views/project/project/requirement/modules/requirement-search.vue +++ b/src/views/project/project/requirement/modules/requirement-search.vue @@ -1,7 +1,5 @@