this.fields.get(c) is undefined

本文介绍了一个关于 ExtJS 中 GridPanel 的分组排序问题。作者在使用 GroupingStore 对数据进行分组并尝试排序时遇到错误,因为排序字段“company”并未在数据中定义。文章详细展示了如何配置 GridPanel 和 GroupingStore,以及如何定义 renderer 和 Cls 函数以自定义单元格样式。

今天在做项目的时候碰到这个问题,和大家一起分享:

源代码:大家一定需要注意我的Store中的sortInfo:

<script type="text/javascript">
             Ext.onReady(function(){
                 
                 Ext.QuickTips.init();
                 
                 //别名
                 var xg=Ext.grid;
                 
                 //创建一个ArrayReader
                 var reader=new Ext.data.ArrayReader({},[
                   {name:"name",type:"string"},
                   {name:"age",type:"int"},
                   {name:"empno",type:"int"},
                   {name:"sex",type:"int"},
                   {name:"birthday",type:"date",dateFormat:"n/j h:ia"},
                   {name:"salary",type:"float"},
                   {name:"deptno",type:"int"}
                 ]);
                 
                 //创建一个store
                 var store=new Ext.data.GroupingStore({
                   reader:reader,
                   data:xg.dummyData,
                   sortInfo:{field:"company",direction:"ASC"},
                   groupField:"deptno"  //根据部门来分组
                 });
                 
                 
                 //name的renderer
                 function nameCls(val){
                    if(val.length>2){
                    return "<span stype='color:red;'>"+val+"</span>";
                    }
                    return "<span stype='color:blue;'>"+val+"</span>";
                 }

                //age的Cls
                function agCls(val){
                  if(val>25){
                  return "<span stype='color:red;'>"+val+"</span>";
                  }
                  return "<span stype='color:green;'>"+val+"</span>";
                }                 
                 
                 //创建一个gridPanel
                 var grid=new xg.GridPanel({
                  store:store,
                  columns:[
                     {header:"姓名",width:20,sortable:true,renderer:nameCls,dataIndex:"name"},
                     {header:"年龄",width:40,sortable:true,renderer:agCls,dataIndex:"age"},
                     {id:"empno",header:"编号",width:60,sortable:true,dataIndex:"empno"},
                     {header:"性别",width:20,sortable:true,dataIndex:"sex"},
                     {header:"生日",width:50,sotrable:true,renderer:Ext.util.Format.dateRenderer("m/d/Y"),dataIndex:"birthday"},
                     {header:"工资",width:30,sortable:true,renderer:Ext.util.Format.usMoney,dataIndex:"salary"},
                     {header:"部门编号",width:50,sortable:true,dataIndex:"deptno"}
                  ],
                  view:new Ext.grid.GroupingView({
                      forceFit:true,
                      groupTextTpl:"{text} ({[values.rs.length]} {[values.rs.length>1 ? 'Items' : 'Item']})"
                  }),
                  frame:true,
                  width:700,
                  height:450,
                  collapsible:true,//True表示为面板是可收缩的,并自动渲染一个展开/收缩的轮换按钮在头部工具条。false表示为保持面板为一个静止的尺寸
                  animCollapse:true,//True 表示为面板闭合过程附有动画效果(默认为true,在类 Ext.Fx 可用的情况下)。
                  title:"雇员信息",
                  iconCls:"icon-grid",
                  fbar:["->",{
                    text:"clear Groping",
                    iconCls:"icon-clear-group",
                    handler:function(){
                      store.clearGouping();
                    }
                  }],
                  renderTo:document.body              
                 });
                 
              
             });
             
                //数据
                 Ext.grid.dummyData=[
                ["张三00",12,1001,1,"1990-05-20",3000,50],
                ["张三01",16,1002,0,"1990-05-21",3000,20],
                ["张三02",19,1003,1,"1990-05-22",3000,20],
                ["张三03",25,1004,1,"1990-05-23",3000,10],
                ["张三04",22,1005,1,"1990-05-24",3000,30],
                ["张三05",24,1006,1,"1990-05-25",3000,20],
                ["张三06",23,1007,0,"1990-05-26",3000,60],
                ["张三07",21,1008,0,"1990-05-27",3000,90],
                ["张三08",25,1009,0,"1990-05-28",3000,10],
                ["张三09",28,1010,1,"1990-05-29",6000,20],
                ["张三20",26,1011,0,"1990-05-20",2000,50],
                ["张三21",29,1012,0,"1990-06-20",8000,40],
                ["张三22",22,1013,1,"1990-01-20",3000,50],
                ["张三23",28,1014,0,"1990-02-20",3000,60],
                ["张三24",21,1015,1,"1990-08-20",3000,50]
               ];
                 
        
              		
		</script>

错误就出现在那里,没有company列但是,我却给company列排序所以报错,


找了很长时间,特地贴出来,不知道对大家有没有帮助.


/* 文件路径: data/DataManager.js */ // 导入基础数据解析函数 function resolveDataReferences(data, rawData) { if (!data || typeof data !== 'object') { return data; } // 创建一个新对象,避免直接修改原始数据 const result = Array.isArray(data) ? [] : {}; for (const key in data) { if (data.hasOwnProperty(key)) { result[key] = resolveDataReference(data[key], rawData); } } return result; } function resolveDataReference(value, rawData) { if (!value || typeof value !== 'object') { return value; } // 检查是否是引用对象(包含_id属性) if (typeof value._id !== 'undefined') { // 获取关联实体类型和ID const id = value._id; // 处理关联实体 if (value.entityType) { const collectionName = `${value.entityType}s`; const collection = rawData[collectionName]; if (collection && Array.isArray(collection)) { // 查找关联实体 const referencedEntity = collection.find(item => item.id === id); if (referencedEntity) { return referencedEntity; } } } // 如果找不到关联实体,返回原始ID return { id }; } // 递归处理嵌套对象 if (Array.isArray(value)) { return value.map(item => resolveDataReference(item, rawData)); } else { const result = {}; for (const key in value) { if (value.hasOwnProperty(key)) { result[key] = resolveDataReference(value[key], rawData); } } return result; } } // 导入基础依赖 import { DataManagerCRUD } from './DataManagerCRUD.js'; import { TransactionApi } from './TransactionApi.js'; import { LazyLoader } from './LazyLoader.js'; /** * DataManager主类 - 整合了所有功能模块,提供完整的数据管理能力 * 保持与原有接口的完全兼容性 */ class DataManager extends DataManagerCRUD { constructor(options = {}) { super(options); // 初始化事务API this.Transaction = new TransactionApi(this); } // 以下是业务特定的方法和实体获取器 /** * 动态生成实体数据获取方法 */ generateEntityGetters() { this.entityTypes.forEach(entityType => { // 创建驼峰式命名的方法名 const getterName = `get${entityType.charAt(0).toUpperCase() + entityType.slice(1)}s`; // 只有在方法不存在的情况下才创建 if (!this[getterName]) { this[getterName] = () => this.getEntity(`${entityType}s`); } }); } // 实体快捷获取方法 getDingdans() { return this.getEntity('dingdans'); } getBancais() { return this.getEntity('bancais'); } getChanpins() { return this.getEntity('chanpins'); } getZujians() { return this.getEntity('zujians'); } getKucuns() { return this.getEntity('kucuns'); } getUsers() { return this.getEntity('users'); } getCaizhis() { return this.getEntity('caizhis'); } getMupis() { return this.getEntity('mupis'); } getDingdanBancais() { return this.getEntity('dingdan_bancais'); } getJinhuos() { return this.getEntity('jinhuos'); } getChanpinZujians() { return this.getEntity('chanpin_zujians'); } getDingdanChanpins() { return this.getEntity('dingdan_chanpins'); } // 业务方法 getChanpinsForDingdan(dingdanId) { const dingdan = this._rawData.dingdans?.find(d => d?.id == dingdanId); if (!dingdan) return []; return (dingdan.dingdan_chanpin_list || []) .map(dc => dc.chanpin) .filter(c => c); } getZujiansForChanpin(chanpinId) { const chanpin = this._rawData.chanpins?.find(c => c?.id == chanpinId); if (!chanpin) return []; return (chanpin.chanpin_zujian_list || []) .map(cz => cz.zujian) .filter(z => z); } getShengchanXiaohaoRecords() { return this._rawData.jinhuos?.filter(jinhuo => jinhuo.the_type_of_operation === 2 && jinhuo.shuliang < 0 ) || []; } getShengchanStatistics() { const today = new Date().toISOString().split('T')[0]; const thisMonth = new Date().toISOString().substring(0, 7); const consumptionRecords = this.getShengchanXiaohaoRecords(); const todayConsumption = consumptionRecords .filter(record => record.date && record.date.startsWith(today)) .reduce((sum, record) => sum + Math.abs(record.shuliang), 0); const monthConsumption = consumptionRecords .filter(record => record.date && record.date.startsWith(thisMonth)) .reduce((sum, record) => sum + Math.abs(record.shuliang), 0); const pendingOrders = this._rawData.dingdans?.filter(dingdan => !dingdan.deleted && dingdan.zhuangtai !== '已完成' ).length || 0; const lowStockCount = this._rawData.kucuns?.filter(kucun => !kucun.deleted && kucun.shuliang < 10 ).length || 0; return { today_consumption: todayConsumption, month_consumption: monthConsumption, pending_orders: pendingOrders, low_stock_count: lowStockCount, total_records: consumptionRecords.length }; } } export { DataManager }; ================================================================================ /* 文件路径: data/DataManagerCore.js */ /** * DataManager核心模块 - 包含核心初始化和配置管理 */ import { LazyLoader } from './LazyLoader.js'; import { IndexedDBManager } from './IndexedDBManager.js'; import { TransactionApi } from './TransactionApi.js'; /** * 解析数据引用 */ export function resolveDataReferences(data, allData) { if (!data || typeof data !== 'object') { return data; } if (Array.isArray(data)) { return data.map(item => resolveDataReferences(item, allData)); } const result = { ...data }; for (const key in result) { if (result.hasOwnProperty(key) && key.endsWith('_id')) { const refKey = key.replace('_id', ''); const collectionName = refKey.endsWith('s') ? refKey : `${refKey}s`; if (allData[collectionName] && result[key]) { const referencedItem = allData[collectionName].find(item => item.id == result[key]); if (referencedItem) { result[refKey] = referencedItem; } } } if (result.hasOwnProperty(key) && typeof result[key] === 'object' && result[key] !== null) { result[key] = resolveDataReferences(result[key], allData); } } return result; } /** * 解析单个数据引用 */ export function resolveDataReference(id, collection) { if (!id || !collection || !Array.isArray(collection)) { return null; } return collection.find(item => item.id == id) || null; } /** * DataManager核心类 */ export class DataManagerCore { constructor(options = {}) { this.baseUrl = options.baseUrl || ''; this.onProgressUpdate = options.onProgressUpdate || null; this.onInitComplete = options.onInitComplete || null; this._rawData = {}; this.callbacks = new Map(); this.isWebSocketConnected = false; this.webSocketClient = null; this.webSocketTopics = new Set(); // 初始化IndexedDB管理器 this.indexedDBManager = new IndexedDBManager(); // 初始化懒加载器 this.lazyLoader = new LazyLoader(this); // 初始化事务API this.Transaction = new TransactionApi(this); // 合并默认配置和用户配置 this.entityConfigs = this.mergeEntityConfigs(options.entityConfigs || []); this.entityTypes = this.entityConfigs.map(config => config.type); this.entityConfigMap = this.createEntityConfigMap(); // 创建数据访问代理 this.data = this.createDataProxy(); } /** * 合并实体配置 */ mergeEntityConfigs(customConfigs) { const defaultEntityConfigs = [ { type: 'dingdan', isCore: true, text: '订单' }, { type: 'bancai', isCore: true, text: '板材' }, { type: 'chanpin', isCore: true, text: '产品' }, { type: 'zujian', isCore: true, text: '组件' }, { type: 'kucun', isCore: true, text: '库存' }, { type: 'user', isCore: true, text: '用户' }, { type: 'caizhi', isCore: true, text: '材质' }, { type: 'mupi', isCore: true, text: '木皮' }, { type: 'dingdan_bancai', isCore: false, text: '订单板材' }, { type: 'jinhuo', isCore: false, text: '进货' }, { type: 'chanpin_zujian', isCore: false, text: '产品组件' }, { type: 'dingdan_chanpin', isCore: false, text: '订单产品' } ]; // 创建配置映射以便快速查找 const configMap = new Map(defaultEntityConfigs.map(config => [config.type, config])); // 合并用户配置 customConfigs.forEach(customConfig => { if (configMap.has(customConfig.type)) { // 合并现有配置 const existingConfig = configMap.get(customConfig.type); configMap.set(customConfig.type, { ...existingConfig, ...customConfig }); } else { // 添加新配置 configMap.set(customConfig.type, customConfig); } }); // 转换回数组 return Array.from(configMap.values()); } /** * 创建实体配置映射 */ createEntityConfigMap() { const configMap = new Map(); this.entityConfigs.forEach(config => { configMap.set(config.type, config); }); return configMap; } /** * 获取实体配置 */ getEntityConfig(entityType) { return this.entityConfigMap.get(entityType) || {}; } /** * 创建空数据结构 */ createEmptyData() { const emptyData = {}; // 为每种实体类型创建空数组 this.entityTypes.forEach(entityType => { const collectionName = `${entityType}s`; emptyData[collectionName] = []; }); // 添加特殊字段 emptyData._lastSync = null; emptyData._isLoading = false; return emptyData; } /** * 创建数据访问代理 */ createDataProxy() { const self = this; return new Proxy({}, { get: (target, property) => { // 懒加载数据 if (property in self._rawData) { // 获取原始数据 const rawData = self._rawData[property]; // 如果是数组,为每个元素创建代理 if (Array.isArray(rawData)) { return rawData.map(item => self.lazyLoader.createProxy(item, property.replace('s', ''))); } return rawData; } return undefined; }, set: (target, property, value) => { self._rawData[property] = value; return true; } }); } /** * 添加实体类型 */ addEntityType(entityType, config = {}) { // 检查是否已存在 if (this.entityTypes.includes(entityType)) { console.warn(`实体类型${entityType}已存在`); return; } // 添加到配置 const newConfig = { type: entityType, isCore: config.isCore || false, text: config.text || entityType, duplicateCheckRule: config.duplicateCheckRule || null }; this.entityConfigs.push(newConfig); this.entityTypes.push(entityType); this.entityConfigMap.set(entityType, newConfig); // 初始化集合 const collectionName = `${entityType}s`; if (!this._rawData[collectionName]) { this._rawData[collectionName] = []; } // 订阅WebSocket主题 if (this.isWebSocketConnected) { this.webSocketClient.send(JSON.stringify({ type: 'subscribe', topic: collectionName })); } // 从IndexedDB加载数据 this.loadEntityFromIndexedDB(entityType); } /** * 初始化DataManager */ async initialize() { try { // 初始化IndexedDB await this.indexedDBManager.init(); // 内部初始化 await this._initializeInternal(); // 初始化WebSocket if (this.baseUrl) { this.initWebSocket(); } // 初始化网络状态监听 this.initNetwork(); // 触发初始化完成回调 if (this.onInitComplete) { this.onInitComplete(); } } catch (error) { console.error('DataManager初始化失败:', error); throw error; } } /** * 内部初始化方法 */ async _initializeInternal() { try { // 创建空数据结构 this._rawData = this.createEmptyData(); this._rawData._isLoading = true; // 获取核心实体类型 const coreEntityTypes = this.entityConfigs .filter(config => config.isCore) .map(config => config.type); // 并行加载核心实体数据 const loadPromises = coreEntityTypes.map(entityType => this.loadEntityFromIndexedDB(entityType) ); await Promise.all(loadPromises); // 标记加载完成 this._rawData._isLoading = false; } catch (error) { console.error('内部初始化失败:', error); this._rawData._isLoading = false; throw error; } } } ================================================================================ /* 文件路径: data/DataManagerCRUD.js */ /** * DataManager CRUD模块 - 包含实体的增删改查操作 */ import { DataManagerNetwork } from './DataManagerNetwork.js'; /** * DataManager CRUD类 */ export class DataManagerCRUD extends DataManagerNetwork { /** * 注册回调函数 */ registerCallback(event, callback) { if (!this.callbacks.has(event)) { this.callbacks.set(event, new Set()); } this.callbacks.get(event).add(callback); } /** * 取消注册回调函数 */ unregisterCallback(event, callback) { if (this.callbacks.has(event)) { this.callbacks.get(event).delete(callback); // 如果没有回调函数了,删除这个事件 if (this.callbacks.get(event).size === 0) { this.callbacks.delete(event); } } } /** * 触发回调函数 */ triggerCallbacks(event, ...args) { if (this.callbacks.has(event)) { this.callbacks.get(event).forEach(callback => { try { callback(...args); } catch (error) { console.error(`触发回调函数时出错: event=${event}`, error); } }); } } /** * 处理实体CRUD操作 */ async crudOperation(operation, entityType, data) { try { const collectionName = this.getCollectionName(entityType); // 触发操作开始回调 this.triggerCallbacks(`${operation}_start`, entityType, data); // 检查网络连接 if (!navigator.onLine && operation !== 'delete') { console.warn('网络连接已断开,尝试离线操作'); // 离线模式下处理 const result = await this.handleOfflineOperation(operation, entityType, data); // 触发操作完成回调 this.triggerCallbacks(`${operation}_complete`, entityType, result); return result; } // 在线模式下发送请求 const response = await fetch(`${this.baseUrl}/app/${operation}/${entityType}`, { 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) { // 触发操作完成回调 this.triggerCallbacks(`${operation}_complete`, entityType, result.data); return result.data; } else { throw new Error(result.text || `${operation}操作失败`); } } catch (error) { console.error(`${operation}${entityType}失败:`, error); // 触发操作失败回调 this.triggerCallbacks(`${operation}_error`, entityType, { data, error: error.message }); throw error; } } /** * 处理离线操作 */ async handleOfflineOperation(operation, entityType, data) { try { const collectionName = this.getCollectionName(entityType); if (!this._rawData[collectionName]) { this._rawData[collectionName] = []; } let result; switch (operation) { case 'add': // 生成临时ID const tempId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; const newItem = { ...data, id: tempId, isTemp: true }; // 添加到本地数据 this._rawData[collectionName].push(newItem); // 保存到IndexedDB await this.indexedDBManager.ensureCollection(collectionName); await this.indexedDBManager.put(collectionName, newItem); result = newItem; break; case 'update': // 查找并更新本地数据 const index = this._rawData[collectionName].findIndex(item => item.id === data.id); if (index !== -1) { this._rawData[collectionName][index] = { ...this._rawData[collectionName][index], ...data }; // 保存到IndexedDB await this.indexedDBManager.ensureCollection(collectionName); await this.indexedDBManager.put(collectionName, this._rawData[collectionName][index]); result = this._rawData[collectionName][index]; } else { throw new Error('未找到要更新的实体'); } break; case 'delete': // 查找并删除本地数据 const deleteIndex = this._rawData[collectionName].findIndex(item => item.id === data.id); if (deleteIndex !== -1) { const deletedItem = this._rawData[collectionName].splice(deleteIndex, 1)[0]; // 从IndexedDB删除 await this.indexedDBManager.ensureCollection(collectionName); await this.indexedDBManager.delete(collectionName, data.id); result = deletedItem; } else { throw new Error('未找到要删除的实体'); } break; default: throw new Error(`不支持的离线操作: ${operation}`); } // 记录离线操作,以便网络恢复时同步 await this.recordOfflineOperation(operation, entityType, data); return result; } catch (error) { console.error(`处理离线${operation}操作失败:`, error); throw error; } } /** * 记录离线操作 */ async recordOfflineOperation(operation, entityType, data) { try { // 获取现有离线操作 let offlineOperations = await this.indexedDBManager.getMetadata('offline_operations') || []; // 添加新的离线操作 offlineOperations.push({ id: `offline_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`, operation, entityType, data, timestamp: new Date().toISOString() }); // 保存离线操作 await this.indexedDBManager.setMetadata('offline_operations', offlineOperations); } catch (error) { console.error('记录离线操作失败:', error); } } /** * 更新本地数据 */ updateLocalData(collectionName, data) { try { if (!this._rawData[collectionName]) { this._rawData[collectionName] = []; } // 如果是数组,批量更新 if (Array.isArray(data)) { data.forEach(item => this._updateSingleItem(collectionName, item)); } else { // 单条更新 this._updateSingleItem(collectionName, data); } // 触发数据更新回调 this.triggerCallbacks('data_updated', collectionName, data); // 异步保存到IndexedDB this._saveToIndexedDBAsync(collectionName); } catch (error) { console.error(`更新本地数据失败: ${collectionName}`, error); } } /** * 更新单条数据 */ _updateSingleItem(collectionName, data) { if (!data || !data.id) { return; } const index = this._rawData[collectionName].findIndex(item => item.id === data.id); if (index !== -1) { // 更新现有项 this._rawData[collectionName][index] = { ...this._rawData[collectionName][index], ...data }; } else { // 如果不存在,添加新项 this._rawData[collectionName].push(data); } } /** * 添加本地数据 */ addLocalData(collectionName, data) { try { if (!this._rawData[collectionName]) { this._rawData[collectionName] = []; } // 如果是数组,批量添加 if (Array.isArray(data)) { data.forEach(item => { // 检查是否已存在 const exists = this._rawData[collectionName].some(existing => existing.id === item.id); if (!exists) { this._rawData[collectionName].push(item); } }); } else { // 单条添加 const exists = this._rawData[collectionName].some(existing => existing.id === data.id); if (!exists) { this._rawData[collectionName].push(data); } } // 触发数据添加回调 this.triggerCallbacks('data_added', collectionName, data); // 异步保存到IndexedDB this._saveToIndexedDBAsync(collectionName); } catch (error) { console.error(`添加本地数据失败: ${collectionName}`, error); } } /** * 删除本地数据 */ deleteLocalData(collectionName, data) { try { if (!this._rawData[collectionName]) { return; } // 如果提供了ID数组,批量删除 if (Array.isArray(data) && data.every(item => typeof item === 'string' || typeof item === 'number')) { this._rawData[collectionName] = this._rawData[collectionName].filter( item => !data.includes(item.id) ); } // 如果提供了ID,删除单条 else if (typeof data === 'string' || typeof data === 'number') { this._rawData[collectionName] = this._rawData[collectionName].filter( item => item.id !== data ); } // 如果提供了完整对象,根据ID删除 else if (data && data.id) { this._rawData[collectionName] = this._rawData[collectionName].filter( item => item.id !== data.id ); } // 触发数据删除回调 this.triggerCallbacks('data_deleted', collectionName, data); // 异步保存到IndexedDB this._saveToIndexedDBAsync(collectionName); } catch (error) { console.error(`删除本地数据失败: ${collectionName}`, error); } } /** * 异步保存到IndexedDB */ _saveToIndexedDBAsync(collectionName) { this.indexedDBManager.ensureCollection(collectionName).then(() => { const data = this._rawData[collectionName]; if (data && data.length > 0) { this.indexedDBManager.putAll(collectionName, data).catch(error => { console.error(`异步保存数据到IndexedDB失败: ${collectionName}`, error); }); } }).catch(error => { console.error(`确保集合存在失败: ${collectionName}`, error); }); } /** * 添加实体 */ async addEntity(entity, data) { // 检查重复数据 if (this.checkDuplicate(entity, data)) { const config = this.getEntityConfig(entity); const errorMsg = `${config.text || entity}数据重复`; this.triggerCallbacks('duplicate_error', entity, { data, error: errorMsg }); throw new Error(errorMsg); } return this.crudOperation('add', entity, data); } /** * 更新实体 */ async updateEntity(entity, data) { return this.crudOperation('update', entity, data); } /** * 删除实体 */ async deleteEntity(entity, id) { return this.crudOperation('delete', entity, { id }); } /** * 事务操作 */ async transactionalOperation(endpoint, data) { return this.Transaction.execute(endpoint, data); } } ================================================================================ /* 文件路径: data/DataManagerNetwork.js */ /** * DataManager网络模块 - 包含WebSocket连接和数据同步功能 */ import { DataManagerStorage } from './DataManagerStorage.js'; /** * DataManager网络类 */ export class DataManagerNetwork extends DataManagerStorage { /** * 初始化WebSocket连接 */ async initWebSocket() { try { // 检查是否已加载SockJS库 if (!window.SockJS) { throw new Error('SockJS库未加载'); } // 创建WebSocket客户端 const socketUrl = `${this.baseUrl}/ws`; this.webSocketClient = new window.SockJS(socketUrl); // 设置WebSocket事件处理程序 this.webSocketClient.onopen = () => { console.log('WebSocket连接已建立'); this.isWebSocketConnected = true; // 重新订阅所有主题 this.resubscribeWebSocketTopics(); // 触发连接成功回调 this.triggerCallbacks('websocket_connected'); }; this.webSocketClient.onmessage = (event) => { try { const message = JSON.parse(event.data); this.handleWebSocketMessage(message); } catch (error) { console.error('解析WebSocket消息失败:', error); } }; this.webSocketClient.onclose = () => { console.log('WebSocket连接已关闭'); this.isWebSocketConnected = false; // 触发连接关闭回调 this.triggerCallbacks('websocket_disconnected'); // 尝试重新连接 setTimeout(() => this.initWebSocket(), 5000); }; this.webSocketClient.onerror = (error) => { console.error('WebSocket连接错误:', error); // 触发连接错误回调 this.triggerCallbacks('websocket_error', error); }; } catch (error) { console.error('初始化WebSocket失败:', error); // 尝试重新连接 setTimeout(() => this.initWebSocket(), 5000); } } /** * 重新订阅所有WebSocket主题 */ resubscribeWebSocketTopics() { if (!this.isWebSocketConnected || !this.webSocketClient) { return; } // 订阅所有实体类型集合 this.entityTypes.forEach(entityType => { const collectionName = `${entityType}s`; this.webSocketTopics.add(collectionName); this.webSocketClient.send(JSON.stringify({ type: 'subscribe', topic: collectionName })); }); } /** * 处理WebSocket消息 */ handleWebSocketMessage(message) { if (!message || !message.type) { return; } try { const { type, topic, data } = message; // 根据消息类型处理 switch (type) { case 'data_update': this.updateLocalData(topic, data); break; case 'data_add': this.addLocalData(topic, data); break; case 'data_delete': this.deleteLocalData(topic, data); break; case 'progress_update': if (this.onProgressUpdate && data.progress !== undefined) { this.onProgressUpdate(data.progress); } break; case 'sync_complete': this.triggerCallbacks('sync_complete'); break; case 'error': console.error('WebSocket错误消息:', data); this.triggerCallbacks('websocket_message_error', data); break; default: console.log('接收到未知类型的WebSocket消息:', type); break; } } catch (error) { console.error('处理WebSocket消息时出错:', error); } } /** * 获取集合名称 */ getCollectionName(entityType) { return entityType.endsWith('s') ? entityType : `${entityType}s`; } /** * 初始化网络状态监听 */ initNetwork() { // 监听网络状态变化 window.addEventListener('online', () => { console.log('网络连接已恢复'); this.triggerCallbacks('network_online'); // 尝试重新同步数据 this.syncData(); }); window.addEventListener('offline', () => { console.log('网络连接已断开'); this.triggerCallbacks('network_offline'); }); } /** * 同步实体数据 */ async syncEntityData(entityType) { try { const collectionName = this.getCollectionName(entityType); const config = this.getEntityConfig(entityType); // 触发同步开始回调 this.triggerCallbacks('sync_start', entityType); // 检查网络连接 if (!navigator.onLine) { console.warn('网络连接已断开,无法同步数据'); this.triggerCallbacks('sync_error', entityType, { error: '网络连接已断开' }); return; } // 发送同步请求 const response = await fetch(`${this.baseUrl}/app/sync/${entityType}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); if (result.status === 200 && Array.isArray(result.data)) { // 更新本地数据 this._rawData[collectionName] = result.data; // 保存到IndexedDB await this.indexedDBManager.ensureCollection(collectionName); await this.indexedDBManager.clear(collectionName); await this.indexedDBManager.putAll(collectionName, result.data); // 更新最后同步时间 this._rawData._lastSync = new Date().toISOString(); await this.indexedDBManager.setMetadata('lastSyncTime', this._rawData._lastSync); // 触发同步完成回调 this.triggerCallbacks('sync_complete', entityType); } else { throw new Error(result.text || '同步数据失败'); } } catch (error) { console.error(`同步${entityType}数据失败:`, error); this.triggerCallbacks('sync_error', entityType, { error: error.message }); } } /** * 添加重复检查规则 */ addDuplicateCheckRule(entityType, rule) { const config = this.getEntityConfig(entityType); if (config) { config.duplicateCheckRule = rule; } } /** * 检查重复数据 */ checkDuplicate(entityType, newData) { const config = this.getEntityConfig(entityType); if (!config.duplicateCheckRule || !newData) { return false; } const { fields, isStrict } = config.duplicateCheckRule; const collectionName = this.getCollectionName(entityType); const collection = this._rawData[collectionName] || []; return collection.some(item => { // 如果是更新操作,跳过当前项 if (newData.id && item.id === newData.id) { return false; } // 检查所有指定字段 return fields.every(field => { if (isStrict) { return item[field] === newData[field]; } else { return item[field] && newData[field] && String(item[field]).toLowerCase() === String(newData[field]).toLowerCase(); } }); }); } /** * 获取实体数据 */ async fetchEntityData(entityType) { try { const response = await fetch(`${this.baseUrl}/app/data/${entityType}`, { method: 'GET', headers: { 'Content-Type': 'application/json' } }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const result = await response.json(); if (result.status === 200 && Array.isArray(result.data)) { const collectionName = this.getCollectionName(entityType); this._rawData[collectionName] = result.data; // 保存到IndexedDB await this.indexedDBManager.ensureCollection(collectionName); await this.indexedDBManager.clear(collectionName); await this.indexedDBManager.putAll(collectionName, result.data); return result.data; } else { throw new Error(result.text || '获取数据失败'); } } catch (error) { console.error(`获取${entityType}数据失败:`, error); throw error; } } /** * 同步所有数据 */ async syncData() { try { // 获取所有实体类型 const syncPromises = this.entityTypes.map(entityType => this.syncEntityData(entityType) ); // 并行同步所有数据 await Promise.all(syncPromises); // 触发全部同步完成回调 this.triggerCallbacks('all_sync_complete'); } catch (error) { console.error('同步所有数据失败:', error); this.triggerCallbacks('sync_all_error', { error: error.message }); } } /** * 并行获取所有实体数据 */ async fetchAll() { try { // 标记为加载中 this._rawData._isLoading = true; // 获取所有实体类型 const entityPromises = this.entityTypes.map(entityType => this.fetchEntityData(entityType).catch(error => { console.error(`获取${entityType}数据失败,但继续处理其他实体:`, error); return null; }) ); // 并行获取所有数据 await Promise.all(entityPromises); // 标记加载完成 this._rawData._isLoading = false; // 触发获取完成回调 this.triggerCallbacks('fetch_all_complete'); } catch (error) { console.error('获取所有数据失败:', error); this._rawData._isLoading = false; this.triggerCallbacks('fetch_all_error', { error: error.message }); throw error; } } } ================================================================================ /* 文件路径: data/DataManagerStorage.js */ /** * DataManager存储模块 - 包含IndexedDB数据操作功能 */ import { DataManagerCore } from './DataManagerCore.js'; /** * DataManager存储类 */ export class DataManagerStorage extends DataManagerCore { /** * 从IndexedDB加载单个实体类型数据 */ async loadEntityFromIndexedDB(entityType) { try { const collectionName = `${entityType}s`; // 确保集合存在 await this.indexedDBManager.ensureCollection(collectionName); // 加载数据 const data = await this.indexedDBManager.getAll(collectionName); if (data && data.length > 0) { this._rawData[collectionName] = data; // 解析数据引用 this.resolveDataReferencesForCollection(collectionName); } } catch (error) { console.error(`从IndexedDB加载${entityType}数据失败:`, error); } } /** * 解析集合中的数据引用 */ resolveDataReferencesForCollection(collectionName) { if (!this._rawData[collectionName]) return; const entityType = collectionName.replace('s', ''); this._rawData[collectionName] = this._rawData[collectionName].map(item => { // 创建代理对象以实现懒加载 return this.lazyLoader.createProxy(item, entityType); }); } /** * 保存数据到IndexedDB */ async saveDataToIndexedDB() { try { // 获取所有实体类型集合 const collectionKeys = Object.keys(this._rawData).filter(key => !key.startsWith('_') && Array.isArray(this._rawData[key]) ); // 使用Promise.all实现并行保存,但限制并发数 const batchSize = 3; let savedCollections = 0; for (let i = 0; i < collectionKeys.length; i += batchSize) { const batch = collectionKeys.slice(i, i + batchSize); // 处理当前批次中的每个集合 await Promise.all(batch.map(async key => { const data = this._rawData[key]; if (data && data.length > 0) { // 先清空再添加 await this.indexedDBManager.clear(key); await this.indexedDBManager.putAll(key, data); } // 更新保存进度 savedCollections++; const saveProgress = Math.floor((savedCollections / collectionKeys.length) * 20); if (this.onProgressUpdate) { this.onProgressUpdate(saveProgress); } })); } // 保存最后同步时间 this._rawData._lastSync = new Date().toISOString(); await this.indexedDBManager.setMetadata('lastSyncTime', this._rawData._lastSync); } catch (error) { console.error('保存数据到IndexedDB失败:', error); } } /** * 兼容旧代码,保留localStorage方法但内部使用IndexedDB */ loadDataFromStorage() { this.loadDataFromIndexedDB(); } saveDataToStorage() { this.saveDataToIndexedDB(); } /** * 移除循环引用的辅助方法 */ removeCircularReferences(obj, seen = new WeakSet()) { if (obj === null || typeof obj !== 'object') { return obj; } if (seen.has(obj)) { return {}; // 返回空对象而不是循环引用 } seen.add(obj); if (Array.isArray(obj)) { return obj.map(item => this.removeCircularReferences(item, seen)); } const result = {}; for (const key in obj) { if (obj.hasOwnProperty(key)) { result[key] = this.removeCircularReferences(obj[key], seen); } } return result; } /** * 从IndexedDB加载所有数据 */ async loadDataFromIndexedDB() { try { // 标记为加载中 this._rawData._isLoading = true; // 获取所有实体类型集合 const collectionKeys = this.entityTypes.map(type => `${type}s`); // 并行加载所有数据,但限制并发数 const batchSize = 3; let loadedCollections = 0; for (let i = 0; i < collectionKeys.length; i += batchSize) { const batch = collectionKeys.slice(i, i + batchSize); await Promise.all(batch.map(async key => { await this.indexedDBManager.ensureCollection(key); const data = await this.indexedDBManager.getAll(key); if (data && data.length > 0) { this._rawData[key] = data; } // 更新加载进度 loadedCollections++; const loadProgress = Math.floor((loadedCollections / collectionKeys.length) * 20); if (this.onProgressUpdate) { this.onProgressUpdate(loadProgress); } })); } // 加载最后同步时间 const lastSyncTime = await this.indexedDBManager.getMetadata('lastSyncTime'); if (lastSyncTime) { this._rawData._lastSync = lastSyncTime; } // 标记加载完成 this._rawData._isLoading = false; } catch (error) { console.error('从IndexedDB加载数据失败:', error); this._rawData._isLoading = false; } } } ================================================================================ /* 文件路径: data/index.js */ // 数据管理模块主入口 // 导入核心模块 import { DataManager } from './DataManager.js'; import { DataManagerCore } from './DataManagerCore.js'; import { DataManagerStorage } from './DataManagerStorage.js'; import { DataManagerNetwork } from './DataManagerNetwork.js'; import { DataManagerCRUD } from './DataManagerCRUD.js'; // 导入辅助模块 import { LazyLoader } from './LazyLoader.js'; import { TransactionApi } from './TransactionApi.js'; import { IndexedDBManager } from './IndexedDBManager.js'; // 导出所有模块,方便外部使用 export { DataManager, DataManagerCore, DataManagerStorage, DataManagerNetwork, DataManagerCRUD, LazyLoader, TransactionApi, IndexedDBManager }; // 提供默认导出,方便直接使用DataManager export default DataManager; ================================================================================ /* 文件路径: data/IndexedDBManager.js */ /** * IndexedDB工具类 - 用于大数据存储 */ class IndexedDBManager { constructor(dbName = 'appData', version = 1) { this.dbName = dbName; this.version = version; this.db = null; this.collections = new Set(); // 添加版本升级队列,解决IndexedDB不允许同时进行多个版本升级的问题 this.upgradeQueue = []; this.isUpgrading = false; } /** * 初始化数据库连接 */ async init() { return new Promise((resolve, reject) => { if (this.db) { resolve(this.db); return; } // 不指定版本号,让浏览器自动使用最新版本 // 这样即使数据库版本高于代码中指定的版本,也能成功打开 const request = indexedDB.open(this.dbName); request.onupgradeneeded = (event) => { const db = event.target.result; // 确保存储元数据的集合存在 if (!db.objectStoreNames.contains('metadata')) { db.createObjectStore('metadata', { keyPath: 'key' }); } // 更新版本号缓存 this.version = db.version; }; request.onsuccess = (event) => { this.db = event.target.result; // 更新版本号缓存 this.version = this.db.version; resolve(this.db); }; request.onerror = (event) => { console.error('IndexedDB初始化失败:', event.target.error); reject(event.target.error); }; }); } /** * 确保对象存储空间(集合)存在 */ async ensureCollection(collectionName) { // 如果是单个集合请求,使用队列处理 return this._ensureCollection(collectionName); } /** * 批量确保多个对象存储空间存在 * 避免多次触发版本升级 */ async batchEnsureCollections(collectionNames) { if (!Array.isArray(collectionNames) || collectionNames.length === 0) { return Promise.resolve([]); } // 首先确保数据库已初始化 const db = await this.init(); // 检查哪些集合已经存在,哪些需要创建 const existingCollections = new Set(); const collectionsToCreate = []; for (const collectionName of collectionNames) { if (this.collections.has(collectionName)) { existingCollections.add(collectionName); } else if (db.objectStoreNames.contains(collectionName)) { this.collections.add(collectionName); existingCollections.add(collectionName); } else { collectionsToCreate.push(collectionName); } } // 如果有需要创建的集合,一次性创建 if (collectionsToCreate.length > 0) { await this._batchCreateCollections(collectionsToCreate); } return [...existingCollections, ...collectionsToCreate]; } /** * 内部方法:确保单个集合存在(使用队列处理) */ async _ensureCollection(collectionName) { try { if (this.collections.has(collectionName)) { return true; } const db = await this.init(); // 检查对象存储空间是否已存在 if (db.objectStoreNames.contains(collectionName)) { this.collections.add(collectionName); return true; } // 需要升级数据库来创建新的对象存储空间 // 使用队列确保一次只进行一个版本升级 return new Promise((resolve, reject) => { // 将升级请求加入队列 this.upgradeQueue.push({ collectionName, resolve, reject }); // 如果当前没有升级在进行,开始处理队列 if (!this.isUpgrading) { this._processUpgradeQueue(); } }); } catch (error) { console.error(`ensureCollection方法执行错误:`, error); // 出错时返回一个拒绝的Promise,保持返回类型一致 return Promise.reject(error); } } /** * 批量创建多个对象存储空间 * 整合到升级队列中,确保与其他升级操作不冲突 */ async _batchCreateCollections(collectionNames) { if (!collectionNames || collectionNames.length === 0) { return Promise.resolve(); } // 将批量创建请求作为一个队列项加入升级队列 return new Promise((resolve, reject) => { // 将升级请求加入队列 this.upgradeQueue.push({ batchCollections: collectionNames, resolve, reject }); // 如果当前没有升级在进行,开始处理队列 if (!this.isUpgrading) { this._processUpgradeQueue(); } }); } /** * 处理版本升级队列,确保一次只进行一个版本升级 * 支持单个集合和批量集合的创建 */ async _processUpgradeQueue() { if (this.isUpgrading || this.upgradeQueue.length === 0) { return; } this.isUpgrading = true; const queueItem = this.upgradeQueue.shift(); const { collectionName, batchCollections, resolve, reject } = queueItem; try { // 首先关闭现有连接,避免版本升级被阻塞 this.close(); // 重新初始化数据库以获取最新连接 const db = await this.init(); // 执行版本升级 const newVersion = db.version + 1; // 判断是单个集合还是批量集合升级 const collectionsToCreate = batchCollections || [collectionName]; const isBatchOperation = !!batchCollections; const request = indexedDB.open(this.dbName, newVersion); // 处理版本升级被阻塞的情况 request.onblocked = (event) => { console.warn(`数据库版本升级被阻塞,可能有其他打开的连接。尝试关闭所有连接...`); // 再次关闭连接,确保没有其他连接阻止升级 this.close(); }; request.onupgradeneeded = (event) => { const newDb = event.target.result; // 创建所有需要的对象存储空间 for (const name of collectionsToCreate) { if (!newDb.objectStoreNames.contains(name)) { newDb.createObjectStore(name, { keyPath: 'id' }); } } }; request.onsuccess = (event) => { this.db = event.target.result; // 将所有创建的集合添加到缓存 for (const name of collectionsToCreate) { this.collections.add(name); } resolve(true); // 处理下一个队列项 this.isUpgrading = false; this._processUpgradeQueue(); }; request.onerror = (event) => { console.error(`${isBatchOperation ? `批量创建集合失败` : `创建集合${collectionName}失败`}:`, event.target.error); reject(event.target.error); // 即使出错,也要处理下一个队列项 this.isUpgrading = false; this._processUpgradeQueue(); }; } catch (error) { console.error(`处理队列项时发生错误:`, error); reject(error); // 即使出错,也要处理下一个队列项 this.isUpgrading = false; this._processUpgradeQueue(); } } /** * 执行IndexedDB事务 */ async transaction(collectionName, mode, callback) { try { // 确保数据库已初始化 if (!this.db) { await this.init(); } // 确保集合存在 await this.ensureCollection(collectionName); // 执行事务 return new Promise((resolve, reject) => { try { const transaction = this.db.transaction([collectionName], mode); const store = transaction.objectStore(collectionName); // 设置事务事件处理器 transaction.onerror = (event) => { console.error(`IndexedDB事务错误: ${collectionName}`, event.target.error); reject(event.target.error); }; transaction.onabort = (event) => { console.error(`IndexedDB事务中止: ${collectionName}`, event.target.error); reject(event.target.error); }; // 执行回调并处理返回结果 try { const result = callback(store); // 如果回调返回的是Promise,等待其完成 if (result instanceof Promise) { result.then(resolve).catch(reject); } else { resolve(result); } } catch (callbackError) { console.error(`回调执行出错: collection=${collectionName}`, callbackError); reject(callbackError); } } catch (error) { console.error('创建事务时出错:', error); reject(error); } }); } catch (error) { console.error(`IndexedDB事务失败: ${collectionName}`, error); // 不抛出错误,而是返回一个被拒绝的Promise,确保上层调用者能捕获错误 return Promise.reject(error); } } /** * 存储单个实体 */ async put(collectionName, entity) { return this.transaction(collectionName, 'readwrite', (store) => { return store.put(entity); }); } /** * 存储多个实体 */ async putAll(collectionName, entities) { return this.transaction(collectionName, 'readwrite', (store) => { return new Promise((resolve, reject) => { const requests = entities.map(entity => store.put(entity)); let completed = 0; requests.forEach(request => { request.onsuccess = () => { completed++; if (completed === requests.length) { resolve(entities.length); } }; request.onerror = (event) => { reject(event.target.error); }; }); }); }); } /** * 获取单个实体 */ async get(collectionName, id) { return this.transaction(collectionName, 'readonly', (store) => { return store.get(id); }); } /** * 获取所有实体 */ async getAll(collectionName) { return this.transaction(collectionName, 'readonly', (store) => { return store.getAll(); }); } /** * 删除单个实体 */ async delete(collectionName, id) { return this.transaction(collectionName, 'readwrite', (store) => { return store.delete(id); }); } /** * 清空集合 */ async clear(collectionName) { return this.transaction(collectionName, 'readwrite', (store) => { return store.clear(); }); } /** * 获取集合中实体的数量 */ async count(collectionName) { return this.transaction(collectionName, 'readonly', (store) => { return store.count(); }); } /** * 查询实体 */ async query(collectionName, query) { return this.transaction(collectionName, 'readonly', (store) => { const results = []; const request = store.openCursor(); return new Promise((resolve, reject) => { request.onsuccess = (event) => { const cursor = event.target.result; if (cursor) { // 执行查询条件 if (!query || this.matchesQuery(cursor.value, query)) { results.push(cursor.value); } cursor.continue(); } else { resolve(results); } }; request.onerror = (event) => { reject(event.target.error); }; }); }); } /** * 检查实体是否匹配查询条件 */ matchesQuery(entity, query) { for (const key in query) { if (entity[key] !== query[key]) { return false; } } return true; } /** * 存储元数据 */ async setMetadata(key, value) { return this.put('metadata', { key, value }); } /** * 获取元数据 */ async getMetadata(key) { const metadata = await this.get('metadata', key); return metadata ? metadata.value : null; } /** * 关闭数据库连接 */ close() { if (this.db) { this.db.close(); this.db = null; } } /** * 删除整个数据库 */ async deleteDatabase() { return new Promise((resolve, reject) => { this.close(); const request = indexedDB.deleteDatabase(this.dbName); request.onsuccess = () => resolve(true); request.onerror = (event) => reject(event.target.error); }); } } export { IndexedDBManager }; ================================================================================ /* 文件路径: data/LazyLoader.js */ /** * 懒加载器 - Web版 (优化版) * 修复了缓存键设计、集合名解析和数组处理问题 */ class LazyLoader { 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' }; } /** * 清除缓存 */ clearCache() { this.proxyCache = new 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) { // 基本类型直接返回 if (typeof relatedItem !== 'object' || relatedItem === null) { return relatedItem; } // 去掉数字后缀 (如 mupi1 → mupi) const basePropName = propName.replace(/\d/g, ''); // 获取实体类型 let entityType; // 检查是否已在映射表中 if (this.entityToCollectionMap[basePropName]) { entityType = basePropName; } else { // 尝试从集合名反向查找实体类型 entityType = Object.keys(this.entityToCollectionMap).find( key => this.entityToCollectionMap[key] === basePropName ) || basePropName; } // 获取集合名 let collectionName; // 检查映射表中是否有对应的集合名 if (this.entityToCollectionMap[entityType]) { collectionName = this.entityToCollectionMap[entityType]; } else { // 动态处理新的实体类型,默认添加's'后缀 collectionName = `${entityType}s`; // 将新的映射关系添加到映射表中 this.entityToCollectionMap[entityType] = collectionName; } // 获取数据集合 const collection = this.dataManager._rawData[collectionName]; if (!collection) return relatedItem; // 查找完整对象 const fullItem = collection.find(item => item?.id === relatedItem?.id); if (!fullItem) return relatedItem; // 返回代理对象 return this.createProxy(fullItem, entityType); } } export { LazyLoader }; ================================================================================ /* 文件路径: data/TransactionApi.js */ /** * 事务API - Web版 * 负责处理所有事务相关的操作 */ class TransactionApi { constructor(dataManager) { this.dataManager = dataManager; this.baseUrl = dataManager.baseUrl; } /** * 执行事务操作 * @param {string} endpoint - 事务端点 * @param {Object} data - 事务数据 * @returns {Promise} 操作结果 */ async execute(endpoint, data) { try { 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'); } // 在WebSocket模式下,数据会自动更新,无需手动同步 // 仅触发事务成功回调 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 {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} 操作结果 */ async updateStock(params) { return this.execute('kucunbianji', params); } /** * 生产消耗事务 * @param {Object} params - 生产消耗参数 * @returns {Promise} 操作结果 */ async shengchanXiaohao(params) { return this.execute('shengchanXiaohao', params); } /** * 批量更新订单板材事务 * @param {Object} params - 批量更新参数 * @returns {Promise} 操作结果 */ async batchUpdateDingdanBancai(params) { return this.execute('batchUpdateDingdanBancai', params); } /** * 提交订单板材采购事务 * @param {Object} params - 提交参数 * @returns {Promise} 操作结果 */ async submitDingdanBancai(params) { return this.execute('submitDingdanBancai', params); } /** * 消耗订单板材事务 * @param {Object} params - 消耗参数 * @returns {Promise} 操作结果 */ async consumeDingdanBancai(params) { return this.execute('consumeDingdanBancai', params); } /** * 采购订单板材事务 * @param {Object} params - 采购参数 * @returns {Promise} 操作结果 */ async 采购DingdanBancai(params) { return this.execute('采购DingdanBancai', params); } /** * 保存所有数据事务 * @param {Object} params - 保存参数 * @returns {Promise} 操作结果 */ async saveAll(params) { try { 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'); } // 在WebSocket模式下,数据会自动更新,无需手动同步 return result.data; } catch (error) { console.error('Save all failed:', error); throw error; } } } export { TransactionApi };
09-23
<!-- 审核流程插件基于https://gitee.com/xiaoka2017/easy-flow修改--> <!--感谢萌级小菜鸟 / easy-flow --> <template> <div v-if="easyFlowVisible" class="flow-panel"> <div style="display: flex; height: 100%; position: relative"> <el-scrollbar style="height: 100%; border-right: 1px solid rgb(220, 227, 232)"> <div style="width: 220px"> <div class="ef-node-pmenu-item"> <i class="el-icon-warning-outline"></i>基础信息 </div> <div :style="{ 'padding-left': $global.labelPosition == 'top' ? '0' : '10px' }"> <VolForm ref="form" labelPosition="top" :label-width="180" :loadKey="true" :formFields="formFields" :formRules="formRules" ></VolForm> </div> <node-menu @addNode="addNode" ref="nodeMenu"></node-menu> </div> </el-scrollbar> <div class="tools"> <el-button circle @click="zoomAdd"><i class="el-icon-zoom-in"></i></el-button> <el-button circle @click="zoomSub"><i class="el-icon-zoom-out"></i></el-button> </div> <div style="flex: 1" id="efContainer" ref="efContainer" class="container efContainer" v-flowDrag > <template :key="node.id" v-for="node in data.nodeList"> <flow-node :id="node.id" @delNode="deleteNode(node.id)" :node="node" :activeElement="activeElement" @changeNodeSite="changeNodeSite" @nodeRightMenu="nodeRightMenu" @clickNode="clickNode" > </flow-node> </template> <!-- 给画布一个默认的宽度和高度 --> <div style="position: absolute; top: 3000px; left: 4000px"> </div> </div> <!-- 右侧表单 --> <div style="width: 400px; border-left: 1px solid #dce3e8; background-color: #fbfbfb" > <el-scrollbar style="height: 100%; padding-bottom: 10px"> <flow-node-form @delNode="deleteNode" ref="nodeForm" @setLineLabel="setLineLabel" @repaintEverything="repaintEverything" ></flow-node-form> </el-scrollbar> </div> </div> </div> </template> <script> import { VueDraggableNext as draggable } from "vue-draggable-next"; // import { jsPlumb } from 'jsplumb' // 使用修改后的jsplumb import "./jsplumb"; import { easyFlowMixin } from "./mixins"; import flowNode from "./node"; import nodeMenu from "./node_menu"; import FlowNodeForm from "./node_form"; import lodash from "lodash"; // import { getDataA } from './data_A' import VolForm from "@/components/basic/VolForm.vue"; export default { data() { return { formFields: { WorkName: "", WorkTable: "", WorkTableName: "", Weight: 1, AuditingEdit: 0, Remark: "", }, formRules: [ [ { dataKey: "流程名称", title: "流程名称", field: "WorkName", required: true, }, ], [ { dataKey: "", title: "流程实例", required: true, field: "WorkTable", data: [], readonly: false, type: "select", onChange: (value, item) => { this.formRules.forEach((options) => { options.forEach((option) => { if (option.field == "WorkTable") { this.formFields.WorkTableName = option.data.find((x) => { return x.key == value; }).value; } }); }); }, }, ], [ { title: "权重(相同条件权重大优先)", field: "Weight", type: "number", }, ], [ { title: "同步更新审批业务数据", field: "AuditingEdit", type: "switch", data: [ { key: 0, value: "否" }, { key: 1, value: "是" }, ], }, ], [ { title: "备注", field: "Remark", }, ], ], // jsPlumb 实例 jsPlumb: null, // 控制画布销毁 easyFlowVisible: true, // 是否加载完毕标志位 loadEasyFlowFinish: false, // 数据 data: {}, // 激活的元素、可能是节点、可能是连线 activeElement: { // 可选值 node 、line type: undefined, // 节点ID nodeId: undefined, // 连线ID sourceId: undefined, targetId: undefined, }, zoom: 1, }; }, // 一些基础配置移动该文件中 mixins: [easyFlowMixin], components: { draggable, flowNode, nodeMenu, FlowNodeForm, VolForm, }, directives: { flowDrag: { mounted(el, binding, vnode, oldNode) { if (!binding) { return; } el.onmousedown = (e) => { if (e.button == 2) { // 右键不管 return; } // 鼠标按下,计算当前原始距离可视区的高度 let disX = e.clientX; let disY = e.clientY; el.style.cursor = "move"; document.onmousemove = function (e) { // 移动时禁止默认事件 e.preventDefault(); const left = e.clientX - disX; disX = e.clientX; el.scrollLeft += -left; const top = e.clientY - disY; disY = e.clientY; el.scrollTop += -top; }; document.onmouseup = function (e) { el.style.cursor = "auto"; document.onmousemove = null; document.onmouseup = null; }; }; }, }, }, mounted() { this.jsPlumb = jsPlumb.getInstance(); // this.$nextTick(() => { // // 默认加载流程A的数据、在这里可以根据具体的业务返回符合流程数据格式的数据即可 // this.dataReload(getDataA()) // }) }, created() { this.http.get("api/Sys_WorkFlow/getTableInfo").then((result) => { this.formRules.forEach((options) => { options.forEach((option) => { if (option.field == "WorkTable") { option.data = result; } }); }); }); this.$store.getters.data().flowTable = this.formFields; }, methods: { // 返回唯一标识 getUUID() { return Math.random().toString(36).substr(3, 10); }, jsPlumbInit() { this.jsPlumb.ready(() => { // 导入默认配置 this.jsPlumb.importDefaults(this.jsplumbSetting); // 会使整个jsPlumb立即重绘。 this.jsPlumb.setSuspendDrawing(false, true); // 初始化节点 this.loadEasyFlow(); // 单点击了连接线, https://www.cnblogs.com/ysx215/p/7615677.html this.jsPlumb.bind("click", (conn, originalEvent) => { this.activeElement.type = "line"; this.activeElement.sourceId = conn.sourceId; this.activeElement.targetId = conn.targetId; this.$refs.nodeForm.lineInit({ from: conn.sourceId, to: conn.targetId, label: conn.getLabel(), }); this.deleteElement(); }); // 连线 this.jsPlumb.bind("connection", (evt) => { let from = evt.source.id; let to = evt.target.id; if (this.loadEasyFlowFinish) { this.data.lineList.push({ from: from, to: to }); } }); // 删除连线回调 this.jsPlumb.bind("connectionDetached", (evt) => { this.deleteLine(evt.sourceId, evt.targetId); }); // 改变线的连接节点 this.jsPlumb.bind("connectionMoved", (evt) => { this.changeLine(evt.originalSourceId, evt.originalTargetId); }); // 连线右击 this.jsPlumb.bind("contextmenu", (evt) => { console.log("contextmenu", evt); }); // 连线 this.jsPlumb.bind("beforeDrop", (evt) => { let from = evt.sourceId; let to = evt.targetId; // if (from === to) { // this.$message.error("节点不支持连接自己"); // return false; // } if (this.hasLine(from, to)) { this.$message.error("该关系已存在,不允许重复创建"); return false; } // if (this.hashOppositeLine(from, to)) { // this.$message.error("不支持两个节点之间连线回环"); // return false; // } this.$message.success("连接成功"); setTimeout(() => { this.setLineLabel(from, to, "x"); }, 50); return true; }); // beforeDetach this.jsPlumb.bind("beforeDetach", (evt) => { console.log("beforeDetach", evt); }); this.jsPlumb.setContainer(this.$refs.efContainer); }); }, // 加载流程图 loadEasyFlow() { // 初始化节点 for (var i = 0; i < this.data.nodeList.length; i++) { let node = this.data.nodeList[i]; // 设置源点,可以拖出线连接其他节点 this.jsPlumb.makeSource(node.id, lodash.merge(this.jsplumbSourceOptions, {})); // // 设置目标点,其他源点拖出的线可以连接该节点 this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions); if (!node.viewOnly) { this.jsPlumb.draggable(node.id, { containment: "parent", stop: function (el) { // 拖拽节点结束后的对调 console.log("拖拽结束: ", el); }, }); } } // 初始化连线 for (var i = 0; i < this.data.lineList.length; i++) { let line = this.data.lineList[i]; var connParam = { source: line.from, target: line.to, connector: line.connector ? line.connector : "", anchors: line.anchors ? line.anchors : undefined, paintStyle: line.paintStyle ? line.paintStyle : undefined, }; this.jsPlumb.connect(connParam, this.jsplumbConnectOptions); } this.$nextTick(function () { this.loadEasyFlowFinish = true; }); }, // 设置连线条件 setLineLabel(from, to, label) { var conn = this.jsPlumb.getConnections({ source: from, target: to, })[0]; if (!label || label === "") { conn.removeClass("flowLabel "); conn.addClass("emptyFlowLabel"); } else { conn.addClass("flowLabel"); } conn.setLabel({ label: "x", //label, }); this.data.lineList.forEach(function (line) { if (line.from == from && line.to == to) { line.label = "x"; // label } }); }, // 删除激活的元素 deleteElement() { if (this.activeElement.type === "node") { this.deleteNode(this.activeElement.nodeId); } else if (this.activeElement.type === "line") { this.$confirm("确定删除所点击的线吗?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }) .then(() => { var conn = this.jsPlumb.getConnections({ source: this.activeElement.sourceId, target: this.activeElement.targetId, })[0]; this.jsPlumb.deleteConnection(conn); }) .catch(() => {}); } }, // 删除线 deleteLine(from, to) { this.data.lineList = this.data.lineList.filter(function (line) { if (line.from == from && line.to == to) { return false; } return true; }); }, // 改变连线 changeLine(oldFrom, oldTo) { this.deleteLine(oldFrom, oldTo); }, // 改变节点的位置 changeNodeSite(data) { for (var i = 0; i < this.data.nodeList.length; i++) { let node = this.data.nodeList[i]; if (node.id === data.nodeId) { node.left = data.left; node.top = data.top; } } }, /** * 拖拽结束后添加新的节点 * @param evt * @param nodeMenu 被添加的节点对象 * @param mousePosition 鼠标拖拽结束的坐标 */ addNode(evt, nodeMenu, mousePosition) { if ( nodeMenu.type == "start" && this.data.nodeList.some((x) => { return x.type == "start"; }) ) { this.$message.error("【流程结束】节点已存在,只有选择一个流程开始节点"); return; } if ( nodeMenu.type == "end" && this.data.nodeList.some((x) => { return x.type == "end"; }) ) { this.$message.error("【流程结束】节点已存在,只有选择一个流程开始节点"); return; } var screenX = evt.originalEvent.clientX, screenY = evt.originalEvent.clientY; let efContainer = this.$refs.efContainer; var containerRect = efContainer.getBoundingClientRect(); var left = screenX, top = screenY; // 计算是否拖入到容器中 if ( left < containerRect.x || left > containerRect.width + containerRect.x || top < containerRect.y || containerRect.y > containerRect.y + containerRect.height ) { this.$message.error("请把节点拖入到画布中"); return; } left = left - containerRect.x + efContainer.scrollLeft; top = top - containerRect.y + efContainer.scrollTop; // 居中 left -= 85; top -= 16; var nodeId = this.getUUID(); // 动态生成名字 var origName = nodeMenu.name; var nodeName = origName; var index = 1; while (index < 10000) { var repeat = false; for (var i = 0; i < this.data.nodeList.length; i++) { let node = this.data.nodeList[i]; if (node.name === nodeName) { nodeName = origName + index; repeat = true; } } if (repeat) { index++; continue; } break; } var node = { id: nodeId, name: nodeName, type: nodeMenu.type, left: left + "px", top: top + "px", ico: nodeMenu.ico, state: "success", }; /** * 这里可以进行业务判断、是否能够添加该节点 */ this.data.nodeList.push(node); this.$nextTick(function () { this.jsPlumb.makeSource(nodeId, this.jsplumbSourceOptions); this.jsPlumb.makeTarget(nodeId, this.jsplumbTargetOptions); this.jsPlumb.draggable(nodeId, { containment: "parent", stop: function (el) { // 拖拽节点结束后的对调 console.log("拖拽结束: ", el); }, }); }); }, /** * 删除节点 * @param nodeId 被删除节点的ID */ deleteNode(nodeId) { this.$confirm("确定要删除节点" + nodeId + "?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", closeOnClickModal: false, }) .then(() => { /** * 这里需要进行业务判断,是否可以删除 */ this.data.nodeList = this.data.nodeList.filter(function (node) { if (node.id === nodeId) { // 伪删除,将节点隐藏,否则会导致位置错位 // node.show = false return false; } return true; }); this.$nextTick(function () { this.jsPlumb.removeAllEndpoints(nodeId); }); }) .catch(() => {}); return true; }, clickNode(nodeId) { this.activeElement.type = "node"; this.activeElement.nodeId = nodeId; this.$refs.nodeForm.nodeInit(this.data, nodeId, this.formFields.WorkTable); }, // 是否具有该线 hasLine(from, to) { for (var i = 0; i < this.data.lineList.length; i++) { var line = this.data.lineList[i]; if (line.from === from && line.to === to) { return true; } } return false; }, // 是否含有相反的线 hashOppositeLine(from, to) { return this.hasLine(to, from); }, nodeRightMenu(nodeId, evt) { this.menu.show = true; this.menu.curNodeId = nodeId; this.menu.left = evt.x + "px"; this.menu.top = evt.y + "px"; }, repaintEverything(node) { let _node = this.data.nodeList.find((x) => { return x.id == node.id; }); Object.assign(_node, node); console.log(_node); this.jsPlumb.repaint(); }, // 加载流程图 dataReload(data, isAdd) { this.easyFlowVisible = false; this.data.nodeList = []; this.data.lineList = []; this.$nextTick(() => { data = lodash.cloneDeep(data); this.easyFlowVisible = true; this.data = data; this.$nextTick(() => { this.jsPlumb = jsPlumb.getInstance(); this.$nextTick(() => { this.jsPlumbInit(); }); }); }); this.formRules.forEach((options) => { options.forEach((option) => { if (option.field == "WorkTable") { option.readonly = !isAdd; } }); }); }, zoomAdd() { if (this.zoom >= 1) { return; } this.zoom = this.zoom + 0.1; this.$refs.efContainer.style.zoom = this.zoom; // this.jsPlumb.setZoom(this.zoom) }, zoomSub() { if (this.zoom <= 0) { return; } this.zoom = this.zoom - 0.1; if (this.zoom < 0.3) { this.zoom = 0.3; } this.$refs.efContainer.style.zoom = this.zoom; // this.jsPlumb.setZoom(this.zoom) }, }, }; </script> <style scoped lang="less"> @import "./index.css"; .ef-node-pmenu-item { padding: 10px; background: #f8f8f8; font-size: 13px; font-weight: 700; letter-spacing: 1px; border-top: 1px solid #eee; border-bottom: 1px solid #eee; border-right: 1px solid #eee; } .flow-panel { position: absolute; height: 100%; width: 100%; } .flow-panel ::v-deep(.el-form-item__label) { margin-bottom: -2px !important; text-align: left; padding: 0 !important; justify-content: flex-start; } .flow-panel ::v-deep(.el-form-item) { display: flex; flex-direction: column; margin-bottom: 7px !important; } .ef-node-menu-form { padding: 0px; } ::-webkit-scrollbar { width: 0px; height: 0px; } ::-webkit-scrollbar-thumb { border-radius: 0px; background: #e0e3e7; height: 20px; } ::-webkit-scrollbar-track { background-color: transparent; } </style> 解析该代码逻辑
09-11
<template> <div> <div class="search-title">查询条件</div> <div class="search-box"> <a-form-model :model="searchForm" layout="inline" ref="searchForm" class="searchForm"> <a-form-model-item label="题库名称" prop="title"> <a-input v-model="searchForm.title" placeholder="请输入题库名称" /> </a-form-model-item> <a-form-model-item class="searchButton"> <a-button type="primary" v-if="QX.read" @click="getSearch">查询</a-button> <a-button type="default" v-if="QX.read" @click="restSearch('searchForm')">重置</a-button> </a-form-model-item> </a-form-model> </div> <div class="table-operation"> <a-button type="primary" @click="addBank" icon="plus" v-if="QX.add">新增</a-button> </div> <a-table :columns="columns" :data-source="dataList" :pagination="false" :loading="loading" rowKey="questionBankId" :scroll="{ y: this.$getViewportSize().height - 300 }" > <span slot="action" slot-scope="text, record"> <a @click="editAuth(record)" v-if="QX.edit"><a-icon class="iconBtn" type="edit" />编辑</a> <a-divider v-if="QX.edit && QX.delete" type="vertical" /> <a-popconfirm title="确认是否删除?" ok-text="是" cancel-text="否" @confirm="removeBank(record)" > <a v-if="QX.delete"><a-icon class="iconBtn" type="delete" />删除</a> </a-popconfirm> </span> </a-table> <template slot="action" slot-scope="text, record, index"> <a @click="removeQuestion(index)">删除</a> </template> <a-pagination show-size-changer :total="totalPage" :current="pageIndex" :pageSize="pageSize" @showSizeChange="onShowSizeChange" @change="onChangePage" style="float: right; margin-top: 15px" /> <a-drawer :closable="true" :title="title" width="auto" :visible="visible" @close="visible = !visible" > <a-spin class="submitLoading" :spinning="submitLoading"> <a-form-model :model="form" :label-col="labelCol" :wrapper-col="wrapperCol" :rules="rules" ref="form" class="lay-drawer-form"> <a-row> <a-col :span="12"> <a-form-model-item label="题库名称" prop="title"> <a-input v-model="form.title" :maxLength="25" placeholder="请输入题库名称" style="width: 380px" /> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item label="关联岗位" prop="positionId"> <a-select v-model="form.positionId" style="width: 380px" placeholder="请选择岗位"> <a-select-option v-for="(label, value) in positionDict" :key="value" :value="value"> {{ label }} </a-select-option> </a-select> </a-form-model-item> </a-col> <a-col :span="12"> <a-form-model-item label="参与PK" prop="participateInPk"> <a-switch v-model="form.participateInPk" :checkedValue="1" :unCheckedValue="0" /> </a-form-model-item> </a-col> </a-row> </a-form-model> <!-- 新增题目区域 --> <div class="question-batch"> <div style="margin: 20px 0; display: flex; justify-content: space-between; align-items: center;"> <a-button type="primary" @click="downloadTemplate">下载模板</a-button> <div class="import-add-buttons"> <a-upload name="file" :showUploadList="false" :beforeUpload="beforeUpload" accept=".xlsx,.xls" :disabled="!importEnabled" > <a-button :disabled="!importEnabled"><a-icon type="upload" /> 导入题目</a-button> </a-upload> <a-button type="dashed" @click="addTopicVisible = true" :disabled="!addEnabled" > <a-icon type="plus" /> 添加题目 </a-button> </div> </div> <!-- 题目列表容器添加样式 --> <div class="topic-list-container"> <!-- 题目列表 --> <div class="topic-grid"> <!-- 题目编号和内容 --> <div v-for="(topic, index) in topicList" :key="topic.topicId" class="topic-item"> <!-- 题目编号和内容 --> <div class="topic-content"> <strong>{{ index + 1 }}. {{ topic.content }}</strong> </div> <!-- 选项列表 --> <div class="options" v-if="topic.topicType === 1"> <label v-for="option in getOptions(topic)" :key="option.key"> <input type="radio" :name="'topic' + topic.topicId" :value="option.key" :checked="topic.correctAnswer === option.key" /> <!-- @change="checkAnswer(topic, option.key)"--> {{ option.key }}. {{ option.value }} </label> </div> <!-- 判断题 --> <div v-if="topic.topicType === 2" class="options"> <label> <input type="radio" :name="'topic' + topic.topicId" value="正确" :checked="topic.correctAnswer === '正确'" /> <!-- @change="checkAnswer(topic, '正确')"--> 正确 </label> <label> <input type="radio" :name="'topic' + topic.topicId" value="错误" :checked="topic.correctAnswer === '错误'" /> <!-- @change="checkAnswer(topic, '错误')"--> 错误 </label> </div> <!-- 删除按钮 --> <div class="topic-delete"> <a @click="removeQuestion(topic.topicId)"> <a-icon type="delete" /> 删除 </a> </div> </div> </div> <!-- 分页组件移到题目列表区域外面 --> <div class="pagination-wrapper"> <a-pagination v-model="topicPageNum" :pageSize="topicPageSize" :total="totalTopicCount" @change="handleTopicPageChange" style="text-align: right;" /> </div> </div> </div> <div :style="{ position: 'absolute', right: 0, bottom: 0, width: '100%', borderTop: '1px solid #e9e9e9', padding: '8px 16px', background: '#fff', textAlign: 'right', zIndex: 1, }"> <a-button type="default" @click="visible = !visible" > 取消 </a-button> <a-button type="primary" @click="submitForm" > 确认 </a-button> </div> </a-spin> </a-drawer> <!-- 新增题目抽屉 --> <a-drawer title="新增题目" :visible="addTopicVisible" @close="addTopicVisible = false" width="500" > <a-form-model :model="addTopicForm" layout="vertical" :rules="rulesForAddTopic" ref="addTopicFormRef"> <!-- 题目类型 --> <a-form-model-item label="题目类型" prop="topicType"> <a-select v-model="addTopicForm.topicType" style="width: 100%"> <a-select-option :value="1">选择题</a-select-option> <a-select-option :value="2">判断题</a-select-option> </a-select> </a-form-model-item> <!-- 题目内容 --> <a-form-model-item label="题目内容" prop="content"> <a-input v-model="addTopicForm.content" placeholder="请输入题目内容" /> </a-form-model-item> <!-- 选择题选项 --> <div v-if="addTopicForm.topicType === 1"> <a-form-model-item label="选项A" prop="optionA"> <a-input v-model="addTopicForm.optionA" placeholder="请输入选项A内容" /> </a-form-model-item> <a-form-model-item label="选项B" prop="optionB"> <a-input v-model="addTopicForm.optionB" placeholder="请输入选项B内容" /> </a-form-model-item> <a-form-model-item label="选项C" prop="optionC"> <a-input v-model="addTopicForm.optionC" placeholder="请输入选项C内容" /> </a-form-model-item> <a-form-model-item label="选项D" prop="optionD"> <a-input v-model="addTopicForm.optionD" placeholder="请输入选项D内容" /> </a-form-model-item> <a-form-model-item label="正确答案" prop="correctAnswer"> <a-select v-model="addTopicForm.correctAnswer" style="width: 100%"> <a-select-option value="A">A</a-select-option> <a-select-option value="B">B</a-select-option> <a-select-option value="C">C</a-select-option> <a-select-option value="D">D</a-select-option> </a-select> </a-form-model-item> </div> <!-- 判断题选项 --> <div v-if="addTopicForm.topicType === 2"> <a-form-model-item label="正确答案" prop="correctAnswer"> <a-select v-model="addTopicForm.correctAnswer" style="width: 100%"> <a-select-option value="正确">正确</a-select-option> <a-select-option value="错误">错误</a-select-option> </a-select> </a-form-model-item> </div> <div class="drawer-footer"> <a-button @click="addTopicVisible = false">取消</a-button> <a-button type="primary" @click="saveNewTopic">保存</a-button> </div> </a-form-model> </a-drawer> </div> </template> <script> import { req, fileDownload } from '../../../api/axiosFun'; import preventBack from 'vue-prevent-browser-back'; export default { name: 'Bank', mixins: [preventBack], data() { return { QX: {}, topicQX: {}, topicList: [], totalTopicCount: 0, // 题目总数 topicPageNum: 1, // 当前页码 topicPageSize: 10, // 每页数量 addTopicVisible: false, addTopicForm: { content: '', // 题目内容 topicType: 1, // 题目类型:1=选择题,2=判断题 optionA: '', optionB: '', optionC: '', optionD: '', correctAnswer: '', }, disabled: false, checkedKeys: [], selectAuth: [], treeData: [], positionDict: {}, title: '', labelCol: { span: 4 }, wrapperCol: { span: 20 }, tableHeight: 0, expanded: false, // 筛选条件是否展开 form: { questionBankId: 0, bankCode: '', title: '', positionId: '', participateInPk: true, }, isEdit: false, // 是否是编辑状态 isAdd: false, // 是否是新增状态 importEnabled: false, // 导入题目按钮是否可用 - 默认为不可用 addEnabled: false, // 添加题目按钮是否可用 - 默认为不可用 rules: { positionId: [ { required: true, message: '请选择岗位', trigger: 'blur' }, ], title: [ { required: true, message: '请输入题库名称', trigger: 'blur' }, ], }, rulesForAddTopic: { content: [ { required: true, message: '请输入题目内容', trigger: ['blur', 'change'] }, ], topicType: [ { required: true, message: '请选择题目类型', trigger: 'change' }, ], optionA: [ { required: (rule, value) => this.addTopicForm.topicType === 1, message: '选择题必须输入选项A', trigger: ['blur', 'change'], }, ], optionB: [ { required: (rule, value) => this.addTopicForm.topicType === 1, message: '选择题必须输入选项B', trigger: ['blur', 'change'], }, ], optionC: [ { required: (rule, value) => this.addTopicForm.topicType === 1, message: '选择题必须输入选项C', trigger: ['blur', 'change'], }, ], optionD: [ { required: (rule, value) => this.addTopicForm.topicType === 1, message: '选择题必须输入选项D', trigger: ['blur', 'change'], }, ], correctAnswer: [ { required: true, message: '请选择正确答案', trigger: 'change' }, ], }, searchForm: { title: '', }, visible: false, dataList: [], columns, loading: false, submitLoading: false, pageIndex: 1, pageSize: 10, totalPage: 0, ops: { vuescroll: {}, scrollPanel: {}, rail: { keepShow: true, }, bar: { hoverStyle: true, onlyShowBarOnScroll: false, // 是否只有滚动的时候才显示滚动条 background: '#F5F5F5', // 滚动条颜色 opacity: 1, // 滚动条透明度 'overflow-x': 'hidden', }, }, }; }, watch: { 'addTopicForm.topicType': function (newVal) { // 当题目类型变化时,触发相关字段的验证 this.$nextTick(() => { if (this.$refs.addTopicFormRef) { // 验证选项字段 if (newVal === 1) { this.$refs.addTopicFormRef.validateFields(['optionA', 'optionB', 'optionC', 'optionD']); } else { // 清除非必填字段的验证状态 this.$refs.addTopicFormRef.clearValidate(['optionA', 'optionB', 'optionC', 'optionD']); } } }); }, visible(newVal, oldVal) { if (!newVal) { this.restForm('form'); this.form.questionBankId = 0; this.checkedKeys = []; this.selectAuth = []; this.treeData = []; // 重置状态标志 this.isEdit = false; this.isAdd = false; } else { // 当抽屉打开时强制更新按钮状态 this.$nextTick(() => { this.$forceUpdate(); }); } }, }, mounted() { this.actionTitle = '操作'; this.getDict('position').then(res => { const dictMap = {}; res.data.forEach(item => { dictMap[item.dicValue] = item.dicDisplayName; }); this.positionDict = dictMap; }); this.getBankList(); }, methods: { // 获取字典数据方法 getDict(type) { return req('get', `/dict/getDictItemByNo`, { dicNo: type }) .then((res) => { if (res.result === 'success') { return res; } throw new Error(res.message || '获取字典数据失败'); }) .catch((error) => { console.error(`获取${type}字典失败:`, error); throw error; }); }, /* 新增题库 */ addBank() { this.visible = true; this.disabled = false; this.title = '新增题库'; // 获取功能权限树 req('get', '/bank/getTree', {}).then((res) => { this.treeData = res.data; }); // 初始化题目列表为空 this.topicList = []; this.totalTopicCount = 0; this.topicPageNum = 1; this.isEdit = false; // 不是编辑状态 this.isAdd = true; // 设置为新增状态 // 在新增状态下禁用导入和添加功能 this.importEnabled = false; this.addEnabled = false; // 强制更新按钮状态 this.$nextTick(() => { this.$forceUpdate(); }); }, // 查询 getSearch() { this.pageIndex = 1; this.getBankList(); }, /* 重置查询 */ restSearch(form) { this.restForm(form); this.pageIndex = 1; this.getBankList(); }, /* 删除题库 */ removeBank(record) { this.loading = true; req('post', '/bank/removeBQuestionBank', { questionBankId: record.questionBankId, }).then((res) => { this.loading = false; if (res.result === 'success') { this.$message.success(res.message); this.getBankList(); } }); }, downloadTemplate() { fileDownload('get', '/topic/downloadTemplate', null).then((res) => { const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', }); const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = '题库模板.xlsx'; link.click(); window.URL.revokeObjectURL(link.href); }).catch(() => { this.$message.error('下载失败'); }); }, beforeUpload(file) { // 显示加载状态 this.loading = true; const formData = new FormData(); formData.append('file', file); formData.append('questionBankId', this.form.questionBankId); // 添加 questionBankId 字段 req('post', '/topic/import', formData, { headers: { 'Content-Type': 'multipart/form-data', }, }) .then((res) => { // 隐藏加载状态 this.loading = false; if (res.result === 'success') { // 确保在DOM更新后再刷新题目列表 this.$nextTick(() => { // 刷新题目列表 this.getTopicList(this.form.questionBankId, this.topicPageNum, this.topicPageSize) .then(() => { this.$message.success('导入成功'); }) .catch(() => { this.$message.error('刷新题目列表失败'); }); }); } else { this.$message.error(res.message || '导入失败'); } }) .catch((error) => { // 隐藏加载状态 this.loading = false; this.$message.error('导入失败,请重试'); console.error('导入题目出错:', error); }); return false; // 阻止默认上传行为 }, // 获取题库下的题目列表(返回Promise以便链式调用) getTopicList(questionBankId, pageNum = 1, pageSize = 10) { return new Promise((resolve, reject) => { req('post', '/topic/list', { questionBankId, page: pageNum, rows: pageSize, }).then((res) => { if (res.result === 'success') { this.topicQX = res.QX; if (!this.topicQX.edit && !this.topicQX.delete) { this.hideAction(); } else { this.topicList = res.data.map((item, index) => ({ index: (pageNum - 1) * pageSize + index + 1, content: item.content, optionA: item.optionA, optionB: item.optionB, optionC: item.optionC, optionD: item.optionD, topicId: item.topicId, correctAnswer: item.correctAnswer, topicType: item.topicType, })); } // 更新总条数用于分页组件 this.totalTopicCount = res.page.totalResult; resolve(res); } else { this.$message.error(res.message || '获取题目列表失败'); reject(res); } }).catch((error) => { this.$message.error('获取题目列表失败'); reject(error); }); }); }, handleTopicPageChange(page) { this.topicPageNum = page; this.getTopicList(this.form.questionBankId, page, this.topicPageSize); }, // 获取选择题的选项 getOptions(topic) { if (topic.topicType === 1) { // 选择题:返回 A/B/C/D 选项 const options = []; if (topic.optionA) options.push({ key: 'A', value: topic.optionA }); if (topic.optionB) options.push({ key: 'B', value: topic.optionB }); if (topic.optionC) options.push({ key: 'C', value: topic.optionC }); if (topic.optionD) options.push({ key: 'D', value: topic.optionD }); return options; } else if (topic.topicType === 2) { // 判断题:直接返回 正确/错误 return [ { key: '正确', value: '正确' }, { key: '错误', value: '错误' }, ]; } return []; }, checkAnswer(topic, selectedAnswer) { // 仅记录用户选择的答案,不进行是否正确的判断 this.$set(topic, 'userAnswer', selectedAnswer); }, resetAddTopicForm() { this.addTopicForm = { content: '', topicType: 1, optionA: '', optionB: '', optionC: '', optionD: '', correctAnswer: '', }; }, saveNewTopic() { this.$refs.addTopicFormRef.validate((valid, fields) => { if (!valid) { console.log('表单验证失败:', fields); // 找出第一个错误字段并聚焦 const firstErrorField = Object.keys(fields).find(key => fields[key]); if (firstErrorField && this.$refs.addTopicFormRef) { const formItem = this.$refs.addTopicFormRef.$children.find( child => child.prop === firstErrorField, ); if (formItem && formItem.$el) { const input = formItem.$el.querySelector('input, select, textarea'); if (input) input.focus(); } } return; } // 验证通过,处理保存逻辑 const newTopic = { ...this.addTopicForm }; // 发送请求保存题目 req('post', '/topic/add', { questionBankId: this.form.questionBankId, optionA: newTopic.optionA, optionB: newTopic.optionB, optionC: newTopic.optionC, optionD: newTopic.optionD, correctAnswer: newTopic.correctAnswer, content: newTopic.content, topicType: newTopic.topicType, }).then((res) => { if (res.result === 'success') { this.$message.success('题目添加成功'); this.addTopicVisible = false; this.getTopicList(this.form.questionBankId); // 刷新题目列表 } else { this.$message.error(res.message || '保存失败'); } this.resetAddTopicForm(); }).catch((err) => { this.$message.error('网络异常,请重试'); }); }); }, /* 删除题库下的题目 */ removeQuestion(topicId) { this.$confirm({ title: '确认删除该题目?', content: '删除后将无法恢复', okText: '是', cancelText: '否', onOk: () => { req('post', '/topic/removeBTopic', { topicId, questionBankId: this.form.questionBankId, }).then((res) => { if (res.result === 'success') { this.$message.success(res.message); // 刷新题目列表 this.getTopicList(this.form.questionBankId, this.topicPageNum, this.topicPageSize); } else { this.$message.error(res.message || '删除失败'); } }); }, }); }, editAuth(record) { this.loading = true; req('post', '/bank/getBQuestionBank', { questionBankId: record.questionBankId, }).then((res) => { this.loading = false; if (res.result === 'success') { this.visible = true; this.disabled = true; this.title = '修改题库'; this.isEdit = true; // 设置为编辑状态 this.isAdd = false; // 不是新增状态 // 在编辑状态下启用导入和添加功能 this.importEnabled = true; this.addEnabled = true; // 强制更新按钮状态 this.$nextTick(() => { this.$forceUpdate(); }); const bank = res.data; this.$nextTick(() => { this.form.questionBankId = bank.questionBankId; this.form.title = bank.title; this.form.participateInPk = Boolean(bank.participateInPk); this.form.positionId = bank.positionId; this.treeData = bank.treeData; this.checkedKeys = bank.auths; // 获取题目列表 this.topicPageNum = 1; this.getTopicList(bank.questionBankId, this.topicPageNum, this.topicPageSize); }); } }); }, /* 保存or修改题库信息 */ submitForm() { this.$refs.form.validate((valid) => { if (valid) { this.form.participateInPk = this.form.participateInPk ? 1 : 0; const url = this.form.questionBankId ? 'edit' : 'add'; const selectAuth = this.selectAuth; this.form.selectAuth = JSON.stringify(selectAuth); this.submitLoading = true; req('post', `/bank/${url}`, this.form).then((res) => { if (res.result === 'success') { this.visible = false; this.getBankList(); // 如果是新增题库且成功,获取题目列表 if (!this.form.questionBankId && res.data && res.data.questionBankId) { this.form.questionBankId = res.data.questionBankId; this.topicPageNum = 1; this.getTopicList(res.data.questionBankId, this.topicPageNum, this.topicPageSize); // 新增成功后启用导入和添加功能 this.importEnabled = true; this.addEnabled = true; } this.$message.success(res.message); // 重置新增/编辑状态 this.isEdit = false; this.isAdd = false; } this.submitLoading = false; }); } }); }, /* 重置表单 */ restForm(form) { this.$refs[form].resetFields(); }, /* 改变页数事件 */ onChangePage(page, pageSize) { this.pageIndex = page; this.getBankList(); }, /* 改变每页显示条数 */ onShowSizeChange(current, pageSize) { this.pageIndex = 1; this.pageSize = pageSize; this.getBankList(); }, /* 题库信息列表 */ getBankList() { this.loading = true; this.searchForm.page = this.pageIndex; this.searchForm.rows = this.pageSize; req('post', '/bank/list', this.searchForm) .then((res) => { if (res.result === 'success') { this.dataList = res.data; this.QX = res.QX; // 无权限隐藏操作列 if (!this.QX.edit && !this.QX.delete) { this.hideAction(); } else if (columns[columns.length - 1].title != '操作') { columns.push(actionShow); } this.totalPage = res.page.totalResult; } this.loading = false; }).catch((error) => { this.loading = false; }); }, /* 无所有行操作权限时,隐藏操作栏 */ hideAction() { if (columns[columns.length - 1].title == '操作') { columns.splice(columns.length - 1, 1); } }, /* 校验代号类型 */ validCode(value) { if (value.length > 20) { value = value.slice(0, 20); } for (let i = value.length - 1; i >= 0; i--) { const unicode = value.charCodeAt(i); if (unicode > 65280 && unicode < 65375) { value = value.substr(0, i); } } this.value = value; }, }, }; const actionShow = { title: '操作', width: '200px', hide: true, dataIndex: 'action', key: 'action', align: 'center', scopedSlots: { customRender: 'action' }, }; const columns = [ { title: '序号', width: '50px', align: 'center', customRender: (text, row, index) => index + 1, }, { title: '题库名称', align: 'center', dataIndex: 'title', key: 'title', width: '120px', }, { title: '涉及岗位', align: 'center', dataIndex: 'positionName', key: 'positionName', width: '110px', }, { title: '题目数量', dataIndex: 'topicCount', key: 'topicCount', align: 'center', width: '180px', }, { title: '操作', width: '150px', hide: true, dataIndex: 'action', key: 'action', align: 'center', scopedSlots: { customRender: 'action' }, }, ]; </script> <style scoped> .topic-list-container { max-height: calc(100vh - 400px); /* 根据实际布局调整最大高度 */ overflow-y: auto; } .pagination-wrapper { position: sticky; bottom: 0; left: 0; right: 0; background: #fff; padding: 10px; z-index: 1; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); margin-top: 15px; } .topic-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; } .topic-item { border: 1px solid #e8e8e8; padding: 15px; border-radius: 4px; position: relative; /* 为绝对定位的子元素提供定位上下文 */ } .topic-content { font-size: 16px; margin-bottom: 10px; } .options label { display: block; margin: 5px 0; font-size: 14px; } .options input[type="radio"] { margin-right: 5px; } /* 按钮禁用状态样式优化 */ .ant-upload.ant-upload-disabled .ant-upload-list, .ant-btn[disabled] { cursor: not-allowed; opacity: 0.5; } .topic-delete { position: absolute; top: 10px; right: 10px; } </style> <style scoped> @import "../../../assets/css/maincss.css"; </style> 在编辑页面中选择某个题目的选项后,再次打开该题时,默认显示的是上次选择的选项而不是正确答案。修改此问题
07-02
// prng4.js - uses Arcfour as a PRNG function Arcfour() { this.i = 0; this.j = 0; this.S = new Array(); } // Initialize arcfour context from key, an array of ints, each from [0..255] function ARC4init(key) { var i, j, t; for(i = 0; i < 256; ++i) this.S[i] = i; j = 0; for(i = 0; i < 256; ++i) { j = (j + this.S[i] + key[i % key.length]) & 255; t = this.S[i]; this.S[i] = this.S[j]; this.S[j] = t; } this.i = 0; this.j = 0; } function ARC4next() { var t; this.i = (this.i + 1) & 255; this.j = (this.j + this.S[this.i]) & 255; t = this.S[this.i]; this.S[this.i] = this.S[this.j]; this.S[this.j] = t; return this.S[(t + this.S[this.i]) & 255]; } Arcfour.prototype.init = ARC4init; Arcfour.prototype.next = ARC4next; // Plug in your RNG constructor here function prng_newstate() { return new Arcfour(); } // Pool size must be a multiple of 4 and greater than 32. // An array of bytes the size of the pool will be passed to init() var rng_psize = 256; var rng_state; var rng_pool; var rng_pptr; // Mix in a 32-bit integer into the pool function rng_seed_int(x) { rng_pool[rng_pptr++] ^= x & 255; rng_pool[rng_pptr++] ^= (x >> 8) & 255; rng_pool[rng_pptr++] ^= (x >> 16) & 255; rng_pool[rng_pptr++] ^= (x >> 24) & 255; if(rng_pptr >= rng_psize) rng_pptr -= rng_psize; } // Mix in the current time (w/milliseconds) into the pool function rng_seed_time() { rng_seed_int(new Date().getTime()); } // Initialize the pool with junk if needed. if(rng_pool == null) { rng_pool = new Array(); rng_pptr = 0; var t; while(rng_pptr < rng_psize) { // extract some randomness from Math.random() t = Math.floor(65536 * Math.random()); rng_pool[rng_pptr++] = t >>> 8; rng_pool[rng_pptr++] = t & 255; } rng_pptr = 0; rng_seed_time(); //rng_seed_int(window.screenX); //rng_seed_int(window.screenY); } function rng_get_byte() { if(rng_state == null) { rng_seed_time(); rng_state = prng_newstate(); rng_state.init(rng_pool); for(rng_pptr = 0; rng_pptr < rng_pool.length; ++rng_pptr) rng_pool[rng_pptr] = 0; rng_pptr = 0; //rng_pool = null; } // TODO: allow reseeding after first request return rng_state.next(); } function rng_get_bytes(ba) { var i; for(i = 0; i < ba.length; ++i) ba[i] = rng_get_byte(); } function SecureRandom() {} SecureRandom.prototype.nextBytes = rng_get_bytes; var dbits; // JavaScript engine analysis var canary = 0xdeadbeefcafe; var j_lm = ((canary&0xffffff)==0xefcafe); // (public) Constructor function BigInteger(a,b,c) { if(a != null) if("number" == typeof a) this.fromNumber(a,b,c); else if(b == null && "string" != typeof a) this.fromString(a,256); else this.fromString(a,b); } // return new, unset BigInteger function nbi() { return new BigInteger(null); } // am: Compute w_j += (x*this_i), propagate carries, // c is initial carry, returns final carry. // c < 3*dvalue, x < 2*dvalue, this_i < dvalue // We need to select the fastest one that works in this environment. // am1: use a single mult and divide to get the high bits, // max digit bits should be 26 because // max internal value = 2*dvalue^2-2*dvalue (< 2^53) function am1(i,x,w,j,c,n) { while(--n >= 0) { var v = x*this[i++]+w[j]+c; c = Math.floor(v/0x4000000); w[j++] = v&0x3ffffff; } return c; } // am2 avoids a big mult-and-extract completely. // Max digit bits should be <= 30 because we do bitwise ops // on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) function am2(i,x,w,j,c,n) { var xl = x&0x7fff, xh = x>>15; while(--n >= 0) { var l = this[i]&0x7fff; var h = this[i++]>>15; var m = xh*l+h*xl; l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); w[j++] = l&0x3fffffff; } return c; } // Alternately, set max digit bits to 28 since some // browsers slow down when dealing with 32-bit numbers. function am3(i,x,w,j,c,n) { var xl = x&0x3fff, xh = x>>14; while(--n >= 0) { var l = this[i]&0x3fff; var h = this[i++]>>14; var m = xh*l+h*xl; l = xl*l+((m&0x3fff)<<14)+w[j]+c; c = (l>>28)+(m>>14)+xh*h; w[j++] = l&0xfffffff; } return c; } BigInteger.prototype.am = am3; dbits = 28; BigInteger.prototype.DB = dbits; BigInteger.prototype.DM = ((1<<dbits)-1); BigInteger.prototype.DV = (1<<dbits); var BI_FP = 52; BigInteger.prototype.FV = Math.pow(2,BI_FP); BigInteger.prototype.F1 = BI_FP-dbits; BigInteger.prototype.F2 = 2*dbits-BI_FP; // Digit conversions var BI_RM = "0123456789abcdefghijklmnopqrstuvwxyz"; var BI_RC = new Array(); var rr,vv; rr = "0".charCodeAt(0); for(vv = 0; vv <= 9; ++vv) BI_RC[rr++] = vv; rr = "a".charCodeAt(0); for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; rr = "A".charCodeAt(0); for(vv = 10; vv < 36; ++vv) BI_RC[rr++] = vv; function int2char(n) { return BI_RM.charAt(n); } function intAt(s,i) { var c = BI_RC[s.charCodeAt(i)]; return (c==null)?-1:c; } // (protected) copy this to r function bnpCopyTo(r) { for(var i = this.t-1; i >= 0; --i) r[i] = this[i]; r.t = this.t; r.s = this.s; } // (protected) set from integer value x, -DV <= x < DV function bnpFromInt(x) { this.t = 1; this.s = (x<0)?-1:0; if(x > 0) this[0] = x; else if(x < -1) this[0] = x+DV; else this.t = 0; } // return bigint initialized to value function nbv(i) { var r = nbi(); r.fromInt(i); return r; } // (protected) set from string and radix function bnpFromString(s,b) { var k; if(b == 16) k = 4; else if(b == 8) k = 3; else if(b == 256) k = 8; // byte array else if(b == 2) k = 1; else if(b == 32) k = 5; else if(b == 4) k = 2; else { this.fromRadix(s,b); return; } this.t = 0; this.s = 0; var i = s.length, mi = false, sh = 0; while(--i >= 0) { var x = (k==8)?s[i]&0xff:intAt(s,i); if(x < 0) { if(s.charAt(i) == "-") mi = true; continue; } mi = false; if(sh == 0) this[this.t++] = x; else if(sh+k > this.DB) { this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<<sh; this[this.t++] = (x>>(this.DB-sh)); } else this[this.t-1] |= x<<sh; sh += k; if(sh >= this.DB) sh -= this.DB; } if(k == 8 && (s[0]&0x80) != 0) { this.s = -1; if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)<<sh; } this.clamp(); if(mi) BigInteger.ZERO.subTo(this,this); } // (protected) clamp off excess high words function bnpClamp() { var c = this.s&this.DM; while(this.t > 0 && this[this.t-1] == c) --this.t; } // (public) return string representation in given radix function bnToString(b) { if(this.s < 0) return "-"+this.negate().toString(b); var k; if(b == 16) k = 4; else if(b == 8) k = 3; else if(b == 2) k = 1; else if(b == 32) k = 5; else if(b == 4) k = 2; else return this.toRadix(b); var km = (1<<k)-1, d, m = false, r = "", i = this.t; var p = this.DB-(i*this.DB)%k; if(i-- > 0) { if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } while(i >= 0) { if(p < k) { d = (this[i]&((1<<p)-1))<<(k-p); d |= this[--i]>>(p+=this.DB-k); } else { d = (this[i]>>(p-=k))&km; if(p <= 0) { p += this.DB; --i; } } if(d > 0) m = true; if(m) r += int2char(d); } } return m?r:"0"; } // (public) -this function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } // (public) |this| function bnAbs() { return (this.s<0)?this.negate():this; } // (public) return + if this > a, - if this < a, 0 if equal function bnCompareTo(a) { var r = this.s-a.s; if(r != 0) return r; var i = this.t; r = i-a.t; if(r != 0) return (this.s<0)?-r:r; while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; return 0; } // returns bit length of the integer x function nbits(x) { var r = 1, t; if((t=x>>>16) != 0) { x = t; r += 16; } if((t=x>>8) != 0) { x = t; r += 8; } if((t=x>>4) != 0) { x = t; r += 4; } if((t=x>>2) != 0) { x = t; r += 2; } if((t=x>>1) != 0) { x = t; r += 1; } return r; } // (public) return the number of bits in "this" function bnBitLength() { if(this.t <= 0) return 0; return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); } // (protected) r = this << n*DB function bnpDLShiftTo(n,r) { var i; for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; for(i = n-1; i >= 0; --i) r[i] = 0; r.t = this.t+n; r.s = this.s; } // (protected) r = this >> n*DB function bnpDRShiftTo(n,r) { for(var i = n; i < this.t; ++i) r[i-n] = this[i]; r.t = Math.max(this.t-n,0); r.s = this.s; } // (protected) r = this << n function bnpLShiftTo(n,r) { var bs = n%this.DB; var cbs = this.DB-bs; var bm = (1<<cbs)-1; var ds = Math.floor(n/this.DB), c = (this.s<<bs)&this.DM, i; for(i = this.t-1; i >= 0; --i) { r[i+ds+1] = (this[i]>>cbs)|c; c = (this[i]&bm)<<bs; } for(i = ds-1; i >= 0; --i) r[i] = 0; r[ds] = c; r.t = this.t+ds+1; r.s = this.s; r.clamp(); } // (protected) r = this >> n function bnpRShiftTo(n,r) { r.s = this.s; var ds = Math.floor(n/this.DB); if(ds >= this.t) { r.t = 0; return; } var bs = n%this.DB; var cbs = this.DB-bs; var bm = (1<<bs)-1; r[0] = this[ds]>>bs; for(var i = ds+1; i < this.t; ++i) { r[i-ds-1] |= (this[i]&bm)<<cbs; r[i-ds] = this[i]>>bs; } if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<<cbs; r.t = this.t-ds; r.clamp(); } // (protected) r = this - a function bnpSubTo(a,r) { var i = 0, c = 0, m = Math.min(a.t,this.t); while(i < m) { c += this[i]-a[i]; r[i++] = c&this.DM; c >>= this.DB; } if(a.t < this.t) { c -= a.s; while(i < this.t) { c += this[i]; r[i++] = c&this.DM; c >>= this.DB; } c += this.s; } else { c += this.s; while(i < a.t) { c -= a[i]; r[i++] = c&this.DM; c >>= this.DB; } c -= a.s; } r.s = (c<0)?-1:0; if(c < -1) r[i++] = this.DV+c; else if(c > 0) r[i++] = c; r.t = i; r.clamp(); } // (protected) r = this * a, r != this,a (HAC 14.12) // "this" should be the larger one if appropriate. function bnpMultiplyTo(a,r) { var x = this.abs(), y = a.abs(); var i = x.t; r.t = i+y.t; while(--i >= 0) r[i] = 0; for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); r.s = 0; r.clamp(); if(this.s != a.s) BigInteger.ZERO.subTo(r,r); } // (protected) r = this^2, r != this (HAC 14.16) function bnpSquareTo(r) { var x = this.abs(); var i = r.t = 2*x.t; while(--i >= 0) r[i] = 0; for(i = 0; i < x.t-1; ++i) { var c = x.am(i,x[i],r,2*i,0,1); if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { r[i+x.t] -= x.DV; r[i+x.t+1] = 1; } } if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); r.s = 0; r.clamp(); } // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) // r != q, this != m. q or r may be null. function bnpDivRemTo(m,q,r) { var pm = m.abs(); if(pm.t <= 0) return; var pt = this.abs(); if(pt.t < pm.t) { if(q != null) q.fromInt(0); if(r != null) this.copyTo(r); return; } if(r == null) r = nbi(); var y = nbi(), ts = this.s, ms = m.s; var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } else { pm.copyTo(y); pt.copyTo(r); } var ys = y.t; var y0 = y[ys-1]; if(y0 == 0) return; var yt = y0*(1<<this.F1)+((ys>1)?y[ys-2]>>this.F2:0); var d1 = this.FV/yt, d2 = (1<<this.F1)/yt, e = 1<<this.F2; var i = r.t, j = i-ys, t = (q==null)?nbi():q; y.dlShiftTo(j,t); if(r.compareTo(t) >= 0) { r[r.t++] = 1; r.subTo(t,r); } BigInteger.ONE.dlShiftTo(ys,t); t.subTo(y,y); // "negative" y so we can replace sub with am later while(y.t < ys) y[y.t++] = 0; while(--j >= 0) { // Estimate quotient digit var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out y.dlShiftTo(j,t); r.subTo(t,r); while(r[i] < --qd) r.subTo(t,r); } } if(q != null) { r.drShiftTo(ys,q); if(ts != ms) BigInteger.ZERO.subTo(q,q); } r.t = ys; r.clamp(); if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder if(ts < 0) BigInteger.ZERO.subTo(r,r); } // (public) this mod a function bnMod(a) { var r = nbi(); this.abs().divRemTo(a,null,r); if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); return r; } // Modular reduction using "classic" algorithm function Classic(m) { this.m = m; } function cConvert(x) { if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); else return x; } function cRevert(x) { return x; } function cReduce(x) { x.divRemTo(this.m,null,x); } function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } Classic.prototype.convert = cConvert; Classic.prototype.revert = cRevert; Classic.prototype.reduce = cReduce; Classic.prototype.mulTo = cMulTo; Classic.prototype.sqrTo = cSqrTo; // (protected) return "-1/this % 2^DB"; useful for Mont. reduction // justification: // xy == 1 (mod m) // xy = 1+km // xy(2-xy) = (1+km)(1-km) // x[y(2-xy)] = 1-k^2m^2 // x[y(2-xy)] == 1 (mod m^2) // if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 // should reduce x and y(2-xy) by m^2 at each step to keep size bounded. // JS multiply "overflows" differently from C/C++, so care is needed here. function bnpInvDigit() { if(this.t < 1) return 0; var x = this[0]; if((x&1) == 0) return 0; var y = x&3; // y == 1/x mod 2^2 y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 // last step - calculate inverse mod DV directly; // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits // we really want the negative inverse, and -DV < y < DV return (y>0)?this.DV-y:-y; } // Montgomery reduction function Montgomery(m) { this.m = m; this.mp = m.invDigit(); this.mpl = this.mp&0x7fff; this.mph = this.mp>>15; this.um = (1<<(m.DB-15))-1; this.mt2 = 2*m.t; } // xR mod m function montConvert(x) { var r = nbi(); x.abs().dlShiftTo(this.m.t,r); r.divRemTo(this.m,null,r); if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); return r; } // x/R mod m function montRevert(x) { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } // x = x/R mod m (HAC 14.32) function montReduce(x) { while(x.t <= this.mt2) // pad x so am has enough room later x[x.t++] = 0; for(var i = 0; i < this.m.t; ++i) { // faster way of calculating u0 = x[i]*mp mod DV var j = x[i]&0x7fff; var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; // use am to combine the multiply-shift-add into one call j = i+this.m.t; x[j] += this.m.am(0,u0,x,i,0,this.m.t); // propagate carry while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } } x.clamp(); x.drShiftTo(this.m.t,x); if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); } // r = "x^2/R mod m"; x != r function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } // r = "xy/R mod m"; x,y != r function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } Montgomery.prototype.convert = montConvert; Montgomery.prototype.revert = montRevert; Montgomery.prototype.reduce = montReduce; Montgomery.prototype.mulTo = montMulTo; Montgomery.prototype.sqrTo = montSqrTo; // (protected) true iff this is even function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) function bnpExp(e,z) { if(e > 0xffffffff || e < 1) return BigInteger.ONE; var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; g.copyTo(r); while(--i >= 0) { z.sqrTo(r,r2); if((e&(1<<i)) > 0) z.mulTo(r2,g,r); else { var t = r; r = r2; r2 = t; } } return z.revert(r); } // (public) this^e % m, 0 <= e < 2^32 function bnModPowInt(e,m) { var z; if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); return this.exp(e,z); } // protected BigInteger.prototype.copyTo = bnpCopyTo; BigInteger.prototype.fromInt = bnpFromInt; BigInteger.prototype.fromString = bnpFromString; BigInteger.prototype.clamp = bnpClamp; BigInteger.prototype.dlShiftTo = bnpDLShiftTo; BigInteger.prototype.drShiftTo = bnpDRShiftTo; BigInteger.prototype.lShiftTo = bnpLShiftTo; BigInteger.prototype.rShiftTo = bnpRShiftTo; BigInteger.prototype.subTo = bnpSubTo; BigInteger.prototype.multiplyTo = bnpMultiplyTo; BigInteger.prototype.squareTo = bnpSquareTo; BigInteger.prototype.divRemTo = bnpDivRemTo; BigInteger.prototype.invDigit = bnpInvDigit; BigInteger.prototype.isEven = bnpIsEven; BigInteger.prototype.exp = bnpExp; // public BigInteger.prototype.toString = bnToString; BigInteger.prototype.negate = bnNegate; BigInteger.prototype.abs = bnAbs; BigInteger.prototype.compareTo = bnCompareTo; BigInteger.prototype.bitLength = bnBitLength; BigInteger.prototype.mod = bnMod; BigInteger.prototype.modPowInt = bnModPowInt; // "constants" BigInteger.ZERO = nbv(0); BigInteger.ONE = nbv(1); var b64map="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; var b64pad="="; function hex2b64(h) { var i; var c; var ret = ""; for(i = 0; i+3 <= h.length; i+=3) { c = parseInt(h.substring(i,i+3),16); ret += b64map.charAt(c >> 6) + b64map.charAt(c & 63); } if(i+1 == h.length) { c = parseInt(h.substring(i,i+1),16); ret += b64map.charAt(c << 2); } else if(i+2 == h.length) { c = parseInt(h.substring(i,i+2),16); ret += b64map.charAt(c >> 2) + b64map.charAt((c & 3) << 4); } while((ret.length & 3) > 0) ret += b64pad; return ret; } // convert a base64 string to hex function b64tohex(s) { var ret = "" var i; var k = 0; // b64 state, 0-3 var slop; for(i = 0; i < s.length; ++i) { if(s.charAt(i) == b64pad) break; v = b64map.indexOf(s.charAt(i)); if(v < 0) continue; if(k == 0) { ret += int2char(v >> 2); slop = v & 3; k = 1; } else if(k == 1) { ret += int2char((slop << 2) | (v >> 4)); slop = v & 0xf; k = 2; } else if(k == 2) { ret += int2char(slop); ret += int2char(v >> 2); slop = v & 3; k = 3; } else { ret += int2char((slop << 2) | (v >> 4)); ret += int2char(v & 0xf); k = 0; } } if(k == 1) ret += int2char(slop << 2); return ret; } // convert a base64 string to a byte/number array function b64toBA(s) { //piggyback on b64tohex for now, optimize later var h = b64tohex(s); var i; var a = new Array(); for(i = 0; 2*i < h.length; ++i) { a[i] = parseInt(h.substring(2*i,2*i+2),16); } return a; } // Depends on jsbn.js and rng.js // Version 1.1: support utf-8 encoding in pkcs1pad2 // convert a (hex) string to a bignum object function parseBigInt(str,r) { return new BigInteger(str,r); } function linebrk(s,n) { var ret = ""; var i = 0; while(i + n < s.length) { ret += s.substring(i,i+n) + "\n"; i += n; } return ret + s.substring(i,s.length); } function byte2Hex(b) { if(b < 0x10) return "0" + b.toString(16); else return b.toString(16); } // PKCS#1 (type 2, random) pad input string s to n bytes, and return a bigint function pkcs1pad2(s,n) { if(n < s.length + 11) { // TODO: fix for utf-8 alert("Message too long for RSA"); return null; } var ba = new Array(); var i = s.length - 1; while(i >= 0 && n > 0) { var c = s.charCodeAt(i--); if(c < 128) { // encode using utf-8 ba[--n] = c; } else if((c > 127) && (c < 2048)) { ba[--n] = (c & 63) | 128; ba[--n] = (c >> 6) | 192; } else { ba[--n] = (c & 63) | 128; ba[--n] = ((c >> 6) & 63) | 128; ba[--n] = (c >> 12) | 224; } } ba[--n] = 0; var rng = new SecureRandom(); var x = new Array(); while(n > 2) { // random non-zero pad x[0] = 0; while(x[0] == 0) rng.nextBytes(x); ba[--n] = x[0]; } ba[--n] = 2; ba[--n] = 0; return new BigInteger(ba); } // "empty" RSA key constructor function RSAKey() { this.n = null; this.e = 0; this.d = null; this.p = null; this.q = null; this.dmp1 = null; this.dmq1 = null; this.coeff = null; } // Set the public key fields N and e from hex strings function RSASetPublic(N,E) { if(N != null && E != null && N.length > 0 && E.length > 0) { this.n = parseBigInt(N,16); this.e = parseInt(E,16); } else alert("Invalid RSA public key"); } // Perform raw public operation on "x": return x^e (mod n) function RSADoPublic(x) { return x.modPowInt(this.e, this.n); } // Return the PKCS#1 RSA encryption of "text" as an even-length hex string function RSAEncrypt(text) { var m = pkcs1pad2(text,(this.n.bitLength()+7)>>3); if(m == null) return null; var c = this.doPublic(m); if(c == null) return null; var h = c.toString(16); if((h.length & 1) == 0) return h; else return "0" + h; } // Return the PKCS#1 RSA encryption of "text" as a Base64-encoded string //function RSAEncryptB64(text) { // var h = this.encrypt(text); // if(h) return hex2b64(h); else return null; //} // protected RSAKey.prototype.doPublic = RSADoPublic; // public RSAKey.prototype.setPublic = RSASetPublic; RSAKey.prototype.encrypt = RSAEncrypt; //RSAKey.prototype.encrypt_b64 = RSAEncryptB64; function jiami(mod,key){ var rsaKey = new RSAKey(); rsaKey.setPublic(b64tohex(mod), b64tohex(key)); var enPassword = hex2b64(rsaKey.encrypt("222222")); } jiami("AKOwMuspyLv1ikxYDimf6OgY/HmRJpFIrsrDrPt3twvBTdAjK9RduNIULmm0qfevprEUWnLC+o20k38acWrHAwIQTXuRflj16u4QyrDRScbWfqAaYs44Ls2Q3TghQJHajDlvWwLMLx0eeDZH75/MxHHSrEQJGJH9PYbY+6FJPRe/","AQAB"); -->Js运行成功! -->输出:undefined 怎么输出没有值
09-21
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值