fix(产品需求、项目需求、工作报告、我的绩效、加班申请): 1、修复搜索区域的下拉框,无法搜索的问题。2、修复工作报告内容溢出的问题。3、修复我的待办 - 待审批,里面的二级tabs没有加权限的问题。4、优化周报里工作日志展示时的分隔符。5、优化项目需求池、我的绩效请求重复发送、影响效率的问题。

feat(我的绩效): 1、增加我的绩效在工作台可以直接处理的功能。2、增加我的绩效全部导出时,合并多个excel的sheet为一个excel的功能。
This commit is contained in:
dk
2026-06-24 18:02:36 +08:00
parent b26a9c8a39
commit 3ffdad142d
16 changed files with 569 additions and 81 deletions

View File

@@ -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<Props>(), {
rowData: null
rowData: null,
actionType: 'confirm',
selectableActionType: false
});
const visible = defineModel<boolean>('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<FormRules>(() => ({
reason: isReject.value ? [createRequiredRule('请输入退回原因')] : []
}));
@@ -63,6 +80,7 @@ async function handleSubmit() {
watch(visible, isVisible => {
if (!isVisible) return;
form.actionType = props.actionType;
form.reason = '';
});
</script>
@@ -84,16 +102,119 @@ watch(visible, isVisible => {
<ElDescriptionsItem label="实际得分">{{ props.rowData?.actualScoreTotal ?? '--' }}</ElDescriptionsItem>
</ElDescriptions>
<ElFormItem class="mt-16px" :label="isReject ? '退回原因' : '确认意见'" prop="reason">
<div v-if="props.selectableActionType" class="performance-action-dialog__approval-form">
<div class="audit-field">
<label>审批结论</label>
<div class="audit-conclusion">
<button
type="button"
class="conclusion-btn"
:class="{
active: form.actionType === 'confirm',
pass: form.actionType === 'confirm'
}"
@click="form.actionType = 'confirm'"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5" />
<path
d="M5 8.5L7 10.5L11 6"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
确认
</button>
<button
type="button"
class="conclusion-btn"
:class="{
active: form.actionType === 'reject',
reject: form.actionType === 'reject'
}"
@click="form.actionType = 'reject'"
>
<svg width="16" height="16" viewBox="0 0 16 16" fill="none">
<circle cx="8" cy="8" r="7" stroke="currentColor" stroke-width="1.5" />
<path d="M6 6L10 10M10 6L6 10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" />
</svg>
退回
</button>
</div>
</div>
</div>
<ElFormItem class="mt-16px" :label="opinionLabel" prop="reason">
<ElInput
v-model="form.reason"
type="textarea"
:rows="4"
maxlength="1000"
show-word-limit
:placeholder="isReject ? '请输入退回原因' : '可填写确认意见'"
:placeholder="opinionPlaceholder"
/>
</ElFormItem>
</ElForm>
</BusinessFormDialog>
</template>
<style scoped>
.performance-action-dialog__approval-form {
display: grid;
gap: 18px;
margin-top: 16px;
}
.audit-field {
display: grid;
gap: 8px;
}
.audit-field label {
color: #475467;
font-size: 13px;
font-weight: 800;
}
.audit-conclusion {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px;
}
.conclusion-btn {
height: 44px;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
border: 1px solid #d8e0e8;
border-radius: 8px;
background: #fff;
color: #475467;
font: inherit;
font-size: 14px;
font-weight: 800;
cursor: pointer;
transition: all 0.18s ease;
}
.conclusion-btn:hover {
border-color: var(--el-color-primary);
color: var(--el-color-primary);
}
.conclusion-btn.active.pass {
border-color: var(--el-color-primary);
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
}
.conclusion-btn.active.reject {
border-color: #dc2626;
background: #fef2f2;
color: #dc2626;
}
</style>

View File

@@ -26,11 +26,13 @@ interface Props {
rowData?: Api.Performance.Sheet.Sheet | null;
mode: 'view' | 'edit' | 'create';
subordinateOptions?: SubordinateOption[];
showApprovalFooter?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
rowData: null,
subordinateOptions: () => []
subordinateOptions: () => [],
showApprovalFooter: false
});
const visible = defineModel<boolean>('visible', { default: false });
@@ -38,6 +40,7 @@ const visible = defineModel<boolean>('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(() => {
<div ref="containerRef" class="performance-excel-editor__container" />
</div>
<template v-if="canSave" #footer>
<template v-if="canSave || showApprovalFooter" #footer>
<div class="performance-excel-editor__footer">
<ElButton :loading="saving" :disabled="sending" @click="handleSaveDraft">保存草稿</ElButton>
<ElButton type="primary" :loading="sending" :disabled="saving" @click="handleSaveAndSend">发送绩效</ElButton>
<template v-if="canSave">
<ElButton :loading="saving" :disabled="sending" @click="handleSaveDraft">保存草稿</ElButton>
<ElButton type="primary" :loading="sending" :disabled="saving" @click="handleSaveAndSend">发送绩效</ElButton>
</template>
<template v-else-if="showApprovalFooter">
<ElButton @click="handleClose">退出审批</ElButton>
<ElButton type="primary" @click="handleStartApproval">开始审批</ElButton>
</template>
</div>
</template>
</ElDrawer>

View File

@@ -45,7 +45,14 @@ const baseFields = computed<SearchField[]>(() => [
const teamFields = computed<SearchField[]>(() => [
baseFields.value[0],
{ key: 'employeeId', label: '下属', type: 'select', placeholder: '请选择下属', options: props.subordinateOptions },
{
key: 'employeeId',
label: '下属',
type: 'select',
placeholder: '请选择下属',
options: props.subordinateOptions,
filterable: true
},
{ key: 'employeeDeptId', label: '部门', type: 'select', placeholder: '请选择部门', options: props.deptOptions },
{ key: 'managerName', label: '直属上级', type: 'input', placeholder: '请输入直属上级' },
baseFields.value[1]

View File

@@ -255,7 +255,7 @@ watch(visible, isVisible => {
</ElTooltip>
</div>
<ElTooltip placement="top" effect="light">
<template #content>支持 .xlsx.xls选择后会在这里显示文件名</template>
<template #content>支持 .xlsx格式选择后会在这里显示文件名</template>
<button type="button" class="performance-template-dialog__hint-button" aria-label="Excel 文件说明">
<icon-mdi-information-outline />
</button>