/* 文件路径: webapp/app.js */
// 应用入口文件
App({
globalData: {
userInfo: null,
isLoggedIn: false,
dataManager: null,
baseUrl: 'http://192.168.1.4:8080'
},
onLaunch: function() {
console.log('小程序启动');
// 初始化数据管理器
this.initDataManager();
// 检查登录状态
const loginInfo = wx.getStorageSync('loginInfo');
if (loginInfo && loginInfo.isLoggedIn) {
console.log('检测到已登录状态,直接登录');
this.globalData.isLoggedIn = true;
this.globalData.userInfo = {
name: loginInfo.name,
role: loginInfo.role,
userId: loginInfo.userId
};
// 加载初始数据
this.globalData.dataManager.fetchAll();
} else {
console.log('未检测到登录状态,准备检查微信登录');
// 未登录,使用延时函数确保在页面渲染后再显示登录提示
this.globalData.needLogin = true;
// 延迟500ms执行,确保页面已经渲染
console.log('设置登录检查延时');
setTimeout(() => {
console.log('执行延时回调');
if (typeof this.checkWechatLogin === 'function') {
console.log('checkWechatLogin方法存在');
this.checkWechatLogin();
} else {
console.error('checkWechatLogin方法不存在');
wx.redirectTo({ url: '/pages/login/login' });
}
}, 500);
}
},
// 检查微信登录
checkWechatLogin: function() {
console.log('准备显示登录提示对话框');
try {
wx.showModal({
title: '登录提示',
content: '是否使用微信账号登录?',
confirmText: '微信登录',
cancelText: '普通登录',
success: (res) => {
console.log('对话框选择结果:', res);
if (res.confirm) {
console.log('用户选择使用微信登录');
this.doWechatLogin();
} else {
console.log('用户选择普通登录');
wx.redirectTo({ url: '/pages/login/login' });
}
},
fail: (err) => {
console.error('显示对话框失败:', err);
// 如果对话框显示失败,直接跳转到普通登录页面
wx.redirectTo({ url: '/pages/login/login' });
}
});
} catch (error) {
console.error('调用showModal异常:', error);
wx.redirectTo({ url: '/pages/login/login' });
}
},
// 执行微信登录
doWechatLogin: function() {
wx.showLoading({ title: '登录中...' });
// 获取微信用户信息
wx.login({
success: (res) => {
if (res.code) {
console.log('获取微信code成功:', res.code);
// 发送code到后端换取用户信息
this.getUserInfoByWechatCode(res.code);
} else {
console.error('微信登录失败:', res);
wx.hideLoading();
wx.showToast({
title: '微信登录失败',
icon: 'none'
});
setTimeout(() => {
wx.redirectTo({ url: '/pages/login/login' });
}, 1500);
}
},
fail: (err) => {
console.error('微信登录失败:', err);
wx.hideLoading();
wx.showToast({
title: '微信登录失败',
icon: 'none'
});
setTimeout(() => {
wx.redirectTo({ url: '/pages/login/login' });
}, 1500);
}
});
},
// 通过微信code获取用户信息
getUserInfoByWechatCode: function(code) {
wx.request({
url: this.globalData.baseUrl + '/users/wechat-login',
method: 'POST',
data: { code: code },
success: (res) => {
wx.hideLoading();
if (res.data.status === 200 && res.data.data) {
// 用户已绑定,直接登录
console.log('微信用户已绑定,直接登录:', res.data);
this.login(res.data.data);
wx.switchTab({ url: '/pages/index/index' });
} else if (res.data.status === 201) {
// 用户未绑定,跳转到申请页面
console.log('微信用户未绑定,跳转到申请页面');
wx.redirectTo({
url: '/pages/register/register?openid=' + res.data.data.openid
});
} else {
// 登录失败
console.error('微信登录失败:', res.data);
wx.showToast({
title: res.data.text || '微信登录失败',
icon: 'none'
});
setTimeout(() => {
wx.redirectTo({ url: '/pages/login/login' });
}, 1500);
}
},
fail: (err) => {
console.error('微信登录请求失败:', err);
wx.hideLoading();
wx.showToast({
title: '网络请求失败',
icon: 'none'
});
setTimeout(() => {
wx.redirectTo({ url: '/pages/login/login' });
}, 1500);
}
});
},
initDataManager: function() {
// 导入新的MiniProgramDataManager类
const MiniProgramDataManager = require('./data/MiniProgramDataManager');
// 实例化数据管理器,并传递配置选项
this.globalData.dataManager = new MiniProgramDataManager(this.globalData.baseUrl);
},
// 全局登录方法
login: function(userInfo) {
console.log('保存用户信息:', userInfo);
wx.setStorageSync('loginInfo', {
isLoggedIn: true,
name: userInfo.name,
role: userInfo.role,
userId: userInfo.id // 使用id字段作为userId
});
this.globalData.isLoggedIn = true;
this.globalData.userInfo = userInfo;
// 重置登录标志,避免循环提示登录
this.globalData.needLogin = false;
// 直接加载数据,而不是重新初始化数据管理器
// 在App初始化时调用
this.globalData.dataManager.initialize();
// 在需要手动刷新时调用
this.globalData.dataManager.syncData();
},
// 全局登出方法
logout: function() {
wx.removeStorageSync('loginInfo');
this.globalData.isLoggedIn = false;
this.globalData.userInfo = null;
wx.redirectTo({ url: '/pages/login/login' });
}
});
================================================================================
/* 文件路径: webapp/app.json */
{
"pages": [
"pages/index/index",
"pages/login/login",
"pages/bancai/bancai",
"pages/dingdan/dingdan",
"pages/guanli/guanli",
"pages/register/register"
],
"window": {
"pageOrientation": "landscape",
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#2c3e50",
"navigationBarTitleText": "峤丞板材库存管理系统",
"navigationBarTextStyle": "white"
},
"tabBar": {
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "images/icon_home.png",
"selectedIconPath": "images/icon_home_selected.png"
},
{
"pagePath": "pages/bancai/bancai",
"text": "库存管理",
"iconPath": "images/icon_bancai.png",
"selectedIconPath": "images/icon_bancai_selected.png"
},
{
"pagePath": "pages/dingdan/dingdan",
"text": "订单录入",
"iconPath": "images/icon_tianjia.png",
"selectedIconPath": "images/icon_tianjia_selected.png"
},
{
"pagePath": "pages/guanli/guanli",
"text": "人员管理",
"iconPath": "images/icon_guanli.png",
"selectedIconPath": "images/icon_guanli_selected.png"
}
],
"color": "#333",
"selectedColor": "#3498db",
"backgroundColor": "#ffffff"
},
"sitemapLocation": "sitemap.json"
}
================================================================================
/* 文件路径: webapp/data/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;
================================================================================
/* 文件路径: webapp/data/ModalManager.js */
/**
* 增强版微信小程序弹窗管理器
* 支持嵌套模态框,统一管理不同类型的模态框
*/
class ModalManager {
constructor() {
// 弹窗配置默认值
this.defaultConfig = {
showClose: true, // 是否显示关闭按钮
maskClosable: true, // 点击遮罩是否可关闭
showCancel: true, // 是否显示取消按钮
cancelText: '取消', // 取消按钮文本
confirmText: '确定', // 确认按钮文本
confirmColor: '#3498db', // 确认按钮颜色
cancelColor: '#95a5a6', // 取消按钮颜色
width: '80%', // 弹窗宽度
zIndex: 1000, // 弹窗层级
animation: true, // 是否启用动画
customClass: '', // 自定义样式类
position: 'center', // 弹窗位置:center, top, bottom
duration: 300 // 动画持续时间(ms)
};
// 模态框栈 - 用于管理嵌套模态框
this.modalStack = [];
// 最大嵌套层级
this.maxNestedLevel = 3;
// 模态框计数器(用于生成唯一ID)
this.modalCounter = 0;
// 模态框类型注册表
this.modalTypes = {
// 订单页模态框
'createOrder': { name: 'showCreateOrderModal', page: 'dingdan' },
'addProduct': { name: 'showProductModal', page: 'dingdan', data: { isEditProduct: false } },
'editProduct': { name: 'showProductModal', page: 'dingdan', data: { isEditProduct: true } },
'addComponent': { name: 'showAddComponentModal', page: 'dingdan' },
'addBancai': { name: 'showAddBancaiModal', page: 'dingdan' },
// 板材页模态框
'bancaiDetail': { name: 'showDetailModal', page: 'bancai' },
'stockEdit': { name: 'showStockModal', page: 'bancai' },
// 首页模态框
'materialDetail': { name: 'showMaterialDetail', page: 'index' }
};
}
/**
* 注册新的模态框类型
* @param {string} type - 模态框类型标识
* @param {object} config - 模态框配置
*/
registerModalType(type, config) {
if (!type || !config.name || !config.page) {
console.error('注册模态框类型失败:缺少必要参数');
return false;
}
this.modalTypes[type] = { ...config };
return true;
}
/**
* 显示模态框
* @param {string} type - 模态框类型
* @param {object} data - 传递给模态框的数据
* @param {object} options - 额外选项
* @returns {Promise} - 返回Promise,resolve时传递模态框结果
*/
showModal(type, data = {}, options = {}) {
// 检查模态框类型是否已注册
const modalType = this.modalTypes[type];
if (!modalType) {
return Promise.reject(new Error(`未注册的模态框类型: ${type}`));
}
// 检查嵌套层级
if (this.modalStack.length >= this.maxNestedLevel) {
return Promise.reject(new Error(`模态框嵌套层级超过限制(${this.maxNestedLevel})`));
}
// 获取当前页面实例
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
// 生成唯一ID
const modalId = `modal_${++this.modalCounter}`;
// 创建模态框数据
const modalData = {
visible: true,
...modalType.data,
...data,
_modalId: modalId,
_nestedLevel: this.modalStack.length + 1,
_zIndex: this.defaultConfig.zIndex + (this.modalStack.length * 100)
};
return new Promise((resolve, reject) => {
// 创建模态框对象
const modal = {
id: modalId,
type,
pageContext: currentPage,
name: modalType.name,
resolve,
reject,
data: modalData
};
// 添加到模态框栈
this.modalStack.push(modal);
// 设置页面数据,显示模态框
const dataUpdate = {};
dataUpdate[modalType.name] = modalData;
currentPage.setData(dataUpdate);
});
}
/**
* 关闭最上层模态框
* @param {any} result - 模态框结果
* @returns {boolean} - 是否成功关闭
*/
closeModal(result) {
if (this.modalStack.length === 0) return false;
// 获取最上层模态框
const modal = this.modalStack.pop();
const { pageContext, name, data } = modal;
// 隐藏模态框
const dataUpdate = {};
dataUpdate[name] = { ...data, visible: false };
pageContext.setData(dataUpdate);
// 延迟移除,等待动画完成
setTimeout(() => {
const cleanUpdate = {};
cleanUpdate[name] = null;
pageContext.setData(cleanUpdate);
// 解析Promise
modal.resolve(result);
}, this.defaultConfig.duration);
return true;
}
/**
* 关闭所有模态框
*/
closeAllModals() {
while (this.modalStack.length > 0) {
this.closeModal();
}
}
/**
* 获取当前模态框栈信息
* @returns {Array} - 模态框栈信息
*/
getModalStackInfo() {
return this.modalStack.map(modal => ({
id: modal.id,
type: modal.type,
name: modal.name,
level: modal.data._nestedLevel
}));
}
// 以下是便捷方法,用于快速显示特定类型的模态框
// 订单页模态框
showCreateOrderModal(data, options) {
return this.showModal('createOrder', data, options);
}
showAddProductModal(data, options) {
return this.showModal('addProduct', data, options);
}
showEditProductModal(data, options) {
return this.showModal('editProduct', data, options);
}
showAddComponentModal(data, options) {
return this.showModal('addComponent', data, options);
}
showAddBancaiModal(data, options) {
return this.showModal('addBancai', data, options);
}
// 板材页模态框
showBancaiDetailModal(data, options) {
return this.showModal('bancaiDetail', data, options);
}
showStockEditModal(data, options) {
return this.showModal('stockEdit', data, options);
}
// 首页模态框
showMaterialDetailModal(data, options) {
return this.showModal('materialDetail', data, options);
}
// 显示提示框
showToast(options) {
return new Promise((resolve, reject) => {
wx.showToast({
title: options.title || '',
icon: options.icon || 'none',
image: options.image,
duration: options.duration || 1500,
mask: options.mask || false,
success: (res) => {
if (options.success) options.success(res);
resolve(res);
},
fail: (err) => {
if (options.fail) options.fail(err);
reject(err);
},
complete: options.complete
});
});
}
// 显示加载提示
showLoading(options = {}) {
options.title = title;
}
return this.showActionSheet(options)
.then(res => res.tapIndex)
.catch(() => -1);
}
// 显示创建订单模态框
showCreateOrderModal(pageContext, data) {
return this.showCustomModal(pageContext, {
name: 'showCreateOrderModal',
data
});
}
// 显示添加产品模态框
showAddProductModal(pageContext, data) {
return this.showCustomModal(pageContext, {
name: 'showProductModal',
data: {
...data,
isEditProduct: false
}
});
}
// 显示编辑产品模态框
showEditProductModal(pageContext, data) {
return this.showCustomModal(pageContext, {
name: 'showProductModal',
data: {
...data,
isEditProduct: true
}
});
}
// 显示添加组件模态框
showAddComponentModal(pageContext, data) {
return this.showCustomModal(pageContext, {
name: 'showAddComponentModal',
data
});
}
// 显示添加板材模态框
showAddBancaiModal(pageContext, data) {
return this.showCustomModal(pageContext, {
name: 'showAddBancaiModal',
data
});
}
// 显示板材详情模态框
showBancaiDetailModal(pageContext, data) {
return this.showCustomModal(pageContext, {
name: 'showDetailModal',
data
});
}
// 显示库存编辑模态框
showStockEditModal(pageContext, data) {
return this.showCustomModal(pageContext, {
name: 'showStockModal',
data
});
}
// 显示板材详情弹窗(index.wxml中的)
showMaterialDetailModal(pageContext, data) {
return this.showCustomModal(pageContext, {
name: 'showMaterialDetail',
data
});
}
}
// 创建单例
const modalManager = new ModalManager();
// 导出模块
module.exports = modalManager;
================================================================================
/* 文件路径: webapp/images/bg-01.jpg */
����-----------------------------------------------------------------------------------把dingdan.wxml,index.wxml,bancai.wxml中的拟态框集合到单独的一个类中,所有的拟态框放入统一的WXML中并统一样式,其他界面调用ModalManager时只要选择哪一类的拟态框并传入数据就可以,并且实现嵌套拟态框