feat(personal-center): 将加班申请功能重构为业务学习功能。

fix(week-report): 修复周报中工作日志的显示问题。
feat(work-report): 点击编辑时,自动刷新出最新的内容。
This commit is contained in:
dk
2026-07-19 14:04:51 +08:00
parent f6cf03a34a
commit 7f31bed6f6
14 changed files with 106 additions and 64 deletions

View File

@@ -408,7 +408,7 @@
"name": "personal-center_overtime-application", "name": "personal-center_overtime-application",
"path": "/personal-center/overtime-application", "path": "/personal-center/overtime-application",
"component": "view.personal-center_overtime-application", "component": "view.personal-center_overtime-application",
"title": "加班申请", "title": "业务学习",
"routeTitle": "personal-center_overtime-application", "routeTitle": "personal-center_overtime-application",
"i18nKey": "route.personal-center_overtime-application", "i18nKey": "route.personal-center_overtime-application",
"icon": "mdi:clock-plus-outline", "icon": "mdi:clock-plus-outline",
@@ -422,7 +422,7 @@
"redirect": null, "redirect": null,
"props": null, "props": null,
"meta": { "meta": {
"title": "加班申请", "title": "业务学习",
"i18nKey": "route.personal-center_overtime-application", "i18nKey": "route.personal-center_overtime-application",
"icon": "mdi:clock-plus-outline", "icon": "mdi:clock-plus-outline",
"localIcon": null, "localIcon": null,

View File

@@ -113,9 +113,9 @@ export const RDMS_REQ_CAN_DELETE_STATUS_DICT_CODE = 'rdms_req_can_delete_status'
export const RDMS_WORKLOG_DIFFICULTY_DICT_CODE = 'rdms_task_item_worklog_difficulty'; export const RDMS_WORKLOG_DIFFICULTY_DICT_CODE = 'rdms_task_item_worklog_difficulty';
/** /**
* 加班时长快捷选项字典编码 * 学习时长快捷选项字典编码
* *
* 对应业务字段:加班申请中的 overtimeDuration * 对应业务字段:业务学习中的 overtimeDuration
* 来源口径:`overtime-application-design.md` 明确时长下拉字典为 rdms_overtime_duration * 来源口径:`overtime-application-design.md` 明确时长下拉字典为 rdms_overtime_duration
*/ */
export const RDMS_OVERTIME_DURATION_DICT_CODE = 'rdms_overtime_duration'; export const RDMS_OVERTIME_DURATION_DICT_CODE = 'rdms_overtime_duration';

View File

@@ -104,7 +104,7 @@ const statusTagTypeRegistry: Record<StatusDomain, Record<string, StatusTagType>>
completed: 'success', completed: 'success',
cancelled: 'danger' cancelled: 'danger'
}, },
// 加班申请 // 业务学习
overtimeApplication: { overtimeApplication: {
pending: 'warning', pending: 'warning',
approved: 'success', approved: 'success',

View File

@@ -175,7 +175,7 @@ const local: App.I18n.Schema = {
'personal-center_work-report_project': '项目半月报', 'personal-center_work-report_project': '项目半月报',
'personal-center_my-performance': '我的绩效', 'personal-center_my-performance': '我的绩效',
'personal-center_my-application': '我的申请', 'personal-center_my-application': '我的申请',
'personal-center_overtime-application': '加班申请', 'personal-center_overtime-application': '业务学习',
'personal-center_pending-approval': '待我审批', 'personal-center_pending-approval': '待我审批',
feedback: '意见反馈', feedback: '意见反馈',
infra: '基础设施', infra: '基础设施',

View File

@@ -180,7 +180,7 @@ const { columns, columnChecks, data, loading, getDataByPage, mobilePagination, r
{ prop: 'index', type: 'index', label: '序号', width: 64 }, { prop: 'index', type: 'index', label: '序号', width: 64 },
{ {
prop: 'overtimeDate', prop: 'overtimeDate',
label: '加班日期', label: '学习日期',
width: 120, width: 120,
formatter: row => formatOvertimeDate(row.overtimeDate) formatter: row => formatOvertimeDate(row.overtimeDate)
} }
@@ -191,17 +191,17 @@ const { columns, columnChecks, data, loading, getDataByPage, mobilePagination, r
} }
cols.push( cols.push(
{ prop: 'overtimeDuration', label: '加班时长', width: 110, showOverflowTooltip: true }, { prop: 'overtimeDuration', label: '学习时长', width: 110, showOverflowTooltip: true },
{ {
prop: 'overtimeReason', prop: 'overtimeReason',
label: '加班原因', label: '学习原因',
minWidth: 180, minWidth: 180,
className: 'overtime-application__cell-ellipsis', className: 'overtime-application__cell-ellipsis',
formatter: row => formatEmptyText(row.overtimeReason) formatter: row => formatEmptyText(row.overtimeReason)
}, },
{ {
prop: 'overtimeContent', prop: 'overtimeContent',
label: '加班内容', label: '学习内容',
minWidth: 200, minWidth: 200,
className: 'overtime-application__cell-ellipsis', className: 'overtime-application__cell-ellipsis',
formatter: row => formatEmptyText(row.overtimeContent) formatter: row => formatEmptyText(row.overtimeContent)
@@ -357,7 +357,7 @@ async function handleExport() {
return; return;
} }
downloadBlob(blob, `加班申请_${dayjs().format('YYYY-MM-DD')}.xls`); downloadBlob(blob, `业务学习_${dayjs().format('YYYY-MM-DD')}.xls`);
} }
async function loadSubordinateTree() { async function loadSubordinateTree() {
@@ -485,7 +485,7 @@ watch(
<template #header> <template #header>
<div class="flex flex-wrap items-center justify-between gap-12px"> <div class="flex flex-wrap items-center justify-between gap-12px">
<div class="flex items-center gap-10px"> <div class="flex items-center gap-10px">
<p class="text-16px font-600">加班申请</p> <p class="text-16px font-600">业务学习</p>
<ElTag effect="plain">{{ totalCount }}</ElTag> <ElTag effect="plain">{{ totalCount }}</ElTag>
</div> </div>
<TableHeaderOperation v-model:columns="columnChecks" :loading="loading" @refresh="reloadTable"> <TableHeaderOperation v-model:columns="columnChecks" :loading="loading" @refresh="reloadTable">
@@ -592,7 +592,7 @@ watch(
white-space: nowrap; white-space: nowrap;
} }
/* 加班原因/加班内容:单元格内容溢出时仅显示省略号,不弹出 tooltip */ /* 学习原因/学习内容:单元格内容溢出时仅显示省略号,不弹出 tooltip */
:deep(.overtime-application__cell-ellipsis .cell) { :deep(.overtime-application__cell-ellipsis .cell) {
display: block; display: block;
overflow: hidden; overflow: hidden;

View File

@@ -49,7 +49,7 @@ watch(
<template> <template>
<BusinessFormDialog <BusinessFormDialog
v-model="visible" v-model="visible"
title="加班申请审批记录" title="业务学习审批记录"
width="820px" width="820px"
:loading="loading" :loading="loading"
:show-footer="false" :show-footer="false"

View File

@@ -12,9 +12,9 @@ defineOptions({ name: 'OvertimeApplicationBatchDetailDialog' });
type ActionType = 'approve' | 'reject'; type ActionType = 'approve' | 'reject';
interface Props { interface Props {
/** 选中的加班申请 id 列表(原始 id */ /** 选中的业务学习 id 列表(原始 id */
selectedIds: string[]; selectedIds: string[];
/** 全部加班申请行数据,用于通过 id 查找 */ /** 全部业务学习行数据,用于通过 id 查找 */
rows: Api.OvertimeApplication.OvertimeApplication[]; rows: Api.OvertimeApplication.OvertimeApplication[];
actionLoading?: boolean; actionLoading?: boolean;
} }
@@ -163,23 +163,23 @@ watch(
<ElDescriptionsItem label="申请人" label-class-name="overtime-application-detail-dialog__label"> <ElDescriptionsItem label="申请人" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.applicantName }} {{ detailData.applicantName }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="加班日期" label-class-name="overtime-application-detail-dialog__label--compact"> <ElDescriptionsItem label="学习日期" label-class-name="overtime-application-detail-dialog__label--compact">
{{ formatOvertimeDate(detailData.overtimeDate) }} {{ formatOvertimeDate(detailData.overtimeDate) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="加班时长" label-class-name="overtime-application-detail-dialog__label"> <ElDescriptionsItem label="学习时长" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.overtimeDuration }} {{ detailData.overtimeDuration }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="提交时间" label-class-name="overtime-application-detail-dialog__label--compact"> <ElDescriptionsItem label="提交时间" label-class-name="overtime-application-detail-dialog__label--compact">
{{ formatOvertimeDateTime(detailData.submitTime) }} {{ formatOvertimeDateTime(detailData.submitTime) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="加班原因" :span="2" label-class-name="overtime-application-detail-dialog__label"> <ElDescriptionsItem label="学习原因" :span="2" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.overtimeReason }} {{ detailData.overtimeReason }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="加班内容" :span="2" label-class-name="overtime-application-detail-dialog__label"> <ElDescriptionsItem label="学习内容" :span="2" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.overtimeContent }} {{ detailData.overtimeContent }}
</ElDescriptionsItem> </ElDescriptionsItem>
</ElDescriptions> </ElDescriptions>
<ElEmpty v-else description="未获取到加班申请详情" /> <ElEmpty v-else description="未获取到业务学习详情" />
<div class="batch-detail__approval-form"> <div class="batch-detail__approval-form">
<div class="audit-field"> <div class="audit-field">

View File

@@ -114,28 +114,28 @@ watch(
</script> </script>
<template> <template>
<BusinessFormDialog v-model="visible" title="加班申请详情" preset="md" :loading="loading"> <BusinessFormDialog v-model="visible" title="业务学习详情" preset="md" :loading="loading">
<ElDescriptions v-if="detailData" class="overtime-application-detail-dialog__descriptions" :column="2" border> <ElDescriptions v-if="detailData" class="overtime-application-detail-dialog__descriptions" :column="2" border>
<ElDescriptionsItem label="申请人" label-class-name="overtime-application-detail-dialog__label"> <ElDescriptionsItem label="申请人" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.applicantName }} {{ detailData.applicantName }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="加班日期" label-class-name="overtime-application-detail-dialog__label--compact"> <ElDescriptionsItem label="学习日期" label-class-name="overtime-application-detail-dialog__label--compact">
{{ formatOvertimeDate(detailData.overtimeDate) }} {{ formatOvertimeDate(detailData.overtimeDate) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="加班时长" label-class-name="overtime-application-detail-dialog__label"> <ElDescriptionsItem label="学习时长" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.overtimeDuration }} {{ detailData.overtimeDuration }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="提交时间" label-class-name="overtime-application-detail-dialog__label--compact"> <ElDescriptionsItem label="提交时间" label-class-name="overtime-application-detail-dialog__label--compact">
{{ formatOvertimeDateTime(detailData.submitTime) }} {{ formatOvertimeDateTime(detailData.submitTime) }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="加班原因" :span="2" label-class-name="overtime-application-detail-dialog__label"> <ElDescriptionsItem label="学习原因" :span="2" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.overtimeReason }} {{ detailData.overtimeReason }}
</ElDescriptionsItem> </ElDescriptionsItem>
<ElDescriptionsItem label="加班内容" :span="2" label-class-name="overtime-application-detail-dialog__label"> <ElDescriptionsItem label="学习内容" :span="2" label-class-name="overtime-application-detail-dialog__label">
{{ detailData.overtimeContent }} {{ detailData.overtimeContent }}
</ElDescriptionsItem> </ElDescriptionsItem>
</ElDescriptions> </ElDescriptions>
<ElEmpty v-else description="未获取到加班申请详情" /> <ElEmpty v-else description="未获取到业务学习详情" />
<div v-if="props.showApprovalActions" class="overtime-application-detail-dialog__approval-form"> <div v-if="props.showApprovalActions" class="overtime-application-detail-dialog__approval-form">
<div class="audit-field"> <div class="audit-field">

View File

@@ -43,21 +43,21 @@ const approverName = ref('');
const currentUserName = computed(() => authStore.userInfo.nickname || authStore.userInfo.userName || '--'); const currentUserName = computed(() => authStore.userInfo.nickname || authStore.userInfo.userName || '--');
const isEdit = computed(() => props.operateType === 'edit'); const isEdit = computed(() => props.operateType === 'edit');
const title = computed(() => (isEdit.value ? '修改并重新提交' : '新增加班申请')); const title = computed(() => (isEdit.value ? '修改并重新提交' : '新增业务学习'));
const model = reactive<Api.OvertimeApplication.SaveOvertimeApplicationParams>(createDefaultModel()); const model = reactive<Api.OvertimeApplication.SaveOvertimeApplicationParams>(createDefaultModel());
const rules = computed( const rules = computed(
() => () =>
({ ({
overtimeDate: [createRequiredRule('请选择加班日期')], overtimeDate: [createRequiredRule('请选择学习日期')],
overtimeDuration: [createRequiredRule('请选择加班时长')], overtimeDuration: [createRequiredRule('请选择学习时长')],
overtimeReason: [ overtimeReason: [
createRequiredRule('请输入加班原因'), createRequiredRule('请输入学习原因'),
{ {
validator: (_rule, value: string, callback) => { validator: (_rule, value: string, callback) => {
if (!value?.trim()) { if (!value?.trim()) {
callback(new Error('请输入加班原因')); callback(new Error('请输入学习原因'));
return; return;
} }
@@ -67,11 +67,11 @@ const rules = computed(
} }
], ],
overtimeContent: [ overtimeContent: [
createRequiredRule('请输入加班内容'), createRequiredRule('请输入学习内容'),
{ {
validator: (_rule, value: string, callback) => { validator: (_rule, value: string, callback) => {
if (!value?.trim()) { if (!value?.trim()) {
callback(new Error('请输入加班内容')); callback(new Error('请输入学习内容'));
return; return;
} }
@@ -152,7 +152,7 @@ async function handleSubmit() {
return; return;
} }
window.$message?.success(isEdit.value ? '加班申请已重新提交' : '加班申请已提交'); window.$message?.success(isEdit.value ? '业务学习已重新提交' : '业务学习已提交');
visible.value = false; visible.value = false;
emit('submitted'); emit('submitted');
} }
@@ -199,52 +199,52 @@ watch(
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem label="加班日期" prop="overtimeDate" style="width: 100%"> <ElFormItem label="学习日期" prop="overtimeDate" style="width: 100%">
<ElDatePicker <ElDatePicker
v-model="model.overtimeDate" v-model="model.overtimeDate"
class="w-full" class="w-full"
type="date" type="date"
value-format="YYYY-MM-DD" value-format="YYYY-MM-DD"
style="width: 100%" style="width: 100%"
placeholder="请选择加班日期" placeholder="请选择学习日期"
/> />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem label="加班时长" prop="overtimeDuration"> <ElFormItem label="学习时长" prop="overtimeDuration">
<DictSelect <DictSelect
v-model="model.overtimeDuration" v-model="model.overtimeDuration"
:dict-code="RDMS_OVERTIME_DURATION_DICT_CODE" :dict-code="RDMS_OVERTIME_DURATION_DICT_CODE"
placeholder="请选择加班时长" placeholder="请选择学习时长"
/> />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
</ElRow> </ElRow>
</BusinessFormSection> </BusinessFormSection>
<BusinessFormSection title="加班说明"> <BusinessFormSection title="学习说明">
<ElRow :gutter="16"> <ElRow :gutter="16">
<ElCol :span="24"> <ElCol :span="24">
<ElFormItem label="加班原因" prop="overtimeReason"> <ElFormItem label="学习原因" prop="overtimeReason">
<ElInput <ElInput
v-model="model.overtimeReason" v-model="model.overtimeReason"
type="textarea" type="textarea"
:rows="3" :rows="3"
maxlength="500" maxlength="500"
show-word-limit show-word-limit
placeholder="请输入加班原因" placeholder="请输入学习原因"
/> />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>
<ElCol :span="24"> <ElCol :span="24">
<ElFormItem label="加班内容" prop="overtimeContent"> <ElFormItem label="学习内容" prop="overtimeContent">
<ElInput <ElInput
v-model="model.overtimeContent" v-model="model.overtimeContent"
type="textarea" type="textarea"
:rows="4" :rows="4"
maxlength="1000" maxlength="1000"
show-word-limit show-word-limit
placeholder="请输入加班内容" placeholder="请输入学习内容"
/> />
</ElFormItem> </ElFormItem>
</ElCol> </ElCol>

View File

@@ -105,9 +105,9 @@ const fields = computed<SearchField[]>(() => {
: []), : []),
{ {
key: 'overtimeDate', key: 'overtimeDate',
label: '加班日期', label: '学习日期',
type: 'dateRange', type: 'dateRange',
placeholder: '请选择加班日期' placeholder: '请选择学习日期'
}, },
{ {
key: 'statusCode', key: 'statusCode',

View File

@@ -388,7 +388,7 @@ async function pullDefaultDraft(confirmOverwrite = false) {
} }
} }
async function refreshDraft() { async function refreshDraft(showSuccess = true) {
if (props.mode !== 'edit') { if (props.mode !== 'edit') {
await pullDefaultDraft(true); await pullDefaultDraft(true);
return; return;
@@ -433,7 +433,9 @@ async function refreshDraft() {
applyProjectEditableFields(result.data as Api.WorkReport.Project.ProjectReport); applyProjectEditableFields(result.data as Api.WorkReport.Project.ProjectReport);
} }
window.$message?.success('最新数据已刷新'); if (showSuccess) {
window.$message?.success('最新数据已刷新');
}
} }
async function loadInitData() { async function loadInitData() {
@@ -479,6 +481,9 @@ watch(visible, async isVisible => {
if (props.rowData?.id) { if (props.rowData?.id) {
await loadDetail(props.rowData.id); await loadDetail(props.rowData.id);
if (props.mode === 'edit') {
await refreshDraft(false);
}
await fillMonthlyPerformanceResult(); await fillMonthlyPerformanceResult();
} }
}); });

View File

@@ -100,7 +100,7 @@ const activeEditField = ref('');
const travelDialogVisible = ref(false); const travelDialogVisible = ref(false);
const isReadonly = computed(() => props.mode === 'detail' || props.scene === 'approval'); const isReadonly = computed(() => props.mode === 'detail' || props.scene === 'approval');
const { dictData: priorityDictData, getLabel: getPriorityLabel } = useDict(RDMS_REQ_PRIORITY_DICT_CODE); const { dictData: priorityDictData, getLabel: getPriorityLabel } = useDict(RDMS_REQ_PRIORITY_DICT_CODE);
const { getLabel: getTaskItemTypeLabel } = useDict(RDMS_TASK_ITEM_TYPE_DICT_CODE); const { dictData: taskItemTypeDictData, getLabel: getTaskItemTypeLabel } = useDict(RDMS_TASK_ITEM_TYPE_DICT_CODE);
const DEFAULT_SECTION_CATEGORY = '工作内容'; const DEFAULT_SECTION_CATEGORY = '工作内容';
const TRAVEL_SECTION_CATEGORY = '差旅'; const TRAVEL_SECTION_CATEGORY = '差旅';
@@ -613,6 +613,41 @@ function createStructuredSectionsFromTextV2(
const legacyTaskLineRe = const legacyTaskLineRe =
/^(?!(?:(?:\d+[..、])|(?:\d+\s+)|(?:[一二三四五六七八九十百千万]+[、.]))\s*)(.+?)\s*[-]\s*(.+)$/u; /^(?!(?:(?:\d+[..、])|(?:\d+\s+)|(?:[一二三四五六七八九十百千万]+[、.]))\s*)(.+?)\s*[-]\s*(.+)$/u;
const isKnownSectionCategory = (category: string) => {
const normalizedCategory = category.trim();
if (!normalizedCategory) return false;
if ([DEFAULT_SECTION_CATEGORY, TRAVEL_SECTION_CATEGORY, MY_AFFAIRS_TITLE].includes(normalizedCategory)) return true;
return taskItemTypeDictData.value.some(item => item.label === normalizedCategory);
};
const isLegacyStructuredTaskLine = (line: string, strict = false) => {
const legacyMatch = line.match(legacyTaskLineRe);
if (!legacyMatch) return false;
const [, rawCategory, rawTaskText] = legacyMatch;
const category = rawCategory.trim();
const normalizedTaskText = rawTaskText.trim();
if (!category || !normalizedTaskText) return false;
const { rawTitle, metricsText, detail } = extractStructuredTaskParts(normalizedTaskText);
const title = stripStructuredTaskSuffix(rawTitle);
if (!title) return false;
const hasTaskStructureHint =
Boolean(detail) ||
isStructuredMetricsText(metricsText) ||
normalizedTaskText.includes('') ||
normalizedTaskText.includes('(') ||
normalizedTaskText.includes('') ||
normalizedTaskText.includes(':');
if (isKnownSectionCategory(category) || hasTaskStructureHint) return true;
if (strict) return false;
return category.length <= 12;
};
const isExplicitStructuredTaskLine = (line: string) => { const isExplicitStructuredTaskLine = (line: string) => {
if (!STRUCTURED_TASK_PREFIX_RE.test(line)) return false; if (!STRUCTURED_TASK_PREFIX_RE.test(line)) return false;
const normalizedLine = stripStructuredTaskPrefix(line); const normalizedLine = stripStructuredTaskPrefix(line);
@@ -623,7 +658,7 @@ function createStructuredSectionsFromTextV2(
const shouldAppendToPreviousTaskDetail = (line: string) => { const shouldAppendToPreviousTaskDetail = (line: string) => {
if (!previousTask?.detail) return false; if (!previousTask?.detail) return false;
if (line.startsWith('#')) return false; if (line.startsWith('#')) return false;
if (legacyTaskLineRe.test(line)) return false; if (isLegacyStructuredTaskLine(line, true)) return false;
if (isExplicitStructuredTaskLine(line)) return false; if (isExplicitStructuredTaskLine(line)) return false;
return true; return true;
}; };
@@ -641,7 +676,9 @@ function createStructuredSectionsFromTextV2(
// 仅当行首不是结构化任务前缀(如 "3、")时,才按旧式 "<分类> - <事项>" 解析; // 仅当行首不是结构化任务前缀(如 "3、")时,才按旧式 "<分类> - <事项>" 解析;
// 否则会把 "2026-06-12 - 2026-06-19" 这种含 " - " 的出差行误判为分类。 // 否则会把 "2026-06-12 - 2026-06-19" 这种含 " - " 的出差行误判为分类。
const legacyMatch = trimmedLine.match(legacyTaskLineRe); const legacyMatch = isLegacyStructuredTaskLine(trimmedLine, Boolean(previousTask?.detail))
? trimmedLine.match(legacyTaskLineRe)
: null;
if (legacyMatch) { if (legacyMatch) {
const [, rawCategory, rawTaskText] = legacyMatch; const [, rawCategory, rawTaskText] = legacyMatch;
const category = rawCategory.trim(); const category = rawCategory.trim();

View File

@@ -255,11 +255,11 @@ function buildTeamBarOption(): ECOption {
if (!params.length) return ''; if (!params.length) return '';
const name = params[0].axisValue as string; const name = params[0].axisValue as string;
const totalHours = Number(params.find((p: any) => p.seriesName === '总工时')?.value ?? 0); const totalHours = Number(params.find((p: any) => p.seriesName === '总工时')?.value ?? 0);
const overtimeHours = Number(params.find((p: any) => p.seriesName === '加班工时')?.value ?? 0); const overtimeHours = Number(params.find((p: any) => p.seriesName === '学习工时')?.value ?? 0);
return [ return [
`<div style="font-weight:600;margin-bottom:4px">${name}</div>`, `<div style="font-weight:600;margin-bottom:4px">${name}</div>`,
`总工时:${totalHours}h`, `总工时:${totalHours}h`,
`加班工时:${overtimeHours}h` `学习工时:${overtimeHours}h`
].join('<br/>'); ].join('<br/>');
} }
}, },
@@ -288,7 +288,7 @@ function buildTeamBarOption(): ECOption {
} }
}, },
{ {
name: '加班工时', name: '学习工时',
type: 'bar', type: 'bar',
barWidth: 16, barWidth: 16,
data: v.members.map(member => member.overtimeHours), data: v.members.map(member => member.overtimeHours),

View File

@@ -145,7 +145,7 @@ const approvalBizTabs: Array<{ key: ApprovalBizType; label: string }> = [
{ key: 'monthly', label: '月报' }, { key: 'monthly', label: '月报' },
{ key: 'project', label: '项目半月报' }, { key: 'project', label: '项目半月报' },
{ key: 'performance', label: '我的绩效' }, { key: 'performance', label: '我的绩效' },
{ key: 'overtime_application', label: '加班申请' } { key: 'overtime_application', label: '业务学习' }
]; ];
const hasWorkReportApprovePermission = computed(() => hasButtonPermission('project:work-report:approve')); const hasWorkReportApprovePermission = computed(() => hasButtonPermission('project:work-report:approve'));
@@ -224,7 +224,7 @@ const performanceActionVisible = ref(false);
const currentPerformanceSheet = ref<Api.Performance.Sheet.Sheet | null>(null); const currentPerformanceSheet = ref<Api.Performance.Sheet.Sheet | null>(null);
const currentPerformanceActionType = ref<PerformanceApprovalActionType>('confirm'); const currentPerformanceActionType = ref<PerformanceApprovalActionType>('confirm');
// 批量审批选中状态(存原始加班申请 id避免映射转换 // 批量审批选中状态(存原始业务学习 id避免映射转换
const selectedOvertimeIds = ref<Set<string>>(new Set()); const selectedOvertimeIds = ref<Set<string>>(new Set());
const APPROVAL_ENTRY_ICON = markRaw(IconMdiClipboardCheckOutline); const APPROVAL_ENTRY_ICON = markRaw(IconMdiClipboardCheckOutline);
@@ -234,7 +234,7 @@ function getApprovalCategoryLabel(bizType: ApprovalBizType) {
if (bizType === 'monthly') return '月报'; if (bizType === 'monthly') return '月报';
if (bizType === 'project') return '项目半月报'; if (bizType === 'project') return '项目半月报';
if (bizType === 'performance') return '我的绩效'; if (bizType === 'performance') return '我的绩效';
if (bizType === 'overtime_application') return '加班申请'; if (bizType === 'overtime_application') return '业务学习';
return '待审批'; return '待审批';
} }
@@ -786,7 +786,7 @@ async function handleOvertimeActionSubmit(payload: { actionType: OvertimeApprova
} }
overtimeDetailVisible.value = false; overtimeDetailVisible.value = false;
window.$message?.success(payload.actionType === 'approve' ? '加班申请已通过' : '加班申请已退回'); window.$message?.success(payload.actionType === 'approve' ? '业务学习已通过' : '业务学习已退回');
await loadOvertimeApprovalItems(); await loadOvertimeApprovalItems();
} }
@@ -902,7 +902,7 @@ watch(activeApprovalBizType, () => {
clearOvertimeSelection(); clearOvertimeSelection();
}); });
// 当前页加班申请是否全部选中(用于全选复选框状态) // 当前页业务学习是否全部选中(用于全选复选框状态)
const allOvertimeItemsSelected = computed(() => { const allOvertimeItemsSelected = computed(() => {
const items = overtimeApprovalItems.value; const items = overtimeApprovalItems.value;
if (items.length === 0) return false; if (items.length === 0) return false;
@@ -980,11 +980,11 @@ async function loadOvertimeApprovalItems() {
data.list.map(item => ({ data.list.map(item => ({
id: `overtime-application-${item.id}`, id: `overtime-application-${item.id}`,
category: 'approval', category: 'approval',
categoryLabel: '加班申请', categoryLabel: '业务学习',
title: `${item.applicantName} · ${item.overtimeDate.slice(5, 7)}加班 ${item.overtimeDuration} 申请待审批`, title: `${item.applicantName} · ${item.overtimeDate.slice(5, 7)}学习 ${item.overtimeDuration} 申请待审批`,
createdTime: item.submitTime || item.createTime, createdTime: item.submitTime || item.createTime,
deadline: item.submitTime || item.createTime, deadline: item.submitTime || item.createTime,
source: `加班申请 · ${item.applicantName}`, source: `业务学习 · ${item.applicantName}`,
priority: 'mid', priority: 'mid',
approvalBizType: 'overtime_application', approvalBizType: 'overtime_application',
approvalBizId: item.id approvalBizId: item.id
@@ -1198,7 +1198,7 @@ onActivated(refresh);
</div> </div>
<div class="workbench-todo__content"> <div class="workbench-todo__content">
<!-- 批量操作栏加班申请待审批时显示 --> <!-- 批量操作栏业务学习待审批时显示 -->
<div <div
v-if=" v-if="
activeTab === 'approval' && activeTab === 'approval' &&
@@ -1239,7 +1239,7 @@ onActivated(refresh);
}" }"
> >
<div class="workbench-todo__leading"> <div class="workbench-todo__leading">
<!-- 加班申请待审批时显示复选框 --> <!-- 业务学习待审批时显示复选框 -->
<ElCheckbox <ElCheckbox
v-if=" v-if="
activeTab === 'approval' && activeTab === 'approval' &&