@cfmm/umi-plugins-ui-v2 0.0.27 → 0.0.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/CrudTable.tpl +1 -1
- package/dist/cjs/components/ImportExecl.less +21 -1
- package/dist/cjs/components/ImportExecl.tpl +7 -3
- package/dist/cjs/types/ImportExeclTypes.d.ts +1 -1
- package/dist/cjs/types/ImportExeclTypes.js +1 -1
- package/dist/cjs/utils/excelHelper.tpl +67 -22
- package/dist/esm/components/CrudTable.tpl +1 -1
- package/dist/esm/components/ImportExecl.less +21 -1
- package/dist/esm/components/ImportExecl.tpl +7 -3
- package/dist/esm/types/ImportExeclTypes.d.ts +1 -1
- package/dist/esm/types/ImportExeclTypes.js +1 -1
- package/dist/esm/utils/excelHelper.tpl +67 -22
- package/package.json +1 -1
|
@@ -404,7 +404,7 @@ function CrudTable<T extends Record<string, any>, U = {}, C = {}>(
|
|
|
404
404
|
* 通用操作处理
|
|
405
405
|
*/
|
|
406
406
|
const handleAction = useMemoizedFn(async (method: API.MethodTypes, fields: any): Promise<boolean> => {
|
|
407
|
-
if(!
|
|
407
|
+
if(!apis.handle) return false;
|
|
408
408
|
|
|
409
409
|
// 自定义操作前处理
|
|
410
410
|
if (onBeforeAction) {
|
|
@@ -7,4 +7,24 @@
|
|
|
7
7
|
:global(.mainAnt-upload-list) {
|
|
8
8
|
display: none;
|
|
9
9
|
}
|
|
10
|
-
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// children 自定义触发区时撑满父容器(如下拉菜单 label),扩大可点击范围
|
|
13
|
+
.importExexlFill {
|
|
14
|
+
display: block;
|
|
15
|
+
width: 100%;
|
|
16
|
+
:global(.ant-upload) {
|
|
17
|
+
display: block;
|
|
18
|
+
width: 100%;
|
|
19
|
+
}
|
|
20
|
+
:global(.ant-upload-select) {
|
|
21
|
+
display: block;
|
|
22
|
+
width: 100%;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
.importTrigger {
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 8px;
|
|
29
|
+
width: 100%;
|
|
30
|
+
}
|
|
@@ -2,6 +2,7 @@ import { UploadOutlined } from '@ant-design/icons';
|
|
|
2
2
|
import { useIntl } from '@umijs/max';
|
|
3
3
|
import type { UploadFile, UploadProps } from 'antd';
|
|
4
4
|
import { Button, message, Modal, Upload } from 'antd';
|
|
5
|
+
import classNames from 'classnames';
|
|
5
6
|
import React from 'react';
|
|
6
7
|
import { ImportExeclProps } from '../types';
|
|
7
8
|
import { importExexl } from '../utils/importHelper';
|
|
@@ -20,6 +21,7 @@ const ImportExecl = <T,>(props: ImportExeclProps<T>) => {
|
|
|
20
21
|
buttonProps,
|
|
21
22
|
children,
|
|
22
23
|
showDefautlMessage = true,
|
|
24
|
+
fillContainer = false,
|
|
23
25
|
onChange,
|
|
24
26
|
} = props;
|
|
25
27
|
|
|
@@ -134,16 +136,18 @@ const ImportExecl = <T,>(props: ImportExeclProps<T>) => {
|
|
|
134
136
|
}
|
|
135
137
|
};
|
|
136
138
|
|
|
139
|
+
const useFillLayout = fillContainer && !!children;
|
|
140
|
+
|
|
137
141
|
return (
|
|
138
142
|
<Upload
|
|
139
143
|
accept={accept}
|
|
140
|
-
className={styles.importExexl}
|
|
144
|
+
className={classNames(styles.importExexl, useFillLayout && styles.importExexlFill, uploadProps?.className)}
|
|
141
145
|
maxCount={1}
|
|
142
146
|
customRequest={handleCustomRequest}
|
|
143
147
|
{...uploadProps}
|
|
144
148
|
>
|
|
145
|
-
|
|
146
|
-
children
|
|
149
|
+
{children ? (
|
|
150
|
+
useFillLayout ? <span className={styles.importTrigger}>{children}</span> : children
|
|
147
151
|
) : (
|
|
148
152
|
<Button type="primary" icon={<UploadOutlined />} {...buttonProps}>
|
|
149
153
|
{title ? title : formatMessage({ id: 'cfmmUI.File.import', defaultMessage: '导入' })}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{\n success: boolean; failMsg?: string; data?: {\n success: boolean;\n insertCount: number;\n updateCount: number;\n deleteCount: number;\n } | number\n } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u7A7A\u5355\u5143\u683C\u5360\u4F4D\u503C\uFF1B\u4F20\u5165\u540E\u6BCF\u884C\u5BF9\u8C61\u4F1A\u5305\u542B\u4E0E\u8868\u5934\u5BF9\u9F50\u7684\u5168\u90E8 key\uFF08\u7A7A\u5355\u5143\u683C\u4E3A\u8BE5\u503C\uFF09\uFF0C\u5426\u5219 SheetJS \u4F1A\u7701\u7565\u7A7A\u5355\u5143\u683C\u5BF9\u5E94\u5B57\u6BB5\n */\n defval?: unknown;\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
|
|
1
|
+
export declare const ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n /**\n * \u81EA\u5B9A\u4E49 children \u65F6\u662F\u5426\u6491\u6EE1\u7236\u5BB9\u5668\uFF08\u5982\u4E0B\u62C9\u83DC\u5355 label\uFF09\uFF0C\u6269\u5927\u53EF\u70B9\u51FB\u8303\u56F4\n */\n fillContainer?: boolean;\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{\n success: boolean; failMsg?: string; data?: {\n success: boolean;\n insertCount: number;\n updateCount: number;\n deleteCount: number;\n } | number\n } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u7A7A\u5355\u5143\u683C\u5360\u4F4D\u503C\uFF1B\u4F20\u5165\u540E\u6BCF\u884C\u5BF9\u8C61\u4F1A\u5305\u542B\u4E0E\u8868\u5934\u5BF9\u9F50\u7684\u5168\u90E8 key\uFF08\u7A7A\u5355\u5143\u683C\u4E3A\u8BE5\u503C\uFF09\uFF0C\u5426\u5219 SheetJS \u4F1A\u7701\u7565\u7A7A\u5355\u5143\u683C\u5BF9\u5E94\u5B57\u6BB5\n */\n defval?: unknown;\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
|
|
@@ -4,4 +4,4 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.ImportExeclTypes = void 0;
|
|
7
|
-
var ImportExeclTypes = exports.ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{\n success: boolean; failMsg?: string; data?: {\n success: boolean;\n insertCount: number;\n updateCount: number;\n deleteCount: number;\n } | number\n } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u7A7A\u5355\u5143\u683C\u5360\u4F4D\u503C\uFF1B\u4F20\u5165\u540E\u6BCF\u884C\u5BF9\u8C61\u4F1A\u5305\u542B\u4E0E\u8868\u5934\u5BF9\u9F50\u7684\u5168\u90E8 key\uFF08\u7A7A\u5355\u5143\u683C\u4E3A\u8BE5\u503C\uFF09\uFF0C\u5426\u5219 SheetJS \u4F1A\u7701\u7565\u7A7A\u5355\u5143\u683C\u5BF9\u5E94\u5B57\u6BB5\n */\n defval?: unknown;\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
|
|
7
|
+
var ImportExeclTypes = exports.ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n /**\n * \u81EA\u5B9A\u4E49 children \u65F6\u662F\u5426\u6491\u6EE1\u7236\u5BB9\u5668\uFF08\u5982\u4E0B\u62C9\u83DC\u5355 label\uFF09\uFF0C\u6269\u5927\u53EF\u70B9\u51FB\u8303\u56F4\n */\n fillContainer?: boolean;\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{\n success: boolean; failMsg?: string; data?: {\n success: boolean;\n insertCount: number;\n updateCount: number;\n deleteCount: number;\n } | number\n } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u7A7A\u5355\u5143\u683C\u5360\u4F4D\u503C\uFF1B\u4F20\u5165\u540E\u6BCF\u884C\u5BF9\u8C61\u4F1A\u5305\u542B\u4E0E\u8868\u5934\u5BF9\u9F50\u7684\u5168\u90E8 key\uFF08\u7A7A\u5355\u5143\u683C\u4E3A\u8BE5\u503C\uFF09\uFF0C\u5426\u5219 SheetJS \u4F1A\u7701\u7565\u7A7A\u5355\u5143\u683C\u5BF9\u5E94\u5B57\u6BB5\n */\n defval?: unknown;\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
|
|
@@ -270,6 +270,12 @@ export interface ExcelExportOptions {
|
|
|
270
270
|
sheetPassword?: string;
|
|
271
271
|
/** 是否锁定基础行(数据行前的行),默认为 true */
|
|
272
272
|
lockHeaderRows?: boolean;
|
|
273
|
+
/**
|
|
274
|
+
* 是否锁定数据填写区(含 columnValidations 预留行)。
|
|
275
|
+
* - 默认 false:数据区可编辑(表头仍由 lockHeaderRows 锁定)
|
|
276
|
+
* - true:整表只读,需配合 lockHeaderRows(默认已 true)启用工作表保护
|
|
277
|
+
*/
|
|
278
|
+
lockDataRows?: boolean;
|
|
273
279
|
/** 自定义合并单元格范围数组(如 ['A1:B2', 'C1:D1']) */
|
|
274
280
|
mergeCells?: string[];
|
|
275
281
|
/** 数据行高度,默认 22 */
|
|
@@ -382,6 +388,35 @@ const applyColumnValidations = (
|
|
|
382
388
|
});
|
|
383
389
|
};
|
|
384
390
|
|
|
391
|
+
/**
|
|
392
|
+
* 工作表保护开启前,解锁数据填写区。
|
|
393
|
+
* Excel 新建单元格默认 protection.locked=true;仅锁表头并 protect 后,未显式解锁的数据区也不可编辑。
|
|
394
|
+
*/
|
|
395
|
+
const unlockDataEntryArea = (
|
|
396
|
+
workSheet: ExcelJS.Worksheet,
|
|
397
|
+
headerRowCount: number,
|
|
398
|
+
colCount: number,
|
|
399
|
+
maxDataRows: number,
|
|
400
|
+
dataRowCount: number,
|
|
401
|
+
hasColumnValidations: boolean,
|
|
402
|
+
lockedColumns: number[],
|
|
403
|
+
) => {
|
|
404
|
+
const rowSpan = hasColumnValidations ? Math.max(dataRowCount, maxDataRows) : dataRowCount;
|
|
405
|
+
if (rowSpan <= 0) return;
|
|
406
|
+
|
|
407
|
+
const startRow = headerRowCount + 1;
|
|
408
|
+
const endRow = startRow + rowSpan - 1;
|
|
409
|
+
|
|
410
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
411
|
+
for (let col = 1; col <= colCount; col++) {
|
|
412
|
+
const colIndex = col - 1;
|
|
413
|
+
if (!lockedColumns.includes(colIndex)) {
|
|
414
|
+
workSheet.getCell(row, col).protection = { locked: false };
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
385
420
|
function buildDataCellBorder(style: ExcelJS.BorderStyle): ExcelJS.Borders {
|
|
386
421
|
const side = { style } as ExcelJS.Border;
|
|
387
422
|
return {
|
|
@@ -538,6 +573,8 @@ export const genExcelAdvanced = async (
|
|
|
538
573
|
|
|
539
574
|
// 是否锁定基础行(默认为 true)
|
|
540
575
|
const lockHeaderRows = options?.lockHeaderRows !== false;
|
|
576
|
+
// 是否锁定数据填写区(整表只读),默认 false
|
|
577
|
+
const lockDataRows = options?.lockDataRows === true;
|
|
541
578
|
// 物理行号:追加行时递增,用于 mergeCells 与「第几行」对应
|
|
542
579
|
let currentRowIndex = 0;
|
|
543
580
|
|
|
@@ -569,11 +606,9 @@ export const genExcelAdvanced = async (
|
|
|
569
606
|
bottom: { style: 'thin' },
|
|
570
607
|
right: { style: 'thin' },
|
|
571
608
|
};
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
});
|
|
576
|
-
}
|
|
609
|
+
titleRow.eachCell((cell) => {
|
|
610
|
+
cell.protection = { locked: lockHeaderRows };
|
|
611
|
+
});
|
|
577
612
|
}
|
|
578
613
|
|
|
579
614
|
// ========== 第二行:注意事项(可选) ==========
|
|
@@ -607,11 +642,9 @@ export const genExcelAdvanced = async (
|
|
|
607
642
|
bottom: { style: 'thin' },
|
|
608
643
|
right: { style: 'thin' },
|
|
609
644
|
};
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
});
|
|
614
|
-
}
|
|
645
|
+
noticeRow.eachCell((cell) => {
|
|
646
|
+
cell.protection = { locked: lockHeaderRows };
|
|
647
|
+
});
|
|
615
648
|
}
|
|
616
649
|
|
|
617
650
|
// ========== 编号行(可选,隐藏) ==========
|
|
@@ -625,9 +658,7 @@ export const genExcelAdvanced = async (
|
|
|
625
658
|
bottom: { style: 'thin' },
|
|
626
659
|
right: { style: 'thin' },
|
|
627
660
|
};
|
|
628
|
-
|
|
629
|
-
cell.protection = { locked: true };
|
|
630
|
-
}
|
|
661
|
+
cell.protection = { locked: lockHeaderRows };
|
|
631
662
|
});
|
|
632
663
|
codeColumnRow.alignment = { horizontal: 'center', vertical: 'middle' };
|
|
633
664
|
codeColumnRow.hidden = true;
|
|
@@ -649,9 +680,7 @@ export const genExcelAdvanced = async (
|
|
|
649
680
|
bottom: { style: 'thin' },
|
|
650
681
|
right: { style: 'thin' },
|
|
651
682
|
};
|
|
652
|
-
|
|
653
|
-
cell.protection = { locked: true };
|
|
654
|
-
}
|
|
683
|
+
cell.protection = { locked: lockHeaderRows };
|
|
655
684
|
});
|
|
656
685
|
});
|
|
657
686
|
} else {
|
|
@@ -672,9 +701,7 @@ export const genExcelAdvanced = async (
|
|
|
672
701
|
bottom: { style: 'thin' },
|
|
673
702
|
right: { style: 'thin' },
|
|
674
703
|
};
|
|
675
|
-
|
|
676
|
-
cell.protection = { locked: true };
|
|
677
|
-
}
|
|
704
|
+
cell.protection = { locked: lockHeaderRows };
|
|
678
705
|
});
|
|
679
706
|
columnHeaderRow.height = 25;
|
|
680
707
|
}
|
|
@@ -706,8 +733,14 @@ export const genExcelAdvanced = async (
|
|
|
706
733
|
if (colNumber <= colCount) {
|
|
707
734
|
const colIndex = colNumber - 1;
|
|
708
735
|
applyDataCellAppearance(cell, dataRowIndex, colIndex, options);
|
|
709
|
-
if (
|
|
736
|
+
if (lockDataRows) {
|
|
737
|
+
// 整表只读:数据行也锁定,不必显式写(ExcelJS 默认 locked=true),这里写明意图
|
|
738
|
+
cell.protection = { locked: true };
|
|
739
|
+
} else if (hasLockedColumns) {
|
|
710
740
|
cell.protection = { locked: lockedColumns.includes(colIndex) };
|
|
741
|
+
} else if (lockHeaderRows) {
|
|
742
|
+
// 仅锁表头时,数据行显式解锁,避免 protect 后整表不可编辑
|
|
743
|
+
cell.protection = { locked: false };
|
|
711
744
|
}
|
|
712
745
|
}
|
|
713
746
|
});
|
|
@@ -720,8 +753,20 @@ export const genExcelAdvanced = async (
|
|
|
720
753
|
applyColumnValidations(workBook, workSheet, options.columnValidations, currentRowIndex, options.maxDataRows ?? 500);
|
|
721
754
|
}
|
|
722
755
|
|
|
723
|
-
// ==========
|
|
724
|
-
if (lockHeaderRows || hasLockedColumns) {
|
|
756
|
+
// ========== 工作表保护:锁定表头/数据区或部分列时启用(密码可选) ==========
|
|
757
|
+
if (lockHeaderRows || hasLockedColumns || lockDataRows) {
|
|
758
|
+
// lockDataRows=true 时整表只读,数据区保持默认 locked=true,不需要解锁
|
|
759
|
+
if (!lockDataRows) {
|
|
760
|
+
unlockDataEntryArea(
|
|
761
|
+
workSheet,
|
|
762
|
+
currentRowIndex,
|
|
763
|
+
colCount,
|
|
764
|
+
options?.maxDataRows ?? 500,
|
|
765
|
+
rows?.length ?? 0,
|
|
766
|
+
!!(options?.columnValidations?.length),
|
|
767
|
+
lockedColumns,
|
|
768
|
+
);
|
|
769
|
+
}
|
|
725
770
|
workSheet.protect(options?.sheetPassword ?? '', {
|
|
726
771
|
selectLockedCells: true,
|
|
727
772
|
selectUnlockedCells: true,
|
|
@@ -404,7 +404,7 @@ function CrudTable<T extends Record<string, any>, U = {}, C = {}>(
|
|
|
404
404
|
* 通用操作处理
|
|
405
405
|
*/
|
|
406
406
|
const handleAction = useMemoizedFn(async (method: API.MethodTypes, fields: any): Promise<boolean> => {
|
|
407
|
-
if(!
|
|
407
|
+
if(!apis.handle) return false;
|
|
408
408
|
|
|
409
409
|
// 自定义操作前处理
|
|
410
410
|
if (onBeforeAction) {
|
|
@@ -7,4 +7,24 @@
|
|
|
7
7
|
:global(.mainAnt-upload-list) {
|
|
8
8
|
display: none;
|
|
9
9
|
}
|
|
10
|
-
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// children 自定义触发区时撑满父容器(如下拉菜单 label),扩大可点击范围
|
|
13
|
+
.importExexlFill {
|
|
14
|
+
display: block;
|
|
15
|
+
width: 100%;
|
|
16
|
+
:global(.ant-upload) {
|
|
17
|
+
display: block;
|
|
18
|
+
width: 100%;
|
|
19
|
+
}
|
|
20
|
+
:global(.ant-upload-select) {
|
|
21
|
+
display: block;
|
|
22
|
+
width: 100%;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
.importTrigger {
|
|
26
|
+
display: inline-flex;
|
|
27
|
+
align-items: center;
|
|
28
|
+
gap: 8px;
|
|
29
|
+
width: 100%;
|
|
30
|
+
}
|
|
@@ -2,6 +2,7 @@ import { UploadOutlined } from '@ant-design/icons';
|
|
|
2
2
|
import { useIntl } from '@umijs/max';
|
|
3
3
|
import type { UploadFile, UploadProps } from 'antd';
|
|
4
4
|
import { Button, message, Modal, Upload } from 'antd';
|
|
5
|
+
import classNames from 'classnames';
|
|
5
6
|
import React from 'react';
|
|
6
7
|
import { ImportExeclProps } from '../types';
|
|
7
8
|
import { importExexl } from '../utils/importHelper';
|
|
@@ -20,6 +21,7 @@ const ImportExecl = <T,>(props: ImportExeclProps<T>) => {
|
|
|
20
21
|
buttonProps,
|
|
21
22
|
children,
|
|
22
23
|
showDefautlMessage = true,
|
|
24
|
+
fillContainer = false,
|
|
23
25
|
onChange,
|
|
24
26
|
} = props;
|
|
25
27
|
|
|
@@ -134,16 +136,18 @@ const ImportExecl = <T,>(props: ImportExeclProps<T>) => {
|
|
|
134
136
|
}
|
|
135
137
|
};
|
|
136
138
|
|
|
139
|
+
const useFillLayout = fillContainer && !!children;
|
|
140
|
+
|
|
137
141
|
return (
|
|
138
142
|
<Upload
|
|
139
143
|
accept={accept}
|
|
140
|
-
className={styles.importExexl}
|
|
144
|
+
className={classNames(styles.importExexl, useFillLayout && styles.importExexlFill, uploadProps?.className)}
|
|
141
145
|
maxCount={1}
|
|
142
146
|
customRequest={handleCustomRequest}
|
|
143
147
|
{...uploadProps}
|
|
144
148
|
>
|
|
145
|
-
|
|
146
|
-
children
|
|
149
|
+
{children ? (
|
|
150
|
+
useFillLayout ? <span className={styles.importTrigger}>{children}</span> : children
|
|
147
151
|
) : (
|
|
148
152
|
<Button type="primary" icon={<UploadOutlined />} {...buttonProps}>
|
|
149
153
|
{title ? title : formatMessage({ id: 'cfmmUI.File.import', defaultMessage: '导入' })}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{\n success: boolean; failMsg?: string; data?: {\n success: boolean;\n insertCount: number;\n updateCount: number;\n deleteCount: number;\n } | number\n } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u7A7A\u5355\u5143\u683C\u5360\u4F4D\u503C\uFF1B\u4F20\u5165\u540E\u6BCF\u884C\u5BF9\u8C61\u4F1A\u5305\u542B\u4E0E\u8868\u5934\u5BF9\u9F50\u7684\u5168\u90E8 key\uFF08\u7A7A\u5355\u5143\u683C\u4E3A\u8BE5\u503C\uFF09\uFF0C\u5426\u5219 SheetJS \u4F1A\u7701\u7565\u7A7A\u5355\u5143\u683C\u5BF9\u5E94\u5B57\u6BB5\n */\n defval?: unknown;\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
|
|
1
|
+
export declare const ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n /**\n * \u81EA\u5B9A\u4E49 children \u65F6\u662F\u5426\u6491\u6EE1\u7236\u5BB9\u5668\uFF08\u5982\u4E0B\u62C9\u83DC\u5355 label\uFF09\uFF0C\u6269\u5927\u53EF\u70B9\u51FB\u8303\u56F4\n */\n fillContainer?: boolean;\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{\n success: boolean; failMsg?: string; data?: {\n success: boolean;\n insertCount: number;\n updateCount: number;\n deleteCount: number;\n } | number\n } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u7A7A\u5355\u5143\u683C\u5360\u4F4D\u503C\uFF1B\u4F20\u5165\u540E\u6BCF\u884C\u5BF9\u8C61\u4F1A\u5305\u542B\u4E0E\u8868\u5934\u5BF9\u9F50\u7684\u5168\u90E8 key\uFF08\u7A7A\u5355\u5143\u683C\u4E3A\u8BE5\u503C\uFF09\uFF0C\u5426\u5219 SheetJS \u4F1A\u7701\u7565\u7A7A\u5355\u5143\u683C\u5BF9\u5E94\u5B57\u6BB5\n */\n defval?: unknown;\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export var ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{\n success: boolean; failMsg?: string; data?: {\n success: boolean;\n insertCount: number;\n updateCount: number;\n deleteCount: number;\n } | number\n } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u7A7A\u5355\u5143\u683C\u5360\u4F4D\u503C\uFF1B\u4F20\u5165\u540E\u6BCF\u884C\u5BF9\u8C61\u4F1A\u5305\u542B\u4E0E\u8868\u5934\u5BF9\u9F50\u7684\u5168\u90E8 key\uFF08\u7A7A\u5355\u5143\u683C\u4E3A\u8BE5\u503C\uFF09\uFF0C\u5426\u5219 SheetJS \u4F1A\u7701\u7565\u7A7A\u5355\u5143\u683C\u5BF9\u5E94\u5B57\u6BB5\n */\n defval?: unknown;\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
|
|
1
|
+
export var ImportExeclTypes = "\nexport interface ImportExeclProps<T = any[]> {\n accept?: string;\n /**\n * \u4E0A\u4F20\u4E8B\u4EF6\uFF0C\u83B7\u53D6execl\u6570\u636E\u5217\u8868\uFF0C\u51FD\u6570\u8FD4\u56DEpromise\u5E76\u4E14showDefautlMessage\u4E0D\u662Ffalse\u65F6\u6709\u9ED8\u8BA4message\u63D0\u793A\n */\n /**\n * \u81EA\u5B9A\u4E49 children \u65F6\u662F\u5426\u6491\u6EE1\u7236\u5BB9\u5668\uFF08\u5982\u4E0B\u62C9\u83DC\u5355 label\uFF09\uFF0C\u6269\u5927\u53EF\u70B9\u51FB\u8303\u56F4\n */\n fillContainer?: boolean;\n onChange: (list: T, fileInfo: Pick<UploadFile, 'name' | 'size' | 'type' | 'uid'>) => Promise<{\n success: boolean; failMsg?: string; data?: {\n success: boolean;\n insertCount: number;\n updateCount: number;\n deleteCount: number;\n } | number\n } | void> | void;\n /**\n * \u663E\u793A\u9ED8\u8BA4\u7684\u63D0\u793A\n */\n showDefautlMessage?: boolean;\n /**\n * \u6309\u94AE\u6587\u672C\n */\n title?: string;\n /**\n * \u8BFB\u53D6\u5DE5\u4F5C\u7C3F\u6570\u91CF\uFF0C\u9ED8\u8BA41\n */\n max?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u884C\u4E3A\u8868\u5934\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\n */\n header?: number;\n /**\n * \u4EE5\u7B2C\u51E0\u5217\u7B2C\u51E0\u884C\u5F00\u59CB\u8BFB\u53D6\uFF0C\u9ED8\u8BA4\u7B2C\u4E00\u884C\u7B2C\u4E00\u5217\n */\n rangeStart?: { c: number; r: number };\n /**\n * \u7A7A\u5355\u5143\u683C\u5360\u4F4D\u503C\uFF1B\u4F20\u5165\u540E\u6BCF\u884C\u5BF9\u8C61\u4F1A\u5305\u542B\u4E0E\u8868\u5934\u5BF9\u9F50\u7684\u5168\u90E8 key\uFF08\u7A7A\u5355\u5143\u683C\u4E3A\u8BE5\u503C\uFF09\uFF0C\u5426\u5219 SheetJS \u4F1A\u7701\u7565\u7A7A\u5355\u5143\u683C\u5BF9\u5E94\u5B57\u6BB5\n */\n defval?: unknown;\n /**\n * \u4FDD\u7559\u7A7A\u884C\uFF0C\u9ED8\u8BA4false\n */\n blankrows?: boolean;\n /**\n * \u81EA\u5B9A\u4E49\u6309\u94AE\u5185\u5BB9\n */\n children?: React.ReactNode;\n /**\n * Upload\u7EC4\u4EF6\u5C5E\u6027\n */\n uploadProps?: UploadProps;\n /**\n * Button\u7EC4\u4EF6\u5C5E\u6027\n */\n buttonProps?: ButtonProps;\n}\n";
|
|
@@ -270,6 +270,12 @@ export interface ExcelExportOptions {
|
|
|
270
270
|
sheetPassword?: string;
|
|
271
271
|
/** 是否锁定基础行(数据行前的行),默认为 true */
|
|
272
272
|
lockHeaderRows?: boolean;
|
|
273
|
+
/**
|
|
274
|
+
* 是否锁定数据填写区(含 columnValidations 预留行)。
|
|
275
|
+
* - 默认 false:数据区可编辑(表头仍由 lockHeaderRows 锁定)
|
|
276
|
+
* - true:整表只读,需配合 lockHeaderRows(默认已 true)启用工作表保护
|
|
277
|
+
*/
|
|
278
|
+
lockDataRows?: boolean;
|
|
273
279
|
/** 自定义合并单元格范围数组(如 ['A1:B2', 'C1:D1']) */
|
|
274
280
|
mergeCells?: string[];
|
|
275
281
|
/** 数据行高度,默认 22 */
|
|
@@ -382,6 +388,35 @@ const applyColumnValidations = (
|
|
|
382
388
|
});
|
|
383
389
|
};
|
|
384
390
|
|
|
391
|
+
/**
|
|
392
|
+
* 工作表保护开启前,解锁数据填写区。
|
|
393
|
+
* Excel 新建单元格默认 protection.locked=true;仅锁表头并 protect 后,未显式解锁的数据区也不可编辑。
|
|
394
|
+
*/
|
|
395
|
+
const unlockDataEntryArea = (
|
|
396
|
+
workSheet: ExcelJS.Worksheet,
|
|
397
|
+
headerRowCount: number,
|
|
398
|
+
colCount: number,
|
|
399
|
+
maxDataRows: number,
|
|
400
|
+
dataRowCount: number,
|
|
401
|
+
hasColumnValidations: boolean,
|
|
402
|
+
lockedColumns: number[],
|
|
403
|
+
) => {
|
|
404
|
+
const rowSpan = hasColumnValidations ? Math.max(dataRowCount, maxDataRows) : dataRowCount;
|
|
405
|
+
if (rowSpan <= 0) return;
|
|
406
|
+
|
|
407
|
+
const startRow = headerRowCount + 1;
|
|
408
|
+
const endRow = startRow + rowSpan - 1;
|
|
409
|
+
|
|
410
|
+
for (let row = startRow; row <= endRow; row++) {
|
|
411
|
+
for (let col = 1; col <= colCount; col++) {
|
|
412
|
+
const colIndex = col - 1;
|
|
413
|
+
if (!lockedColumns.includes(colIndex)) {
|
|
414
|
+
workSheet.getCell(row, col).protection = { locked: false };
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
385
420
|
function buildDataCellBorder(style: ExcelJS.BorderStyle): ExcelJS.Borders {
|
|
386
421
|
const side = { style } as ExcelJS.Border;
|
|
387
422
|
return {
|
|
@@ -538,6 +573,8 @@ export const genExcelAdvanced = async (
|
|
|
538
573
|
|
|
539
574
|
// 是否锁定基础行(默认为 true)
|
|
540
575
|
const lockHeaderRows = options?.lockHeaderRows !== false;
|
|
576
|
+
// 是否锁定数据填写区(整表只读),默认 false
|
|
577
|
+
const lockDataRows = options?.lockDataRows === true;
|
|
541
578
|
// 物理行号:追加行时递增,用于 mergeCells 与「第几行」对应
|
|
542
579
|
let currentRowIndex = 0;
|
|
543
580
|
|
|
@@ -569,11 +606,9 @@ export const genExcelAdvanced = async (
|
|
|
569
606
|
bottom: { style: 'thin' },
|
|
570
607
|
right: { style: 'thin' },
|
|
571
608
|
};
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
});
|
|
576
|
-
}
|
|
609
|
+
titleRow.eachCell((cell) => {
|
|
610
|
+
cell.protection = { locked: lockHeaderRows };
|
|
611
|
+
});
|
|
577
612
|
}
|
|
578
613
|
|
|
579
614
|
// ========== 第二行:注意事项(可选) ==========
|
|
@@ -607,11 +642,9 @@ export const genExcelAdvanced = async (
|
|
|
607
642
|
bottom: { style: 'thin' },
|
|
608
643
|
right: { style: 'thin' },
|
|
609
644
|
};
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
});
|
|
614
|
-
}
|
|
645
|
+
noticeRow.eachCell((cell) => {
|
|
646
|
+
cell.protection = { locked: lockHeaderRows };
|
|
647
|
+
});
|
|
615
648
|
}
|
|
616
649
|
|
|
617
650
|
// ========== 编号行(可选,隐藏) ==========
|
|
@@ -625,9 +658,7 @@ export const genExcelAdvanced = async (
|
|
|
625
658
|
bottom: { style: 'thin' },
|
|
626
659
|
right: { style: 'thin' },
|
|
627
660
|
};
|
|
628
|
-
|
|
629
|
-
cell.protection = { locked: true };
|
|
630
|
-
}
|
|
661
|
+
cell.protection = { locked: lockHeaderRows };
|
|
631
662
|
});
|
|
632
663
|
codeColumnRow.alignment = { horizontal: 'center', vertical: 'middle' };
|
|
633
664
|
codeColumnRow.hidden = true;
|
|
@@ -649,9 +680,7 @@ export const genExcelAdvanced = async (
|
|
|
649
680
|
bottom: { style: 'thin' },
|
|
650
681
|
right: { style: 'thin' },
|
|
651
682
|
};
|
|
652
|
-
|
|
653
|
-
cell.protection = { locked: true };
|
|
654
|
-
}
|
|
683
|
+
cell.protection = { locked: lockHeaderRows };
|
|
655
684
|
});
|
|
656
685
|
});
|
|
657
686
|
} else {
|
|
@@ -672,9 +701,7 @@ export const genExcelAdvanced = async (
|
|
|
672
701
|
bottom: { style: 'thin' },
|
|
673
702
|
right: { style: 'thin' },
|
|
674
703
|
};
|
|
675
|
-
|
|
676
|
-
cell.protection = { locked: true };
|
|
677
|
-
}
|
|
704
|
+
cell.protection = { locked: lockHeaderRows };
|
|
678
705
|
});
|
|
679
706
|
columnHeaderRow.height = 25;
|
|
680
707
|
}
|
|
@@ -706,8 +733,14 @@ export const genExcelAdvanced = async (
|
|
|
706
733
|
if (colNumber <= colCount) {
|
|
707
734
|
const colIndex = colNumber - 1;
|
|
708
735
|
applyDataCellAppearance(cell, dataRowIndex, colIndex, options);
|
|
709
|
-
if (
|
|
736
|
+
if (lockDataRows) {
|
|
737
|
+
// 整表只读:数据行也锁定,不必显式写(ExcelJS 默认 locked=true),这里写明意图
|
|
738
|
+
cell.protection = { locked: true };
|
|
739
|
+
} else if (hasLockedColumns) {
|
|
710
740
|
cell.protection = { locked: lockedColumns.includes(colIndex) };
|
|
741
|
+
} else if (lockHeaderRows) {
|
|
742
|
+
// 仅锁表头时,数据行显式解锁,避免 protect 后整表不可编辑
|
|
743
|
+
cell.protection = { locked: false };
|
|
711
744
|
}
|
|
712
745
|
}
|
|
713
746
|
});
|
|
@@ -720,8 +753,20 @@ export const genExcelAdvanced = async (
|
|
|
720
753
|
applyColumnValidations(workBook, workSheet, options.columnValidations, currentRowIndex, options.maxDataRows ?? 500);
|
|
721
754
|
}
|
|
722
755
|
|
|
723
|
-
// ==========
|
|
724
|
-
if (lockHeaderRows || hasLockedColumns) {
|
|
756
|
+
// ========== 工作表保护:锁定表头/数据区或部分列时启用(密码可选) ==========
|
|
757
|
+
if (lockHeaderRows || hasLockedColumns || lockDataRows) {
|
|
758
|
+
// lockDataRows=true 时整表只读,数据区保持默认 locked=true,不需要解锁
|
|
759
|
+
if (!lockDataRows) {
|
|
760
|
+
unlockDataEntryArea(
|
|
761
|
+
workSheet,
|
|
762
|
+
currentRowIndex,
|
|
763
|
+
colCount,
|
|
764
|
+
options?.maxDataRows ?? 500,
|
|
765
|
+
rows?.length ?? 0,
|
|
766
|
+
!!(options?.columnValidations?.length),
|
|
767
|
+
lockedColumns,
|
|
768
|
+
);
|
|
769
|
+
}
|
|
725
770
|
workSheet.protect(options?.sheetPassword ?? '', {
|
|
726
771
|
selectLockedCells: true,
|
|
727
772
|
selectUnlockedCells: true,
|