Shopify商品表单多文件上传优化方案技术问询
Shopify订单文件上传界面优化方案咨询
我正在优化Shopify店铺的订单文件上传界面,Shopify提供product line item properties功能用于收集商品自定义信息。
目前我用以下HTML代码做了概念验证,将其放在商品表单内时,Shopify后台能得到预期的文件上传结果,但用户体验很差——必须逐个点击选择文件:
<input type="file" name="properties[upload1]"> <input type="file" name="properties[upload2]"> <input type="file" name="properties[upload3]">
现有界面需要用户逐个选择文件,操作繁琐,体验不佳。
由于Shopify不开放服务器端代码权限,我希望基于原生功能实现以下需求:
- 支持一次性选择多个文件
- (可选)支持对已选择的文件进行逐个删除操作
注:不考虑使用第三方应用,这类应用通常会将文件上传到自身服务器,再把文件URL作为文本传入Shopify。
我想确认以下两种方案是否可行:
- 将单个
<input type="file" multiple>控件选择的多个文件,拆分到多个独立的name="properties[uploadN]"输入框中,让Shopify能识别为独立的商品属性文件 - 直接使用
<input type="file" name="some_magic_array_name" multiple>,让Shopify自动将选中的多个文件处理成类似properties[upload1]、properties[upload2]这样的独立商品属性
经过调研我几乎认为无法实现,但仍希望征求更多思路。
更新:我可以接受异步JavaScript或纯HTML表单的实现方式,以下是我当前使用的异步加入购物车代码:
if (!customElements.get('product-form')) { customElements.define('product-form', class ProductForm extends HTMLElement { constructor() { super(); this.form = this.querySelector('form'); this.form.querySelector('[name=id]').disabled = false; this.form.addEventListener('submit', this.onSubmitHandler.bind(this)); this.cart = document.querySelector('cart-notification') || document.querySelector('cart-drawer'); this.submitButton = this.querySelector('[type="submit"]'); if (document.querySelector('cart-drawer')) this.submitButton.setAttribute('aria-haspopup', 'dialog'); this.hideErrors = this.dataset.hideErrors === 'true'; } onSubmitHandler(evt) { evt.preventDefault(); if (this.submitButton.getAttribute('aria-disabled') === 'true') return; this.handleErrorMessage(); this.submitButton.setAttribute('aria-disabled', true); this.submitButton.classList.add('loading'); this.querySelector('.loading-overlay__spinner').classList.remove('hidden'); const config = fetchConfig('javascript'); config.headers['X-Requested-With'] = 'XMLHttpRequest'; delete config.headers['Content-Type']; const formData = new FormData(this.form); if (this.cart) { formData.append('sections', this.cart.getSectionsToRender().map((section) => section.id)); formData.append('sections_url', window.location.pathname); this.cart.setActiveElement(document.activeElement); } //Added to generate selling plan add to cart response const sellingPlanId = window.getCurrentSellingPlanId(); formData.append("selling_plan", sellingPlanId); config.body = formData; fetch(`${routes.cart_add_url}`, config) .then((response) => response.json()) .then((response) => { if (response.status) { publish(PUB_SUB_EVENTS.cartError, {source: 'product-form', productVariantId: formData.get('id'), errors: response.description, message: response.message}); this.handleErrorMessage(response.description); const soldOutMessage = this.submitButton.querySelector('.sold-out-message'); if (!soldOutMessage) return; this.submitButton.setAttribute('aria-disabled', true); this.submitButton.querySelector('span').classList.add('hidden'); soldOutMessage.classList.remove('hidden'); this.error = true; return; } else if (!this.cart) { window.location = window.routes.cart_url; return; } if (!this.error) publish(PUB_SUB_EVENTS.cartUpdate, {source: 'product-form', productVariantId: formData.get('id')}); this.error = false; const quickAddModal = this.closest('quick-add-modal'); if (quickAddModal) { document.body.addEventListener('modalClosed', () => { setTimeout(() => { this.cart.renderContents(response) }); }, { once: true }); quickAddModal.hide(true); } else { this.cart.renderContents(response); } }) .catch((e) => { console.error(e); }) .finally(() => { this.submitButton.classList.remove('loading'); if (this.cart && this.cart.classList.contains('is-empty')) this.cart.classList.remove('is-empty'); if (!this.error) this.submitButton.removeAttribute('aria-disabled'); this.querySelector('.loading-overlay__spinner').classList.add('hidden'); }); } handleErrorMessage(errorMessage = false) { if (this.hideErrors) return; this.errorMessageWrapper = this.errorMessageWrapper || this.querySelector('.product-form__error-message-wrapper'); if (!this.errorMessageWrapper) return; this.errorMessage = this.errorMessage || this.errorMessageWrapper.querySelector('.product-form__error-message'); this.errorMessageWrapper.toggleAttribute('hidden', !errorMessage); if (errorMessage) { this.errorMessage.textContent = errorMessage; } } }); }
内容的提问来源于stack exchange,提问作者Dmitrii Kuznetsov
相关产品推荐
相关产品推荐

