|
@@ -5,6 +5,7 @@ $(document).ready(function() {
|
|
|
constructor() {
|
|
constructor() {
|
|
|
this.spread = SpreadJsObj.createNewSpread($('#config-dir-spread')[0]);
|
|
this.spread = SpreadJsObj.createNewSpread($('#config-dir-spread')[0]);
|
|
|
this.spread.options.showHorizontalScrollbar = false;
|
|
this.spread.options.showHorizontalScrollbar = false;
|
|
|
|
|
+ this.spread.options.allowUndo = false;
|
|
|
this.sheet = this.spread.getActiveSheet();
|
|
this.sheet = this.spread.getActiveSheet();
|
|
|
this.treeIcons = {
|
|
this.treeIcons = {
|
|
|
closed: new Image(),
|
|
closed: new Image(),
|
|
@@ -47,24 +48,27 @@ $(document).ready(function() {
|
|
|
getValue: function(data) { return data.is_fixed ? '\u{1F512}' : ''; },
|
|
getValue: function(data) { return data.is_fixed ? '\u{1F512}' : ''; },
|
|
|
},
|
|
},
|
|
|
{ title: '归档单位', colSpan: '1', rowSpan: '1', field: 'file_company', hAlign: 0, width: 160, formatter: '@', cellType: 'autoTip', wordWrap: true },
|
|
{ title: '归档单位', colSpan: '1', rowSpan: '1', field: 'file_company', hAlign: 0, width: 160, formatter: '@', cellType: 'autoTip', wordWrap: true },
|
|
|
- { title: '上传说明', colSpan: '1', rowSpan: '1', field: 'upload_tips', hAlign: 0, width: 200, formatter: '@', cellType: 'autoTip', wordWrap: true },
|
|
|
|
|
|
|
+ { title: '上报说明', colSpan: '1', rowSpan: '1', field: 'upload_tips', hAlign: 0, width: 200, formatter: '@', cellType: 'autoTip', wordWrap: true },
|
|
|
{ title: '授权用户', colSpan: '1', rowSpan: '1', field: 'auth_users', hAlign: 0, width: 320, formatter: '@', cellType: 'autoTip' },
|
|
{ title: '授权用户', colSpan: '1', rowSpan: '1', field: 'auth_users', hAlign: 0, width: 320, formatter: '@', cellType: 'autoTip' },
|
|
|
{ title: '添加人', colSpan: '1', rowSpan: '1', field: 'add_user', hAlign: 0, width: 90, formatter: '@', cellType: 'autoTip' },
|
|
{ title: '添加人', colSpan: '1', rowSpan: '1', field: 'add_user', hAlign: 0, width: 90, formatter: '@', cellType: 'autoTip' },
|
|
|
],
|
|
],
|
|
|
emptyRows: 0, headRows: 1, headRowHeight: [32], defaultRowHeight: 24,
|
|
emptyRows: 0, headRows: 1, headRowHeight: [32], defaultRowHeight: 24,
|
|
|
headColWidth: [25], headerFont: '12px 微软雅黑', font: '12px 微软雅黑',
|
|
headColWidth: [25], headerFont: '12px 微软雅黑', font: '12px 微软雅黑',
|
|
|
- readOnly: true, selectedBackColor: '#e8f4fd',
|
|
|
|
|
|
|
+ readOnly: !configDirLocked || !canManageConfigDir, selectedBackColor: '#e8f4fd',
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
|
|
|
|
|
this.curNode = null;
|
|
this.curNode = null;
|
|
|
- this.editableTextFields = [ 'upload_tips', 'file_company' ];
|
|
|
|
|
- var editableTextFields = this.editableTextFields;
|
|
|
|
|
- this.editableTextColumnIndexes = this.spreadSetting.cols.reduce(function(indexes, col, index) {
|
|
|
|
|
- if (editableTextFields.indexOf(col.field) >= 0) indexes.push(index);
|
|
|
|
|
- return indexes;
|
|
|
|
|
- }, []);
|
|
|
|
|
|
|
+ this.editableTextColumnIndexes = [];
|
|
|
|
|
+ this.spreadSetting.cols.forEach((col, index) => {
|
|
|
|
|
+ col.readOnly = col.field !== 'file_company' && col.field !== 'upload_tips';
|
|
|
|
|
+ if (!col.readOnly) {
|
|
|
|
|
+ this.editableTextColumnIndexes.push(index);
|
|
|
|
|
+ col.getTip = node => _.escape(node[col.field] || '');
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ SpreadJsObj.initSheet(this.sheet, this.spreadSetting);
|
|
|
this._textCellEditing = null;
|
|
this._textCellEditing = null;
|
|
|
|
|
+ this._pendingTextCells = new Map();
|
|
|
this._editableTextCellType = null;
|
|
this._editableTextCellType = null;
|
|
|
this._authUserSaving = false;
|
|
this._authUserSaving = false;
|
|
|
this._authTargetSaving = false;
|
|
this._authTargetSaving = false;
|
|
@@ -273,15 +277,18 @@ $(document).ready(function() {
|
|
|
self._refreshAuthUserButton(node);
|
|
self._refreshAuthUserButton(node);
|
|
|
});
|
|
});
|
|
|
this.spread.bind(spreadNS.Events.EditStarting, function(e, info) {
|
|
this.spread.bind(spreadNS.Events.EditStarting, function(e, info) {
|
|
|
- var editing = self._textCellEditing;
|
|
|
|
|
- if (!editing || info.row !== editing.row || info.col !== editing.col ||
|
|
|
|
|
- !configDirLocked || !canManageConfigDir) {
|
|
|
|
|
|
|
+ const editing = self._getTextCellEdit(info.row, info.col);
|
|
|
|
|
+ if (!editing) {
|
|
|
info.cancel = true;
|
|
info.cancel = true;
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
+ self._textCellEditing = editing;
|
|
|
|
|
+ self._showTextCellStatus();
|
|
|
});
|
|
});
|
|
|
this.spread.bind(spreadNS.Events.EditEnded, function(e, info) {
|
|
this.spread.bind(spreadNS.Events.EditEnded, function(e, info) {
|
|
|
self._saveTextCellEdit(info);
|
|
self._saveTextCellEdit(info);
|
|
|
});
|
|
});
|
|
|
|
|
+ this._bindTextCellShortcuts();
|
|
|
|
|
|
|
|
$('#cd-add-sibling').click(function() {
|
|
$('#cd-add-sibling').click(function() {
|
|
|
var node = self._getSelectedNode();
|
|
var node = self._getSelectedNode();
|
|
@@ -452,57 +459,42 @@ $(document).ready(function() {
|
|
|
var node = self._getSelectedNode();
|
|
var node = self._getSelectedNode();
|
|
|
if (!node) return;
|
|
if (!node) return;
|
|
|
var selection = self.sheet.getSelections()[0];
|
|
var selection = self.sheet.getSelections()[0];
|
|
|
- if (selection && self._isEditableTextColumn(selection.col)) {
|
|
|
|
|
- self._startTextCellEdit(selection.row, selection.col, node);
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (selection && self._isEditableTextColumn(selection.col)) return;
|
|
|
self._showEditModal(node);
|
|
self._showEditModal(node);
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- _startTextCellEdit(row, col, node) {
|
|
|
|
|
- if (row < 0 || !node || !this._isEditableTextColumn(col)) return;
|
|
|
|
|
- var field = this.spreadSetting.cols[col].field;
|
|
|
|
|
- this._textCellEditing = {
|
|
|
|
|
- row: row,
|
|
|
|
|
- col: col,
|
|
|
|
|
- field: field,
|
|
|
|
|
- filingId: String(node.id),
|
|
|
|
|
|
|
+ _getTextCellEdit(row, col) {
|
|
|
|
|
+ const node = this.filingTree.nodes[row];
|
|
|
|
|
+ if (!configDirLocked || !canManageConfigDir || !node || !this._isEditableTextColumn(col)) return null;
|
|
|
|
|
+ const { field, title } = this.spreadSetting.cols[col];
|
|
|
|
|
+ const key = node.id + ':' + field;
|
|
|
|
|
+ if (this._pendingTextCells.has(key)) return null;
|
|
|
|
|
+ return {
|
|
|
|
|
+ row, col, field, title, key, filingId: node.id,
|
|
|
originalValue: String(node[field] || ''),
|
|
originalValue: String(node[field] || ''),
|
|
|
};
|
|
};
|
|
|
- this.sheet.getCell(row, col).locked(false);
|
|
|
|
|
- this.sheet.startEdit(true);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_saveTextCellEdit(info) {
|
|
_saveTextCellEdit(info) {
|
|
|
- if (!info || !this._isEditableTextColumn(info.col)) return;
|
|
|
|
|
- info.sheet.getCell(info.row, info.col).locked(true);
|
|
|
|
|
- var editing = this._textCellEditing;
|
|
|
|
|
|
|
+ const editing = this._textCellEditing;
|
|
|
|
|
+ if (!editing || editing.row !== info.row || editing.col !== info.col) return;
|
|
|
this._textCellEditing = null;
|
|
this._textCellEditing = null;
|
|
|
- var node = this.filingTree.nodes[info.row];
|
|
|
|
|
- if (!editing || !node || editing.row !== info.row || editing.col !== info.col ||
|
|
|
|
|
- String(node.id) !== editing.filingId) {
|
|
|
|
|
- if (node) SpreadJsObj.reLoadRowData(info.sheet, info.row);
|
|
|
|
|
|
|
+ const node = this.filingTree.nodes[info.row];
|
|
|
|
|
+ if (!node || node.id !== editing.filingId) {
|
|
|
|
|
+ this._showTextCellStatus();
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- var textValue = String(info.editingText || '').trim();
|
|
|
|
|
- if (textValue === editing.originalValue) {
|
|
|
|
|
- SpreadJsObj.reLoadRowData(info.sheet, info.row);
|
|
|
|
|
|
|
+ // Esc 时 editingText 仍可能是取消前的草稿,读取实际提交到单元格的值。
|
|
|
|
|
+ const value = info.sheet.getValue(info.row, info.col);
|
|
|
|
|
+ const raw = value == null ? '' : String(value);
|
|
|
|
|
+ const textValue = raw.trim();
|
|
|
|
|
+ if (raw === editing.originalValue || textValue === editing.originalValue) {
|
|
|
|
|
+ if (raw !== editing.originalValue) info.sheet.setValue(info.row, info.col, editing.originalValue);
|
|
|
|
|
+ this._showTextCellStatus();
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- var saveData = { updateType: 'save', id: node.id };
|
|
|
|
|
- saveData[editing.field] = textValue;
|
|
|
|
|
- postData('config-dir/update', saveData, function() {
|
|
|
|
|
- var srcNode = filingData.find(function(item) { return String(item.id) === String(node.id); });
|
|
|
|
|
- if (srcNode) srcNode[editing.field] = textValue;
|
|
|
|
|
- node[editing.field] = textValue;
|
|
|
|
|
- SpreadJsObj.reLoadRowData(info.sheet, info.row);
|
|
|
|
|
- }, function() {
|
|
|
|
|
- node[editing.field] = editing.originalValue;
|
|
|
|
|
- SpreadJsObj.reLoadRowData(info.sheet, info.row);
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ this._persistTextCellValue(editing, textValue);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_getSubtreeTreeIds(node) {
|
|
_getSubtreeTreeIds(node) {
|
|
@@ -1563,6 +1555,102 @@ $(document).ready(function() {
|
|
|
return self._getAuthUserOptionHtml(user, isTarget);
|
|
return self._getAuthUserOptionHtml(user, isTarget);
|
|
|
}).join('');
|
|
}).join('');
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ _bindTextCellShortcuts() {
|
|
|
|
|
+ const commands = this.spread.commandManager();
|
|
|
|
|
+ const keys = GC.Spread.Commands.Key;
|
|
|
|
|
+ // 接管表格的清空命令;文字编辑器内仍由浏览器处理退格和删除。
|
|
|
|
|
+ const clear = () => {
|
|
|
|
|
+ if (this.sheet.isEditing()) return false;
|
|
|
|
|
+ this._replaceSelectedTextCell('');
|
|
|
|
|
+ return true;
|
|
|
|
|
+ };
|
|
|
|
|
+ [keys.backspace, keys.del].forEach(key => {
|
|
|
|
|
+ commands.setShortcutKey(null, key, false, false, false, false);
|
|
|
|
|
+ commands.register('configDirClearText' + key, clear, key, false, false, false, false);
|
|
|
|
|
+ });
|
|
|
|
|
+ commands.setShortcutKey(null, keys.enter, true, true, false, false);
|
|
|
|
|
+ const host = this.spread.getHost();
|
|
|
|
|
+ host.addEventListener('copy', e => {
|
|
|
|
|
+ if (this.sheet.isEditing() || !e.clipboardData) return;
|
|
|
|
|
+ const cell = this._getSelectedTextCell();
|
|
|
|
|
+ if (!cell) return;
|
|
|
|
|
+ e.clipboardData.setData('text/plain', this.sheet.getText(cell.row, cell.col));
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ e.stopImmediatePropagation();
|
|
|
|
|
+ }, true);
|
|
|
|
|
+ host.addEventListener('paste', e => {
|
|
|
|
|
+ if (this.sheet.isEditing() || !e.clipboardData) return;
|
|
|
|
|
+ e.preventDefault();
|
|
|
|
|
+ e.stopImmediatePropagation();
|
|
|
|
|
+ // 直接取原始文本,避免 SpreadJS 把说明中的换行、制表符拆成多个单元格。
|
|
|
|
|
+ if (Array.from(e.clipboardData.types).includes('text/plain')) {
|
|
|
|
|
+ this._replaceSelectedTextCell(e.clipboardData.getData('text/plain'));
|
|
|
|
|
+ }
|
|
|
|
|
+ }, true);
|
|
|
|
|
+ this.sheet.bind(spreadNS.Events.ClipboardPasting, (e, info) => {
|
|
|
|
|
+ if (this.sheet.isEditing()) return;
|
|
|
|
|
+ info.cancel = true;
|
|
|
|
|
+ const text = info.pasteData && info.pasteData.text;
|
|
|
|
|
+ if (typeof text === 'string' && text !== '') this._replaceSelectedTextCell(text);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _persistTextCellValue(editing, value) {
|
|
|
|
|
+ this._pendingTextCells.set(editing.key, editing);
|
|
|
|
|
+ this._showTextCellStatus();
|
|
|
|
|
+ const finish = saved => {
|
|
|
|
|
+ this._pendingTextCells.delete(editing.key);
|
|
|
|
|
+ const row = this.filingTree.nodes.findIndex(node => node.id === editing.filingId);
|
|
|
|
|
+ if (saved) {
|
|
|
|
|
+ const source = filingData.find(node => node.id === editing.filingId);
|
|
|
|
|
+ if (source) source[editing.field] = value;
|
|
|
|
|
+ if (row >= 0) this.filingTree.nodes[row][editing.field] = value;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 紧接着编辑同一行的另一列时,只刷新原单元格。
|
|
|
|
|
+ if (row >= 0) {
|
|
|
|
|
+ this.sheet.setValue(row, editing.col, saved ? value : editing.originalValue);
|
|
|
|
|
+ if (this.sheet.getRowVisible(row)) this.sheet.autoFitRow(row);
|
|
|
|
|
+ }
|
|
|
|
|
+ this._showTextCellStatus(editing.title + (saved ? ':已保存' : ':保存失败,请重新编辑'),
|
|
|
|
|
+ saved ? 'text-success' : 'text-danger');
|
|
|
|
|
+ };
|
|
|
|
|
+ postData('config-dir/update', { updateType: 'save', id: editing.filingId, [editing.field]: value },
|
|
|
|
|
+ () => finish(true), () => finish(false), false);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _showTextCellStatus(message = '', color = 'text-muted') {
|
|
|
|
|
+ if (color !== 'text-danger') {
|
|
|
|
|
+ if (this._textCellEditing) {
|
|
|
|
|
+ message = this._textCellEditing.title + ':编辑中';
|
|
|
|
|
+ color = 'text-muted';
|
|
|
|
|
+ } else if (this._pendingTextCells.size) {
|
|
|
|
|
+ message = this._pendingTextCells.values().next().value.title + ':保存中…';
|
|
|
|
|
+ color = 'text-muted';
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ $('#config-dir-text-status').removeClass('text-muted text-success text-danger').addClass(color).text(message);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _getSelectedTextCell() {
|
|
|
|
|
+ const selections = this.sheet.getSelections();
|
|
|
|
|
+ const cell = selections[0];
|
|
|
|
|
+ if (selections.length !== 1 || cell.rowCount !== 1 || cell.colCount !== 1 ||
|
|
|
|
|
+ !this._isEditableTextColumn(cell.col) || !this.filingTree.nodes[cell.row]) return null;
|
|
|
|
|
+ return cell;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ _replaceSelectedTextCell(text) {
|
|
|
|
|
+ const cell = this._getSelectedTextCell();
|
|
|
|
|
+ if (!cell) return;
|
|
|
|
|
+ const editing = this._getTextCellEdit(cell.row, cell.col);
|
|
|
|
|
+ if (!editing) return;
|
|
|
|
|
+ const raw = text.replace(/\r\n?/g, '\n');
|
|
|
|
|
+ const value = raw.trim();
|
|
|
|
|
+ if (raw === editing.originalValue || value === editing.originalValue) return;
|
|
|
|
|
+ this.sheet.setValue(cell.row, cell.col, value);
|
|
|
|
|
+ this._persistTextCellValue(editing, value);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
new ConfigDir();
|
|
new ConfigDir();
|