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

@@ -1,19 +1,28 @@
<script setup lang="ts">
/* eslint-disable no-void */
import { computed, onMounted, ref } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import dayjs from 'dayjs';
import isoWeek from 'dayjs/plugin/isoWeek';
import { fetchGetWorkReportStatusDict } from '@/service/api';
import type { SearchField } from '@/components/custom/table-search-fields.vue';
import TableSearchFields from '@/components/custom/table-search-fields.vue';
import { BOOLEAN_TRUE_FALSE_OPTIONS, type WorkReportSearchParams, type WorkReportType } from '../types';
import { formatIsoWeekRangeLabel, normalizeWeeklySearchRange } from '../utils';
dayjs.extend(isoWeek);
defineOptions({ name: 'WorkReportSearch' });
interface Props {
reportType: WorkReportType;
teamMode?: boolean;
subordinateOptions?: Array<{ label: string; value: string }>;
projectOptions?: Api.WorkReport.Project.ProjectReportOwnerProjectOption[];
}
const props = withDefaults(defineProps<Props>(), {
teamMode: false,
subordinateOptions: () => [],
projectOptions: () => []
});
@@ -35,6 +44,12 @@ const statusOptions = computed(() =>
}))
);
const weeklyPeriodPlaceholder = computed(() => {
const range = normalizeWeeklySearchRange(model.value.periodStartDate as string[] | undefined);
if (!range?.length) return '请选择周报周期';
return formatIsoWeekRangeLabel(range[0], range[1]) || '请选择周报周期';
});
const fields = computed<SearchField[]>(() => {
const baseFields: SearchField[] = [
{ key: 'statusCode', label: '状态', type: 'select', options: statusOptions.value, placeholder: '请选择状态' },
@@ -51,8 +66,33 @@ const fields = computed<SearchField[]>(() => {
};
if (props.reportType === 'weekly') {
const weeklyPeriodField: SearchField = {
key: 'periodStartDate',
label: '周期',
type: 'dateRange',
placeholder: weeklyPeriodPlaceholder.value,
format: 'YYYY[年第]ww[周]',
rangeSeparator: '至'
};
const teamReporterField: SearchField[] = props.teamMode
? [
{
key: 'reporterIds',
label: '提交人',
type: 'select',
options: props.subordinateOptions,
placeholder: '请选择提交人',
transformValue: value => (value ? [value] : undefined),
resolveValue: value => (Array.isArray(value) ? value[0] : value)
}
]
: [];
return [
...baseFields,
baseFields[0],
weeklyPeriodField,
...teamReporterField,
{
key: 'isBusinessTrip',
label: '是否出差',
@@ -81,7 +121,21 @@ const fields = computed<SearchField[]>(() => {
}
if (props.reportType === 'monthly') {
return [baseFields[0], monthPeriodField];
const teamReporterField: SearchField[] = props.teamMode
? [
{
key: 'reporterIds',
label: '提交人',
type: 'select',
options: props.subordinateOptions,
placeholder: '请选择提交人',
transformValue: value => (value ? [value] : undefined),
resolveValue: value => (Array.isArray(value) ? value[0] : value)
}
]
: [];
return [baseFields[0], monthPeriodField, ...teamReporterField];
}
return baseFields;
@@ -95,6 +149,34 @@ async function loadStatusDict() {
onMounted(() => {
loadStatusDict();
});
watch(
() => props.reportType,
type => {
if (type !== 'weekly') return;
model.value.periodStartDate = normalizeWeeklySearchRange(model.value.periodStartDate as string[] | undefined);
},
{ immediate: true }
);
watch(
() => model.value.periodStartDate,
value => {
if (props.reportType !== 'weekly') return;
const normalizedValue = normalizeWeeklySearchRange(value as string[] | undefined);
const currentValue = Array.isArray(value) ? value : [];
if (
normalizedValue?.length === currentValue.length &&
normalizedValue?.every((item, index) => item === currentValue[index])
) {
return;
}
model.value.periodStartDate = normalizedValue;
}
);
</script>
<template>

View File

@@ -1,18 +1,21 @@
<script setup lang="ts">
import { computed, ref } from 'vue';
import dayjs from 'dayjs';
import { fetchRemindTeamReport } from '@/service/api';
import { formatIsoWeekRangeLabel } from '../utils';
defineOptions({ name: 'TeamReportSummary' });
interface Props {
reportType: Api.WorkReport.Common.ReportType;
periodKey: string;
periodKeys?: string[];
periodLabel?: string;
loading?: boolean;
summary?: Api.WorkReport.Common.TeamReportSummary | null;
}
const props = withDefaults(defineProps<Props>(), {
periodKeys: () => [],
periodLabel: '',
loading: false,
summary: null
@@ -24,6 +27,34 @@ const emit = defineEmits<{
const remindingAll = ref(false);
const remindingUserId = ref('');
const canRemind = computed(() => props.periodKeys.length > 0);
function formatSummaryPeriodLabel() {
if (!props.summary?.periodStartDate || !props.summary?.periodEndDate) {
return props.periodLabel;
}
const start = dayjs(props.summary.periodStartDate);
const end = dayjs(props.summary.periodEndDate);
if (!start.isValid() || !end.isValid()) {
return `${props.summary.periodStartDate}${props.summary.periodEndDate}`;
}
if (props.reportType === 'monthly' || props.reportType === 'project') {
const startMonth = start.format('YYYY-MM');
const endMonth = end.format('YYYY-MM');
return startMonth === endMonth ? startMonth : `${startMonth}${endMonth}`;
}
if (props.reportType === 'weekly') {
return formatIsoWeekRangeLabel(props.summary.periodStartDate, props.summary.periodEndDate);
}
return `${start.format('YYYY-MM-DD')}${end.format('YYYY-MM-DD')}`;
}
const displayPeriodLabel = computed(() => formatSummaryPeriodLabel());
const cards = computed(() => [
{ label: '应填人数', value: props.summary?.totalShouldSubmit ?? 0 },
@@ -33,6 +64,8 @@ const cards = computed(() => [
]);
async function handleRemind(userIds?: string[]) {
if (!props.periodKeys.length) return;
const targetUserId = userIds?.length === 1 ? userIds[0] : '';
if (targetUserId) {
@@ -41,27 +74,36 @@ async function handleRemind(userIds?: string[]) {
remindingAll.value = true;
}
const { error, data } = await fetchRemindTeamReport({
reportType: props.reportType,
periodKey: props.periodKey,
userIds
});
const results = await Promise.all(
props.periodKeys.map(periodKey =>
fetchRemindTeamReport({
reportType: props.reportType,
periodKey,
userIds
})
)
);
if (!targetUserId) {
remindingAll.value = false;
}
remindingUserId.value = '';
if (error) return;
const remindedCount = results.reduce((total, result) => {
if (result.error) return total;
return total + (result.data?.remindedCount ?? 0);
}, 0);
window.$message?.success(`已催办 ${data?.remindedCount ?? 0}`);
if (!remindedCount) return;
window.$message?.success(props.periodKeys.length > 1 ? '已按所选区间发送催办提醒' : `已催办 ${remindedCount}`);
emit('reminded');
}
</script>
<template>
<div v-loading="props.loading" class="team-report-summary">
<div v-if="props.periodLabel" class="team-report-summary__period">{{ props.periodLabel }}</div>
<div v-if="displayPeriodLabel" class="team-report-summary__period">{{ displayPeriodLabel }}</div>
<div class="team-report-summary__grid">
<div v-for="card in cards" :key="card.label" class="team-report-summary__item">
@@ -83,6 +125,7 @@ async function handleRemind(userIds?: string[]) {
>
<span class="team-report-summary__user-name">{{ user.userNickname }}</span>
<ElButton
v-if="canRemind"
link
type="primary"
:loading="remindingUserId === user.userId"
@@ -94,7 +137,7 @@ async function handleRemind(userIds?: string[]) {
</div>
<ElEmpty v-else :image-size="60" description="暂无待提交人员" />
<div class="team-report-summary__popover-footer">
<div v-if="canRemind" class="team-report-summary__popover-footer">
<ElButton
size="small"
type="primary"