feat(工作报告、加班申请团队视角): 工作报告、加班申请现在可以查看团队视角了(查看下属)。

fix(工作报告): 修复周报在新增/编辑时,不能展示工作日志。
This commit is contained in:
dk
2026-06-14 23:57:42 +08:00
parent 17690283f6
commit 3c1cf6c7fa
19 changed files with 1618 additions and 94 deletions

View File

@@ -1,18 +1,19 @@
<script setup lang="tsx">
/* eslint-disable no-void */
import { markRaw, reactive, ref } from 'vue';
import { computed, markRaw, reactive, ref } from 'vue';
import { ElMessageBox, ElTag, ElTooltip } from 'element-plus';
import {
fetchDeleteWeeklyReport,
fetchExportWeeklyReportContent,
fetchGetTeamReportSummary,
fetchGetWeeklyReportPage,
fetchSubmitWeeklyReport
} from '@/service/api';
import { useAuth } from '@/hooks/business/auth';
import { useUIPaginatedTable } from '@/hooks/common/table';
import BusinessTableActionCell, { type BusinessTableAction } from '@/components/custom/business-table-action-cell';
import { type TeamViewContext, resolveTeamQueryUserIds } from '@/views/personal-center/shared/team-dashboard';
import {
WORK_REPORT_TYPE_LABEL,
type WorkReportRow,
createWeeklySearchParams,
createWorkReportContentExportFallbackName,
@@ -23,19 +24,27 @@ import {
formatPeriodDateRange,
formatWeeklyPeriodLabel,
getWorkReportStatusLabel,
getWorkReportTypeDisplayLabel,
resolveExportFilename,
resolveWorkReportStatusTagType,
transformWorkReportPage
} from '../shared/types';
import { resolveWorkReportSummaryPeriod } from '../shared/utils';
import TeamReportSummary from '../shared/components/team-report-summary.vue';
import WeeklyReportSearch from './modules/search-panel.vue';
import IconMdiDeleteOutline from '~icons/mdi/delete-outline';
import IconMdiEyeOutline from '~icons/mdi/eye-outline';
import IconMdiFileDocumentCheckOutline from '~icons/mdi/file-document-check-outline';
import IconMdiPencilOutline from '~icons/mdi/pencil-outline';
import IconMdiSendOutline from '~icons/mdi/send-outline';
import IconMdiDownloadOutline from '~icons/mdi/download-outline';
defineOptions({ name: 'WeeklyWorkReportIndex' });
const props = defineProps<{
teamContext?: TeamViewContext | null;
}>();
const emit = defineEmits<{
(e: 'create'): void;
(e: 'edit', row: WorkReportRow): void;
@@ -47,21 +56,33 @@ const { hasAuth } = useAuth();
const exporting = ref(false);
const selectedRows = ref<Api.WorkReport.Weekly.WeeklyReport[]>([]);
const searchParams = reactive(createWeeklySearchParams());
const teamSummaryLoading = ref(false);
const teamSummary = ref<Api.WorkReport.Common.TeamReportSummary | null>(null);
const ACTION_ICON_MAP = {
detail: markRaw(IconMdiEyeOutline),
edit: markRaw(IconMdiPencilOutline),
submit: markRaw(IconMdiSendOutline),
delete: markRaw(IconMdiDeleteOutline),
approvalRecord: markRaw(IconMdiFileDocumentCheckOutline)
approvalRecord: markRaw(IconMdiFileDocumentCheckOutline),
export: markRaw(IconMdiDownloadOutline)
};
const isTeamMode = computed(() => props.teamContext?.mode === 'team');
const isTeamRootSelected = computed(() => Boolean(isTeamMode.value && props.teamContext?.isRootSelected));
const currentTeamReporterIds = computed(() => resolveTeamQueryUserIds(props.teamContext));
const reportTitle = computed(() => getWorkReportTypeDisplayLabel('weekly', isTeamMode.value));
const table = useUIPaginatedTable<
Awaited<ReturnType<typeof fetchGetWeeklyReportPage>>,
Api.WorkReport.Weekly.WeeklyReport
>({
paginationProps: { currentPage: searchParams.pageNo, pageSize: searchParams.pageSize },
api: () => fetchGetWeeklyReportPage(searchParams),
api: () =>
fetchGetWeeklyReportPage({
...searchParams,
reporterIds: currentTeamReporterIds.value
}),
transform: response => transformWorkReportPage(response, searchParams.pageNo ?? 1, searchParams.pageSize ?? 10),
onPaginationParamsChange: params => {
searchParams.pageNo = params.currentPage ?? 1;
@@ -69,6 +90,7 @@ const table = useUIPaginatedTable<
},
columns: () => [
{ prop: 'index', type: 'index', label: '序号', width: 64 },
...(isTeamMode.value ? [{ prop: 'reporterName', label: '提交人', minWidth: 100, showOverflowTooltip: true }] : []),
{
prop: 'periodLabel',
label: '周期',
@@ -122,7 +144,7 @@ const table = useUIPaginatedTable<
{
prop: 'operate',
label: '操作',
width: 180,
width: isTeamMode.value ? 140 : 180,
align: 'center',
fixed: 'right',
formatter: row => <BusinessTableActionCell actions={getRowActions(row)} variant="icon" />
@@ -130,17 +152,53 @@ const table = useUIPaginatedTable<
]
});
const summaryPeriod = computed(() =>
resolveWorkReportSummaryPeriod('weekly', {
currentRow: table.data.value[0],
periodRange: searchParams.periodStartDate
})
);
function getRowActions(row: Api.WorkReport.Weekly.WeeklyReport): BusinessTableAction[] {
const actions: BusinessTableAction[] = [
{
key: 'detail',
label: '详情',
label: '查看',
buttonType: 'primary',
icon: ACTION_ICON_MAP.detail,
onClick: () => emit('detail', row)
}
];
if (isTeamMode.value) {
actions.push({
key: 'export',
label: '导出',
buttonType: 'success',
icon: ACTION_ICON_MAP.export,
onClick: () =>
exportReportContent(
{
exportAll: false,
ids: [row.id]
},
1
)
});
if (['approved', 'rejected'].includes(row.statusCode)) {
actions.push({
key: 'approval-record',
label: '审批记录',
buttonType: 'info',
icon: ACTION_ICON_MAP.approvalRecord,
onClick: () => emit('approvalRecord', row)
});
}
return actions;
}
if (['draft', 'rejected'].includes(row.statusCode) && row.allowEdit && hasAuth('project:work-report:update')) {
actions.push({
key: 'edit',
@@ -183,6 +241,7 @@ function getRowActions(row: Api.WorkReport.Weekly.WeeklyReport): BusinessTableAc
async function reload(page?: number) {
await table.getDataByPage(page ?? searchParams.pageNo ?? 1);
await loadTeamSummary();
}
function resetSearchParams() {
@@ -238,7 +297,10 @@ function handleSelectionChange(rows: Api.WorkReport.Weekly.WeeklyReport[]) {
function createExportSearchParams() {
const { pageNo: _pageNo, pageSize: _pageSize, ...params } = searchParams;
return params;
return {
...params,
reporterIds: currentTeamReporterIds.value
};
}
async function exportReportContent(
@@ -296,6 +358,23 @@ async function handleExportCommand(command: 'selected' | 'all') {
await handleExportAll();
}
async function loadTeamSummary() {
if (!isTeamRootSelected.value) {
teamSummaryLoading.value = false;
teamSummary.value = null;
return;
}
teamSummaryLoading.value = true;
const { error, data } = await fetchGetTeamReportSummary({
reportType: 'weekly',
periodKey: summaryPeriod.value.periodKey
});
teamSummaryLoading.value = false;
teamSummary.value = error || !data ? null : data;
}
defineExpose({ reload });
</script>
@@ -303,11 +382,21 @@ defineExpose({ reload });
<div class="flex-col-stretch gap-16px overflow-hidden">
<WeeklyReportSearch v-model:model="searchParams" @reset="resetSearchParams" @search="handleSearch" />
<TeamReportSummary
v-if="isTeamRootSelected"
report-type="weekly"
:period-key="summaryPeriod.periodKey"
:period-label="formatWeeklyPeriodLabel(summaryPeriod)"
:loading="teamSummaryLoading"
:summary="teamSummary"
@reminded="loadTeamSummary"
/>
<ElCard class="flex-1-hidden card-wrapper" body-class="business-table-card-body">
<template #header>
<div class="flex flex-wrap items-center justify-between gap-12px">
<div class="flex items-center gap-10px">
<p class="text-16px font-600">{{ WORK_REPORT_TYPE_LABEL.weekly }}</p>
<p class="text-16px font-600">{{ reportTitle }}</p>
<ElTag effect="plain">{{ table.mobilePagination.value.total || 0 }}</ElTag>
</div>
@@ -333,7 +422,13 @@ defineExpose({ reload });
</ElDropdownMenu>
</template>
</ElDropdown>
<ElButton v-auth="'project:work-report:create'" plain type="primary" @click="emit('create')">
<ElButton
v-if="!isTeamMode"
v-auth="'project:work-report:create'"
plain
type="primary"
@click="emit('create')"
>
<template #icon>
<icon-ic-round-plus class="text-icon" />
</template>