GC와 GraphQL을 사용하여 Strapi를 위한 식당 목록 UI 구축 방법

간단한 소개


이 글은 디자인을 감상하는 자유롭지만 설치할 필요가 없는 웹 기반 개발 환경에서 코드를 적게 쓰는 웹 개발자를 위해 쓴 것이다.
이것은 Strapi 사용GlueCodes Studio과 통합된 도구로 다른 곳에서 본 적이 없는 방식으로 일상적인 업무에 동력을 제공한다.자동화 부하에 만족하는 사람들에게는 매우 빠르고 확장 가능한 코드, 즉 서로 다른 JSX 구축 시간을 사용하고 은밀한 단방향 데이터 흐름을 둘러싸고 조직할 수 있다.분명히 너는 그것을 무료로 사용할 수 있다.더 이상 상하문 그리기가 없으니 시작합시다.

SolidJS 우리는 무엇을 짓고 있습니까?


우리는 를 머리 없는 CMS로 사용할 것이다.그것은 가상 미식 컨설턴트 사이트의 위탁 관리 시범을 곁들였고 식당 데이터를 심어 놓았다.실례를 요청할 수 있습니다 Strapi.양식을 작성하면 소량의 URL이 포함된 이메일이 전송됩니다.내 것은 이렇게 보인다.
Demo URL: https://api-hi2zm.strapidemo.com/admin

API restaurants URL: https://api-hi2zm.strapidemo.com/restaurants

GraphQL URL: https://api-hi2zm.strapidemo.com/graphql

Credentials: [email protected] / welcomeToStrapi123

Don't try to be a smartass, the URLs won't work longer that the demo duration you provided in the demo form.


Strapi를 어떻게 사용하는지 소개하지 않겠습니다. 마음에 들면 직접 탐색해 보십시오.Google 자습서에는 다음 두 개의 URL만 필요합니다.
GraphQL:
https://api-{someHash}.strapidemo.com/graphql

Image Server:
https://api-{someHash}.strapidemo.com
애플리케이션에는 다음과 같은 기능이 제공됩니다.
  • 명칭, 설명, 유형과 이미지를 포함하는 식당 격자
  • 범주별로 필터링
  • 인접 지역별 필터
  • 언어별 필터
  • 페이지
  • 이 응용 프로그램은 브라우저의 하드 로드 없이 필터를 적용하므로 SPA가 됩니다.1부에서는 Strapi 통합을 중점적으로 소개하고 페이지와 이동 응답을 2부에 남겨 두겠습니다.이것은 CSS 강좌가 아니기 때문에, 나는 모든 스타일의 개선을 너에게 남겨 줄 것이다.보아하니 이렇다.
    here

    부호화


    우선, 당신은 로 이동해야 합니다.구글이나 Github를 통해 등록을 요구받을 것입니다.걱정하지 마라, 그것은 너의 어떤 세부 사항도 필요 없다.프로젝트 관리자가 되면 Strapi Food Advisor 템플릿을 선택합니다.항목에 저장할 디렉터리를 선택하라는 요청을 받을 것입니다.하나만 선택하면 IDE로 리디렉션됩니다.
    GlueCodes Studio
    당신은 다음과 같은 소개적인 안내를 받을 수 있습니다.

    위에서 설명한 대로 두 개의 URL이 필요합니다.
    GraphQL:
    https://api-{someHash}.strapidemo.com/graphql
    
    Image Server:
    https://api-{someHash}.strapidemo.com
    
    글로벌 변수GQL_URLIMAGE_BASE_URL에 추가하겠습니다.

    현재 실행 중인 프로그램을 보기 위해 '미리 보기' 를 누르면 됩니다.

    응용 프로그램 데이터 흐름 설계


    Strapi의 GraphQL API에서 추출한 식당 목록이 필요합니다. 데이터 흐름 관리 기능이 내장되어 있습니다.비즈니스 논리는 응용 프로그램 작업에 분포되어 있으며, 이 작업은 되돌아오거나 해석된 값을 단일 대상 저장소에 저장합니다.데이터 변경 사항이 한 방향으로 흐르고 UI는 저장된 변경 사항에 반응하며 영향을 받는 부분만 업데이트합니다.DOM 차이는 컴파일할 때 발생합니다GlueCodes Studio.
    두 가지 유형의 행위가 있다.이전에 제공한 데이터를 공급자라고 하고 사용자가 촉발한 것을 명령자라고 한다.두 반환/해결 값은 각각 이름을 사용하여 단일 객체 저장소에서 액세스할 수 있습니다.UI에서 전역 변수: actionsactionResults에 액세스할 수 있습니다.변수actions는 조작(예를 들어 반환/해석으로 얻은 데이터)을 실행할 수 있는 명령을 호출할 수 있는 대상이다.너는 SolidJS에서 더 많은 내용을 읽을 수 있다.하는 것이 말하는 것보다 쉬우니 인내심을 가지고 내 말을 들어 주세요.
    우리가 사용하는 API 호출은 식당과 클래스로 돌아갑니다.GraphQL 호출에 영향을 줄 커뮤니티 목록과 URL 조회 파라미터를 분석해야 합니다.UI로 데이터를 전송하기 전에 몇 가지 기본적인 데이터 변환이 필요합니다.이러한 정보를 바탕으로 다음 공급업체를 선택하기로 결정했습니다.
  • fetchRestaurantData
  • getCategories
  • getLanguages
  • GetNeights
  • getRestaurants
  • parseUrlQueryParams
  • 필터링을 수행하려면 다음 명령이 필요합니다.
  • 변경 범주
  • 언어 변경
  • 변화 커뮤니티
  • 나는 하나하나 그것들을 소개할 것이지만, 그 전에, 당신은 제공자의 메커니즘을 더욱 이해해야 합니다.제공 프로그램은 돌아올 때 자신의 이름으로 단일 대상 저장소에 은밀하게 기록됩니다.그런 다음 이 스토리지의 스냅샷을 한 공급업체에서 다른 공급업체로 전송합니다.이것은 이전에 호출된 공급자의 결과를 방문할 수 있음을 의미합니다.이것 또한 네가 그들의 집행 순서를 설정해야 한다는 것을 의미한다.특정 공급자로 이동하여 "Run After"버튼을 클릭한 다음 해당 창에서 이전에 수행해야 할 공급자를 선택하면 이 작업을 수행할 수 있습니다.너는 이런 일을 기대할 수 있다.
    docs
    우리는 다음과 같은 목표를 달성하기를 희망한다.fetchRestaurantData 사용parseUrlQueryParams의 결과.getRestaurantsgetCategoriesfetchRestaurantData를 사용한 결과.
    다음과 같습니다.
  • GetNeights
  • parseUrlQueryParams
  • fetchRestaurantData
  • getRestaurants
  • getLanguages
  • getCategories
  • 자, 이제 함수를 깊이 연구합시다.

    행동

    providers/fetchRestaurantData :
    export default async (actionResults) => {
      const { category, district, locale } = actionResults.parseUrlQueryParams 
    
      const where = {
        locale: 'en'
      }
    
      if (category !== 'all') {
        where.category = category
      }
    
      if (district !== 'all') {
        where.district = district
      }
    
      if (locale) {
        where.locale = locale
      }
    
      const query = `
        query ($limit: Int, $start: Int, $sort: String, $locale: String, $where: JSON) {
          restaurants(limit: $limit, start: $start, sort: $sort, locale: $locale, where: $where) {
            id
            description
            district
            cover {
              url
            }
            category {
              name
            }
            name
            locale
            localizations {
              id
              locale
            }
            note
            price
            reviews {
              note
              content
            }
          }
          restaurantsConnection(where: $where) {
            aggregate {
              count
            }
          }
          categories {
            id
            name
          }
        }
      `
    
      const records = await (await fetch(global.GQL_URL, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          query,
          variables: {
            limit: 15,
            start: actionResults.parseUrlQueryParams.start || 0,
            sort: 'name:ASC',
            locale: 'en',
            where
          }
        })
      })).json()
    
      return records.data
    }
    
    
    노트:
  • actionResults.parseUrlQueryParams 액세스 쿼리 URL 매개 변수
  • global.GQL_URL 액세스GQL_URL 글로벌 변수
  • providers/getCategories :
    export default (actionResults) => {
      return [
        {
          id: 'all',
          name: 'All'
        },
        ...actionResults.fetchRestaurantData.categories  
      ]
    }
    
    노트:
  • actionResults.fetchRestaurantData.categories 액세스가 fetchRestaurantData 결과
  • 에 속하는 범주providers/getLanguages :
    export default () => {
      return [
        {
          id: 'en',
          name: 'En'
        },
        {
          id: 'fr',
          name: 'Fr'
        }
      ]
    }
    
    providers/getNeighborhoods :
    export default () => {
      return [
        { name: 'All', id: 'all' },
        { name: '1st', id: '_1st' },
        { name: '2nd', id: '_2nd' },
        { name: '3rd', id: '_3rd' },
        { name: '4th', id: '_4th' },
        { name: '5th', id: '_5th' },
        { name: '6th', id: '_6th' },
        { name: '7th', id: '_7th' },
        { name: '8th', id: '_8th' },
        { name: '9th', id: '_9th' },
        { name: '10th', id: '_10th' },
        { name: '11th', id: '_11th' },
        { name: '12th', id: '_12th' },
        { name: '13th', id: '_13th' },
        { name: '14th', id: '_14th' },
        { name: '15th', id: '_15th' },
        { name: '16th', id: '_16th' },
        { name: '17th', id: '_17th' },
        { name: '18th', id: '_18th' },
        { name: '19th', id: '_19th' },
        { name: '20th', id: '_20th' }
      ]
    }
    
    providers/getRestaurants :
    export default (actionResults) => {
      return actionResults.fetchRestaurantData.restaurants
        .map((record) => ({
          id: record.id,
          name: record.name,
          description: record.description,
          category: record.category.name,
          district: record.district,
          thumbnail: record.cover[0].url
        }))
    }
    
    노트:
  • actionResults.fetchRestaurantData.restaurants방문fetchRestaurantData결과
  • 식당providers/parseUrlQueryParams :
    export default (actionResults) => {
      return imports.parseUrlQueryParams()
    }
    
    노트:
  • imports.parseUrlQueryParams 외부 의존 함수에 접근합니다.
  • In GlueCodes Studio you can use any UMD-bundled modules including those in UNPKG. Just click on Dependencies icon and edit the JSON file to look like:


    {
      "css": {
        "bootstrap": "https://unpkg.com/[email protected]/dist/css/bootstrap.min.css",
        "fa": "https://unpkg.com/@fortawesome/[email protected]/css/all.min.css"
      },
      "js": {
        "modules": {
          "parseUrlQueryParams": "https://ide.glue.codes/repos/df67f7a82cbdc5efffcb31c519a48bf6/basic/reusable-parseUrlQueryParams-1.0.4/index.js",
          "setUrlQueryParam": "https://ide.glue.codes/repos/df67f7a82cbdc5efffcb31c519a48bf6/basic/reusable-setUrlQueryParam-1.0.4/index.js"
        },
        "imports": {
          "parseUrlQueryParams": {
            "source": "parseUrlQueryParams",
            "importedName": "default"
          },
          "setUrlQueryParam": {
            "source": "setUrlQueryParam",
            "importedName": "default"
          }
        }
      }
    }
    
    commands/changeCategory :
    export default (categoryId) => {
      imports.setUrlQueryParam({ name: 'category', value: categoryId })
    }
    
    노트:
  • imports.setUrlQueryParam 외부 의존 함수 접근
  • commands/changeLanguage :
    export default (languageId) => {
      imports.setUrlQueryParam({ name: 'locale', value: languageId })
    }
    
    commands/changeNeighborhood :
    export default (neighborhoodId) => {
      imports.setUrlQueryParam({ name: 'district', value: neighborhoodId })
    }
    

    구조


    에서는 각 페이지가 논리적 UI 섹션으로 나누어져 있어 UI를 모듈식으로 유지할 수 있습니다.단일 슬롯에는 해당 역할 도메인 CSS가 있습니다. 즉, 지정된 슬롯에만 영향을 주는 클래스 설정 스타일이 있고 다른 슬롯에서 해당 이름을 복사할 수 있습니다.내보낸 코드에서 슬롯은 전용 파일로 추출되어 유지보수하기 쉽습니다.
    HTML을 동적으로 하려면 현대 웹 프레임워크에서처럼 속성 명령을 사용할 수 있습니다.대부분의 명령을 입력하면 필요한 명령을 자동으로 만들고 프로그램을 제공하거나 작은 위젯을 설치하는 알림을 받을 수 있습니다.어휘표는 매우 간단합니다.attribute[gc-as]는 우리에게 그것이 무엇인지 알려주고 기타[gc-*] 속성은 매개 변수입니다.주의: 모든 명칭 속성에 대해camelcase를 사용하십시오. 예를 들어 사용할 슬롯[gc-name="myAwesomeSlot"]을 사용하십시오.
    다음은 간략한 색인 페이지 HTML입니다.
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      <meta gc-as="navName" gc-name="Home">
      <title>FoodAdvisor</title>
    <body>
      <div gc-as="layout">
        <div class="container-fluid">
          <div gc-as="slot" gc-name="header"></div>
          <div class="d-flex">
            <div gc-as="slot" gc-name="filters"></div>
            <div gc-as="slot" gc-name="content">
              <div class="contentWrapper">
                <h1 class="heading">Best restaurants in Paris</h1>
                <div class="grid">
                  <div gc-as="listItemPresenter" gc-provider="getRestaurants" class="card">
                    <img-x class="card-img-top thumbnail" alt="Card image cap">
                      <script>
                        props.src = `${global.IMAGE_BASE_URL}${getRestaurantsItem.thumbnail}`
                      </script>
                    </img-x>
                    <div class="card-body">
                      <h4 gc-as="listFieldPresenter" gc-provider="getRestaurants" gc-field="name" class="name">restaurant name</h4>
                      <h5 gc-as="listFieldPresenter" gc-provider="getRestaurants" gc-field="category" class="category">restaurant category</h5>
                      <p gc-as="listFieldPresenter" gc-provider="getRestaurants" gc-field="description" class="card-text">restuarant description</p>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div gc-as="slot" gc-name="footer"></div>
        </div>
      </div>
    </body>
    </html>
    
    노트:
  • <div gc-as="layout">는 응용 프로그램 패키지입니다.
  • <div gc-as="slot" gc-name="content">는 논리 사용자 인터페이스로 역할 영역 CSS가 있고 전용 파일로 추출됩니다.이것은 유일한 (페이지 내) camelcase gc 이름을 필요로 합니다.슬롯의 모든 물건은 저장, 명령, 기타 유용한 변수에 접근할 수 있다.당신은 더 많은 것을 알 수 있습니다GlueCodes Studio.
  • <div gc-as="slot" gc-name="filters"></div>는 재사용 가능한 슬롯입니다.슬롯과 비슷하지만 여러 페이지에서 사용할 수 있습니다.재사용 가능한 플러그는 일부 플러그로 이해할 수 있다.전용 HTML 편집기에서 재사용 가능한 슬롯을 편집하고 empty slot 명령을 사용하여 페이지에 삽입합니다.
  • <div gc-as="listItemPresenter" gc-provider="getRestaurants" class="card"> 프로그램이 되돌아오는 그룹에서 이div를 반복합니다.
  • getRestaurants 프로그램에서 순환할 때 항목의 속성<h4 gc-as="listFieldPresenter" gc-provider="getRestaurants" gc-field="name" class="name">restaurant name</h4>을 표시합니다.
  • 다시 한 번 봅시다.
    <img-x class="card-img-top thumbnail" alt="Card image cap">
      <script>
        props.src = `${global.IMAGE_BASE_URL}${getRestaurantsItem.thumbnail}`
      </script>
    </img-x>
    
    정적 HTML은 내장된 방식으로 반응하지 않습니다.따라서 herelike:name라고 명명되고 삽입식getRestaurants이 포함된 확장 라벨이라는 개념이 있다.코드는 샌드박스입니다. 다른 명령 (예를 들어 슬롯이나 목록 항목 표시기) 에서 사용할 수 있는 변수에 접근할 수 있습니다.스크립트는 tagName + '-x' 변수에 분배하여 확장 표시된 도구/속성을 변경할 수 있습니다.

    Note that when an extended tag is placed inside a list item presenter you get access to a variable called like: providerName + Item, in our case getRestaurantsItem which is an item while looping over getRestaurants provider. You could also access getRestaurantsIndex for a numeric index in the array.


    기타 템플릿:<script> :
    <div class="wrapper">
      <h2 class="heading">Categories</h2>
      <ul class="filterSet">
        <li gc-as="listItemPresenter" gc-provider="getCategories" class="filterItem">
          <label>
            <input-x type="radio">
              <script>
                props.name = 'category'
                props.value = getCategoriesItem.id
                props.checked = getCategoriesItem.id === (actionResults.parseUrlQueryParams.category || 'all')
                props.onChange = (e) => {
                  actions.changeCategory(e.target.value)
                  actions.reload()
                }
              </script>
            </input-x>
            <span gc-as="listFieldPresenter" gc-provider="getCategories" gc-field="name" class="label">category name</span>
          </label>
        </li>
      </ul>
      <h2 class="heading">Neighborhood</h2>
      <ul class="filterSet">
        <li gc-as="listItemPresenter" gc-provider="getNeighborhoods" class="filterItem">
          <label>
            <input-x type="radio">
              <script>
                props.name = 'neighborhood'
                props.value = getNeighborhoodsItem.id
                props.checked = getNeighborhoodsItem.id === (actionResults.parseUrlQueryParams.district || 'all')
                props.onChange = (e) => {
                  actions.changeNeighborhood(e.target.value)
                  actions.reload()
                }
              </script>
            </input-x>
            <span gc-as="listFieldPresenter" gc-provider="getNeighborhoods" gc-field="name" class="label">neighborhood name</span>
          </label>
        </li>
      </ul>
      <h2 class="heading">Language</h2>
      <ul class="filterSet">
        <li gc-as="listItemPresenter" gc-provider="getLanguages" class="filterItem">
          <label>
            <input-x type="radio">
              <script>
                props.name = 'languages'
                props.value = getLanguagesItem.id
                props.checked = getLanguagesItem.id === (actionResults.parseUrlQueryParams.locale || 'en')
                props.onChange = (e) => {
                  actions.changeLanguage(e.target.value)
                  actions.reload()
                }
              </script>
            </input-x>
            <span gc-as="listFieldPresenter" gc-provider="getLanguages" gc-field="name" class="label">language name</span>
          </label>
        </li>
      </ul>
    </div>
    
    props :
    <footer class="wrapper">
      <p>Try <a href="https://www.glue.codes" class="link">GlueCodes Studio</a> now!</p>
      <ul class="nav">
        <li class="navItem">
          <a href="https://www.facebook.com/groups/gluecodesstudio" class="navLink"><i class="fab fa-facebook"></i></a>
        </li>
        <li class="navItem">
          <a href="https://www.youtube.com/channel/UCDtO8rCRAYyzM6pRXy39__A/featured?view_as=subscriber" class="navLink"><i class="fab fa-youtube"></i></a>
        </li>
        <li class="navItem">
          <a href="https://www.linkedin.com/company/gluecodes" class="navLink"><i class="fab fa-linkedin-in"></i></a>
        </li>
      </ul>
    </footer>
    
    reusableSlots/filters :
    <nav class="navbar navbar-light bg-light wrapper">
      <a class="navbar-brand link" href="/">
        <img-x width="30" height="30" alt="FoodAdvisor" class="logo">
          <script>
            props.src = mediaFiles['logo.png'].src
          </script>
        </img-x> FoodAdvisor
      </a>
    </nav>
    

    You can access any images or videos you drop in the studio via mediaFiles variable which is an object where file names are the keys. Implicitly there is a Webpack Responsive Loader involved which gives you src and placeholder.


    GlueCodes Room 콘셉트


    스타일에 대해서는 구식 HTML과 CSS를 작성하는 것 같지만, 은식으로 사용합니다. CSS Modules 범위와 글로벌 스타일 사이의 완벽한 균형을 제공합니다.따라서 당신은 당신의 응용에 대해 전역적인 주제화를 할 수 있고 사용자 인터페이스에서 선택한 부분에 대해 단독으로 스타일 디자인을 할 수 있다.CSS 클래스만 사용하면 스텔스 필드로 인해 서로 다른 슬롯 사이에서 클래스 이름을 안전하게 복사할 수 있습니다.
    GlueCodes Studio

    Notice a rather unusual @import statements. It's a way of importing third-party CSS from dependencies or global styles. The names must match the ones in Dependencies JSON or name of a global stylesheet.

    reusableSlots/footer
    @import 'bootstrap';
    
    reusableSlots/header
    @import 'bootstrap';
    @import 'fa';
    @import 'theme';
    
    .contentWrapper {
      padding: 0 20px;
    }
    
    .grid {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      grid-gap: 30px;
      margin-top: 40px;
    }
    
    .heading {
      margin-bottom: 0;
      font-size: 32px;
    }
    
    .thumbnail {
      transition: transform 0.3s;
    }
    
    .thumbnail:hover {
      transform: translateY(-4px); 
    }
    
    .name {
      font-weight: 700;
      font-size: 16px;
      color: rgb(25, 25, 25);
    }
    
    .category {
      font-size: 13px;
      color: #666;
    }
    
    pages/index/This Page CSS :
    .wrapper {
      padding: 0 20px;
      padding-top: 75px;
      min-width: 250px;
    }
    
    .filterSet, .filterItem {
      margin: 0;
      padding: 0;
    }
    
    .filterSet {
      margin-bottom: 30px;
    }
    
    .filterItem {
      list-style: none;
    }
    
    .filterItem label {
      cursor: pointer;
    }
    
    .label {
      padding-left: 4px;
    }
    
    .heading {
      padding-bottom: 15px;
      font-weight: 700;
      font-size: 16px;
      color: rgb(25, 25, 25);
    }
    
    pages/index/Content Slot CSS :
    @import 'fa';
    
    .wrapper {
      margin-top: 70px;
      padding: 20px;
      background-color: #1C2023;
      color: white;
    }
    
    .link {
      color: white;
    }
    
    .link:hover {
      color: #219F4D;
      text-decoration: none;
    }
    
    .nav {
      display: flex;
      margin: 0;
      padding: 0;
    }
    
    .navItem {
      list-style: none;  
    }
    
    .navLink {
      display: inline-block;
      margin-right: 2px;
      width: 40px;
      height: 40px;
      line-height: 40px;
      text-align: center;
      font-size: 18px;
      border-radius: 50%;
      background-color: #272a2e;
    }
    
    .navLink,
    .navLink:hover,
    .navLink:active,
    .navLink.visited {
      text-decoration: none;
      color: white;
    }
    
    .navLink:hover {
      background-color: #219F4D;
    }
    
    reusableSlots/filters :
    .wrapper {
      padding: 20px;
      background: #1C2023;
      margin-bottom: 30px;
    }
    
    .link {
      color: white;
      font-size: 18px;
      font-weight: 700;
    }
    
    .link,
    .link:hover,
    .link:active,
    .link:visited {
      color: white;
      text-decoration: none;
    }
    
    .logo {
      margin-right: 3px;
    }
    

    다음은요?


    네가 이미 알아차렸듯이, 여기에는 세부적인 기조가 있는데, 그것이 합리적으로 흡수되기를 바란다.본문을 발표한 지 얼마 되지 않아 저는 여러분과 이 프로젝트의 직접 링크를 공유할 것입니다. 및 Strapi를 사용하여 맞춤형 CMS를 구축할 수 있습니다.
    내가 두 번째 부분을 써야 하는지, 아니면 네가 보고 싶은 통합이 있는지 알게 해 줘.
    또한 DellGlueCodes Studio에 가입해 주십시오.

    좋은 웹페이지 즐겨찾기