1890 lines
67 KiB
JavaScript
1890 lines
67 KiB
JavaScript
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'); |