promise.all 批量调接口时遇到res为undefined

本文介绍了一种基于DD框架的图片下载及预览技术方案。通过解析HTTP响应状态码来判断请求是否成功,并利用Promise进行异步操作的管理。文章详细展示了如何使用dd.downloadFile API下载图片并获取文件路径,最终设置图片预览的数据源。

 

  if (+res.status === 200) {
          // console.log(res.data);
          let imgNameGroupList = res.data;

          const allApi = [];
          let urlPrev = "https://hyaiot.net/v1/images/";

          for (let i = 0; i < imgNameGroupList.length; i++) {

            let item = `${urlPrev}${this.data.fileCode}/${imgNameGroupList[i]}`;
            const generateItem = this.generateTemp(item);
            allApi.push(generateItem);
            Promise.all(allApi).then(tempUrlGroup => {
              this.setData({
                images: tempUrlGroup
              });
            });
          }
        
       

          
        }

         generateTemp(url) {
        return new Promise((resolve, reject) => {
          dd.downloadFile({
            url: url,
            headers: {
          "X-Access-Token": this.data.token
        },
        success({ filePath }) {
          console.log(filePath,'transfer successfully!');
        },
        fail(res) {
          console.log(res);
          dd.alert({
            content: res.errorMessage || res.error
          });
        }
      }).then(res => {
        resolve(res.filePath);  
        // 需要用resolve将结果包裹起来这样promise.all 拿到的res才不会为undefined
      });
    });
  },

 

/* 文件路径: data/DataManager.js */ // @ts-nocheck // 导入依赖 import { LazyLoader } from './LazyLoader.js'; import { TransactionApi } from './TransactionApi.js'; import { IndexedDBManager } from './IndexedDBManager.js'; // 确保SockJS和Stomp库已加载 if (typeof SockJS === 'undefined') { throw new Error('SockJS library is required but not loaded. Please include it in your HTML.'); } if (typeof Stomp === 'undefined') { throw new Error('Stomp library is required but not loaded. Please include it in your HTML.'); } //这个函数不许改,也禁止废话,属性名和其他命名都哼规范不会出现意外, function resolveDataReferences(data) { // 获取 data 对象的所有顶层键 const keys = Object.keys(data); // 遍历每个顶层键(如 users, posts 等) for (const key of keys) { const entities = data[key]; // 遍历该顶层键下的每个实体(如每个 user 或 post) for (const entity of entities) { // 遍历实体的每个属性 for (const attribute in entity) { if (entity?.hasOwnProperty(attribute)) { var trpe=attribute?.replace(/\d/g, ''); // 确保属性属于当前实体 if (Array.isArray(entity[attribute])) { if(data[trpe]==null){ trpe+="s" } // 如果属性是一个数组,则将数组中的每个 ID 替换为对应的实际对象 entity[attribute] = entity[attribute].map(item => data[trpe ]?.find(updateItem => updateItem?.id === item?.id) || item ); } else if (typeof entity[attribute] === "object" && entity[attribute] !== null) { // 如果属性是一个对象,则将其替换为对应的实际对象 entity[attribute] = data[trpe + "s"]?.find(updateItem => updateItem?.id === entity[attribute]?.id); } } } } } return data; } function resolveDataReference(entity,data) { // 遍历实体的每个属性 for (const attribute in entity) { if (entity?.hasOwnProperty(attribute)) { var trpe=attribute?.replace(/\d/g, ''); // 确保属性属于当前实体 if (Array.isArray(entity[attribute])) { if(data[trpe]==null){ trpe+="s" } // 如果属性是一个数组,则将数组中的每个 ID 替换为对应的实际对象 entity[attribute] = entity[attribute].map(item => data[trpe ]?.find(updateItem => updateItem?.id === item?.id) || item ); } else if (typeof entity[attribute] === "object" && entity[attribute] !== null) { // 如果属性是一个对象,则将其替换为对应的实际对象 entity[attribute] = data[trpe + "s"]?.find(updateItem => updateItem?.id === entity[attribute]?.id); } } } return entity; } /** * 数据管理器类 - Web版 * 基于小程序版MiniProgramDataManager重构 */ class DataManager { /** * 构造函数 * @param {string} baseUrl - API基础URL * @param {Set} initializedEntityTypes - 已初始化的实体类型集合 * @param {Object} entityConfig - 实体类型配置对象 */ constructor(baseUrl = "../php_api/index.php", initializedEntityTypes = new Set(), entityConfig = {}) { this.baseUrl = baseUrl; this.debug = true; this.networkAvailable = true; this.isSyncing = false; this.lastSync = null; this.storageKey = 'webAppData'; // 初始化IndexedDB管理器 this.indexedDBManager = new IndexedDBManager('KuCunAppData', 1); // 默认实体类型配置 this.defaultEntityConfig = { // 实体类型定义 entityTypes: [ 'bancai', 'dingdan', 'mupi', 'chanpin', 'kucun', 'dingdan_bancai', 'chanpin_zujian', 'zujian', 'caizhi', 'dingdan_chanpin', 'user', 'jinhuo' ], // 默认核心实体类型(用于初始化) coreEntityTypes: ['bancai', 'dingdan', 'mupi', 'chanpin', 'kucun', 'user'], // 实体文本映射 entityTexts: { bancai: '板材已存在', dingdan: '订单已存在', mupi: '木皮已存在', chanpin: '产品已存在', kucun: '已有库存记录', chanpin_zujian: '产品已有该组件', dingdan_bancai: '', zujian: '组件已定义过了', caizhi: '材质已定义过了', dingdan_chanpin: '订单下已有该产品', user: '' }, // 重复检查规则配置 duplicateCheckRules: { bancai: (data, collection) => collection.some(b => b.houdu === data.houdu && b.caizhi?.id === data.caizhi?.id && b.mupi1?.id === data.mupi1?.id && b.mupi2?.id === data.mupi2?.id ), caizhi: (data, collection) => collection.some(c => c.name === data.name), mupi: (data, collection) => collection.some(m => m.name === data.name && m.you === data.you), chanpin: (data, collection) => collection.some(c => c.bianhao === data.bianhao), zujian: (data, collection) => collection.some(z => z.name === data.name), dingdan: (data, collection) => collection.some(d => d.number === data.number), chanpin_zujian: (data, collection) => collection.some(cz => cz.chanpin?.id === data.chanpin?.id && cz.zujian?.id === data.zujian?.id ), dingdan_chanpin: (data, collection) => collection.some(dc => dc.dingdan?.id === data.dingdan?.id && dc.chanpin?.id === data.chanpin?.id && dc.zujian?.id === data.zujian?.id && dc.bancai?.id === data.bancai?.id ), user: (data, collection) => collection.some(u => u.name === data.name) } }; // 合并默认配置和用户配置 this.entityConfig = {...this.defaultEntityConfig, ...entityConfig}; this.entityConfig.entityTexts = {...this.defaultEntityConfig.entityTexts, ...(entityConfig.entityTexts || {})}; this.entityConfig.duplicateCheckRules = {...this.defaultEntityConfig.duplicateCheckRules, ...(entityConfig.duplicateCheckRules || {})}; this._rawData = this.createEmptyData(); this.lazyLoader = new LazyLoader(this); // 初始化回对象,支持动态添加新的数据类型 this.callbacks = { all: [] }; // 创建事务API实例 this.Transaction = new TransactionApi(this); // 进度更新回函数 this.onProgressUpdate = null; // 已初始化的实体类型集合 this.initializedEntityTypes = initializedEntityTypes; // 初始化状态管理 this.isInitializing = false; this.initializationPromise = null; // 初始化网络状态 this.initNetwork(); // 注意:现在只初始化网络状态,实际的数据加载和WebSocket连接应该通过initialize方法触发 // 这样可以更好地控制初始化流程和进度更新 } /** * 创建空的数据结构 * 根据配置的实体类型动态创建 */ createEmptyData() { const emptyData = { _lastModified: null, _lastSync: null }; // 根据配置的实体类型创建空数组 (this.entityConfig.entityTypes || []).forEach(entityType => { const collectionName = `${entityType}s`; emptyData[collectionName] = []; }); return emptyData; } get data() { // 创建数据访问代理,保持懒加载功能 const self = this; return new Proxy(this._rawData, { get: (target, prop) => { if (prop.startsWith('_')) { return target[prop]; } if (Array.isArray(target[prop])) { // 为数组中的每个对象创建懒加载代理 return target[prop].map(item => { if (typeof item === 'object' && item !== null) { // 获取实体类型(去掉末尾的s) const entityType = prop.replace(/s$/, ''); return self.lazyLoader.createProxy(item, entityType); } return item; }); } return target[prop]; } }); } /** * 添加实体类型到已初始化集合,并动态订阅WebSocket更新 * @param {String} entityType - 实体类型 */ async addEntityType(entityType) { if (this.initializedEntityTypes.has(entityType)) { return; // 已初始化,无需重复添加 } // 添加到已初始化集合 this.initializedEntityTypes.add(entityType); // 确保集合存在 const collectionName = `${entityType}s`; if (!this._rawData[collectionName]) { this._rawData[collectionName] = []; } // 确保回数组存在 if (!this.callbacks[collectionName]) { this.callbacks[collectionName] = []; } // 重新订阅WebSocket,包括新添加的实体类型 if (this.stompClient && this.stompClient.connected) { this.resubscribeWebSocketTopics(); } // 加载实体数据 await this.loadEntityFromIndexedDB(entityType); // 如果有网络连接,同步数据 if (this.networkAvailable) { const lastSyncTime = await this.indexedDBManager.getMetadata('lastSyncTime'); await this.fetchEntityData(entityType, lastSyncTime); } } /** * 初始化指定的实体类型数据 * @param {Array} entityTypes - 要初始化的实体类型数组 */ async initialize(entityTypes = []) { // 防止重复初始化 if (this.isInitializing) { return this.initializationPromise; } this.isInitializing = true; this.initializationPromise = this._initializeInternal(entityTypes); try { return await this.initializationPromise; } finally { this.isInitializing = false; } } /** * 内部初始化方法 */ async _initializeInternal(entityTypes = []) { try { // 初始化IndexedDB await this.indexedDBManager.init(); // 如果没有指定实体类型,使用配置的核心实体类型 if (entityTypes.length === 0) { entityTypes = this.entityConfig.coreEntityTypes || ['bancai', 'dingdan', 'mupi', 'chanpin', 'kucun', 'user']; } // 初始化每个实体类型的数据 const totalTypes = entityTypes.length; let completedCount = 0; // 先尝试从本地数据获取最后更新间 const lastSyncTime = await this.indexedDBManager.getMetadata('lastSyncTime'); // 定义每个实体类型的加载函数 const loadEntityType = async (entityType) => { try { // 先从IndexedDB加载数据 const localData = await this.loadEntityFromIndexedDB(entityType); if (localData && localData.length > 0) { this._rawData[`${entityType}s`] = localData; } // 如果有网络连接,使用间增量获取后端数据补全 if (this.networkAvailable) { await this.fetchEntityData(entityType, lastSyncTime); } // 添加到已初始化集合 this.initializedEntityTypes.add(entityType); return true; } catch (error) { console.error(`初始化${entityType}数据失败:`, error); return false; } finally { // 更新进度 completedCount++; if (this.onProgressUpdate) { const progressValue = Math.floor((completedCount / totalTypes) * 80) + 10; const progress = Math.min(90, progressValue); this.onProgressUpdate(progress); } } }; // 使用Promise.all实现并行加载,但限制并发数以避免事务冲突 const batchSize = 3; // 控制并发数 let results = []; for (let i = 0; i < entityTypes.length; i += batchSize) { const batch = entityTypes.slice(i, i + batchSize); const batchResults = await Promise.all(batch.map(type => loadEntityType(type))); results = results.concat(batchResults); } this._rawData._lastSync = new Date().toISOString(); // 保存到IndexedDB await this.saveDataToIndexedDB(); // 更新进度到100% if (this.onProgressUpdate) { this.onProgressUpdate(100); } // 初始化WebSocket(在数据完全加载完成后) this.initWebSocket(); return true; } catch (error) { console.error('初始化失败:', error); if (this._rawData._lastSync) return true; throw error; } } /** * 从IndexedDB加载单个实体类型数据 */ async loadEntityFromIndexedDB(entityType) { try { const collectionName = `${entityType}s`; const data = await this.indexedDBManager.getAll(collectionName); return data || []; } catch (error) { console.error(`从IndexedDB加载${entityType}数据失败:`, error); return []; } } /** * 解析集合中的数据引用 */ resolveDataReferencesForCollection(collectionName) { if (!this._rawData[collectionName]) return; const entities = this._rawData[collectionName]; for (const entity of entities) { // 为每个实体解析引用 resolveDataReference(entity, this._rawData); } } /** * 初始化WebSocket连接 */ initWebSocket() { try { // 构建WebSocket URL(注意:SockJS需要使用HTTP或HTTPS协议而不是WS或WSS) const wsProtocol = window.location.protocol === 'https:' ? 'https:' : 'http:'; const wsHost = window.location.host; const wsUrl = `${wsProtocol}//${wsHost}/ws`; // 使用SockJS客户端 this.socket = new SockJS(wsUrl); this.stompClient = Stomp.over(this.socket); // 配置WebSocket连接 this.stompClient.connect({}, () => { // 订阅实体更新主题 this.resubscribeWebSocketTopics(); }, (error) => { console.error('WebSocket连接失败:', error); // 连接失败尝试重新连接 setTimeout(() => this.initWebSocket(), 5000); }); // 监听连接关闭事件 this.socket.onclose = () => { // 连接关闭尝试重新连接 setTimeout(() => this.initWebSocket(), 5000); }; } catch (error) { console.error('WebSocket初始化失败:', error); // 初始化失败尝试重新初始化 setTimeout(() => this.initWebSocket(), 5000); } } /** * 根据已初始化的实体类型重新订阅WebSocket主题 */ resubscribeWebSocketTopics() { // 先取消所有现有订阅 if (this.webSocketSubscriptions) { this.webSocketSubscriptions.forEach(subscription => subscription.unsubscribe()); } // 重新创建订阅数组 this.webSocketSubscriptions = []; // 订阅所有实体更新主题 const allSubscription = this.stompClient.subscribe('/topic/entity-updates', (message) => { this.handleWebSocketMessage(message); }); this.webSocketSubscriptions.push(allSubscription); // 根据已初始化的实体类型订阅特定主题 this.initializedEntityTypes.forEach(entityType => { const entitySubscription = this.stompClient.subscribe(`/topic/${entityType}-updates`, (message) => { this.handleWebSocketMessage(message); }); this.webSocketSubscriptions.push(entitySubscription); }); } /** * 处理WebSocket消息 */ async handleWebSocketMessage(message) { try { const updateData = JSON.parse(message.body); const { operation, entityType, id, entityData } = updateData; // 转换实体类型为复数形式(集合名) const collectionName = this.getCollectionName(entityType); // 检查是否是已初始化的实体类型 if (collectionName && (this.initializedEntityTypes.has(entityType) || this.initializedEntityTypes.has(collectionName.replace(/s$/, '')))) { // 确保集合存在 if (!this._rawData[collectionName]) { this._rawData[collectionName] = []; } if (operation === 'create' || operation === 'update') { // 添加或更新实体 const index = this._rawData[collectionName].findIndex(item => item.id === id); if (index >= 0) { this._rawData[collectionName][index] = entityData; } else { this._rawData[collectionName].push(entityData); } // 更新到IndexedDB await this.indexedDBManager.put(collectionName, entityData); } else if (operation === 'delete') { // 删除实体 this._rawData[collectionName] = this._rawData[collectionName].filter(item => item.id !== id); // 从IndexedDB删除 await this.indexedDBManager.delete(collectionName, id); } // 触发回,包含数据刷新类型 this.triggerCallbacks(operation, collectionName, entityData); // 清除缓存 this.lazyLoader.clearCache(); // 保存最后更新间 await this.indexedDBManager.setMetadata('lastSyncTime', new Date().toISOString()); } } catch (error) { console.error('处理WebSocket消息失败:', error); // 触发错误回,包含错误信息 this.triggerCallbacks('websocket_error', 'all', { error: error.message }); } } /** * 获取实体类型对应的集合名 */ getCollectionName(entityType) { // 转换首字母为小写 const lowerCaseType = entityType.charAt(0).toLowerCase() + entityType.slice(1); // 检查LazyLoader中的映射表 return this.lazyLoader.entityToCollectionMap[lowerCaseType] || `${lowerCaseType}s`; } async initNetwork() { // Web环境默认网络可用 this.networkAvailable = navigator.onLine; // 监听网络状态变化 window.addEventListener('online', () => { this.networkAvailable = true; }); window.addEventListener('offline', () => { this.networkAvailable = false; }); } /** * 同步指定实体类型的数据 * @param {String} entityType - 实体类型 */ async syncEntityData(entityType) { if (this.isSyncing) return; this.isSyncing = true; try { const since = this._rawData._lastSync; const success = await this.fetchEntityData(entityType, since); if (success) { this.lazyLoader.clearCache(); await this.saveDataToIndexedDB(); this.triggerCallbacks('refresh', `${entityType}s`, this.data); } } catch (error) { console.error(`同步${entityType}数据失败:`, error); this.triggerCallbacks('sync_error', entityType, { error }); } finally { this.isSyncing = false; } } /** * 添加重复检查规则 * @param {String} entityType - 实体类型 * @param {Function} rule - 检查规则函数 */ addDuplicateCheckRule(entityType, rule) { if (typeof rule === 'function') { this.entityConfig.duplicateCheckRules[entityType] = rule; } } /** * 检查重复数据 * @param {String} entityType - 实体类型 * @param {Object} data - 要检查的数据 */ checkDuplicate(entityType, data) { // 获取实体类型对应的集合 const collectionName = `${entityType}s`; const collection = this._rawData[collectionName] || []; // 检查是否有对应的检查规则 if (this.entityConfig.duplicateCheckRules[entityType]) { return this.entityConfig.duplicateCheckRules[entityType](data, collection); } // 默认不检查重复 return false; } /** * 获取指定实体类型的数据 * @param {String} entityType - 实体类型 * @param {String} since - 上次同步间 */ async fetchEntityData(entityType, since = null) { try { const params = since ? { since } : {}; const url = since ? `${this.baseUrl}/all/${entityType}?since=${encodeURIComponent(since)}` : `${this.baseUrl}/all/${entityType}`; const response = await fetch(url); if (!response.ok) throw new Error(`HTTP错误: ${response.status}`); const result = await response.json(); if (result.status !== 200) throw new Error(result.text || 'API错误'); const collectionName = `${entityType}s`; const entityData = result.data || []; // 处理解析到的数据 if (since) { // 增量更新 if (!this._rawData[collectionName]) { this._rawData[collectionName] = []; } try { // 创建所有更新Promise const updatePromises = entityData.map(newItem => { const index = this._rawData[collectionName].findIndex(item => item.id === newItem.id); if (index >= 0) { this._rawData[collectionName][index] = newItem; } else { this._rawData[collectionName].push(newItem); } // 更新到IndexedDB return this.indexedDBManager.put(collectionName, newItem); }); // 等待所有更新完成 await Promise.all(updatePromises); } catch (dbError) { console.error(`保存${entityType}增量数据到IndexedDB失败:`, dbError); // 继续执行,不中断流程 } } else { // 全量更新 this._rawData[collectionName] = entityData; // 更新到IndexedDB(先清空再添加) try { await this.indexedDBManager.clear(collectionName); if (entityData.length > 0) { // 分批保存大量数据,避免IndexedDB操作超 const batchSize = 50; for (let i = 0; i < entityData.length; i += batchSize) { const batch = entityData.slice(i, i + batchSize); await this.indexedDBManager.putAll(collectionName, batch); } } } catch (dbError) { console.error(`IndexedDB操作失败:`, dbError); // 这里不抛出错误,继续执行后续操作 } } // 确保回数组存在 if (!this.callbacks[collectionName]) { this.callbacks[collectionName] = []; } // 更新最后同步间 try { const syncTime = new Date().toISOString(); await this.indexedDBManager.setMetadata('lastSyncTime', syncTime); } catch (metaError) { console.error(`更新${entityType}最后同步间失败:`, metaError); } return true; } catch (error) { console.error(`获取${entityType}数据失败:`, error); this.triggerCallbacks('fetch_error', entityType, { error }); // 不要抛出异常,继续处理其他实体类型 return false; } } /** * 同步所有已加载的实体类型数据 * 主要用于初始化完成后,按需同步特定实体 */ async syncData() { if (this.isSyncing) return; // 获取所有已加载的实体类型 const entityTypes = []; for (const key in this._rawData) { if (!key.startsWith('_') && Array.isArray(this._rawData[key])) { // 从集合名转换为实体类型(去掉末尾的s) const entityType = key.replace(/s$/, ''); entityTypes.push(entityType); } } // 使用Promise.all实现并行同步,但限制并发数 const batchSize = 3; let results = []; for (let i = 0; i < entityTypes.length; i += batchSize) { const batch = entityTypes.slice(i, i + batchSize); const batchResults = await Promise.all(batch.map(type => this.syncEntityData(type))); results = results.concat(batchResults); } this._rawData._lastSync = new Date().toISOString(); await this.saveDataToIndexedDB(); } async fetchAll(since) { try { // 首先确保IndexedDB已初始化 try { await this.indexedDBManager.init(); } catch (initError) { console.warn('IndexedDB初始化失败,但会继续尝试在需要自动初始化:', initError); } // 通知开始加载数据,设置初始进度为10% if (this.onProgressUpdate) { this.onProgressUpdate(10); } // 不使用app/all接口,而是通过循环用单个实体接口 const defaultEntityTypes = this.entityConfig.coreEntityTypes || ['bancai', 'dingdan', 'mupi', 'chanpin', 'kucun', 'user']; const totalTypes = defaultEntityTypes.length; let completedCount = 0; // 在并行加载前,批量创建所有需要的IndexedDB集合,避免多次版本升级 if (this.indexedDBManager.batchEnsureCollections) { try { // 将实体类型转换为复数形式的集合名,确保只创建一套集合 const collectionNames = defaultEntityTypes.map(type => this.getCollectionName(type)); await this.indexedDBManager.batchEnsureCollections(collectionNames); } catch (batchError) { console.error('批量确保IndexedDB集合存在失败:', batchError); // 即使失败也继续,让单个操作尝试创建集合 } } // 使用Promise.all实现并行加载,但限制并发数 const batchSize = 3; let results = []; // 确保批次循环完整执行的增强实现 let currentBatchIndex = 0; while (currentBatchIndex < defaultEntityTypes.length) { const batch = defaultEntityTypes.slice(currentBatchIndex, currentBatchIndex + batchSize); const batchNumber = Math.floor(currentBatchIndex / batchSize) + 1; // 为每个实体类型创建一个带try-catch的Promise,确保单个实体失败不会影响整个批次 const batchPromises = batch.map(async (entityType) => { try { const result = await this.fetchEntityData(entityType, since); // 更新进度 completedCount++; if (this.onProgressUpdate) { const progressValue = Math.floor((completedCount / totalTypes) * 90) + 10; const progress = Math.min(90, progressValue); this.onProgressUpdate(progress); } return result; } catch (error) { console.error(`处理${entityType}发生错误:`, error); // 即使出错,也要增加完成计数并更新进度,确保流程继续 completedCount++; if (this.onProgressUpdate) { const progressValue = Math.floor((completedCount / totalTypes) * 90) + 10; const progress = Math.min(90, progressValue); this.onProgressUpdate(progress); } return false; } }); try { // 使用Promise.allSettled代替Promise.all,确保所有Promise都能完成 const batchResults = await Promise.all(batchPromises); results = results.concat(batchResults); } catch (batchError) { console.error(`批次${batchNumber}处理异常:`, batchError); // 即使批次处理异常,也要继续处理下一批 } // 确保批次索引正确增加 currentBatchIndex += batchSize; } // 通知数据获取完成 if (this.onProgressUpdate) { this.onProgressUpdate(100); } return true; } catch (error) { console.error('获取所有数据失败:', error); this.triggerCallbacks('fetch_error', 'all', { error }); // 不要抛出异常,继续处理其他实体类型 return false; } } registerCallback(entity, callback) { // 动态创建回数组 if (!this.callbacks[entity]) { this.callbacks[entity] = []; } this.callbacks[entity].push(callback); } unregisterCallback(entity, callback) { const arr = this.callbacks[entity] || this.callbacks.all; const index = arr.indexOf(callback); if (index !== -1) arr.splice(index, 1); } triggerCallbacks(operation, entity, data) { // 触发所有回 this.callbacks.all.forEach(cb => cb(operation, entity, data)); // 触发特定实体的回 this.callbacks[entity]?.forEach(cb => cb(operation, data)); } async crudOperation(operation, entity, data) { try { const response = await fetch(`${this.baseUrl}/app/${operation}/${entity}`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(data) }); if (!response.ok) throw new Error('Network response was not ok'); const result = await response.json(); if (result.status !== 200) throw new Error(result.text || 'API error'); const resultData = result.data || data; this.updateLocalData(operation, entity, resultData); this.triggerCallbacks(operation, entity, resultData); return resultData; } catch (error) { this.triggerCallbacks(`${operation}_error`, entity, { data, error }); throw error; } } async updateLocalData(operation, entity, data) { const key = `${entity}s`; // 确保集合存在 if (!this._rawData[key]) { this._rawData[key] = []; } // 确保回数组存在 if (!this.callbacks[key]) { this.callbacks[key] = []; } const collection = this._rawData[key]; switch (operation) { case 'add': collection.push(resolveDataReference(data, this._rawData)); // 保存到IndexedDB await this.indexedDBManager.put(key, data); break; case 'update': const index = collection.findIndex(item => item.id === data.id); index >= 0 ? Object.assign(collection[index], data) : collection.push(resolveDataReference(data, this._rawData)); // 保存到IndexedDB await this.indexedDBManager.put(key, data); break; case 'delete': const deleteIndex = collection.findIndex(item => item.id === data.id); if (deleteIndex >= 0) collection.splice(deleteIndex, 1); // 从IndexedDB删除 await this.indexedDBManager.delete(key, data.id); break; } this._rawData._lastModified = new Date().toISOString(); this.lazyLoader.clearCache(); // 保存元数据 await this.indexedDBManager.setMetadata('lastSyncTime', new Date().toISOString()); } async loadDataFromIndexedDB() { try { await this.indexedDBManager.init(); // 获取所有实体类型(从配置中获取或使用默认值) const entityTypes = this.entityConfig.entityTypes || ['bancai', 'dingdan', 'mupi', 'chanpin', 'kucun', 'dingdan_bancai', 'chanpin_zujian', 'zujian', 'caizhi', 'dingdan_chanpin', 'user', 'jinhuo']; // 使用Promise.all实现并行加载,但限制并发数 const batchSize = 3; for (let i = 0; i < entityTypes.length; i += batchSize) { const batch = entityTypes.slice(i, i + batchSize); await Promise.all(batch.map(entityType => { const collectionName = `${entityType}s`; return this.indexedDBManager.getAll(collectionName).then(data => { if (data && data.length > 0) { this._rawData[collectionName] = data; } }); })); } // 加载最后同步间 const lastSyncTime = await this.indexedDBManager.getMetadata('lastSyncTime'); if (lastSyncTime) { this._rawData._lastSync = lastSyncTime; } } catch (error) { console.error('从IndexedDB加载数据失败:', error); } } 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; } async addEntity(entity, data) { // 检查重复数据 if (this.checkDuplicate(entity, data)) { const errorMsg = `${this.entityConfig.entityTexts[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 }); } /** * * @param {*} endpoint * @param {*} data * @returns */ async transactionalOperation(endpoint, data) { return this.Transaction.execute(endpoint, data); } /** * 动态获取数据的方法 * @param {string} entityType - 实体类型名称或集合名称 * @returns {Array} 实体数据数组 */ getEntity(entityType) { const key = entityType.endsWith('s') ? entityType : `${entityType}s`; return this.data[key] || []; } /** * 动态生成实体数据获取方法 */ generateEntityGetters() { const entityTypes = this.entityConfig.entityTypes || []; 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/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
<template> <div class="app-container shop-order-index" style="position: relative; height: calc(100vh - 110px);"> <!-- style="height: calc(100% - 8px);" --> <div class="container" > <!--工具栏--> <div class="head-container work-order-head"> <!-- 搜索 --> <el-form :inline="true" :model="query" class="demo-form-inline" :show-message="false" label-width="90px" > <el-form-item label="客服工单号"> <el-input v-model="query.workOrderNum" clearable size="small" style="width:150px;font-size:12px" placeholder="请输入客服工单号" @keyup.enter.native="toQuery" /> </el-form-item> <el-form-item label="订单号"> <el-input v-model="query.orderId" clearable size="small" style="width:150px;font-size:12px" placeholder="请输入订单号" @keyup.enter.native="toQuery" /> </el-form-item> <el-form-item label="工单来源"> <el-select v-model="query.fromType" clearable size="small" style="width:150px;font-size:12px" placeholder="请选择工单来源" > <el-option v-for="(item, ind) in queryTypeOptions" :key="'laiyuan' + ind" :label="item.display_name" :value="item.key" /> </el-select> </el-form-item> <el-form-item label="订单来源"> <el-select v-model="query.orderSource" clearable size="small" style="width:150px;font-size:12px" placeholder="请选择订单来源" > <el-option label="福利商城" value="1"></el-option> <el-option label="陆海商城" value="0"></el-option> <el-option label="鲁西福礼" value="3"></el-option> <el-option label="浪潮商城" value="2"></el-option> </el-select> </el-form-item> <el-form-item label="提报人姓名"> <el-input v-model="query.linkUserName" clearable size="small" style="width:150px;font-size:12px" placeholder="请输入提报人姓名" @keyup.enter.native="toQuery" /> </el-form-item> <el-form-item label="提报人电话"> <el-input v-model="query.linkUserPhone" clearable size="small" style="width:150px;font-size:12px" placeholder="请输入提报人电话" @keyup.enter.native="toQuery" /> </el-form-item> <el-form-item label="工单添加人"> <el-input v-model="query.addUser" clearable size="small" style="width:150px;font-size:12px" placeholder="请输入工单添加人" @keyup.enter.native="toQuery" /> </el-form-item> <el-form-item label="状态"> <el-select v-model="query.isPass" clearable size="small" style="width:150px;font-size:12px" placeholder="请选择状态" > <el-option v-for="(item, ind) in isPassOptions" :key="'jiejue' + ind" :label="item.label" :value="item.value" /> </el-select> </el-form-item> <el-form-item label="商户" > <el-select v-model="query.merId" clearable filterable size="small" style="width:150px;font-size:12px" placeholder="请选择商户" > <el-option v-for="(item, ind) in queryMersOptions" :key="'sh' + ind" :label="item.merchantName" :value="item.id" /> </el-select> </el-form-item> <el-form-item label="问题分类"> <el-select v-model="query.problemType" clearable size="small" style="width:150px;font-size:12px" placeholder="请选择问题分类" > <el-option v-for="item in problemTypes" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> <el-form-item label="处理方式"> <el-select v-model="query.dealingType" clearable size="small" style="width:150px;font-size:12px" placeholder="请选择处理方式" > <el-option v-for="item in dealingTypes" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> <el-form-item label="售后类型"> <el-select v-model="query.saleType" clearable size="small" style="width:150px;font-size:12px" placeholder="请选择售后类型" > <el-option v-for="item in AftersalesStatus" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> <el-form-item label="创建间"> <el-date-picker v-model="query.addTime" :default-time="['00:00:00', '23:59:59']" type="datetimerange" :unlink-panels="true" range-separator=":" size="small" class="date-item" style="width:307px;font-size:12px" value-format="yyyy-MM-dd HH:mm:ss" start-placeholder="请选择开始间" end-placeholder="请选择结束间" /> </el-form-item> <el-form-item> <el-button class="filter-item" size="small" type="primary" icon="el-icon-search" @click="toQuery">查 询</el-button> <el-button plain class="filter-item" size="small" icon="el-icon-close" @click="toQuerys">清 空</el-button> </el-form-item> </el-form> <!-- 新增 --> <div style="width: 100%;"> <el-button v-if="permission.add" class="filter-item" size="small" type="primary" icon="el-icon-plus" @click="addList">新增</el-button> <el-button class="filter-item" size="small" type="primary" icon="el-icon-check" @click="batchReview">批量复核</el-button> <el-button v-if="permission.delete" class="filter-item" size="small" type="primary" icon="el-icon-close" @click="delList">批量删除</el-button> <el-button class="filter-item" size="small" type="primary" icon="el-icon-check" @click="submitList">批量提交</el-button> <el-button class="filter-item" size="small" type="primary" icon="el-icon-download" @click="exportXlsx">导出工单</el-button> <el-button class="filter-item" size="small" type="primary" icon="el-icon-download" @click="exportXlsxStatistics">导出统计</el-button> </div> </div> <!--表单组件--> <work-form ref="workFormForm" :is-add="isAdd" /> <deliver-good ref="deliverGoodForm" :is-add="isAdd" /> <!--表格渲染--> <div > <el-table v-loading="loading" :data="data" size="small" style="width: 100%;" :height="tableHeight" @selection-change="handleSelectionChange" :row-class-name="tableRowClassName" > <el-table-column type="selection" width="55"> </el-table-column> <!--<el-table-column prop="id" label="ID"/>--> <el-table-column prop="workOrderNum" label="客服工单编号" align="center" width="128"/> <el-table-column prop="orderId" label="订单号" align="center" width="180"/> <el-table-column prop="orderTime" label="订单日期" align="center" width="140"/> <el-table-column prop="saleType" label="售后类型" width="80" align="center"> <template slot-scope="scope"> <div> <div v-if="scope.row.saleType == 1" :type="'success'">退款</div> <div v-if="scope.row.saleType == 2" :type="'success'">退货退款</div> <div v-if="scope.row.saleType == 3" :type="'success'">换货</div> <div v-if="scope.row.saleType == 4" :type="'success'">补寄</div> <div v-if="scope.row.saleType == 5" :type="'success'">待发货退款</div> </div> </template> </el-table-column> <el-table-column prop="isTemp" label="售后状态" width="150" align="center"> <template slot-scope="scope"> {{analysisOrderStatus(scope.row.afterSaleType)}} </template> </el-table-column> <el-table-column prop="isPass" label="解决状态" width="80" align="center"> <template slot-scope="scope"> <div> <!-- <span>{{ scope.row.isPass==0?'未解决':scope.row.isPass==1?'部分解决':scope.row.isPass==2?'全部解决':scope.row.isPass==3?'已复核':'' }}</span> --> <el-tag v-if="scope.row.isPass == 0" :type="'danger'">未解决</el-tag> <el-tag v-else-if="scope.row.isPass == 1" :type="'warning'" >部分解决</el-tag > <el-tag v-else-if="scope.row.isPass == 2" :type="''" >全部解决</el-tag> <el-tag v-else-if="scope.row.isPass == 3" :type="'success'" >已复核</el-tag> </div> </template> </el-table-column> <el-table-column prop="isTemp" label="工单状态" width="80" align="center"> <template slot-scope="scope"> <div> <el-tag v-if="scope.row.isTemp == 1" :type="'warning'">暂存</el-tag> <el-tag v-else :type="'success'">已提交</el-tag> </div> </template> </el-table-column> <el-table-column prop="fromType" label="工单来源" width="80"> <template slot-scope="scope"> <span>{{ scope.row.fromType==1?'小程序':scope.row.fromType==2?'电话':scope.row.fromType==3?'客服评价':'' }}</span> </template> </el-table-column> <el-table-column label="订单来源" align="center" prop="orderSource"> <template slot-scope="scope"> <span v-if="scope.row.orderSource === '0'">陆海商城</span> <span v-if="scope.row.orderSource === '1'">福利商城</span> <span v-if="scope.row.orderSource === '3'">鲁西福礼</span> <span v-if="scope.row.orderSource === '2'">浪潮商城</span> </template> </el-table-column> <el-table-column prop="merName" label="所属商户" width="150" show-overflow-tooltip /> <el-table-column prop="payPrice" label="商品信息" width="320px" > <template slot-scope="scope"> <el-row :gutter="10" v-for="(item,index) in scope.row.detailList" :key="index"> <el-col :span="4" style="height: 46px; padding: 0; overflow: hidden;" v-if="item.image"> <img :src="item.image" :width="45" :height="45" /> </el-col> <el-col :span="20"> <div> <el-tag v-if="item.isCombination == '1'" @click="queryProductCombinationOrderDetail(item.orderCartId)" size="mini" effect="dark"><span style="cursor: pointer;">组</span></el-tag> <span style="border-bottom: 1px dashed #409eff;color: #409eff;cursor: pointer;padding-bottom: 3px;" @click="lookDetail(item.productId)" v-if="item.productId" > {{ item.productName }} </span> <span style="color: #409eff;padding-bottom: 3px;" v-if="!item.productId" > {{ item.productName }} </span> | {{ item.productNum }} </div> <div v-if="item.problemTypeName">问题分类:{{ item.problemTypeName}}</div> <div v-if="item.dealingTypeName">处理方式:{{ item.dealingTypeName}}</div> <div v-if="item.problemDesc">问题描述:{{ item.problemDesc }}</div> </el-col> </el-row> </template> </el-table-column> <el-table-column prop="payPrice" label="图片记录" width="370px" > <template slot-scope="scope"> <div v-if="scope.row.problemPhotoUrlList.length > 0"> <div v-for="item in scope.row.problemPhotoUrlList" :key="item"> <el-image style="width:85px;height:85px;float:left;margin-right:2px;" :src="item" :preview-src-list="scope.row.problemPhotoUrlList" fit="contain" :z-index="999" /> </div> </div> <!-- <img :src="" :preview-src-list="dialogUrl" @click="handlepic(scope.row.problemPhotoUrlList)" :width="85" :height="85" /> --> </template> </el-table-column> <el-table-column prop="linkUserName" label="提报人姓名" width="90" /> <el-table-column prop="linkUserPhone" label="提报人电话" width="100" /> <el-table-column prop="addUser" label="工单添加人" width="80" /> <el-table-column prop="addTime" label="创建间" width="150"> <template slot-scope="scope"> <span>{{ parseTime(scope.row.addTime) }}</span> </template> </el-table-column> <el-table-column prop="passUser" label="解决人" width="90" /> <el-table-column label="解决间" width="140" > <template slot-scope="scope"> <span>{{ parseTime(scope.row.passTime) }}</span> </template> </el-table-column> <el-table-column prop="reviewUser" label="复核人" width="90" /> <el-table-column label="复核间" width="140" > <template slot-scope="scope"> <span>{{ parseTime(scope.row.reviewTime) }}</span> </template> </el-table-column> <el-table-column min-width="280" label="操作" align="center" fixed="right" v-if="permission.coupon_edit||permission.coupon_delete"> <template slot-scope="scope"> <el-link type="info" icon="el-icon-info" @click="detail(scope.row.id,1)" v-if="permission.detail && scope.row.saleType == null">详情</el-link> <el-link type="info" icon="el-icon-info" @click="checkDetail(scope.row)" v-if="permission.detail && scope.row.saleType != null">详情</el-link> <el-link title="修改" type="warning" icon="el-icon-edit" style="margin-left:5px" v-if="scope.row.isTemp == 1 && permission.edit" @click="detail(scope.row.id,2)">修改</el-link> <el-link type="primary" icon="el-icon-truck" @click="deliverGood(scope.row, scope.$index)" :underline="false" v-show="scope.row.isTemp == 0 && scope.row.isPass<2 && scope.row.isDeliveryEnd!='2'" v-if="permission.redelivery && scope.row.saleType == null" style="margin-left:5px">补发快递</el-link> <el-popover :ref="'pass'+scope.row.id" placement="top" width="180"> <p>确定本条工单已处理完成吗?</p> <div style="text-align: right; margin: 0"> <el-button size="mini" type="text" @click="$refs['pass'+scope.row.id].doClose()" >取消 </el-button> <el-button :loading="delLoading" type="primary" size="mini" @click="getWorkOut(scope.row.id)" >确定 </el-button> </div> <el-link slot="reference" type="primary" icon="el-icon-document-checked" :underline="false" v-show="scope.row.isTemp == 0 && scope.row.isPass<2" v-if="permission.workOut && scope.row.saleType == null" style="margin-left:5px">解决</el-link> </el-popover> <el-popover :ref="'review'+scope.row.id" placement="top" width="180"> <p>确定本条工单复核完成吗?</p> <div style="text-align: right; margin: 0"> <el-button size="mini" type="text" @click="$refs['review'+scope.row.id].doClose()" >取消 </el-button> <el-button :loading="delLoading" type="primary" size="mini" @click="getRecordList(scope.row.id)" >确定 </el-button> </div> <!-- <el-link title="复核" icon="el-icon-edit" type="warning" v-if="permission.recheck" style="text-decoration:none;margin-left:5px;" v-show="scope.row.isPass==2">复核</el-link> --> <!-- <el-link slot="reference" type="warning" icon="el-icon-edit" :underline="false" v-if="scope.row.isPass==2" style="text-decoration:none;margin-left:5px;">复核</el-link> --> <el-link slot="reference" type="warning" icon="el-icon-check" :underline="false" v-if="scope.row.isTemp == 0 && scope.row.isPass==2 && permission.recheck" style="margin-left:5px">复核</el-link> </el-popover> <el-popover :ref="scope.row.id" placement="top" width="180"> <p>确定删除本条数据吗?</p> <div style="text-align: right; margin: 0"> <el-button size="mini" type="text" @click="$refs[scope.row.id].doClose()" >取消 </el-button> <el-button :loading="delLoading" type="primary" size="mini" @click="subDelete(scope.row.id)" >确定 </el-button> </div> <el-link slot="reference" type="danger" icon="el-icon-delete" :underline="false" v-if="scope.row.isTemp==1 && permission.delete" style="margin-left:5px">删除</el-link> </el-popover> <!-- <el-link title="处理工单" type="warning" icon="el-icon-edit" style="margin-left:5px" v-show="scope.row.isPass!=2" @click="detail(scope.row.id,2)">处理工单</el-link> --> <el-link title="工单记录" type="warning" icon="el-icon-tickets" style="margin-left:5px" v-if="permission.orderRecord" @click="gdrecord(scope.row.id,2)">工单记录</el-link> <el-link type="primary" icon="el-icon-chat-line-round" @click="sendmessage(scope.row, scope.$index)" v-if="scope.row.isTemp == 1 && scope.row.isSendSms == 0 && scope.row.orderSource == 2" :underline="false" style="margin-left:5px">发送短信</el-link> </template> </el-table-column> </el-table> </div> </div> <el-footer class="footer-contains"> <!--分页组件--> <el-pagination :total="total" :current-page="page + 1" style="margin-top: 8px;" layout="total, prev, pager, next, sizes" @size-change="sizeChange" @current-change="pageChange" /> </el-footer> <!-- 新增 --> <el-dialog :append-to-body="true" :close-on-click-modal="false" :before-close="cancel" :visible.sync="addDialog" title="新增客服工单" width="400px" > <el-form ref="form" :model="form" size="small" label-width="90px" v-loading="loading"> <el-row > <el-col :span="24"> <el-form-item label="订单号:" prop="orderId" :rules=" [{ required: true, message: '请输入订单号', trigger: 'blur',}]"> <input type="text" clearable size="small" v-model=" form.orderId " style="width:80%;"/> </el-form-item> </el-col> </el-row> <div class="dialog-footer"> <el-button :disabled="loading" type="primary" @click="doSubmit">确定</el-button> <el-button plain @click="cancel">关闭</el-button> </div> </el-form> </el-dialog> <work-order ref="workOrderFrom" ></work-order> <order-Record ref="orderRecord" ></order-Record> <product-detail ref="productDetailComp" :pshow.sync="aa"></product-detail> <sendmessage ref="sendmessage" ></sendmessage> <!--组合商品详情组件--> <groupDetail ref="groupDetail"></groupDetail> <detail ref="detail"></detail> </div> </template> <script> import sendmessage from './sendmessage.vue' import checkPermission from '@/utils/permission' import initData from '@/mixins/crud' import { del,delList,submitList,getAllInfo,getWorkOut,getOrderAllInfoByOid,reviewByIds,reviewById,exportData,exportStatistics } from '@/api/store/yxStoreWorkOrder' import workForm from './workForm' import deliverGood from './toDeliverGood' import { parseTime } from '@/utils/index' import { mapGetters } from "vuex"; import workOrder from "./form"; import orderRecord from "./orderRecord"; import { da } from 'date-fns/locale' import productDetail from "../../stock/warning/productDetail"; import { getAllMerchants } from "@/api/store/yxStoreProduct"; import { queryProductCombinationOrderDetail } from "@/api/store/yxStoreOrder"; import groupDetail from '@/views/shop/order/groupDetail.vue' import detail from "./index_detail"; import { dataCustom_to_sheetFormat, exportFile, mergeCell } from "@/utils/processFile"; // import xlsx from "xlsx"; import * as xlsx from 'xlsx-js-style'; import { saveAs } from 'file-saver'; export default { components: { workForm, deliverGood, workOrder, orderRecord, sendmessage, "product-detail": productDetail, groupDetail, detail }, mixins: [initData], data() { return { addDialog:false, delLoading: false, cids:[], cnames:'', query:{ "orderId":undefined, "fromType":undefined, "linkUserName":undefined, "addUser":undefined, "linkUserPhone":undefined, "isPass":undefined, "workOrderNum":undefined, "merId":undefined, "problemType":undefined, "dealingType":undefined, "saleType":undefined, }, queryTypeOptions: [ { key: "1", display_name: "小程序" }, { key: "2", display_name: "电话" }, { key: "3", display_name: "客服评价" } ], problemTypes:[ {label: '漏发', value: 1}, {label: '破损', value: 2}, {label: '错发', value: 3}, {label: '无物流信息', value: 4}, {label: '物流信息错误', value: 5}, {label: '临期产品', value: 6}, {label: '退货', value: 7}, {label: '商品与图片不符', value: 8}, {label: '其他', value: 9}, {label: '质量问题', value: 10}, {label: '待确认', value: -1} ], dealingTypes:[ {label: '补发', value: 1}, {label: '换货', value: 2}, {label: '退货', value: 3}, {label: '退货退款', value: 4}, {label: '仅退款', value: 5}, {label: '等待商户答复', value: 6}, {label: '待确认', value: -1} ], AftersalesStatus:[{label: '退款', value: 1},{label: '退货退款', value: 2},{label: '换货', value: 3},{label: '补寄', value: 4},{label: '待发货退款', value: 5}], isPassOptions: [ { value: "0", label: "未解决" }, { value: "1", label: "部分解决" }, { value: "2", label: "全部解决" }, { value: "3", label: "已复核" }], isAdd:false, workdialog:false, form:{ orderId:'' }, queryMersOptions: [], qustionList:[{ id:1, label:'漏发' },{ id:2, label:'破损' },{ id:3, label:'错发' },{ id:4, label:'无物流信息' },{ id:5, label:'物流信息错误' },{ id:6, label:'临期产品' },{ id:7, label:'退货' },{ id:8, label:'商品与图片不符' },{ id:10, label:'质量问题' },{ id:9, label:'其他' },{ id:-1, label:'待确认' }], orderStatus: [ { key: 0, value: '无售后' }, { key: 1, value: '用户取消售后申请' }, { key: 2, value: '超自动取消售后申请' }, { key: 3, value: '售后完成' }, { key: 4, value: '赔付E卡' }, { key: 5, value: '待上门取件' }, { key: 6, value: '返品质检不通过' }, { key: 7, value: '待返品质检' }, { key: 8, value: '待用户提交返品物流单号' }, { key: 11, value: '退款待审核' }, { key: 12, value: '退款审核通过' }, { key: 13, value: '退款审核不通过' }, { key: 14, value: '退款补发成功' }, { key: 15, value: '退款成功' }, { key: 21, value: '退货退款待审核' }, { key: 22, value: '退货退款审核不通过' }, { key: 23, value: '待退款' }, { key: 24, value: '退货退款成功' }, { key: 31, value: '换货待审核' }, { key: 32, value: '换货审核不通过' }, { key: 33, value: '待生成换货订单' }, { key: 34, value: '换货成功' }, { key: 41, value: '补寄待审核' }, { key: 42, value: '补寄审核不通过' }, { key: 43, value: '补寄成功' } ], exportLoading:false, tableHeight:document.body.clientHeight - 360, aa:false, dialogVisible:false, dialogUrl:[], } }, created() { this.$nextTick(() => { this.init() }) this.getAllMers() }, computed: { ...mapGetters(["permission"]) }, filters:{ filterType(val){ console.log('val++',val) this.qustionList.forEach(item=>{ if(item.id==val){ return item.label } }) } }, mounted() { this.setTableHeight(); window.addEventListener('resize', this.setTableHeight); }, beforeDestroy() { window.removeEventListener('resize', this.setTableHeight); }, methods: { analysisOrderStatus(val) { const status = this.orderStatus.find(item => item.key == val); return status ? status.value : '未知状态'; }, /** 导出 */ // exportXlsx() { // let addTimeStart // let addTimeEnd // if(this.query.addTime != undefined && this.query.addTime.length>0){ // addTimeStart=this.query.addTime[0] // addTimeEnd=this.query.addTime[1] // }else{ // addTimeStart=null // addTimeEnd=null // } // let params = { // orderId:this.query.orderId, // fromType:this.query.fromType, // linkUserName:this.query.linkUserName, // addUser:this.query.addUser, // linkUserPhone:this.query.linkUserPhone, // isPass:this.query.isPass, // problemType:this.query.problemType, // dealingType:this.query.dealingType, // saleType:this.query.saleType, // orderSource:this.query.orderSource, // workOrderNum:this.query.workOrderNum, // isDel: 0, // addTimeStart:addTimeStart, // addTimeEnd:addTimeEnd, // } // const loading = this.$loading({ // lock: true, // text: '数据正在导出中...', // spinner: 'el-icon-loading', // background: 'rgba(0, 0, 0, 0.7)' // }); // this.exportLoading = true; // exportData(params).then((res) => { // this.exportLoading = false; // if (res.data.success) { // loading.close(); // let flag = null; // let arr = []; // res.data.data.forEach((item, index) => { // if (flag != item.工单编号) { // flag = item.工单编号; // } else { // if (!arr.find((item1) => item1.name == item.工单编号)) { // let len = res.data.data.filter((item1) => item1.工单编号 == item.工单编号).length; // arr.push({ name: item.工单编号, index, len }); // } // } // }); // let header = {}; // header = { // 工单编号: "工单编号", // 订单号: "订单号", // 订单日期: "订单日期", // 状态: "状态", // 订单来源: "订单来源", // 领导工单: '领导工单', // 商户名称: "商户名称", // 商品名称: "商品名称", // 商品编码: "商品编码", // 问题类型: "问题类型", // 处理方式: "处理方式", // 问题描述: "问题描述", // 快递公司: "快递公司", // 快递单号: "快递单号", // 提报人: "提报人", // 提报人联系方式: "提报人联系方式", // 售后收货地址: "售后收货地址", // 创建间: "创建间", // 解决人:'解决人', // 图片链接:'图片链接', // }; // exportFile({ // titleStartRow: 0, // titleStartCol: 0, // startRow: 0, // data: res?.data?.data, // header, // fileName: "客服工单", // Func: (ws) => { // arr.forEach((item) => { // mergeCell( // ws, // `A${item.index + 1}`, // `A${item.index + item.len}` // ); // mergeCell( // ws, // `B${item.index + 1}`, // `B${item.index + item.len}` // ); // }); // return ws; // }, // }); // }else{ // loading.close(); // } // }) // .catch((err) => { // loading.close(); // this.exportLoading = false; // }); // }, exportXlsx() { let addTimeStart; let addTimeEnd; if (this.query.addTime && this.query.addTime.length > 0) { addTimeStart = this.query.addTime[0]; addTimeEnd = this.query.addTime[1]; } else { addTimeStart = null; addTimeEnd = null; } let params = { orderId: this.query.orderId, fromType: this.query.fromType, linkUserName: this.query.linkUserName, addUser: this.query.addUser, linkUserPhone: this.query.linkUserPhone, isPass: this.query.isPass, problemType: this.query.problemType, dealingType: this.query.dealingType, saleType: this.query.saleType, orderSource: this.query.orderSource, workOrderNum: this.query.workOrderNum, isDel: 0, addTimeStart, addTimeEnd, }; const loading = this.$loading({ lock: true, text: '数据正在导出中...', spinner: 'el-icon-loading', background: 'rgba(0, 0, 0, 0.7)' }); this.exportLoading = true; exportData(params).then((res) => { this.exportLoading = false; if (res.data.success) { loading.close(); // 定义表头 let header = { 工单编号: "工单编号", 订单号: "订单号", 订单日期: "订单日期", 状态: "状态", 订单来源: "订单来源", 领导工单: "领导工单", 商户名称: "商户名称", 商品名称: "商品名称", 商品编码: "商品编码", 问题类型: "问题类型", 处理方式: "处理方式", 问题描述: "问题描述", 快递公司: "快递公司", 快递单号: "快递单号", 提报人: "提报人", 提报人联系方式: "提报人联系方式", 售后收货地址: "售后收货地址", 创建间: "创建间", 解决人: "解决人", 用户上传凭证: "用户上传凭证" }; // 使用 xlsx.utils 创建空工作表 const ws = {}; const wb = xlsx.utils.book_new(); // 表头行 const headerRow = Object.values(header); xlsx.utils.sheet_add_aoa(ws, [headerRow], { origin: "A1" }); // 图片插入准备 const imagePromises = []; const imageInfoList = []; // 每行数据 + 图片处理 res.data.data.forEach((item, rowIndex) => { const rowData = [ item.工单编号, item.订单号, item.订单日期, item.状态, item.订单来源, item.领导工单, item.商户名称, item.商品名称, item.商品编码, item.问题类型, item.处理方式, item.问题描述, item.快递公司, item.快递单号, item.提报人, item.提报人联系方式, item.售后收货地址, item.创建间, item.解决人 ]; // 插入数据行 xlsx.utils.sheet_add_aoa(ws, [rowData], { origin: `A${rowIndex + 2}` }); // 图片插入 const imageUrl = item['用户上传凭证'][0]; console.log(imageUrl) if (imageUrl) { const imagePromise = fetch(imageUrl) .then(res => res.arrayBuffer()) .then(buffer => { const imageType = imageUrl.split('.').pop().toLowerCase(); const ext = imageType === 'jpg' || imageType === 'jpeg' ? 'jpeg' : imageType; const image = { image: buffer, extension: ext, }; imageInfoList.push({ image, position: `T${rowIndex + 2}`, // 插入到 T 列 }); }); imagePromises.push(imagePromise); } }); console.log(imagePromises) // 等待所有图片加载完成 Promise.all(imagePromises).then(() => { // 插入图片 xlsx.utils.sheet_add_img(ws, 0, imageInfoList); // 添加工作表到工作簿 xlsx.utils.book_append_sheet(wb, ws, "客服工单"); // 导出文件 xlsx.write(wb, { bookType: 'xlsx', type: 'binary', cellStyles: true }); // 使用 FileSaver.js 保存文件 xlsx.writeFile(wb, "客服工单.xlsx"); }); } else { loading.close(); this.$message.error(res.data.message || '导出失败'); } }).catch((err) => { loading.close(); this.exportLoading = false; this.$message.error('导出过程中发生错误'); console.error('导出错误:', err); }); }, checkDetail(row) { let _this = this.$refs.detail getAllInfo(row.id).then(res => { let data = res.data.data _this.form = data _this.form.merchantName = row.merchantName _this.form.merchantPhone = row.merchantPhone; let merchantAddressId = data.merchantAddressId if(merchantAddressId){ _this.queryMerAddressById({id:merchantAddressId}) } _this.dialog = true; }).catch(err => { }) }, queryProductCombinationOrderDetail(id){ queryProductCombinationOrderDetail(id).then((res)=>{ //主商品信息 let mainProduct = res.data.data.productInfo //子商品信息 let subProduct = res.data.data.combinationList this.$refs.groupDetail.form = mainProduct this.$refs.groupDetail.form.image = mainProduct.image.split(',') this.$refs.groupDetail.form.sliderImage = mainProduct.sliderImage.split(',') this.$refs.groupDetail.shopInfoData = subProduct this.$refs.groupDetail.dialog = true }) }, exportXlsxStatistics(){ let time = this.query.addTime let addTimeStart let addTimeEnd if(this.query.addTime != undefined && this.query.addTime.length>0){ addTimeStart=time[0] addTimeEnd=time[1] }else{ addTimeStart=null addTimeEnd=null } let params = { orderId:this.query.orderId, fromType:this.query.fromType, linkUserName:this.query.linkUserName, addUser:this.query.addUser, linkUserPhone:this.query.linkUserPhone, isPass:this.query.isPass, problemType:this.query.problemType, dealingType:this.query.dealingType, saleType:this.query.saleType, workOrderNum:this.query.workOrderNum, isDel: 0, addTimeStart:addTimeStart, addTimeEnd:addTimeEnd, } this.exportLoading = true; exportStatistics(params).then((res) => { this.exportLoading = false; if (res.data.success) { let flag = null; let arr = []; res.data.data.forEach((item, index) => { if (flag != item.id) { flag = item.id; } else { if (!arr.find((item1) => item1.name == item.id)) { let len = res.data.data.filter((item1) => item1.id == item.id).length; arr.push({ name: item.id, index, len }); } } }); let header = {}; header = { 商户名称: "商户名称", 质量问题: "质量问题", 商品与图片不符: "商品与图片不符", 无物流信息: "无物流信息", 物流信息错误: "物流信息错误", 临期产品: "临期产品", 漏发: "漏发", 破损: "破损", 退货: "退货", 错发: "错发", 其他: "其他", }; exportFile({ titleStartRow: 0, titleStartCol: 0, startRow: 0, data: res?.data?.data, header, fileName: "导出统计", Func: (ws) => { // arr.forEach((item) => { // mergeCell( // ws, // `A${item.index + 1}`, // `A${item.index + item.len}` // ); // }); return ws; }, }); } }) .catch((err) => { this.exportLoading = false; }); }, getAllMers() { getAllMerchants().then(({ data }) => { if (data && data.code === 200) { this.queryMersOptions = data.data; } else { this.$message({ message: data.msg, type: "error" }); this.queryMersOptions = []; } }); }, getRecordList(id){ this.delLoading = true reviewById(id).then(res => { console.log(res) // if(res.data.code==200){ // this.init() // this.$notify({ // title: res.data.msg, // type: 'success', // duration: 2500 // }) // } this.delLoading = false this.$refs['review'+id].doClose() this.dleChangePage() this.init() this.$notify({ title: '复核成功', type: 'success', duration: 2500 }) }).catch(err => { this.delLoading = false this.$refs['review'+id].doClose() console.log(err) }) }, lookDetail(id) { this.aa = true; let that=this this.$nextTick(() => { that.$refs.productDetailComp.getProductDetail(id); }) }, gdrecord(id){ let _this = this.$refs.orderRecord; _this.query.workOrderId=id this.$nextTick(() => { _this.init() }) _this.recordDialog=true; }, batchReview(){ if(this.cids.length<=0){ this.$notify({ title: '请至少选择一条数据!', type: 'warning', duration: 2500 }) return false } this.$confirm("确定所选工单复核通过吗?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }) .then(() => { reviewByIds(this.cids).then(res => { this.delLoading = false this.dleChangePage() this.init() this.$notify({ title: '复核成功', type: 'success', duration: 2500 }) }).catch(err => { this.delLoading = false console.log(err) }) }) .catch(() => {}); }, setTableHeight() { // console.log(window.innerWidth); // 1328 this.tableHeight = window.innerHeight - 120; // 假设顶部有其他内容占用100px }, cancel() { this.addDialog = false; }, addList(){ this.form.orderId=""; this.addDialog=true; }, doSubmit(){ let _thisAdd = this.$refs.workOrderFrom,orderObj; this.$refs['form'].validate(valid => { if (valid) { getOrderAllInfoByOid(this.form.orderId).then(res => { // console.log('res+++',res) if(res.data.code==200){ this.addDialog=false; orderObj=res.data.data const odeDetailList=JSON.parse(JSON.stringify(orderObj.detailList)) // odeDetailList.map(item => { // item.orderCartId=item.id // }) _thisAdd.goodsList = odeDetailList.map(item => { item.orderCartId=item.id item.maxProductNum = item.productNum; item.previewSrcList = []; return item; }); // console.log(odeDetailList) _thisAdd.form = { id: orderObj.id, //工单ID oid: orderObj.id, //工单ID orderId: orderObj.orderId, //订单编号 fromType:"", rmk:"", linkUserName:orderObj.linkUserName, addUser:orderObj.addUser, linkUserPhone:orderObj.linkUserPhone, linkUserAddress:orderObj.linkUserAddress, detailList:[], }; _thisAdd.workdialog = true; } }) } }) }, deliverGood(row){ const _this = this.$refs.deliverGoodForm; _this.resetForm(); _this.listId=row.id; _this.getMore(); _this.goodDialog = true; _this.loading = false; }, sendmessage(row){ this.$refs.sendmessage.form.linkUserPhone = row.linkUserPhone; this.$refs.sendmessage.form.id = row.id; this.$refs.sendmessage.sendDialog = true; }, //解决 getWorkOut(id){ this.delLoading = true getWorkOut(id).then(res => { this.delLoading = false this.$refs['pass'+id].doClose() this.dleChangePage() this.init() this.$notify({ title: '解决成功', type: 'success', duration: 2500 }) }).catch(err => { this.delLoading = false this.$refs['pass'+id].doClose() console.log(err) }) }, //批量删除 delList(){ if(this.cids.length<=0){ this.$notify({ title: '请至少选择一条数据', type: 'warning', duration: 2500 }) return false } this.$confirm("确定删除所选工单吗?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }) .then(() => { delList(this.cids).then(res => { this.delLoading = false // this.$refs[id].doClose() this.dleChangePage() this.init() this.$notify({ title: '删除成功', type: 'success', duration: 2500 }) }).catch(err => { this.delLoading = false console.log(err) }) }) .catch(() => {}); }, //批量提交 submitList(){ if(this.cids.length<=0){ this.$notify({ title: '请至少选择一条数据', type: 'warning', duration: 2500 }) return false } this.$confirm("确定提交所选工单吗?", "提示", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }).then(() => { submitList(this.cids).then(res => { this.delLoading = false // this.$refs[id].doClose() this.dleChangePage() this.init() this.$notify({ title: '提交成功', type: 'success', duration: 2500 }) }).catch(err => { this.delLoading = false console.log(err) }) }) .catch(() => {}); }, parseTime, checkPermission, handleSelectionChange(val){ let delIds = []; // this.cnames = '' val.forEach(x=>{ delIds.push(x.id) this.cnames += x.title + ',' }) console.log(delIds) this.cids=delIds; console.log(this.cids) // console.log(this.cnames) }, beforeInit() { this.url = 'api/lh-yshop-system/yxWorkOrder/list' const sort = 'id,desc' // console.log(this.query.addTime) // let time = this.query.addTime // let addTimeStart = '' // let addTimeEnd = '' // if(this.query.addTime != undefined && this.query.addTime.length>0){ // addTimeStart=time[0] // addTimeEnd=time[1] // }else{ // addTimeStart="" // addTimeEnd="" // } this.params = { addTime: this.query.addTime, current: this.page + 1, orderId:this.query.orderId, fromType:this.query.fromType, linkUserName:this.query.linkUserName, addUser:this.query.addUser, linkUserPhone:this.query.linkUserPhone, isPass:this.query.isPass, problemType:this.query.problemType, dealingType:this.query.dealingType, saleType:this.query.saleType, workOrderNum:this.query.workOrderNum, size: this.size, isDel: 0, } return true }, toQuerys() { delete this.query.fromType; delete this.query.isPass; (this.query.workOrderNum = ""), (this.query.orderId = ""), (this.query.linkUserName = ""), (this.query.addUser = ""), (this.query.linkUserPhone = ""), (this.query.merId = ""), (this.query.problemType = ""), (this.query.dealingType = ""), (this.query.addTime = []), (this.query.saleType = ""), this.$nextTick(() => { this.init(); }); }, subDelete(id) { this.delLoading = true del(id).then(res => { this.delLoading = false this.$refs[id].doClose() this.dleChangePage() this.init() this.$notify({ title: '删除成功', type: 'success', duration: 2500 }) }).catch(err => { this.delLoading = false this.$refs[id].doClose() console.log(err.response.data.message) }) }, detail(rowId,val) { this.isAdd = true const _this = this.$refs.workFormForm,that=this _this.listId=rowId; if(val==1){ _this.formFlag=true }else if(val==2){ _this.formFlag=false } getAllInfo(rowId).then(res => { let data=res.data.data const detailList = JSON.parse(JSON.stringify(data.detailList)); let formDetailList = detailList.map((item,ind)=>{ item.dealingType=Number(item.dealingType); item.problemType=Number(item.problemType); item.isPass=Number(item.isPass) console.log('dizhi +++',item.problemPhotoUrl) if(item.problemPhotoUrl!=''){ let ary = item.problemPhotoUrl.split(',') let previewSrcList = []; // _this.form.detailList[ind].problemPhotoUrl=ary // that.$set('_this.previewSrcList[ind]','srcList','') // _this.previewSrcList.push({srcList:[]}) ary.forEach((image,k)=>{ // _this.previewSrcList[ind].srcList.push({'url':item}) if(image) { previewSrcList.push({'url':image}) } }) item.previewSrcList = previewSrcList }else{ item.previewSrcList=[] // _this.previewSrcList.push({srcList:[]}) // _this.previewSrcList[ind].srcList.push('[]') } // console.log(' _this.previewSrcList', _this.previewSrcList) return item; }) _this.previewSrcList=[] _this.form={ id:data.id, merId:data.merId, orderId:data.orderId, orderSource: data.orderSource, workOrderNum:data.workOrderNum, linkUserName:data.linkUserName, addUser:data.addUser, linkUserPhone:data.linkUserPhone, linkUserAddress:data.linkUserAddress, orderAddTime:data.orderAddTime, orderPayTime:data.orderPayTime, isMultAdress:Number(data.isMultAdress), isDeliveryEnd:Number(data.isDeliveryEnd), isPass:Number(data.isPass), dealingType:Number(data.dealingType), problemType:Number(data.problemType), isTemp: data.isTemp, rmk:data.rmk, addTime:data.addTime, detailList:formDetailList } _this.workdialog = true _this.handelChage() }).catch(err => { // console.log(err.response.data.message) }) }, edit(data) { this.isAdd = false const _this = this.$refs.workFormForm _this.form = { id: data.id, title: data.title, integral: data.integral, couponPrice: data.couponPrice, useMinPrice: data.useMinPrice, couponTime: data.couponTime, sort: data.sort, isTemp: data.isTemp, addTime: data.addTime, isDel: data.isDel } _this.dialog = true }, /** 设置行的样式 */ tableRowClassName({ row, rowIndex }) { if (row.isLeader === "1" && (row.isPass == 0 || row.isPass == 1)) { return "leader-row"; } return ""; }, } } </script> <style lang="scss" scoped> // ::v-deep .el-input { // position: relative; // font-size: 12px; // line-height: normal; // } // ::v-deep .el-range-editor--small .el-range-input { // font-size: 12px; // } // .el-col input{border:#C0C4CC solid 1px !important; border-radius:3px !important;height:32px !important;width:100%;} // ::v-deep .el-form-item__label{font-size:12px !important; font-weight:500 !important;} // .dialog-footer { display: flex; justify-content:flex-end;} ::v-deep .el-form-item{ margin-bottom: 0px; } .el-table ::v-deep .leader-row { background: #e6a23c; } .app-container .app-container { padding: 0; } ::v-deep .app-container { padding-top: 10px; } ::v-deep .el-select { .el-input { .el-icon-circle-close { line-height: 30px !important; } } } .el-table ::v-deep .leader-row { background: #e6a23c; } .el-col ::v-deep { margin-bottom: 0; } ::v-deep .el-button--mini{ padding: 7px 8px; } </style>
07-31
// 解析数据引用关系的辅助函数 /** 解析数据中的引用关系 该函数用于处理嵌套的数据结构,将数据中的引用关系解析为实际的对象引用。 它会遍历数据中的所有实体,查找属性名中包含的数字(如"item1"), 并尝试将这些属性值替换为对应类型数据中的实际对象引用。 @param {Object} data - 包含嵌套引用的原始数据对象 @returns {Object} 处理后的数据对象,其中引用已被解析为实际对象 */ function resolveDataReferences(data) { const keys = Object.keys(data); for (const key of keys) { const entities = data[key]; for (const entity of entities) { for (const attribute in entity) { if (entity.hasOwnProperty(attribute)) { // 修复:统一使用复数形式查找引用类型 let refType = attribute.replace(/\d/g, ‘’); // 尝试直接查找复数形式 if (!data[refType] && data[${refType}s]) { refType = ${refType}s; } if (Array.isArray(entity[attribute])) { entity[attribute] = entity[attribute].map(item => data[refType]?.find(updateItem => updateItem.id === item.id) || item ); } else if (typeof entity[attribute] === “object” && entity[attribute] !== null) { entity[attribute] = data[refType]?.find(updateItem => updateItem.id === entity[attribute].id) || entity[attribute]; } } } } } return data; } // 解析单个实体的数据引用 /** 解析数据引用关系 该函数用于处理实体对象与数据源之间的引用关系,自动匹配并更新实体中的引用字段。 @param {Object} entity - 需要处理的实体对象 @param {Object} data - 包含引用数据的数据源对象 @returns {Object} 处理后的实体对象 功能说明: 遍历实体对象的每个属性 如果属性值是数组,则尝试在数据源中查找匹配项更新数组元素 如果属性值是对象,则尝试在数据源中查找匹配项更新该对象 自动处理单复数形式的数据源键名 */ function resolveDataReference(entity, data) { for (const attribute in entity) { if (entity.hasOwnProperty(attribute)) { // 修复:统一使用复数形式查找引用类型 let refType = attribute.replace(/\d/g, ‘’); // 尝试直接查找复数形式 if (!data[refType] && data[ParseError: KaTeX parse error: Expected '}', got 'EOF' at end of input: …]) { refType = {refType}s; } if (Array.isArray(entity[attribute])) { entity[attribute] = entity[attribute].map(item => data[refType]?.find(updateItem => updateItem.id === item.id) || item ); } else if (typeof entity[attribute] === “object” && entity[attribute] !== null) { entity[attribute] = data[refType]?.find(updateItem => updateItem.id === entity[attribute].id) || entity[attribute]; } } } return entity; } /** 懒加载处理器 负责管理实体类之间的关联依赖,实现按需加载 */ class LazyLoader { constructor(dataManager) { this.dataManager = dataManager; this.cache = new Map(); this.entityTypeMap = { bancai: ‘bancais’, dingdan: ‘dingdans’, mupi: ‘mupis’, chanpin: ‘chanpins’, kucun: ‘kucuns’, dingdan_bancai: ‘dingdan_bancais’, chanpin_zujian: ‘chanpin_zujians’, zujian: ‘zujians’, caizhi: ‘caizhis’, dingdan_chanpin: ‘dingdan_chanpins’, user: ‘users’, jinhuo: ‘jinhuos’ }; } /** 创建实体代理 @param {Object} entity 实体对象 @param {string} entityType 实体类型 @returns {Proxy} 返回代理后的实体 */ createProxy(entity, entityType) { const handler = { get: (target, prop) => { // 如果是普通属性,直接返回 if (typeof target[prop] !== ‘object’ || target[prop] === null) { return target[prop]; } // 检查是否是引用属性 const refType = this.getReferenceType(prop); if (refType) { // 如果是数组引用 if (Array.isArray(target[prop])) { return this.loadReferences(target[prop], refType); } // 如果是对象引用 else { return this.loadReference(target[prop], refType); } } return target[prop]; } }; if(!entity.__isProxy)return new Proxy(entity, handler); return entity; } /** 获取引用类型 @param {string} prop 属性名 @returns {string|null} 引用类型(复数形式) */ getReferenceType(prop) { // 去除数字后缀(如 “mupi1” -> “mupi”) const baseProp = prop.replace(/\d/g, ‘’); // 尝试直接匹配实体类型 if (this.entityTypeMap[baseProp]) { return this.entityTypeMap[baseProp]; } // 尝试添加 ‘s’ 后缀匹配 const pluralProp = ${baseProp}s; if (this.dataManager._rawData[pluralProp]) { return pluralProp; } return null; } /** 加载单个关联引用 @param {Object} ref 引用对象(包含id) @param {string} refType 引用类型(复数形式) @returns {Promise} 解析后的实体对象 */ loadReference(ref, refType) { if (!ref || !ref.id) const cacheKey = ${refType}_${ref.id}; // 检查缓存 if (this.cache.has(cacheKey)) { return this.cache.get(cacheKey); } // 尝试从本地数据查找 const entities = this.dataManager._rawData[refType] || []; let entity = entities.find(e => e.id === ref.id); // 如果本地找不到,尝试同步数据 if (!entity) { //this.dataManager.syncData(); entity = (this.dataManager._rawData[refType] || []).find(e => e.id === ref.id); } if (entity) { // 递归解析嵌套引用 const resolvedEntity = resolveDataReference({…entity}, this.dataManager._rawData); // 创建代理并缓存 const proxy = this.createProxy(resolvedEntity, refType); this.cache.set(cacheKey, proxy); return proxy; } return ref; // 返回原始引用 } /** 加载多个关联引用 @param {Array} refs 引用对象数组 @param {string} refType 引用类型(复数形式) @returns {Promise} 解析后的实体对象数组 */ loadReferences(refs, refType) { if (!Array.isArray(refs)) return []; return Promise.all( refs.map(ref => this.loadReference(ref, refType)) ); } /** 清除缓存 */ clearCache() { this.cache.clear(); } } class MiniProgramDataManager { constructor(baseUrl) { this.baseUrl = baseUrl; this.debug = true; // 试模式开关 this.requestCount = 0; // 请求计数器 // 数据结构定义 this._rawData = { bancais: [], dingdans: [], mupis: [], chanpins: [], kucuns: [], dingdan_bancais: [], chanpin_zujians: [], zujians: [], caizhis: [], dingdan_chanpins: [], users: [], jinhuos: [], _lastModified: null, _lastSync: null }; // 初始化网络状态 this.networkAvailable = false; this.checkNetwork().then(type => { this.networkAvailable = type !== ‘none’; }); this.lazyLoader = new LazyLoader(this); this.loadDataFromStorage(); this.isSyncing = false; this.lastSync = null; this.callbacks = { all: [], bancais: [], dingdan: [], mupi: [], chanpin: [], kucun: [], chanpin_zujian: [], dingdan_bancai: [], zujian: [], caizhi: [], dingdan_chanpin: [], user: [], jinhuo: [] }; this.syncQueue = Promise.resolve(); this.entiyeText = { bancai: ‘板材已存在’, dingdan: ‘订单已存在’, mupi: ‘木皮已存在’, chanpin: ‘产品已存在’, kucun: ‘已有库存记录’, chanpin_zujian: ‘产品已有该组件’, dingdan_bancai: ‘’, zujian: ‘组件已定义过了’, caizhi: ‘材质已定义过了’, dingdan_chanpin: ‘订单下已有该产品’, user: ‘’ }; this.syncInterval = 5 * 60 * 1000; // 5分钟 this.storageKey = ‘miniProgramData’; // 本地存储的键名 } get data() { // 创建数据代理 const handler = { get: (target, prop) => { // 处理特殊属性 if (prop.startsWith(‘_’)) { return target[prop]; } // 处理数组类型的实体集合 if (Array.isArray(target[prop])) { return target[prop].map(item => this.lazyLoader.createProxy(item, prop.replace(/s$/, ‘’)) ); } if (typeof target[prop] == ‘object’ && target[prop] === null) { return this.lazyLoader.createProxy(item, prop) } // 默认返回原始值 return target[prop]; }, // 保持其他操作不变 set: (target, prop, value) => { target[prop] = value; return true; } }; return new Proxy(this._rawData, handler); } // 添加显式初始化方法 async initialize() { console.log(‘初始化数据管理器…’); // 先尝试从本地存储加载数据 this.loadDataFromStorage(); // 检查是否有本地数据 const hasLocalData = this._rawData._lastSync !== null; console.log(‘本地存储数据状态:’, hasLocalData ? ‘有数据’ : ‘无数据’); // 启动自动同步 this.startAutoSync(); // 执行首次数据同步 try { await this.syncData(); console.log(‘数据同步完成’); // 打印数据统计 const stats = { bancais: this._rawData.bancais.length, kucuns: this._rawData.kucuns.length, dingdans: this._rawData.dingdans.length, chanpins: this._rawData.chanpins.length }; console.log(‘数据统计:’, stats); return true; } catch (error) { console.error(‘数据同步失败:’, error); // 如果同步失败但有本地数据,仍然返回成功 if (hasLocalData) { console.log(‘使用本地缓存数据’); return true; } throw error; } } /** 启动自动同步定器 每隔syncInterval毫秒检查并执行数据同步 如果已有同步任务进行中则跳过 */ startAutoSync() { if (this.autoSyncTimer) clearInterval(this.autoSyncTimer); this.autoSyncTimer = setInterval(() => { if (!this.isSyncing) this.syncData(); }, this.syncInterval); } /** 停止自动同步 */ stopAutoSync() { clearInterval(this.autoSyncTimer); } /** 检查网络状态 */ checkNetwork() { return new Promise((resolve) => { wx.getNetworkType({ success: (res) => { resolve(res.networkType); }, fail: () => { resolve(‘unknown’); } }); }); } /** 获取所有数据(全量或增量) @async @param {string} [since] - 增量获取的间戳,不传则全量获取 @returns {Promise} 是否获取成功 @description 根据since参数决定全量或增量获取数据 增量获取会合并新数据到现有数据 全量获取会直接替换现有数据 成功后会更新同步间并保存到本地存储 失败会触发错误回,若无历史数据则抛出错误 */ async fetchAll(since) { try { console.log(since ? 增量获取数据(自${since})… : ‘全量获取数据…’); let resolvedData; // 如果baseUrl为空,尝试加载本地测试数据 if (!this.baseUrl) { console.log(‘使用本地测试数据’); try { // 尝试从本地存储加载数据 this.loadDataFromStorage(); // 如果本地存储没有数据,尝试加载测试数据 if (!this._rawData._lastSync) { console.log(‘本地存储无数据,尝试加载测试数据’); // 创建测试数据 const testData = this.createTestData(); resolvedData = testData; console.log(‘已创建测试数据’); } else { console.log(‘使用本地存储的数据’); return true; // 已经从本地存储加载了数据 } } catch (e) { console.error(‘加载本地测试数据失败:’, e); throw new Error(‘无法加载测试数据’); } } else { // 正常从API获取数据 const params = since ? { since } : {}; resolvedData = await this.request(‘/app/all’, ‘GET’, params); } // 更新networkData if (resolvedData) { Object.keys(this.rawData).forEach(key => { if (key.startsWith(‘’)) return; if (resolvedData[key]) { if (since) { // 增量更新: 合并新数据到现有数据 resolvedData[key].forEach(newItem => { const index = this._rawData[key].findIndex(item => item.id === newItem.id); if (index >= 0) { this._rawData[key][index] = newItem; } else { this._rawData[key].push(newItem); } }); } else { // 全量更新: 直接替换 this._rawData[key] = resolvedData[key]; } } }); } // 更新同步间 this.lastSync = new Date(); this._rawData._lastSync = this.lastSync.toISOString(); // 保存到本地存储 this.saveDataToStorage(); this.triggerCallbacks(‘refresh’, ‘all’, this.data); return true; } catch (error) { console.error(‘Fetch error:’, error); this.triggerCallbacks(‘fetch_error’, ‘all’, { error }); // 失败尝试使用本地数据 if (!this.lastSync) { throw new Error(‘初始化数据获取失败’); } return false; } } // 创建测试数据 createTestData() { console.log(‘创建测试数据’); // 创建材质数据 const caizhis = [ { id: 1, name: ‘实木’ }, { id: 2, name: ‘密度板’ }, { id: 3, name: ‘多层板’ } ]; // 创建木皮数据 const mupis = [ { id: 1, name: ‘橡木’, you: false }, { id: 2, name: ‘胡桃木’, you: true }, { id: 3, name: ‘枫木’, you: false } ]; // 创建板材数据 const bancais = [ { id: 1, houdu: 18, caizhi: { id: 1 }, mupi1: { id: 1 }, mupi2: null }, { id: 2, houdu: 25, caizhi: { id: 2 }, mupi1: { id: 2 }, mupi2: { id: 3 } }, { id: 3, houdu: 12, caizhi: { id: 3 }, mupi1: { id: 3 }, mupi2: null } ]; // 创建库存数据 const kucuns = [ { id: 1, bancai: { id: 1 }, shuliang: 100 }, { id: 2, bancai: { id: 2 }, shuliang: 50 }, { id: 3, bancai: { id: 3 }, shuliang: 75 } ]; // 创建组件数据 const zujians = [ { id: 1, name: ‘桌面’ }, { id: 2, name: ‘桌腿’ }, { id: 3, name: ‘抽屉’ } ]; // 创建产品数据 const chanpins = [ { id: 1, name: ‘办公桌’, bianhao: ‘CP001’ }, { id: 2, name: ‘餐桌’, bianhao: ‘CP002’ }, { id: 3, name: ‘书桌’, bianhao: ‘CP003’ } ]; // 创建产品组件关联 const chanpin_zujians = [ { id: 1, chanpin: { id: 1 }, zujian: { id: 1 }, bancai: { id: 1 } }, { id: 2, chanpin: { id: 1 }, zujian: { id: 2 }, bancai: { id: 2 } }, { id: 3, chanpin: { id: 2 }, zujian: { id: 1 }, bancai: { id: 3 } } ]; // 创建订单数据 const dingdans = [ { id: 1, number: ‘DD001’, xiadan: ‘2023-01-01T00:00:00.000Z’, jiaohuo: ‘2023-01-15T00:00:00.000Z’ }, { id: 2, number: ‘DD002’, xiadan: ‘2023-02-01T00:00:00.000Z’, jiaohuo: ‘2023-02-15T00:00:00.000Z’ } ]; // 创建订单产品关联 const dingdan_chanpins = [ { id: 1, dingdan: { id: 1 }, chanpin: { id: 1 }, shuliang: 2 }, { id: 2, dingdan: { id: 1 }, chanpin: { id: 2 }, shuliang: 1 }, { id: 3, dingdan: { id: 2 }, chanpin: { id: 3 }, shuliang: 3 } ]; // 创建订单板材关联 const dingdan_bancais = [ { id: 1, dingdan: { id: 1 }, chanpin: { id: 1 }, zujian: { id: 1 }, bancai: { id: 1 }, shuliang: 2 }, { id: 2, dingdan: { id: 1 }, chanpin: { id: 1 }, zujian: { id: 2 }, bancai: { id: 2 }, shuliang: 8 }, { id: 3, dingdan: { id: 1 }, chanpin: { id: 2 }, zujian: { id: 1 }, bancai: { id: 3 }, shuliang: 1 } ]; // 创建用户数据 const users = [ { id: 1, name: ‘admin’, password: ‘admin123’ } ]; // 创建进货记录 const jinhuos = [ { id: 1, kucun: { id: 1 }, shuliang: 100, date: ‘2023-01-01T00:00:00.000Z’, text: ‘初始库存’, theTypeOfOperation: 1, user: { id: 1 } }, { id: 2, kucun: { id: 2 }, shuliang: 50, date: ‘2023-01-01T00:00:00.000Z’, text: ‘初始库存’, theTypeOfOperation: 1, user: { id: 1 } }, { id: 3, kucun: { id: 3 }, shuliang: 75, date: ‘2023-01-01T00:00:00.000Z’, text: ‘初始库存’, theTypeOfOperation: 1, user: { id: 1 } } ]; return { bancais, dingdans, mupis, chanpins, kucuns, dingdan_bancais, chanpin_zujians, zujians, caizhis, dingdan_chanpins, users, jinhuos }; } /** 微信小程序API请求封装 */ request(url, method = ‘GET’, data = null, retryCount = 3) { return new Promise((resolve, reject) => { const makeRequest = (attempt) => { const fullUrl = t h i s . b a s e U r l this.baseUrl{url}; if (this.debug) { console.log([请求] m e t h o d method{fullUrl}, { attempt, data, timestamp: new Date().toISOString() }); } wx.request({ url: fullUrl, method, data, header: { ‘Content-Type’: ‘application/json’ }, success: (res) => { if (this.debug) { console.log([响应] ${fullUrl}, { status: res.statusCode, data: res.data, headers: res.header }); } // 修复:更灵活的响应格式处理 if (!res.data) { const err = new Error(‘空响应数据’); if (attempt < retryCount) { this.retryRequest(makeRequest, attempt, retryCount, err); } else { reject(err); } return; } // 修复:支持多种成功状态码和响应格式 const isSuccess = res.statusCode >= 200 && res.statusCode < 300; const hasData = res.data && (res.data.data !== undefined || typeof res.data === ‘object’); if (isSuccess && hasData) { resolve(res.data.data || res.data); } else { const errMsg = res.data.message || res.data.text || ‘API错误’; const err = new Error(errMsg); if (attempt < retryCount) { this.retryRequest(makeRequest, attempt, retryCount, err); } else { reject(err); } } }, fail: (err) => { if (this.debug) { console.error([失败] ${fullUrl}, err); } const error = new Error(网络请求失败: ${err.errMsg || '未知错误'}); if (attempt < retryCount) { this.retryRequest(makeRequest, attempt, retryCount, error); } else { reject(error); } } }); }; makeRequest(1); }); } retryRequest(makeRequest, attempt, retryCount, error) { const delay = 1000 * attempt; console.warn(请求失败 ( a t t e m p t / attempt/{retryCount}), ${delay}ms后重试:, error.message); setTimeout(() => makeRequest(attempt + 1), delay); } /** 注册回函数 */ registerCallback(entity, callback) { if (!this.callbacks[entity]) { this.callbacks[entity] = []; } this.callbacks[entity].push(callback); } /** 注销回函数 */ unregisterCallback(entity, callback) { if (!this.callbacks[entity]) return; const index = this.callbacks[entity].indexOf(callback); if (index !== -1) { this.callbacks[entity].splice(index, 1); } } /** 触发回函数 */ triggerCallbacks(operation, entity, data) { this.callbacks.all.forEach(cb => cb(operation, entity, data)); if (this.callbacks[entity]) { this.callbacks[entity].forEach(cb => cb(operation, data)); } } /** 检查重复实体 */ checkDuplicate(entity, data) { // 修复:确保引用已解析 const resolvedData = resolveDataReference(data, this.data); switch (entity) { case ‘bancai’: return this.data.bancais.some(b => b.houdu === resolvedData.houdu && b.caizhi?.id === resolvedData.caizhi?.id && b.mupi1?.id === resolvedData.mupi1?.id && b.mupi2?.id === resolvedData.mupi2?.id ); case ‘caizhi’: return this.data.caizhis.some(c => c.name === resolvedData.name); case ‘mupi’: return this.data.mupis.some(m => m.name === resolvedData.name && m.you === resolvedData.you ); case ‘chanpin’: return this.data.chanpins.some(c => c.bianhao === resolvedData.bianhao); case ‘zujian’: return this.data.zujians.some(z => z.name === resolvedData.name); case ‘dingdan’: return this.data.dingdans.some(d => d.number === resolvedData.number); case ‘chanpin_zujian’: return this.data.chanpin_zujians.some(cz => cz.chanpin?.id === resolvedData.chanpin?.id && cz.zujian?.id === resolvedData.zujian?.id ); case ‘dingdan_chanpin’: return this.data.dingdan_chanpins.some(dc => dc.dingdan?.id === resolvedData.dingdan?.id && dc.chanpin?.id === resolvedData.chanpin?.id ); case ‘dingdan_bancai’: return this.data.dingdan_bancais.some(db => db.dingdan?.id === resolvedData.dingdan?.id && db.chanpin?.id === resolvedData.chanpin?.id && db.zujian?.id === resolvedData.zujian?.id && db.bancai?.id === resolvedData.bancai?.id ); case ‘user’: return this.data.users.some(u => u.name === resolvedData.name); default: return false; } } /** CRUD操作通用方法 */ async crudOperation(operation, entity, data) { try { // 使用微信请求API替代fetch const result = await this.request(/app/ o p e r a t i o n / operation/{entity}, ‘POST’, data); this.updateLocalData(operation, entity, result || data); this.triggerCallbacks(operation, entity, result || data); return result; } catch (error) { console.error(‘CRUD error:’, error); this.triggerCallbacks(${operation}_error, entity, { data, error: error.message }); throw error; } } /** 更新本地数据 @param {string} operation - 操作类型: ‘add’ | ‘update’ | ‘delete’ @param {string} entity - 实体名称 @param {Object} newData - 新数据对象(包含id字段) @description 根据操作类型对本地数据进行增删改操作 */ updateLocalData(operation, entity, newData) { const key = ${entity}s; const entities = this._rawData[key]; // 确保新数据的引用已解析 const resolvedData = resolveDataReference(newData, this._rawData); switch (operation) { case ‘add’: entities.push(resolvedData); break; case ‘update’: const index = entities.findIndex(item => item.id === resolvedData.id); if (index !== -1) { // 修复:使用对象展开操作符确保属性完整覆盖 entities[index] = { …entities[index], …resolvedData }; } else { entities.push(resolvedData); } break; case ‘delete’: const deleteIndex = entities.findIndex(item => item.id === resolvedData.id); if (deleteIndex !== -1) { entities.splice(deleteIndex, 1); } break; } // 更新最后修改间 this._rawData._lastModified = new Date().toISOString(); // 清除懒加载缓存 this.lazyLoader.clearCache(); // 保存修改后的数据到本地存储 this.saveDataToStorage(); } /** 同步数据方法 该方法用于异步获取所有数据,并处理同步过程中的并发请求 如果同步正在进行中,会将请求标记为待处理(pendingSync) 同步完成后会自动处理待处理的请求 @async @throws {Error} 当获取数据失败会抛出错误并记录日志 */ async syncData() { if (this.isSyncing) { this.pendingSync = true; return; } this.isSyncing = true; try { // 1. 先加载本地数据 this.loadDataFromStorage(); // 2. 获取最后同步间,用于增量更新 const since = this._rawData._lastSync || null; // 3. 获取增量数据 await this.fetchAll(since); // 4. 清除懒加载缓存 this.lazyLoader.clearCache(); // 5. 保存更新后的数据到本地存储 this.saveDataToStorage(); // 6. 触发数据更新回 this.triggerCallbacks(‘refresh’, ‘all’, this.data); } catch (error) { console.error(‘Sync failed:’, error); this.triggerCallbacks(‘sync_error’, ‘all’, { error }); // 失败尝试使用本地数据 if (!this._rawData._lastSync) { throw new Error(‘初始化数据同步失败’); } } finally { this.isSyncing = false; if (this.pendingSync) { this.pendingSync = false; this.syncData(); } } } /** 从本地存储加载数据 使用微信小程序的同步存储API获取之前保存的数据 */ loadDataFromStorage() { try { const storedData = wx.getStorageSync(this.storageKey); if (storedData) { // 修复:加载到_rawData而非data代理对象 this._rawData = storedData; // 解析所有引用关系 // resolveDataReferences(this._rawData); } } catch (error) { console.error(‘加载本地存储数据失败:’, error); // 提供默认空数据 this._rawData = { bancais: [], dingdans: [], mupis: [], chanpins: [], kucuns: [], dingdan_bancais: [], chanpin_zujians: [], zujians: [], caizhis: [], dingdan_chanpins: [], users: [], jinhuos: [], _lastModified: null, _lastSync: null }; } } /** 保存数据到本地存储 使用微信小程序的同步存储API持久化当前数据 */ saveDataToStorage() { try { // 修复:保存_rawData而非localData wx.setStorageSync(this.storageKey, this._rawData); } catch (error) { console.error(‘保存数据到本地存储失败:’, error); // 提示用户或执行降级策略 wx.showToast({ title: ‘数据保存失败,请稍后重试’, icon: ‘none’ }); } } /** 添加实体数据 @async @param {string} entity - 实体类型 @param {Object} data - 要添加的实体数据 @returns {Promise} 返回CRUD操作结果 @throws {Error} 如果数据已存在则抛出错误 */ async addEntity(entity, data) { if (this.checkDuplicate(entity, data)) { const errorMsg = ${this.entiyeText[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 }); } getBancaisForZujian(zujianId) { const dingdan_bancais = this.data.dingdan_bancais.filter(db => db.zujian?.id == zujianId); return dingdan_bancais.map(db => db.bancai).filter(Boolean); } /** 获取板材的库存信息 */ getKucunForBancai(bancaiId) { return this.data.kucuns.find(k => k.bancai?.id == bancaiId); } } // 导出模块 module.exports = MiniProgramDataManager; 添加一个函数,专门处理后端提供的事务接口@RestController @RequestMapping("/app/Transactional") public class TransactionalController{ @Autowired private BancaiService bancaiService; @RequestMapping(value = "/kucunbianji", method = RequestMethod.GET),给接口提供很多的具体事务的下级接口,并且大多不返回具体数据,成功了要主动获取增量信息更新数据
07-26
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值