Manufacturing

Your Trusted Partner in Handmade Product Manufacturing

Rattan Handicraft Manufacturing

EHM is proud to offer high-quality handcrafted rattan products. Using sustainable materials and meticulous manufacturing processes, we create beautiful and durable rattan items, from furniture to decorative accessories. Our products are not only eco-friendly but also bring natural beauty and warmth to any living space. Explore our collection and bring the elegance of rattan into your home.
View More
handicraf-material-1024w

Bamboo Handicraft Manufacturing

EHM excels in crafting exquisite bamboo products. With a commitment to sustainability, we utilize the strength and versatility of bamboo to create furniture and decor that are both stylish and durable. Our bamboo items blend traditional craftsmanship with modern design, offering eco-friendly solutions that enhance any environment.
View More
the-art-of-rattan-weaving-traditional-techniques-in-modern-times-1024w

Custom Handicraft Manufacturing

At EHM, we specialize in bespoke rattan handicraft manufacturing, crafting unique pieces that embody your personal style and preferences. Through a collaborative approach, we meticulously understand your requirements and aspirations, transforming them into exquisite handcrafted creations. Our expertise encompasses a wide range of rattan products, from furnishings to decorative accents, catering to diverse design needs and quality standards. Entrust us with your vision, and we’ll bring it to life, meticulously crafting exceptional rattan pieces that reflect your unique style.

FAQs

We are a manufacturer with many years of experience in producing storage baskets, storage boxes, storage trays, serving trays, and charger plates.

Featured Customers

function getQueryParameter(paramName) { const queryString = window.location.search; const urlParams = new URLSearchParams(queryString); return urlParams.get(paramName); } async function queryAll(url, select = '*', params = {}) { let data = []; const MAX_RETRIES = 3; let lastError = null; let lastStatus = 0; // 1. FETCH DỮ LIỆU VỚI LOGIC THỬ LẠI VÀ BẮT LỖI MẠNG/HTTP for (let attempt = 0; attempt < MAX_RETRIES; attempt++) { try { const response = await fetch(url); if (!response.ok) { lastStatus = response.status; throw new Error(`HTTP error! Status: ${lastStatus}`); } data = await response.json(); lastError = null; break; } catch (error) { lastError = error; if (attempt === MAX_RETRIES - 1) break; await new Promise(resolve => setTimeout(resolve, 1000 * (attempt + 1))); } } // XỬ LÝ LỖI CUỐI CÙNG (NÉM LỖI RA NGOÀI - PHÂN TÁCH TRÁCH NHIỆM) if (lastError) { let errorMessage = `Không thể tải dữ liệu từ API sau ${MAX_RETRIES} lần thử.`; if (lastStatus === 404) { errorMessage += ` (Lỗi 404: Không tìm thấy tài nguyên)`; } else if (lastStatus >= 400 && lastStatus < 500) { errorMessage += ` (Lỗi Client: ${lastStatus})`; } else if (lastStatus >= 500) { errorMessage += ` (Lỗi Server: ${lastStatus})`; } else { errorMessage += ` (Lỗi mạng/CORS)`; } throw new Error(errorMessage); } // --- 2. XỬ LÝ THAM SỐ VÀ DỮ LIỆU ĐÃ TẢI THÀNH CÔNG --- if (!Array.isArray(data)) return []; const defaultParams = { orderby: 'id', order: 'desc', limit: 10, offset: 0, where: {}, priority_ids: [] }; const filteredParams = Object.fromEntries( Object.entries(params).filter(([_, value]) => value !== undefined && value !== null && value !== '' ) ); const queryParams = { ...defaultParams, ...filteredParams }; let results = data; // --- 3. Lọc dữ liệu theo where (Giữ lại logic của bạn) --- if (queryParams.where && Object.keys(queryParams.where).length > 0) { results = results.filter(item => { return Object.entries(queryParams.where).every(([field, condition]) => { const itemValue = item[field]; if (typeof condition === 'object' && condition !== null) { if (condition.BETWEEN) { const [min, max] = condition.BETWEEN; return itemValue >= min && itemValue <= max; } if (condition.IN) { if (!Array.isArray(condition.IN)) return false; return condition.IN.includes(itemValue); } const entries = Object.entries(condition); if (entries.length === 0) return true; const [operator, value] = entries[0]; switch (operator) { case '=': return itemValue === value; case '>': return itemValue > value; case '<': return itemValue < value; case '>=': return itemValue >= value; case '<=': return itemValue <= value; case '<>': return itemValue != value; case 'LIKE': { if (typeof value !== 'string') return false; const regex = new RegExp(value.replace(/%/g, '.*'), 'i'); return regex.test(String(itemValue)); } default: return false; } } return itemValue == condition; }); }); } // --- 4. Xử lý select fields (Giữ lại logic của bạn) --- if (select && select !== '*') { const fields = Array.isArray(select) ? select : select.split(',').map(field => field.trim()); results = results.map(item => { const selectedData = {}; fields.forEach(field => { if (field in item) { selectedData[field] = item[field]; } }); return selectedData; }); } // --- 5. Sắp xếp kết quả (Giữ lại logic của bạn) --- results.sort((a, b) => { const PRIORITY_IDS = queryParams.priority_ids; const aId = a.id; const bId = b.id; const aIndex = Array.isArray(PRIORITY_IDS) ? PRIORITY_IDS.indexOf(aId) : -1; const bIndex = Array.isArray(PRIORITY_IDS) ? PRIORITY_IDS.indexOf(bId) : -1; const aInPriority = aIndex !== -1; const bInPriority = bIndex !== -1; // LOGIC ƯU TIÊN ID (Chạy đầu tiên) if (aInPriority && bInPriority) { // Case 1: Cả hai đều có ưu tiên -> Sắp xếp theo thứ tự mảng ưu tiên return aIndex - bIndex; } if (aInPriority) { // Case 2: Chỉ a có ưu tiên -> a lên trước (-1) return -1; } if (bInPriority) { // Case 3: Chỉ b có ưu tiên -> b lên trước (1) return 1; } // KẾT THÚC LOGIC ƯU TIÊN // --- Bắt đầu Sắp xếp Chung (Nếu không có ưu tiên) --- const sortCriteria = Array.isArray(queryParams.orderby) ? queryParams.orderby : [{ field: queryParams.orderby, order: queryParams.order }]; for (const sort of sortCriteria) { const field = sort.field; const order = (sort.order || 'desc').toLowerCase(); // Lấy giá trị thô để phân biệt true, false, và null/undefined const aValRaw = a[field]; const bValRaw = b[field]; // Gán giá trị fallback cho các trường hợp không phải featured_post let aVal = aValRaw ?? a.id ?? 0; let bVal = bValRaw ?? b.id ?? 0; let comparison = 0; // NHÁNH XỬ LÝ ĐẶC BIỆT: featured_post (true > null/false) if (field === 'featured_post') { // Chỉ có 'true' (so sánh nghiêm ngặt) mới nhận điểm 1 const aScore = aValRaw === true ? 1 : 0; const bScore = bValRaw === true ? 1 : 0; comparison = aScore - bScore; } else if (typeof aVal === 'boolean') { // Xử lý các trường boolean khác theo cách thông thường comparison = (aVal ? 1 : 0) - (bVal ? 1 : 0); } else if (field === 'date' || field.includes('date') || field.includes('updated')) { const dateA = new Date(aVal || 0).getTime(); const dateB = new Date(bVal || 0).getTime(); comparison = dateB - dateA; } else if (typeof aVal === 'number' && typeof bVal === 'number') { comparison = aVal - bVal; } else { const valueA = String(aVal || '').toLowerCase(); const valueB = String(bVal || '').toLowerCase(); comparison = valueA.localeCompare(valueB); } if (comparison !== 0) { return order === 'desc' ? -comparison : comparison; } } return 0; }); // --- 6. PHÂN TRANG / LOAD MORE --- const page = queryParams.page || 1; const offset = (page - 1) * queryParams.limit; const limit = queryParams.limit; return results.slice(offset, offset + limit); } // ======================================================= // II. HÀM RENDER LINH HOẠT (renderPost) // ======================================================= /** * Hiển thị một bài viết lên giao diện bằng Data Binding linh hoạt (dựa trên data-bind). * @param {Object} post Dữ liệu bài viết. * @param {HTMLElement} container Vùng chứa để thêm bài viết vào. * @param {HTMLElement} template Template HTML. */ function renderPost(post, container, template) { const clonedPost = template.cloneNode(true); // 1. Duyệt qua tất cả các phần tử có thuộc tính data-bind trong template const bindElements = clonedPost.querySelectorAll('[data-bind]'); bindElements.forEach(element => { const bindString = element.getAttribute('data-bind'); // Phân tích chuỗi data-bind: "text: title; href: url" bindString.split(';').forEach(binding => { const parts = binding.trim().split(':'); if (parts.length < 2) return; const [attribute, fieldName] = parts.map(s => s.trim()); // Lấy giá trị trường (có thể là title, body, userId, v.v.) const value = post[fieldName]; // Nếu giá trị không tồn tại hoặc là null/undefined if (value === undefined || value === null) { // Xử lý đặc biệt cho : nếu không có src, loại bỏ element if (element.tagName === 'IMG' && (attribute.toLowerCase() === 'src' || attribute.toLowerCase() === 'alt')) { element.remove(); } return; } // 2. Điền dữ liệu dựa trên thuộc tính: switch (attribute.toLowerCase()) { case 'text': element.innerHTML = value; break; case 'html': element.innerHTML = value; break; case 'src': case 'href': case 'alt': case 'caption': element.setAttribute(attribute, value); break; default: // Đặt các thuộc tính khác (ví dụ: data-*) element.setAttribute(attribute, value); break; } }); }); container.appendChild(clonedPost); } // ======================================================= // IV. CLASS QUẢN LÝ LOAD MORE ĐỘC LẬP (LoadMoreComponent) // ======================================================= class LoadMoreComponent { /** * @param {Object} config Cấu hình bao gồm ID/Selector của các phần tử UI và tham số truy vấn ban đầu. */ constructor(config) { // Tham chiếu DOM cục bộ (không phải toàn cục) this.container = document.getElementById(config.containerId); this.template = document.querySelector(config.templateSelector); this.loadMoreBtn = document.getElementById(config.loadMoreButtonId); this.loader = document.querySelector(config.loaderSelector); this.noMorePostsMsg = document.getElementById(config.noMorePostsMsgId); this.apiUrl = config.apiUrl; this.currentPage = 1; this.isLoading = false; this.hasMorePosts = true; this.initialParams = config.initialParams; this.delayMs = config.delayMs || 500; this.initialLimit = this.initialParams.limit || 5; // BIND: Đảm bảo 'this' trỏ đúng đến instance khi hàm được gọi như một callback this.handleLoadMore = this.handleLoadMore.bind(this); this.handleLoadError = this.handleLoadError.bind(this); } /** * Hàm xử lý lỗi tải: Dọn dẹp giao diện controls của instance này. * @param {string} message Thông báo lỗi */ handleLoadError(message) { console.error(`[${this.container.id}] Fetch/Load Error Handled:`, message); // Ẩn các phần tử controls của instance hiện tại if (this.loadMoreBtn) this.loadMoreBtn.style.display = 'none'; if (this.loader) this.loader.style.display = 'none'; // Hiển thị thông báo lỗi trên khu vực hiển thị bài viết if (this.container) { this.container.innerHTML = `

Đã xảy ra lỗi: ${message}

`; } if (this.noMorePostsMsg) this.noMorePostsMsg.style.display = 'none'; } /** * Logic chính để tải thêm bài viết (Load More). */ async handleLoadMore() { if (!this.loadMoreBtn || this.isLoading || !this.hasMorePosts) return; try { this.isLoading = true; this.loadMoreBtn.style.display = 'none'; if (this.loader) this.loader.style.display = 'block'; // MÔ PHỎNG ĐỘ TRỄ MẠNG await new Promise(resolve => setTimeout(resolve, this.delayMs)); const nextPage = this.currentPage + 1; const nextParams = { ...this.initialParams, limit: this.initialLimit, page: nextPage }; // Gọi hàm queryAll const newPosts = await queryAll(this.apiUrl, '*', nextParams); if (newPosts && newPosts.length > 0) { this.currentPage = nextPage; newPosts.forEach(post => { renderPost(post, this.container, this.template); }); this.hasMorePosts = newPosts.length === this.initialLimit; } else { this.hasMorePosts = false; } } catch (error) { console.error('Lỗi Load More:', error.message); this.handleLoadError(error.message); // Gọi hàm xử lý lỗi cục bộ this.hasMorePosts = false; } finally { this.isLoading = false; if (this.loader) this.loader.style.display = 'none'; if (this.hasMorePosts) { if (this.loadMoreBtn) this.loadMoreBtn.style.display = 'inline-flex'; } else { if (this.loadMoreBtn) this.loadMoreBtn.style.display = 'none'; if (this.noMorePostsMsg) this.noMorePostsMsg.style.display = 'inline-flex'; } } } /** * Khởi tạo: Tải trang đầu tiên và thiết lập sự kiện. */ async init() { if (!this.container || !this.template) { console.error(`[${this.container ? this.container.id : 'N/A'}] Cấu hình DOM không hợp lệ.`); return; } // --- BƯỚC 4: BÁO LỖI NẾU KHÔNG CÓ URL --- if (!this.apiUrl) { this.handleLoadError("Thiếu URL API trong cấu hình."); return; } try { const firstPagePosts = await queryAll(this.apiUrl, '*', { ...this.initialParams, page: 1 }); if (firstPagePosts && firstPagePosts.length > 0) { firstPagePosts.forEach(post => { renderPost(post, this.container, this.template); }); this.hasMorePosts = firstPagePosts.length === this.initialLimit; if (this.loadMoreBtn && this.hasMorePosts) { this.loadMoreBtn.style.display = 'inline-flex'; this.loadMoreBtn.addEventListener('click', this.handleLoadMore); } else if (this.loadMoreBtn) { this.loadMoreBtn.style.display = 'none'; if (this.noMorePostsMsg) this.noMorePostsMsg.style.display = 'inline-flex'; } } else { this.container.innerHTML = `

Không tìm thấy bài viết nào.

`; if (this.loadMoreBtn) this.loadMoreBtn.style.display = 'none'; } } catch (error) { console.error('Lỗi Tải Trang Ban Đầu:', error.message); this.handleLoadError(error.message); } } }