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 @@