fix(加班申请、工作报告、我的绩效): 重构页面样式、修复一系列bug、对不合理的地方进行调整。

This commit is contained in:
dk
2026-06-22 23:07:21 +08:00
parent b1d52b852f
commit 632c123112
30 changed files with 1574 additions and 451 deletions

View File

@@ -45,6 +45,7 @@ function getInitSearchParams(): Api.OvertimeApplication.OvertimeApplicationSearc
pageNo: 1,
pageSize: 10,
keyword: undefined,
applicantIds: undefined,
applicantName: undefined,
approverId: undefined,
approverName: undefined,
@@ -95,11 +96,30 @@ const ACTION_ICON_MAP = {
const canUseTeamDashboard = computed(() => hasAuth('project:overtime-application:team-dashboard'));
const allSubordinateUserIds = computed(() => collectSubordinateUserIds(subordinateTree.value));
const subordinateOptions = computed(() => {
const options: Array<{ label: string; value: string }> = [];
const walk = (nodes?: Api.SystemManage.MySubordinateTreeNode[] | null) => {
nodes?.forEach(node => {
options.push({ label: node.userNickname, value: node.userId });
walk(node.children ?? null);
});
};
walk(subordinateTree.value?.children ?? null);
return options;
});
const selectedSubordinateNode = computed(() =>
findSubordinateNode(subordinateTree.value, selectedSubordinateUserId.value)
);
const isTeamMode = computed(() => teamViewMode.value === 'team');
const isRootSelected = computed(() => Boolean(isTeamMode.value && selectedSubordinateNode.value?.isRoot));
const summaryPeriodLabel = computed(() => {
if (teamSummary.value?.overtimeDateStart && teamSummary.value?.overtimeDateEnd) {
return `${teamSummary.value.overtimeDateStart}${teamSummary.value.overtimeDateEnd}`;
}
return '';
});
const selectedTeamLabel = computed(() => {
if (!isTeamMode.value) return '我自己';
if (!selectedSubordinateNode.value) return '--';
@@ -125,8 +145,19 @@ const currentApplicantIds = computed(() => {
if (isRootSelected.value) return [];
return teamContext.value?.selectedUserIds ?? [];
});
const resolvedApplicantIds = computed(() => {
if (!isTeamMode.value) {
return undefined;
}
const { columns, columnChecks, data, loading, getDataByPage, mobilePagination } = useUIPaginatedTable<
if (searchParams.applicantIds?.length) {
return searchParams.applicantIds;
}
return currentApplicantIds.value;
});
const { columns, columnChecks, data, loading, getDataByPage, mobilePagination, reloadColumns } = useUIPaginatedTable<
OvertimeApplicationPageResponse,
Api.OvertimeApplication.OvertimeApplication
>({
@@ -137,70 +168,80 @@ const { columns, columnChecks, data, loading, getDataByPage, mobilePagination }
api: () =>
fetchGetOvertimeApplicationPage({
...searchParams,
applicantIds: currentApplicantIds.value
applicantIds: resolvedApplicantIds.value
}),
transform: response => transformPageResult(response, searchParams.pageNo ?? 1, searchParams.pageSize ?? 10),
onPaginationParamsChange: params => {
searchParams.pageNo = params.currentPage ?? 1;
searchParams.pageSize = params.pageSize ?? 10;
},
columns: () => [
{ prop: 'index', type: 'index', label: '序号', width: 64 },
...(isTeamMode.value ? [{ prop: 'applicantName', label: '申请人', minWidth: 120, showOverflowTooltip: true }] : []),
{
prop: 'overtimeDate',
label: '加班日期',
width: 120,
formatter: row => formatOvertimeDate(row.overtimeDate)
},
{ prop: 'overtimeDuration', label: '加班时长', width: 110, showOverflowTooltip: true },
{
prop: 'overtimeReason',
label: '加班原因',
minWidth: 180,
className: 'overtime-application__cell-ellipsis',
formatter: row => formatEmptyText(row.overtimeReason)
},
{
prop: 'overtimeContent',
label: '加班内容',
minWidth: 200,
className: 'overtime-application__cell-ellipsis',
formatter: row => formatEmptyText(row.overtimeContent)
},
{
prop: 'statusCode',
label: '状态',
width: 110,
align: 'center',
formatter: row => (
<ElTag type={resolveOvertimeApplicationStatusTagType(row.statusCode)}>
{getOvertimeApplicationStatusLabel(row.statusCode, row.statusName)}
</ElTag>
)
},
{ prop: 'approverName', label: '审批人', minWidth: 80, showOverflowTooltip: true },
{
prop: 'submitTime',
label: '提交时间',
minWidth: 150,
formatter: row => formatOvertimeDateTime(row.submitTime)
},
{
prop: 'approvalTime',
label: '审批时间',
minWidth: 150,
formatter: row => formatOvertimeDateTime(row.approvalTime)
},
{
prop: 'operate',
label: '操作',
width: isTeamMode.value ? 140 : 170,
align: 'center',
fixed: 'right',
formatter: row => <BusinessTableActionCell actions={getRowActions(row)} variant="icon" />
columns: () => {
const cols: UI.TableColumn<Api.OvertimeApplication.OvertimeApplication>[] = [
{ prop: 'index', type: 'index', label: '序号', width: 64 },
{
prop: 'overtimeDate',
label: '加班日期',
width: 120,
formatter: row => formatOvertimeDate(row.overtimeDate)
}
];
if (isTeamMode.value) {
cols.push({ prop: 'applicantName', label: '申请人', minWidth: 120, showOverflowTooltip: true });
}
]
cols.push(
{ prop: 'overtimeDuration', label: '加班时长', width: 110, showOverflowTooltip: true },
{
prop: 'overtimeReason',
label: '加班原因',
minWidth: 180,
className: 'overtime-application__cell-ellipsis',
formatter: row => formatEmptyText(row.overtimeReason)
},
{
prop: 'overtimeContent',
label: '加班内容',
minWidth: 200,
className: 'overtime-application__cell-ellipsis',
formatter: row => formatEmptyText(row.overtimeContent)
},
{
prop: 'statusCode',
label: '状态',
width: 110,
align: 'center',
formatter: row => (
<ElTag type={resolveOvertimeApplicationStatusTagType(row.statusCode)}>
{getOvertimeApplicationStatusLabel(row.statusCode, row.statusName)}
</ElTag>
)
},
{ prop: 'approverName', label: '审批人', minWidth: 80, showOverflowTooltip: true },
{
prop: 'submitTime',
label: '提交时间',
minWidth: 150,
formatter: row => formatOvertimeDateTime(row.submitTime)
},
{
prop: 'approvalTime',
label: '审批时间',
minWidth: 150,
formatter: row => formatOvertimeDateTime(row.approvalTime)
},
{
prop: 'operate',
label: '操作',
width: isTeamMode.value ? 140 : 170,
align: 'center',
fixed: 'right',
formatter: row => <BusinessTableActionCell actions={getRowActions(row)} variant="icon" />
}
);
return cols;
}
});
const totalCount = computed(() => mobilePagination.value.total || data.value.length);
@@ -283,10 +324,12 @@ function resetSearchParams() {
const pageSize = searchParams.pageSize ?? 10;
Object.assign(searchParams, getInitSearchParams(), { pageSize });
reloadTable(1);
loadTeamSummary();
}
function handleSearch() {
reloadTable(1);
loadTeamSummary();
}
function handleSubmitted() {
@@ -298,7 +341,7 @@ function createExportParams() {
const { pageNo: _pageNo, pageSize: _pageSize, ...params } = searchParams;
return {
...params,
applicantIds: currentApplicantIds.value
applicantIds: resolvedApplicantIds.value
};
}
@@ -306,7 +349,7 @@ async function handleExport() {
exporting.value = true;
const { error, data: blob } = await fetchExportOvertimeApplications({
...createExportParams(),
applicantIds: currentApplicantIds.value
applicantIds: resolvedApplicantIds.value
});
exporting.value = false;
@@ -334,8 +377,16 @@ async function loadTeamSummary() {
return;
}
const summaryParams: Api.OvertimeApplication.TeamOvertimeSummaryParams = {};
const dateRange = searchParams.overtimeDate;
if (dateRange?.length === 2) {
summaryParams.overtimeDateStart = dateRange[0];
summaryParams.overtimeDateEnd = dateRange[1];
}
teamSummaryLoading.value = true;
const { error, data: summaryData } = await fetchGetTeamOvertimeSummary();
const { error, data: summaryData } = await fetchGetTeamOvertimeSummary(summaryParams);
teamSummaryLoading.value = false;
teamSummary.value = error || !summaryData ? null : summaryData;
@@ -364,6 +415,13 @@ watch(
}
);
watch(
() => isTeamMode.value,
() => {
reloadColumns();
}
);
watch(
() => isRootSelected.value,
() => {
@@ -383,21 +441,24 @@ watch(
@update:mode="handleTeamViewModeChange"
>
<div v-if="isRootSelected" v-loading="teamSummaryLoading" class="team-overtime-summary">
<div class="team-overtime-summary__item">
<span class="team-overtime-summary__label">本月申请单数</span>
<strong class="team-overtime-summary__value">{{ teamSummary?.totalApplicationCount ?? 0 }}</strong>
</div>
<div class="team-overtime-summary__item">
<span class="team-overtime-summary__label">本月待审批</span>
<strong class="team-overtime-summary__value">{{ teamSummary?.pendingCount ?? 0 }}</strong>
</div>
<div class="team-overtime-summary__item">
<span class="team-overtime-summary__label">本月已通过</span>
<strong class="team-overtime-summary__value">{{ teamSummary?.approvedCount ?? 0 }}</strong>
</div>
<div class="team-overtime-summary__item">
<span class="team-overtime-summary__label">本月已退回</span>
<strong class="team-overtime-summary__value">{{ teamSummary?.rejectedCount ?? 0 }}</strong>
<div v-if="summaryPeriodLabel" class="team-overtime-summary__period">{{ summaryPeriodLabel }}</div>
<div class="team-overtime-summary__grid">
<div class="team-overtime-summary__item">
<span class="team-overtime-summary__label">申请单数</span>
<strong class="team-overtime-summary__value">{{ teamSummary?.totalApplicationCount ?? 0 }}</strong>
</div>
<div class="team-overtime-summary__item">
<span class="team-overtime-summary__label">待审批</span>
<strong class="team-overtime-summary__value">{{ teamSummary?.pendingCount ?? 0 }}</strong>
</div>
<div class="team-overtime-summary__item">
<span class="team-overtime-summary__label">已通过</span>
<strong class="team-overtime-summary__value">{{ teamSummary?.approvedCount ?? 0 }}</strong>
</div>
<div class="team-overtime-summary__item">
<span class="team-overtime-summary__label">已退回</span>
<strong class="team-overtime-summary__value">{{ teamSummary?.rejectedCount ?? 0 }}</strong>
</div>
</div>
</div>
</TeamContextPanel>
@@ -412,7 +473,13 @@ watch(
</div>
<div class="overtime-application-page__main">
<OvertimeApplicationSearch v-model:model="searchParams" @reset="resetSearchParams" @search="handleSearch" />
<OvertimeApplicationSearch
v-model:model="searchParams"
:team-mode="isTeamMode"
:subordinate-options="subordinateOptions"
@reset="resetSearchParams"
@search="handleSearch"
/>
<ElCard class="flex-1-hidden card-wrapper" body-class="business-table-card-body">
<template #header>
@@ -534,6 +601,16 @@ watch(
}
.team-overtime-summary {
display: grid;
gap: 12px;
}
.team-overtime-summary__period {
color: var(--el-text-color-secondary);
font-size: 12px;
}
.team-overtime-summary__grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 12px;

View File

@@ -5,6 +5,21 @@ import TableSearchFields, { type SearchField } from '@/components/custom/table-s
defineOptions({ name: 'OvertimeApplicationSearch' });
interface Option {
label: string;
value: string | number;
}
interface Props {
teamMode?: boolean;
subordinateOptions?: Option[];
}
const props = withDefaults(defineProps<Props>(), {
teamMode: false,
subordinateOptions: () => []
});
const emit = defineEmits<{
reset: [];
search: [];
@@ -15,7 +30,7 @@ const model = defineModel<Api.OvertimeApplication.OvertimeApplicationSearchParam
});
const searchModel = reactive<Record<string, any>>({
applicantName: '',
applicantIds: undefined,
overtimeDate: undefined,
statusCode: undefined,
approverName: ''
@@ -26,11 +41,10 @@ const statusOptions = ref<Array<{ label: string; value: string }>>([]);
let syncingFromSource = false;
watch(
() =>
[model.value.applicantName, model.value.overtimeDate, model.value.statusCode, model.value.approverName] as const,
([applicantName, overtimeDate, statusCode, approverName]) => {
() => [model.value.applicantIds, model.value.overtimeDate, model.value.statusCode, model.value.approverName] as const,
([applicantIds, overtimeDate, statusCode, approverName]) => {
syncingFromSource = true;
searchModel.applicantName = applicantName ?? '';
searchModel.applicantIds = applicantIds;
searchModel.overtimeDate = overtimeDate;
searchModel.statusCode = statusCode;
searchModel.approverName = approverName ?? '';
@@ -40,14 +54,14 @@ watch(
);
watch(
() =>
[searchModel.applicantName, searchModel.overtimeDate, searchModel.statusCode, searchModel.approverName] as const,
([applicantName, overtimeDate, statusCode, approverName]) => {
() => [searchModel.applicantIds, searchModel.overtimeDate, searchModel.statusCode, searchModel.approverName] as const,
([applicantIds, overtimeDate, statusCode, approverName]) => {
if (syncingFromSource) {
return;
}
model.value.applicantName = applicantName?.trim() || undefined;
model.value.applicantIds = applicantIds;
model.value.applicantName = undefined;
model.value.overtimeDate = overtimeDate;
model.value.statusCode = statusCode;
model.value.approverName = approverName?.trim() || undefined;
@@ -73,33 +87,47 @@ onMounted(async () => {
await loadStatusOptions();
});
const fields = computed<SearchField[]>(() => [
{
key: 'applicantName',
label: '申请人',
type: 'input',
placeholder: '请输入申请人'
},
{
key: 'overtimeDate',
label: '加班日期',
type: 'dateRange',
placeholder: '请选择加班日期'
},
{
key: 'statusCode',
label: '状态',
type: 'select',
options: statusOptions.value,
placeholder: '请选择状态'
},
{
key: 'approverName',
label: '审批人',
type: 'input',
placeholder: '请输入审批人'
const fields = computed<SearchField[]>(() => {
const baseFields: SearchField[] = [
...(props.teamMode
? [
{
key: 'applicantIds',
label: '申请人',
type: 'select' as const,
options: props.subordinateOptions,
placeholder: '请选择申请人',
transformValue: (value: string | number | null | undefined) => (value ? [value] : undefined),
resolveValue: (value: unknown) => (Array.isArray(value) ? value[0] : value)
}
]
: []),
{
key: 'overtimeDate',
label: '加班日期',
type: 'dateRange',
placeholder: '请选择加班日期'
},
{
key: 'statusCode',
label: '状态',
type: 'select',
options: statusOptions.value,
placeholder: '请选择状态'
}
];
if (props.teamMode) {
baseFields.push({
key: 'approverName',
label: '审批人',
type: 'input',
placeholder: '请输入审批人'
});
}
]);
return baseFields;
});
function handleReset() {
emit('reset');