首頁 > web前端 > js教程 > 主體

從零到店面:我建立汽車租賃網站和行動應用程式的旅程

Mary-Kate Olsen
發布: 2024-11-26 00:26:12
原創
615 人瀏覽過

內容

  1. 簡介
  2. 技術堆疊
  3. 快速概覽
  4. API
  5. 前端
  6. 行動應用程式
  7. 管理儀表板
  8. 興趣點
  9. 資源

原始碼:https://github.com/aelassas/bookcars

示範:https://bookcars.dynv6.net:3002

介紹

這個想法源於建立無邊界的願望 - 一個完全可定制的汽車租賃網站和行動應用程序,其中每個方面都在您的控制之下:

  • 擁有 UI/UX:設計獨特的客戶體驗,而無需克服模板限制
  • 控制後端:實現完美匹配需求的自訂業務邏輯和資料結構
  • 掌握 DevOps:使用首選工具和工作流程部署、擴展和監控應用程式
  • 自由擴充:增加新功能和集成,無需平台限製或額外費用

技術堆疊

這是使其成為可能的技術堆疊:

  • 打字稿
  • Node.js
  • MongoDB
  • 反應
  • MUI
  • React Native
  • 世博會
  • 條紋
  • 碼頭工人

由於 TypeScript 具有眾多優點,因此做出了使用 TypeScript 的關鍵設計決定。 TypeScript 提供強大的類型、工具和集成,從而產生高品質、可擴展、更具可讀性和可維護性的程式碼,並且易於調試和測試。

我選擇React是因為它強大的渲染能力,MongoDB是為了靈活的資料建模,而Stripe是為了安全的支付處理。

選擇此堆疊,您不僅僅是在建立網站和行動應用程式 - 您正在投資一個可以根據您的需求不斷發展的基礎,並得到強大的開源技術和不斷發展的開發者社群的支持。

React 因其以下優點而成為絕佳選擇:

  1. 基於組件的架構
    • 讓您將複雜的 UI 分解為更小的、可重複使用的部分
    • 使程式碼更易於維護且更易於測試
    • 實現更好的程式碼組織和可重複使用性
  2. 虛擬 DOM 效能
    • React 的虛擬 DOM 有效率地僅更新必要的內容
    • 帶來更快的頁面載入和更好的使用者體驗
    • 減少不必要的重新渲染
  3. 豐富的生態系統
    • 龐大的預建元件庫
    • 豐富的工具
    • 提供支持和資源的大型社區
  4. 豐富的開發人員經驗
    • 熱重載以取得即時回饋
    • 優秀的調試工具
    • JSX 讓寫 UI 程式碼更直覺
  5. 業界支持
    • 由 Meta(以前的 Facebook)支援
    • 被許多大公司使用
    • 持續開發與改進
  6. 靈活性
    • 適用於小型和大型應用程式
    • 可以逐步整合到現有專案中
    • 支援多種渲染策略(客戶端、服務端、靜態)

快速概覽

在本部分中,您將看到前端、管理儀表板和行動應用程式的主頁。

前端

在前端,使用者可以搜尋可用的汽車、選擇汽車並結帳。

下面是前端主頁,使用者可以選擇上下車地點和時間,並搜尋可用的車輛。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是主頁的搜尋結果,用戶可以在其中選擇租車。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

下面是結帳頁面,使用者可以在其中設定租賃選項和結帳。如果用戶未註冊,可以同時結帳和註冊。如果他尚未註冊,他將收到一封確認和啟動電子郵件以設定密碼。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是登入頁面。在生產中,身份驗證 cookie 是 httpOnly、簽署的、安全且嚴格的 sameSite。 這些選項可防止 XSS、CSRF 和 MITM 攻擊。 身份驗證 cookie 也可以透過自訂中間件免受 XST 攻擊。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是註冊頁面。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是使用者可以查看和管理他的預訂的頁面。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

下面是用戶可以查看預訂詳細資訊的頁面。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是聯絡頁面。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是租車地點頁。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是客戶可以查看和管理他的通知的頁面。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

還有其他頁面,但這些是前端的主要頁面。

管理儀表板

使用者分為三種:

  • 管理員:他擁有管理儀表板的完全存取權。他什麼都能做。
  • 供應商:他對管理儀表板的存取權限受到限制。他只能管理他的汽車和預訂。
  • 使用者:他只能存取前端和行動應用程式。他無法存取管理儀表板。

該平台旨在與多個供應商合作。每個供應商都可以從管理儀表板管理他的車隊和預訂。該平台也可以只與一個供應商合作。

透過管理儀表板,管理員使用者可以建立和管理供應商、汽車、位置、使用者和預訂。

當管理員使用者建立新供應商時,供應商將收到一封自動電子郵件,用於建立他的帳戶以存取管理儀表板,以便他可以管理他的車隊和預訂。

以下是管理儀表板的登入頁面。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是管理儀表板的儀表板頁面,管理員和供應商可以在其中查看和管理預訂。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是車隊展示和管理的頁面。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是管理員和供應商可以透過提供圖像和汽車資訊來建立新車的頁面。對於免費包含的汽車選項,請將相應的汽車選項設為 0。否則,請設定選項的價格,如果您不想包含它,請將其留空。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是管理員和供應商可以編輯汽車的頁面。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是管理員可以管理平台使用者的頁面。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

以下是編輯預訂的頁面。

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

還有其他頁面,但這些是管理儀表板的主頁。

應用程式介面

From Zero to Storefront: My Journey Building a Car Rental Website and Mobile App

API 公開了管理儀表板、前端和行動應用程式所需的所有功能。 API遵循MVC設計模式。 JWT 用於身份驗證。有些功能需要身份驗證,例如與管理汽車、預訂和客戶相關的功能,而其他功能則不需要身份驗證,例如為未經過身份驗證的用戶檢索位置和可用汽車:

  • ./api/src/models/ 資料夾包含 MongoDB 模型。
  • ./api/src/routes/ 資料夾包含 Express 路線。
  • ./api/src/controllers/ 資料夾包含控制器。
  • ./api/src/middlewares/ 資料夾包含中間件。
  • ./api/src/config/env.config.ts 包含設定和 TypeScript 類型定義。
  • ./api/src/lang/ 資料夾包含在地化內容。
  • ./api/src/app.ts 是載入路由的主伺服器。
  • ./api/index.ts 是 api 的主要入口點。

index.ts 是 api 的主要入口點:

import 'dotenv/config'
import process from 'node:process'
import fs from 'node:fs/promises'
import http from 'node:http'
import https, { ServerOptions } from 'node:https'
import * as env from './config/env.config'
import * as databaseHelper from './common/databaseHelper'
import app from './app'
import * as logger from './common/logger'

if (
  await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG)
  && await databaseHelper.initialize()
) {
  let server: http.Server | https.Server

  if (env.HTTPS) {
    https.globalAgent.maxSockets = Number.POSITIVE_INFINITY
    const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8')
    const certificate = await fs.readFile(env.CERTIFICATE, 'utf8')
    const credentials: ServerOptions = { key: privateKey, cert: certificate }
    server = https.createServer(credentials, app)

    server.listen(env.PORT, () => {
      logger.info('HTTPS server is running on Port', env.PORT)
    })
  } else {
    server = app.listen(env.PORT, () => {
      logger.info('HTTP server is running on Port', env.PORT)
    })
  }

  const close = () => {
    logger.info('Gracefully stopping...')
    server.close(async () => {
      logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`)
      await databaseHelper.close(true)
      logger.info('MongoDB connection closed')
      process.exit(0)
    })
  }

  ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close))
}
登入後複製
登入後複製

這是一個使用 Node.js 和 Express 啟動伺服器的 TypeScript 檔案。它導入了多個模組,包括 dotenv、process、fs、http、https、mongoose 和 app。然後,它檢查 HTTPS 環境變數是否設為 true,如果是,則使用 https 模組以及提供的私密金鑰和憑證建立 HTTPS 伺服器。否則,它使用 http 模組建立一個 HTTP 伺服器。伺服器監聽 PORT 環境變數中指定的連接埠。

close 函數被定義為在收到終止訊號時優雅地停止伺服器。它關閉伺服器和 MongoDB 連接,然後以狀態碼 0 退出進程。最後,它註冊當進程收到 SIGINT、SIGTERM 或 SIGQUIT 訊號時要呼叫的 close 函數。

app.ts 是 api 的主要入口點:

import express from 'express'
import compression from 'compression'
import helmet from 'helmet'
import nocache from 'nocache'
import cookieParser from 'cookie-parser'
import i18n from './lang/i18n'
import * as env from './config/env.config'
import cors from './middlewares/cors'
import allowedMethods from './middlewares/allowedMethods'
import supplierRoutes from './routes/supplierRoutes'
import bookingRoutes from './routes/bookingRoutes'
import locationRoutes from './routes/locationRoutes'
import notificationRoutes from './routes/notificationRoutes'
import carRoutes from './routes/carRoutes'
import userRoutes from './routes/userRoutes'
import stripeRoutes from './routes/stripeRoutes'
import countryRoutes from './routes/countryRoutes'
import * as helper from './common/helper'

const app = express()

app.use(helmet.contentSecurityPolicy())
app.use(helmet.dnsPrefetchControl())
app.use(helmet.crossOriginEmbedderPolicy())
app.use(helmet.frameguard())
app.use(helmet.hidePoweredBy())
app.use(helmet.hsts())
app.use(helmet.ieNoOpen())
app.use(helmet.noSniff())
app.use(helmet.permittedCrossDomainPolicies())
app.use(helmet.referrerPolicy())
app.use(helmet.xssFilter())
app.use(helmet.originAgentCluster())
app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' }))
app.use(helmet.crossOriginOpenerPolicy())

app.use(nocache())
app.use(compression({ threshold: 0 }))
app.use(express.urlencoded({ limit: '50mb', extended: true }))
app.use(express.json({ limit: '50mb' }))

app.use(cors())
app.options('*', cors())
app.use(cookieParser(env.COOKIE_SECRET))
app.use(allowedMethods)

app.use('/', supplierRoutes)
app.use('/', bookingRoutes)
app.use('/', locationRoutes)
app.use('/', notificationRoutes)
app.use('/', carRoutes)
app.use('/', userRoutes)
app.use('/', stripeRoutes)
app.use('/', countryRoutes)

i18n.locale = env.DEFAULT_LANGUAGE

await helper.mkdir(env.CDN_USERS)
await helper.mkdir(env.CDN_TEMP_USERS)
await helper.mkdir(env.CDN_CARS)
await helper.mkdir(env.CDN_TEMP_CARS)
await helper.mkdir(env.CDN_LOCATIONS)
await helper.mkdir(env.CDN_TEMP_LOCATIONS)

export default app
登入後複製
登入後複製

首先,我們檢索 MongoDB 連接字串,然後與 MongoDB 資料庫建立連線。然後我們創建一個 Express 應用程式並載入 cors、壓縮、頭盔和 nocache 等中間件。我們使用頭盔中間件庫設置了各種安全措施。我們也為應用程式的不同部分匯入各種路線文件,例如 sellerRoutes、bookingRoutes、locationRoutes、notificationRoutes、carRoutes 和 userRoutes。最後,我們載入 Express 路線並匯出應用程式。

api中有8條路由。每條路線都有自己的控制器,遵循 MVC 設計模式和 SOLID 原則。主要路線如下:

  • userRoutes:提供與使用者相關的REST功能
  • supplierRoutes:提供與供應商相關的REST功能
  • countryRoutes:提供與國家相關的REST功能
  • locationRoutes:提供與位置相關的REST函數
  • carRoutes:提供與汽車相關的REST功能
  • bookingRoutes:提供與預訂相關的REST功能
  • notificationRoutes:提供與通知相關的REST功能
  • stripeRoutes:提供與Stripe支付網關相關的REST功能

我們不會一一解釋每條路線。我們將以 CountryRoutes 為例,看看它是如何製作的:

import express from 'express'
import routeNames from '../config/countryRoutes.config'
import authJwt from '../middlewares/authJwt'
import * as countryController from '../controllers/countryController'

const routes = express.Router()

routes.route(routeNames.validate).post(authJwt.verifyToken, countryController.validate)
routes.route(routeNames.create).post(authJwt.verifyToken, countryController.create)
routes.route(routeNames.update).put(authJwt.verifyToken, countryController.update)
routes.route(routeNames.delete).delete(authJwt.verifyToken, countryController.deleteCountry)
routes.route(routeNames.getCountry).get(authJwt.verifyToken, countryController.getCountry)
routes.route(routeNames.getCountries).get(authJwt.verifyToken, countryController.getCountries)
routes.route(routeNames.getCountriesWithLocations).get(countryController.getCountriesWithLocations)
routes.route(routeNames.checkCountry).get(authJwt.verifyToken, countryController.checkCountry)
routes.route(routeNames.getCountryId).get(authJwt.verifyToken, countryController.getCountryId)

export default routes
登入後複製
登入後複製

首先,我們建立一個 Express Router。然後,我們使用名稱、方法、中間件和控制器來建立路由。

routeNames 包含countryRoutes 路線名稱:

const routes = {
  validate: '/api/validate-country',
  create: '/api/create-country',
  update: '/api/update-country/:id',
  delete: '/api/delete-country/:id',
  getCountry: '/api/country/:id/:language',
  getCountries: '/api/countries/:page/:size/:language',
  getCountriesWithLocations: '/api/countries-with-locations/:language/:imageRequired/:minLocations',
  checkCountry: '/api/check-country/:id',
  getCountryId: '/api/country-id/:name/:language',
}

export default routes
登入後複製
登入後複製

countryController 包含有關國家的主要業務邏輯。我們不會看到控制器的所有原始程式碼,因為它相當大,但我們將以創建控制器函數為例。

以下是國家模型:

import { Schema, model } from 'mongoose'
import * as env from '../config/env.config'

const countrySchema = new Schema<env.Country>(
  {
    values: {
      type: [Schema.Types.ObjectId],
      ref: 'LocationValue',
      required: [true, "can't be blank"],
      validate: (value: any): boolean => Array.isArray(value),
    },
  },
  {
    timestamps: true,
    strict: true,
    collection: 'Country',
  },
)

const Country = model<env.Country>('Country', countrySchema)

export default Country
登入後複製
登入後複製

下面是 env.Country TypeScript 類型:

export interface Country extends Document {
  values: Types.ObjectId[]
  name?: string
}
登入後複製

一個國家有多種價值觀。每種語言一個。預設支援英語和法語。

以下是 LocationValue 模型:

import 'dotenv/config'
import process from 'node:process'
import fs from 'node:fs/promises'
import http from 'node:http'
import https, { ServerOptions } from 'node:https'
import * as env from './config/env.config'
import * as databaseHelper from './common/databaseHelper'
import app from './app'
import * as logger from './common/logger'

if (
  await databaseHelper.connect(env.DB_URI, env.DB_SSL, env.DB_DEBUG)
  && await databaseHelper.initialize()
) {
  let server: http.Server | https.Server

  if (env.HTTPS) {
    https.globalAgent.maxSockets = Number.POSITIVE_INFINITY
    const privateKey = await fs.readFile(env.PRIVATE_KEY, 'utf8')
    const certificate = await fs.readFile(env.CERTIFICATE, 'utf8')
    const credentials: ServerOptions = { key: privateKey, cert: certificate }
    server = https.createServer(credentials, app)

    server.listen(env.PORT, () => {
      logger.info('HTTPS server is running on Port', env.PORT)
    })
  } else {
    server = app.listen(env.PORT, () => {
      logger.info('HTTP server is running on Port', env.PORT)
    })
  }

  const close = () => {
    logger.info('Gracefully stopping...')
    server.close(async () => {
      logger.info(`HTTP${env.HTTPS ? 'S' : ''} server closed`)
      await databaseHelper.close(true)
      logger.info('MongoDB connection closed')
      process.exit(0)
    })
  }

  ['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach((signal) => process.on(signal, close))
}
登入後複製
登入後複製

以下是 env.LocationValue TypeScript 類型:

import express from 'express'
import compression from 'compression'
import helmet from 'helmet'
import nocache from 'nocache'
import cookieParser from 'cookie-parser'
import i18n from './lang/i18n'
import * as env from './config/env.config'
import cors from './middlewares/cors'
import allowedMethods from './middlewares/allowedMethods'
import supplierRoutes from './routes/supplierRoutes'
import bookingRoutes from './routes/bookingRoutes'
import locationRoutes from './routes/locationRoutes'
import notificationRoutes from './routes/notificationRoutes'
import carRoutes from './routes/carRoutes'
import userRoutes from './routes/userRoutes'
import stripeRoutes from './routes/stripeRoutes'
import countryRoutes from './routes/countryRoutes'
import * as helper from './common/helper'

const app = express()

app.use(helmet.contentSecurityPolicy())
app.use(helmet.dnsPrefetchControl())
app.use(helmet.crossOriginEmbedderPolicy())
app.use(helmet.frameguard())
app.use(helmet.hidePoweredBy())
app.use(helmet.hsts())
app.use(helmet.ieNoOpen())
app.use(helmet.noSniff())
app.use(helmet.permittedCrossDomainPolicies())
app.use(helmet.referrerPolicy())
app.use(helmet.xssFilter())
app.use(helmet.originAgentCluster())
app.use(helmet.crossOriginResourcePolicy({ policy: 'cross-origin' }))
app.use(helmet.crossOriginOpenerPolicy())

app.use(nocache())
app.use(compression({ threshold: 0 }))
app.use(express.urlencoded({ limit: '50mb', extended: true }))
app.use(express.json({ limit: '50mb' }))

app.use(cors())
app.options('*', cors())
app.use(cookieParser(env.COOKIE_SECRET))
app.use(allowedMethods)

app.use('/', supplierRoutes)
app.use('/', bookingRoutes)
app.use('/', locationRoutes)
app.use('/', notificationRoutes)
app.use('/', carRoutes)
app.use('/', userRoutes)
app.use('/', stripeRoutes)
app.use('/', countryRoutes)

i18n.locale = env.DEFAULT_LANGUAGE

await helper.mkdir(env.CDN_USERS)
await helper.mkdir(env.CDN_TEMP_USERS)
await helper.mkdir(env.CDN_CARS)
await helper.mkdir(env.CDN_TEMP_CARS)
await helper.mkdir(env.CDN_LOCATIONS)
await helper.mkdir(env.CDN_TEMP_LOCATIONS)

export default app
登入後複製
登入後複製

LocationValue 具有語言代碼 (ISO 639-1) 和字串值。

以下是建立控制器函數:

import express from 'express'
import routeNames from '../config/countryRoutes.config'
import authJwt from '../middlewares/authJwt'
import * as countryController from '../controllers/countryController'

const routes = express.Router()

routes.route(routeNames.validate).post(authJwt.verifyToken, countryController.validate)
routes.route(routeNames.create).post(authJwt.verifyToken, countryController.create)
routes.route(routeNames.update).put(authJwt.verifyToken, countryController.update)
routes.route(routeNames.delete).delete(authJwt.verifyToken, countryController.deleteCountry)
routes.route(routeNames.getCountry).get(authJwt.verifyToken, countryController.getCountry)
routes.route(routeNames.getCountries).get(authJwt.verifyToken, countryController.getCountries)
routes.route(routeNames.getCountriesWithLocations).get(countryController.getCountriesWithLocations)
routes.route(routeNames.checkCountry).get(authJwt.verifyToken, countryController.checkCountry)
routes.route(routeNames.getCountryId).get(authJwt.verifyToken, countryController.getCountryId)

export default routes
登入後複製
登入後複製

在此函數中,我們檢索請求的正文,迭代正文中提供的值(每種語言一個值)並建立一個 LocationValue。最後,我們根據建立的位置值建立國家/地區。

前端

前端是一個使用 Node.js、React、MUI 和 TypeScript 建立的 Web 應用程式。在前端,客戶可以根據接送點和時間搜尋可用的汽車,選擇汽車並繼續結帳:

  • ./frontend/src/assets/ 資料夾包含 CSS 和圖片。
  • ./frontend/src/pages/ 資料夾包含 React 頁面。
  • ./frontend/src/components/ 資料夾包含 React 元件。
  • ./frontend/src/services/ 包含 api 客戶端服務。
  • ./frontend/src/App.tsx 是包含路由的主 React 應用程式。
  • ./frontend/src/index.tsx 是前端的主要入口點。

TypeScript 類型定義在套件 ./packages/bookcars-types 中定義。

App.tsx 是主要的 React 應用程式:

const routes = {
  validate: '/api/validate-country',
  create: '/api/create-country',
  update: '/api/update-country/:id',
  delete: '/api/delete-country/:id',
  getCountry: '/api/country/:id/:language',
  getCountries: '/api/countries/:page/:size/:language',
  getCountriesWithLocations: '/api/countries-with-locations/:language/:imageRequired/:minLocations',
  checkCountry: '/api/check-country/:id',
  getCountryId: '/api/country-id/:name/:language',
}

export default routes
登入後複製
登入後複製

我們使用 React 延遲載入來載入每個路由。

我們不會涵蓋前端的每一頁,但如果您願意,您可以打開原始程式碼並查看每一頁。

手機應用程式

該平台提供適用於 Android 和 iOS 的本機行動應用程式。這個行動應用程式是使用 React Native、Expo 和 TypeScript 建構的。與前端一樣,行動應用程式允許客戶根據接送點和時間搜尋可用的汽車,選擇汽車並繼續結帳。

如果他的預訂從後端更新,客戶會收到推播通知。推播通知是使用 Node.js、Expo Server SDK 和 Firebase 建構的。

  • ./mobile/assets/ 資料夾包含影像。
  • ./mobile/screens/ 資料夾包含主要的 React Native 螢幕。
  • ./mobile/components/ 資料夾包含 React Native 元件。
  • ./mobile/services/ 包含 api 客戶端服務。
  • ./mobile/App.tsx 是主要的 React Native 應用程式。

TypeScript 類型定義定義於:

  • ./mobile/types/index.d.ts
  • ./mobile/types/env.d.ts
  • ./mobile/miscellaneous/bookcarsTypes.ts

./mobile/types/ 載入到 ./mobile/tsconfig.json 中,如下所示:

import { Schema, model } from 'mongoose'
import * as env from '../config/env.config'

const countrySchema = new Schema<env.Country>(
  {
    values: {
      type: [Schema.Types.ObjectId],
      ref: 'LocationValue',
      required: [true, "can't be blank"],
      validate: (value: any): boolean => Array.isArray(value),
    },
  },
  {
    timestamps: true,
    strict: true,
    collection: 'Country',
  },
)

const Country = model<env.Country>('Country', countrySchema)

export default Country
登入後複製
登入後複製

App.tsx 是 React Native 應用程式的主要入口點:

導入'react-native-gesture-handler'
從 'react' 導入 React, { useCallback, useEffect, useRef, useState }
從 'react-native-root-siblings' 導入 { RootSiblingParent }
從'@react-navigation/native'導入{NavigationContainer,NavigationContainerRef}
從“expo-status-bar”導入 { StatusBar as ExpoStatusBar }
從 'react-native-safe-area-context' 導入 { SafeAreaProvider }
從 'react-native-paper' 導入 { Provider }
從“expo-splash-screen”導入 * as SplashScreen
導入 * 作為來自“expo-notifications”的通知
從 '@stripe/stripe-react-native' 導入 { StripeProvider }
從 './components/DrawerNavigator' 導入 DrawerNavigator
從 './common/helper' 導入 * 作為助手
從'./services/NotificationService'導入*作為NotificationService
從 './services/UserService' 導入 * 作為 UserService
從 './context/GlobalContext' 導入 { GlobalProvider }
從 './config/env.config' 導入 * 作為 env

通知.setNotificationHandler({
  handlerNotification: async() =>; ({
    應該會顯示警報:真,
    應該播放聲音:真,
    應該要設定徽章:真,
  }),
})

//
// 防止本機啟動畫面在應用程式元件宣告之前自動隱藏
//
SplashScreen.preventAutoHideAsync()
  .then((結果) => console.log(`SplashScreen.preventAutoHideAsync() 成功:${result}`))
  .catch(console.warn) // 最好明確捕獲並檢查任何錯誤

const App = () =>; {
  const [appIsReady, setAppIsReady] = useState(false)

  const responseListener = useRef<notifications.subscription>()
  const navigationRef = useRef<navigationcontainerref>>(null)

  useEffect(() => {
    const 暫存器 = async() => {
      const LoggedIn = 等待 UserService.loggedIn()
      如果(登入){
        const currentUser = 等待 UserService.getCurrentUser()
        if (目前使用者?._id) {
          等待 helper.registerPushToken(currentUser._id)
        } 別的 {
          helper.error()
        }
      }
    }

    //
    // 註冊推播通知令牌
    //
    登記()

    //
    // 每當使用者點擊通知或與通知互動時就會觸發此偵聽器(當應用程式處於前台、背景或終止時有效)
    //
    responseListener.current = Notifications.addNotificationResponseReceivedListener(async (response) => {
      嘗試 {
        如果(navigationRef.current){
          const { 資料 } = 回應.通知.請求.內容

          如果(資料.預訂){
            if (data.user && data.notification) {
              等待NotificationService.markAsRead(data.user, [data.notification])
            }
            navigationRef.current.navigate('預訂', { id: data.booking })
          } 別的 {
            navigationRef.current.navigate('通知', {})
          }
        }
      } 捕獲(錯誤){
        helper.error(錯誤,錯誤)
      }
    })

    返回() => {
      Notifications.removeNotificationSubscription(responseListener.current!)
    }
  }, [])

  setTimeout(() => {
    設定應用程式已就緒(true)
  }, 500)

  const onReady = useCallback(async () => {
    如果(應用程式已就緒){
      //
      // 這告訴啟動畫面立即隱藏!如果我們之後調用這個
      // `setAppIsReady`,那麼當應用程式運行時我們可能會看到一個空白螢幕
      // 載入其初始狀態並渲染其第一個像素。所以相反,
      // 一旦我們知道根視圖已經隱藏了啟動畫面
      // 執行佈局。
      //
      等待 SplashScreen.hideAsync()
    }
  }, [應用程式已就緒])

  如果(!appIsReady){
    傳回空值
  }

  返回 (
    
      
        
          <stripeproviderpublishablekey>;
            <rootsiblingparent>
              <navigationcontainer ref="{navigationRef}" onready="{onReady}">
                



<p>我們不會涵蓋行動應用程式的每個螢幕,但您可以根據需要打開原始程式碼並查看每個螢幕。 </p>

<h2>
  
  
  管理儀表板
</h2>

<p>管理儀表板是一個使用 Node.js、React、MUI 和 TypeScript 建立的 Web 應用程式。管理員可以從後端建立和管理供應商、汽車、位置、客戶和預訂。當從後端創建新的供應商時,他們將收到一封電子郵件,提示他們建立一個帳戶,以便訪問後端並管理他們的車隊和預訂。 </p>

<ul>
<li>./backend/assets/ 資料夾包含 CSS 和圖片。 </li>
<li>./backend/pages/ 資料夾包含 React 頁面。 </li>
<li>./backend/components/ 資料夾包含 React 元件。 </li>
<li>./backend/services/ 包含 api 客戶端服務。 </li>
<li>./backend/App.tsx 是包含路由的主要 React 應用程式。 </li>
<li>./backend/index.tsx 是管理儀表板的主要入口點。 </li>
</ul>

<p>TypeScript 類型定義在套件 ./packages/bookcars-types 中定義。 </p>

<p>後端的 App.tsx 與前端的 App.tsx 遵循類似的邏輯。 </p>

<p>我們不會涵蓋管理儀表板的每一頁,但您可以根據需要開啟原始程式碼並查看每一頁。 </p>

<h2>
  
  
  興趣點
</h2>

<p>使用 React Native 和 Expo 建立行動應用程式非常簡單。 Expo 讓使用 React Native 進行行動開發變得非常簡單。 </p>

<p>後端、前端和行動裝置開發使用同一種語言(TypeScript)非常方便。 </p>

<p>TypeScript 是一門非常有趣的語言,並且有很多優點。透過向 JavaScript 添加靜態類型,我們可以避免許多錯誤並產生高品質、可擴展、更具可讀性和可維護性的程式碼,並且易於調試和測試。 </p>

<p>就是這樣!我希望您喜歡閱讀這篇文章。 </p>
<h2>
  
  
  資源
</h2>

<ol>
<li>概述</li>
<li>建築</li>
<li>安裝(自架)</li>
<li>安裝(VPS)</li>
<li>
安裝(Docker)

<ol>
<li>Docker 映像</li>
<li>SSL</li>
</ol>


</li>

<li>設定條紋</li>

<li>建立行動應用程式</li>

<li>

演示資料庫

<ol>
<li>Windows、Linux 和 macOS</li>
<li>碼頭工人</li>
</ol>


</li>

<li>從源頭運行</li>

<li>

運行行動應用程式

<ol>
<li>先決條件</li>
<li>使用說明</li>
<li>推播通知</li>
</ol>


</li>

<li>更改貨幣</li>

<li>新增語言</li>

<li>單元測試和覆蓋率</li>

<li>日誌</li>

</ol>


          

            
        </navigationcontainer></rootsiblingparent></stripeproviderpublishablekey></navigationcontainerref></notifications.subscription>
登入後複製

以上是從零到店面:我建立汽車租賃網站和行動應用程式的旅程的詳細內容。更多資訊請關注PHP中文網其他相關文章!

來源:dev.to
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
作者最新文章
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板