Files
cn-rdms-web/src/views/personal-center/my-performance/modules/performance-action-dialog.vue
dk 08fff8bbc5 feat(performance): 新增绩效下签功能支持
- 在月报审批页面增加 sign-off 场景支持
- 添加绩效下签对话框组件实现批量下签功能
- 增加导入绩效压缩包和直接下签两种操作模式
- 实现电子签名预览和日期格式化显示功能
- 更新绩效权限配置增加下签相关权限
- 优化审批对话框标题和按钮文案显示
- 添加导入结果提示和文件验证功能
- 实现团队模式下的下签操作入口
- 完善绩效表格状态和操作权限判断逻辑
2026-07-19 18:21:14 +08:00

221 lines
6.1 KiB
Vue

<script setup lang="ts">
import { computed, reactive, ref, watch } from 'vue';
import type { FormRules } from 'element-plus';
import { confirmPerformanceSheet, rejectPerformanceSheet } from '@/service/api';
import { useForm, useFormRules } from '@/hooks/common/form';
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?: ActionType;
selectableActionType?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
rowData: null,
actionType: 'confirm',
selectableActionType: false
});
const visible = defineModel<boolean>('visible', { default: false });
const emit = defineEmits<{
submitted: [];
}>();
const { formRef, validate } = useForm();
const { createRequiredRule } = useFormRules();
const submitting = ref(false);
const form = reactive<{
actionType: ActionType;
reason: string;
}>({
actionType: 'confirm',
reason: ''
});
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('请输入退回原因')] : []
}));
async function handleSubmit() {
if (!props.rowData?.id) return;
if (isReject.value) {
try {
await validate();
} catch {
return;
}
}
submitting.value = true;
const result = isReject.value
? await rejectPerformanceSheet(props.rowData.id, { reason: form.reason.trim() })
: await confirmPerformanceSheet(props.rowData.id, { reason: form.reason.trim() || undefined });
submitting.value = false;
if (result.error) return;
window.$message?.success(isReject.value ? '绩效表已退回' : '绩效表已确认');
visible.value = false;
emit('submitted');
}
watch(visible, isVisible => {
if (!isVisible) return;
form.actionType = props.actionType;
form.reason = '';
});
</script>
<template>
<BusinessFormDialog
v-model="visible"
:title="title"
preset="sm"
append-to-body
:confirm-text="confirmText"
:confirm-loading="submitting"
@confirm="handleSubmit"
>
<ElForm ref="formRef" :model="form" :rules="rules" label-position="top">
<ElDescriptions :column="1" border>
<ElDescriptionsItem label="绩效月份">{{ props.rowData?.periodMonth || '--' }}</ElDescriptionsItem>
<ElDescriptionsItem label="被考核人">{{ props.rowData?.employeeName || '--' }}</ElDescriptionsItem>
<ElDescriptionsItem label="实际得分">{{ props.rowData?.actualScoreTotal ?? '--' }}</ElDescriptionsItem>
</ElDescriptions>
<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="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>