Files
cn-rdms-web/src/views/personal-center/work-report/shared/components/action-dialog.vue
dk 9a5845708d feat(我的绩效): 开发我的绩效功能。
fix(加班申请、工作报告): 重构加班申请在审批时的样式,工作报告在新增时的对话框、报告详情页的样式。
2026-06-21 18:22:44 +08:00

404 lines
12 KiB
Vue

<script setup lang="ts">
import { computed, nextTick, reactive, watch } from 'vue';
import dayjs from 'dayjs';
import { useForm, useFormRules } from '@/hooks/common/form';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import BusinessFormSection from '@/components/custom/business-form-section.vue';
import { WORK_REPORT_TYPE_LABEL, type WorkReportType } from '../types';
defineOptions({ name: 'WorkReportActionDialog' });
type ActionType = 'approve' | 'reject';
type ApprovalConclusion = 'approve' | 'reject';
const visible = defineModel<boolean>('visible', { default: false });
const props = withDefaults(
defineProps<{
reportType: WorkReportType;
actionType: ActionType;
initialMonthlyApproveData?: Partial<Api.WorkReport.Monthly.MonthlyReportApproveParams> | null;
loading?: boolean;
}>(),
{
initialMonthlyApproveData: null,
loading: false
}
);
const emit = defineEmits<{
(
e: 'submit',
payload: Api.WorkReport.Common.StatusActionParams | Api.WorkReport.Monthly.MonthlyReportApproveParams,
actionType?: ActionType
): void;
}>();
const { formRef, validate } = useForm();
const { createRequiredRule } = useFormRules();
const reasonModel = reactive<Api.WorkReport.Common.StatusActionParams>({
reason: ''
});
const commonApprovalModel = reactive<{
conclusion: ApprovalConclusion | '';
opinion: string;
}>({
conclusion: 'approve',
opinion: ''
});
const monthlyModel = reactive<Api.WorkReport.Monthly.MonthlyReportApproveParams>({
reason: '',
meetingDate: '',
strengthDesc: '',
strengthExample: '',
weaknessDesc: '',
weaknessExample: '',
improvementSuggestion: '',
performanceResult: '',
employeeSignName: '',
employeeSignedDate: '',
supervisorSignName: '',
supervisorSignedDate: ''
});
const isMonthlyApprove = computed(() => props.reportType === 'monthly' && props.actionType === 'approve');
const isCommonApprove = computed(() => props.reportType !== 'monthly' && props.actionType === 'approve');
const title = computed(() => {
if (isCommonApprove.value) {
return `审批${WORK_REPORT_TYPE_LABEL[props.reportType]}`;
}
const actionLabel = props.actionType === 'approve' ? '审批通过' : '退回';
return `${actionLabel}${WORK_REPORT_TYPE_LABEL[props.reportType]}`;
});
const preset = computed(() => (isMonthlyApprove.value ? 'lg' : 'sm'));
const rejectOpinionRequired = computed(() => isCommonApprove.value && commonApprovalModel.conclusion === 'reject');
const opinionLabel = computed(() => (rejectOpinionRequired.value ? '退回原因' : '审批意见'));
const opinionPlaceholder = computed(() =>
rejectOpinionRequired.value ? `请输入${opinionLabel.value}` : '可填写审批意见'
);
const confirmText = computed(() => {
if (isCommonApprove.value) return '确认提交';
if (props.actionType === 'approve') return '通过';
return '退回';
});
const confirmDisabled = computed(() => isCommonApprove.value && !commonApprovalModel.conclusion);
const commonRules = computed(() => ({
opinion: rejectOpinionRequired.value
? [
createRequiredRule(`请输入${opinionLabel.value}`),
{
validator: (_rule, value: string, callback) => {
if (!value?.trim()) {
callback(new Error(`请输入${opinionLabel.value}`));
return;
}
callback();
},
trigger: 'blur'
}
]
: []
}));
watch(visible, isVisible => {
if (!isVisible) return;
reasonModel.reason = '';
Object.assign(commonApprovalModel, {
conclusion: 'approve',
opinion: ''
});
Object.assign(monthlyModel, {
reason: '',
meetingDate: dayjs().format('YYYY-MM-DD'),
strengthDesc: '',
strengthExample: '',
weaknessDesc: '',
weaknessExample: '',
improvementSuggestion: '',
performanceResult: '',
employeeSignName: '',
employeeSignedDate: dayjs().format('YYYY-MM-DD'),
supervisorSignName: '',
supervisorSignedDate: dayjs().format('YYYY-MM-DD')
});
if (props.initialMonthlyApproveData) {
Object.assign(monthlyModel, props.initialMonthlyApproveData);
}
});
watch(
() => visible.value,
async isVisible => {
if (!isVisible || !isCommonApprove.value) return;
await nextTick();
formRef.value?.clearValidate();
}
);
watch(rejectOpinionRequired, async () => {
if (!visible.value || !isCommonApprove.value) return;
await nextTick();
formRef.value?.clearValidate('opinion');
});
async function handleSubmit() {
if (isCommonApprove.value) {
if (!commonApprovalModel.conclusion) {
window.$message?.warning('请选择审批结论');
return;
}
await validate();
emit(
'submit',
{
reason: commonApprovalModel.opinion.trim() || (commonApprovalModel.conclusion === 'approve' ? '通过' : '退回')
},
commonApprovalModel.conclusion
);
return;
}
emit('submit', isMonthlyApprove.value ? { ...monthlyModel } : { ...reasonModel });
}
</script>
<template>
<BusinessFormDialog
v-model="visible"
:title="title"
:preset="preset"
:confirm-loading="loading"
:confirm-disabled="confirmDisabled"
:confirm-text="confirmText"
max-body-height="76vh"
@confirm="handleSubmit"
>
<template v-if="isCommonApprove">
<div class="audit-form">
<div class="audit-field">
<label>审批结论</label>
<div class="audit-conclusion">
<button
type="button"
class="conclusion-btn"
:class="{
active: commonApprovalModel.conclusion === 'approve',
pass: commonApprovalModel.conclusion === 'approve'
}"
@click="commonApprovalModel.conclusion = 'approve'"
>
<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: commonApprovalModel.conclusion === 'reject',
reject: commonApprovalModel.conclusion === 'reject'
}"
@click="commonApprovalModel.conclusion = '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>
<ElForm
ref="formRef"
:model="commonApprovalModel"
:rules="commonRules"
label-position="top"
:validate-on-rule-change="false"
>
<ElFormItem :label="opinionLabel" prop="opinion">
<ElInput
v-model="commonApprovalModel.opinion"
type="textarea"
:rows="5"
maxlength="1000"
show-word-limit
:placeholder="opinionPlaceholder"
/>
</ElFormItem>
</ElForm>
</div>
</template>
<template v-else-if="isMonthlyApprove">
<BusinessFormSection title="当期工作反馈">
<ElRow :gutter="16">
<ElCol :span="12">
<ElFormItem label="面谈时间">
<ElDatePicker v-model="monthlyModel.meetingDate" class="w-full" type="date" value-format="YYYY-MM-DD" />
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="绩效考核结果">
<ElInput v-model="monthlyModel.performanceResult" placeholder="请输入绩效结果" />
</ElFormItem>
</ElCol>
<ElCol :span="24">
<ElFormItem label="审批意见">
<ElInput v-model="monthlyModel.reason" type="textarea" :rows="3" />
</ElFormItem>
</ElCol>
</ElRow>
</BusinessFormSection>
<BusinessFormSection title="优势与不足">
<ElRow :gutter="16">
<ElCol :span="12">
<ElFormItem label="优势描述">
<ElInput v-model="monthlyModel.strengthDesc" type="textarea" :rows="3" />
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="优势行为事例">
<ElInput v-model="monthlyModel.strengthExample" type="textarea" :rows="3" />
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="劣势描述">
<ElInput v-model="monthlyModel.weaknessDesc" type="textarea" :rows="3" />
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="劣势行为事例">
<ElInput v-model="monthlyModel.weaknessExample" type="textarea" :rows="3" />
</ElFormItem>
</ElCol>
<ElCol :span="24">
<ElFormItem label="改进建议">
<ElInput v-model="monthlyModel.improvementSuggestion" type="textarea" :rows="3" />
</ElFormItem>
</ElCol>
</ElRow>
</BusinessFormSection>
<BusinessFormSection title="签字区">
<ElRow :gutter="16">
<ElCol :span="12">
<ElFormItem label="被考核人签名">
<ElInput v-model="monthlyModel.employeeSignName" />
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="被考核人签字日期">
<ElDatePicker
v-model="monthlyModel.employeeSignedDate"
class="w-full"
type="date"
value-format="YYYY-MM-DD"
/>
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="上级签名">
<ElInput v-model="monthlyModel.supervisorSignName" />
</ElFormItem>
</ElCol>
<ElCol :span="12">
<ElFormItem label="上级签字日期">
<ElDatePicker
v-model="monthlyModel.supervisorSignedDate"
class="w-full"
type="date"
value-format="YYYY-MM-DD"
/>
</ElFormItem>
</ElCol>
</ElRow>
</BusinessFormSection>
</template>
<ElForm v-else label-position="top">
<ElFormItem :label="actionType === 'approve' ? '审批意见' : '原因'">
<ElInput v-model="reasonModel.reason" type="textarea" :rows="5" placeholder="请输入原因或意见" />
</ElFormItem>
</ElForm>
</BusinessFormDialog>
</template>
<style scoped>
.audit-form {
display: grid;
gap: 18px;
}
.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>