2026-06-21 18:22:44 +08:00
|
|
|
<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' });
|
|
|
|
|
|
2026-06-24 18:02:36 +08:00
|
|
|
type ActionType = 'confirm' | 'reject';
|
|
|
|
|
|
2026-06-21 18:22:44 +08:00
|
|
|
interface Props {
|
|
|
|
|
rowData?: Api.Performance.Sheet.Sheet | null;
|
2026-06-24 18:02:36 +08:00
|
|
|
actionType?: ActionType;
|
|
|
|
|
selectableActionType?: boolean;
|
2026-06-21 18:22:44 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const props = withDefaults(defineProps<Props>(), {
|
2026-06-24 18:02:36 +08:00
|
|
|
rowData: null,
|
|
|
|
|
actionType: 'confirm',
|
|
|
|
|
selectableActionType: false
|
2026-06-21 18:22:44 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const visible = defineModel<boolean>('visible', { default: false });
|
|
|
|
|
|
|
|
|
|
const emit = defineEmits<{
|
|
|
|
|
submitted: [];
|
|
|
|
|
}>();
|
|
|
|
|
|
|
|
|
|
const { formRef, validate } = useForm();
|
|
|
|
|
const { createRequiredRule } = useFormRules();
|
|
|
|
|
|
|
|
|
|
const submitting = ref(false);
|
2026-06-24 18:02:36 +08:00
|
|
|
const form = reactive<{
|
|
|
|
|
actionType: ActionType;
|
|
|
|
|
reason: string;
|
|
|
|
|
}>({
|
|
|
|
|
actionType: 'confirm',
|
2026-06-21 18:22:44 +08:00
|
|
|
reason: ''
|
|
|
|
|
});
|
|
|
|
|
|
2026-06-24 18:02:36 +08:00
|
|
|
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}` : '可填写审批意见'));
|
2026-06-21 18:22:44 +08:00
|
|
|
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;
|
2026-06-24 18:02:36 +08:00
|
|
|
form.actionType = props.actionType;
|
2026-06-21 18:22:44 +08:00
|
|
|
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>
|
2026-06-25 21:34:23 +08:00
|
|
|
<ElDescriptionsItem label="被考核人">{{ props.rowData?.employeeName || '--' }}</ElDescriptionsItem>
|
2026-06-21 18:22:44 +08:00
|
|
|
<ElDescriptionsItem label="实际得分">{{ props.rowData?.actualScoreTotal ?? '--' }}</ElDescriptionsItem>
|
|
|
|
|
</ElDescriptions>
|
|
|
|
|
|
2026-06-24 18:02:36 +08:00
|
|
|
<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">
|
2026-06-21 18:22:44 +08:00
|
|
|
<ElInput
|
|
|
|
|
v-model="form.reason"
|
|
|
|
|
type="textarea"
|
|
|
|
|
:rows="4"
|
|
|
|
|
maxlength="1000"
|
|
|
|
|
show-word-limit
|
2026-06-24 18:02:36 +08:00
|
|
|
:placeholder="opinionPlaceholder"
|
2026-06-21 18:22:44 +08:00
|
|
|
/>
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
</ElForm>
|
|
|
|
|
</BusinessFormDialog>
|
|
|
|
|
</template>
|
2026-06-24 18:02:36 +08:00
|
|
|
|
|
|
|
|
<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>
|