Files
N_XLXSBJ----MX/images/TBBJKSLR.js
T
2026-08-21 17:03:02 +08:00

1890 lines
67 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
class CustomRichSelectEditor {
constructor() {
this.eGui = null;
this.input = null;
this.list = null;
this.values = [];
this.filteredValues = [];
this.isOpen = false;
this.currentValue = '';
this.params = null;
}
init(params) {
this.params = params;
this.values = params.values || [];
this.filteredValues = [...this.values];
this.currentValue = params.value || '';
// 创建容器
this.eGui = document.createElement('div');
this.eGui.className = 'custom-rich-select';
this.eGui.style.cssText = `
position: relative;
width: 100%;
height: 100%;
`;
// 创建输入框
this.input = document.createElement('input');
this.input.type = 'text';
this.input.value = this.currentValue;
this.input.style.cssText = `
width: 100%;
height: 100%;
border: 1px solid #d9d9d9;
padding: 0 8px;
font-size: 14px;
outline: none;
box-sizing: border-box;
`;
this.input.addEventListener('input', this.handleInput.bind(this));
this.input.addEventListener('focus', this.handleFocus.bind(this));
this.input.addEventListener('blur', this.handleBlur.bind(this));
this.input.addEventListener('keydown', this.handleKeyDown.bind(this));
// 创建下拉列表
this.list = document.createElement('div');
this.list.className = 'custom-rich-select-list';
this.list.style.cssText = `
position: absolute;
top: 100%;
left: 0;
width: 100%;
max-height: 200px;
background: white;
border: 1px solid #d9d9d9;
border-top: none;
z-index: 1000;
overflow-y: auto;
display: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
`;
this.eGui.appendChild(this.input);
this.eGui.appendChild(this.list);
this.updateList();
// 返回一个Promise,确保ag-Grid能正确处理异步初始化
return new Promise((resolve) => {
// 延迟一小段时间确保DOM已经完全渲染
setTimeout(() => {
resolve(this.eGui);
}, 0);
});
}
handleInput(event) {
const searchValue = event.target.value.toLowerCase();
this.filteredValues = this.values.filter(value =>
value.toLowerCase().includes(searchValue)
);
this.updateList();
this.showList();
}
handleFocus() {
this.showList();
}
handleBlur() {
setTimeout(() => {
this.hideList();
}, 200);
}
handleKeyDown(event) {
switch (event.key) {
case 'Enter':
if (this.isOpen && this.filteredValues.length > 0) {
this.selectValue(this.filteredValues[0]);
event.preventDefault();
}
break;
case 'Escape':
this.hideList();
event.preventDefault();
break;
case 'ArrowDown':
if (!this.isOpen) {
this.showList();
}
event.preventDefault();
break;
}
}
showList() {
if (!this.isOpen) {
this.list.style.display = 'block';
this.isOpen = true;
}
}
hideList() {
if (this.isOpen) {
this.list.style.display = 'none';
this.isOpen = false;
}
}
updateList() {
this.list.innerHTML = '';
if (this.filteredValues.length === 0) {
const noResults = document.createElement('div');
noResults.textContent = '无匹配结果';
noResults.style.cssText = `
padding: 8px;
color: #999;
font-style: italic;
`;
this.list.appendChild(noResults);
} else {
this.filteredValues.forEach(value => {
const item = document.createElement('div');
item.textContent = value;
item.style.cssText = `
padding: 8px;
cursor: pointer;
border-bottom: 1px solid #f0f0f0;
`;
item.addEventListener('mouseenter', () => {
item.style.backgroundColor = '#f5f5f5';
});
item.addEventListener('mouseleave', () => {
item.style.backgroundColor = '';
});
item.addEventListener('click', () => {
this.selectValue(value);
});
this.list.appendChild(item);
});
}
}
selectValue(value) {
this.input.value = value;
this.currentValue = value;
this.hideList();
if (this.params && this.params.stopEditing) {
this.params.stopEditing();
}
}
getValue() {
return this.currentValue;
}
getGui() {
return this.eGui;
}
isPopup() {
return true;
}
afterGuiAttached() {
if (this.input) {
this.input.focus();
this.input.select();
}
}
destroy() {
// 清理事件监听器
if (this.input) {
this.input.removeEventListener('input', this.handleInput);
this.input.removeEventListener('focus', this.handleFocus);
this.input.removeEventListener('blur', this.handleBlur);
this.input.removeEventListener('keydown', this.handleKeyDown);
}
}
}
// 自定义编辑器类已定义,无需再次注册到ag-Grid
// ag-Grid Community Edition 会自动识别自定义编辑器类
// Product类定义
class Product {
// 私有字段
#_serialNumber;
#_quoteProduct;
#_model;
#_specification;
#_standard;
#_voltage;
#_unit;
#_quantity;
// 公共属性(使用属性封装字段)
get SerialNumber() {
return this.#_serialNumber || 0;
}
set SerialNumber(value) {
this.#_serialNumber = parseInt(value) || 0;
}
get QuoteProduct() {
return this.#_quoteProduct || '';
}
set QuoteProduct(value) {
this.#_quoteProduct = value;
}
get Model() {
return this.#_model || '';
}
set Model(value) {
this.#_model = value;
}
get Specification() {
return this.#_specification || '';
}
set Specification(value) {
this.#_specification = value;
}
get Standard() {
return this.#_standard || '';
}
set Standard(value) {
this.#_standard = value;
}
get Voltage() {
return this.#_voltage || '';
}
set Voltage(value) {
this.#_voltage = value;
}
get Unit() {
return this.#_unit || '';
}
set Unit(value) {
this.#_unit = value;
}
get Quantity() {
return this.#_quantity || 0;
}
set Quantity(value) {
this.#_quantity = parseFloat(value) || 0;
}
// 默认构造函数
constructor() {
this.#_serialNumber = 0;
this.#_quoteProduct = '';
this.#_model = '';
this.#_specification = '';
this.#_standard = '';
this.#_voltage = '';
this.#_unit = '';
this.#_quantity = 0;
}
// 带参数的构造函数(可选)
static createWithParams(serialNumber, quoteProduct, model, specification, standard, voltage, unit, quantity) {
const product = new Product();
product.SerialNumber = serialNumber;
product.QuoteProduct = quoteProduct;
product.Model = model;
product.Specification = specification;
product.Standard = standard;
product.Voltage = voltage;
product.Unit = unit;
product.Quantity = quantity;
return product;
}
// 转换为JSON格式
toJSON() {
return {
SerialNumber: this.SerialNumber,
QuoteProduct: this.QuoteProduct,
Model: this.Model,
Specification: this.Specification,
Standard: this.Standard,
Voltage: this.Voltage,
Unit: this.Unit,
Quantity: this.Quantity
};
}
// 从JSON创建实例
static fromJSON(json) {
const product = new Product();
if (json) {
product.SerialNumber = json.SerialNumber || 0;
product.QuoteProduct = json.QuoteProduct || json.quoteProduct || '';
product.Model = json.Model || '';
product.Specification = json.Specification || '';
product.Standard = json.Standard || '';
product.Voltage = json.Voltage || '';
product.Unit = json.Unit || '';
product.Quantity = json.Quantity || 0;
}
return product;
}
}
function getUrlParameterBJDID() {
const results = document.getElementById("txtBJDID").value;
return results === null ? "" : results;
}
const BJDID = getUrlParameterBJDID();
// 投标数据快速录入功能
const { createApp } = Vue;
const { ElMessage } = ElementPlus;
const app = createApp({
data() {
return {
gridApi: null,
gridColumnApi: null,
rowData: [],
isEditing: false, // 编辑状态
originalData: [], // 原始数据备份
standardDictionary: null, // 标准字典数据
voltageLevels: null, // 电压等级列表数据
isProcessing: false, // 处理状态
progressPercentage: 0, // 进度百分比
progressText: '', // 进度文本
columnDefs: [
{
headerName: '序号',
width: 80,
pinned: 'left',
lockPinned: true,
suppressMovable: true,
sortable: false,
resizable: false,
editable: false,
filter: false,
cellStyle: {
backgroundColor: '#f8f9fa',
fontWeight: 'bold',
textAlign: 'center'
},
valueGetter: (params) => {
return params.node.rowIndex + 1;
},
valueSetter: (params) => {
return false;
}
},
{
headerName: '报价产品',
field: 'quoteProduct',
editable: () => this.isEditing,
filter: 'agSetColumnFilter',
filterParams: {
applyMiniFilterWhileTyping: true,
suppressSorting: true
},
sortable: true,
resizable: true,
width: 200,
cellStyle: (params) => {
return this.isEditing ? { backgroundColor: '#f0f9ff' } : {};
}
},
{
headerName: '型号',
field: 'model',
editable: () => this.isEditing, // 只有在编辑状态下可编辑
filter: 'agSetColumnFilter',
filterParams: {
applyMiniFilterWhileTyping: true,
suppressSorting: true
},
sortable: true,
resizable: true,
width: 150,
cellStyle: (params) => {
return this.isEditing ? { backgroundColor: '#f0f9ff' } : {};
}
},
{
headerName: '规格',
field: 'specification',
editable: () => this.isEditing,
filter: 'agSetColumnFilter',
filterParams: {
applyMiniFilterWhileTyping: true,
suppressSorting: true
},
sortable: true,
resizable: true,
width: 120,
cellStyle: (params) => {
return this.isEditing ? { backgroundColor: '#f0f9ff' } : {};
}
},
{
headerName: '标准',
field: 'standard',
editable: () => this.isEditing,
cellEditor: CustomRichSelectEditor,
cellEditorParams: {
values: [''] // 初始值,实际值会在编辑器中动态获取
},
filter: 'agSetColumnFilter',
filterParams: {
applyMiniFilterWhileTyping: true,
suppressSorting: true
},
sortable: true,
resizable: true,
width: 120,
valueFormatter: (params) => {
// 显示key-value格式,如果已经是key-value格式则不重复显示
if (!params.value) return '';
const valueStr = params.value.toString();
// 检查是否已经是key-value格式(包含 | 分隔符)
if (valueStr.includes('|')) {
return valueStr;
}
// 如果是纯key,尝试获取对应的value进行显示
if (this.standardDictionary && this.standardDictionary[valueStr]) {
return `${valueStr}|${this.standardDictionary[valueStr]}`;
}
// 如果没有字典数据,直接显示key
return valueStr;
},
cellStyle: (params) => {
return this.isEditing ? { backgroundColor: '#f0f9ff' } : {};
}
},
{
headerName: '电压等级',
field: 'voltage',
editable: () => this.isEditing,
cellEditor: CustomRichSelectEditor,
cellEditorParams: {
values: [''] // 初始值,实际值会在编辑器中动态获取
},
filter: 'agSetColumnFilter',
filterParams: {
applyMiniFilterWhileTyping: true,
suppressSorting: true
},
sortable: true,
resizable: true,
width: 100,
cellStyle: (params) => {
return this.isEditing ? { backgroundColor: '#f0f9ff' } : {};
}
},
{
headerName: '计量单位',
field: 'unit',
editable: () => this.isEditing,
filter: 'agSetColumnFilter',
filterParams: {
applyMiniFilterWhileTyping: true,
suppressSorting: true
},
sortable: true,
resizable: true,
width: 100,
cellStyle: (params) => {
return this.isEditing ? { backgroundColor: '#f0f9ff' } : {};
}
},
{
headerName: '数量',
field: 'quantity',
editable: () => this.isEditing,
filter: 'agNumberColumnFilter',
sortable: true,
resizable: true,
width: 100,
type: 'numericColumn',
cellStyle: (params) => {
return this.isEditing ? { backgroundColor: '#f0f9ff' } : {};
}
}
],
defaultColDef: {
flex: 1,
minWidth: 80,
enableCellChangeFlash: true,
cellClass: 'cell-editable'
}
}
},
mounted() {
this.initGrid();
// 延迟设置粘贴监听器,确保DOM已完全加载
this.$nextTick(() => {
this.setupPasteListener();
});
},
methods: {
// 初始化ag-Grid
initGrid() {
const gridOptions = {
columnDefs: this.columnDefs,
defaultColDef: this.defaultColDef,
rowData: this.rowData,
// 增强Excel式编辑功能
enableRangeSelection: true,
enableFillHandle: true,
rowSelection: 'multiple',
enableCellTextSelection: false, // 禁用单元格文本选择,避免多行选中时文本被选中
suppressCellTextSelection: true, // 进一步抑制文本选择
enableRangeHandle: true,
enableRangeEdit: true,
undoRedoCellEditing: true,
undoRedoCellEditingLimit: 20,
enableCellEditingOnBackspace: true,
// 移除分页,使用无限滚动
rowModelType: 'clientSide',
ensureDomOrder: true,
animateRows: true,
onGridReady: (params) => {
this.gridApi = params.api;
this.gridColumnApi = params.columnApi;
// 设置初始数据
this.addSampleData();
// 配置Excel式编辑
this.configureExcelEditing();
},
onCellValueChanged: (event) => {
// 更新过滤器的值
this.updateFilterValues();
},
onCellKeyDown: (event) => {
// 支持Ctrl+V粘贴
if (event.event.ctrlKey && event.event.key === 'v') {
event.event.preventDefault();
this.handlePaste();
}
// 支持Excel式编辑快捷键
this.handleExcelShortcuts(event);
},
onRangeSelectionChanged: (event) => {
// 选中范围变化时处理
this.handleRangeSelection(event);
}
};
const gridDiv = document.querySelector('#myGrid');
new agGrid.Grid(gridDiv, gridOptions);
},
// 配置Excel式编辑
configureExcelEditing() {
if (this.gridApi) {
// 启用范围编辑
this.gridApi.setSuppressClipboardPaste(false);
// 设置Enter键行为:向下移动(使用兼容性方法)
try {
if (typeof this.gridApi.setEnterMovesDownAfterEdit === 'function') {
this.gridApi.setEnterMovesDownAfterEdit(true);
}
if (typeof this.gridApi.setEnterMovesDown === 'function') {
this.gridApi.setEnterMovesDown(true);
}
} catch (error) {
console.warn('Excel编辑配置警告:', error);
}
}
},
// 处理Excel快捷键
handleExcelShortcuts(event) {
const key = event.event.key;
const ctrlKey = event.event.ctrlKey;
if (ctrlKey) {
switch (key) {
case 'd': // Ctrl+D: 向下填充
event.event.preventDefault();
this.fillDown();
break;
case 'r': // Ctrl+R: 向右填充
event.event.preventDefault();
this.fillRight();
break;
case 'z': // Ctrl+Z: 撤销
event.event.preventDefault();
this.gridApi.undoCellEditing();
break;
case 'y': // Ctrl+Y: 重做
event.event.preventDefault();
this.gridApi.redoCellEditing();
break;
}
}
},
// 处理范围选择
handleRangeSelection(event) {
const ranges = this.gridApi.getCellRanges();
if (ranges && ranges.length > 0) {
}
},
// 向下填充
fillDown() {
const ranges = this.gridApi.getCellRanges();
if (!ranges || ranges.length === 0) return;
const range = ranges[0];
const startRow = range.startRow.rowIndex;
const endRow = range.endRow.rowIndex;
const column = range.columns[0];
const field = column.getColDef().field;
if (startRow === endRow) return; // 单行不需要填充
// 防止对序号列进行填充(通过列ID检查)
if (column.colId === 'serialNumber' || column.getColDef().headerName === '序号') {
this.showSimpleMessage('序号列不支持填充操作');
return;
}
const sourceValue = this.rowData[startRow][field];
// 填充选中范围内的所有行
for (let i = startRow + 1; i <= endRow; i++) {
this.rowData[i][field] = sourceValue;
}
this.updateGrid({ columns: [field] });
this.showSimpleMessage('向下填充完成');
},
// 向右填充
fillRight() {
const ranges = this.gridApi.getCellRanges();
if (!ranges || ranges.length === 0) return;
const range = ranges[0];
const startCol = range.columns[0];
const endCol = range.columns[range.columns.length - 1];
const rowIndex = range.startRow.rowIndex;
if (startCol === endCol) return; // 单列不需要填充
const sourceValue = this.rowData[rowIndex][startCol.getColDef().field];
// 找到开始列和结束列的索引
const colDefs = this.gridColumnApi.getAllGridColumns();
const startColIndex = colDefs.indexOf(startCol);
const endColIndex = colDefs.indexOf(endCol);
// 填充选中范围内的所有列
for (let i = startColIndex + 1; i <= endColIndex; i++) {
const field = colDefs[i].getColDef().field;
// 跳过序号列
if (colDefs[i].getColDef().headerName === '序号') {
continue;
}
this.rowData[rowIndex][field] = sourceValue;
}
const updatedCols = colDefs.slice(startColIndex + 1, endColIndex + 1)
.filter(c => c.getColDef().headerName !== '序号')
.map(c => c.getColDef().field);
this.updateGrid({ columns: updatedCols });
this.showSimpleMessage('向右填充完成');
},
// 更新过滤器的值
updateFilterValues() {
// ag-Grid的Set过滤器会自动更新,这里只需要刷新过滤器
if (this.gridApi) {
this.gridApi.refreshHeader();
}
},
// 添加示例数据
addSampleData() {
const sampleData = [];
this.rowData = sampleData;
if (this.gridApi) {
this.gridApi.setRowData(this.rowData);
}
},
// 粘贴数据处理
handlePaste() {
if (this.isSecureContext()) {
// HTTPS环境,直接读取剪贴板
this.readClipboard();
} else {
// HTTP环境,直接提示用户使用Ctrl+V
this.showSimpleMessage('请使用 Ctrl+V 粘贴数据到表格中');
}
},
// 检查是否安全上下文
isSecureContext() {
return window.isSecureContext || window.location.protocol === 'https:';
},
// 读取剪贴板数据
async readClipboard() {
try {
const text = await navigator.clipboard.readText();
this.processPastedData(text);
} catch (error) {
this.showSimpleMessage('请使用 Ctrl+V 粘贴数据到表格中');
}
},
// 处理粘贴的数据
processPastedData(text) {
try {
const lines = text.split(/\r?\n/).filter(line => line.trim());
const newData = [];
lines.forEach((line, index) => {
if (index === 0 && (line.includes('序号') || line.includes('型号') || line.includes('报价产品') || line.includes('规格') || line.includes('标准'))) {
return;
}
const cells = line.split('\t');
const hasSerialColumn = cells.length >= 8 && /^\d+$/.test(cells[0].trim());
let quoteProductIdx, modelIdx, specIdx, standardIdx, voltageIdx, unitIdx, quantityIdx;
if (hasSerialColumn) {
quoteProductIdx = 1;
modelIdx = 2;
specIdx = 3;
standardIdx = 4;
voltageIdx = 5;
unitIdx = 6;
quantityIdx = 7;
} else {
quoteProductIdx = 0;
modelIdx = 1;
specIdx = 2;
standardIdx = 3;
voltageIdx = 4;
unitIdx = 5;
quantityIdx = 6;
}
const requiredCells = hasSerialColumn ? 8 : 7;
if (cells.length < requiredCells) {
const spaceCells = line.split(/\s+/).filter(cell => cell.trim());
if (spaceCells.length >= requiredCells) {
cells.length = 0;
cells.push(...spaceCells);
}
}
if (cells.length >= requiredCells) {
const row = {
quoteProduct: (cells[quoteProductIdx] || '').trim(),
model: (cells[modelIdx] || '').trim(),
specification: (cells[specIdx] || '').trim(),
standard: (cells[standardIdx] || '').trim(),
voltage: (cells[voltageIdx] || '').trim(),
unit: (cells[unitIdx] || '').trim(),
quantity: this.parseQuantity(cells[quantityIdx])
};
newData.push(row);
} else {
}
});
if (newData.length > 0) {
this.rowData = [...this.rowData, ...newData];
if (this.gridApi) {
this.gridApi.setRowData(this.rowData);
}
this.showSimpleMessage(`成功添加 ${newData.length} 条数据`);
} else {
this.showSimpleMessage('未检测到有效数据,请检查数据格式');
}
} catch (error) {
console.error('处理粘贴数据失败:', error);
this.showSimpleMessage('数据处理失败,请检查数据格式');
}
},
// 解析数量
parseQuantity(value) {
if (!value) return 0;
// 移除千分位分隔符
const cleanValue = value.toString().replace(/[,,]/g, '');
const num = parseFloat(cleanValue);
return isNaN(num) ? 0 : num;
},
// 清空数据
handleClear() {
// 使用自定义的确认框,避免使用el-message的图标
const messageBox = document.createElement('div');
messageBox.className = 'custom-message-box';
messageBox.innerHTML = `
<div class="message-content">
<div class="message-text">确定要清空所有数据吗?</div>
<div class="message-buttons">
<button class="el-button el-button--primary el-button--small confirm-btn">确定</button>
<button class="el-button el-button--default el-button--small cancel-btn">取消</button>
</div>
</div>
`;
// 创建遮罩层
const overlay = document.createElement('div');
overlay.className = 'custom-message-overlay';
// 添加到页面
document.body.appendChild(overlay);
document.body.appendChild(messageBox);
// 绑定事件
messageBox.querySelector('.confirm-btn').addEventListener('click', () => {
this.rowData = [];
if (this.gridApi) {
this.gridApi.setRowData([]);
}
this.removeCustomMessage();
this.showSimpleMessage('数据已清空');
});
messageBox.querySelector('.cancel-btn').addEventListener('click', () => {
this.removeCustomMessage();
});
// 点击遮罩层关闭
overlay.addEventListener('click', () => {
this.removeCustomMessage();
});
},
// 移除自定义消息框
removeCustomMessage() {
const messageBox = document.querySelector('.custom-message-box');
const overlay = document.querySelector('.custom-message-overlay');
if (messageBox) {
messageBox.remove();
}
if (overlay) {
overlay.remove();
}
},
// 显示简单的文本提示
showSimpleMessage(text) {
// 创建简单的提示元素
const messageEl = document.createElement('div');
messageEl.className = 'simple-message';
messageEl.innerHTML = `
<div class="simple-message-content">${text}</div>
`;
// 添加到页面
document.body.appendChild(messageEl);
// 1秒后自动移除
setTimeout(() => {
if (messageEl.parentNode) {
messageEl.remove();
}
}, 1000);
},
// 统一刷新表格方法 - 优化性能
// @param {Object} options - 刷新选项
// @param {boolean} options.fullRefresh - 是否完全刷新(默认false)
// @param {Array} options.rows - 指定刷新的行节点数组
// @param {Array} options.columns - 指定刷新的列定义数组
updateGrid(options = {}) {
const { fullRefresh = false, rows, columns } = options;
if (!this.gridApi) return;
if (fullRefresh) {
this.gridApi.setRowData(this.rowData);
} else if (rows && rows.length > 0) {
this.gridApi.refreshCells({ rowNodes: rows });
} else if (columns && columns.length > 0) {
this.gridApi.refreshCells({ columns: columns });
} else {
this.gridApi.refreshCells({ force: true });
}
},
// 统一确认框方法 - 减少重复代码
showConfirm(message, onConfirm, onCancel) {
const messageBox = document.createElement('div');
messageBox.className = 'custom-message-box';
messageBox.innerHTML = `
<div class="message-content">
<div class="message-text">${message}</div>
<div class="message-buttons">
<button class="el-button el-button--primary el-button--small confirm-btn">确定</button>
<button class="el-button el-button--default el-button--small cancel-btn">取消</button>
</div>
</div>
`;
const overlay = document.createElement('div');
overlay.className = 'custom-message-overlay';
document.body.appendChild(overlay);
document.body.appendChild(messageBox);
messageBox.querySelector('.confirm-btn').addEventListener('click', () => {
this.removeCustomMessage();
if (onConfirm && typeof onConfirm === 'function') {
onConfirm();
}
});
const cancelHandler = () => {
this.removeCustomMessage();
if (onCancel && typeof onCancel === 'function') {
onCancel();
}
};
messageBox.querySelector('.cancel-btn').addEventListener('click', cancelHandler);
overlay.addEventListener('click', cancelHandler);
},
// 显示加载框
showLoading(message = '正在处理,请稍候...') {
// 如果已有加载框,先移除
this.hideLoading();
// 创建加载遮罩层
const loadingOverlay = document.createElement('div');
loadingOverlay.className = 'loading-overlay';
loadingOverlay.id = 'loadingOverlay';
loadingOverlay.innerHTML = `
<div class="loading-container">
<div class="loading-spinner"></div>
<div class="loading-text">${message}</div>
</div>
`;
// 添加到页面
document.body.appendChild(loadingOverlay);
},
// 隐藏加载框
hideLoading() {
const loadingOverlay = document.getElementById('loadingOverlay');
if (loadingOverlay) {
loadingOverlay.remove();
}
},
// 导出Excel
handleExport() {
if (this.rowData.length === 0) {
this.showSimpleMessage('没有数据可导出');
return;
}
try {
const wb = XLSX.utils.book_new();
const exportData = [
['序号', '报价产品', '型号', '规格', '标准', '电压等级', '计量单位', '数量'],
...this.rowData.map((row, index) => [
index + 1,
row.quoteProduct || '',
row.model,
row.specification,
row.standard,
row.voltage,
row.unit,
row.quantity
])
];
const ws = XLSX.utils.aoa_to_sheet(exportData);
XLSX.utils.book_append_sheet(wb, ws, '投标数据');
XLSX.writeFile(wb, `投标数据_${new Date().toLocaleDateString().replace(/\//g, '-')}.xlsx`);
this.showSimpleMessage('导出成功');
} catch (error) {
console.error('导出失败:', error);
this.showSimpleMessage('导出失败');
}
},
// 设置粘贴监听器
setupPasteListener() {
document.addEventListener('paste', (event) => {
// 如果焦点在ag-Grid上,让ag-Grid处理
const activeElement = document.activeElement;
if (activeElement && activeElement.classList.contains('ag-cell')) {
return;
}
// 否则处理全局粘贴
event.preventDefault();
const text = event.clipboardData.getData('text/plain');
if (text && text.trim()) {
this.processPastedData(text);
}
});
},
// 批量编辑选中单元格
batchEditSelectedCells(value) {
const ranges = this.gridApi.getCellRanges();
if (!ranges || ranges.length === 0) {
this.showSimpleMessage('请先选择要编辑的单元格');
return;
}
ranges.forEach(range => {
const startRow = Math.min(range.startRow.rowIndex, range.endRow.rowIndex);
const endRow = Math.max(range.startRow.rowIndex, range.endRow.rowIndex);
const startCol = Math.min(range.columns.indexOf(range.startColumn), range.columns.indexOf(range.endColumn));
const endCol = Math.max(range.columns.indexOf(range.startColumn), range.columns.indexOf(range.endColumn));
for (let rowIndex = startRow; rowIndex <= endRow; rowIndex++) {
for (let colIndex = startCol; colIndex <= endCol; colIndex++) {
const column = range.columns[colIndex];
const field = column.getColDef().field;
// 跳过序号列
if (column.getColDef().headerName === '序号') {
continue;
}
this.rowData[rowIndex][field] = value;
}
}
});
this.updateGrid();
this.showSimpleMessage('批量编辑完成');
},
// 关闭页面
handleClose() {
this.showConfirm('确定要关闭页面吗?', () => {
this.closePage();
});
},
// 批量调整标准
async handleBatchAdjustStandard() {
// 检查是否在编辑状态
if (this.isEditing) {
this.showSimpleMessage('请在非编辑状态下使用批量调整功能');
return;
}
// 检查是否有选中行
const selectedNodes = this.gridApi.getSelectedNodes();
if (!selectedNodes || selectedNodes.length === 0) {
this.showSimpleMessage('请先选择要调整的行');
return;
}
try {
// 获取标准列表
const standardDict = await this.GetXLBZ();
if (!standardDict || Object.keys(standardDict).length === 0) {
this.showSimpleMessage('获取标准列表失败,请稍后重试');
return;
}
// 创建标准选择对话框
this.showBatchAdjustDialog('标准', standardDict, (selectedValue) => {
// 更新选中行的标准字段
selectedNodes.forEach(node => {
if (node.data) {
node.data.standard = selectedValue;
}
});
this.updateGrid({ rows: selectedNodes });
this.showSimpleMessage(`已为 ${selectedNodes.length} 行更新标准为: ${selectedValue}`);
});
} catch (error) {
console.error('批量调整标准失败:', error);
this.showSimpleMessage('批量调整标准失败,请稍后重试');
}
},
// 批量调整电压等级
async handleBatchAdjustVoltage() {
// 检查是否在编辑状态
if (this.isEditing) {
this.showSimpleMessage('请在非编辑状态下使用批量调整功能');
return;
}
// 检查是否有选中行
const selectedNodes = this.gridApi.getSelectedNodes();
if (!selectedNodes || selectedNodes.length === 0) {
this.showSimpleMessage('请先选择要调整的行');
return;
}
try {
// 获取电压等级列表
const voltageLevels = await this.GetDYDJ();
if (!voltageLevels || voltageLevels.length === 0) {
this.showSimpleMessage('获取电压等级列表失败,请稍后重试');
return;
}
// 创建电压等级选择对话框
this.showBatchAdjustDialog('电压等级', voltageLevels, (selectedValue) => {
// 更新选中行的电压等级字段
selectedNodes.forEach(node => {
if (node.data) {
node.data.voltage = selectedValue;
}
});
this.updateGrid({ rows: selectedNodes });
this.showSimpleMessage(`已为 ${selectedNodes.length} 行更新电压等级为: ${selectedValue}`);
});
} catch (error) {
console.error('批量调整电压等级失败:', error);
this.showSimpleMessage('批量调整电压等级失败,请稍后重试');
}
},
// 显示批量调整对话框
showBatchAdjustDialog(title, dataList, onConfirm) {
// 创建对话框容器
const messageBox = document.createElement('div');
messageBox.className = 'custom-message-box';
// 构建选项HTML
let optionsHtml = '<option value="">请选择...</option>';
if (typeof dataList === 'object' && !Array.isArray(dataList)) {
// 字典格式数据(标准)
for (const [key, value] of Object.entries(dataList)) {
optionsHtml += `<option value="${key}">${key}|${value}</option>`;
}
} else {
// 数组格式数据(电压等级)
dataList.forEach(item => {
optionsHtml += `<option value="${item}">${item}</option>`;
});
}
messageBox.innerHTML = `
<div class="message-content">
<div class="message-title" style="font-size: 16px; font-weight: bold; margin-bottom: 15px;">
批量调整${title}
</div>
<div class="message-text">
<div style="margin-bottom: 15px;">
<div style="color: #606266; margin-bottom: 8px;">
已选择 <span style="color: #409eff; font-weight: bold;">${this.gridApi.getSelectedNodes().length}</span> 行数据
</div>
<label style="display: block; margin-bottom: 8px; font-weight: bold;">选择${title}:</label>
<select id="batchAdjustSelect" class="el-select__inner" style="width: 100%; padding: 8px; border: 1px solid #dcdfe6; border-radius: 4px;">
${optionsHtml}
</select>
</div>
</div>
<div class="message-buttons">
<button class="el-button el-button--primary el-button--small confirm-btn">确认</button>
<button class="el-button el-button--default el-button--small cancel-btn">取消</button>
</div>
</div>
`;
// 创建遮罩层
const overlay = document.createElement('div');
overlay.className = 'custom-message-overlay';
// 添加到页面
document.body.appendChild(overlay);
document.body.appendChild(messageBox);
// 确认按钮点击事件
messageBox.querySelector('.confirm-btn').addEventListener('click', () => {
const selectedValue = document.getElementById('batchAdjustSelect').value;
if (!selectedValue) {
this.showSimpleMessage(`请选择${title}`);
return;
}
// 移除对话框
this.removeCustomMessage();
// 执行确认回调
if (onConfirm && typeof onConfirm === 'function') {
onConfirm(selectedValue);
}
});
// 取消按钮点击事件
messageBox.querySelector('.cancel-btn').addEventListener('click', () => {
this.removeCustomMessage();
});
// 遮罩层点击事件
overlay.addEventListener('click', () => {
this.removeCustomMessage();
});
},
// 关闭页面逻辑
closePage() {
let topDocument;
try {
topDocument = top.document;
} catch (e) {
topDocument = document; // 跨域时回退到当前文档
}
try {
// 获取元素引用
const divZDY = topDocument.getElementById("div_ZDY");
const divZDY_100860140 = topDocument.getElementById("divZDY_100860140");
// 确保主容器存在
if (!divZDY) return;
// 清除divZDY_102849000(如果存在)
if (divZDY_100860140) {
divZDY.removeChild(divZDY_100860140);
}
while (divZDY.firstChild) {
divZDY.removeChild(divZDY.firstChild);
}
// 隐藏主容器
divZDY.style.display = "none";
} catch (error) {
console.error("隐藏自定义元素时出错:", error);
}
// 刷新报价单
const frameElement = topDocument.getElementById('XLBJDInfo_frame');
if (!frameElement) {
} else {
const iframeDoc = frameElement.contentWindow.document;
if (!iframeDoc) {
} else {
const reloadButton = iframeDoc.getElementById('ctl00_CPHButton_btnReLoad');
if (reloadButton) {
reloadButton.click();
} else {
}
}
}
},
// 开始编辑
async startEdit() {
try {
// 先获取标准字典数据,确保下拉选择器有数据
if (!this.standardDictionary) {
await this.GetXLBZ();
}
// 获取电压等级数据,确保下拉选择器有数据
if (!this.voltageLevels) {
await this.GetDYDJ();
}
// 备份原始数据
this.originalData = JSON.parse(JSON.stringify(this.rowData));
this.isEditing = true;
// 更新标准列的下拉选项
this.updateStandardColumnOptions();
// 更新电压等级列的下拉选项
this.updateVoltageColumnOptions();
// 刷新表格以应用编辑状态
if (this.gridApi) {
this.gridApi.refreshHeader();
this.gridApi.setRowData(this.rowData);
}
this.showSimpleMessage('已进入编辑模式,可以修改表格内容');
} catch (error) {
console.error('进入编辑模式失败:', error);
this.showSimpleMessage('获取数据失败,无法进入编辑模式');
}
},
// 更新标准列的下拉选项
updateStandardColumnOptions() {
if (!this.standardDictionary) {
console.warn('标准字典数据为空,无法更新下拉选项');
return;
}
// 构建选项数组 - 同时显示key和value
const options = [''];
for (const [key, value] of Object.entries(this.standardDictionary)) {
options.push(`${key}|${value}`); // 同时显示key和value,格式为 "key|value"
}
// 更新标准列的下拉选项
const standardColumn = this.gridColumnApi.getColumn('standard');
if (standardColumn) {
standardColumn.colDef.cellEditorParams = {
values: options
};
// 刷新列定义
this.gridApi.refreshHeader();
}
},
// 更新电压等级列的下拉选项
updateVoltageColumnOptions() {
if (!this.voltageLevels) {
console.warn('电压等级数据为空,无法更新下拉选项');
return;
}
// 构建选项数组(电压等级是List<string>,直接使用)
const options = ['', ...this.voltageLevels];
// 更新电压等级列的下拉选项
const voltageColumn = this.gridColumnApi.getColumn('voltage');
if (voltageColumn) {
voltageColumn.colDef.cellEditorParams = {
values: options
};
// 刷新列定义
this.gridApi.refreshHeader();
}
},
// 取消编辑
cancelEdit() {
this.showConfirm('确定要取消编辑吗?所有未保存的修改将丢失', () => {
this.rowData = JSON.parse(JSON.stringify(this.originalData));
this.isEditing = false;
if (this.gridApi) {
this.gridApi.setRowData(this.rowData);
}
this.showSimpleMessage('已取消编辑,数据已恢复');
});
},
// 保存数据
saveData() {
this.showConfirm('确定要保存数据吗?', () => {
this.isEditing = false;
this.originalData = JSON.parse(JSON.stringify(this.rowData));
if (this.gridApi) {
this.gridApi.refreshHeader();
}
this.showSimpleMessage('数据保存成功');
});
},
// InsertBJDMX方法 - 调用服务处理报价单明细导入
async InsertBJDMX(items) {
try {
// 构建服务参数
const serviceParams = {
'-RefUmId': 102855005,
'items': items.map(item => item.toJSON()), // 转换为JSON格式
'sBJDID': BJDID || '' // 获取报价单ID
};
// 调用InsertBJDMX服务
const result = await this.callService('InsertBJDMX', serviceParams);
return result;
} catch (error) {
console.error('InsertBJDMX服务调用失败:', error);
throw error;
}
},
// 导入报价单明细
async handleImportBJD() {
// 检查是否有数据
if (this.rowData.length === 0) {
this.showSimpleMessage('没有数据可导入');
return;
}
try {
// 显示加载框
this.showLoading('正在导入报价单明细,请稍候...');
// 构建Product列表
const items = this.buildProductList();
// 调用InsertBJDMX服务
const result = await this.InsertBJDMX(items);
// 隐藏加载框
this.hideLoading();
if (result && result.success) {
// 导入成功,提示并关闭页面
this.showSimpleMessage('导入成功');
// 延迟500毫秒后关闭页面,让用户看到提示信息
setTimeout(() => {
this.closePage();
}, 500);
} else {
// 导入失败,只显示错误信息
const errorMsg = result ? result.msg : '未知错误';
this.showSimpleMessage(`导入失败: ${errorMsg}`);
}
} catch (error) {
// 隐藏加载框
this.hideLoading();
console.error('导入报价单明细失败:', error);
this.showSimpleMessage(`导入失败: ${error.message || '服务异常'}`);
}
},
async handleParseQuoteProduct() {
if (this.rowData.length === 0) {
this.showSimpleMessage('没有数据可解析');
return;
}
const hasQuoteProduct = this.rowData.some(row => row.quoteProduct && row.quoteProduct.trim());
if (!hasQuoteProduct) {
this.showSimpleMessage('没有报价产品数据可解析');
return;
}
try {
this.showLoading('正在解析报价产品,请稍候...');
const items = this.buildProductList();
const result = await this.JXBJCP(items);
this.hideLoading();
if (result && Array.isArray(result)) {
this.updateTableWithResult(result);
this.showSimpleMessage('解析报价产品成功');
} else {
console.warn('服务返回结果不是预期的List<Product>格式');
this.showSimpleMessage('解析完成,但返回结果格式有误');
}
} catch (error) {
this.hideLoading();
console.error('解析报价产品失败:', error);
this.showSimpleMessage(`解析失败: ${error.message || '服务异常'}`);
}
},
async JXBJCP(items) {
try {
const serviceParams = {
'-RefUmId': 102855005,
'items': items.map(item => item.toJSON()),
'sBJDID': BJDID || ''
};
const result = await this.callService('JXBJCP', serviceParams);
return result;
} catch (error) {
console.error('JXBJCP服务调用失败:', error);
throw error;
}
},
// 匹配标准电压
async matchStandardVoltage() {
// 检查是否有数据
if (this.rowData.length === 0) {
this.showSimpleMessage('没有数据可匹配');
return;
}
try {
// 获取标准列表
const standardDict = await this.GetXLBZ();
if (!standardDict || Object.keys(standardDict).length === 0) {
this.showSimpleMessage('获取标准列表失败,请稍后重试');
return;
}
// 创建标准电压选择对话框
const messageBox = document.createElement('div');
messageBox.className = 'custom-message-box';
// 构建选项HTML,同时显示value和label
let optionsHtml = '<option value="">请选择...</option>';
for (const [key, value] of Object.entries(standardDict)) {
optionsHtml += `<option value="${key}">${key}|${value}</option>`;
}
messageBox.innerHTML = `
<div class="message-content">
<div class="message-text">
<div style="margin-bottom: 15px;">
<label style="display: block; margin-bottom: 8px; font-weight: bold;">选择标准:</label>
<select id="standardVoltageSelect" class="el-select__inner" style="width: 100%; padding: 8px; border: 1px solid #dcdfe6; border-radius: 4px;">
${optionsHtml}
</select>
</div>
</div>
<div class="message-buttons">
<button class="el-button el-button--primary el-button--small confirm-btn">确认</button>
<button class="el-button el-button--default el-button--small cancel-btn">取消</button>
</div>
</div>
`;
const overlay = document.createElement('div');
overlay.className = 'custom-message-overlay';
document.body.appendChild(overlay);
document.body.appendChild(messageBox);
// 确认按钮点击事件
messageBox.querySelector('.confirm-btn').addEventListener('click', async () => {
const selectedStandard = document.getElementById('standardVoltageSelect').value;
if (!selectedStandard) {
this.showSimpleMessage('请选择标准');
return;
}
this.removeCustomMessage();
try {
// 显示加载框
this.showLoading('正在匹配标准电压,请稍候...');
// 调用PPBZDYDJ服务
await this.PPBZDYDJ(selectedStandard);
// 隐藏加载框
this.hideLoading();
} catch (error) {
// 隐藏加载框
this.hideLoading();
console.error('匹配标准电压失败:', error);
this.showSimpleMessage('匹配标准电压失败');
}
});
// 取消按钮点击事件
messageBox.querySelector('.cancel-btn').addEventListener('click', () => {
this.removeCustomMessage();
});
// 遮罩层点击事件
overlay.addEventListener('click', () => {
this.removeCustomMessage();
});
} catch (error) {
console.error('获取标准列表失败:', error);
this.showSimpleMessage('获取标准列表失败,请稍后重试');
}
},
// PPBZDYDJ方法 - 调用服务处理标准电压匹配
async PPBZDYDJ(sMRXLBZID) {
try {
// 构建Product列表
const items = this.buildProductList();
// 构建服务参数
const serviceParams = {
'-RefUmId': 102855005,
'items': items.map(item => item.toJSON()), // 转换为JSON格式
'sBJDID': BJDID || '', // 使用全局BJDID
'sMRXLBZID': sMRXLBZID
};
// 调用PPBZDYDJ服务
const result = await this.callService('PPBZDYDJ', serviceParams);
if (result && Array.isArray(result)) {
// 调用成功,更新表格数据
this.updateTableWithResult(result);
this.showSimpleMessage('标准电压匹配成功');
} else {
// 返回结果不是数组,可能是错误或空结果
console.warn('服务返回结果不是预期的List<Product>格式');
this.showSimpleMessage('标准电压匹配完成,但返回结果格式有误');
}
} catch (error) {
console.error('PPBZDYDJ服务调用失败:', error);
this.showSimpleMessage('标准电压匹配失败,表格数据保持不变');
// 失败时表格数据保持不变,不抛出错误
throw error;
}
},
// 根据服务返回结果更新表格
updateTableWithResult(resultList) {
if (!resultList || !Array.isArray(resultList)) {
console.warn('无效的返回结果,无法更新表格');
return;
}
const newRowData = resultList.map(product => {
let productObj;
if (typeof product === 'object' && product !== null) {
productObj = product;
} else if (typeof product === 'string') {
try {
productObj = JSON.parse(product);
} catch (e) {
console.warn('无法解析Product字符串:', product);
productObj = {};
}
} else {
productObj = {};
}
return {
quoteProduct: productObj.QuoteProduct || productObj.quoteProduct || '',
model: productObj.Model || productObj.model || '',
specification: productObj.Specification || productObj.specification || '',
standard: productObj.Standard || productObj.standard || '',
voltage: productObj.Voltage || productObj.voltage || '',
unit: productObj.Unit || productObj.unit || '',
quantity: parseFloat(productObj.Quantity || productObj.quantity || 0)
};
});
this.rowData = newRowData;
if (this.gridApi) {
this.gridApi.setRowData(newRowData);
}
},
// 开始进度条
// 构建Product列表
buildProductList() {
const items = [];
this.rowData.forEach((rowData, index) => {
const product = new Product();
product.SerialNumber = index + 1;
product.QuoteProduct = rowData.quoteProduct || '';
product.Model = rowData.model || '';
product.Specification = rowData.specification || '';
product.Standard = rowData.standard || '';
product.Voltage = rowData.voltage || '';
product.Unit = rowData.unit || '';
product.Quantity = parseFloat(rowData.quantity) || 0;
items.push(product);
});
return items;
},
// GetXLBZ方法 - 获取标准列表
async GetXLBZ() {
try {
// 构建服务参数(GetXLBZ方法通常不需要额外参数)
const serviceParams = {
'-RefUmId': 102855005
};
// 调用GetXLBZ服务
const result = await this.callService('GetXLBZ', serviceParams);
// 处理返回结果,确保是Dictionary<string, string>格式
let standardDict;
if (result && typeof result === 'object') {
// 如果返回的是对象,直接使用
standardDict = result;
} else if (typeof result === 'string') {
// 如果是字符串,尝试解析JSON
try {
standardDict = JSON.parse(result);
} catch (parseError) {
console.warn('无法解析GetXLBZ返回的字符串:', result);
throw new Error('标准列表格式错误');
}
} else {
console.warn('GetXLBZ返回结果格式异常:', result);
throw new Error('获取标准列表失败');
}
// 保存标准字典数据供表格显示使用
this.standardDictionary = standardDict;
return standardDict;
} catch (error) {
console.error('GetXLBZ服务调用失败:', error);
throw error;
}
},
// GetDYDJ方法 - 获取电压等级列表
async GetDYDJ() {
try {
// 构建服务参数(GetDYDJ方法通常不需要额外参数)
const serviceParams = {
'-RefUmId': 102855005
};
// 调用GetDYDJ服务
const result = await this.callService('GetDYDJ', serviceParams);
// 处理返回结果,确保是List<string>格式
let voltageLevels;
if (result && Array.isArray(result)) {
// 如果返回的是数组,直接使用
voltageLevels = result;
} else if (typeof result === 'string') {
// 如果是字符串,尝试解析JSON
try {
voltageLevels = JSON.parse(result);
if (!Array.isArray(voltageLevels)) {
throw new Error('返回结果不是数组格式');
}
} catch (parseError) {
console.warn('无法解析GetDYDJ返回的字符串:', result);
throw new Error('电压等级列表格式错误');
}
} else {
console.warn('GetDYDJ返回结果格式异常:', result);
throw new Error('获取电压等级列表失败');
}
// 保存电压等级列表数据供表格显示使用
this.voltageLevels = voltageLevels;
return voltageLevels;
} catch (error) {
console.error('GetDYDJ服务调用失败:', error);
throw error;
}
},
// 通用服务调用方法
/**
* 通用服务调用方法
* @param {string} methodName - 服务方法名称
* @param {Object} params - 调用参数
* @param {Object} options - 选项配置
* @returns {Promise<Object>} 调用结果
*/
async callService(methodName, params = {}, options = {}) {
if (!methodName || typeof methodName !== 'string') {
console.error('callService: methodName 不能为空');
throw new Error('服务方法名称不能为空');
}
const methodTimeouts = {
'GetXLBZ': 30000,
'GetDYDJ': 30000,
'InsertBJDMX': 120000,
'PPBZDYDJ': 120000,
'JXBJCP': 120000
};
try {
GThinking.Web.AjaxServiceProxy.servicePath = 'S_XLXSBJ/102373013.ashx';
const defaultOptions = {
refUmId: 102855005,
timeout: methodTimeouts[methodName] || 60000,
isAsync: false
};
const mergedOptions = { ...defaultOptions, ...options };
const result = await new Promise((resolve, reject) => {
GThinking.Web.AjaxServiceProxy.invoke(
methodName,
mergedOptions.isAsync,
params,
(data) => resolve(data),
(error) => reject(error),
methodName,
mergedOptions.timeout
);
});
return result;
} catch (error) {
console.error(`服务调用失败 [${methodName}]:`, error);
throw error;
}
}
}
});
// 使用Element Plus
app.use(ElementPlus);
// 挂载应用
app.mount('#app');