shopify 基础使用

471 阅读16分钟

Shopify 开发文档

Liquid 文档

Liquid 语法案例查询

Visual Studio Code 插件安装 - Shopify Liquid

Visual Studio Code 插件安装 - Liquid

翻译插件 - 腾讯翻译

网络资料:

Shopify 社区

一、环境搭建

安装参考:www.jianshu.com/p/3d0ec5b6a…

1、ruby 安装

https://www.ruby-lang.org/zh_cn/
​
# 下载地址
https://rubyinstaller.cn/

注:下载完成后,一直下一步就好,忽略掉过程,完成完成后 fish 会打开 cmd。

image.png

记得按回车,安装成功后 cmd 自动关闭。

image.png 2、shopify-cli 下载(gem 后面放弃掉)

# 这个下载的,必然是 2.X.X,可惜这样的是错的
gem install shopify-cli
# 使用上面这种方式安装后升级
shopify upgrade

使用 npm 下载(推荐)

注:也可看 shopify 官网的升级流程。

# 用它下载的是 3.X.x 版本
npm install -g @shopify/cli @shopify/theme

3、shopify 版本查看

shopify version

4、登出(拉取项目时的异常处理)

image.png

注:如果报上面的错,就代表你需要登出一下,才可以拉取项目。

shopify auth logout

二、Shopify 开发

官网地址:shopify.dev/docs

1、Shopify cli 的使用

1) 登录

必须要在网页中进行当前项目的的登陆哦,负责会出现问题的。

accounts.shopify.com/

2)拉取项目

shopify theme pull --store 你的项目名.myshopify.com
# 以下面的地址为例子,你登陆完项目后,需要定位到主页
# 然后去查看项目名
# https://admin.shopify.com/store/quickstart-c566b596 这个里面的项目名就是 quickstart-c566b596
# 下载的链接就为:
# shopify theme pull --store quickstart-c566b596.myshopify.com

image.png

这个时候会在出现一次登陆,记得登陆之前 ① 的账户。

3)运行

shopify theme dev

image.png

4) 推送项目 ⚠️

注:应该做一个 git 脚本化,每次 cr 到 main 分支后,自动执行 shopify theme push --store quickstart-c566b596.myshopify.com 去实现,拒绝人工搬运。

shopify theme push --store 你的项目名.myshopify.com
# 以下面的地址为例子,你登陆完项目后,需要定位到主页
# 然后去查看项目名
# https://admin.shopify.com/store/quickstart-c566b596 这个里面的项目名就是 quickstart-c566b596
# 下载的链接就为:
# shopify theme push --store quickstart-c566b596.myshopify.com

2、目录结构

image.png

3、修改项目

image.png

三、liquid 的语法基础

注:vsc 插件下载 Shopify Liquid。

image.png

1、schema

注:一个基础的 liquid 文件。

<style>

</style>
<div  class="">
  <div>
    {{ section.settings.collection_id }}
  </div>
</div>
<script>

</script>
{% schema %}
  {
    "name": "测试",
    "class": "",
    "settings": [
    ],
    "presets": [
      {
        "name": "测试"
      }
    ]
  }
  
{% endschema %}

1)settings 的使用

文档

<style>
  .box_list {
    display: flex;
    width: 100%;
    background-color: rosybrown;

  }
  .box_item {
  	/* 使用 schema 的变量 */
    width: {{ section.settings.width }}px;
    height: {{ section.settings.height }}px;
    border: 1px solid red;
    background-color: aqua;
  }
</style>
<div class="box_list">
  <div class="box_item">
    <button>
      {{ section.settings.height }}
    </button>
  </div>
  <div class="box_item">121</div>
  <div class="box_item">131</div>
</div>

{% schema %}
  {
    "name": "Box List",
    "settings": [
      {
        // 作为模板时的输入框类型
        "type": "text",
        // 作为获取的 key
        "id": "height",
        // 输入框的文案
        "label": "每个 item 的高度",
        // 输入框的默认值
        "default": "100px"
      },
      {
        "type": "text",
        "id": "width",
        "label": "每个 item 的宽度",
        "default": "100px"
      }
    ],
    "presets": [
      {
        "name": "Box List"
      }
    ]
  }
{% endschema %}

image.png

settings 是一个 json 数组,json 对象的参数如下:

属性参数描述
typenumber 数字 string 字符串 text 文本 textarea 文本域 range checkbox radio select 下拉框 image_picker 图片选择 url 链接(内部跳转的时候使用) collection 只能使用 产品 —> 收藏里面的内容 inline_richtext richtext color 颜色控制 image.png 配置栏的类型(也就是各个面板项目)
id自己定义的字符串1、自己定义的字符串; 2、取值的时候使用 section.settings.你刚才定义的 id 字符串; 3、理解成获取变量的 key
label自己起的面板文案定义面板的的文案
default定义的默认值
info面板的提示信息

注:

① collection 案例

<div>
  {{ section.settings.collection_id }}
  {{ collections[section.settings.collection_id].url }}
  {{ collections[section.settings.collection_id].title }}
  {{ collections[section.settings.collection_id].image }}
  {{
    collections[section.settings.collection_id].image
    | image_url: width: 500
    | image_tag: loading: 'lazy', widths: '165, 360, 535, 750, 1070, 1500'
  }}
</div>
{% schema %}
{
  "name": "test",
  "settings": [
    {
      "type": "collection",
      "id": "collection_id",
      "label": "collection 使用"
    }  
  ],
  "presets": [
    {
      "name": "测试"
    }
  ]
}
{% endschema %}

只能展示 image.png 这个下面的内容。

2)blocks

文档

注:blocks 是一个 json 数组

属性参数描述
type一个自己定义的 string
name作为编辑器中的名称使用
limit当面这个控制面板,在 添加块 中,能使用的次数(默认是不限制的)
settings一个 json 数组,参数同 schema 中 settings 的使用

初步案例:

注:

① blocks 为一个对象的数组。

<div>
  {% comment %} 读取 blocks 中的参数 {% endcomment %}
  {%- for block in section.blocks -%}
    <div class="{{ block.settings.font_size }}">{{ block.settings.richtext }}</div>
  {%- endfor %}
</div>

{% schema %}
{
  "name": "blocks 测试",
  "tag": "section",
  "class": "section",
  "blocks": [
    {
      "type": "test",
      "name": "添加 inline_richtext",
      "settings": [
        {
          "type": "inline_richtext",
          "id": "richtext",
          "default": "文案",
          "label": "标题文案"
        },
        {
          "type": "select",
          "id": "font_size",
          "options": [
            {
              "value": "h0",
              // FOXME 反人类,html 店铺是从 h1 开始的
              "label": "H0"
            },
            {
              "value": "h1",
              "label": "H1"
            },
            {
              "value": "h2",
              "label": "H2"
            },
            {
              "value": "h3",
              "label": "H3"
            },
            {
              "value": "h4",
              "label": "H4"
            },
            {
              "value": "h5",
              "label": "H5"
            }
          ],
          "default": "h0",
          "label": "标题大小"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "blocks 测试"
    }
  ]
}
{% endschema %}

② blocks 为多个个对象的数组

<div>
  {% comment %} 读取 blocks 中的参数 {% endcomment %}
  {%- for block in section.blocks -%}
    {%- case block.type -%}
      {%- when 'test1' -%}
        <div class="{{ block.settings.font_size }}">{{ block.settings.richtext }}</div>
      {%- when 'test2' -%}
        {% comment %} 一定要有图片不为 blank 的判断 {% endcomment %}
        {%- if block.settings.image != blank -%}
          {%- assign widths = '165, 360, 535, 750, 1070, 1500' -%}
          {{ block.settings.image | image_url: width: 500 | image_tag: loading: 'lazy', widths: widths }}
        {%- endif -%}
    {%- endcase -%}
  {%- endfor -%}
</div>

{% schema %}
{
  "name": "blocks 测试",
  "tag": "section",
  "class": "section",
  "blocks": [
    /*
      特殊 "type": "@app" 是添加一个应用
      使用的时候是:
      	{% for block in section.blocks %}
          {% case block.type %}
            {% when '@app' %}
              {% render block %}
          {% endcase %}
        {% endfor %}
     */
    // {
    //   "name": "",
    //   "type": "@app"
    // },
    {
      "type": "test1",
      "name": "添加 inline_richtext",
      "limit": 2,
      "settings": [
        {
          "type": "inline_richtext",
          "id": "richtext",
          "default": "文案",
          "label": "标题文案"
        },
        {
          "type": "select",
          "id": "font_size",
          "options": [
            {
              "value": "h0",
              // FOXME 反人类,html 店铺是从 h1 开始的
              "label": "H0"
            },
            {
              "value": "h1",
              "label": "H1"
            },
            {
              "value": "h2",
              "label": "H2"
            },
            {
              "value": "h3",
              "label": "H3"
            },
            {
              "value": "h4",
              "label": "H4"
            },
            {
              "value": "h5",
              "label": "H5"
            }
          ],
          "default": "h0",
          "label": "标题大小"
        }
      ]
    },
    {
      "type": "test2",
      "name": "图片",
      "settings": [
        {
          "type": "image_picker",
          "id": "image",
          // 这个一般就不要默认地址了
          // "default": "",
          "info": "图片",
          "label": "选择图片"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "blocks 测试"
    }
  ]
}
{% endschema %}

3)在Dom 元素中的使用 schema

文档 - section

2、css

文档 - 资源加载

注:css 的引入,只能在 sections、layout 中使用。

1)局部引入(直接引入 css 文件)

{{- 'test.css' | asset_url | stylesheet_tag: preload: true -}}

image.png

2)分区中设置 css (也就是局部变量的设置)

/*
#shopify-section- 是所有分区的统一命名,是不会改变的
{{ section.id }} 是系统自己生成
如果你要局部修改,就只能这样修改,负责你就是全局修改的样式
*/
#shopify-section-{{ section.id }} .section {
    padding-block-start: 30px;
    padding-block-end: 30px;
}

3)icon-font 的引入

{% comment %} 直接在 layout ——> theme.liquid 引入就好 {% endcomment %}
{{- 'iconfont.css' | asset_url | stylesheet_tag: preload: true -}}  

3、图片展示

img 响应式图片

图片展示

文档

image.png

image.png

schema 选择图片

  {{
    section.settings.image
    | image_url: width: 500
    | image_tag: loading: 'lazy', 
    widths: '165, 360, 535, 750, 1070, 1500'
  }}

4、render ✅

文档

注:render 渲染的 liquid 文件,只能在 snippets 文件里,并且 render 渲染的文件中不支持 schema

image.png

1)传参

image.png

image.png

5、style ⁉️

{%- style -%}

{%- endstyle -%}

基本上等于

所以使用的时候,你就使用 style 标签就好,没必要使用 {%- style -%}。

6、过滤器

文档

所有文件的引入,都要使用 {{ '文件名.css/js' | asset_url }}

7、引入 vue

注:不推荐,会感觉四不像,如果真的使用 推荐方法一,文件比较集中(而且 liquid 中用 vue 就很变态)。

引入文档

1)引入 vue 的方法一

① 在 layout ——> theme.liquid 中

  {% comment %} 记得放在 head 里 {% endcomment %}
   <script src="https://unpkg.com/vue@next"></script>

② 使用 sections ——> test.liquid

注:raw 文档

<div id="vue-app">
  {% comment %} 这块也可以加一个 div 去渲染 data 中的数据,写法如下,不推荐
    <div v-html="name"></div>
  {% endcomment %}
  {% assign variable = '测试 assign 变量在 Vue 中的使用' %}

  {% raw %}
    {{ name }}
  {% endraw %}

  <button @Click="handleClick">修改 assign 的变量 </button>
</div>
<script>
  const el = {
    data() {
      return {
        name: '{{ variable }}'
      }
    },
    methods: {
      handleClick() {
        this.name = '修改'
        console.log(this.name);
      }
    }
}
const app = Vue.createApp(el)
app.mount("#vue-app");

</script>

{% schema %}
{
  "name": "测试",
  "settings": [
    
  ],
  "presets": [
    {
      "name": "测试"
    }
  ]
}
{% endschema %}

2)引入 vue 的方法二

① 在 layout ——> theme.liquid 中

  {% comment %} 记得放在 head 里 {% endcomment %}
  <script src="{{ 'vue 的js 文件路径' |  asset_url }}" defer></script>

② 在 layout ——> test.js.liquid 中

new Vue({
  el: '#app',
  data: {
    name: '2222'
  }
})

③ 使用 sections ——> test.liquid

<div id="app">
  <div v-html="name"></div>
</div>

<script src="{{ 'test.js' | asset_url }}" defer></script>

{% schema %}
{
  "name": "测试",
  "settings": [
    
  ],
  "presets": [
    {
      "name": "测试"
    }
  ]
}
{% endschema %}

注:react 的引用同理

四、常用 liquid 语法及方法

1、上下间距调节器

<style>
  .top-bottom_-margin {
      height: 0;
      background-color: black !important;
      margin-top: {{ section.settings.top_bottom_space }}px;
  }
</style>
<div class="top-bottom_-margin" style="display: block;"></div>

{% schema %}
{
  "name": "上下间距调节器",
  "settings":[
    {
      "type": "range",
      "id": "top_bottom_space",
      "label": "左右拖动调节间距",
      "max": 100,
      "min": 0,
      "step": 1,
      "default": 30
    }
  ],
  "presets":[
    {
      "name": "上下间距调节器",
      "category": "Advanced"
    }
  ]
}
{% endschema %}

2、链接

<style>
  .section-more-box {
    display: flex;
    justify-content: center;
    margin-top: 20px;
  }
  .section-more-box .more-link {
    display: block;
    border: 1px solid #000;
    background: #fff;
    padding: 10px 4px 8px;
    text-align: center;
    width: 200px;
    color: #000;
    font-weight: 700;
    cursor: pointer;
    box-sizing: border-box;
  }
  .section-more-box .more-link:hover {
    color: #fff;
    background: #000;
  }
  @media only screen and (max-width:767px) {
    .section-more-box {
      padding: 0 16px;
    }
    .section-more-box .more-link {
      width: 100%;
    }
  }
</style>
<div class="section-more-box">
  <div class="more-link"> {%- if section.settings.link and section.settings.text -%}{{ section.settings.text | link_to: section.settings.link }}{%- endif -%}</div>
</div>
{% schema %}
{
  "name": "链接",
  "settings": [
    {
      "type": "url",
      "id": "link",
      "label": "链接"
    },
    {
      "type": "text",
      "id": "text",
      "label": "链接文案",
      "default": "链接文案"
    }
  ],
  "presets": [
    {
      "name": "链接"
    }
  ]
}
{% endschema %}

3、发送邮件

文档

form 提交信息处理

注:会让你验证,但是这个是成功的,倒霉的让我改了好久(不要在本地验证,本地他不让发邮件的)。

<style>
  .box-email {
    position: relative;
    display: inline-block;
  }
  .submit {
    position: absolute;
    top: 0;
    right: 0;
  }

  /* 修改 input 聚焦后的颜色 */
  input:active, input:focus {
    border-color: var(--colorBorder);
  }

  /* 修改 input 填充后颜色 */
  input:-webkit-autofill {
    transition: background-color 5000s ease-in-out 0s;
  }

  /* 修改 input 填入文字的颜色 */
  input {
	-webkit-text-fill-color: var(--colorBorder);
  }
</style>
<div  class="box-email">
  {% form 'customer' %}
    {{ form.errors | default_errors }}
    {%- if form.posted_successfully? -%}
      成功
    {%- else -%}
      {%- if form.errors -%}
        失败
      {%- endif -%}
  
      <input type="hidden" name="contact[tags]">
  
      <div class="email">
        <input type="email" name="contact[email]">
      </div>
      <div class="submit">
        <input type="submit" value="Send">
      </div>
    {%- endif -%}
  {% endform %}
</div>

4、返回顶部按钮

文档

<style>
  html {
    scroll-behavior: smooth;
  }

  .scroll-to-top-btn {
    position: fixed;
    right: 0.51rem;
    bottom: 2.0267rem;
    z-index: 2;
    height: 38px;
    width: 38px;
    border-radius: 100%;
    box-shadow: 0 0 2px rgba(0,0,0,.12);
    transform: translateY(0);
    background: #fff;
    /* 居中 */
    display: flex;
    justify-content: center;
    align-items: center;
    /* 动画 */
    opacity: 0;
    transform: translateY(100px);
    transition: all .5s ease;
  }

  .showBtn {
    opacity: 1;
    transform: translateY(0);
  }

  .scroll-to-top-btn .icon-top-arrow {
    height: 28px;
    width: 28px;
    color: #000;
  }
  .scroll-to-top-btn[disabled] {
     color: white !important;
     background: #730000 !important;
  }
</style>
<div>
  {% comment %} 返回 {% endcomment %}
  <button class="scroll-to-top-btn">
    {% render "top-arrow.svg" %}
  </button>
</div>
<script>
  let scrollToTopBtn = document.querySelector(".scroll-to-top-btn");
  let scrollTop = 0;

  window.onscroll = function() {
    scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
	
	if(scrollTop == 0) {
		scrollToTopBtn.disabled = false;
	}
    if (scrollTop > 100) {
      scrollToTopBtn.classList.add("showBtn")
    } else {
      scrollToTopBtn.classList.remove("showBtn")
    }
  };

  scrollToTopBtn.onclick = function() {
    window.scrollTo(0, 0);
    scrollToTopBtn.disabled = true;
  };
</script>
{% schema %}
  {
  "name": "返回顶部",
  "presets": [
    {
      "name": "返回顶部"
    }
  ]
}
{% endschema %}
{% comment %} 返回 svg {% endcomment %}
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg class="icon-top-arrow" width="100%" height="00%" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path fill="#000000" d="M486.4 263.509333l-175.7184 175.7184a25.6 25.6 0 1 1-36.181333-36.215466l219.409066-219.409067a25.6 25.6 0 0 1 36.181334 0l219.4432 219.409067a25.6 25.6 0 0 1-36.215467 36.181333L537.6 263.509333v574.293334a25.6 25.6 0 1 1-51.2 0V263.509333z"  /></svg>

5、表单

文档

form 提交信息处理

注:

① create_customer 创建表单时,他不支持返回成功的提示(深坑 !!!)

image.png

错误信息处理 default_errors

获取请求 request (也就是当前 url 的信息)之后的状态

注:表单不是很好用。

6、调用 shopify 中定义的方法

注:类似调用购物车的弹出框。

/**
 * 直接获取到 DOM 元素,然后去 click,不要尝试使用 theme.js 里面的方法哦,里面全是原型链,难以阅读,需要    大量的时间
 */
document.querySelector(".mobile-nav-trigger").click();

7、顶部 bar

注:如果你只链接收藏的话,也可以考虑 collection 属性,但是他很单一,很多是无法进行设置的,不推荐。

<style>
  @media only screen and (min-width: 768px) {
    #shopify-section-{{ section.id }} .tabs {
      display: none;
    }
  }
  .tabs-sticky {
    /* position: fixed;
    top: {{ section.settings.top_space }}vh; */
  }
  .tabs {
    width: 100vh;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 36%;
    max-width: 100vw;
    overflow-x: scroll;
    gap: 8px;
    background: #fff;
    padding: 0 10px;
  }

  .tabs::-webkit-scrollbar {
    display: none;
  }

  .tabs a {
    display: inline-block;
    padding: 8px;
    box-sizing: border-box;
    margin: 0;
    background-color: #f5f5f5;
    border-radius: 4px;
    vertical-align: bottom;

    display: flex;
    flex-direction: row;
    align-items: center;
  }
  .tabs a:first-child {
    margin-left: 0px;
  }
  #shopify-section-{{ section.id }} p {
    white-space: wrap;
    overflow: hidden;
    text-overflow: ellipsis;
    display:-webkit-box;
    -webkit-box-orient:vertical;
    -webkit-line-clamp:2;
    font-size: 12px;
    color: #666;
    margin-top: 4px;
    text-align: center;
  }
  #shopify-section-{{ section.id }} .image {
    width: 30%;
  }
  [data-status="selected"] {
    border:1px solid #4e4d4d;
  }
  /* #4e4d4d */
</style>
<div>
  <div class="tabs">
    {%- for block in section.blocks -%}
      <a href="{{ block.settings.link }}" class="tabs-link">
        {%- if block.settings.image != blank -%}
          <div class="image">
            {{
              block.settings.image
              | image_url: width: 500
              | image_tag: loading: 'lazy', widths: '165, 360, 535, 750, 1070, 1500'
            }}
          </div>
        {%- endif -%}
        {%- if block.settings.text != blank -%}
          <p>
            {{ block.settings.text }}
          </p>
        {%- endif -%}
      </a>
    {%- endfor %}
  </div>
</div>

<script>
  let tabs = document.querySelector('.tabs')

  window.addEventListener('scroll',function(e){
    if(window.pageYOffset > tabs.offsetTop){
      tabs.style.position = 'fixed'
      tabs.style.top = '60px'
      tabs.style.zIndex = '9'
      tabs.style.paddingBottom = '10px'
    }else {
      tabs.style.position = 'unset'
    }
  })

  const bottomBar = document.getElementsByClassName('tabs-link');

  const url = window.location.href;

  for (let index = 0; index < bottomBar.length; index++) {
    if(element.href == url) {
      tabs.scrollTo(element.offsetLeft, 0);
      element.setAttribute('data-status', 'selected')
    }else {
      element.removeAttribute('data-status')
    }
  }
</script>

{% schema %}
{
  "name": "顶部 tabs",
  "class": "tabs-sticky",
  "settings":[
    {
      "type": "range",
      "id": "top_space",
      "label": "距离顶部距离",
      "max": 100,
      "min": 0,
      "step": 1,
      "default": 6
    }
  ],
  "blocks": [
    {
      "type": "tabs",
      "limit": 8,
      "name": "tabs",
      "settings": [
        {
          "type": "image_picker",
          "id": "image",
          "label": "tab 图片"
        },
        {
          "type": "url",
          "id": "link",
          "label": "tab 链接"
        },
        {
          "type": "text",
          "id": "text",
          "label": "tab 文案"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "顶部 tabs"
    }
  ]
}
{% endschema %}

8、获取产品信息

文档

9、获取登陆信息

文档 - 示例

文档 - shop 对象的属性

  {% if shop.customer_accounts_enabled %}
    {% if customer %} 
      {% comment %} 已经登陆 {% endcomment %}
      <a href="{{ routes.account_url }}">账户 url</a>
    {% else %}
      暂无登陆
      <a href="{{ routes.account_login_url }}">登陆 url</a>
  
      {% if shop.customer_accounts_optional %}
        <a href="{{ routes.account_register_url }}">创建账户 url</a>
      {% endif %}
    {% endif %}
  {% endif %}

10、元字段

文档:

11、界面跳转

文档

image.png

12、获取当前 liquid 文件中的部分数据

文档

13、assign (变量)

文档

<div id="vue-app">
  {% assign variable = '测试' %}
  {{ variable }}
  
</div>
<script>
console.log("{{ variable }}");

</script>
{% schema %}
{
  "name": "测试",
  "settings": [
    
  ],
  "presets": [
    {
      "name": "测试"
    }
  ]
}
{% endschema %}

image.png

14、capture 定义 html

文档

<div id="vue-app">
  {% capture testHtml %}
    <div>渲染的时 html 标签</div>
  {%- endcapture -%}

  {{ testHtml }}
</div>
{% schema %}
{
  "name": "测试",
  "settings": [
    
  ],
  "presets": [
    {
      "name": "测试"
    }
  ]
}
{% endschema %}

15、for (遍历)

文档

16、过滤器 🗑️

文档

中文文档

注:很差劲的东西,大部分前端框架已经 🚮 的玩意,liquid 反而在大量使用,体验差!!!

17、订单属性

文档

18、图片的引入

background-image: url({{ '图片名.格式' | asset_url }});
<img src="{{ '图片名.格式' | asset_url }}" alt="播放" width="30" height="30" />

19、显示指定行数,超出显示省略号

  元素选择器 {
    display: -webkit-box;
    overflow: hidden;
    white-space: normal;
    text-overflow: ellipsis;
    word-wrap: break-word;
    -webkit-line-clamp: 2; /* 要显示的行数,超出显示省略号 */
    -webkit-box-orient: vertical;
  }

20、三角

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    .small-triangle {
      width: 0;
      height: 0;
      border-left: 6px solid #ff8e59;
      border-top: 6px solid transparent;
      border-bottom: 6px solid transparent;
    }
  </style>
</head>
<body>
  <div class="small-triangle"></div>
</body>
</html>

21、走马灯 🪲

<style>
  .revolving-door, .second-line_revolving-door {
    position: relative;
    width: calc(100vw - 80px);
    height: 200px;
    overflow: hidden;
  }
  .revolving-door-ul, .second-line_revolving-door-ul {
    list-style: none;
    position: absolute;
    left:0;
    top:0;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 200px;
    gap: 15px;
  }
  .revolving-door-li, .second-line_revolving-door-li img{
    width:100%;
    height: 100%;
    border-radius: 20px;
  }
  .revolving-door-button {
    display: flex;
    justify-content: flex-end;
  }
  .second-line_revolving-door {
    margin-top: 2.5rem;
  }
  .revolving-door::-webkit-scrollbar{
    width:10px;
    height:10px;
  }
  .revolving-door::-webkit-scrollbar-track{
    background: rgb(239, 239, 239);
    border-radius:2px;
  }
  .revolving-door::-webkit-scrollbar-thumb{
    background: #bfbfbf;
    border-radius:10px;
  }
  .revolving-door::-webkit-scrollbar-thumb:hover{
    background: #333;
  }
  .revolving-door::-webkit-scrollbar-corner{
    
  }
  .second-line_revolving-door::-webkit-scrollbar{
    width:10px;
    height:10px;
  }
  .second-line_revolving-door::-webkit-scrollbar-track{
    background: rgb(239, 239, 239);
    border-radius:2px;
  }
  .second-line_revolving-door::-webkit-scrollbar-thumb{
    background: #bfbfbf;
    border-radius:10px;
  }
  .second-line_revolving-door::-webkit-scrollbar-thumb:hover{
    background: #333;
  }
</style>
<div style="padding: 2.5rem 40px;">
  <div class="revolving-door-button">
    <button class="pause">
      <img src="../assets/zanting.png" alt="暂停" width="30" height="30" />
    </button>
    <button class="play">
      <img src="../assets/bofang.png" alt="播放" width="30" height="30" />
    </button>
  </div>
  <div class="revolving-door">
    <ul class="revolving-door-ul">
      {%- for block in section.blocks -%}
        {% if block.type == 'first-row' %} 
          <li class="revolving-door-li">
            
              {% if block.settings.image != block %}
                {{
                  block.settings.image
                  | image_url: width: 500
                  | image_tag: loading: 'lazy', 
                  widths: '200',
                  class: 'revolving-door-image'
                }}
              {% endif %}
              {% if block.settings.video != block %}
                <video
                id="Mp4Video-{{ block.settings.video }}"
                class="video-div"
                data-type="mp4"
                src="{{ block.settings.video }}"
                loop muted playsinline autoplay></video>
              {% endif %}
        </li>
        {% endif %}
      {%- endfor %}
   </ul>
  </div>

  <div class="second-line_revolving-door">
    <ul class="second-line_revolving-door-ul">
      {%- for block in section.blocks -%}
        {% if block.type == 'second-line' %} 
          <li class="second-line_revolving-door-li ">
              {% if block.settings.second-line_image != block %}
                {{
                  block.settings.second-line_image
                  | image_url: width: 500
                  | image_tag: loading: 'lazy', 
                  widths: '200',
                  class: 'second-line_revolving-door-image'
                }}
              {% endif %}
        </li>
      {% endif %}
      {%- endfor %}
   </ul>
  </div>
</div>
<script>

// 暂停
const pause = document.querySelector('.pause');
const play = document.querySelector('.play');
// 第一行
let left = 0;
let timer = null;
let secondLineLeft = -10;
let secondLineTimer = null;
setTimeout(() => {
  const revolvingDoorUl = document.querySelector('.revolving-door-ul');
  const revolvingDoorLi = document.getElementsByClassName('revolving-door-li');
  const revolvingImage = document.getElementsByClassName('revolving-door-image')[0];
  const revolvingDoor = document.querySelector('.revolving-door');

  // 获取图片的宽高,赋值给 revolving-door-ul
  revolvingDoor.style.height = revolvingImage.offsetHeight + 'px';
  revolvingDoor.style.gridAutoColumns = revolvingImage.offsetWidth + 'px';

  if(revolvingDoorLi.length < 6) {
    clearInterval(timer)
  }


  revolvingDoorUl.innerHTML += revolvingDoorUl.innerHTML;
  function firstRow() {
    timer = setInterval(function () {
      left -= 1;

      revolvingDoorUl.style.width = revolvingDoorLi.length * revolvingDoorLi[0].offsetWidth+'px';

      if(left < -revolvingDoorUl.offsetWidth/2) {
        left = 0;
      }

      revolvingDoorUl.style.left=left+'px'
    }, 10);
  }
  firstRow();

  // 第二行
  const secondLineRevolvingDoorUl = document.querySelector('.second-line_revolving-door-ul');
  const secondLineRevolvingDoorLi = document.getElementsByClassName('second-line_revolving-door-li');
  const secondLineRevolvingImage = document.getElementsByClassName('second-line_revolving-door-image')[0];
  const secondLineRevolvingDoor = document.querySelector('.second-line_revolving-door');

  // 获取图片的宽高,赋值给 revolving-door-ul
  secondLineRevolvingDoor.style.height = secondLineRevolvingImage.offsetHeight + 'px';
  secondLineRevolvingDoor.style.gridAutoColumns = secondLineRevolvingImage.offsetWidth + 'px';

  secondLineRevolvingDoorUl.innerHTML += secondLineRevolvingDoorUl.innerHTML;
  function secondLine() {
    
    secondLineTimer = setInterval(function () {
      secondLineLeft -= 1;

      secondLineRevolvingDoorUl.style.width = secondLineRevolvingDoorLi.length * secondLineRevolvingDoorLi[0].offsetWidth+'px';

      if(secondLineLeft < -secondLineRevolvingDoorUl.offsetWidth/2) {
        secondLineLeft = 0;
      }

      secondLineRevolvingDoorUl.style.left=secondLineLeft+'px'
    }, 10);
  }
  if(secondLineRevolvingDoorUl.length <= 6) {
    console.log("我走了");
    clearInterval(secondLineTimer)
  }
  secondLine();

  pause.onclick = function() {
    clearInterval(timer)
    clearInterval(secondLineTimer)

    pause.style.display = 'none';
    play.style.display = 'block';

    revolvingDoor.style.overflowX = 'scroll';
    secondLineRevolvingDoor.style.overflowX = 'scroll';
  }

  play.onclick = function() {
    firstRow();
    secondLine();
    pause.style.display = 'block';
    play.style.display = 'none';
  }

  // 额外的事件处理
  revolvingDoor.addEventListener("drag", (params) => {
    clearInterval(timer);
    revolvingDoor.style.overflowX = 'scroll';
  })
  secondLineRevolvingDoor.addEventListener("drag", (params) => {
    clearInterval(secondLineTimer);
    secondLineRevolvingDoor.style.overflowX = 'scroll';
  })

  function scroll() {
    
  }
}, 500)

play.style.display = 'none';

</script>
{% schema %}
{
  "name": "跑马灯",
  "blocks": [
    {
      "type": "first-row",
      "name": "第一行",
      "settings": [
        {
          "type": "image_picker",
          "id": "image",
          "label": "图片(每行至少六个图片 或 视频)"
        },
        {
          "id": "video",
          "type": "video",
          "label": "视频"
        }
      ]
    },
    {
      "type": "second-line",
      "name": "第二行",
      "settings": [
        {
          "type": "image_picker",
          "id": "second-line_image",
          "label": "图片(每行至少六个图片 或 视频)"
        },
        {
          "id": "video",
          "type": "video",
          "label": "视频"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "走马灯"
    }
  ]
}
{% endschema %}

22、swiper 的引入

文档

文档 - 推荐

<link rel="stylesheet" href="https://unpkg.com/swiper@8/swiper-bundle.min.css">
<style>
  .swiper {
    overflow: hidden;
    width: 100%;
    height: {{ section.settings.desktop_image_height }}px;
  }
  @media screen and (max-width: 699px) {
    .banner-gailery {
      width: 100%;
      height: {{ section.settings.mobile_image_height }}px;
    }
  }
  .swiper-container {
    height: 100%;
    position: relative;
  }
  .swiper-button-next:after, .swiper-button-prev:after {
    font-size: 16px;
    color: #000;
    font-weight: 900;
  }
  .swiper-slide {
    width: {{ section.settings.image_width }}%;
  }
  .swiper-pagination-bullet {
    background-color: {{ section.settings.paginator_color }};
    height: 10px;
    width: 10px;
    opacity: 1;
  }
  .swiper-pagination-bullet-active {
    border: 2px solid {{ section.settings.paginator_color }};
    background-color: transparent;
  }
</style>
<div class="banner-gailery">
  <div class="swiper-container">
    <div class="swiper-wrapper">
      <!--
        <div class="swiper-slide">Slide 1</div>
        <div class="swiper-slide">Slide 2</div>
        <div class="swiper-slide">Slide 3</div>
      -->
      <!-- 添加更多的轮播项 -->
      {%- for block in section.blocks -%}
        {%- if block.settings.image -%}
          {% capture img %}
            <div class="swiper-slide">
              {{
                block.settings.image
                | image_url: width: 500
                | image_tag: loading: 'lazy', widths: '165, 360, 535, 750, 1070, 1500'
              }}
            </div>
          {%- endcapture -%}
          {{ img }}
        {%- endif -%}
        
        {%- if block.settings.image and block.settings.link_url -%}
          <a  href="{{ block.settings.link_url }}" class="swiper-slide">
            {{ img }}
          </a>
        {%- endif -%}
      {%- endfor %}
    </div>

    <!-- 如果需要分页器 -->
    {% if section.settings.paginator %}
      <div class="swiper-pagination"></div>
    {% endif %}
    <!-- 如果需要导航按钮 -->
    {% if section.settings.navigation %}
      <div class="swiper-button-next"></div>
      <div class="swiper-button-prev"></div>
    {% endif %}
  </div>
</div>
<script src="https://unpkg.com/swiper@8/swiper-bundle.min.js" defer></script>
<script>
  document.addEventListener('DOMContentLoaded', function () {
    new Swiper('.swiper-container', {
      slidesPerView: 'auto',
      spaceBetween: 10,
      loop: true,
      pagination: {
        el: '.swiper-pagination',
        clickable: true,
      },
      autoplay: '{{ section.settings.play }}'
        ? {
            delay: 3000, // 自动播放的延迟时间,单位为毫秒
            stopOnLastSlide: false,
            disableOnInteraction: false, // 用户操作后是否停止自动播放
          }
        : null,
      navigation: {
        nextEl: '.swiper-button-next',
        prevEl: '.swiper-button-prev',
      },
    });
  });
</script>

{% schema %}
{
  "name": "轮播",
  "settings": [
    {
      "type": "checkbox",
      "id": "navigation",
      "label": "左右箭头"
    },
    {
      "type": "checkbox",
      "id": "play",
      "label": "是否自动播放"
    },
    {
      "type": "checkbox",
      "id": "paginator",
      "label": "分页器"
    },
    {
      "type": "color",
      "id": "paginator_color",
      "label": "分页器颜色",
      "default": "#fff"
    },
    {
      "type": "range",
      "id": "desktop_image_height",
      "min": 100,
      "max": 1000,
      "step": 10,
      "unit": "px",
      "label": "桌面图像行高",
      "default": 460
    },
    {
      "type": "range",
      "id": "mobile_image_height",
      "min": 100,
      "max": 700,
      "step": 10,
      "unit": "px",
      "label": "移动图像行高",
      "default": 400
    },
    {
      "type": "range",
      "id": "image_width",
      "min": 1,
      "max": 100,
      "step": 1,
      "unit": "%",
      "label": "图像的宽",
      "default": 80
    }
  ],
  "blocks": [
    {
      "type": "images",
      "name": "图片",
      "settings": [
        {
          "type": "image_picker",
          "label": "图片",
          "id": "image"
        },
        {
          "type": "url",
          "label": "点击图片跳转的 url",
          "id": "link_url"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "轮播",
      "blocks": [
        {
          "type": "image"
        },
        {
          "type": "image"
        },
        {
          "type": "image"
        },
        {
          "type": "image"
        }
      ]
    }
  ]
}
{% endschema %}

五、创建一个 app

文档

视频 1

视频 2

视频 3

视频 4 - 推荐

注:建议使用 pnpm

创建后报错处理

image.png

image.png

注:理论上 Shopify 支持 Vue 作为应用框架的开发。

1、创建应用前配置环境

1)登陆 Gadget

gadget.dev/

image.png

image.png

image.png

image.png

image.png

2)登陆 shopify partners

partners.shopify.com/

image.png

image.png

等商店创建成功。

image.png

image.png

image.png

3)根据 shopify partners 生成的信息去填写 Gadget

image.png

image.png

image-20231102161124205转存失败,建议直接上传图片文件

image.png

4)根据 Gadget 去填写 shopify partners

image.png

image.png

image.png

image.png

5)shopify 中安装程序

www.shopify.com/

image.png

image.png

image.png

image.png

先写一个 hello word 查看效果。

image.png

6)Gadget 进行初步数据的创建

image.png

image.png

这步待定。

7)在本地监听 Gadget 的变化 🚮

npx @gadgetinc/ggt@latest sync --app 自己定义的名字 ~/gadget/自己定义的名字

image.png

8)本地开发

注:如果没安装,提示你安装 @shopify/create-app 。

npm init @shopify/app@latest

image.png

未完待续

六、注意事项

1、window.onload

window.onload 在一个 html 文件中,只执行一次,所以 你在界面中,有时候它是不执行的,所以要尽量避免使用它(因为你不确定在别处是否使用),可以考虑使用 document.addEventListener("load", function() {}) 来替代,可参考:blog.csdn.net/m0_61420899…

document.addEventListener("load",  function() {
    
})

2、shopify 缓存很严重

chrome://settings/clearBrowserData/

image.png

及时清理。

七、shopify配置 ⚠️

1、结账页税费和货到付款的运费都只能用插件处理

image.png

2、货到付款配置需要结合运费设置

image.png

八、GA4 (用户习惯收集)

注:必须要填写 你要收集的网站(不友好)。

analytics.google.com/

tagmanager.google.com/