fix(加班申请): 去掉撤销相关的状态和动作。

feat(工作报告): 开发工作报告功能
This commit is contained in:
dk
2026-06-11 10:56:24 +08:00
parent 2e369b23a9
commit d53a8dfae5
56 changed files with 14312 additions and 2910 deletions

View File

@@ -5,7 +5,7 @@ import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
defineOptions({ name: 'OvertimeApplicationActionDialog' });
type ActionType = 'approve' | 'reject' | 'cancel';
type ActionType = 'approve' | 'reject';
interface Props {
actionType: ActionType;
@@ -34,8 +34,7 @@ const model = reactive({
const title = computed(() => {
const map: Record<ActionType, string> = {
approve: '通过加班申请',
reject: '退回加班申请',
cancel: '撤销加班申请'
reject: '退回加班申请'
};
return map[props.actionType];
@@ -44,8 +43,7 @@ const title = computed(() => {
const reasonLabel = computed(() => {
const map: Record<ActionType, string> = {
approve: '审核意见',
reject: '退回原因',
cancel: '撤销原因'
reject: '退回原因'
};
return map[props.actionType];
@@ -58,7 +56,7 @@ const reasonPlaceholder = computed(() => {
return `请输入${reasonLabel.value}`;
}
return props.actionType === 'cancel' ? '可填写撤销原因' : '可填写审核意见';
return '可填写审核意见';
});
const rules = computed(() => ({

View File

@@ -0,0 +1,72 @@
<script setup lang="ts">
import { ref, watch } from 'vue';
import { fetchGetOvertimeApplicationApprovalRecords } from '@/service/api';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import {
formatEmptyText,
formatOvertimeDateTime,
getOvertimeApplicationStatusLabel
} from './overtime-application-shared';
defineOptions({ name: 'OvertimeApplicationApprovalRecordDialog' });
interface Props {
rowData?: Api.OvertimeApplication.OvertimeApplication | null;
}
const props = defineProps<Props>();
const visible = defineModel<boolean>('visible', {
default: false
});
const loading = ref(false);
const records = ref<Api.OvertimeApplication.OvertimeApplicationApprovalRecord[]>([]);
async function loadRecords() {
if (!props.rowData?.id) {
records.value = [];
return;
}
loading.value = true;
const { error, data } = await fetchGetOvertimeApplicationApprovalRecords(props.rowData.id);
loading.value = false;
records.value = error || !data ? [] : data;
}
watch(
() => visible.value,
value => {
if (value) {
loadRecords();
}
}
);
</script>
<template>
<BusinessFormDialog
v-model="visible"
title="加班申请审批记录"
width="820px"
:loading="loading"
:show-footer="false"
max-body-height="72vh"
>
<ElTable border :data="records">
<ElTableColumn prop="approvalRound" label="轮次" width="80" />
<ElTableColumn label="结论" width="110">
<template #default="{ row }">{{ getOvertimeApplicationStatusLabel(row.conclusion) }}</template>
</ElTableColumn>
<ElTableColumn label="审批意见" min-width="240" show-overflow-tooltip>
<template #default="{ row }">{{ formatEmptyText(row.opinion) }}</template>
</ElTableColumn>
<ElTableColumn prop="auditorName" label="审批人" width="130" show-overflow-tooltip />
<ElTableColumn label="审批时间" width="170">
<template #default="{ row }">{{ formatOvertimeDateTime(row.createTime) }}</template>
</ElTableColumn>
</ElTable>
</BusinessFormDialog>
</template>

View File

@@ -1,22 +1,28 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import { ref, watch } from 'vue';
import { fetchGetOvertimeApplicationDetail } from '@/service/api';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import {
formatEmptyText,
formatOvertimeDate,
formatOvertimeDateTime,
getOvertimeApplicationStatusLabel,
resolveOvertimeApplicationStatusTagType
} from './overtime-application-shared';
import { formatOvertimeDate, formatOvertimeDateTime } from './overtime-application-shared';
import IconMdiCheckCircleOutline from '~icons/mdi/check-circle-outline';
import IconMdiCloseCircleOutline from '~icons/mdi/close-circle-outline';
defineOptions({ name: 'OvertimeApplicationDetailDialog' });
interface Props {
rowData?: Api.OvertimeApplication.OvertimeApplication | null;
showApprovalActions?: boolean;
actionLoading?: boolean;
}
const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
showApprovalActions: false,
actionLoading: false
});
const emit = defineEmits<{
approve: [];
reject: [];
}>();
const visible = defineModel<boolean>('visible', {
default: false
@@ -25,11 +31,6 @@ const visible = defineModel<boolean>('visible', {
const loading = ref(false);
const detailData = ref<Api.OvertimeApplication.OvertimeApplication | null>(null);
const statusTagType = computed(() => resolveOvertimeApplicationStatusTagType(detailData.value?.statusCode));
const statusLabel = computed(() =>
getOvertimeApplicationStatusLabel(detailData.value?.statusCode, detailData.value?.statusName)
);
async function loadDetail() {
if (!props.rowData?.id) {
detailData.value = null;
@@ -54,30 +55,96 @@ watch(
</script>
<template>
<BusinessFormDialog v-model="visible" title="加班申请详情" preset="md" :loading="loading" :show-footer="false">
<ElDescriptions v-if="detailData" :column="2" border>
<ElDescriptionsItem label="状态">
<ElTag :type="statusTagType">{{ statusLabel }}</ElTag>
</ElDescriptionsItem>
<ElDescriptionsItem label="申请人">
<BusinessFormDialog
v-model="visible"
title="加班申请详情"
preset="md"
:loading="loading"
:show-footer="props.showApprovalActions"
>
<ElDescriptions v-if="detailData" class="overtime-application-detail-dialog__descriptions" :column="2" border>
<ElDescriptionsItem label="申请人" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.applicantName }}
</ElDescriptionsItem>
<ElDescriptionsItem label="加班日期">{{ formatOvertimeDate(detailData.overtimeDate) }}</ElDescriptionsItem>
<ElDescriptionsItem label="加班时长">{{ detailData.overtimeDuration }}</ElDescriptionsItem>
<ElDescriptionsItem label="审核人">
{{ detailData.approverName }}
<ElDescriptionsItem label="加班日期" label-class-name="overtime-application-detail-dialog__label--compact">
{{ formatOvertimeDate(detailData.overtimeDate) }}
</ElDescriptionsItem>
<ElDescriptionsItem label="加班时长" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.overtimeDuration }}
</ElDescriptionsItem>
<ElDescriptionsItem label="提交时间" label-class-name="overtime-application-detail-dialog__label--compact">
{{ formatOvertimeDateTime(detailData.submitTime) }}
</ElDescriptionsItem>
<ElDescriptionsItem label="加班原因" :span="2" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.overtimeReason }}
</ElDescriptionsItem>
<ElDescriptionsItem label="加班内容" :span="2" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.overtimeContent }}
</ElDescriptionsItem>
<ElDescriptionsItem label="提交时间">{{ formatOvertimeDateTime(detailData.submitTime) }}</ElDescriptionsItem>
<ElDescriptionsItem label="审核时间">{{ formatOvertimeDateTime(detailData.approvalTime) }}</ElDescriptionsItem>
<ElDescriptionsItem label="审核意见">{{ formatEmptyText(detailData.approvalComment) }}</ElDescriptionsItem>
<ElDescriptionsItem label="加班原因" :span="2">{{ detailData.overtimeReason }}</ElDescriptionsItem>
<ElDescriptionsItem label="加班内容" :span="2">{{ detailData.overtimeContent }}</ElDescriptionsItem>
</ElDescriptions>
<ElEmpty v-else description="未获取到加班申请详情" />
<template #footer>
<div class="overtime-application-detail-dialog__footer">
<ElButton
class="overtime-application-detail-dialog__approve-btn"
type="success"
:loading="props.actionLoading"
:disabled="props.actionLoading || !detailData"
@click="emit('approve')"
>
<template #icon>
<IconMdiCheckCircleOutline />
</template>
通过
</ElButton>
<ElButton type="danger" plain :disabled="props.actionLoading || !detailData" @click="emit('reject')">
<template #icon>
<IconMdiCloseCircleOutline />
</template>
退回
</ElButton>
</div>
</template>
</BusinessFormDialog>
</template>
<style scoped>
.overtime-application-detail-dialog__footer {
display: flex;
justify-content: flex-end;
gap: 12px;
}
.overtime-application-detail-dialog__approve-btn {
--el-button-bg-color: #0f766e;
--el-button-border-color: #0f766e;
--el-button-hover-bg-color: #115e59;
--el-button-hover-border-color: #115e59;
--el-button-active-bg-color: #134e4a;
--el-button-active-border-color: #134e4a;
}
:deep(.overtime-application-detail-dialog__descriptions .el-descriptions__cell) {
line-height: 1.7;
}
:deep(.overtime-application-detail-dialog__label),
:deep(.overtime-application-detail-dialog__label--compact) {
white-space: nowrap;
vertical-align: middle;
}
:deep(.overtime-application-detail-dialog__label) {
width: 96px;
min-width: 96px;
}
:deep(.overtime-application-detail-dialog__label--compact) {
width: 86px;
min-width: 86px;
}
:deep(.overtime-application-detail-dialog__readonly-input .el-input__wrapper) {
background: linear-gradient(180deg, rgb(241 245 249 / 98%), rgb(226 232 240 / 94%)), rgb(241 245 249);
box-shadow: 0 0 0 1px rgb(203 213 225 / 96%) inset;

View File

@@ -7,16 +7,14 @@ export const overtimeApplicationStatusOptions: Array<{
}> = [
{ label: '待审批', value: 'pending' },
{ label: '已通过', value: 'approved' },
{ label: '已退回', value: 'rejected' },
{ label: '已撤销', value: 'cancelled' }
{ label: '已退回', value: 'rejected' }
];
export const overtimeApplicationActionNameMap: Record<Api.OvertimeApplication.OvertimeApplicationActionType, string> = {
submit: '提交',
resubmit: '重新提交',
approve: '通过',
reject: '退回',
cancel: '撤销'
reject: '退回'
};
export function getOvertimeApplicationStatusLabel(statusCode?: string | null, statusName?: string | null) {

View File

@@ -1,89 +0,0 @@
<script setup lang="tsx">
import { ref, watch } from 'vue';
import { ElTag } from 'element-plus';
import { fetchGetOvertimeApplicationStatusLogs } from '@/service/api';
import BusinessFormDialog from '@/components/custom/business-form-dialog.vue';
import {
formatEmptyText,
formatOvertimeDate,
formatOvertimeDateTime,
getOvertimeApplicationActionLabel,
getOvertimeApplicationStatusLabel,
resolveOvertimeApplicationStatusTagType
} from './overtime-application-shared';
defineOptions({ name: 'OvertimeApplicationStatusLogDialog' });
interface Props {
rowData?: Api.OvertimeApplication.OvertimeApplication | null;
}
const props = defineProps<Props>();
const visible = defineModel<boolean>('visible', {
default: false
});
const loading = ref(false);
const logs = ref<Api.OvertimeApplication.OvertimeApplicationStatusLog[]>([]);
async function loadLogs() {
if (!props.rowData?.id) {
logs.value = [];
return;
}
loading.value = true;
const { error, data } = await fetchGetOvertimeApplicationStatusLogs(props.rowData.id);
loading.value = false;
logs.value = error || !data ? [] : data;
}
function renderStatus(code?: string | null) {
if (!code) {
return '--';
}
return <ElTag type={resolveOvertimeApplicationStatusTagType(code)}>{getOvertimeApplicationStatusLabel(code)}</ElTag>;
}
watch(
() => visible.value,
value => {
if (value) {
loadLogs();
}
}
);
</script>
<template>
<BusinessFormDialog
v-model="visible"
title="状态日志"
width="920px"
:loading="loading"
:show-footer="false"
max-body-height="72vh"
>
<ElTable border :data="logs">
<ElTableColumn prop="createTime" label="操作时间" width="170">
<template #default="{ row }">{{ formatOvertimeDateTime(row.createTime) }}</template>
</ElTableColumn>
<ElTableColumn prop="actionType" label="动作" width="110">
<template #default="{ row }">{{ getOvertimeApplicationActionLabel(row.actionType) }}</template>
</ElTableColumn>
<ElTableColumn prop="operatorName" label="操作人" width="120" show-overflow-tooltip />
<ElTableColumn prop="fromStatus" label="原状态" width="110" :formatter="row => renderStatus(row.fromStatus)" />
<ElTableColumn prop="toStatus" label="新状态" width="110" :formatter="row => renderStatus(row.toStatus)" />
<ElTableColumn prop="reason" label="原因/意见" min-width="180" show-overflow-tooltip>
<template #default="{ row }">{{ formatEmptyText(row.reason) }}</template>
</ElTableColumn>
<ElTableColumn prop="overtimeDateSnapshot" label="加班日期" width="120">
<template #default="{ row }">{{ formatOvertimeDate(row.overtimeDateSnapshot) }}</template>
</ElTableColumn>
<ElTableColumn prop="overtimeDurationSnapshot" label="时长" width="90" show-overflow-tooltip />
</ElTable>
</BusinessFormDialog>
</template>