如何创建 Shopify 应用 - 分步指南

2025-06-08

如何创建 Shopify 应用 - 分步指南

大家好,我打算创建一个 Shopify 应用,用于订阅,特别是根据德国新法规
取消订阅 。因此,我制定了一个分步计划来创建 Shopify 应用。
我只是从其他来源复制了大部分内容,希望对您有所帮助 :)

词汇:

应用程序与 Shopify 平台交互的关键区域有三个:Shopify 管理员在线商店结帐

Shopify 管理员

登录 Shopify 后,商家可以使用Shopify 管理员设置商店、配置设置并管理业务。

Shopify 后台包含商家 Shopify 业务的核心内容,包括订单、产品和客户。商家还可以在 Shopify 后台安装应用程序。

网上商店

网上商店商家业务的在线平台。商家可以使用网上商店创建网页、发布博客并销售产品。

作为应用程序开发人员,您可以在商家想要销售的所有地方以及客户想要购买的所有地方建立集成。

查看

商家使用安全的Shopify 结账平台,无论在哪里进行在线销售,都可以接受订单并收取款项。顾客将商品添加到购物车后,可以使用 Shopify 结账平台输入配送信息和付款详情,然后再下单。

应用可以与 Shopify 结账功能集成,为商家和顾客提供更多功能。例如,商家可以使用支持购买后优惠的应用,在结账时向顾客展示交叉销售或追加销售优惠。

创建您的第一个 Shopify 应用

步骤1:注册Shopify合作伙伴账户

首先在此处创建合作伙伴帐户:
https://partners.shopify.com/signup

第 2 步:创建测试商店

开发商店是一个免费的 Shopify 帐户,但有一些限制。作为Shopify 合作伙伴,您可以创建无限数量的开发商店。

您可以使用开发商店测试您创建的任何主题或应用程序,或为客户设置 Shopify 商店。设置开发商店并将其转移给客户可为您带来定期佣金。2.1
. 登录您的合作伙伴控制面板。2.2
. 单击商店。2.3
. 单击添加商店。2.4
. 在商店类型部分中,选择开发商店。2.5
. 在登录信息部分,输入您的商店名称和可用于登录的密码。默认情况下,与您的合作伙伴控制面板关联的电子邮件用作用户名,但您可以根据需要更改它。2.6
. 可选:通过选中创建使用开发人员预览版的不可转让商店来启用开发人员预览版。从下拉列表中选择开发人员预览版。2.7
. 在商店地址部分,输入您的地址。2.8
. 可选:在商店用途部分,选择您创建此开发商店的原因
。2.9单击“保存”

步骤3:安装最新的Node.js

https://nodejs.org/en/download/

步骤 4:安装 Shopify CLI

如果您想在 Windows 10 上原生使用 Shopify CLI,请首先确保已使用RubyInstaller for Windows(2.7 或更高版本)安装了 Ruby+Devkit。
或者,您也可以通过 Windows Subsystem for Linux 使用 Shopify CLI,在这种情况下,您需要安装以下内容:

安装完先决条件后,您可以使用RubyGems.org软件包管理器将 Shopify CLI 安装为 Ruby gem 。在新的终端窗口中,导航到您的主目录并运行以下命令:

gem install shopify-cli
Enter fullscreen mode Exit fullscreen mode

要验证 Shopify CLI 是否正确安装,请运行以下命令:

shopify version
Enter fullscreen mode Exit fullscreen mode

步骤 5:使用 CLI 登录 Shopify

在使用 CLI 创建项目之前,我们需要登录 Shopify。因此,我们在终端中输入:

shopify login
Enter fullscreen mode Exit fullscreen mode

浏览器标签页将会打开,请登录您的合作伙伴账户。成功登录后,您应该会在终端中看到一条消息

登录合作伙伴组织 xxxx 的商店 xxxxxx.myshopify.com

步骤 6:创建新项目

安装 Shopify CLI 后,您就可以创建新项目了。

导航到要创建项目的目录并运行shopify app create node。此命令会在子目录中搭建一个新的 Node.js 应用,并在合作伙伴控制面板中创建您的应用。

步骤 7:启动本地开发服务器

创建应用程序后,您可以通过导航到项目目录并运行以shopify node serve启动本地开发服务器来使用它。

Shopify CLI 使用ngrok创建一个隧道,允许使用唯一的 HTTPS URL 访问您的应用程序,这在创建应用程序时是必需的。

步骤 8:在开发商店中安装您的应用

在服务器运行时,打开终端在上一步中打印出来的 URL。打开该 URL 后,系统会提示您在开发商店中安装该应用。
在此处输入图片描述

如果你点击此链接,你应该会看到一条包含你的应用及其作用域的消息。点击“安装”,即可继续下一步。

步骤 9:开始构建应用程序

之前,您使用Shopify CLI创建了一个新应用。现在您可以开始构建您的应用了。

在本教程中,你将完成一系列任务,为你的应用添加一些特定的功能。最终的应用会比较简单,但你将学习如何找到资源来自行构建更复杂的功能。
完成本教程后,你将完成以下任务:

  • 在您的开发商店中填充产品以测试您的应用
  • 使用 Polaris 构建用户界面的雏形
  • 设置 GraphQL 查询来检索产品
  • 设置 GraphQL 突变来更新产品价格

步骤 10:填充产品

Shopify CLI 可帮助您添加示例数据来测试应用的行为。您可以使用 Shopify CLI 创建产品、客户和草稿订单的记录。

由于您的应用需要与产品数据进行交互,因此首先需要在开发商店中填充产品:

  1. 打开一个新的终端窗口。
  2. 导航到您的项目目录。
  3. 跑步shopify populate products GIF 展示了如何在开发商店中填充数据

步骤 11:添加空状态

现在您可以在 Shopify 中运行您的应用,并在构建前端组件时查看和测试它们。您可以使用Shopify 的 React 组件库和设计系统Polaris来构建用户界面。

使用 Polaris 为您的应用添加空状态。Polaris空状态组件有助于在商家首次将应用添加到 Shopify 后台时,传达应用的价值及其主要操作。

  1. 在代码编辑器中,导航到您的pages/index.js文件。
  2. 用组件替换文件的内容EmptyState
import { Heading, Page, TextStyle, Layout, EmptyState} from "@shopify/polaris";
const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg';
const Index = () => (
  <Page>
    <Layout>
      <EmptyState // Empty state component
        heading="Discount your products temporarily"
        action={{
          content: 'Select products',
          onAction: () => this.setState({ open: true }),
        }}
        image={img}
      >
        <p>Select products to change their price temporarily.</p>
      </EmptyState>
    </Layout>
  </Page>
);
export default Index;
Enter fullscreen mode Exit fullscreen mode

当您预览嵌入式应用程序时,它会显示空状态。

显示应用空状态的屏幕截图

步骤 12:添加资源选择器

接下来,添加一个资源选择器,以便您可以从应用中选择产品。您可以使用Shopify 的独立原生 JavaScript 库App Bridge向您的应用添加资源选择器。

App BridgeResourcePicker操作集提供了基于搜索的界面,帮助您查找并选择一个或多个产品,然后将选定的资源返回到您的应用程序。

在您的pages/index.js文件中,添加一个用于设置资源选择器状态的类。然后,将ResourcePicker组件添加到组件上的主要操作按钮EmptyState

import React from 'react';
import { Heading, Page, TextStyle, Layout, EmptyState} from "@shopify/polaris";
import { ResourcePicker, TitleBar } from '@shopify/app-bridge-react';
const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg';
// Sets the state for the resource picker
class Index extends React.Component {
  state = { open: false };
  render() {
    return (
      <Page>
        <TitleBar
          primaryAction={{
            content: 'Select products',
            onAction: () => this.setState({ open: true }),
          }}
        />
        <ResourcePicker // Resource picker component
          resourceType="Product"
          showVariants={false}
          open={this.state.open}
          onSelection={(resources) => this.handleSelection(resources)}
          onCancel={() => this.setState({ open: false })}
        />
        <Layout>
          <EmptyState
            heading="Discount your products temporarily"
            action={{
              content: 'Select products',
              onAction: () => this.setState({ open: true }),
            }}
            image={img}
          >
            <p>Select products to change their price temporarily.</p>
          </EmptyState>
        </Layout>
      </Page>
    );
  }
  handleSelection = (resources) => {
    this.setState({ open: false });
    console.log(resources);
  };
}
export default Index;
Enter fullscreen mode Exit fullscreen mode

在您的嵌入式应用程序中,当您单击“选择产品”时,将打开“添加产品”模式。

显示“添加产品”模式的 GIF

步骤 13:添加资源列表

现在您已经设置了资源选择器,接下来需要一种检索产品的方法。您可以使用GraphQL Admin API检索产品。最终,您希望将这些产品显示在资源列表中。

要允许您的应用程序使用 GraphQL 查询数据,请创建一个新文件并在该文件中ResourceList.js包含graphql-tag和导入。react-apollo

然后,设置一个 GraphQL 查询来getProducts检索产品及其价格的列表。

  1. 跑步npm install store-js

  2. 在你的项目中的文件夹components创建一个新文件夹,并在该文件夹中创建一个新文件。pagesResourceList.js

  3. 将导入添加到您的ResourceList.js文件并设置您的 GraphQL 查询以检索产品及其价格:

import React from 'react';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
import {
  Card,
  ResourceList,
  Stack,
  TextStyle,
  Thumbnail,
} from '@shopify/polaris';
import store from 'store-js';
import { Redirect } from '@shopify/app-bridge/actions';
import { Context } from '@shopify/app-bridge-react';
// GraphQL query to retrieve products by IDs.
// The price field belongs to the variants object because
// variations of a product can have different prices.
const GET_PRODUCTS_BY_ID = gql`
  query getProducts($ids: [ID!]!) {
    nodes(ids: $ids) {
      ... on Product {
        title
        handle
        descriptionHtml
        id
        images(first: 1) {
          edges {
            node {
              originalSrc
              altText
            }
          }
        }
        variants(first: 1) {
          edges {
            node {
              price
              id
            }
          }
        }
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

在您的ResourceList.js文件中,在 GraphQL 查询之后,设置一个名为 的类,ResourceListWithProducts该类扩展了ResourceList组件并返回产品和价格。然后,定义您的ResourceList组件:

class ResourceListWithProducts extends React.Component {
  static contextType = Context;

  render() {
    const app = this.context;

    return (
      // GraphQL query to retrieve products and their prices
      <Query query={GET_PRODUCTS_BY_ID} variables={{ ids: store.get('ids') }}>
        {({ data, loading, error }) => {
          if (loading) return <div>Loading</div>;
          if (error) return <div>{error.message}</div>;

          return (
            <Card>
              <ResourceList // Defines your resource list component
                showHeader
                resourceName={{ singular: 'Product', plural: 'Products' }}
                items={data.nodes}
                renderItem={item => {
                  const media = (
                    <Thumbnail
                      source={
                        item.images.edges[0]
                          ? item.images.edges[0].node.originalSrc
                          : ''
                      }
                      alt={
                        item.images.edges[0]
                          ? item.images.edges[0].node.altText
                          : ''
                      }
                    />
                  );
                  const price = item.variants.edges[0].node.price;
                  return (
                    <ResourceList.Item
                      id={item.id}
                      media={media}
                      accessibilityLabel={`View details for ${item.title}`}
                      onClick={() => {
                        store.set('item', item);
                      }}
                    >
                      <Stack>
                        <Stack.Item fill>
                          <h3>
                            <TextStyle variation="strong">
                              {item.title}
                            </TextStyle>
                          </h3>
                        </Stack.Item>
                        <Stack.Item>
                          <p>${price}</p>
                        </Stack.Item>
                      </Stack>
                    </ResourceList.Item>
                    );
                  }}
                />
              </Card>
            );
          }}
        </Query>
      );
    }
  }
export default ResourceListWithProducts;
Enter fullscreen mode Exit fullscreen mode

pages/index.js文件中,添加导入代码并为应用的空状态定义一个常量。然后,更新控制 空状态布局的
代码,并指定使用新的资源列表 来显示产品:


import React from 'react';
import { Page, Layout, EmptyState} from "@shopify/polaris";
import { ResourcePicker, TitleBar } from '@shopify/app-bridge-react';
import store from 'store-js';
import ResourceListWithProducts from './components/ResourceList';
const img = 'https://cdn.shopify.com/s/files/1/0757/9955/files/empty-state.svg';
class Index extends React.Component {
  state = { open: false };
  render() {
    // A constant that defines your app's empty state
    const emptyState = !store.get('ids');
    return (
      <Page>
        <TitleBar
          primaryAction={{
            content: 'Select products',
            onAction: () => this.setState({ open: true }),
          }}
        />
        <ResourcePicker
          resourceType="Product"
          showVariants={false}
          open={this.state.open}
          onSelection={(resources) => this.handleSelection(resources)}
          onCancel={() => this.setState({ open: false })}
        />
        {emptyState ? ( // Controls the layout of your app's empty state
          <Layout>
            <EmptyState
              heading="Discount your products temporarily"
              action={{
                content: 'Select products',
                onAction: () => this.setState({ open: true }),
              }}
              image={img}
            >
              <p>Select products to change their price temporarily.</p>
            </EmptyState>
          </Layout>
        ) : (
          // Uses the new resource list that retrieves products by IDs
          <ResourceListWithProducts />
        )}
      </Page>
    );
  }
  handleSelection = (resources) => {
    const idsFromResources = resources.selection.map((product) => product.id);
    this.setState({ open: false });
    store.set('ids', idsFromResources);
  };
}
export default Index;
Enter fullscreen mode Exit fullscreen mode

现在,当您单击“选择产品”并从“添加产品”模式添加产品时,将显示产品列表。
GIF 展示了如何从模态框添加产品

步骤 14:更新产品价格

您已实现 GraphQL 查询来读取产品数据,并添加了在资源列表中显示检索到的产品的功能。接下来,您将使用 GraphQL 修改产品数据。

设置一个 GraphQL 突变,用于ProductVariantUpdate更新应用中的产品价格。

  1. 在您的文件夹中创建一个新ApplyRandomPrices.js文件components
  2. 将导入添加到您的ApplyRandomPrices.js文件并设置 GraphQL 变异,以允许您的应用更新产品价格:

页面/组件/ApplyRandomPrices.js

import React, { useState } from 'react';
import gql from 'graphql-tag';
import { Mutation } from 'react-apollo';
import { Layout, Button, Banner, Toast, Stack, Frame } from '@shopify/polaris';
import { Context } from '@shopify/app-bridge-react';
// GraphQL mutation that updates the prices of products
const UPDATE_PRICE = gql`
  mutation productVariantUpdate($input: ProductVariantInput!) {
    productVariantUpdate(input: $input) {
      product {
        title
      }
      productVariant {
        id
        price
      }
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode
  1. 在您进行变异之后ApplyRandomPrices.js,设置一个名为的类,ApplyRandomPrices该类接受您变异的输入并将随机价格应用于所选产品:

    页面/组件/ApplyRandomPrices.js

  class ApplyRandomPrices extends React.Component {
  static contextType = Context;

  render() {
    return ( // Uses mutation's input to update product prices
      <Mutation mutation={UPDATE_PRICE}>
        {(handleSubmit, {error, data}) => {
          const [hasResults, setHasResults] = useState(false);

          const showError = error && (
            <Banner status="critical">{error.message}</Banner>
          );

          const showToast = hasResults && (
            <Toast
              content="Successfully updated"
              onDismiss={() => setHasResults(false)}
            />
          );

          return (
            <Frame>
              {showToast}
              <Layout.Section>
                {showError}
              </Layout.Section>

              <Layout.Section>
                <Stack distribution={"center"}>
                  <Button
                    primary
                    textAlign={"center"}
                    onClick={() => {
                      let promise = new Promise((resolve) => resolve());
                      for (const variantId in this.props.selectedItems) {
                        const price = Math.random().toPrecision(3) * 10;
                        const productVariableInput = {
                          id: this.props.selectedItems[variantId].variants.edges[0].node.id,
                          price: price,
                        };

                        promise = promise.then(() => handleSubmit({ variables: { input: productVariableInput }}));
                      }

                      if (promise) {
                        promise.then(() => this.props.onUpdate().then(() => setHasResults(true)));
                    }}
                  }
                  >
                    Randomize prices
                  </Button>
                </Stack>
              </Layout.Section>
            </Frame>
          );
        }}
      </Mutation>
    );
  }
}
export default ApplyRandomPrices;
```

`
5.  Update your  `pages/index.js`  file to include the following imports:
`

```typescript
import React from 'react';
import gql from 'graphql-tag';
import { Mutation } from 'react-apollo';
import { Page, Layout, EmptyState, Button, Card } from "@shopify/polaris";
import { ResourcePicker, TitleBar } from '@shopify/app-bridge-react';
import store from 'store-js';
import ResourceListWithProducts from './components/ResourceList';
```

`

6.  In  `ResourceList.js`, add the  `ApplyRandomPrices`  import. Implement a constructor on your  `ResourceListWithProducts`  class and update your GraphQL query to enable refetching products by ID. Finally, update your  `ResourceList`  component:

    pages/components/ResourceList.js
  `

```typescript
 import React from 'react';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
import {
  Card,
  ResourceList,
  Stack,
  TextStyle,
  Thumbnail,
} from '@shopify/polaris';
import store from 'store-js';
import { Redirect } from '@shopify/app-bridge/actions';
import { Context } from '@shopify/app-bridge-react';
import ApplyRandomPrices from './ApplyRandomPrices';
// GraphQL query that retrieves products by ID
const GET_PRODUCTS_BY_ID = gql`
  query getProducts($ids: [ID!]!) {
    nodes(ids: $ids) {
      ... on Product {
        title
        handle
        descriptionHtml
        id
        images(first: 1) {
          edges {
            node {
              originalSrc
              altText
            }
          }
        }
        variants(first: 1) {
          edges {
            node {
              price
              id
            }
          }
        }
      }
    }
  }
`;
class ResourceListWithProducts extends React.Component {
  static contextType = Context;

  // A constructor that defines selected items and nodes
  constructor(props) {
    super(props);
    this.state = {
      selectedItems: [],
      selectedNodes: {},
    };
  }

  render() {
    const app = this.context;

    // Returns products by ID
    return (
        <Query query={GET_PRODUCTS_BY_ID} variables={{ ids: store.get('ids') }}>
          {({ data, loading, error, refetch }) => { // Refetches products by ID
            if (loading) return <div>Loading…</div>;
            if (error) return <div>{error.message}</div>;

            const nodesById = {};
            data.nodes.forEach(node => nodesById[node.id] = node);

            return (
              <>
                <Card>
                  <ResourceList
                    showHeader
                    resourceName={{ singular: 'Product', plural: 'Products' }}
                    items={data.nodes}
                    selectable
                    selectedItems={this.state.selectedItems}
                    onSelectionChange={selectedItems => {
                      const selectedNodes = {};
                      selectedItems.forEach(item => selectedNodes[item] = nodesById[item]);

                      return this.setState({
                        selectedItems: selectedItems,
                        selectedNodes: selectedNodes,
                      });
                    }}
                    renderItem={item => {
                      const media = (
                        <Thumbnail
                          source={
                            item.images.edges[0]
                              ? item.images.edges[0].node.originalSrc
                              : ''
                          }
                          alt={
                            item.images.edges[0]
                              ? item.images.edges[0].node.altText
                              : ''
                          }
                        />
                      );
                      const price = item.variants.edges[0].node.price;
                      return (
                        <ResourceList.Item
                          id={item.id}
                          media={media}
                          accessibilityLabel={`View details for ${item.title}`}
                          verticalAlignment="center"
                          onClick={() => {
                            let index = this.state.selectedItems.indexOf(item.id);
                            const node = nodesById[item.id];
                            if (index === -1) {
                                this.state.selectedItems.push(item.id);
                                this.state.selectedNodes[item.id] = node;
                            } else {
                              this.state.selectedItems.splice(index, 1);
                                delete this.state.selectedNodes[item.id];
                            }

                            this.setState({
                              selectedItems: this.state.selectedItems,
                              selectedNodes: this.state.selectedNodes,
                              });
                          }}
                        >
                          <Stack alignment="center">
                            <Stack.Item fill>
                              <h3>
                                <TextStyle variation="strong">
                                  {item.title}
                                </TextStyle>
                              </h3>
                            </Stack.Item>
                            <Stack.Item>
                              <p>${price}</p>
                            </Stack.Item>
                          </Stack>
                        </ResourceList.Item>
                      );
                    }}
                  />
                </Card>

              <ApplyRandomPrices selectedItems={this.state.selectedNodes} onUpdate={refetch} />
            </>
          );
        }}
      </Query>
    );
  }
}
export default ResourceListWithProducts;
```

`
In your app, you can now update the prices of products.

![GIF showing how to populate data in a development store](https://shopify.dev/assets/apps/randomize-prices-a8c49c220e447a3b5ac233f582eddd2a9bc81050c32c601b5de6ae99001e8ae8.gif)




## Next steps[](https://shopify.dev/apps/getting-started/add-functionality#next-steps)

-   Use  [webhooks](https://shopify.dev/apps/webhooks)  to stay in sync with Shopify or execute code after a specific event occurs in a shop.
-   Identify your  [app business model](https://shopify.dev/apps/billing/models)  and learn how to use the  [Billing API](https://shopify.dev/apps/billing)  to bill customers with recurring monthly charges or one-time purchases.
-   Learn how to use  [app extensions](https://shopify.dev/apps/app-extensions)  to add features to Shopify admin or POS.
-   Explore the  [GraphQL Admin API](https://shopify.dev/api/admin/graphql/reference)  and  [REST Admin API](https://shopify.dev/api/admin/rest/reference)  references.
Enter fullscreen mode Exit fullscreen mode
鏂囩珷鏉ユ簮锛�https://dev.to/hayerhans/shopify-app-step-by-step-283n
PREV
Docker 最佳实践。
NEXT
我只需要 5 个 vim 插件 结论