jQuery.callbacks 注释

本文深入解析了jQuery.Callbacks的功能及实现原理,详细介绍了如何通过不同的标志位(如once、memory等)来定制回调函数列表的行为,并提供了具体的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1 (function( jQuery ) {
  2 
  3 // String to Object flags format cache
  4 var flagsCache = {};
  5 
  6 // Convert String-formatted flags into Object-formatted ones and store in cache
  7 // 将字符串形式的flags转换成对象形式,并且存到cache中,例: "once memory" =>  {"once":true,"memory":true}
  8 function createFlags( flags ) {
  9     var object = flagsCache[ flags ] = {},
 10         i, length;
 11     flags = flags.split( /\s+/ );
 12     for ( i = 0, length = flags.length; i < length; i++ ) {
 13         object[ flags[i] ] = true;
 14     }
 15     return object;
 16 }
 17 
 18 /*
 19  * 用以下参数创建一个回调函数列表:
 20  *
 21  *    flags:    可选的以空格分隔的flags,会影响回调函数列表的行为
 22  *     
 23  *
 24  * 默认情况下,回调函数列表的行为如同一个事件回调函数列表一样,并且可以触发多次。 
 25  *
 26  * 可选的 flags:
 27  *
 28  *    once:            会确保回调列表只被触发一次(像Deferred一样), 这个不会阻止memory模式,也就是说 "once memory"的参数下 虽然只能fire一次,但是fire后再add 还是有效果的
 29  *
 30  *    memory:          会记录上一次fire的上下文(一般是$.callbacks自身)和参数,并且会立即以最近一次fire记录下的上下文和参数执行新添加进来回调函数 (像Deferred一样)
 31  *                     具体看 (1) 88行 memory = !flags.memory || [ context, args ];  保存memory模式下的 函数上下文和参数
 32  *                           (2) 127-130行 self.add添加回调函数时候的处理
 33  *
 34  *    unique:          会确同一个回调函数只在列表中出现一次
 35  *
 36  *    stopOnFalse:     其中一个回调函数返回false即停止调用其余函数
 37  *
 38  */
 39 jQuery.Callbacks = function( flags ) {
 40 
 41     // Convert flags from String-formatted to Object-formatted
 42     // (we check in cache first)
 43     flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
 44 
 45     var // 实际存放回调函数的容器
 46         list = [],
 47 
 48         // 用以存放可重复fire(即非once模式),重复fire的回调函数  例: 在一个回调函数体里边也调用了fire的情景 
 49         // 另外这个stack 还用以标示整个回调列表是否处于锁定状态(仅仅判可不可触发fire, 还是可以add, remove的)  205行 + 192-202行
 50         stack = [],
 51 
 52         // 上次fire的上下文和参数, 
 53         // 可能的值: (1) [context,args]
 54         //          (2) true , 非记忆执行上下文和参数 或者 stopOnFalse模式下有返回为false的回调函数
 55         memory,
 56         // 标识当前是否处于fire状态
 57         firing,
 58         // fire第一个执行的回调函数在list中的索引位置 (内部fireWith使用和add导致list个数变化时修正执行中的索引位置 128行) 
 59         firingStart,
 60         // 循环的结尾位置 (如果在firing状态下self.add添加新函数和self.remove移除函数时候会调整 123行 146行 )
 61         firingLength,
 62         // 当前触发的函数索引 ,需要的时候会调整
 63         firingIndex,
 64         //工具方法: 添加一个或多个(args为数组时)函数到list 
 65         add = function( args ) {
 66             var i,
 67                 length,
 68                 elem,
 69                 type,
 70                 actual;
 71             for ( i = 0, length = args.length; i < length; i++ ) {
 72                 elem = args[ i ];
 73                 type = jQuery.type( elem );
 74                 if ( type === "array" ) {
 75                     // 递归检查
 76                     add( elem );
 77                 } else if ( type === "function" ) {
 78                     // 非unique模式且新回调函数不存在
 79                     if ( !flags.unique || !self.has( elem ) ) {
 80                         list.push( elem );
 81                     }
 82                 }
 83             }
 84         },
 85         //工具方法: 触发回调函数
 86         fire = function( context, args ) {
 87             args = args || [];
 88             memory = !flags.memory || [ context, args ];
 89             firing = true;
 90             firingIndex = firingStart || 0;
 91             firingStart = 0;
 92             firingLength = list.length;
 93             for ( ; list && firingIndex < firingLength; firingIndex++ ) {
 94                  if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
 95                     memory = true; // 标记为终止  注意看106行, 销毁$.callbacks 
 96                     break;
 97                 }
 98             }
 99             firing = false; // 标记执行结束
100             if ( list ) {
101                 if ( !flags.once ) { // fire完后检查是否有回调函数内部重复fire保留下来的执行上下文和参数
102                     if ( stack && stack.length ) {
103                         memory = stack.shift();
104                         self.fireWith( memory[ 0 ], memory[ 1 ] );
105                     }
106                 } else if ( memory === true ) { // stopOnFalse
107                     self.disable();
108                 } else {
109                     list = [];
110                 }
111             }
112         },
113         // 实际的回调函数对象
114         self = {
115             //实例方法: 添加一个或多个(args为数组时)函数到list 
116             add: function() {
117                 if ( list ) {
118                     var length = list.length;
119                     add( arguments );
120                     // Do we need to add the callbacks to the
121                     // current firing batch?
122                     if ( firing ) {
123                         firingLength = list.length;
124                     // With memory, if we're not firing then
125                     // we should call right away, unless previous
126                     // firing was halted (stopOnFalse)
127                     } else if ( memory && memory !== true ) {
128                         firingStart = length;
129                         fire( memory[ 0 ], memory[ 1 ] );
130                     }
131                 }
132                 return this;
133             },
134             // 从列表中移除函数 
135             remove: function() {
136                 if ( list ) {
137                     var args = arguments,
138                         argIndex = 0,
139                         argLength = args.length;
140                     for ( ; argIndex < argLength ; argIndex++ ) {
141                         for ( var i = 0; i < list.length; i++ ) {
142                             if ( args[ argIndex ] === list[ i ] ) {
143                                 // 在firing时移除函数,需要修正当前索引firingIndex和长度firingLength
144                                 if ( firing ) {
145                                     if ( i <= firingLength ) {
146                                         firingLength--;
147                                         if ( i <= firingIndex ) {
148                                             firingIndex--;
149                                         }
150                                     }
151                                 }
152                                 // Remove the element
153                                 list.splice( i--, 1 );
154                                 // 如果是unique模式(这时不会有重复的函数),移除一次就可以了
155                                 if ( flags.unique ) {
156                                     break;
157                                 }
158                             }
159                         }
160                     }
161                 }
162                 return this;
163             },
164             // 判断指定回调函数是否存在
165             has: function( fn ) {
166                 if ( list ) {
167                     var i = 0,
168                         length = list.length;
169                     for ( ; i < length; i++ ) {
170                         if ( fn === list[ i ] ) {
171                             return true;
172                         }
173                     }
174                 }
175                 return false;
176             },
177             // Remove all callbacks from the list
178             empty: function() {
179                 list = [];
180                 return this;
181             },
182             // Have the list do nothing anymore
183             disable: function() {
184                 list = stack = memory = undefined;
185                 return this;
186             },
187             // Is it disabled?
188             disabled: function() {
189                 return !list;
190             },
191             // Lock the list in its current state
192             lock: function() {
193                 stack = undefined;
194                 if ( !memory || memory === true ) { 
195                     self.disable();
196                 }
197                 return this;
198             },
199             // Is it locked?
200             locked: function() {
201                 return !stack;
202             },
203             // Call all callbacks with the given context and arguments
204             fireWith: function( context, args ) {
205                 if ( stack ) { // stack=[] 也是true  
206                     if ( firing ) {
207                         if ( !flags.once ) {
208                             stack.push( [ context, args ] );
209                         }
210                     } else if ( !( flags.once && memory ) ) {
211                         fire( context, args );
212                     }
213                 }
214                 return this;
215             },
216             // Call all the callbacks with the given arguments
217             fire: function() {
218                 self.fireWith( this, arguments );
219                 return this;
220             },
221             // To know if the callbacks have already been called at least once
222             fired: function() {
223                 return !!memory; // 其实这个有问题, 当调用disable() 的时候 memory==undefined
224             }
225         };
226 
227     return self;
228 };
229 
230 })( jQuery );

下面是一些检验这些参数逻辑的代码:

 1      /*
 2              jquery.Callbacks
 3          */
 4          // var testUrl="http://www.runoob.com/try/ajax/demo_test.php";
 5          // var callbacks=new jQuery.Callbacks("memory");
 6          // callbacks.add(function(){alert("first callback")});
 7          // callbacks.add(function(){alert("second callback")});
 8          // callbacks.fire(); 
 9          // memory
10         // callbacks.add(function(){alert("third callback")});            
11 
12 
13         var callback2=
14          jQuery.Callbacks("once memory"), // once 表示回调函数列表只会fire一次,但是还会运行memory机制,也不限制回调函数列表里边有多个相同的函数(可以用unique 去重)
15          something = true;
16         function fn1(args)
17         {
18              alert('fn1 args:'+args);
19              console.log(this);
20             if(something){
21                 callback2.fire(" test:第2次触发");
22                 callback2.fire(" test:第3次触发"); //fire内部再触发的话 先放入stack中 
23                 something=false;
24             }
25         }
26         function fn2(){
27             alert('fn2');
28         }        
29         callback2.add(fn1); 
30         callback2.add(fn2);
31 
32         callback2.fire('测试:第1次触发');
33         callback2.fire('测试:第4次触发');  //once模式 只会fire一次, memory在add的时候促发
34         callback2.add(fn2);
View Code

 源码地址

转载于:https://www.cnblogs.com/mushishi/p/5759307.html

/** * 数据管理器类,负责与后端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() { 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);<!doctype html> <html> <head data-genuitec="wc1-2-10"> <meta charset="utf-8" data-genuitec="wc1-2-11"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" data-genuitec="wc1-2-12"> <title data-genuitec="wc1-2-13">峤丞板材库存管理</title> <meta name="viewport" content="width=device-width, initial-scale=1" data-genuitec="wc1-2-14"> <link rel="stylesheet" type="text/css" href="fonts/font-awesome-4.7.0/css/font-awesome.min.css" data-genuitec="wc1-2-15"> <link rel="stylesheet" type="text/css" href="main/bootstrap-3.3.7-dist/css/bootstrap.css.map" data-genuitec="wc1-2-16"> <link rel="stylesheet" type="text/css" href="css/util.css" data-genuitec="wc1-2-17"> <link rel="stylesheet" type="text/css" href="css/main.css" data-genuitec="wc1-2-18"> <link rel="stylesheet" type="text/css" href="css/index2.css" data-genuitec="wc1-2-19"> <script type="text/javascript" src="js/jquery-3.2.1.min.js" data-genuitec="wc1-2-55"></script> <script type="text/javascript" src="js/jsyilai.js" data-genuitec="wc1-2-56"></script> <script type="module" data-genuitec="wc1-2-57"> // 共享的DataManager类 // 导入并创建 DataManager 单例 import { DataManager } from './data/DataManager.js'; // 创建单例实例并挂载到 window // window.dataManager = new DataManager('http://127.0.0.1:8080'); // console.log(dataManager) document.addEventListener('DOMContentLoaded', async () => { try { window.dataManager = new DataManager('http://127.0.0.1:8080/KuCun2'); await window.dataManager.fetchAll(); console.log('Data Manager initialized successfully'); const iframe = document.getElementById('iframeid'); iframe.onload = () => { console.log('Iframe loaded successfully'); iframe.contentWindow.postMessage('DataManagerReady', '*'); }; } catch (error) { console.error('Failed to initialize DataManager:', error); } }); </script> <style type="text/css" data-genuitec="wc1-2-20"> *{ 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> <script>"undefined"==typeof CODE_LIVE&&(!function(e){var t={nonSecure:"59356",secure:"59361"},c={nonSecure:"http://",secure:"https://"},r={nonSecure:"127.0.0.1",secure:"gapdebug.local.genuitec.com"},n="https:"===window.location.protocol?"secure":"nonSecure";script=e.createElement("script"),script.type="text/javascript",script.async=!0,script.src=c[n]+r[n]+":"+t[n]+"/codelive-assets/bundle.js",e.getElementsByTagName("head")[0].appendChild(script)}(document),CODE_LIVE=!0);</script></head> <body data-genuitec="wc1-2-8" data-genuitec-lp-enabled="true" data-genuitec-file-id="wc1-2" data-genuitec-path="/KuCun2/src/main/webapp/index.html"> <div class="frame-header" data-genuitec="wc1-2-9" data-genuitec-lp-enabled="true" data-genuitec-file-id="wc1-2" data-genuitec-path="/KuCun2/src/main/webapp/index.html"> <a class='frame-header-li' style="color:#fff" data-genuitec="wc1-2-21">峤丞木材仓库管理</a> <a id="username" class='frame-header-li' style="color:#520" data-genuitec="wc1-2-22">峤丞木材仓库管理</a> <ul class="menu" data-genuitec="wc1-2-23"> <li data-genuitec="wc1-2-24">选项 1</li> <li data-genuitec="wc1-2-25">选项 2</li> <li data-genuitec="wc1-2-26">选项 3</li> </ul> </div> <div class="frame-body" data-genuitec="wc1-2-27"> <div class="frame-side" data-genuitec="wc1-2-28"> <ul id="main_u" class='frame-ul' style="text-align:center;" data-genuitec="wc1-2-29"> <li data-genuitec="wc1-2-30"><a href="main/test.html" target="main" data-genuitec="wc1-2-31">首页</a></li> <li data-genuitec="wc1-2-32"><a href="main/dingdan.html" target="main" data-genuitec="wc1-2-33">订单查询</a></li> <li data-genuitec="wc1-2-34"><a href="main/bancai.html" target="main" data-genuitec="wc1-2-35">板材查询</a></li> <li data-genuitec="wc1-2-36"><a href="main/Guanli.html" target="main" data-genuitec="wc1-2-37">人员管理</a></li> <li data-genuitec="wc1-2-38"><a href="main/test.html" target="main" data-genuitec="wc1-2-39">test</a></li> <li data-genuitec="wc1-2-40"><a href="main/tianjia.html" target="main" data-genuitec="wc1-2-41">test</a></li> </ul> </div> <div class="frame-main" data-genuitec="wc1-2-42"> <iframe id="iframeid" name="main" src="main/bancai.html" width="100%" height="100%" frameborder="0" data-genuitec="wc1-2-43"> </iframe> </div> </div> </body> </html>Uncaught ReferenceError: dataManager is not defined at DataManager.js:241:21
06-15
------------------------/data/ 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() { 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}); } } // 创建单例实例 const dataManager = new DataManager(‘http://your-backend-url.com’); // 初始化时获取所有数据 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); ------------------------/js/ bancai.js ------------------------ $(document).ready(function() { const modal = new bootstrap.Modal(‘#bancaiModal’); let currentMode = ‘view’; // ‘view’, ‘edit’ 或 ‘add’ let caizhiList = []; // 存储材质列表 let mupiList = []; // 存储木皮列表 let currentSearchText = ‘’; // 当前搜索文本 // 导入 DataManager import { data, addEntity, updateEntity, deleteEntity, fetchAll } from '../js/DataManager.js'; // 初始化函数 async function initialize() { // 等待数据加载完成 await fetchAll(); // 更新材质和木皮选项 updateOptions(); // 渲染板材表格 refreshTable(); } // 更新材质和木皮选项 function updateOptions() { // 获取材质列表 caizhiList = data.caizhis; updateSelectOptions('#caizhiSelect', caizhiList); // 获取木皮列表并格式化名称 mupiList = 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 data.bancais; return data.bancais.filter(bancai => { const caizhiName = bancai.caizhi?.name || ''; const mupi1Name = bancai.mupi1?.name || ''; const mupi2Name = bancai.mupi2?.name || ''; const houdu = bancai.houdu.toString(); const searchFields = [ caizhiName.toLowerCase(), mupi1Name.toLowerCase(), mupi2Name.toLowerCase(), houdu ]; return searchFields.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 = 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'); // 更新选项(确保下拉框是最新的) updateOptions(); 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(); }); ------------------------/main/ 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> bancai.js:9 Uncaught SyntaxError: Unexpected token '{' (at bancai.js:9:12)
06-14
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值