<think>我们需要在ReusableModalSystem中支持Select2的渲染。由于Select2需要初始化,我们可以在弹窗渲染后执行初始化代码。同时,为了确保Select2的下拉菜单在模态框中正确显示(避免被遮挡),我们需要设置dropdownParent属性。修改ReusableModalSystem.js:1.在render方法中,如果字段类型是select,则添加一个类名以便后续初始化Select2。2.在弹窗渲染完成后,初始化所有select元素为Select2,并设置dropdownParent为当前模态框。但是,由于我们的弹窗系统是动态渲染的,我们需要在每次弹窗渲染完成后执行Select2的初始化。我们可以在bindEvents之后执行这个操作。另外,我们还需要考虑弹窗关闭时销毁Select2实例以避免内存泄漏。具体步骤:1.在ReusableModalSystem的open方法中,在调用modalDef.render和modalDef.bindEvents之后,初始化Select2。2.在初始化Select2时,设置dropdownParent为当前模态框的内容区域(.modal-content)。3.在弹窗关闭时,销毁Select2实例。注意:由于我们的弹窗模板是固定的,每个弹窗都有一个唯一的ID,我们可以使用这个模态框的容器作为dropdownParent。修改ReusableModalSystem.js的open方法:</think>我将为您修改`ReusableModalSystem.js`,使其支持在弹窗中渲染Select2下拉框。以下是完整的解决方案:
### 修改后的 ReusableModalSystem.js
```javascript
// @ts-nocheck
export class ReusableModalSystem {
constructor() {
this.modalRegistry = {};
this.fieldRegistry = {};
this.activeModals = [];
this.initCoreComponents();
}
// 初始化核心组件(无数据依赖)
initCoreComponents() {
// 注册基础字段类型
this.registerFieldType('text', {
render: (config) => {
return `<div class="mb-3">
<label class="form-label">${config.label}${config.required ? '*' : ''}</label>
<input type="text" class="form-control" name="${config.name}"
value="${config.value || ''}" ${config.required ? 'required' : ''}>
</div>`;
}
});
this.registerFieldType('select', {
render: (config) => {
let options = '<option value="">请选择</option>';
(config.options || []).forEach(opt => {
const selected = config.value == opt.value ? 'selected' : '';
options += `<option value="${opt.value}" ${selected}>${opt.label}</option>`;
});
return `<div class="mb-3">
<label class="form-label">${config.label}${config.required ? '*' : ''}</label>
<select class="form-select select2-field" name="${config.name}"
${config.required ? 'required' : ''}
${config.select2 ? 'data-select2="true"' : ''}
${config.select2Config ? `data-select2-config='${JSON.stringify(config.select2Config)}'` : ''}>
${options}
</select>
</div>`;
},
afterRender: (element, config) => {
if (config.select2) {
const $select = $(element).find('select');
const configData = config.select2Config || {};
// 默认配置
const defaultConfig = {
theme: 'bootstrap',
placeholder: config.placeholder || '请选择',
allowClear: true,
dropdownParent: $select.closest('.modal')
};
// 合并配置
const finalConfig = {...defaultConfig, ...configData};
// 初始化Select2
$select.select2(finalConfig);
// 添加创建按钮(如果配置了)
if (config.addButton) {
const $container = $select.closest('.mb-3');
const $button = $(`
<button type="button" class="btn btn-sm btn-outline-secondary mt-2 add-entity-btn"
data-entity-type="${config.addButton.entityType}">
<i class="fa fa-plus"></i> ${config.addButton.label}
</button>
`);
$container.append($button);
}
}
}
});
// 注册基础弹窗模板
this.registerModalType('baseForm', {
template: `<div class="modal fade" id="baseFormModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="baseFormTitle"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="baseFormBody"></div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="baseFormSubmit">保存</button>
</div>
</div>
</div>
</div>`,
render: (config, container) => {
container.find('#baseFormTitle').text(config.title);
let formHTML = '';
config.fields.forEach(fieldConfig => {
const fieldType = this.fieldRegistry[fieldConfig.type];
if (fieldType) {
formHTML += fieldType.render(fieldConfig);
}
});
container.find('#baseFormBody').html(formHTML);
},
bindEvents: (config, container, onSubmit) => {
container.find('#baseFormSubmit').off('click').on('click', () => {
const formData = this.getFormData(container);
onSubmit(formData);
});
// 绑定创建实体按钮事件
container.find('.add-entity-btn').on('click', function() {
const entityType = $(this).data('entity-type');
const title = `创建新${getEntityLabel(entityType)}`;
// 打开创建实体模态框
window.parent.modalSystem.open('baseForm', {
title: title,
fields: getEntityFields(entityType)
}, {
onSubmit: async (formData) => {
try {
// 保存新实体
const result = await window.parent.dataManager.addEntity(entityType, formData);
if (result.status === 200) {
// 刷新父弹窗中的下拉框
const $parentSelect = container.find(`select[name="${$(this).data('target-field')}"]`);
if ($parentSelect.length) {
const newOption = new Option(
result.data.name || result.data.bianhao || formatBancaiInfo(result.data),
result.data.id,
true,
true
);
$parentSelect.append(newOption).trigger('change');
}
return true; // 关闭弹窗
} else {
alert(`创建失败: ${result.text}`);
return false;
}
} catch (error) {
console.error('创建实体时出错:', error);
alert(`创建失败: ${error.message}`);
return false;
}
}
});
});
},
afterRender: (config, container) => {
// 执行字段的afterRender回调
config.fields.forEach(fieldConfig => {
const fieldType = this.fieldRegistry[fieldConfig.type];
if (fieldType && fieldType.afterRender) {
const fieldElement = container.find(`[name="${fieldConfig.name}"]`).closest('.mb-3');
if (fieldElement.length) {
fieldType.afterRender(fieldElement[0], fieldConfig);
}
}
});
}
});
}
// 注册字段类型
registerFieldType(type, definition) {
this.fieldRegistry[type] = definition;
}
// 注册弹窗类型
registerModalType(type, definition) {
this.modalRegistry[type] = definition;
if (!$(`#${type}Modal`).length) {
$('body').append(definition.template);
}
}
// 注册实体表单样式(仅样式,无数据)
registerEntityForm(entityType, fieldsConfig) {
const modalType = `${entityType}Form`;
this.registerModalType(modalType, {
extends: 'baseForm',
render: (config, container) => {
// 使用传入的字段配置
this.modalRegistry.baseForm.render({
...config,
title: config.title || `${config.entity?.id ? '编辑' : '添加'}${this.getEntityLabel(entityType)}`,
fields: config.fields || fieldsConfig
}, container);
}
});
return modalType;
}
// 打开弹窗(数据由外部提供)
open(type, config, callbacks = {}) {
const modalDef = this.modalRegistry[type];
if (!modalDef) throw new Error(`未注册的弹窗类型: ${type}`);
const container = $(`#${type}Modal`);
modalDef.render(config, container);
if (modalDef.bindEvents) {
modalDef.bindEvents(config, container, callbacks.onSubmit || callbacks.onConfirm);
}
if (modalDef.afterRender) {
modalDef.afterRender(config, container);
}
const modal = new bootstrap.Modal(container[0]);
modal.show();
// 存储弹窗实例
this.activeModals.push({
container,
modal
});
// 绑定关闭事件清理Select2
container.on('hidden.bs.modal', () => {
// 销毁所有Select2实例
container.find('.select2-field').each(function() {
if ($(this).data('select2')) {
$(this).select2('destroy');
}
});
// 从活动弹窗中移除
this.activeModals = this.activeModals.filter(m => m.container[0] !== container[0]);
});
return {
close: () => modal.hide(),
update: (newConfig) => {
modalDef.render(newConfig, container);
if (modalDef.afterRender) {
modalDef.afterRender(newConfig, container);
}
}
};
}
// 辅助方法
getEntityLabel(entityType) {
const labels = {
'bancai': '板材', 'caizhi': '材质', 'mupi': '木皮',
'user': '用户', 'dingdan': '订单', 'chanpin': '产品'
};
return labels[entityType] || entityType;
}
getFormData(container) {
const formData = {};
container.find(':input').each(function() {
const $input = $(this);
const name = $input.attr('name');
const type = $input.attr('type');
if (name) {
if (type === 'checkbox') {
formData[name] = $input.prop('checked');
} else if ($input.hasClass('select2-field')) {
// 处理Select2字段
formData[name] = $input.val();
} else {
formData[name] = $input.val();
}
}
});
return formData;
}
}
// 辅助函数 - 获取实体字段配置
function getEntityFields(entityType) {
switch (entityType) {
case 'chanpin':
return [
{ type: 'text', name: 'bianhao', label: '产品编号', required: true }
];
case 'zujian':
return [
{ type: 'text', name: 'name', label: '组件名称', required: true }
];
case 'bancai':
return [
{
type: 'number',
name: 'houdu',
label: '厚度(mm)',
required: true,
step: 0.1,
min: 0.1
},
{
type: 'select',
name: 'caizhi_id',
label: '材质',
required: true,
select2: true,
options: window.parent.dataManager.data.caizhis.map(cz => ({
value: cz.id,
label: cz.name
}))
},
{
type: 'select',
name: 'mupi1_id',
label: '木皮1',
select2: true,
options: window.parent.dataManager.data.mupis.map(mp => ({
value: mp.id,
label: `${mp.name}${mp.you ? ' (油)' : ''}`
}))
},
{
type: 'select',
name: 'mupi2_id',
label: '木皮2',
select2: true,
options: window.parent.dataManager.data.mupis.map(mp => ({
value: mp.id,
label: `${mp.name}${mp.you ? ' (油)' : ''}`
}))
}
];
default:
return [
{ type: 'text', name: 'name', label: '名称', required: true }
];
}
}
// 辅助函数 - 格式化板材信息
function formatBancaiInfo(bancai) {
if (!bancai) return '';
let info = `厚度: ${bancai.houdu}mm, 材质: `;
if (bancai.caizhi) {
info += bancai.caizhi.name;
}
if (bancai.mupi1) {
info += `, 木皮1: ${bancai.mupi1.name}${bancai.mupi1.you ? ' (油)' : ''}`;
}
if (bancai.mupi2) {
info += `, 木皮2: ${bancai.mupi2.name}${bancai.mupi2.you ? ' (油)' : ''}`;
}
return info;
}
// 辅助函数 - 获取实体标签
function getEntityLabel(entityType) {
const labels = {
'bancai': '板材',
'caizhi': '材质',
'mupi': '木皮',
'user': '用户',
'dingdan': '订单',
'chanpin': '产品',
'dingdan_chanpin': '订单产品',
'chanpin_zujian': '产品组件'
};
return labels[entityType] || entityType;
}
```
### 关键改进说明
1. **Select2集成**:
- 在`select`字段类型中添加了`afterRender`回调
- 自动检测`select2`配置属性并初始化Select2组件
- 设置`dropdownParent`确保下拉菜单在模态框中正确显示
2. **创建实体按钮**:
- 在`select`字段配置中添加`addButton`属性
- 自动在字段下方添加"创建"按钮
- 按钮点击时打开嵌套弹窗创建新实体
3. **弹窗生命周期管理**:
- 添加`afterRender`回调用于字段级初始化
- 弹窗关闭时自动销毁Select2实例
- 维护活动弹窗列表避免内存泄漏
4. **辅助函数**:
- 添加`getEntityFields`获取实体字段配置
- 添加`formatBancaiInfo`格式化板材信息
- 添加`getEntityLabel`获取实体标签
### 使用示例
在`tianjia2.js`中,您可以这样配置带Select2和创建按钮的下拉框:
```javascript
// 在showEntityModal函数中配置产品下拉框
{
type: 'select',
name: 'chanpin_id',
label: '产品',
required: true,
select2: true, // 启用Select2
placeholder: '选择产品或创建新产品',
options: chanpinOptions,
addButton: { // 添加创建按钮
label: '创建新产品',
entityType: 'chanpin'
}
}
```
### 嵌套弹窗工作流程
1. 用户在主弹窗点击"创建新产品"按钮
2. 打开嵌套弹窗创建新产品
3. 保存成功后:
- 关闭嵌套弹窗
- 自动刷新父弹窗中的产品下拉框
- 新创建的产品会出现在下拉选项中
### 相关问题