iframe,modaldialog父子窗口相互通信的问题

本文介绍了如何使用JavaScript在不同窗口间进行交互,包括父窗口与子窗口间的对象访问及iframe内容的操作方法。涵盖window.open方法的详细参数说明,以及如何通过window.opener、window.parent等属性访问窗口对象。

--- 子窗口访问父窗口的window对象 ---

打开新窗口一般有几种方法,window.open(...),window.showModalDialog(...),以及iframe中嵌套页面,另外还有window.navigate 

(...)、window.location.href="..."、window.history.back(-1);都是实现同一页面内容跳转的,这里不讨论。

****************************************

window.navigate('http://www.baidu.com'); //IE ONLY

window.location.href='http://www.baidu.com'; // all

window.open(URL,name,features,replace)

参数描述
URL一个可选的字符串,声明了要在新窗口中显示的文档的 URL。如果省略了这个参数,或者它的值是空字符串,那么新窗口就不会显示任何文档。
name一个可选的字符串,该字符串是一个由逗号分隔的特征列表,其中包括数字、字母和下划线,该字符声明了新窗口的名称。这个名称可以用作标记 <a> 和 <form> 的属性 target 的值。如果该参数指定了一个已经存在的窗口,那么 open() 方法就不再创建一个新窗口,而只是返回对指定窗口的引用。在这种情况下,features 将被忽略。
features一个可选的字符串,声明了新窗口要显示的标准浏览器的特征。如果省略该参数,新窗口将具有所有标准特征。在窗口特征这个表格中,我们对该字符串的格式进行了详细的说明。
replace

 

一个可选的布尔值。规定了装载到窗口的 URL 是在窗口的浏览历史中创建一个新条目,还是替换浏览历史中的当前条目。支持下面的值:

  • true - URL 替换浏览历史中的当前条目。
  • false - URL 在浏览历史中创建新的条目。

四.窗口特征(Window Features)

 

channelmode=yes|no|1|0是否使用剧院模式显示窗口。默认为 no。
directories=yes|no|1|0是否添加目录按钮。默认为 yes。
fullscreen=yes|no|1|0是否使用全屏模式显示浏览器。默认是 no。处于全屏模式的窗口必须同时处于剧院模式。
height=pixels窗口文档显示区的高度。以像素计。
left=pixels窗口的 x 坐标。以像素计。
location=yes|no|1|0是否显示地址字段。默认是 yes。
menubar=yes|no|1|0是否显示菜单栏。默认是 yes。
resizable=yes|no|1|0窗口是否可调节尺寸。默认是 yes。
scrollbars=yes|no|1|0是否显示滚动条。默认是 yes。
status=yes|no|1|0是否添加状态栏。默认是 yes。
titlebar=yes|no|1|0是否显示标题栏。默认是 yes。
toolbar=yes|no|1|0是否显示浏览器的工具栏。默认是 yes。
top=pixels窗口的 y 坐标。
width=pixels窗口的文档显示区的宽度。以像素计。

--- example: ---

var url=document.location.href; //获得本窗口属性名
      newWin=window.open(url,'','fullscreen=1,scrollbars=0');      
      window.opener=null;//出掉关闭时候的提示窗口
      window.open('','_self'); //ie7      
      window.close();

 

*********************************

 1、window.open打开子窗口:子窗口中用window.opener访问父窗口的window对象

 2、window.showModalDialog打开模态子窗口:把父窗口的window对象作为参数传入子窗口,如:

window.showModalDialog('3.html', window);

// -- 3.html --

var parentWindow = window.dialogArguments

 3、iframe子页面中:用window.parent访问父窗口的window对象

 

--- iframe用法总结 ---

mpage.html:

<!doctype html>
<html>
<head>
<title>iframe about contentWindow contentDocument</title>
<meta charset="utf-8" />
</head>
<body>
<h1 id="mtitle">美丽的一天</h1>
<p>早上吃早点,中午约会吃饭,下午K歌</p>
<iframe id="ifrm" name="ifrmWin" src="ipage.html" frameborder="0" ></iframe>


<script>
function $id(id){return document.getElementById(id); }

// ifrmNode.document 最简洁
/*result=> chrome: undefined, firefox:undefined ,ie8:document */
console.log($id('ifrm').document);

//ifrmNode.contentWindow 基本兼容
/*result=> chrome: window , firefox:window, ie8:window*/
console.log($id('ifrm').contentWindow);

//iframNode.contentDocument 标准且快捷
/* result=> chrome: document, firefox:document, ie8:document */
console.log($id('ifrm').contentDocument);

</script>
</body>
</html>

ipage.html:

<!doctype html>
<html>
<head>
<title>iframe page</title>
<meta charset="utf-8" />
</head>
<body>
<h1 id="ititle">美丽的一天的下午</h1>
<p>吃饭看书</p>
</body>
</html>

下面讨论ie下JS是怎么操作以上两个页面,再讨论firefox的做法,最后给出兼容ie,firefox浏览器操作iframe对象的方法。

一、ie下访问操作iframe里内容

1. ie通过document.frames["ifrmWin"]获取iframe对象 ~~~ie: 可通过ifrmNode.document获得子页面的document对象
window.onload = function () {
alert(document.frames["ifrmWin"].document.getElementsByTagName(‘h1‘)[0].firstChild.data);
};
2. ie另一种方法contentWindow获取它,代码: ~~~ ie&firefox: 通过ifrmNode.contentWindow获得子页面的window对象
window.onload = (function () {
var iObj = document.getElementById(‘ifrm‘).contentWindow;
alert(iObj.document.getElementsByTagName(‘h1‘)[0].firstChild.data);
});
此方法经过ie6,ie7,firefox2.0,firefox3.0测试都通过,好事啊!嘿嘿。(网上一查,发现Mozilla Firefox
iframe.contentWindow.focus缓冲区溢出漏洞,有脚本注入攻击的危险。
后来听说可以在后台防止这样的事情发生,算是松了口气。不过还是希望firefox新版本可以解决这样的危险。)

二、firefox下访问操作iframe里内容
Mozilla支持通过IFrameElmRef.contentDocument访问iframe的document对象的W3C标准,通过标准可以少
写一个document,代码:~~~firefox: ifrmNode.contentDocument 直接获取到子页面的document对象
var iObj = document.getElementById(‘ifrm‘).contentDocument;
alert(iObj.getElementsByTagName(‘h1‘)[0].innerHTML=‘我想变成她一天的一部分‘);
alert(iObj.getElementsByTagName(‘p‘)[0].firstChild.data);
兼容这两种浏览器的方法,现在也出来了,就是使用contentWindow这个方法。
嘿嘿!操作iframe是不是可以随心所欲了呢?如果还觉得不爽,你甚至可以重写iframe里的内容。

三、重写iframe里的内容
通过designMode(设置文档为可编辑设计模式)和contentEditable(设置内容为可编辑),你可以重写iframe里的内容。代码:
var iObj = document.getElementById(‘ifrm‘).contentWindow;
iObj.document.designMode = ‘On‘;
iObj.document.contentEditable = true;
iObj.document.open();
iObj.document.writeln(‘<html><head>‘);
iObj.document.writeln(‘<mce:style><!--
body {background:#000;font-size:9pt;margin: 2px; padding: 0px;}
--></mce:style><style mce_bogus="1">body
{background:#000;font-size:9pt;margin: 2px; padding:
0px;}</style>‘);
iObj.document.writeln(‘</head><body></body></html>‘);

iObj.document.close();

四、iframe自适应高度
有了上面的原理要实现这个相当简单,就是把iframe的height值设置成它里面文档的height值就可以。代码:
window.onload = (function () {
var iObj = document.getElementById(‘ifrm‘);
iObj.height = iObj.contentWindow.document.documentElement.scrollHeight;});
现在对JS操作iframe你已经有了全新的认识,说不定那天会因为这个有什么新的web技术名词,嘿嘿,臭美下!

PS:
1. Document.designMode
,Document.contentEditable在你这里的使用场景错了,他一般是应用在在线编辑器上的,如果你并不是开放给用户操作的话,根本没必
要设置这个属性!
2. 另外之所以要用
window.onload,是因为页面加载中,iframe的加载顺序是在最后的,也就是说,在没用window.onload的情况下,在执行你那段
js的时候iframe里的dom都还没加载到,这样自然是无输出的了
3. 如果是在两个不同的子域下,上面的代码需要做小的改动。
调用iframe的页面和被iframe的页面需要增加设置 document.domain 的代码,指明同一个根域即可。

------------------------ bancai.js ------------------------ // bancai.js $(document).ready(function () { const modal = new bootstrap.Modal('#bancaiModal'); let currentMode = 'view'; let caizhiList = []; let mupiList = []; let currentSearchText = ''; // 从父窗口获取 DataManager let dataManager = null; // 等待父窗口的 DataManager 准备就绪 async function waitForDataManager() { return new Promise((resolve, reject) => { if (window.parent && window.parent.dataManager) { resolve(window.parent.dataManager); } else { reject(new Error('无法从父窗口获取 DataManager')); } }); } // 初始化函数 async function initialize() { try { dataManager = await waitForDataManager(); if (!dataManager || typeof dataManager.fetchAll !== 'function') { throw new Error('无效的 DataManager 实例'); } // 解构需要的方法和属性 const { addEntity, updateEntity, deleteEntity, fetchAll } = dataManager; // 确保数据已加载 await fetchAll(); // 更新材质和木皮选项 updateOptions(); // 渲染板材表格 refreshTable(); } catch (error) { console.error('初始化失败:', error); alert('系统初始化失败,请刷新页面或联系管理员'); } } // 更新材质和木皮选项 function updateOptions() { caizhiList = dataManager.data.caizhis; updateSelectOptions('#caizhiSelect', caizhiList); mupiList = dataManager.data.mupis.map(m => ({ ...m, name: m.you ? `${m.name}(油漆)` : m.name, })); updateSelectOptions('#mupi1Select', mupiList); updateSelectOptions('#mupi2Select', mupiList); } // 更新下拉框选项 function updateSelectOptions(selector, data) { $(selector).empty(); data.forEach(item => { $(selector).append(`<option value="${item.id}">${item.name}</option>`); }); } // 刷新表格 function refreshTable() { const filteredData = filterBancais(currentSearchText); renderBancaiTable(filteredData); } // 搜索过滤 function filterBancais(searchText) { if (!searchText) return dataManager.data.bancais; return dataManager.data.bancais.filter(bancai => { const caizhiName = bancai.caizhi?.name || ''; const mupi1Name = bancai.mupi1?.name || ''; const mupi2Name = bancai.mupi2?.name || ''; const houdu = bancai.houdu.toString(); return [ caizhiName.toLowerCase(), mupi1Name.toLowerCase(), mupi2Name.toLowerCase(), houdu.toLowerCase(), ].some(field => field.includes(searchText.toLowerCase())); }); } // 渲染表格 function renderBancaiTable(bancais) { const $tbody = $('#bancaiTable tbody'); $tbody.empty(); bancais.forEach(bancai => { const caizhiName = bancai.caizhi?.name || '未知'; const mupi1Name = bancai.mupi1?.name || '未知'; const mupi2Name = bancai.mupi2?.name || '未知'; const row = ` <tr data-id="${bancai.id}"> <td>${bancai.id}</td> <td>${caizhiName}</td> <td>${mupi1Name} ${bancai.mupi1?.you ? '(油漆)' : ''}</td> <td>${mupi2Name} ${bancai.mupi2?.you ? '(油漆)' : ''}</td> <td>${bancai.houdu}</td> <td> <button class="btn btn-sm btn-info view-btn">查看</button> <button class="btn btn-sm btn-warning edit-btn">编辑</button> <button class="btn btn-sm btn-danger delete-btn">删除</button> </td> </tr> `; $tbody.append(row); }); bindTableEvents(); } // 绑定表格事件 function bindTableEvents() { $('.view-btn').click(function () { const id = $(this).closest('tr').data('id'); openModalForBancai(id, 'view'); }); $('.edit-btn').click(function () { const id = $(this).closest('tr').data('id'); openModalForBancai(id, 'edit'); }); $('.delete-btn').click(function () { const id = $(this).closest('tr').data('id'); deleteBancai(id); }); } // 添加按钮事件 $('#addBancaiBtn').click(function () { $('#bancaiForm')[0].reset(); $('#modalTitle').text('添加新板材'); currentMode = 'add'; enableForm(true); updateOptions(); modal.show(); }); // 搜索按钮事件 $('#searchBtn').click(function () { currentSearchText = $('#searchInput').val(); refreshTable(); }); // 输入框实时搜索 $('#searchInput').on('input', function () { currentSearchText = $(this).val(); refreshTable(); }); // 打开弹窗显示板材数据 function openModalForBancai(id, mode) { const bancai = dataManager.data.bancais.find(b => b.id === id); if (!bancai) return; currentMode = mode; $('#bancaiId').val(bancai.id); $('#caizhiSelect').val(bancai.caizhi.id); $('#mupi1Select').val(bancai.mupi1.id); $('#mupi2Select').val(bancai.mupi2.id); $('#houdu').val(bancai.houdu); $('#modalTitle').text(mode === 'view' ? '板材详情' : '编辑板材'); enableForm(mode === 'edit'); modal.show(); } // 启用/禁用表单 function enableForm(enable) { $('#caizhiSelect').prop('disabled', !enable); $('#mupi1Select').prop('disabled', !enable); $('#mupi2Select').prop('disabled', !enable); $('#houdu').prop('disabled', !enable); $('#saveBtn').toggle(enable); } // 保存按钮点击事件 $('#saveBtn').click(async function () { const formData = { id: $('#bancaiId').val(), caizhiId: parseInt($('#caizhiSelect').val()), mupi1Id: parseInt($('#mupi1Select').val()), mupi2Id: parseInt($('#mupi2Select').val()), houdu: parseFloat($('#houdu').val()), }; try { if (currentMode === 'add') { await addEntity('bancais', formData); } else { await updateEntity('bancais', formData); } refreshTable(); modal.hide(); } catch (error) { console.error('操作失败:', error); alert('操作失败,请重试'); } }); // 删除板材 async function deleteBancai(id) { if (!confirm('确定要删除此板材吗?')) return; try { await deleteEntity('bancais', id); refreshTable(); } catch (error) { console.error('删除失败:', error); alert('删除失败,请重试'); } } // 初始化应用 initialize(); }); ------------------------ DataManager.js ------------------------ /** * 数据管理器类,负责与后端API通信并管理数据 */ class DataManager { constructor(baseUrl) { this.baseUrl = baseUrl; this.data = { bancais: [], dingdans: [], mupis: [], chanpins: [], kucuns: [], dingdan_chanpin_zujians: [], chanpin_zujians: [], zujians: [], caizhis: [], dingdan_chanpins: [], users: [] }; this.isSyncing = false; this.lastSync = null; // 回调注册表 this.callbacks = { // 全局回调 all: [], // 按实体类型分类的回调 bancais: [], dingdan: [], mupi: [], chanpin: [], kucun: [], dingdan_chanpin_zujian: [], chanpin_zujian: [], zujian: [], caizhi: [], dingdan_chanpin: [], user: [] // ...其他实体 }; } /** * 获取所有数据 * @returns {Promise<boolean>} 是否成功 */ async fetchAll() { // 添加更详细的错误信息 if (!this.baseUrl) { throw new Error('baseUrl is undefined'); } try { const response = await fetch(`${this.baseUrl}/app/all`); if (!response.ok) throw new Error('Network response was not ok'); const result = await response.json(); if (result.status !== 200) throw new Error(result.text || 'API error'); // 更新本地数据 Object.keys(this.data).forEach(key => { if (result.data[key]) { this.data[key] = result.data[key]; } }); this.lastSync = new Date(); return true; } catch (error) { console.error('Fetch error:', error); return false; } } /** * 注册回调函数 * @param {string} entity - 实体类型(如'bancai')或'all'表示全局回调 * @param {Function} callback - 回调函数,参数为(operation, data) */ registerCallback(entity, callback) { if (!this.callbacks[entity]) { this.callbacks[entity] = []; } this.callbacks[entity].push(callback); } /** * 移除回调函数 * @param {string} entity - 实体类型 * @param {Function} callback - 要移除的回调函数 */ unregisterCallback(entity, callback) { if (!this.callbacks[entity]) return; const index = this.callbacks[entity].indexOf(callback); if (index !== -1) { this.callbacks[entity].splice(index, 1); } } /** * 触发回调 * @param {string} operation - 操作类型('add', 'update', 'delete') * @param {string} entity - 实体类型 * @param {Object} data - 相关数据 */ triggerCallbacks(operation, entity, data) { // 触发全局回调 this.callbacks.all.forEach(cb => cb(operation, entity, data)); // 触发特定实体回调 if (this.callbacks[entity]) { this.callbacks[entity].forEach(cb => cb(operation, data)); } } /** * 执行CRUD操作并触发回调 */ async crudOperation(operation, entity, data) { try { const response = await fetch(`${this.baseUrl}/app/${operation}/${entity}`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) }); if (!response.ok) throw new Error('Network response was not ok'); const result = await response.json(); if (result.status !== 200) throw new Error(result.text || 'API error'); // 触发操作成功的回调 this.triggerCallbacks(operation, entity, data); // 自动同步数据 this.syncData(); return result; } catch (error) { console.error('CRUD error:', error); // 触发操作失败的回调 this.triggerCallbacks(`${operation}_error`, entity, { data, error: error.message }); throw error; } } /** * 执行CRUD操作 * @param {string} operation - 'add', 'delete', 'update' * @param {string} entity - 实体名称(小写) * @param {Object} data - 要发送的数据 * @returns {Promise<Object>} 响应结果 */ async crudOperation(operation, entity, data) { try { const response = await fetch(`${this.baseUrl}/app/${operation}/${entity}`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) }); if (!response.ok) throw new Error('Network response was not ok'); const result = await response.json(); if (result.status !== 200) throw new Error(result.text || 'API error'); // 触发操作成功的回调 this.triggerCallbacks(operation, entity, data); // 自动同步数据 this.syncData(); return result; } catch (error) { console.error('CRUD error:', error); // 触发操作失败的回调 this.triggerCallbacks(`${operation}_error`, entity, { data, error: error.message }); throw error; } } /** * 自动同步数据(防止频繁请求) */ async syncData() { if (this.isSyncing) return; // 距离上次同步超过5秒才执行新同步 if (this.lastSync && new Date() - this.lastSync < 5000) { setTimeout(() => this.syncData(), 5000 - (new Date() - this.lastSync)); return; } this.isSyncing = true; try { await this.fetchAll(); } finally { this.isSyncing = false; } } /** * 添加实体 * @param {string} entity - 实体名称 * @param {Object} data - 实体数据 */ async addEntity(entity, data) { return this.crudOperation('add', entity, data); } /** * 更新实体 * @param {string} entity - 实体名称 * @param {Object} data - 实体数据(必须包含id) */ async updateEntity(entity, data) { return this.crudOperation('update', entity, data); } /** * 删除实体 * @param {string} entity - 实体名称 * @param {number} id - 实体ID */ async deleteEntity(entity, id) { return this.crudOperation('delete', entity, {id}); } } export { DataManager }; // 创建单例实例 //const dataManager = new DataManager('http://127.0.0.1:8080/KuCun2'); // 初始化时获取所有数据 //dataManager.fetchAll().then(() => { // console.log('Initial data loaded'); //}); // 导出数据对象,外部可以直接访问 data.bancais, data.dingdans 等 export const data = dataManager.data; // 导出操作方法 export const addEntity = dataManager.addEntity.bind(dataManager); export const updateEntity = dataManager.updateEntity.bind(dataManager); export const deleteEntity = dataManager.deleteEntity.bind(dataManager); export const fetchAll = dataManager.fetchAll.bind(dataManager); ------------------------ bancai.html ------------------------ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>板材数据管理</title> <!-- 引入 Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <!-- 引入 jQuery --> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> </head> <body> <div class="container mt-4"> <h1 class="mb-4">板材数据管理</h1> <!-- 搜索框 --> <div class="row mb-3"> <div class="col-md-6"> <div class="input-group"> <input type="text" class="form-control" id="searchInput" placeholder="搜索材质、木皮或厚度..."> <button class="btn btn-outline-secondary" type="button" id="searchBtn"> <i class="bi bi-search"></i> 搜索 </button> </div> </div> <div class="col-md-6 text-end"> <button class="btn btn-primary" id="addBancaiBtn">添加新板材</button> </div> </div> <table class="table table-striped mt-3" id="bancaiTable"> <thead> <tr> <th>ID</th> <th>材质</th> <th>木皮1</th> <th>木皮2</th> <th>厚度</th> <th>操作</th> </tr> </thead> <tbody> <!-- 数据将通过 DataManager 加载 --> </tbody> </table> </div> <!-- 模态框保持不变 --> <div class="modal fade" id="bancaiModal" tabindex="-1" aria-hidden="true"> <!-- ... 原有模态框内容 ... --> </div> <!-- 查看/编辑弹窗 --> <div class="modal fade" id="bancaiModal" tabindex="-1" aria-hidden="true"> <div class="modal-dialog modal-lg"> <div class="modal-content"> <div class="modal-header"> <h5 class="modal-title" id="modalTitle">板材详情</h5> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> </div> <div class="modal-body"> <form id="bancaiForm"> <input type="hidden" id="bancaiId"> <div class="mb-3"> <label class="form-label">材质</label> <select class="form-select" id="caizhiSelect" name="caizhi"></select> </div> <div class="mb-3"> <label class="form-label">木皮1</label> <select class="form-select" id="mupi1Select" name="mupi1"></select> </div> <div class="mb-3"> <label class="form-label">木皮2</label> <select class="form-select" id="mupi2Select" name="mupi2"></select> </div> <div class="mb-3"> <label class="form-label">厚度</label> <input type="number" step="0.01" class="form-control" id="houdu" name="houdu"> </div> </form> </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="saveBtn">保存</button> </div> </div> </div> </div> <!-- 引入 Bootstrap JS --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script type="module" src="../js/bancai.js"></script> <!-- 改为模块方式导入 --> </body> </html> ------------------------ index.html ------------------------ <!doctype html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title>峤丞板材库存管理</title> <meta name="viewport" content="width=device-width, initial-scale=1"> <link rel="stylesheet" type="text/css" href="fonts/font-awesome-4.7.0/css/font-awesome.min.css"> <link rel="stylesheet" type="text/css" href="main/bootstrap-3.3.7-dist/css/bootstrap.css.map"> <link rel="stylesheet" type="text/css" href="css/util.css"> <link rel="stylesheet" type="text/css" href="css/main.css"> <link rel="stylesheet" type="text/css" href="css/index2.css"> <script type="text/javascript" src="js/jquery-3.2.1.min.js"></script> <script type="text/javascript" src="js/jsyilai.js"></script> <script type="module"> // 共享的DataManager类 import { DataManager } from './data/DataManager.js'; document.addEventListener('DOMContentLoaded', async () => { try { // 创建实例并挂载到window window.dataManager = new DataManager('http://127.0.0.1:8080/KuCun2'); // 初始化数据 await window.dataManager.fetchAll(); console.log('Data Manager initialized successfully'); // 设置iframe通信 const iframe = document.getElementById('iframeid'); iframe.onload = () => { console.log('Iframe loaded'); // 通知iframe数据已准备好 iframe.contentWindow.postMessage('DataManagerReady', '*'); }; } catch (error) { console.error('Failed to initialize DataManager:', error); } }); </script> <style type="text/css"> *{ margin:0; padding:0; } .frame-header { height: 60px; background-color: #23262E; justify-content: space-between; } .frame-header-li{ font-family: Arial, Helvetica, sans-serif; font-size:40px; } .frame-ul{ } .frame-ul li{ border-style: solid; border-width:1px 0px 1px 0px; margin-top: 1px; height: 35px; text-align: center } #username{ position: absolute; right: 0; /* 靠右 */ } .frame-body { position: fixed; top: 60px; right: 0; bottom: 0; left: 0; display: flex; flex-direction: row; } .frame-side { scrollbar-width: none; /* firefox隐藏滚动条 */ -ms-overflow-style: none; /* IE 10+隐藏滚动条 */ overflow-x: hidden; overflow-y: auto; width: 200px; background-color:#9e5; } .frame-side::-webkit-scrollbar { display: none; /* Chrome Safari 隐藏滚动条*/ } .frame-main { flex-grow: 1; background-color:#fff; } .jiaoluo{ margin: auto; margin-right: 0px; } .menu { display: none; position: absolute; background-color: #f9f9f9; border: 1px solid #ccc; padding: 10px; list-style-type: none; margin: 0; z-index: 10; } </style> </head> <body> <div class="frame-header"> <a class='frame-header-li' style="color:#fff">峤丞木材仓库管理</a> <a id="username" class='frame-header-li' style="color:#520">峤丞木材仓库管理</a> <!-- 菜单 --> <ul class="menu"> <li>选项 1</li> <li>选项 2</li> <li>选项 3</li> </ul> </div> <div class="frame-body"> <div class="frame-side"> <ul id="main_u" class='frame-ul' style="text-align:center;"> <li ><a href="main/test.html" target="main">首页</a></li> <li><a href="main/dingdan.html" target="main">订单查询</a></li> <li><a href="main/bancai.html" target="main">板材查询</a></li> <li><a href="main/Guanli.html" target="main">人员管理</a></li> <li><a href="main/test.html" target="main">test</a></li> <li><a href="main/tianjia.html" target="main">test</a></li> </ul> </div> <div class="frame-main"> <!-- 内容主体区域 --> <iframe id="iframeid" name="main" src="main/bancai.html" width="100%" height="100%" frameborder="0"> </iframe> </div> </div> </body> </html> bancai.js:44 初始化失败: Error: 无法从父窗口获取 DataManager at bancai.js:18:24 at new Promise (<anonymous>) at waitForDataManager (bancai.js:14:16) at initialize (bancai.js:26:33) at HTMLDocument.<anonymous> (bancai.js:232:5) at e (jquery-3.6.0.min.js:2:30038) at t (jquery-3.6.0.min.js:2:30340)
最新发布
06-16
------------------------ bancai.html ------------------------ <!DOCTYPE html> <html lang=“en”> <head> <meta charset=“UTF-8”> <meta name=“viewport” content=“width=device-width, initial-scale=1.0”> <title>板材数据管理</title> <!-- 引入 Bootstrap CSS --> <link href=“https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css” rel=“stylesheet”> <!-- 引入 jQuery --> <script src=“https://code.jquery.com/jquery-3.6.0.min.js”></script> </head> <body> <div class=“container mt-4”> <h1 class=“mb-4”>板材数据管理</h1> <!-- 搜索框 --> <div class="row mb-3"> <div class="col-md-6"> <div class="input-group"> <input type="text" class="form-control" id="searchInput" placeholder="搜索材质、木皮或厚度..."> <button class="btn btn-outline-secondary" type="button" id="searchBtn"> <i class="bi bi-search"></i> 搜索 </button> </div> </div> <div class="col-md-6 text-end"> <button class="btn btn-primary" id="addBancaiBtn">添加新板材</button> </div> </div> <table class="table table-striped mt-3" id="bancaiTable"> <thead> <tr> <th>ID</th> <th>材质</th> <th>木皮1</th> <th>木皮2</th> <th>厚度</th> <th>操作</th> </tr> </thead> <tbody> <!-- 数据将通过 DataManager 加载 --> </tbody> </table> </div> <!-- 模态框保持不变 --> <div class=“modal fade” id=“bancaiModal” tabindex=“-1” aria-hidden=“true”> <div class=“modal-dialog modal-lg”> <div class=“modal-content”> <div class=“modal-header”> <h5 class=“modal-title” id=“modalTitle”>板材详情</h5> <button type=“button” class=“btn-close” data-bs-dismiss=“modal” aria-label=“Close”></button> </div> <div class=“modal-body”> <form id=“bancaiForm”> <input type=“hidden” id=“bancaiId”> <div class=“mb-3”> <label class=“form-label”>材质</label> <select class=“form-select” id=“caizhiSelect” name=“caizhi”></select> </div> <div class=“mb-3”> <label class=“form-label”>木皮1</label> <select class=“form-select” id=“mupi1Select” name=“mupi1”></select> </div> <div class=“mb-3”> <label class=“form-label”>木皮2</label> <select class=“form-select” id=“mupi2Select” name=“mupi2”></select> </div> <div class=“mb-3”> <label class=“form-label”>厚度</label> <input type=“number” step=“0.01” class=“form-control” id=“houdu” name=“houdu”> </div> </form> </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=“saveBtn”>保存</button> </div> </div> </div> </div> <!-- 引入 Bootstrap JS --> <script src=“https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js”></script> <script type=“module” src=“…/js/bancai.js”></script> <!-- 改为模块方式导入 --> </body> </html> ------------------------ bancai.js ------------------------ // bancai.js $(document).ready(function () { const modal = new bootstrap.Modal(‘#bancaiModal’); let currentMode = ‘view’; let caizhiList = []; let mupiList = []; let currentSearchText = ‘’; // 从父窗口获取 DataManager let dataManager = null; // 等待父窗口的 DataManager 准备就绪 async function waitForDataManager() { return new Promise((resolve, reject) => { if (window.parent && window.parent.dataManager) { resolve(window.parent.dataManager); } else { reject(new Error('无法从父窗口获取 DataManager')); } }); } window.addEventListener('message', (event) => { if (event.data === 'DataManagerReady') { initialize(); } }); // 初始化函数 async function initialize() { try { dataManager = await waitForDataManager(); if (!dataManager || typeof dataManager.fetchAll !== 'function') { throw new Error('无效的 DataManager 实例'); } // 解构需要的方法和属性 // const { addEntity, dataManager.deleteEntity, deleteEntity, fetchAll } = dataManager; // 确保数据已加载 await dataManager.fetchAll(); // 更新材质和木皮选项 updateOptions(); // 渲染板材表格 refreshTable(); } catch (error) { console.error('初始化失败:', error); alert('系统初始化失败,请刷新页面或联系管理员'); } } // 更新材质和木皮选项 function updateOptions() { caizhiList = dataManager.data.caizhis; updateSelectOptions('#caizhiSelect', caizhiList); mupiList = dataManager.data.mupis.map(m => ({ ...m, name: m.you ? `${m.name}(油漆)` : m.name, })); updateSelectOptions('#mupi1Select', mupiList); updateSelectOptions('#mupi2Select', mupiList); } // 更新下拉框选项 function updateSelectOptions(selector, data) { $(selector).empty(); data.forEach(item => { $(selector).append(`<option value="${item.id}">${item.name}</option>`); }); } // 刷新表格 function refreshTable() { const filteredData = filterBancais(currentSearchText); renderBancaiTable(filteredData); } // 搜索过滤 function filterBancais(searchText) { if (!searchText) return dataManager.data.bancais; return dataManager.data.bancais.filter(bancai => { const caizhiName = bancai.caizhi?.name || ''; const mupi1Name = bancai.mupi1?.name || ''; const mupi2Name = bancai.mupi2?.name || ''; const houdu = bancai.houdu.toString(); return [ caizhiName.toLowerCase(), mupi1Name.toLowerCase(), mupi2Name.toLowerCase(), houdu.toLowerCase(), ].some(field => field.includes(searchText.toLowerCase())); }); } // 渲染表格 function renderBancaiTable(bancais) { const $tbody = $('#bancaiTable tbody'); $tbody.empty(); bancais.forEach(bancai => { const caizhiName = bancai.caizhi?.name || '未知'; const mupi1Name = bancai.mupi1?.name || '未知'; const mupi2Name = bancai.mupi2?.name || '未知'; const row = ` <tr data-id="${bancai.id}"> <td>${bancai.id}</td> <td>${caizhiName}</td> <td>${mupi1Name} ${bancai.mupi1?.you ? '(油漆)' : ''}</td> <td>${mupi2Name} ${bancai.mupi2?.you ? '(油漆)' : ''}</td> <td>${bancai.houdu}</td> <td> <button class="btn btn-sm btn-info view-btn">查看</button> <button class="btn btn-sm btn-warning edit-btn">编辑</button> <button class="btn btn-sm btn-danger delete-btn">删除</button> </td> </tr> `; $tbody.append(row); }); bindTableEvents(); } // 绑定表格事件 function bindTableEvents() { $('.view-btn').click(function () { const id = $(this).closest('tr').data('id'); openModalForBancai(id, 'view'); }); $('.edit-btn').click(function () { const id = $(this).closest('tr').data('id'); openModalForBancai(id, 'edit'); }); $('.delete-btn').click(function () { const id = $(this).closest('tr').data('id'); deleteBancai(id); }); } // 添加按钮事件 $('#addBancaiBtn').click(function () { $('#bancaiForm')[0].reset(); $('#modalTitle').text('添加新板材'); currentMode = 'add'; enableForm(true); updateOptions(); modal.show(); }); // 搜索按钮事件 $('#searchBtn').click(function () { currentSearchText = $('#searchInput').val(); refreshTable(); }); // 输入框实时搜索 $('#searchInput').on('input', function () { currentSearchText = $(this).val(); refreshTable(); }); // 打开弹窗显示板材数据 function openModalForBancai(id, mode) { const bancai = dataManager.data.bancais.find(b => b.id === id); if (!bancai) return; currentMode = mode; $('#bancaiId').val(bancai.id); $('#caizhiSelect').val(bancai.caizhi.id); $('#mupi1Select').val(bancai.mupi1.id); $('#mupi2Select').val(bancai.mupi2.id); $('#houdu').val(bancai.houdu); $('#modalTitle').text(mode === 'view' ? '板材详情' : '编辑板材'); enableForm(mode === 'edit'); modal.show(); } // 启用/禁用表单 function enableForm(enable) { $('#caizhiSelect').prop('disabled', !enable); $('#mupi1Select').prop('disabled', !enable); $('#mupi2Select').prop('disabled', !enable); $('#houdu').prop('disabled', !enable); $('#saveBtn').toggle(enable); } // 保存按钮点击事件 $('#saveBtn').click(async function () { const formData = { id: $('#bancaiId').val(), caizhiId: parseInt($('#caizhiSelect').val()), mupi1Id: parseInt($('#mupi1Select').val()), mupi2Id: parseInt($('#mupi2Select').val()), houdu: parseFloat($('#houdu').val()), }; try { if (currentMode === 'add') { await dataManager.addEntity('bancai', formData); } else { await dataManager.updateEntity('bancai', formData); } refreshTable(); modal.hide(); } catch (error) { console.error('操作失败:', error); alert('操作失败,请重试'); } }); // 删除板材 async function deleteBancai(id) { if (!confirm('确定要删除此板材吗?')) return; try { await deleteEntity('bancai', id); refreshTable(); } catch (error) { console.error('删除失败:', error); alert('删除失败,请重试'); } } // 初始化应用 //initialize(); }); ------------------------ DataManager.js ------------------------ //{ 实体类的关联和属性列表 // “entities”: { // “Dingdan”: { // “properties”: { // “id”: “Integer”, // “number”: “String”, // “xiadan”: “Date”, // “jiaohuo”: “Date”, // “dingdan_chanpins”: “List<Dingdan_chanpin> (OneToMany)”, // “dingdan_chanpins_zujians”: “List<Dingdan_chanpin_zujian> (OneToMany)” // }, // “relations”: [ // “关联订单产品(Dingdan_chanpin)”, // “关联订单组件(Dingdan_chanpin_zujian)” // ] // }, // “Dingdan_chanpin”: { // “properties”: { // “id”: “Integer”, // “shuliang”: “Integer” // }, // “relations”: [ // “多对一关联订单(Dingdan)”, // “多对一关联产品(Chanpin)” // ] // }, // “Dingdan_chanpin_zujian”: { // “properties”: { // “id”: “Integer”, // “shuliang”: “Integer” // }, // “relations”: [ // “多对一关联订单(Dingdan)”, // “多对一关联组件(Chanpin_zujian)”, // “多对一关联板材(Bancai)” // ] // }, // “Jinhuo”: { // “properties”: { // “id”: “Integer”, // “shuliang”: “Integer”, // “date”: “Date” // }, // “relations”: [ // “多对一关联订单(Dingdan)”, // “多对一关联产品(Chanpin)”, // “多对一关联组件(Zujian)”, // “多对一关联板材(Bancai)”, // “多对一关联用户(User)” // ] // }, // “Kucun”: { // “properties”: { // “id”: “Integer”, // “shuliang”: “Long” // }, // “relations”: [ // “一对一关联板材(Bancai)” // ] // }, // “Mupi”: { // “properties”: { // “id”: “Integer”, // “name”: “String”, // “you”: “Boolean” // }, // “relations”: [ // “被板材关联(Bancai - mupi1/mupi2)” // ] // }, // “User”: { // “properties”: { // “id”: “Integer”, // “name”: “String”, // “andy”: “String”, // “pass”: “String”, // “role”: “int” // } // }, // “Zujian”: { // “properties”: { // “id”: “Integer”, // “name”: “String” // }, // “relations”: [ // “一对多关联产品组件(Chanpin_zujian)” // ] // }, // “Bancai”: { // “properties”: { // “id”: “Integer”, // “houdu”: “Double” // }, // “relations”: [ // “多对一关联材质(Caizhi)”, // “多对一关联木皮(Mupi - mupi1/mupi2)”, // “一对一关联库存(Kucun)” // ] // }, // “Caizhi”: { // “properties”: { // “id”: “Integer”, // “name”: “String” // }, // “relations”: [ // “一对多关联板材(Bancai)” // ] // }, // “Chanpin”: { // “properties”: { // “id”: “Integer”, // “bianhao”: “String” // }, // “relations”: [ // “一对多关联订单产品(Dingdan_chanpin)”, // “一对多关联产品组件(Chanpin_zujian)” // ] // }, // “Chanpin_zujian”: { // “properties”: { // “id”: “Integer”, // “one_howmany”: “Double” // }, // “relations”: [ // “多对一关联产品(Chanpin)”, // “多对一关联组件(Zujian)”, // “多对一关联板材(Bancai)” // ] // } // }, // “relationsSummary”: [ // “订单(Dingdan) 1:N 订单产品(Dingdan_chanpin)”, // “订单(Dingdan) 1:N 订单组件(Dingdan_chanpin_zujian)”, // “产品(Chanpin) 1:N 产品组件(Chanpin_zujian)”, // “组件(Zujian) 1:N 产品组件(Chanpin_zujian)”, // “板材(Bancai) 1:1 库存(Kucun)”, // “材质(Caizhi) 1:N 板材(Bancai)” // ] //} /** 数据管理器类,负责与后端API通信并管理数据 */ class DataManager { constructor(baseUrl) { this.baseUrl = baseUrl; this.data = { bancais: [], dingdans: [], mupis: [], chanpins: [], kucuns: [], dingdan_chanpin_zujians: [], chanpin_zujians: [], zujians: [], caizhis: [], dingdan_chanpins: [], users: [] }; this.isSyncing = false; this.lastSync = null; // 回调注册表 this.callbacks = { // 全局回调 all: [], // 按实体类型分类的回调 bancais: [], dingdan: [], mupi: [], chanpin: [], kucun: [], dingdan_chanpin_zujian: [], chanpin_zujian: [], zujian: [], caizhi: [], dingdan_chanpin: [], user: [] // …其他实体 }; } /** 获取所有数据 @returns {Promise<boolean>} 是否成功 */ async fetchAll() { console.log(this) try { const response = await fetch(${this.baseUrl}/app/all); if (!response.ok) throw new Error(‘Network response was not ok’); const result = await response.json(); if (result.status !== 200) throw new Error(result.text || ‘API error’); // 更新本地数据 Object.keys(this.data).forEach(key => { if (result.data[key]) { this.data[key] = result.data[key]; } }); this.lastSync = new Date(); return true; } catch (error) { console.error(‘Fetch error:’, error); return false; } } /** 解析数据关联关系,将ID引用转换为对象引用 @param {Object} data - 从后端加载的原始数据 @returns {Object} - 处理后的数据,包含完整的对象关联 */ function resolveDataReferences(data) { // 创建ID映射表 const idMaps = {}; Object.keys(data).forEach(key => { idMaps[key] = new Map(); data[key].forEach(item => idMaps[key].set(item.id, item)); }); // 处理多对一和一对一关系 const resolveRef = (source, sourceKey, targetKey, propertyName) => { source.forEach(item => { if (item[propertyName] && item[propertyName].id) { const refId = item[propertyName].id; const target = idMaps[targetKey]?.get(refId); if (target) { item[propertyName] = target; // 建立反向引用(一对多关系) if (!target[sourceKey]) target[sourceKey] = []; if (!target[sourceKey].includes(item)) { target[sourceKey].push(item); } } } }); }; // 处理一对多关系(直接创建关联数组) const resolveOneToMany = (sourceKey, targetKey, propertyName) => { const sourceItems = data[sourceKey]; sourceItems.forEach(source => { if (!source[propertyName]) source[propertyName] = []; }); data[targetKey].forEach(target => { if (target[sourceKey]?.id) { const sourceId = target[sourceKey].id; const source = idMaps[sourceKey]?.get(sourceId); if (source && source[propertyName]) { source[propertyName].push(target); } } }); }; // 处理特定关联关系 // 订单 ↔ 订单产品 (1:N) resolveOneToMany('dingdans', 'dingdan_chanpins', 'dingdan_chanpins'); resolveRef(data.dingdan_chanpins, 'dingdans', 'dingdans', 'dingdan'); // 订单 ↔ 订单组件 (1:N) resolveOneToMany('dingdans', 'dingdan_chanpin_zujians', 'dingdan_chanpin_zujians'); resolveRef(data.dingdan_chanpin_zujians, 'dingdans', 'dingdans', 'dingdan'); // 产品 ↔ 产品组件 (1:N) resolveOneToMany('chanpins', 'chanpin_zujians', 'chanpin_zujians'); resolveRef(data.chanpin_zujians, 'chanpins', 'chanpins', 'chanpin'); // 组件 ↔ 产品组件 (1:N) resolveOneToMany('zujians', 'chanpin_zujians', 'chanpin_zujians'); resolveRef(data.chanpin_zujians, 'zujians', 'zujians', 'zujian'); // 材质 ↔ 板材 (1:N) resolveOneToMany('caizhis', 'bancais', 'bancais'); resolveRef(data.bancais, 'caizhis', 'caizhis', 'caizhi'); // 板材 ↔ 库存 (1:1) resolveRef(data.bancais, 'kucuns', 'kucuns', 'kucun'); resolveRef(data.kucuns, 'bancais', 'bancais', 'bancai'); // 板材 ↔ 木皮 (多对一) resolveRef(data.bancais, 'mupis', 'mupis', 'mupi1'); resolveRef(data.bancais, 'mupis', 'mupis', 'mupi2'); // 订单产品 ↔ 产品 (多对一) resolveRef(data.dingdan_chanpins, 'chanpins', 'chanpins', 'chanpin'); // 订单组件 ↔ 组件 (多对一) resolveRef(data.dingdan_chanpin_zujians, 'chanpin_zujians', 'chanpin_zujians', 'chanpin_zujian'); // 订单组件 ↔ 板材 (多对一) resolveRef(data.dingdan_chanpin_zujians, 'bancais', 'bancais', 'bancai'); // 进货 ↔ 相关实体 (多对一) ['dingdans', 'chanpins', 'zujians', 'bancais', 'users'].forEach(entity => { resolveRef(data.jinhuos, entity, entity, entity.slice(0, -1)); }); return data; } /** 注册回调函数 @param {string} entity - 实体类型(如’bancai’)或’all’表示全局回调 @param {Function} callback - 回调函数,参数为(operation, data) */ registerCallback(entity, callback) { if (!this.callbacks[entity]) { this.callbacks[entity] = []; } this.callbacks[entity].push(callback); } /** 移除回调函数 @param {string} entity - 实体类型单数性质 @param {Function} callback - 要移除的回调函数 */ unregisterCallback(entity, callback) { if (!this.callbacks[entity]) return; const index = this.callbacks[entity].indexOf(callback); if (index !== -1) { this.callbacks[entity].splice(index, 1); } } /** 触发回调 @param {string} operation - 操作类型(‘add’, ‘update’, ‘delete’) @param {string} entity - 实体类型单数性质 @param {Object} data - 相关数据 */ triggerCallbacks(operation, entity, data) { // 触发全局回调 this.callbacks.all.forEach(cb => cb(operation, entity, data)); // 触发特定实体回调 if (this.callbacks[entity]) { this.callbacks[entity].forEach(cb => cb(operation, data)); } } /** 执行CRUD操作并触发回调 */ async crudOperation(operation, entity, data) { try { const response = await fetch(${this.baseUrl}/app/${operation}/${entity}, { method: ‘POST’, headers: {‘Content-Type’: ‘application/json’}, body: JSON.stringify(data) }); if (!response.ok) throw new Error(‘Network response was not ok’); const result = await response.json(); if (result.status !== 200) throw new Error(result.text || ‘API error’); // 触发操作成功的回调 this.triggerCallbacks(operation, entity, data); // 自动同步数据 this.syncData(); return result; } catch (error) { console.error(‘CRUD error:’, error); // 触发操作失败的回调 this.triggerCallbacks(${operation}_error, entity, { data, error: error.message }); throw error; } } /** 执行CRUD操作 @param {string} operation - ‘add’, ‘delete’, ‘update’ @param {string} entity - 实体名称单数性质(小写) @param {Object} data - 要发送的数据 @returns {Promise<Object>} 响应结果 */ async crudOperation(operation, entity, data) { try { const response = await fetch(${this.baseUrl}/app/${operation}/${entity}, { method: ‘POST’, headers: {‘Content-Type’: ‘application/json’}, body: JSON.stringify(data) }); if (!response.ok) throw new Error('Network response was not ok'); const result = await response.json(); if (result.status !== 200) throw new Error(result.text || 'API error'); // 触发操作成功的回调 this.triggerCallbacks(operation, entity, data); // 自动同步数据 this.syncData(); return result; } catch (error) { console.error(‘CRUD error:’, error); // 触发操作失败的回调 this.triggerCallbacks(${operation}_error, entity, { data, error: error.message }); throw error; } } /** 自动同步数据(防止频繁请求) */ async syncData() { if (this.isSyncing) return; // 距离上次同步超过5秒才执行新同步 if (this.lastSync && new Date() - this.lastSync < 5000) { setTimeout(() => this.syncData(), 5000 - (new Date() - this.lastSync)); return; } this.isSyncing = true; try { await this.fetchAll(); } finally { this.isSyncing = false; } } /** 添加实体 @param {string} entity - 实体名称单数性质 @param {Object} data - 实体数据 */ async addEntity(entity, data) { return this.crudOperation(‘add’, entity, data); } /** 更新实体 @param {string} entity - 实体名称单数性质 @param {Object} data - 实体数据(必须包含id) */ async updateEntity(entity, data) { return this.crudOperation(‘update’, entity, data); } /** 删除实体 @param {string} entity - 实体名称单数性质 @param {number} id - 实体ID */ async deleteEntity(entity, id) { return this.crudOperation(‘delete’, entity, {id}); } } export { DataManager }; // 创建单例实例 //const dataManager = new DataManager(‘http://127.0.0.1:8080/KuCun2’); //// 初始化时获取所有数据 //dataManager.fetchAll().then(() => { // console.log(‘Initial data loaded’); //}); // 导出数据对象,外部可以直接访问 data.bancais, data.dingdans 等 //export const data = dataManager.data; //// 导出操作方法 //export const addEntity = dataManager.addEntity.bind(dataManager); //export const updateEntity = dataManager.updateEntity.bind(dataManager); //export const deleteEntity = dataManager.deleteEntity.bind(dataManager); //export const fetchAll = dataManager.fetchAll.bind(dataManager);只修改api格式问题 发送api格式为{属性:“”,关联对象:{“id”:0},关联对象集:[{“id”:0},{“id”:1}]}
06-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值