refactor(admin): 按 aislop 质量门禁修复两个代码质量警告
- RoleTour: buildSteps 拆分为各角色的声明式步骤常量,函数从 91 行降至组装逻辑 - ImportWizardModal: 抽取 uploadRun 共享 helper,消除 handleUpload/handleReupload 之间重复的上传进度与 loading 状态处理 aislop scan --changes: 99/100 → 100/100 (5 引擎 0 issues)
This commit is contained in:
@@ -40,6 +40,7 @@ import {
|
||||
type ImportPreviewResult,
|
||||
type ImportReceipt,
|
||||
type ImportRunDetail,
|
||||
type ImportStageRequest,
|
||||
type ImportStepKey,
|
||||
} from './types';
|
||||
|
||||
@@ -187,25 +188,41 @@ export const ImportWizardModal: React.FC<ImportWizardModalProps> = ({
|
||||
return [...headers];
|
||||
}, [run, activeStepKey, sheetSelection]);
|
||||
|
||||
const handleUpload: UploadProps['customRequest'] = async (options) => {
|
||||
const file = options.file as File;
|
||||
/** 创建导入任务并加载详情:统一处理上传进度与 loading 状态。成功返回 run 详情,失败返回 null */
|
||||
const uploadRun = async (
|
||||
file: File,
|
||||
options: {
|
||||
source: 'ai' | 'manual';
|
||||
conversationId?: number;
|
||||
stages?: ImportStageRequest[];
|
||||
mapping?: Record<string, Record<string, string>>;
|
||||
},
|
||||
errorMessage: string,
|
||||
): Promise<ImportRunDetail | null> => {
|
||||
setUploading(true);
|
||||
setUploadPercent(0);
|
||||
try {
|
||||
const detail = await createImportRun(file, {
|
||||
source: 'manual',
|
||||
...options,
|
||||
onProgress: (percent) => setUploadPercent(percent),
|
||||
});
|
||||
await loadRun(detail.id);
|
||||
message.success(`已识别 ${detail.sheets.length} 个工作表`);
|
||||
return detail;
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '文件上传失败');
|
||||
message.error(error instanceof Error ? error.message : errorMessage);
|
||||
return null;
|
||||
} finally {
|
||||
setUploading(false);
|
||||
setUploadPercent(0);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpload: UploadProps['customRequest'] = async (options) => {
|
||||
const file = options.file as File;
|
||||
const detail = await uploadRun(file, { source: 'manual' }, '文件上传失败');
|
||||
if (detail) message.success(`已识别 ${detail.sheets.length} 个工作表`);
|
||||
};
|
||||
|
||||
const handlePreview = async () => {
|
||||
if (!run || !activeStepKey || !activeStep) return;
|
||||
const mapping = mappingDraft[activeStepKey] ?? {};
|
||||
@@ -262,23 +279,16 @@ export const ImportWizardModal: React.FC<ImportWizardModalProps> = ({
|
||||
|
||||
const handleReupload = async (file: File) => {
|
||||
if (!run || !activeStepKey) return;
|
||||
setUploading(true);
|
||||
setUploadPercent(0);
|
||||
try {
|
||||
const detail = await createImportRun(file, {
|
||||
const detail = await uploadRun(
|
||||
file,
|
||||
{
|
||||
source: 'manual',
|
||||
stages: [{ stepKey: activeStepKey, sheets: sheetSelection[activeStepKey] ?? [] }],
|
||||
mapping: { [activeStepKey]: mappingDraft[activeStepKey] ?? {} },
|
||||
onProgress: (percent) => setUploadPercent(percent),
|
||||
});
|
||||
await loadRun(detail.id);
|
||||
message.success('已重新上传,并保留原列映射');
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '重新上传失败');
|
||||
} finally {
|
||||
setUploading(false);
|
||||
setUploadPercent(0);
|
||||
}
|
||||
},
|
||||
'重新上传失败',
|
||||
);
|
||||
if (detail) message.success('已重新上传,并保留原列映射');
|
||||
};
|
||||
|
||||
const previewRows = useMemo(() => {
|
||||
|
||||
@@ -25,94 +25,100 @@ function menuTarget(label: string): () => HTMLElement {
|
||||
const aiTarget = (): HTMLElement =>
|
||||
document.querySelector<HTMLElement>('button[aria-label="打开 AI 助理"]') as HTMLElement;
|
||||
|
||||
function buildSteps(domains: Set<string>): TourStep[] {
|
||||
const steps: TourStep[] = [];
|
||||
const has = (key: string) => domains.has(key);
|
||||
/** 各角色的引导步骤(声明式,按角色域组装) */
|
||||
const TEACHER_STEPS: TourStep[] = [
|
||||
{
|
||||
target: menuTarget('今日教学'),
|
||||
title: '今日教学',
|
||||
description: '在这里查看今天的课程安排。课程开始后可以拉取钉钉考勤并点名。',
|
||||
},
|
||||
{
|
||||
target: menuTarget('课程考勤'),
|
||||
title: '课程考勤',
|
||||
description: '查看历史考勤记录,课程截止后系统会自动结算缺勤。',
|
||||
},
|
||||
];
|
||||
|
||||
if (has('teacher')) {
|
||||
steps.push(
|
||||
{
|
||||
target: menuTarget('今日教学'),
|
||||
title: '今日教学',
|
||||
description: '在这里查看今天的课程安排。课程开始后可以拉取钉钉考勤并点名。',
|
||||
},
|
||||
{
|
||||
target: menuTarget('课程考勤'),
|
||||
title: '课程考勤',
|
||||
description: '查看历史考勤记录,课程截止后系统会自动结算缺勤。',
|
||||
},
|
||||
);
|
||||
}
|
||||
if (has('academic')) {
|
||||
steps.push(
|
||||
{
|
||||
target: menuTarget('学生管理'),
|
||||
title: '学生管理',
|
||||
description: '管理学生档案:可单个录入、Excel 批量导入,或用 AI 助手帮你录入。',
|
||||
},
|
||||
{
|
||||
target: menuTarget('班级管理'),
|
||||
title: '班级管理',
|
||||
description: '学生入学后先分班,再排课、考勤,形成完整教学闭环。',
|
||||
},
|
||||
);
|
||||
}
|
||||
if (has('accommodation')) {
|
||||
steps.push(
|
||||
{
|
||||
target: menuTarget('住宿总览'),
|
||||
title: '住宿总览',
|
||||
description: '可视化查看各宿舍入住情况,支持历史日期回溯和查寝。',
|
||||
},
|
||||
{
|
||||
target: menuTarget('入住管理'),
|
||||
title: '入住管理',
|
||||
description: '学生入住/退宿/换宿都在这里办理。入住后再录费用、生成账单。',
|
||||
},
|
||||
{
|
||||
target: menuTarget('账单管理'),
|
||||
title: '账单管理',
|
||||
description: '每月生成账单、确认并标记已付,完成「住宿→计费」闭环。',
|
||||
},
|
||||
);
|
||||
}
|
||||
if (has('classroom')) {
|
||||
steps.push(
|
||||
{
|
||||
target: menuTarget('教室排期'),
|
||||
title: '教室排期',
|
||||
description: '查看教室占用与排期,避免时间冲突。',
|
||||
},
|
||||
{
|
||||
target: menuTarget('租赁订单'),
|
||||
title: '租赁订单',
|
||||
description: '管理教室租赁订单与合同,跟踪租期与状态。',
|
||||
},
|
||||
);
|
||||
}
|
||||
if (has('system') || has('super')) {
|
||||
steps.push(
|
||||
{
|
||||
target: menuTarget('账号管理'),
|
||||
title: '账号管理',
|
||||
description: '管理系统账号、角色与权限,为不同岗位分配对应能力。',
|
||||
},
|
||||
);
|
||||
}
|
||||
if (has('super')) {
|
||||
steps.unshift({
|
||||
target: menuTarget('数据面板'),
|
||||
title: '数据面板',
|
||||
description: '全局经营概览:入住率、收入、待办都在这里。',
|
||||
});
|
||||
}
|
||||
const ACADEMIC_STEPS: TourStep[] = [
|
||||
{
|
||||
target: menuTarget('学生管理'),
|
||||
title: '学生管理',
|
||||
description: '管理学生档案:可单个录入、Excel 批量导入,或用 AI 助手帮你录入。',
|
||||
},
|
||||
{
|
||||
target: menuTarget('班级管理'),
|
||||
title: '班级管理',
|
||||
description: '学生入学后先分班,再排课、考勤,形成完整教学闭环。',
|
||||
},
|
||||
];
|
||||
|
||||
steps.push({
|
||||
const ACCOMMODATION_STEPS: TourStep[] = [
|
||||
{
|
||||
target: menuTarget('住宿总览'),
|
||||
title: '住宿总览',
|
||||
description: '可视化查看各宿舍入住情况,支持历史日期回溯和查寝。',
|
||||
},
|
||||
{
|
||||
target: menuTarget('入住管理'),
|
||||
title: '入住管理',
|
||||
description: '学生入住/退宿/换宿都在这里办理。入住后再录费用、生成账单。',
|
||||
},
|
||||
{
|
||||
target: menuTarget('账单管理'),
|
||||
title: '账单管理',
|
||||
description: '每月生成账单、确认并标记已付,完成「住宿→计费」闭环。',
|
||||
},
|
||||
];
|
||||
|
||||
const CLASSROOM_STEPS: TourStep[] = [
|
||||
{
|
||||
target: menuTarget('教室排期'),
|
||||
title: '教室排期',
|
||||
description: '查看教室占用与排期,避免时间冲突。',
|
||||
},
|
||||
{
|
||||
target: menuTarget('租赁订单'),
|
||||
title: '租赁订单',
|
||||
description: '管理教室租赁订单与合同,跟踪租期与状态。',
|
||||
},
|
||||
];
|
||||
|
||||
const SYSTEM_STEPS: TourStep[] = [
|
||||
{
|
||||
target: menuTarget('账号管理'),
|
||||
title: '账号管理',
|
||||
description: '管理系统账号、角色与权限,为不同岗位分配对应能力。',
|
||||
},
|
||||
];
|
||||
|
||||
const SUPER_STEPS: TourStep[] = [
|
||||
{
|
||||
target: menuTarget('数据面板'),
|
||||
title: '数据面板',
|
||||
description: '全局经营概览:入住率、收入、待办都在这里。',
|
||||
},
|
||||
];
|
||||
|
||||
const AI_STEPS: TourStep[] = [
|
||||
{
|
||||
target: aiTarget,
|
||||
title: 'AI 助手',
|
||||
description:
|
||||
'有任何问题都可以问我。我可以帮你查询数据、录入学生、生成批量导入预览——写操作都会先经你确认。',
|
||||
});
|
||||
},
|
||||
];
|
||||
|
||||
function buildSteps(domains: Set<string>): TourStep[] {
|
||||
const steps: TourStep[] = [];
|
||||
const has = (key: string) => domains.has(key);
|
||||
|
||||
if (has('super')) steps.unshift(...SUPER_STEPS);
|
||||
if (has('teacher')) steps.push(...TEACHER_STEPS);
|
||||
if (has('academic')) steps.push(...ACADEMIC_STEPS);
|
||||
if (has('accommodation')) steps.push(...ACCOMMODATION_STEPS);
|
||||
if (has('classroom')) steps.push(...CLASSROOM_STEPS);
|
||||
if (has('system') || has('super')) steps.push(...SYSTEM_STEPS);
|
||||
steps.push(...AI_STEPS);
|
||||
|
||||
return steps;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user