让 SHOPLINE 购物车更友好地显示定制数据

让 SHOPLINE 购物车更友好地显示定制数据

2025年9月18日

先看效果



让我们开始吧!

登录你的 SHOPLINE 后台,进入 在线商店 > 店铺设置 页面。


这里我们用 SHOPLINE 的 Modern 主题(2.1)为例并拷贝,点击 “更多” 按钮,进入 “编辑代码” 页面。


选择 “Snippets” 文件并点击 “新建 snippet” 按钮。


创建一个名为 customeow-data.html 的文件。


复制下面全部代码并粘贴到 customeow-data.html 的文件中。

{{!--
  Renders CustoMeow Custom Data for Shopline Theme 2.0

  Accepts:
  - properties: {Array} Cart item properties
  - title: {String} Custom data title (optional)
  - enable_dark: {Boolean} Website dark mode, default is false (optional)
  - text_color: {String} text color, hex or rgba, default is oklch(37.3% .034 259.733), enable_dark text color is white. (optional)
  - preview_font_size: {Number} Preview text font size (optional), 12~16, default is 12
  - preview_image_width: {Number} Preview image width (optional), default is 60
  - preview_image_radius: {Number} Preview image border radius (optional), default is 6
  - enable_modal: {Boolean} Show image modal when true
  - modal_background_color: '' {String} Image modal background color, hex or rgba, default is black (optional)
  - cart_item_id: {String} Cart item id (optional)
  - cart_item_image_classname: {String} Preview first image cover thumbnail (optional)

  Usage:
  {{> customeow-data properties=item.properties title='Your personalization' preview_font_size=12 preview_image_width=60 preview_image_radius=6 enable_modal=true}}
--}}

{{assign "show_title" true}}
{{#unless title}}
  {{assign "show_title" false}}
{{/unless}}

{{assign "is_dark" false}}
{{#if enable_dark}}
    {{assign "is_dark" true}}
{{/if}}

{{assign "label_color" "oklch(37.3% .034 259.733)"}}
{{#if text_color}}
    {{assign "label_color" text_color}}
{{/if}}

{{#if preview_font_size}}
  {{#if preview_font_size < 12 or preview_font_size > 16}}
    {{assign "preview_font_size" 12}}
  {{/if}}
{{else}}
  {{assign "preview_font_size" 12}}
{{/if}}

{{#unless preview_image_width}}
  {{assign "preview_image_width" 60}}
{{/unless}}

{{assign "preview_modal_radius" 12}}
{{assign "preview_modal_inner_radius" 11}}
{{#if preview_image_radius}}
  {{assign "preview_modal_radius" (times preview_image_radius 2)}}
  {{assign "preview_modal_inner_radius" (minus preview_modal_radius 1)}}
{{/if}}

{{assign "enable_image_modal" true}}
{{#unless enable_modal}}
  {{assign "enable_image_modal" false}}
{{/unless}}

{{#unless modal_background_color}}
  {{assign modal_background_color 'black'}}
{{/unless}}

{{assign "item_id" ''}}
{{#if cart_item_id}}
    {{assign "item_id" cart_item_id}}
{{/if}}

{{assign "cart_item_class" ''}}
{{#if cart_item_image_classname}}
    {{assign "cart_item_class" cart_item_image_classname}}
{{/if}}

{{assign "property_texts" ''}}
{{assign "property_orginal_images" ''}}
{{#for properties as |property|}}
    {{#if property.name == "preview.texts"}}
        {{assign "property_texts" (split property.value ',')}}
    {{else if property.name == "preview.effects" or property.name == "preview.images"}}
        {{#if (size property_orginal_images) > 0}}
            {{assign "property_orginal_images" (append property_orginal_images (append ',' property.value))}}
        {{else}}
            {{assign "property_orginal_images" property.value}}
        {{/if}}
    {{/if}}
{{/for}}
{{assign "property_images" (split property_orginal_images ',')}}


{{#if enable_image_modal}}
  <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
{{/if}}

<style>
  .cm-customization {
    margin-top: 20px;
  }
  .cm-customization-title {
    font-size: 14px;
    font-weight: 500;
    color: {{#if is_dark}}white{{else}}{{label_color}}{{/if}};
  }
  .cm-customization-container {
    padding: 4px 0 8px;
    display: none;
  }
  .cm-customization-container.active {
    display: block;
  }
  .cm-customization-texts {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
  }
  .cm-customization-texts-tag {
    padding: 2px 4px;
    border-radius: 4px;
    background-color: {{#if is_dark}}rgba(255,255,255,0.06){{else}}rgba(0,0,0,0.06){{/if}};
    color: {{#if is_dark}}white{{else}}{{label_color}}{{/if}};
    font-size: {{preview_font_size}}px;
  }
  .cm-customization-images {
    margin-top: 8px;
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
  }
  .cm-customization-images-thumbnail {
    width: {{preview_image_width}}px;
    border-radius: {{preview_image_radius}}px;
    overflow: hidden;
    position: relative;
    aspect-ratio: 1 / 1;
  }
  .cm-customization-images-thumbnail.cursor-pointer {
    cursor: pointer;
  }
  .cm-customization-images-thumbnail:before {
    content: '';
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    border: 1px solid {{#if is_dark}}rgba(255,255,255,0.2){{else}}rgba(0,0,0,0.2){{/if}};
    border-radius: {{preview_image_radius}}px;
  }
  .cm-customization-images-thumbnail img {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .cm-customization-images-thumbnail-mask {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    background-color: rgba(0,0,0,0.4);
    display: flex;
    justify-content: center;
    align-items: center;
    opacity: 0;
    transition: all cubic-bezier(0.33, 1, 0.68, 1) 200ms;
  }
  .cm-customization-images-thumbnail:hover .cm-customization-images-thumbnail-mask {
    opacity: 1;
  }
  .cm-customization-images-thumbnail-svg {
    width: 20px;
    height: 20px;
  }

  .cm-customization-transition {
    transition: all 200ms cubic-bezier(0.33, 1, 0.68, 1);
  }
  .cm-customization-bg-from {
    opacity: 0;
  }
  .cm-customization-bg-to {
    opacity: 1;
  }
  .cm-customization-modal-from {
    opacity: 0;
    transform: scale(0.94);
  }
  .cm-customization-modal-to {
    opacity: 1;
    transform: scale(1);
  }
  .cm-customization-close-from {
    opacity: 0;
    transform: translateY(10px);
  }
  .cm-customization-close-to {
    opacity: 1;
    transform: translateY(0);
  }
  .cm-customization-modal {
    width: 100%;
    height: 100%;
    position: fixed;
    left: 0;
    top: 0;
    z-index: 99999999;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: column;
  }
  .cm-customization-modal-bg {
    width: 100%;
    height: 100%;
    position: absolute;
    background-color: rgba(0,0,0,0.8);
    z-index: 0;
  }
  .cm-customization-splide {
    width: 80%;
    aspect-ratio: 1 / 1;
    background-color: {{modal_background_color}};
    position: relative;
    z-index: 1;
    border-radius: {{preview_modal_radius}}px;
    overflow: hidden;
  }
  .cm-customization-splide:before {
    content: '';
    position: absolute;
    left: 1px;
    right: 1px;
    top: 1px;
    bottom: 1px;
    border: 1px solid rgba(255,255,255,0.16);
    border-radius: {{preview_modal_inner_radius}}px;
    z-index: 10;
    pointer-events: none;
  }
  .cm-customization-splide .cm-customization-splide-frame,
  .cm-customization-splide img {
    width: 100%;
    height: 100%;
    object-fit: contain;
  }
  .cm-customization-splide .cm-customization-splide-frame {
    aspect-ratio: 1 / 1;
    position: relative;
  }
  .cm-customization-splide .cm-customization-splide-button {
    display: none;
  }
  .cm-customization-splide:hover .cm-customization-splide-button {
    display: block;
  }
  .cm-customization-splide-button {
    width: 36px;
    height: 36px;
    background-color: white;
    box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
    border-radius: 100px;
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    cursor: pointer;
  }
  .cm-customization-splide-button span {
    width: 100%;
    height: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .cm-customization-splide-button span svg {
    width: 16px;
    height: 16px;
    fill: oklch(44.6% 0.03 256.802);
  }
  .cm-customization-splide-left {
    left: 12px;
  }
  .cm-customization-splide-right {
    right: 12px;
  }
  .cm-customization-modal-close {
    width: 36px;
    height: 36px;
    border: 2px solid white;
    border-radius: 100%;
    position: relative;
    z-index: 5;
    display: flex;
    justify-content: center;
    align-items: center;
    margin-top: 20px;
    cursor: pointer;
  }
  .cm-customization-modal-close svg {
    width: 20px;
    height: 20px;
  }
  @media (width >= 640px) {
    .cm-customization-splide {
      width: 480px;
    }
  }
  {{#if item_id and cart_item_class}}
    #{{item_id}} .{{cart_item_class}} {
      position: relative;
    }
    #{{item_id}} .{{cart_item_class}}:before {
      content: '';
      width: 100%;
      height: 100%;
      background-image: url({{first (split property_orginal_images ',')}});
      background-position: center top;
      background-size: contain;
      background-color: white;
      background-repeat: no-repeat;
      position: absolute;
      left: 0;
      top: 0;
    }
  {{/if}}
</style>

<div class="cm-customization">
  {{#if show_title}}
    <div class="cm-customization-title">{{ title }}</div>
  {{/if}}
  <div class="cm-customization-container" :class="init ? 'active' : ''"
  x-data="{
    init: false,
    openModal: false,
    previewIndex: 0,
    imageArray: [],
    currentImageUrl: '',
    showSlideLeftButton: false,
    showSlideRightButton: false
  }"
  x-init="init = true">
    {{!-- texts --}}
    {{#if (size property_texts) > 0 }}
      <div class="cm-customization-texts">
        {{#for property_texts as |item| }}
          <span class="cm-customization-texts-tag">{{ item }}</span>
        {{/for}}
      </div>
    {{/if}}
    {{!-- preview thumbnail --}}
    {{#if (size property_images) > 0}}
    <div class="cm-customization-images">
        {{#for property_images as |item|}}
            <div class="cm-customization-images-thumbnail {{#if enable_image_modal}}cursor-pointer{{/if}}" 
                x-data="{
                index: {{ forloop.index0 }},
                propertyImages: '{{ property_orginal_images }}'
                }"
                @click="
                previewIndex = index
                imageArray = propertyImages.split(',')
                if (imageArray.length > 1) {
                    if (index > 0 && index < imageArray.length - 1) {
                    showSlideLeftButton = true
                    showSlideRightButton = true
                    } else if (index === 0) {
                    showSlideLeftButton = false
                    showSlideRightButton = true
                    } else if (index === imageArray.length - 1) {
                    showSlideLeftButton = true
                    showSlideRightButton = false
                    }
                } 
                currentImageUrl = imageArray[index]
                openModal = true
                ">
                {{#if enable_image_modal}}
                <div class="cm-customization-images-thumbnail-mask">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="white" class="cm-customization-images-thumbnail-svg">
                    <path d="M9 6a.75.75 0 0 1 .75.75v1.5h1.5a.75.75 0 0 1 0 1.5h-1.5v1.5a.75.75 0 0 1-1.5 0v-1.5h-1.5a.75.75 0 0 1 0-1.5h1.5v-1.5A.75.75 0 0 1 9 6Z" />
                    <path fill-rule="evenodd" d="M2 9a7 7 0 1 1 12.452 4.391l3.328 3.329a.75.75 0 1 1-1.06 1.06l-3.329-3.328A7 7 0 0 1 2 9Zm7-5.5a5.5 5.5 0 1 0 0 11 5.5 5.5 0 0 0 0-11Z" clip-rule="evenodd" />
                    </svg>
                </div>
                {{/if}}
                <img width="120" height="120" src="{{ item }}?x-oss-process=image/resize,s_{{times preview_image_width 2}}" />
            </div>
        {{/for}}
    </div>
    {{/if}}
    {{!-- preview image modal --}}
    {{#if enable_image_modal}}
        <div class="cm-customization-modal"
        x-show="openModal">
            <div class="cm-customization-splide" role="group" aria-label="{{ title }}"
            x-show="openModal"
            x-transition:enter="cm-customization-transition"
            x-transition:enter-start="cm-customization-modal-from"
            x-transition:enter-end="cm-customization-modal-to"
            x-transition:leave="cm-customization-transition"
            x-transition:leave-start="cm-customization-modal-to"
            x-transition:leave-end="cm-customization-modal-from">
            <div class="cm-customization-splide-frame">
                <img width="400" height="400" :src="currentImageUrl + '?x-oss-process=image/resize,l_960'">
                <div x-show="showSlideLeftButton" class="cm-customization-splide-button cm-customization-splide-left"
                @click="
                previewIndex -= 1
                currentImageUrl = imageArray[previewIndex]
                if (imageArray.length > 1) {
                    if (previewIndex > 0 && previewIndex < imageArray.length - 1) {
                    showSlideLeftButton = true
                    showSlideRightButton = true
                    } else if (previewIndex === 0) {
                    showSlideLeftButton = false
                    showSlideRightButton = true
                    } else if (previewIndex === imageArray.length - 1) {
                    showSlideLeftButton = true
                    showSlideRightButton = false
                    }
                } 
                ">
                <span>
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
                    <path fill-rule="evenodd" d="M14 8a.75.75 0 0 1-.75.75H4.56l3.22 3.22a.75.75 0 1 1-1.06 1.06l-4.5-4.5a.75.75 0 0 1 0-1.06l4.5-4.5a.75.75 0 0 1 1.06 1.06L4.56 7.25h8.69A.75.75 0 0 1 14 8Z" clip-rule="evenodd" />
                    </svg>
                </span>
                </div>
                <div x-show="showSlideRightButton" class="cm-customization-splide-button cm-customization-splide-right"
                @click="
                previewIndex += 1
                currentImageUrl = imageArray[previewIndex]
                if (imageArray.length > 1) {
                    if (previewIndex > 0 && previewIndex < imageArray.length - 1) {
                    showSlideLeftButton = true
                    showSlideRightButton = true
                    } else if (previewIndex === 0) {
                    showSlideLeftButton = false
                    showSlideRightButton = true
                    } else if (previewIndex === imageArray.length - 1) {
                    showSlideLeftButton = true
                    showSlideRightButton = false
                    }
                } 
                ">
                <span>
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor" class="size-4">
                    <path fill-rule="evenodd" d="M2 8a.75.75 0 0 1 .75-.75h8.69L8.22 4.03a.75.75 0 0 1 1.06-1.06l4.5 4.5a.75.75 0 0 1 0 1.06l-4.5 4.5a.75.75 0 0 1-1.06-1.06l3.22-3.22H2.75A.75.75 0 0 1 2 8Z" clip-rule="evenodd" />
                    </svg>
                </span>
                </div>
            </div>
            </div>
            <div class="cm-customization-modal-close" 
            x-show="openModal"
            x-transition:enter="cm-customization-transition"
            x-transition:enter-start="cm-customization-close-from"
            x-transition:enter-end="cm-customization-close-to"
            x-transition:leave="cm-customization-transition"
            x-transition:leave-start="cm-customization-close-to"
            x-transition:leave-end="cm-customization-close-from"
            @click="
                openModal = false
            ">
            <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="white">
                <path d="M5.28 4.22a.75.75 0 0 0-1.06 1.06L6.94 8l-2.72 2.72a.75.75 0 1 0 1.06 1.06L8 9.06l2.72 2.72a.75.75 0 1 0 1.06-1.06L9.06 8l2.72-2.72a.75.75 0 0 0-1.06-1.06L8 6.94 5.28 4.22Z" />
            </svg>
            </div>
            <div class="cm-customization-modal-bg" 
            x-show="openModal"
            x-transition:enter="cm-customization-transition"
            x-transition:enter-start="cm-customization-bg-from"
            x-transition:enter-end="cm-customization-bg-to"
            x-transition:leave="cm-customization-transition"
            x-transition:leave-start="cm-customization-bg-to"
            x-transition:leave-end="cm-customization-bg-from"
            @click="
            openModal = false
            "><span></span></div>
        </div>
    {{/if}}
  </div>
</div>

你也可以下载 customeow-data.html 文件。

下载文件


保存代码文件(快捷键:保存文件)


  • 找到 snippets/cart-item.html 文件。

  • 本教程基于 SHOPLINE 2.0 主题版本,如果你的主题版本与教程不匹配,你可以尝试搜索 item.properties as |property| 来找到代码位置。


按下 Command/Ctrl + F 来打开搜索窗口,搜索 item.properties as |property| 代码。(快捷键:搜索)

选择红色区域按 Command/Ctrl + / 注释代码。(快捷键:注释代码)

复制下列代码并粘贴到 snippets/cart-item.html 文件中。

{{!-- CustoMeow Custom Data --}}
{{snippet 'customeow-data'
    properties=item.properties
    title='Your personalization'
    enable_dark=false
    text_color='#000000'
    preview_font_size=12
    preview_image_width=60
    preview_image_radius=6
    enable_modal=true
    modal_background_color='#000000' 
    cart_item_id=''
    cart_item_image_classname=''
}}

粘贴到所注释的代码下面。

保存代码文件。

(快捷键:保存文件)




那么,我们测试一下!

推出编辑器,返回店铺设计页面,点击“设计”按钮。

保持 CustoMeow 应用开启状态,不要忘记保存!

现在预览你的网站。

找到你的定制化产品,定制后加入购物车,进入购物车查看是否有效。

Cool! 购物车现在已经能够更友好的显示定制数据啦!

如果你的购物车是抽屉样式,推荐设置 enable_modal=false 避免抽屉宽度小于查看图片宽度影响用户体验。



高级设置

你可以更改设置来满足你的个性化。

{{!-- CustoMeow Custom Data --}}
{{snippet 'customeow-data'
    properties=item.properties
    title='Your personalization'
    enable_dark=false
    text_color='#000000'
    preview_font_size=12
    preview_image_width=60
    preview_image_radius=6
    enable_modal=true
    modal_background_color='#000000' 
    cart_item_id=''
    cart_item_image_classname=''
}}

接下来,让我们逐一了解每一个设置。

  • properties(必填项):接收购物车的定制化参数。

    properties=item.properties


  • title(可选项):显示定制化模块的标题。

    title='Your personalization'

    推荐标题使用获取国际化文案的方式。

    {{assign "customeow_title" (t "cart.properie.title")}}
    {{snippet 'customeow-data'
        properties=item.properties
        title=customeow_title
        enable_modal=true
    }}


  • enable_dark(可选项):如果设置为 true,text color 将强制为白色,图片风格也会更改为暗黑模式, 默认是 false。

    enable_dark=false


  • text_color(可选项):文字颜色, 支持 hex 或 rgba 格式, 默认是 'oklch(37.3% .034 259.733)'。

    text_color='#000000'


  • preview_font_size(可选项):预览内容的文字尺寸,12~16px 之间, 默认是 12px。

    preview_font_size=12
  • preview_image_width(可选项):预览图片的缩略图宽度,默认是 60px。

    preview_image_width=60


  • preview_image_radius(可选项):预览图片的缩略图圆角,默认是 6px。

    preview_image_radius=6
  • enable_modal(必填项):开启点击预览图片缩略图,将弹出查看大图窗口,默认是 true。

    enable_modal=true


  • modal_background_color(可选项):设置查看大图窗口的背景色。

    modal_background_color='#000000'



将预览图片覆盖到产品图片上

cart_item_idcart_item_image_classname 参数必须同时设置才能有效。

  • cart_item_id(可选项):

    (1)购物车商品唯一 ID,通常使用商品排序作为 ID,您可以在 snippets/cart-item.html 文件中找到购物车商品 ID。

    (2)如果没有 ID,你也可以手动添加 item.index 来实现唯一 ID。

    CartItem-{{plus item.index 1}}


  • cart_item_image_classname(可选项):购物车商品项中图片父级的 classname。


    这里是最终代码。

    {{!-- add item id --}}
    {{assign "cart_item_index" (plus item.index 1)}}
    {{assign "cart_item_id" (append 'CartItem-' cart_item_index)}}
    
    {{!-- CustoMeow Custom Data --}}
    {{snippet 'customeow-data'
        properties=item.properties
        title='Your personalization'
        enable_dark=false
        text_color='#000000'
        preview_font_size=12
        preview_image_width=60
        preview_image_radius=6
        enable_modal=true
        modal_background_color='#000000' 
        cart_item_id=cart_item_id
        cart_item_image_classname='meow-cart-item-image'
    }}


  • 保存代码,回到购物车,我们去看看实际效果!


好了,就到这里了,Bye!