解析 this.initialize.apply(this, arguments)

本文深入浅出地介绍了JavaScript中的面向对象编程概念,包括构造函数、原型链、initialize方法的作用及其实现方式,同时探讨了call()与apply()的区别。

原文地址:http://www.cnblogs.com/uedt/archive/2010/06/24/1764561.html

一、 起因
那天用到prototype.js于是打开看看,才看几行就满头雾水,原因是对js的面向对象不是很熟悉,于是百度+google了一把,最后终于算小有收获,写此纪念一下^_^。
prototype.js代码片段

代码如下:
var Class = {
    create: function() {
        return function() {
            this.initialize.apply(this , arguments);
        }
    }
}
// Class使用方法如下
var A = Class.create();
A. prototype={
    initialize:function(v){
        this .value=v;
    }
    showValue:function(){
        alert(this.value);
    }
}
var a = new A(‘helloWord!');
a. showValue();//弹出对话框helloWord!

l initialize是什么?
l apply方法是干什么的?
l arguments变量呢?
l 为什么new A后就会执行initialize方法?
寻找答案:

二、 Js的面向对象
initialize是什么?
只不过是个变量,代表一个方法,用途是类的构造函数。
其具体功能靠js的面向对象支持,那么js的面向对象是什么样子的那?和java 的有什么相同与不同?
看代码:

代码如下:
var ClassName = function(v){
    this.value=v;
    this.getValue=function(){
        return this.value;
    }
    this.setValue=function(v){
        this.value=v;
    }
}

那么JS中的函数和类有什么不同呢?
其实是一样的,ClassName就是一个函数,当出现在new后面的时候就作为一个构造函数来构造对象。

代码如下:
var objectName1 = new ClassName(“a”);//得到一个对象

其中objectName1就是执行ClassName构造函数后得到的对象,而在ClassName函数中的this指的就是new之后构造出来的对象,所以objectName1会后一个属性和两个方法。可以通过这样来调用他们:

代码如下:
objectName1.setValue(''hello'');
alert(objectName1.getValue());//对话框hello
alert(objectName1.value) ;//对话框hello

那么

复制代码 代码如下:
var objectName2 = ClassName(“b”);//得到一个对象

这样objectName2得到的是什么呢?显然是方法的返回值,这里ClassName只作为了一个普通的函数(虽然首字母大写了)。但是在之前写的ClassName中并没有返回值,所以objectName2会是undifinded那么“b”赋给谁了呢?在这并没有产生一个对象,而只是单纯的执行这个方法,所以这个“b”赋值给了调用这个方法的对象window,证据如下:
var objectName2 = ClassName(“b”);//得到一个对象
alert(window.value);//对话框b
所以JS中的所有function都是一样的,但是用途可能是不同的(用作构造对象抑或是执行一个过程)。
下面该回到主题了initialize是干什么的?

代码如下:
var Class = {
    create: function() {
        return function() {
            this.initialize.apply(this , arguments);
        }
    }
}
var A = Class.create();

这段代码是构造个一个function复制给A,这个function是

复制代码 代码如下:
function() {
    this.initialize.apply(this , arguments);
}

并且后面这个方法是用来做构造函数的。当使用这个构造函数来构造对象的时候,会让构造出来的这个对象的initialize变量执行apply()方法,apply()的用途后面在说,继续说initialize。这样在初始化对象的时候会联系到initialize(怎么联系就要看apply的了)。
那么

代码如下:
A.prototype={
    initialize:function(v){
        this .value=v;
    }
    showValue:function(){
        alert(this.value);
    }
}

是什么意思呢?
Prototype是“原型”的意思。A是一个function(),那么A. prototype,就是function中的一个变量,其实是个对象。这个对象拥有什么方法,那么function产生的对象就拥有什么方法,故
var a = new A(‘helloWord!');
a. showValue();//弹出对话框helloWord!
所以a对象也会有initialize方法,不只如此,每一个有A构造出来的对象都会有一个initialize方法,而在前面说过,构造的时候会调用构造函数,构造函数里面会让initialize去调用apply方法,于是在new A(‘helloWord!')的时候initialize回去调用apply方法。这也就是调用了一个初始化的方法。

三、 call()和apply()
下面开始研究apply(),在网上找了几个资料,并结合自己的研究,了解了call()和apply()的功能。功能基本一样,function().call(object,{},{}……)或者function().apply (object,[……])的功能就是对象object调用这里的funciton(),不同之处是call参数从第二个开始都是传递给funciton的,可以依次罗列用“,”隔开。而apply只有两个参数,第二个是一个数组,其中存储了所有传递给function的参数。
this.initialize.apply(this , arguments);
是什么意思?
这里的第一个this,是指用new调用构造函数之后生成的对象,也就是前面的a,那么第二个this也当然应该是指同一个对象。那这句话就是this(也就是a)调用initialize方法,参数是arguments对象(参数的数组对象),所以在构造函数执行的时候,对象a就会去执行initialize方法来初始化,这样就和单词“initialize”的意思对上了。
那么执行initialize方法的参数怎么传递进去的呢?

四、 Arguments对象
这段代码能说明一切了:

代码如下:
function test(){
    alert(typeof arguments);
    for(var i=0; i<arguments.length; i++){
        alert(arguments[i]);
    }
}
test("1","2","3");
test("a","b");

执行后alert(typeof arguments);会显示object,说明arguments是对象。然后会依次打出1、2、3。说明arguments就是调用函数的实参数组。

代码如下:
var Class = {
    create: function() {
        return function() {
            this.initialize.apply(this , arguments);
        }
    }
}

arguments 就是create返回的构造函数的实参数组,那么在
var a = new A(‘helloWord!');
的时候‘helloWord!'就是实参数组(虽然只有一个字符串),传递给方法apply,然后在调用initialize 的时候作为参数传递给初始化函数initialize。

 

Strophe.Builder = function (name, attrs) { // Holds the tree being built. this.nodeTree = this._makeNode(name, attrs); // Points to the current operation node. this.node = this.nodeTree; }; Strophe.Builder.prototype = { /** Function: tree * Return the DOM tree. * * This function returns the current DOM tree as an element object. This * is suitable for passing to functions like Strophe.Connection.send(). * * Returns: * The DOM tree as a element object. */ tree: function () { return this.nodeTree; }, /** Function: toString * Serialize the DOM tree to a String. * * This function returns a string serialization of the current DOM * tree. It is often used internally to pass data to a * Strophe.Request object. * * Returns: * The serialized DOM tree in a String. */ toString: function () { return Strophe.serialize(this.nodeTree); }, /** Function: up * Make the current parent element the new current element. * * This function is often used after c() to traverse back up the tree. * For example, to add two children to the same element * > builder.c('child1', {}).up().c('child2', {}); * * Returns: * The Stophe.Builder object. */ up: function () { this.node = this.node.parentNode; return this; }, /** Function: attrs * Add or modify attributes of the current element. * * The attributes should be passed in object notation. This function * does not move the current element pointer. * * Parameters: * (Object) moreattrs - The attributes to add/modify in object notation. * * Returns: * The Strophe.Builder object. */ attrs: function (moreattrs) { for (var k in moreattrs) this.node.setAttribute(k, moreattrs[k]); return this; }, /** Function: c * Add a child to the current element and make it the new current * element. * * This function moves the current element pointer to the child. If you * need to add another child, it is necessary to use up() to go back * to the parent in the tree. * * Parameters: * (String) name - The name of the child. * (Object) attrs - The attributes of the child in object notation. * * Returns: * The Strophe.Builder object. */ c: function (name, attrs) { var child = this._makeNode(name, attrs); this.node.appendChild(child); this.node = child; return this; }, /** Function: cnode * Add a child to the current element and make it the new current * element. * * This function is the same as c() except that instead of using a * name and an attributes object to create the child it uses an * existing DOM element object. * * Parameters: * (XMLElement) elem - A DOM element. * * Returns: * The Strophe.Builder object. */ cnode: function (elem) { this.node.appendChild(elem); this.node = elem; return this; }, /** Function: t * Add a child text element. * * This *does not* make the child the new current element since there * are no children of text elements. * * Parameters: * (String) text - The text data to append to the current element. * * Returns: * The Strophe.Builder object. */ t: function (text) { var child = Strophe.xmlTextNode(text); this.node.appendChild(child); return this; }, /** PrivateFunction: _makeNode * _Private_ helper function to create a DOM element. * * Parameters: * (String) name - The name of the new element. * (Object) attrs - The attributes for the new element in object * notation. * * Returns: * A new DOM element. */ _makeNode: function (name, attrs) { var node = Strophe.xmlElement(name); for (var k in attrs) node.setAttribute(k, attrs[k]); return node; } }; /** PrivateClass: Strophe.Handler * _Private_ helper class for managing stanza handlers. * * A Strophe.Handler encapsulates a user provided callback function to be * executed when matching stanzas are received by the connection. * Handlers can be either one-off or persistant depending on their * return value. Returning true will cause a Handler to remain active, and * returning false will remove the Handler. * * Users will not use Strophe.Handler objects directly, but instead they * will use Strophe.Connection.addHandler() and * Strophe.Connection.deleteHandler(). */ /** PrivateConstructor: Strophe.Handler * Create and initialize a new Strophe.Handler. * * Parameters: * (Function) handler - A function to be executed when the handler is run. * (String) ns - The namespace to match. * (String) name - The element name to match. * (String) type - The element type to match. * (String) id - The element id attribute to match. * (String) from - The element from attribute to match. * * Returns: * A new Strophe.Handler object. */ Strophe.Handler = function (handler, ns, name, type, id, from) { this.handler = handler; this.ns = ns; this.name = name; this.type = type; this.id = id; this.from = from; // whether the handler is a user handler or a system handler this.user = true; }; Strophe.Handler.prototype = { /** PrivateFunction: isMatch * Tests if a stanza matches the Strophe.Handler. * * Parameters: * (XMLElement) elem - The XML element to test. * * Returns: * true if the stanza matches and false otherwise. */ isMatch: function (elem) { var nsMatch, i; nsMatch = false; if (!this.ns) { nsMatch = true; } else { var self = this; Strophe.forEachChild(elem, null, function (elem) { if (elem.getAttribute("xmlns") == self.ns) nsMatch = true; }); nsMatch = nsMatch || elem.getAttribute("xmlns") == this.ns; } if (nsMatch && (!this.name || Strophe.isTagEqual(elem, this.name)) && (!this.type || elem.getAttribute("type") == this.type) && (!this.id || elem.getAttribute("id") == this.id) && (!this.from || elem.getAttribute("from") == this.from)) { return true; } return false; }, /** PrivateFunction: run * Run the callback on a matching stanza. * * Parameters: * (XMLElement) elem - The DOM element that triggered the * Strophe.Handler. * * Returns: * A boolean indicating if the handler should remain active. */ run: function (elem) { var result = null; try { result = this.handler(elem); } catch (e) { if (e.sourceURL) { Strophe.fatal("error: " + this.handler + " " + e.sourceURL + ":" + e.line + " - " + e.name + ": " + e.message); } else if (e.fileName) { if (typeof(console) != "undefined") { console.trace(); console.error(this.handler, " - error - ", e, e.message); } Strophe.fatal("error: " + this.handler + " " + e.fileName + ":" + e.lineNumber + " - " + e.name + ": " + e.message); } else { Strophe.fatal("error: " + this.handler); } throw e; } return result; }, /** PrivateFunction: toString * Get a String representation of the Strophe.Handler object. * * Returns: * A String. */ toString: function () { return "{Handler: " + this.handler + "(" + this.name + "," + this.id + "," + this.ns + ")}"; } }; /** PrivateClass: Strophe.TimedHandler * _Private_ helper class for managing timed handlers. * * A Strophe.TimedHandler encapsulates a user provided callback that * should be called after a certain period of time or at regular * intervals. The return value of the callback determines whether the * Strophe.TimedHandler will continue to fire. * * Users will not use Strophe.TimedHandler objects directly, but instead * they will use Strophe.Connection.addTimedHandler() and * Strophe.Connection.deleteTimedHandler(). */ /** PrivateConstructor: Strophe.TimedHandler * Create and initialize a new Strophe.TimedHandler object. * * Parameters: * (Integer) period - The number of milliseconds to wait before the * handler is called. * (Function) handler - The callback to run when the handler fires. This * function should take no arguments. * * Returns: * A new Strophe.TimedHandler object. */ Strophe.TimedHandler = function (period, handler) { this.period = period; this.handler = handler; this.lastCalled = new Date().getTime(); this.user = true; }; Strophe.TimedHandler.prototype = { /** PrivateFunction: run * Run the callback for the Strophe.TimedHandler. * * Returns: * true if the Strophe.TimedHandler should be called again, and false * otherwise. */ run: function () { this.lastCalled = new Date().getTime(); return this.handler(); }, /** PrivateFunction: reset * Reset the last called time for the Strophe.TimedHandler. */ reset: function () { this.lastCalled = new Date().getTime(); }, /** PrivateFunction: toString * Get a string representation of the Strophe.TimedHandler object. * * Returns: * The string representation. */ toString: function () { return "{TimedHandler: " + this.handler + "(" + this.period +")}"; } }; /** PrivateClass: Strophe.Request * _Private_ helper class that provides a cross implementation abstraction * for a BOSH related XMLHttpRequest. * * The Strophe.Request class is used internally to encapsulate BOSH request * information. It is not meant to be used from user's code. */ /** PrivateConstructor: Strophe.Request * Create and initialize a new Strophe.Request object. * * Parameters: * (String) data - The data to be sent in the request. * (Function) func - The function that will be called when the * XMLHttpRequest readyState changes. * (Integer) rid - The BOSH rid attribute associated with this request. * (Integer) sends - The number of times this same request has been * sent. */ Strophe.Request = function (data, func, rid, sends) { this.id = ++Strophe._requestId; this.data = data; // save original function in case we need to make a new request // from this one. this.origFunc = func; this.func = func; this.rid = rid; this.date = NaN; this.sends = sends || 0; this.abort = false; this.dead = null; this.age = function () { if (!this.date) return 0; var now = new Date(); return (now - this.date) / 1000; }; this.timeDead = function () { if (!this.dead) return 0; var now = new Date(); return (now - this.dead) / 1000; }; this.xhr = this._newXHR(); }; Strophe.Request.prototype = { /** PrivateFunction: getResponse * Get a response from the underlying XMLHttpRequest. * * This function attempts to get a response from the request and checks * for errors. * * Throws: * "parsererror" - A parser error occured. * * Returns: * The DOM element tree of the response. */ getResponse: function () { var node = null; if (this.xhr.responseXML && this.xhr.responseXML.documentElement) { node = this.xhr.responseXML.documentElement; if (node.tagName == "parsererror") { Strophe.error("invalid response received"); Strophe.error("responseText: " + this.xhr.responseText); Strophe.error("responseXML: " + Strophe.serialize(this.xhr.responseXML)); throw "parsererror"; } } else if (this.xhr.responseText) { Strophe.error("invalid response received"); Strophe.error("responseText: " + this.xhr.responseText); Strophe.error("responseXML: " + Strophe.serialize(this.xhr.responseXML)); } return node; }, /** PrivateFunction: _newXHR * _Private_ helper function to create XMLHttpRequests. * * This function creates XMLHttpRequests across all implementations. * * Returns: * A new XMLHttpRequest. */ _newXHR: function () { var xhr = null; if (window.XMLHttpRequest) { xhr = new XMLHttpRequest(); if (xhr.overrideMimeType) { xhr.overrideMimeType("text/xml"); } } else if (window.ActiveXObject) { xhr = new ActiveXObject("Microsoft.XMLHTTP"); } xhr.onreadystatechange = this.func.prependArg(this); return xhr; } };
08-08
/* 文件路径: data/DataManager.js */ /** * DataManager - 数据管理主类 * 整合所有功能模块并保持原有API不变 */ import { DataManagerCore } from './core/dataManagerCore.js'; import { createEntityAccessors } from './utils/entityAccessors.js'; import { checkDuplicate } from './utils/dataUtils.js'; /** * 数据管理器类 * 提供统一的数据访问、操作和同步接口 */ export class DataManager { constructor() { // 初始化核心管理器 this.core = new DataManagerCore(); // 混入实体访问器方法 const accessors = createEntityAccessors(this.core); Object.assign(this, accessors); // 绑定方法到当前实例 this.bindMethods(); } /** * 绑定方法到当前实例 */ bindMethods() { // 绑定核心方法 const coreMethods = [ 'initialize', 'createEmptyData', 'registerCallback', 'unregisterCallback', 'triggerCallbacks', 'crudOperation', 'updateLocalData', 'loadDataFromStorage', 'saveDataToStorage', 'startAutoSync', 'stopAutoSync', 'initNetwork', 'syncData', 'fetchAll', 'fetchEntityData', 'initWebSocket', 'handleWebSocketUpdate', 'refreshEntityData', 'updateWechatUser', 'transactionalOperation' ]; coreMethods.forEach(methodName => { if (typeof this.core[methodName] === 'function') { this[methodName] = this.core[methodName].bind(this.core); } }); // 绑定存储方法 const storageMethods = [ 'getEntityById', 'addEntity', 'updateEntity', 'deleteEntity', 'addEntities', 'clearEntities' ]; storageMethods.forEach(methodName => { if (typeof this.core.storage[methodName] === 'function') { this[methodName] = this.core.storage[methodName].bind(this.core.storage); } }); } /** * 获取数据访问器 */ get data() { return this.core.data; } /** * 从本地存储加载数据 * @returns {boolean} 加载是否成功 */ loadDataFromStorage() { return this.core.storage.loadDataFromStorage(); } /** * 保存数据到本地存储 * @returns {boolean} 保存是否成功 */ saveDataToStorage() { return this.core.storage.saveDataToStorage(); } /** * 初始化网络状态监听 */ initNetwork() { // 此方法已在DataSync初始化时调用 console.log('Network initialized'); } /** * 检查实体是否重复 * @param {string} entityType - 实体类型 * @param {Object} newEntity - 新实体 * @returns {boolean} 是否重复 */ checkDuplicate(entityType, newEntity) { const entities = this.core.storage.getEntities(entityType); return checkDuplicate(entities, newEntity); } // ===== 以下是为了保持向后兼容的额外方法 ===== /** * 获取特定类型的所有实体 * @param {string} entityType - 实体类型 * @returns {Array} 实体数组 */ getAllEntities(entityType) { return this.core.storage.getEntities(entityType); } /** * 批量更新实体 * @param {string} entityType - 实体类型 * @param {Array} entities - 实体数组 * @returns {Array} 更新结果数组 */ bulkUpdateEntities(entityType, entities) { return entities.map(entity => this.core.storage.updateEntity(entityType, entity)); } /** * 批量删除实体 * @param {string} entityType - 实体类型 * @param {Array} ids - 实体ID数组 * @returns {Array} 删除结果数组 */ bulkDeleteEntities(entityType, ids) { return ids.map(id => this.core.storage.deleteEntity(entityType, id)); } /** * 清空所有数据 */ clearAllData() { return this.core.storage.clearAllData(); } } // 创建默认实例 export const dataManager = new DataManager(); ================================================================================ /* 文件路径: data/core/dataManagerCore.js */ /** * DataManager核心类 * 整合所有组件并提供统一的数据管理接口 */ import { DataStorage } from './dataStorage.js'; import { DataSync } from './dataSync.js'; import { LazyLoader } from '../components/LazyLoader.js'; import { TransactionApi } from '../components/TransactionApi.js'; import { WebSocketClient } from '../components/WebSocketClient.js'; import { resolveDataReferences } from '../utils/dataUtils.js'; export class DataManagerCore { constructor() { // 初始化各个组件 this.storage = new DataStorage(); this.sync = new DataSync(this); this.lazyLoader = new LazyLoader(this); this.transactionApi = new TransactionApi(this); this.webSocketClient = new WebSocketClient(this); // 初始化回调管理 this.callbacks = {}; // 初始化数据 this.initialize(); } /** * 初始化数据管理器 */ initialize() { // 从本地存储加载数据 this.storage.loadDataFromStorage(); // 初始化WebSocket连接 this.initWebSocket(); } /** * 创建空数据对象 * @returns {Object} 空数据对象 */ createEmptyData() { return { dingdans: [], chanpins: [], zujians: [], bancais: [], cailiaos: [], xiaoshous: [], shengchans: [], xiaohaos: [], wechatUsers: [] }; } /** * 获取数据访问器 */ get data() { return this.storage.data; } // ===== 回调管理 ===== /** * 注册回调函数 * @param {string} eventType - 事件类型 * @param {Function} callback - 回调函数 * @returns {Function} 取消注册的函数 */ registerCallback(eventType, callback) { if (!this.callbacks[eventType]) { this.callbacks[eventType] = []; } this.callbacks[eventType].push(callback); // 返回取消注册的函数 return () => this.unregisterCallback(eventType, callback); } /** * 取消注册回调函数 * @param {string} eventType - 事件类型 * @param {Function} callback - 回调函数 */ unregisterCallback(eventType, callback) { if (!this.callbacks[eventType]) { return; } this.callbacks[eventType] = this.callbacks[eventType].filter(cb => cb !== callback); } /** * 触发回调函数 * @param {string} eventType - 事件类型 * @param {*} data - 回调数据 */ triggerCallbacks(eventType, data) { if (!this.callbacks[eventType]) { return; } this.callbacks[eventType].forEach(callback => { try { callback(data); } catch (error) { console.error(`Error in callback for ${eventType}:`, error); } }); } // ===== WebSocket相关 ===== /** * 初始化WebSocket连接 */ initWebSocket() { this.webSocketClient.connect(); } /** * 处理WebSocket更新 * @param {Object} updateData - 更新数据 */ handleWebSocketUpdate(updateData) { if (!updateData || !updateData.type) { return; } switch (updateData.type) { case 'entity_update': this.handleEntityUpdate(updateData); break; case 'entity_delete': this.handleEntityDelete(updateData); break; case 'sync_complete': this.triggerCallbacks('syncComplete', updateData); break; default: this.triggerCallbacks('ws_update', updateData); break; } } /** * 处理实体更新 * @param {Object} updateData - 更新数据 */ handleEntityUpdate(updateData) { const { entityType, entity } = updateData; if (entityType && entity) { this.updateEntity(entityType, entity); this.triggerCallbacks(`${entityType}_updated`, entity); } } /** * 处理实体删除 * @param {Object} updateData - 更新数据 */ handleEntityDelete(updateData) { const { entityType, entityId } = updateData; if (entityType && entityId) { this.deleteEntity(entityType, entityId); this.triggerCallbacks(`${entityType}_deleted`, { id: entityId }); } } // ===== 实体操作方法 ===== /** * 执行CRUD操作 * @param {string} operation - 操作类型 * @param {string} entityType - 实体类型 * @param {Object|Array} data - 操作数据 * @returns {Promise} 操作结果Promise */ async crudOperation(operation, entityType, data) { try { let result; switch (operation.toLowerCase()) { case 'create': case 'add': result = Array.isArray(data) ? this.storage.addEntities(entityType, data) : this.storage.addEntity(entityType, data); break; case 'read': case 'get': result = data ? this.storage.getEntityById(entityType, data) : this.storage.getEntities(entityType); break; case 'update': result = Array.isArray(data) ? data.map(item => this.storage.updateEntity(entityType, item)) : this.storage.updateEntity(entityType, data); break; case 'delete': case 'remove': result = Array.isArray(data) ? data.map(id => this.storage.deleteEntity(entityType, id)) : this.storage.deleteEntity(entityType, data); break; default: throw new Error(`Unknown operation: ${operation}`); } // 保存到本地存储 this.storage.saveDataToStorage(); // 触发回调 this.triggerCallbacks(`${entityType}_${operation}d`, result); return result; } catch (error) { console.error(`Error performing ${operation} on ${entityType}:`, error); throw error; } } /** * 更新本地数据 * @param {Object} newData - 新数据 */ updateLocalData(newData) { for (const entityType in newData) { if (Object.prototype.hasOwnProperty.call(newData, entityType)) { const entities = newData[entityType]; if (Array.isArray(entities)) { this.storage.addEntities(entityType, entities); } } } // 保存到本地存储 this.storage.saveDataToStorage(); } /** * 刷新实体数据 * @param {string} entityType - 实体类型 * @returns {Promise} 刷新结果Promise */ async refreshEntityData(entityType) { const data = await this.sync.fetchEntityData(entityType); this.triggerCallbacks(`${entityType}_refreshed`, data); return data; } /** * 执行事务操作 * @param {Function} operation - 操作函数 * @returns {Promise} 操作结果Promise */ transactionalOperation(operation) { return this.transactionApi.executeTransaction(operation); } /** * 更新微信用户信息 * @param {Object} userInfo - 用户信息 * @returns {Promise} 更新结果Promise */ async updateWechatUser(userInfo) { return this.crudOperation('update', 'wechatUsers', userInfo); } } ================================================================================ /* 文件路径: data/core/dataStorage.js */ /** * 数据存储核心类 * 提供数据的基本CRUD操作和本地存储功能 */ import { removeCircularReferences } from '../utils/dataUtils.js'; const STORAGE_KEY = 'inventory_data'; export class DataStorage { constructor() { this.data = { dingdans: [], chanpins: [], zujians: [], bancais: [], cailiaos: [], xiaoshous: [], shengchans: [], xiaohaos: [], wechatUsers: [] }; this.entityTypes = Object.keys(this.data); } /** * 获取特定类型的所有实体 * @param {string} entityType - 实体类型 * @returns {Array} 实体数组 */ getEntities(entityType) { if (!this.entityTypes.includes(entityType)) { console.warn(`Unknown entity type: ${entityType}`); return []; } return this.data[entityType] || []; } /** * 根据ID获取特定实体 * @param {string} entityType - 实体类型 * @param {string|number} id - 实体ID * @returns {Object|null} 实体对象或null */ getEntityById(entityType, id) { const entities = this.getEntities(entityType); return entities.find(entity => entity.id === id) || null; } /** * 添加实体 * @param {string} entityType - 实体类型 * @param {Object} entity - 实体对象 * @returns {Object} 添加后的实体对象 */ addEntity(entityType, entity) { if (!this.entityTypes.includes(entityType)) { console.warn(`Cannot add to unknown entity type: ${entityType}`); return entity; } if (!this.data[entityType]) { this.data[entityType] = []; } // 检查是否已存在相同ID的实体 const existingIndex = this.data[entityType].findIndex(e => e.id === entity.id); if (existingIndex >= 0) { // 如果已存在,更新它 this.data[entityType][existingIndex] = entity; } else { // 否则添加新实体 this.data[entityType].push(entity); } return entity; } /** * 更新实体 * @param {string} entityType - 实体类型 * @param {Object} entity - 实体对象 * @returns {boolean} 更新是否成功 */ updateEntity(entityType, entity) { if (!this.entityTypes.includes(entityType)) { console.warn(`Cannot update unknown entity type: ${entityType}`); return false; } if (!entity.id) { console.warn('Cannot update entity without ID'); return false; } const entities = this.data[entityType]; const index = entities.findIndex(e => e.id === entity.id); if (index === -1) { console.warn(`Entity not found: ${entityType} with ID ${entity.id}`); return false; } entities[index] = entity; return true; } /** * 删除实体 * @param {string} entityType - 实体类型 * @param {string|number} id - 实体ID * @returns {boolean} 删除是否成功 */ deleteEntity(entityType, id) { if (!this.entityTypes.includes(entityType)) { console.warn(`Cannot delete from unknown entity type: ${entityType}`); return false; } const entities = this.data[entityType]; const index = entities.findIndex(e => e.id === id); if (index === -1) { console.warn(`Entity not found: ${entityType} with ID ${id}`); return false; } entities.splice(index, 1); return true; } /** * 批量添加实体 * @param {string} entityType - 实体类型 * @param {Array} entities - 实体数组 * @returns {Array} 添加后的实体数组 */ addEntities(entityType, entities) { if (!Array.isArray(entities)) { console.warn('Entities must be an array'); return []; } return entities.map(entity => this.addEntity(entityType, entity)); } /** * 清空特定类型的所有实体 * @param {string} entityType - 实体类型 */ clearEntities(entityType) { if (!this.entityTypes.includes(entityType)) { console.warn(`Cannot clear unknown entity type: ${entityType}`); return; } this.data[entityType] = []; } /** * 从本地存储加载数据 * @returns {boolean} 加载是否成功 */ loadDataFromStorage() { try { const storedData = localStorage.getItem(STORAGE_KEY); if (storedData) { const parsedData = JSON.parse(storedData); // 合并存储的数据到当前数据对象 for (const entityType in parsedData) { if (Object.prototype.hasOwnProperty.call(parsedData, entityType) && this.entityTypes.includes(entityType)) { this.data[entityType] = parsedData[entityType]; } } return true; } } catch (error) { console.error('Failed to load data from storage:', error); } return false; } /** * 保存数据到本地存储 * @returns {boolean} 保存是否成功 */ saveDataToStorage() { try { // 移除循环引用 const dataToSave = removeCircularReferences(this.data); localStorage.setItem(STORAGE_KEY, JSON.stringify(dataToSave)); return true; } catch (error) { console.error('Failed to save data to storage:', error); } return false; } /** * 清空所有数据 */ clearAllData() { for (const entityType of this.entityTypes) { this.data[entityType] = []; } localStorage.removeItem(STORAGE_KEY); } } ================================================================================ /* 文件路径: data/core/dataSync.js */ /** * 数据同步核心类 * 负责处理数据与服务器的同步、自动同步和网络状态监听 */ export class DataSync { constructor(dataManager, autoSyncInterval = 30000) { this.dataManager = dataManager; this.autoSyncInterval = autoSyncInterval; // 默认30秒 this.syncTimer = null; this.isAutoSyncEnabled = false; this.isOnline = true; this.pendingOperations = []; // 初始化网络状态监听 this.initNetworkStatus(); } /** * 初始化网络状态监听 */ initNetworkStatus() { // 监听在线状态变化 window.addEventListener('online', () => this.handleNetworkChange(true)); window.addEventListener('offline', () => this.handleNetworkChange(false)); // 初始状态 this.isOnline = window.navigator.onLine; } /** * 处理网络状态变化 * @param {boolean} isOnline - 是否在线 */ handleNetworkChange(isOnline) { this.isOnline = isOnline; if (isOnline) { console.log('Network connection restored, syncing pending operations...'); // 当网络恢复时,同步待处理的操作 this.syncPendingOperations(); } else { console.log('Network connection lost'); } // 触发回调通知网络状态变化 this.dataManager.triggerCallbacks('networkStatusChange', { isOnline }); } /** * 同步待处理的操作 */ async syncPendingOperations() { while (this.pendingOperations.length > 0) { const operation = this.pendingOperations.shift(); try { await operation(); } catch (error) { console.error('Failed to sync pending operation:', error); // 如果同步失败,将操作重新添加到队列末尾 this.pendingOperations.push(operation); break; } } } /** * 开始自动同步 */ startAutoSync() { if (this.isAutoSyncEnabled) { return; } this.isAutoSyncEnabled = true; this.syncTimer = setInterval(() => { if (this.isOnline) { this.syncData(); } }, this.autoSyncInterval); console.log('Auto-sync started with interval:', this.autoSyncInterval); } /** * 停止自动同步 */ stopAutoSync() { if (this.syncTimer) { clearInterval(this.syncTimer); this.syncTimer = null; } this.isAutoSyncEnabled = false; console.log('Auto-sync stopped'); } /** * 同步数据到服务器 * @returns {Promise} 同步结果Promise */ async syncData() { if (!this.isOnline) { console.warn('Cannot sync data while offline'); return false; } try { // 收集所有需要同步的数据 const syncData = this.collectSyncData(); if (Object.keys(syncData).length === 0) { console.log('No data to sync'); return true; } const response = await fetch('/api/sync', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(syncData) }); if (!response.ok) { throw new Error(`Sync failed with status: ${response.status}`); } const result = await response.json(); // 处理同步结果 this.handleSyncResult(result); console.log('Data synced successfully'); return true; } catch (error) { console.error('Error syncing data:', error); // 如果同步失败,将操作添加到待处理队列 this.pendingOperations.push(() => this.syncData()); return false; } } /** * 收集需要同步的数据 * @returns {Object} 同步数据对象 */ collectSyncData() { const syncData = {}; // 在实际应用中,这里应该只收集需要同步的变更数据 // 为简化实现,我们收集所有数据 for (const entityType of this.dataManager.storage.entityTypes) { const entities = this.dataManager.storage.getEntities(entityType); if (entities.length > 0) { syncData[entityType] = entities; } } return syncData; } /** * 处理同步结果 * @param {Object} result - 同步结果 */ handleSyncResult(result) { // 在实际应用中,这里应该根据服务器返回的结果更新本地数据 // 例如,处理冲突、应用服务器端的变更等 if (result.success && result.updatedEntities) { // 应用服务器端的更新 for (const entityType in result.updatedEntities) { const updatedEntities = result.updatedEntities[entityType]; updatedEntities.forEach(entity => { this.dataManager.storage.updateEntity(entityType, entity); }); } } } /** * 从服务器获取数据 * @param {Array} entityTypes - 要获取的实体类型数组 * @returns {Promise} 获取结果Promise */ async fetchData(entityTypes) { if (!this.isOnline) { console.warn('Cannot fetch data while offline'); return false; } try { const response = await fetch('/api/data', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ entityTypes }) }); if (!response.ok) { throw new Error(`Fetch data failed with status: ${response.status}`); } const data = await response.json(); // 处理获取的数据 this.handleFetchedData(data); return true; } catch (error) { console.error('Error fetching data:', error); return false; } } /** * 处理获取的数据 * @param {Object} data - 获取的数据 */ handleFetchedData(data) { // 将获取的数据保存到存储中 for (const entityType in data) { if (Object.prototype.hasOwnProperty.call(data, entityType)) { const entities = data[entityType]; if (Array.isArray(entities)) { this.dataManager.storage.clearEntities(entityType); this.dataManager.storage.addEntities(entityType, entities); } } } } /** * 获取所有实体数据 * @returns {Promise} 获取结果Promise */ fetchAll() { return this.fetchData(this.dataManager.storage.entityTypes); } /** * 获取特定实体类型的数据 * @param {string} entityType - 实体类型 * @param {Object} filters - 过滤条件 * @returns {Promise} 获取结果Promise */ async fetchEntityData(entityType, filters = {}) { if (!this.isOnline) { console.warn('Cannot fetch entity data while offline'); return []; } try { const response = await fetch(`/api/data/${entityType}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(filters) }); if (!response.ok) { throw new Error(`Fetch ${entityType} data failed with status: ${response.status}`); } const data = await response.json(); // 将获取的数据保存到存储中 if (Array.isArray(data)) { this.dataManager.storage.clearEntities(entityType); this.dataManager.storage.addEntities(entityType, data); return data; } return []; } catch (error) { console.error(`Error fetching ${entityType} data:`, error); return []; } } } ================================================================================ /* 文件路径: data/utils/dataUtils.js */ /** * 数据工具函数 * 提供数据引用解析、循环引用处理等功能 */ /** * 解析数据对象中的所有引用 * @param {Object} data - 要解析的数据对象 * @param {Object} entitiesMap - 实体映射表 * @returns {Object} 解析后的对象 */ export function resolveDataReferences(data, entitiesMap) { if (!data || typeof data !== 'object') { return data; } if (Array.isArray(data)) { return data.map(item => resolveDataReferences(item, entitiesMap)); } const result = {}; for (const key in data) { if (Object.prototype.hasOwnProperty.call(data, key)) { result[key] = resolveDataReference(data[key], entitiesMap); } } return result; } /** * 解析单个数据引用 * @param {*} value - 要解析的值 * @param {Object} entitiesMap - 实体映射表 * @returns {*} 解析后的值 */ export function resolveDataReference(value, entitiesMap) { if (!value || typeof value !== 'object') { return value; } if (Array.isArray(value)) { return value.map(item => resolveDataReference(item, entitiesMap)); } // 检查是否是引用对象 if (value.__ref && value.entityType && entitiesMap[value.entityType]) { const entity = entitiesMap[value.entityType].find(e => e.id === value.__ref); return entity || value; } // 递归处理嵌套对象 const result = {}; for (const key in value) { if (Object.prototype.hasOwnProperty.call(value, key)) { result[key] = resolveDataReference(value[key], entitiesMap); } } return result; } /** * 移除对象中的循环引用 * @param {Object} obj - 要处理的对象 * @returns {Object} 处理后的对象 */ export function removeCircularReferences(obj) { const seen = new WeakSet(); const replacer = (key, value) => { if (typeof value === 'object' && value !== null) { if (seen.has(value)) { return undefined; } seen.add(value); } return value; }; return JSON.parse(JSON.stringify(obj, replacer)); } /** * 检查实体是否重复 * @param {Array} entities - 实体数组 * @param {Object} newEntity - 新实体 * @returns {boolean} 是否重复 */ export function checkDuplicate(entities, newEntity) { if (!entities || !Array.isArray(entities)) { return false; } return entities.some(entity => entity.id === newEntity.id); } ================================================================================ /* 文件路径: data/utils/entityAccessors.js */ /** * 实体访问器函数 * 提供各种实体数据的访问方法 */ import { resolveDataReferences } from './dataUtils.js'; /** * 创建实体访问器方法 * @param {DataManagerCore} dataManager - 数据管理器实例 * @returns {Object} 访问器方法对象 */ export function createEntityAccessors(dataManager) { const accessors = {}; // 创建实体访问方法 const entityTypes = [ 'dingdans', 'chanpins', 'zujians', 'bancais', 'cailiaos', 'xiaoshous', 'shengchans', 'xiaohaos', 'wechatUsers' ]; // 创建同步访问方法 entityTypes.forEach(entityType => { const methodName = `get${capitalize(entityType)}`; accessors[methodName] = function() { const entities = dataManager.storage.getEntities(entityType); return dataManager.lazyLoader.proxyEntities(entities, entityType); }; }); // 创建异步访问方法 entityTypes.forEach(entityType => { const methodName = `get${capitalize(entityType)}Async`; accessors[methodName] = async function(filters = {}) { // 先尝试从缓存获取 let entities = dataManager.storage.getEntities(entityType); // 如果没有数据或强制刷新,则从服务器获取 if (entities.length === 0 || filters.forceRefresh) { entities = await dataManager.sync.fetchEntityData(entityType, filters); } // 应用过滤器 if (filters) { entities = applyFilters(entities, filters); } return dataManager.lazyLoader.proxyEntities(entities, entityType); }; }); // 创建关联数据查询方法 accessors.getChanpinsForDingdan = function(dingdanId) { const chanpins = dataManager.storage.getEntities('chanpins'); return chanpins.filter(chanpin => chanpin.dingdanId === dingdanId); }; accessors.getZujiansForChanpin = function(chanpinId) { const zujians = dataManager.storage.getEntities('zujians'); return zujians.filter(zujian => zujian.chanpinId === chanpinId); }; accessors.getShengchanXiaohaoRecords = function(params = {}) { const { startDate, endDate, chanpinId } = params; const shengchans = dataManager.storage.getEntities('shengchans'); const xiaohaos = dataManager.storage.getEntities('xiaohaos'); // 合并生产和消耗记录 let records = [...shengchans, ...xiaohaos]; // 应用日期过滤 if (startDate) { records = records.filter(record => new Date(record.date) >= new Date(startDate)); } if (endDate) { records = records.filter(record => new Date(record.date) <= new Date(endDate)); } // 应用产品过滤 if (chanpinId) { records = records.filter(record => record.chanpinId === chanpinId); } // 按日期排序 records.sort((a, b) => new Date(a.date) - new Date(b.date)); return records; }; accessors.getShengchanStatistics = function(params = {}) { const { startDate, endDate } = params; const shengchans = dataManager.storage.getEntities('shengchans'); // 应用日期过滤 let filteredShengchans = shengchans; if (startDate) { filteredShengchans = filteredShengchans.filter(record => new Date(record.date) >= new Date(startDate)); } if (endDate) { filteredShengchans = filteredShengchans.filter(record => new Date(record.date) <= new Date(endDate)); } // 计算统计数据 const statistics = {}; filteredShengchans.forEach(record => { if (!statistics[record.chanpinId]) { statistics[record.chanpinId] = { chanpinId: record.chanpinId, totalQuantity: 0, records: [] }; } statistics[record.chanpinId].totalQuantity += record.quantity; statistics[record.chanpinId].records.push(record); }); // 转换为数组并添加产品名称 const chanpinsMap = createEntityMap(dataManager.storage.getEntities('chanpins')); return Object.values(statistics).map(stat => { const chanpin = chanpinsMap[stat.chanpinId]; return { ...stat, chanpinName: chanpin ? chanpin.name : 'Unknown' }; }); }; return accessors; } /** * 首字母大写 * @param {string} str - 字符串 * @returns {string} 首字母大写的字符串 */ function capitalize(str) { return str.charAt(0).toUpperCase() + str.slice(1); } /** * 应用过滤器 * @param {Array} entities - 实体数组 * @param {Object} filters - 过滤条件 * @returns {Array} 过滤后的实体数组 */ function applyFilters(entities, filters) { return entities.filter(entity => { for (const key in filters) { if (key !== 'forceRefresh' && filters[key] !== undefined) { if (entity[key] !== filters[key]) { return false; } } } return true; }); } /** * 创建实体映射 * @param {Array} entities - 实体数组 * @returns {Object} 实体ID到实体的映射 */ function createEntityMap(entities) { const map = {}; entities.forEach(entity => { map[entity.id] = entity; }); return map; } ================================================================================ /* 文件路径: data/index.js */ /** * 数据管理模块入口 * 导出所有公共API,保持向后兼容 */ import { DataManager, dataManager } from './DataManager.js'; import { DataStorage } from './core/dataStorage.js'; import { DataSync } from './core/dataSync.js'; import { LazyLoader } from './components/LazyLoader.js'; import { TransactionApi } from './components/TransactionApi.js'; import { WebSocketClient } from './components/WebSocketClient.js'; import { resolveDataReferences, resolveDataReference, removeCircularReferences, checkDuplicate } from './utils/dataUtils.js'; // 导出所有公共API export { DataManager, dataManager, DataStorage, DataSync, LazyLoader, TransactionApi, WebSocketClient, resolveDataReferences, resolveDataReference, removeCircularReferences, checkDuplicate }; // 导出默认模块 export default dataManager; ================================================================================ /* 文件路径: data/LazyLoader.js */ /** * 懒加载器 - Web版 (优化版) * 修复了缓存键设计、集合名解析和数组处理问题 * 负责为数据对象创建代理,实现关联数据的延迟加载 * 支持按需加载机制 */ class LazyLoader { /** * 构造函数 * @param {DataManager} dataManager - 数据管理器实例 */ constructor(dataManager) { this.dataManager = dataManager; // 数据管理器引用 // 使用WeakMap避免内存泄漏 this.proxyCache = new WeakMap(); // 代理对象缓存,避免重复创建代理 // 实体名到集合名的映射表 this.entityToCollectionMap = { bancai: 'bancais', dingdan: 'dingdans', mupi: 'mupis', chanpin: 'chanpins', kucun: 'kucuns', chanpin_zujian: 'chanpin_zujians', dingdan_bancai: 'dingdan_bancais', zujian: 'zujians', caizhi: 'caizhis', dingdan_chanpin: 'dingdan_chanpins', user: 'users', jinhuo: 'jinhuos' }; // 集合名到实体名的映射表(反向映射) this.collectionToEntityMap = {}; for (const [entity, collection] of Object.entries(this.entityToCollectionMap)) { this.collectionToEntityMap[collection] = entity; } } /** * 清除所有缓存 * 用于数据刷新后重置所有缓存状态 */ clearCache() { this.proxyCache = new WeakMap(); // 重新创建WeakMap以清空缓存 } /** * 创建数据代理 * 为对象添加延迟加载关联数据的能力 * @param {Object} item - 原始数据项 * @param {string} entityType - 实体类型 * @returns {Proxy} 具有延迟加载能力的代理对象 */ createProxy(item, entityType) { // 非对象直接返回 if (!item || typeof item !== 'object') return item; // 检查是否已有代理 if (this.proxyCache.has(item)) { return this.proxyCache.get(item); } const proxy = new Proxy(item, { get: (target, prop) => { // 直接返回基本属性、符号属性或以下划线开头的属性 if (typeof prop === 'symbol' || prop.startsWith('_') || typeof target[prop] !== 'object') { return target[prop]; } const value = target[prop]; // 处理null值 if (value === null) return null; // 处理数组关联 - 对数组中的每个元素进行关系解析 if (Array.isArray(value)) { return value.map(relatedItem => this.resolveRelation(relatedItem, prop) ); } // 处理对象关联 - 对单个对象进行关系解析 else if (typeof value === 'object') { return this.resolveRelation(value, prop); } return value; }, set: (target, prop, value) => { // 允许设置属性值 target[prop] = value; return true; } }); // 缓存代理对象,避免重复创建 this.proxyCache.set(item, proxy); return proxy; } /** * 解析关联对象 * 根据属性名和关联项获取完整的关联对象 * 支持按需加载关联数据 * @param {Object|string|number} relatedItem - 关联项 * @param {string} propName - 属性名 * @returns {Object} 解析后的关联对象 */ resolveRelation(relatedItem, propName) { // 如果关联项是简单的ID值 if (typeof relatedItem === 'string' || typeof relatedItem === 'number') { // 从属性名推断关联的实体类型 const entityType = this.getEntityTypeFromPropName(propName); if (!entityType) return relatedItem; // 查找对应的实体 const collectionName = this.entityToCollectionMap[entityType.replace(/s$/, '')] || entityType; const collection = this.dataManager._rawData[collectionName]; // 如果集合不存在或为空,但存在关联ID,则返回原始ID(按需加载时可能发生) if (!collection || !Array.isArray(collection) || collection.length === 0) { return relatedItem; } // 查找匹配的实体 const matchedEntity = collection.find(item => item.id === relatedItem); return matchedEntity ? this.createProxy(matchedEntity, entityType.replace(/s$/, '')) : relatedItem; } // 如果关联项是对象,创建代理 if (typeof relatedItem === 'object' && relatedItem !== null) { // 从属性名推断实体类型 const entityType = this.getEntityTypeFromPropName(propName); if (entityType) { return this.createProxy(relatedItem, entityType.replace(/s$/, '')); } return this.createProxy(relatedItem, null); } return relatedItem; } /** * 从集合名获取实体类型 * @param {string} collectionName - 集合名称 * @returns {string|null} 实体类型名称 */ getEntityTypeFromCollection(collectionName) { return this.collectionToEntityMap[collectionName] || null; } /** * 从属性名获取实体类型 * @param {string} propName - 属性名称 * @returns {string|null} 实体类型名称 */ getEntityTypeFromPropName(propName) { // 处理常见的关联属性命名模式 if (propName.endsWith('Id') || propName.endsWith('_id')) { // 例如: userId -> user, dingdan_id -> dingdan const baseName = propName.replace(/Id$/, '').replace(/_id$/, ''); return this.entityToCollectionMap[baseName.toLowerCase()] || null; } // 直接查找属性名对应的实体类型 const entityType = this.collectionToEntityMap[propName]; if (entityType) { return this.entityToCollectionMap[entityType]; } // 尝试单复数转换 if (propName.endsWith('s')) { const singular = propName.slice(0, -1); return this.entityToCollectionMap[singular] || null; } return null; } } export { LazyLoader }; ================================================================================ /* 文件路径: data/components/LazyLoader.js */ /** * 延迟加载器组件 * 为数据对象创建代理,实现关联数据的延迟加载 */ export class LazyLoader { constructor(dataManager) { this.dataManager = dataManager; } /** * 为对象创建延迟加载代理 * @param {Object} obj - 要代理的对象 * @param {string} entityType - 实体类型 * @returns {Proxy} 代理对象 */ createProxy(obj, entityType) { const self = this; return new Proxy(obj, { get(target, property) { // 正常属性访问 if (property in target) { return target[property]; } // 处理延迟加载属性 if (property.startsWith('get') && property.endsWith('Async')) { const methodName = property; return function() { return self.dataManager[methodName].apply(self.dataManager, arguments); }; } // 尝试加载关联数据 return self.loadRelatedData(target, property, entityType); }, set(target, property, value) { target[property] = value; return true; } }); } /** * 加载关联数据 * @param {Object} entity - 实体对象 * @param {string} relationName - 关联名称 * @param {string} entityType - 实体类型 * @returns {Promise|undefined} 关联数据或undefined */ loadRelatedData(entity, relationName, entityType) { // 检查是否有对应的关联数据加载方法 const relationMethodMap = { 'chanpins': 'getChanpinsForDingdan', 'zujians': 'getZujiansForChanpin', 'dingdan': 'getDingdanForChanpin', 'bancai': 'getBancaiForZuJian' // 可以添加更多的关联映射 }; const methodName = relationMethodMap[relationName]; if (methodName && this.dataManager[methodName]) { // 返回一个Promise,用于异步加载数据 return this.dataManager[methodName](entity.id); } return undefined; } /** * 批量为实体数组创建代理 * @param {Array} entities - 实体数组 * @param {string} entityType - 实体类型 * @returns {Array} 代理对象数组 */ proxyEntities(entities, entityType) { if (!Array.isArray(entities)) { return entities; } return entities.map(entity => this.createProxy(entity, entityType)); } } ================================================================================ /* 文件路径: data/TransactionApi.js */ // @ts-nocheck /** * 事务API类 * 提供复合数据操作的事务支持 * 处理需要原子性执行的多个相关操作 * 支持按需加载机制 */ class TransactionApi { /** * 构造函数 * @param {DataManager} dataManager - 数据管理器实例 */ constructor(dataManager) { this.dataManager = dataManager; // 数据管理器引用,用于访问数据和基本操作 this.baseUrl = dataManager.baseUrl; } /** * 执行事务操作 * 根据端点名称调用对应的事务接口 * @param {string} endpoint - 事务端点名称 * @param {Object} data - 事务数据 * @returns {Promise<Object>} 事务执行结果 */ async execute(endpoint, data) { try { // 调用API执行事务 const response = await fetch(`${this.baseUrl}/app/transactional/${endpoint}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(data) }); // 检查响应状态 if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } // 解析响应结果 const result = await response.json(); if (result.status !== 200) { throw new Error(result.text || 'Transaction failed'); } // 事务成功后,根据端点类型决定是否需要刷新特定实体数据 // 不再同步所有数据,而是只刷新受影响的数据 await this.refreshRelatedData(endpoint, result.data); // 触发事务成功回调 this.dataManager.triggerCallbacks('transaction_success', endpoint, result.data); return result.data; } catch (error) { console.error(`Transaction ${endpoint} failed:`, error); // 触发事务失败回调 this.dataManager.triggerCallbacks('transaction_error', endpoint, { data, error: error.message }); throw error; } } /** * 根据事务端点和结果刷新相关数据 * 按需刷新,而不是刷新所有数据 * @param {string} endpoint - 事务端点名称 * @param {Object} resultData - 事务结果数据 * @returns {Promise<void>} */ async refreshRelatedData(endpoint, resultData) { try { // 根据端点名称确定需要刷新的实体类型 const entitiesToRefresh = this.getEntitiesToRefresh(endpoint); // 刷新受影响的实体数据 for (const entityType of entitiesToRefresh) { // 标记实体类型为未加载,下次访问时会重新获取 if (this.dataManager.loadedEntities) { this.dataManager.loadedEntities.delete(entityType); } } // 清除懒加载缓存 if (this.dataManager.lazyLoader) { this.dataManager.lazyLoader.clearCache(); } } catch (error) { console.error('Failed to refresh related data:', error); // 失败时不抛出异常,避免影响事务整体结果 } } /** * 根据事务端点确定需要刷新的实体类型 * @param {string} endpoint - 事务端点名称 * @returns {Array<string>} 需要刷新的实体类型列表 */ getEntitiesToRefresh(endpoint) { // 定义事务端点与影响实体类型的映射关系 const endpointToEntitiesMap = { 'kucunbianji': ['kucun', 'jinhuo'], // 库存编辑影响库存和进货记录 'shengchanXiaohao': ['kucun', 'jinhuo', 'dingdan_bancai'], // 生产消耗影响库存、进货记录和订单板材 'batchUpdateDingdan_bancai': ['dingdan_bancai'], // 批量更新订单板材 'submitDingdan_bancai': ['dingdan_bancai', 'kucun'], // 提交订单板材影响订单板材和库存 'consumeDingdan_bancai': ['dingdan_bancai', 'kucun', 'jinhuo'], // 消耗订单板材影响订单板材、库存和进货记录 '采购Dingdan_bancai': ['dingdan_bancai', 'kucun', 'jinhuo'] // 采购订单板材影响订单板材、库存和进货记录 }; // 返回与端点相关的实体类型列表,如果没有匹配项则返回空数组 return endpointToEntitiesMap[endpoint] || []; } /** * 库存编辑事务 * 更新指定材料的库存数量 * @param {Object} params - 事务参数 * @param {Number} params.kucunId - 库存ID * @param {Number} params.newStock - 新的库存数量 * @param {Number} params.oldStock - 原库存数量 * @param {String} params.note - 备注信息 * @param {Number} params.userId - 用户ID * @returns {Promise<Object>} 操作结果 */ async updateStock(params) { return this.execute('kucunbianji', params); } /** * 生产消耗事务 * 记录生产过程中消耗的材料 * @param {Object} params - 生产消耗参数 * @returns {Promise<Object>} 操作结果 */ async shengchanXiaohao(params) { return this.execute('shengchanXiaohao', params); } /** * 批量更新订单板材事务 * 同时更新多个订单中的板材信息 * @param {Object} params - 批量更新参数 * @returns {Promise<Object>} 操作结果 */ async batchUpdateDingdan_bancai(params) { return this.execute('batchUpdateDingdan_bancai', params); } /** * 提交订单板材采购事务 * 将订单板材提交到采购流程 * @param {Object} params - 提交参数 * @returns {Promise<Object>} 操作结果 */ async submitDingdan_bancai(params) { return this.execute('submitDingdan_bancai', params); } /** * 消耗订单板材事务 * 标记订单中的板材已被消耗 * @param {Object} params - 消耗参数 * @returns {Promise<Object>} 操作结果 */ async consumeDingdan_bancai(params) { return this.execute('consumeDingdan_bancai', params); } /** * 采购订单板材事务 * 执行订单板材的采购操作 * @param {Object} params - 采购参数 * @returns {Promise<Object>} 操作结果 */ async 采购Dingdan_bancai(params) { return this.execute('采购Dingdan_bancai', params); } /** * 保存所有数据事务 * 批量保存多种类型的数据到服务器 * @param {Object} params - 包含所有需要保存数据的对象 * @returns {Promise<Object>} 事务执行结果 */ async saveAll(params) { try { // 调用API执行批量保存 const response = await fetch(`${this.baseUrl}/app/save-all`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(params) }); // 检查响应状态 if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } // 解析响应结果 const result = await response.json(); if (result.status !== 200) { throw new Error(result.text || 'Save all failed'); } // 保存成功后刷新数据 await this.dataManager.syncData(); return result.data; } catch (error) { console.error('Save all failed:', error); throw error; } } } export { TransactionApi }; ================================================================================ /* 文件路径: data/components/TransactionApi.js */ /** * 事务API组件 * 提供复合数据操作的事务支持 */ export class TransactionApi { constructor(dataManager) { this.dataManager = dataManager; } /** * 执行事务操作 * @param {Function} operation - 要执行的操作函数 * @returns {Promise} 事务结果Promise */ async executeTransaction(operation) { try { // 标记事务开始 this.beginTransaction(); // 执行操作 const result = await operation(); // 提交事务 this.commitTransaction(); return result; } catch (error) { // 回滚事务 this.rollbackTransaction(); throw error; } } /** * 开始事务 */ beginTransaction() { // 在实际应用中,这里可能需要保存当前状态以便回滚 // 对于这个简单实现,我们只是记录事务开始 this.isInTransaction = true; this.transactionOperations = []; } /** * 提交事务 */ commitTransaction() { // 执行所有事务操作 this.transactionOperations.forEach(op => { if (typeof op === 'function') { try { op(); } catch (error) { console.error('Transaction operation failed:', error); } } }); // 清空事务状态 this.isInTransaction = false; this.transactionOperations = []; } /** * 回滚事务 */ rollbackTransaction() { // 在实际应用中,这里需要恢复到事务开始前的状态 // 对于这个简单实现,我们只是清空事务状态 this.isInTransaction = false; this.transactionOperations = []; } /** * 向事务中添加操作 * @param {Function} operation - 要添加的操作函数 */ addOperation(operation) { if (this.isInTransaction) { this.transactionOperations.push(operation); } } /** * 执行复合数据更新 * @param {Object} updateData - 更新数据对象 * @returns {Promise} 更新结果Promise */ async updateMultipleEntities(updateData) { return this.executeTransaction(async () => { const results = {}; // 处理不同类型的实体更新 for (const entityType in updateData) { if (Object.prototype.hasOwnProperty.call(updateData, entityType)) { const entities = updateData[entityType]; if (Array.isArray(entities)) { results[entityType] = await this.updateEntitiesOfType(entityType, entities); } } } return results; }); } /** * 更新特定类型的实体 * @param {string} entityType - 实体类型 * @param {Array} entities - 要更新的实体数组 * @returns {Promise} 更新结果Promise */ async updateEntitiesOfType(entityType, entities) { const results = []; for (const entity of entities) { if (entity.id) { // 已存在的实体,执行更新 const result = await this.dataManager.updateEntity(entityType, entity); results.push(result); } else { // 新实体,执行添加 const result = await this.dataManager.addEntity(entityType, entity); results.push(result); } } return results; } } ================================================================================ /* 文件路径: data/WebSocketClient.js */ /** * WebSocket客户端类 * 负责连接WebSocket服务器并处理实体更新通知 * 注意:使用前需要在HTML页面中引入SockJS和Stomp客户端库 * <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script> * <script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script> */ export class WebSocketClient { constructor() { // WebSocket连接实例 this.socket = null; this.stompClient = null; // 消息处理回调函数 this.onEntityUpdate = null; this.onConnectionStatusChange = null; // 连接状态标志 this.isConnected = false; // 检查SockJS和Stomp客户端库是否已加载 if (typeof window.SockJS === 'undefined') { console.warn('SockJS客户端库未加载,请在HTML页面中引入SockJS库'); } if (typeof window.Stomp === 'undefined') { console.warn('Stomp客户端库未加载,请在HTML页面中引入Stomp库'); } } /** * 连接WebSocket服务器 */ connect() { // 构建WebSocket服务器URL const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'; const wsUrl = `${wsProtocol}//${window.location.host}${window.location.pathname}ws`; // 使用SockJS客户端连接 this.socket = new SockJS(wsUrl); const stompClient = Stomp.over(this.socket); // 设置调试标志 stompClient.debug = null; // 禁用调试输出 // 连接回调 stompClient.connect({}, // 连接成功回调 () => { console.log('WebSocket连接成功,开始接收实时数据更新'); this.isConnected = true; // 触发连接状态变化回调 if (this.onConnectionStatusChange) { this.onConnectionStatusChange(true); } // 订阅实体更新频道 stompClient.subscribe('/topic/entity-updates', (message) => { this.handleEntityUpdate(message); }); }, // 连接错误/断开回调 (error) => { console.error('WebSocket连接失败:', error); this.isConnected = false; // 触发连接状态变化回调 if (this.onConnectionStatusChange) { this.onConnectionStatusChange(false); } // 尝试重新连接 setTimeout(() => { console.log('尝试重新连接WebSocket...'); this.connect(); }, 5000); // 5秒后重试 } ); // 保存Stomp客户端引用 this.stompClient = stompClient; } /** * 断开WebSocket连接 */ disconnect() { if (this.stompClient) { this.stompClient.disconnect(() => { console.log('WebSocket连接已断开'); this.isConnected = false; if (this.onConnectionStatusChange) { this.onConnectionStatusChange(false); } }); } } /** * 处理接收到的实体更新消息 */ handleEntityUpdate(message) { try { // 解析消息内容 const updateData = JSON.parse(message.body); // 触发实体更新回调 if (this.onEntityUpdate) { this.onEntityUpdate(updateData); } } catch (error) { console.error('解析实体更新消息失败:', error); } } /** * 获取当前连接状态 */ getConnectionStatus() { return this.isConnected; } } ================================================================================ /* 文件路径: data/components/WebSocketClient.js */ /** * WebSocket客户端组件 * 负责连接WebSocket服务器并处理实体更新通知 */ export class WebSocketClient { constructor(dataManager, url = '/ws') { this.dataManager = dataManager; this.url = url; this.socket = null; this.reconnectAttempts = 0; this.maxReconnectAttempts = 5; this.reconnectInterval = 1000; this.isConnecting = false; } /** * 连接到WebSocket服务器 */ connect() { if (this.socket && this.socket.readyState === WebSocket.OPEN) { return; } if (this.isConnecting) { return; } this.isConnecting = true; try { // 创建WebSocket连接 this.socket = new WebSocket(this.url); // 设置事件处理器 this.socket.onopen = this.handleOpen.bind(this); this.socket.onmessage = this.handleMessage.bind(this); this.socket.onclose = this.handleClose.bind(this); this.socket.onerror = this.handleError.bind(this); } catch (error) { console.error('Failed to create WebSocket connection:', error); this.isConnecting = false; this.attemptReconnect(); } } /** * 断开WebSocket连接 */ disconnect() { if (this.socket) { this.socket.close(); this.socket = null; } this.reconnectAttempts = 0; this.isConnecting = false; } /** * 处理WebSocket连接打开事件 * @param {Event} event - 连接事件 */ handleOpen(event) { console.log('WebSocket connection established'); this.isConnecting = false; this.reconnectAttempts = 0; // 发送连接确认消息 this.send({ type: 'connect', timestamp: new Date().getTime() }); } /** * 处理WebSocket消息 * @param {MessageEvent} event - 消息事件 */ handleMessage(event) { try { const data = JSON.parse(event.data); this.dataManager.handleWebSocketUpdate(data); } catch (error) { console.error('Failed to parse WebSocket message:', error); } } /** * 处理WebSocket连接关闭事件 * @param {CloseEvent} event - 关闭事件 */ handleClose(event) { console.log('WebSocket connection closed:', event.code, event.reason); this.socket = null; this.isConnecting = false; // 尝试重新连接 this.attemptReconnect(); } /** * 处理WebSocket错误事件 * @param {Event} event - 错误事件 */ handleError(event) { console.error('WebSocket error:', event); } /** * 发送消息到服务器 * @param {Object} message - 要发送的消息对象 */ send(message) { if (this.socket && this.socket.readyState === WebSocket.OPEN) { this.socket.send(JSON.stringify(message)); } else { console.warn('WebSocket is not connected, cannot send message'); } } /** * 尝试重新连接 */ attemptReconnect() { if (this.reconnectAttempts < this.maxReconnectAttempts) { this.reconnectAttempts++; const delay = this.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1); console.log(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`); setTimeout(() => { this.connect(); }, delay); } else { console.error('Max reconnection attempts reached, giving up'); } } /** * 订阅特定实体类型的更新 * @param {string} entityType - 实体类型 */ subscribe(entityType) { this.send({ type: 'subscribe', entityType: entityType }); } /** * 取消订阅特定实体类型的更新 * @param {string} entityType - 实体类型 */ unsubscribe(entityType) { this.send({ type: 'unsubscribe', entityType: entityType }); } }
最新发布
09-19
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值