|
|
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
|
|
createPerformanceSheet,
|
|
|
|
|
downloadFile,
|
|
|
|
|
fetchPerformanceSheet,
|
|
|
|
|
fetchPerformanceSheetPrefill,
|
|
|
|
|
fetchPerformanceTemplateCurrent,
|
|
|
|
|
sendPerformanceSheet,
|
|
|
|
|
updatePerformanceSheetExcel,
|
|
|
|
|
@@ -54,12 +55,16 @@ const { createRequiredRule } = useFormRules();
|
|
|
|
|
|
|
|
|
|
const containerRef = ref<HTMLDivElement>();
|
|
|
|
|
const loading = ref(false);
|
|
|
|
|
const prefillLoading = ref(false);
|
|
|
|
|
const saving = ref(false);
|
|
|
|
|
const sending = ref(false);
|
|
|
|
|
const currentSheet = ref<Api.Performance.Sheet.Sheet | null>(null);
|
|
|
|
|
const currentTemplate = ref<Api.Performance.Template.Template | null>(null);
|
|
|
|
|
const errorMessage = ref('');
|
|
|
|
|
const prefillSourcePeriodMonth = ref('');
|
|
|
|
|
const viewportWidth = ref(typeof window === 'undefined' ? 1920 : window.innerWidth);
|
|
|
|
|
const loadedCreateContextKey = ref('');
|
|
|
|
|
const createContextInitializing = ref(false);
|
|
|
|
|
|
|
|
|
|
const createForm = reactive({
|
|
|
|
|
periodMonth: createDefaultPeriodMonth(),
|
|
|
|
|
@@ -380,6 +385,10 @@ function getActiveWorkbook() {
|
|
|
|
|
return univerAPI?.getActiveWorkbook?.();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function getCreateContextKey() {
|
|
|
|
|
return `${createForm.periodMonth || ''}::${createForm.employeeId || ''}`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseCellAddress(address?: string | null) {
|
|
|
|
|
const text = String(address || '')
|
|
|
|
|
.trim()
|
|
|
|
|
@@ -415,6 +424,29 @@ function readCell(address?: string | null) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function writeCell(address: string, value: string) {
|
|
|
|
|
const position = parseCellAddress(address);
|
|
|
|
|
const workbook = getActiveWorkbook();
|
|
|
|
|
const sheet = workbook?.getActiveSheet?.();
|
|
|
|
|
if (!position || !sheet) return false;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const range = sheet.getRange(position.row, position.column);
|
|
|
|
|
range?.setValue?.(value);
|
|
|
|
|
return true;
|
|
|
|
|
} catch {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyPrefillValues(cellValues: Record<string, string>) {
|
|
|
|
|
Object.entries(cellValues).forEach(([address, value]) => {
|
|
|
|
|
if (!address) return;
|
|
|
|
|
|
|
|
|
|
writeCell(address, value);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function readScores() {
|
|
|
|
|
const mapping = currentTemplate.value?.scoreCellMapping;
|
|
|
|
|
|
|
|
|
|
@@ -451,9 +483,36 @@ function createInitialFileName() {
|
|
|
|
|
return '绩效表.xlsx';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function applyCreatePrefill() {
|
|
|
|
|
prefillSourcePeriodMonth.value = '';
|
|
|
|
|
|
|
|
|
|
if (!isCreateMode.value || !createForm.employeeId || !createForm.periodMonth) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prefillLoading.value = true;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const { error, data } = await fetchPerformanceSheetPrefill(createForm.employeeId, createForm.periodMonth);
|
|
|
|
|
if (error || !data) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prefillSourcePeriodMonth.value = data.sourcePeriodMonth ?? '';
|
|
|
|
|
|
|
|
|
|
if (Object.keys(data.cellValues).length) {
|
|
|
|
|
applyPrefillValues(data.cellValues);
|
|
|
|
|
}
|
|
|
|
|
} finally {
|
|
|
|
|
prefillLoading.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadWorkbook() {
|
|
|
|
|
loading.value = true;
|
|
|
|
|
prefillLoading.value = false;
|
|
|
|
|
errorMessage.value = '';
|
|
|
|
|
prefillSourcePeriodMonth.value = '';
|
|
|
|
|
currentSheet.value = null;
|
|
|
|
|
currentTemplate.value = null;
|
|
|
|
|
|
|
|
|
|
@@ -505,6 +564,14 @@ async function loadWorkbook() {
|
|
|
|
|
const snapshot = await transformExcelToUniver(file);
|
|
|
|
|
await nextTick();
|
|
|
|
|
createWorkbook(applyCreateEmployeeName(snapshot));
|
|
|
|
|
|
|
|
|
|
if (isCreateMode.value) {
|
|
|
|
|
await nextTick();
|
|
|
|
|
await applyCreatePrefill();
|
|
|
|
|
loadedCreateContextKey.value = getCreateContextKey();
|
|
|
|
|
} else {
|
|
|
|
|
loadedCreateContextKey.value = '';
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
errorMessage.value = error instanceof Error ? error.message : 'Excel 解析失败';
|
|
|
|
|
} finally {
|
|
|
|
|
@@ -546,6 +613,7 @@ function createInitialPeriodMonth() {
|
|
|
|
|
function initializeCreateForm() {
|
|
|
|
|
createForm.periodMonth = createInitialPeriodMonth();
|
|
|
|
|
createForm.employeeId = props.initialEmployeeId || '';
|
|
|
|
|
prefillSourcePeriodMonth.value = '';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function executeSave(): Promise<PerformanceSheetSavePayload | null> {
|
|
|
|
|
@@ -643,31 +711,48 @@ watch(visible, async isVisible => {
|
|
|
|
|
disposeUniver();
|
|
|
|
|
currentSheet.value = null;
|
|
|
|
|
currentTemplate.value = null;
|
|
|
|
|
loadedCreateContextKey.value = '';
|
|
|
|
|
createContextInitializing.value = false;
|
|
|
|
|
prefillLoading.value = false;
|
|
|
|
|
prefillSourcePeriodMonth.value = '';
|
|
|
|
|
initializeCreateForm();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (isCreateMode.value) {
|
|
|
|
|
initializeCreateForm();
|
|
|
|
|
}
|
|
|
|
|
createContextInitializing.value = isCreateMode.value;
|
|
|
|
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
await loadWorkbook();
|
|
|
|
|
try {
|
|
|
|
|
if (isCreateMode.value) {
|
|
|
|
|
initializeCreateForm();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await nextTick();
|
|
|
|
|
await loadWorkbook();
|
|
|
|
|
} finally {
|
|
|
|
|
createContextInitializing.value = false;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
() => createForm.employeeId,
|
|
|
|
|
async (employeeId, previousEmployeeId) => {
|
|
|
|
|
if (!visible.value || !isCreateMode.value || !employeeId || employeeId === previousEmployeeId) {
|
|
|
|
|
() => [createForm.employeeId, createForm.periodMonth] as const,
|
|
|
|
|
async ([employeeId, periodMonth], [previousEmployeeId, previousPeriodMonth]) => {
|
|
|
|
|
if (!visible.value || !isCreateMode.value || createContextInitializing.value) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const workbook = getActiveWorkbook();
|
|
|
|
|
if (!workbook) return;
|
|
|
|
|
const currentKey = `${periodMonth || ''}::${employeeId || ''}`;
|
|
|
|
|
const previousKey = `${previousPeriodMonth || ''}::${previousEmployeeId || ''}`;
|
|
|
|
|
if (
|
|
|
|
|
currentKey === previousKey ||
|
|
|
|
|
currentKey === loadedCreateContextKey.value ||
|
|
|
|
|
loading.value ||
|
|
|
|
|
prefillLoading.value
|
|
|
|
|
) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const snapshot = workbook.save();
|
|
|
|
|
await nextTick();
|
|
|
|
|
createWorkbook(applyCreateEmployeeName(snapshot));
|
|
|
|
|
await loadWorkbook();
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
@@ -701,14 +786,24 @@ onMounted(() => {
|
|
|
|
|
type="month"
|
|
|
|
|
value-format="YYYY-MM"
|
|
|
|
|
placeholder="选择绩效月份"
|
|
|
|
|
:disabled="loading || prefillLoading || saving || sending"
|
|
|
|
|
/>
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
<ElFormItem label="下属" prop="employeeId" class="performance-excel-editor__form-item">
|
|
|
|
|
<ElSelect v-model="createForm.employeeId" filterable placeholder="选择下属" style="width: 200px">
|
|
|
|
|
<ElSelect
|
|
|
|
|
v-model="createForm.employeeId"
|
|
|
|
|
filterable
|
|
|
|
|
placeholder="选择下属"
|
|
|
|
|
style="width: 200px"
|
|
|
|
|
:disabled="loading || prefillLoading || saving || sending"
|
|
|
|
|
>
|
|
|
|
|
<ElOption v-for="opt in props.subordinateOptions" :key="opt.value" :label="opt.label" :value="opt.value" />
|
|
|
|
|
</ElSelect>
|
|
|
|
|
</ElFormItem>
|
|
|
|
|
</ElForm>
|
|
|
|
|
<div v-if="prefillLoading || prefillSourcePeriodMonth" class="performance-excel-editor__prefill-tip">
|
|
|
|
|
{{ prefillLoading ? '正在自动带出上一份绩效数据...' : `已自动带出 ${prefillSourcePeriodMonth} 的绩效数据` }}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-loading="loading" class="performance-excel-editor">
|
|
|
|
|
@@ -727,14 +822,26 @@ onMounted(() => {
|
|
|
|
|
<template v-if="canSave || showApprovalFooter" #footer>
|
|
|
|
|
<div class="performance-excel-editor__footer">
|
|
|
|
|
<template v-if="canSave">
|
|
|
|
|
<ElButton v-if="!props.hideDraftAction" :loading="saving" :disabled="sending" @click="handleSaveDraft">
|
|
|
|
|
<ElButton
|
|
|
|
|
v-if="!props.hideDraftAction"
|
|
|
|
|
:loading="saving"
|
|
|
|
|
:disabled="sending || loading || prefillLoading"
|
|
|
|
|
@click="handleSaveDraft"
|
|
|
|
|
>
|
|
|
|
|
保存草稿
|
|
|
|
|
</ElButton>
|
|
|
|
|
<ElButton type="primary" :loading="sending" :disabled="saving" @click="handleSaveAndSend">发送绩效</ElButton>
|
|
|
|
|
<ElButton
|
|
|
|
|
type="primary"
|
|
|
|
|
:loading="sending"
|
|
|
|
|
:disabled="saving || loading || prefillLoading"
|
|
|
|
|
@click="handleSaveAndSend"
|
|
|
|
|
>
|
|
|
|
|
发送绩效
|
|
|
|
|
</ElButton>
|
|
|
|
|
</template>
|
|
|
|
|
<template v-else-if="showApprovalFooter">
|
|
|
|
|
<ElButton @click="handleClose">退出审批</ElButton>
|
|
|
|
|
<ElButton type="primary" @click="handleStartApproval">开始审批</ElButton>
|
|
|
|
|
<ElButton @click="handleClose">退出</ElButton>
|
|
|
|
|
<ElButton type="primary" @click="handleStartApproval">审批</ElButton>
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
@@ -763,6 +870,12 @@ onMounted(() => {
|
|
|
|
|
margin-right: 24px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.performance-excel-editor__prefill-tip {
|
|
|
|
|
margin-top: 12px;
|
|
|
|
|
color: var(--el-text-color-secondary);
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.performance-excel-editor__footer {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|