import type { FillFieldType } from '../domain/types';
|
|
export function inspectFieldsCommand(): string {
|
function errorMessage(error: unknown): string {
|
return error instanceof Error ? error.message : String(error);
|
}
|
|
function textOf(control: any): string {
|
return String(control.GetRange().GetText() ?? '');
|
}
|
|
function isPlaceholder(control: any): boolean {
|
const json = control.ToJSON?.(false);
|
if (typeof json !== 'string') return false;
|
try {
|
return JSON.parse(json)?.sdtPr?.showingPlcHdr === true;
|
} catch {
|
return false;
|
}
|
}
|
|
function isBlockControl(control: any): boolean {
|
if (control.IsBlockLevel?.()) return true;
|
const json = control.ToJSON?.(false);
|
if (typeof json !== 'string') return false;
|
try {
|
return JSON.parse(json)?.type === 'blockLvlSdt';
|
} catch {
|
return false;
|
}
|
}
|
|
function kindOf(control: any): FillFieldType {
|
if (control.IsCheckBox?.()) return 'checkbox';
|
if (control.IsComboBox?.() || control.IsDropDownList?.()) return 'select';
|
if (control.IsDatePicker?.()) return 'date';
|
if (isBlockControl(control)) return 'multiline';
|
return 'text';
|
}
|
|
function dateFormatOf(control: any): string {
|
const json = control.ToJSON?.(false);
|
if (typeof json !== 'string') throw new Error('无法读取日期控件格式');
|
let dateFormat: unknown;
|
try {
|
dateFormat = JSON.parse(json)?.sdtPr?.date?.dateFormat;
|
} catch {
|
throw new Error('日期控件结构数据无效');
|
}
|
if (typeof dateFormat === 'string' && dateFormat.trim()) return dateFormat;
|
throw new Error('不支持的日期控件格式');
|
}
|
|
function optionsOf(control: any): Array<{ display: string; value: string }> {
|
const list = control.GetDropdownList?.();
|
if (!list?.GetElementsCount || !list.GetItem) return [];
|
const options = [];
|
for (let index = 0; index < list.GetElementsCount(); index += 1) {
|
const item = list.GetItem(index);
|
options.push({ display: String(item.GetText()), value: String(item.GetValue()) });
|
}
|
return options;
|
}
|
|
function readValue(control: any, type: FillFieldType): boolean | string {
|
if (isPlaceholder(control)) return type === 'checkbox' ? false : '';
|
const text = textOf(control);
|
if (type === 'checkbox') return ['☑', '☒'].includes(text);
|
if (type === 'select') return optionsOf(control).find((item) => item.display === text)?.value ?? '';
|
if (type === 'multiline') return text.replace(/\r\n?/gu, '\n').replace(/\n$/u, '');
|
return text;
|
}
|
|
try {
|
const controls = Api.GetDocument().GetAllContentControls();
|
const fields = controls
|
.filter((control: any) => /^eln\.(?:field\.[\w.-]+|ref\.[\w.-]+\.[\w-]+|repeat\.[\w-]+\.[\w-]+\.[\w.-]+)$/u.test(control.GetTag()))
|
.map((control: any) => {
|
const type = kindOf(control);
|
return {
|
alias: String(control.GetAlias?.() ?? ''),
|
currentValue: readValue(control, type),
|
...(type === 'date' ? { dateFormat: dateFormatOf(control) } : {}),
|
internalId: String(control.GetInternalId?.() ?? ''),
|
options: type === 'select' ? optionsOf(control) : [],
|
tag: String(control.GetTag()),
|
type,
|
};
|
});
|
const tags = fields.map((field: any) => field.tag);
|
if (new Set(tags).size !== tags.length) throw new Error('文档存在重复字段 Tag');
|
return JSON.stringify({ ok: true, fields });
|
} catch (error) {
|
return JSON.stringify({ ok: false, error: errorMessage(error), fields: [] });
|
}
|
}
|
|
export function applyFieldsCommand(): string {
|
function errorMessage(error: unknown): string {
|
return error instanceof Error ? error.message : String(error);
|
}
|
|
function textOf(control: any): string {
|
return String(control.GetRange().GetText() ?? '');
|
}
|
|
function isPlaceholder(control: any): boolean {
|
const json = control.ToJSON?.(false);
|
if (typeof json !== 'string') return false;
|
try {
|
return JSON.parse(json)?.sdtPr?.showingPlcHdr === true;
|
} catch {
|
return false;
|
}
|
}
|
|
function isBlockControl(control: any): boolean {
|
if (control.IsBlockLevel?.()) return true;
|
const json = control.ToJSON?.(false);
|
if (typeof json !== 'string') return false;
|
try {
|
return JSON.parse(json)?.type === 'blockLvlSdt';
|
} catch {
|
return false;
|
}
|
}
|
|
function kindOf(control: any): FillFieldType {
|
if (control.IsCheckBox?.()) return 'checkbox';
|
if (control.IsComboBox?.() || control.IsDropDownList?.()) return 'select';
|
if (control.IsDatePicker?.()) return 'date';
|
if (isBlockControl(control)) return 'multiline';
|
return 'text';
|
}
|
|
function dateFormatOf(control: any): string {
|
const json = control.ToJSON?.(false);
|
if (typeof json !== 'string') throw new Error('无法读取日期控件格式');
|
let dateFormat: unknown;
|
try {
|
dateFormat = JSON.parse(json)?.sdtPr?.date?.dateFormat;
|
} catch {
|
throw new Error('日期控件结构数据无效');
|
}
|
if (typeof dateFormat === 'string' && dateFormat.trim()) return dateFormat;
|
throw new Error('不支持的日期控件格式');
|
}
|
|
function optionsOf(control: any): Array<{ display: string; value: string }> {
|
const list = control.GetDropdownList?.();
|
if (!list?.GetElementsCount || !list.GetItem) return [];
|
const options = [];
|
for (let index = 0; index < list.GetElementsCount(); index += 1) {
|
const item = list.GetItem(index);
|
options.push({ display: String(item.GetText()), value: String(item.GetValue()) });
|
}
|
return options;
|
}
|
|
function readValue(control: any, type: FillFieldType): boolean | string {
|
if (isPlaceholder(control)) return type === 'checkbox' ? false : '';
|
const text = textOf(control);
|
if (type === 'checkbox') return ['☑', '☒'].includes(text);
|
if (type === 'select') return optionsOf(control).find((item) => item.display === text)?.value ?? '';
|
if (type === 'multiline') return text.replace(/\r\n?/gu, '\n').replace(/\n$/u, '');
|
return text;
|
}
|
|
function writeValue(control: any, type: FillFieldType, value: boolean | string): void {
|
const originalTextPr = control.GetRange?.().GetTextPr?.();
|
function restoreTextPr(): void {
|
if (!originalTextPr) return;
|
const range = control.GetRange?.();
|
if (!range || typeof range.SetTextPr !== 'function' || !range.SetTextPr(originalTextPr)) {
|
throw new Error('保留字段格式失败');
|
}
|
}
|
if (value === '' && (type === 'select' || type === 'date')) {
|
if (control.RemoveAllElements() !== true) throw new Error('清空字段失败');
|
restoreTextPr();
|
return;
|
}
|
if (type === 'checkbox') {
|
control.SetCheckBoxChecked(Boolean(value));
|
return;
|
}
|
if (type === 'select') {
|
if (!optionsOf(control).some((item) => item.value === value)) throw new Error('下拉值不在文档选项中');
|
control.SelectListItem(value);
|
return;
|
}
|
if (type === 'date') {
|
const format = dateFormatOf(control);
|
const match = /h/iu.test(format)
|
? /^(\d{4})-(\d{2})-(\d{2}) (\d{2}):(\d{2}):(\d{2})$/u.exec(String(value))
|
: /^(\d{4})-(\d{2})-(\d{2})$/u.exec(String(value));
|
if (!match) throw new Error('日期格式无效');
|
const values = match.slice(1).map(Number);
|
const year = values[0]!;
|
const month = values[1]!;
|
const day = values[2]!;
|
const hour = values[3] ?? 0;
|
const minute = values[4] ?? 0;
|
const second = values[5] ?? 0;
|
const date = new Date(year, month - 1, day, hour, minute, second);
|
if (
|
date.getFullYear() !== year ||
|
date.getMonth() !== month - 1 ||
|
date.getDate() !== day ||
|
date.getHours() !== hour ||
|
date.getMinutes() !== minute ||
|
date.getSeconds() !== second
|
)
|
throw new Error('日期格式无效');
|
control.SetDate(date);
|
return;
|
}
|
control.RemoveAllElements();
|
if (type === 'multiline') {
|
String(value)
|
.split('\n')
|
.forEach((line, index) => {
|
const paragraph = Api.CreateParagraph();
|
paragraph.AddText(line);
|
control.AddElement(paragraph, index);
|
});
|
restoreTextPr();
|
return;
|
}
|
const run = Api.CreateRun();
|
run.AddText(String(value));
|
control.AddElement(run, 0);
|
restoreTextPr();
|
}
|
|
function isReferenceTag(tag: string): boolean {
|
return /^eln\.ref\.[\w.-]+\.[\w-]+$/u.test(tag);
|
}
|
|
const changes = Asc.scope.elnRecordFillChanges;
|
if (!Array.isArray(changes)) return JSON.stringify({ ok: false, error: '缺少字段修改', results: [] });
|
const controls = Api.GetDocument().GetAllContentControls();
|
const results = changes.map((change) => {
|
const matches = controls.filter((control: any) => control.GetTag() === change.tag);
|
if (matches.length !== 1) return { tag: change.tag, ok: false, error: '无法按 Tag 定位唯一控件' };
|
const control = matches[0];
|
const actualType = kindOf(control);
|
if (actualType !== change.type) return { tag: change.tag, ok: false, error: '字段类型与文档控件不一致' };
|
const before = readValue(control, actualType);
|
const originalLock = String(control.GetLock?.() ?? '');
|
const unlockForWrite = originalLock === 'sdtContentLocked'
|
|| (isReferenceTag(change.tag) && originalLock === 'contentLocked');
|
let temporarilyUnlocked = false;
|
try {
|
if (unlockForWrite) {
|
if (control.SetLock?.('unlocked') !== true) throw new Error('受控字段解锁失败');
|
temporarilyUnlocked = true;
|
}
|
writeValue(control, actualType, change.value);
|
if (readValue(control, actualType) !== change.value) throw new Error('字段写入后回读不一致');
|
if (temporarilyUnlocked && control.SetLock?.(originalLock) !== true) throw new Error('受控字段恢复锁失败');
|
return { tag: change.tag, ok: true };
|
} catch (error) {
|
try {
|
if (temporarilyUnlocked) writeValue(control, actualType, before);
|
if (temporarilyUnlocked) control.SetLock?.(originalLock);
|
else writeValue(control, actualType, before);
|
} catch {
|
/* 原生撤销是最终恢复手段。 */
|
}
|
return { tag: change.tag, ok: false, error: errorMessage(error) };
|
}
|
});
|
return JSON.stringify({ ok: true, results });
|
}
|
|
export function cloneRepeatRowCommand(): string {
|
function errorMessage(error: unknown): string {
|
return error instanceof Error ? error.message : String(error);
|
}
|
|
function requireMethod(target: any, name: string, message: string): void {
|
if (!target || typeof target[name] !== 'function') throw new Error(message);
|
}
|
|
function internalId(target: any, message: string): string {
|
requireMethod(target, 'GetInternalId', message);
|
const value = String(target.GetInternalId() ?? '');
|
if (!value) throw new Error(message);
|
return value;
|
}
|
|
function rowOf(control: any): any {
|
requireMethod(control, 'GetParentTableCell', '重复列必须位于表格单元格内');
|
const cell = control.GetParentTableCell();
|
requireMethod(cell, 'GetParentRow', '重复列所在表格行无效');
|
return cell.GetParentRow();
|
}
|
|
function tableOf(control: any): any {
|
const cell = control.GetParentTableCell();
|
requireMethod(cell, 'GetParentTable', '重复列所在表格无效');
|
return cell.GetParentTable();
|
}
|
|
let targetRow: any = null;
|
try {
|
const spec = Asc.scope.elnRecordFillRepeatRow;
|
if (!spec || !/^[\w-]+$/u.test(spec.groupCode) || !/^[\w-]+$/u.test(spec.rowId) || spec.rowId === 'prototype'
|
|| (spec.beforeRowId !== undefined && (!/^[\w-]+$/u.test(spec.beforeRowId)
|
|| spec.beforeRowId === 'prototype' || spec.beforeRowId === spec.rowId))) {
|
throw new Error('重复行参数无效');
|
}
|
const prefix = `eln.repeat.${spec.groupCode}.prototype.`;
|
const document = Api.GetDocument();
|
const controls = document.GetAllContentControls();
|
const prototypes = controls.filter((control: any) => String(control.GetTag?.() ?? '').startsWith(prefix));
|
if (!prototypes.length) throw new Error('文档中没有重复组原型行');
|
|
const sourceRow = rowOf(prototypes[0]);
|
const sourceTable = tableOf(prototypes[0]);
|
const sourceRowId = internalId(sourceRow, '无法识别重复组原型行');
|
const sourceTableId = internalId(sourceTable, '无法识别重复组表格');
|
const fields = new Set<string>();
|
for (const control of prototypes) {
|
if (internalId(rowOf(control), '无法识别重复组原型行') !== sourceRowId
|
|| internalId(tableOf(control), '无法识别重复组表格') !== sourceTableId) {
|
throw new Error('重复组原型列不在同一表格行');
|
}
|
const fieldCode = String(control.GetTag()).slice(prefix.length);
|
if (!fieldCode || fields.has(fieldCode)) throw new Error('重复组原型列 Tag 无效或重复');
|
fields.add(fieldCode);
|
}
|
|
const runtimePrefix = `eln.repeat.${spec.groupCode}.${spec.rowId}.`;
|
const existing = controls.filter((control: any) => String(control.GetTag?.() ?? '').startsWith(runtimePrefix));
|
if (existing.length) {
|
const existingFields = new Set<string>();
|
const existingRowId = internalId(rowOf(existing[0]), '无法识别已有重复行');
|
for (const control of existing) {
|
const fieldCode = String(control.GetTag()).slice(runtimePrefix.length);
|
if (!fields.has(fieldCode) || existingFields.has(fieldCode)
|
|| internalId(rowOf(control), '无法识别已有重复行') !== existingRowId
|
|| internalId(tableOf(control), '无法识别已有重复行表格') !== sourceTableId) {
|
throw new Error('已有重复行控件不完整');
|
}
|
existingFields.add(fieldCode);
|
}
|
if (existingFields.size !== fields.size) throw new Error('已有重复行控件不完整');
|
return JSON.stringify({ ok: true, tags: existing.map((control: any) => String(control.GetTag())) });
|
}
|
|
requireMethod(sourceTable, 'GetRowsCount', '当前编辑器不支持读取表格行');
|
requireMethod(sourceTable, 'GetRow', '当前编辑器不支持读取表格行');
|
requireMethod(sourceTable, 'AddRow', '当前编辑器不支持新增表格行');
|
requireMethod(sourceRow, 'GetCellsCount', '当前编辑器不支持读取原型行单元格');
|
requireMethod(sourceRow, 'GetCell', '当前编辑器不支持读取原型行单元格');
|
const rowsBefore = Number(sourceTable.GetRowsCount());
|
const cellsCount = Number(sourceRow.GetCellsCount());
|
if (!Number.isInteger(rowsBefore) || rowsBefore < 1 || !Number.isInteger(cellsCount) || cellsCount < 1) {
|
throw new Error('重复组表格结构无效');
|
}
|
let anchorRow = sourceTable.GetRow(rowsBefore - 1);
|
let insertBefore = false;
|
let targetIndex = rowsBefore;
|
if (spec.beforeRowId) {
|
const beforePrefix = `eln.repeat.${spec.groupCode}.${spec.beforeRowId}.`;
|
const beforeControls = controls.filter((control: any) => String(control.GetTag?.() ?? '').startsWith(beforePrefix));
|
if (!beforeControls.length) throw new Error('恢复位置的下一重复行不存在');
|
anchorRow = rowOf(beforeControls[0]);
|
if (internalId(tableOf(beforeControls[0]), '无法识别恢复位置表格') !== sourceTableId) {
|
throw new Error('恢复位置与重复组原型不在同一表格');
|
}
|
const anchorRowId = internalId(anchorRow, '无法识别恢复位置行');
|
targetIndex = -1;
|
for (let rowIndex = 0; rowIndex < rowsBefore; rowIndex += 1) {
|
if (internalId(sourceTable.GetRow(rowIndex), '无法识别表格行') === anchorRowId) {
|
targetIndex = rowIndex;
|
break;
|
}
|
}
|
if (targetIndex < 0) throw new Error('无法定位恢复位置行');
|
insertBefore = true;
|
}
|
requireMethod(anchorRow, 'GetCell', '当前编辑器不支持定位表格锚点行');
|
const addedRow = sourceTable.AddRow(anchorRow.GetCell(0), insertBefore);
|
if (!addedRow) throw new Error('新增重复行失败');
|
targetRow = addedRow;
|
if (Number(sourceTable.GetRowsCount()) !== rowsBefore + 1) throw new Error('新增重复行后表格行数未增加');
|
if (internalId(targetRow, '无法识别新增重复行')
|
!== internalId(sourceTable.GetRow(targetIndex), '无法识别表格目标行')) {
|
throw new Error(insertBefore ? '恢复重复行未位于指定顺序' : '新增重复行未位于表格末尾');
|
}
|
requireMethod(targetRow, 'GetCellsCount', '新增重复行结构无效');
|
requireMethod(targetRow, 'GetCell', '新增重复行结构无效');
|
if (Number(targetRow.GetCellsCount()) !== cellsCount) throw new Error('新增重复行列数与原型不一致');
|
|
// ONLYOFFICE 9.4 AddRow emits an isolated vMerge restart on every new cell.
|
// Clear it before saving so the runtime row preserves the unmerged prototype contract.
|
for (let cellIndex = 0; cellIndex < cellsCount; cellIndex += 1) {
|
const targetCell = targetRow.GetCell(cellIndex);
|
requireMethod(targetCell?.Cell, 'SetVMerge', '当前编辑器不支持清除新增行跨行合并标记');
|
targetCell.Cell.SetVMerge(undefined);
|
}
|
|
const firstTargetCell = targetRow.GetCell(0);
|
requireMethod(firstTargetCell, 'Clear', '无法清空新增行内容');
|
if (firstTargetCell.Clear() === false) throw new Error('清空新增行内容失败');
|
|
for (let cellIndex = 0; cellIndex < cellsCount; cellIndex += 1) {
|
const sourceCell = sourceRow.GetCell(cellIndex);
|
const targetCell = targetRow.GetCell(cellIndex);
|
requireMethod(sourceCell, 'GetContent', '无法读取原型行内容');
|
requireMethod(targetCell, 'AddElement', '无法复制原型行内容');
|
const sourceContent = sourceCell.GetContent();
|
requireMethod(sourceContent, 'GetContent', '无法读取原型行内容');
|
const elements = sourceContent.GetContent();
|
if (!Array.isArray(elements)) throw new Error('原型行内容结构无效');
|
for (let elementIndex = 0; elementIndex < elements.length; elementIndex += 1) {
|
const element = elements[elementIndex];
|
requireMethod(element, 'Copy', '当前编辑器不支持复制原型行内容');
|
if (targetCell.AddElement(elementIndex, element.Copy()) === false) throw new Error('复制原型行内容失败');
|
}
|
}
|
|
const targetRowId = internalId(targetRow, '无法识别新增重复行');
|
const copied = document.GetAllContentControls().filter((control: any) => {
|
try {
|
return internalId(rowOf(control), '无法识别新增重复行') === targetRowId
|
&& String(control.GetTag?.() ?? '').startsWith(prefix);
|
} catch {
|
return false;
|
}
|
});
|
if (copied.length !== fields.size) throw new Error('新增重复行控件复制不完整');
|
const tags: string[] = [];
|
const copiedFields = new Set<string>();
|
for (const control of copied) {
|
const fieldCode = String(control.GetTag()).slice(prefix.length);
|
if (!fields.has(fieldCode) || copiedFields.has(fieldCode)) throw new Error('新增重复行控件 Tag 无效或重复');
|
copiedFields.add(fieldCode);
|
requireMethod(control, 'SetTag', '当前编辑器不支持重写重复行 Tag');
|
const tag = `eln.repeat.${spec.groupCode}.${spec.rowId}.${fieldCode}`;
|
if (control.SetTag(tag) !== true || String(control.GetTag()) !== tag) throw new Error('重写重复行 Tag 失败');
|
tags.push(tag);
|
}
|
if (copiedFields.size !== fields.size) throw new Error('新增重复行字段不完整');
|
return JSON.stringify({ ok: true, tags });
|
} catch (error) {
|
let rollbackFailed = false;
|
if (targetRow) {
|
try {
|
rollbackFailed = typeof targetRow.Remove !== 'function' || targetRow.Remove() !== true;
|
} catch {
|
rollbackFailed = true;
|
}
|
}
|
const message = errorMessage(error);
|
return JSON.stringify({ ok: false, error: rollbackFailed ? `${message};文档回滚失败` : message });
|
}
|
}
|
|
export function removeRepeatRowCommand(): string {
|
interface InternalIdObject {
|
GetInternalId?: () => unknown;
|
}
|
interface TableRow extends InternalIdObject {
|
Remove?: () => unknown;
|
}
|
type TableObject = InternalIdObject;
|
interface TableCell {
|
GetParentRow?: () => TableRow;
|
GetParentTable?: () => TableObject;
|
}
|
interface ContentControl {
|
GetParentTableCell?: () => TableCell;
|
GetTag?: () => unknown;
|
}
|
|
function errorMessage(error: unknown): string {
|
return error instanceof Error ? error.message : String(error);
|
}
|
|
function internalId(target: InternalIdObject, message: string): string {
|
if (typeof target?.GetInternalId !== 'function') throw new Error(message);
|
const value = String(target.GetInternalId() ?? '');
|
if (!value) throw new Error(message);
|
return value;
|
}
|
|
function rowOf(control: ContentControl): TableRow {
|
if (typeof control?.GetParentTableCell !== 'function') throw new Error('重复列必须位于表格单元格内');
|
const cell = control.GetParentTableCell();
|
if (typeof cell?.GetParentRow !== 'function') throw new Error('重复列所在表格行无效');
|
return cell.GetParentRow();
|
}
|
|
function tableOf(control: ContentControl): TableObject {
|
if (typeof control?.GetParentTableCell !== 'function') throw new Error('重复列必须位于表格单元格内');
|
const cell = control.GetParentTableCell();
|
if (typeof cell?.GetParentTable !== 'function') throw new Error('重复列所在表格无效');
|
return cell.GetParentTable();
|
}
|
|
try {
|
const spec = Asc.scope.elnRecordFillRepeatRow;
|
if (!spec || !/^[\w-]+$/u.test(spec.groupCode) || !/^[\w-]+$/u.test(spec.rowId) || spec.rowId === 'prototype') {
|
throw new Error('重复行参数无效');
|
}
|
const prototypePrefix = `eln.repeat.${spec.groupCode}.prototype.`;
|
const runtimePrefix = `eln.repeat.${spec.groupCode}.${spec.rowId}.`;
|
const document = Api.GetDocument();
|
const controls = document.GetAllContentControls() as ContentControl[];
|
const prototypes = controls.filter((control) => String(control.GetTag?.() ?? '').startsWith(prototypePrefix));
|
const targets = controls.filter((control) => String(control.GetTag?.() ?? '').startsWith(runtimePrefix));
|
if (!prototypes.length) throw new Error('文档中没有重复组原型行');
|
if (!targets.length) throw new Error('文档中没有目标重复行');
|
|
const prototypeFields = new Set<string>();
|
for (const control of prototypes) {
|
const fieldCode = String(control.GetTag?.() ?? '').slice(prototypePrefix.length);
|
if (!fieldCode || prototypeFields.has(fieldCode)) throw new Error('重复组原型列 Tag 无效或重复');
|
prototypeFields.add(fieldCode);
|
}
|
const targetRow = rowOf(targets[0]);
|
const targetTable = tableOf(targets[0]);
|
const targetRowId = internalId(targetRow, '无法识别目标重复行');
|
const targetTableId = internalId(targetTable, '无法识别目标重复行表格');
|
const targetFields = new Set<string>();
|
const tags: string[] = [];
|
for (const control of targets) {
|
const fieldCode = String(control.GetTag?.() ?? '').slice(runtimePrefix.length);
|
if (!prototypeFields.has(fieldCode) || targetFields.has(fieldCode)
|
|| internalId(rowOf(control), '无法识别目标重复行') !== targetRowId
|
|| internalId(tableOf(control), '无法识别目标重复行表格') !== targetTableId) {
|
throw new Error('目标重复行控件不完整');
|
}
|
targetFields.add(fieldCode);
|
tags.push(String(control.GetTag?.() ?? ''));
|
}
|
if (targetFields.size !== prototypeFields.size) throw new Error('目标重复行控件不完整');
|
|
for (const control of controls) {
|
let sameRow = false;
|
try { sameRow = internalId(rowOf(control), '无法识别重复行') === targetRowId; } catch { continue; }
|
if (!sameRow) continue;
|
const tag = String(control.GetTag?.() ?? '');
|
if (tag.startsWith('eln.repeat.') && !tag.startsWith(runtimePrefix)) {
|
throw new Error('目标物理行包含其他重复身份');
|
}
|
}
|
if (typeof targetRow.Remove !== 'function') throw new Error('当前编辑器不支持删除表格行');
|
if (targetRow.Remove() !== true) throw new Error('删除重复行失败');
|
const remaining = (document.GetAllContentControls() as ContentControl[])
|
.filter((control) => String(control.GetTag?.() ?? '').startsWith(runtimePrefix));
|
if (remaining.length) throw new Error('删除重复行后回读仍存在目标控件');
|
return JSON.stringify({ ok: true, tags });
|
} catch (error) {
|
return JSON.stringify({ ok: false, error: errorMessage(error) });
|
}
|
}
|