fix(加班申请、工作报告、我的绩效): 重构页面样式、修复一系列bug、对不合理的地方进行调整。
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user