內容
- 簡介
- 技術堆疊
- 快速概述
- API
- 前端
- 行動應用程式
- 管理儀表板
- 興趣點
- 資源
原始碼:https://github.com/aelassas/movinin
示範:https://movinin.dynv6.net:3004
介紹
這個想法源於建立無邊界的願望 - 一個完全可自訂和可操作的房地產租賃平台,其中每個方面都在您的控制之下:
- 擁有 UI/UX:設計獨特的客戶體驗,而無需克服模板限制
- 控制後端:實現完美匹配需求的自訂業務邏輯和資料結構
- 掌握 DevOps:使用首選工具和工作流程部署、擴展和監控應用程式
- 自由擴充:增加新功能和集成,無需平台限製或額外費用
技術堆疊
這是使其成為可能的技術堆疊:
- 打字稿
- Node.js
- MongoDB
- 反應
- MUI
- 世博會
- 條紋
- 碼頭工人
由於 TypeScript 具有眾多優點,因此做出了使用 TypeScript 的關鍵設計決定。 TypeScript 提供強大的類型、工具和集成,從而產生高品質、可擴展、更具可讀性和可維護性的程式碼,並且易於調試和測試。
我選擇React是因為它強大的渲染能力,MongoDB是為了靈活的資料建模,而Stripe是為了安全的支付處理。
選擇此堆疊,您不僅僅是在建立網站和行動應用程式 - 您正在投資一個可以根據您的需求不斷發展的基礎,並得到強大的開源技術和不斷發展的開發者社群的支持。
快速概覽
在本部分中,您將看到前端、管理儀表板和行動應用程式的主頁。
前端
在前端,客戶可以搜尋可用房產、選擇房產並結帳。
下面是前端的主頁,客戶可以在其中輸入位置點和時間,並搜尋可用的屬性。
以下是主頁的搜尋結果,客戶可以在其中選擇出租房產。
以下是客戶可以查看房產詳情的頁面:
以下是該物業的圖片視圖:
下面是結帳頁面,客戶可以在其中設定租賃選項和結帳。如果顧客未註冊,可以同時結帳和註冊。如果他尚未註冊,他將收到一封確認和啟動電子郵件以設定密碼。
以下是登入頁面。在生產中,身份驗證 cookie 是 httpOnly、簽署的、安全且嚴格的 sameSite。這些選項可防止 XSS、CSRF 和 MITM 攻擊。身份驗證 cookie 也可以透過自訂中間件免受 XST 攻擊。
以下是註冊頁面。
以下是客戶可以查看和管理他的預訂的頁面。
以下是客戶可以查看預訂詳細資訊的頁面。
以下是客戶可以看到他的通知的頁面。
以下是客戶可以管理其設定的頁面。
以下是客戶可以更改密碼的頁面。
就是這樣。這是前端的主要頁面。
管理儀表板
三類使用者:
- 管理員:他們擁有對管理儀表板的完全存取權。他們什麼都能做。
- 機構:他們對管理儀表板的存取權限有限。他們只能管理自己的財產、預訂和客戶。
- 客戶:他們只能存取前端和行動應用程式。他們無法存取管理儀表板。
該平台旨在與多個機構合作。每個機構都可以透過管理儀表板管理其財產、客戶和預訂。該平台也可以只與一個機構合作。
管理員可以從後端建立和管理代理商、飯店、地點、客戶和預訂。
建立新代理商時,他們會收到一封電子郵件,提示他們建立帳戶以存取管理儀表板,以便他們可以管理其財產、客戶和預訂。
以下是管理儀表板的登入頁面。
下面是儀表板頁面,管理員和代理商可以在其中查看和管理預訂。
如果預訂狀態發生變化,相關客戶將收到通知和電子郵件。
下面是顯示和管理屬性的頁面。
以下是管理員和機構可以透過提供圖像和房產資訊來建立新房產的頁面。如需免費取消,請將其設為 0。否則,請設定該選項的價格,或者如果您不想包含它,則將其留空。
以下是管理員和機構可以編輯屬性的頁面。
以下是管理員可以管理客戶的頁面。
如果代理商想要從管理儀表板建立預訂,以下是建立預訂的頁面。否則,當從前端或行動應用程式完成結帳流程時,會自動建立預訂。
以下是編輯預訂的頁面。
以下是管理代理商的頁面。
以下是建立新代理商的頁面。
以下是編輯機構的頁面。
以下是查看代理商屬性的頁面。
以下是查看客戶預訂的頁面。
以下是管理員和機構可以管理其設定的頁面。
還有其他頁面,但這些是管理儀表板的主頁。
就是這樣。這是管理儀表板的主要頁面。
應用程式介面
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 app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' 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 agencyRoutes from './routes/agencyRoutes' import bookingRoutes from './routes/bookingRoutes' import locationRoutes from './routes/locationRoutes' import notificationRoutes from './routes/notificationRoutes' import propertyRoutes from './routes/propertyRoutes' 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('/', agencyRoutes) app.use('/', bookingRoutes) app.use('/', locationRoutes) app.use('/', notificationRoutes) app.use('/', propertyRoutes) app.use('/', userRoutes) app.use('/', stripeRoutes) app.use('/', countryRoutes) i18n.locale = env.DEFAULT_LANGUAGE helper.mkdir(env.CDN_USERS) helper.mkdir(env.CDN_TEMP_USERS) helper.mkdir(env.CDN_PROPERTIES) helper.mkdir(env.CDN_TEMP_PROPERTIES) helper.mkdir(env.CDN_LOCATIONS) helper.mkdir(env.CDN_TEMP_LOCATIONS) export default app
首先,我們檢索 MongoDB 連接字串,然後與 MongoDB 資料庫建立連線。然後我們建立一個 Express 應用並載入 cors、compression、helmet 和 nocache 等中間件。我們使用頭盔中間件庫設置了各種安全措施。我們也為應用程式的不同部分匯入各種路由文件,例如:supplierRoutes、bookingRoutes、locationRoutes、notificationRoutes、propertyRoutes 和 userRoutes。最後,我們載入 Express 路線並匯出應用程式。
API中有8條路由。每條路線都有自己的控制器,遵循 MVC 設計模式和 SOLID 原則。主要路線如下:
- userRoutes:提供與使用者相關的REST功能
- agencyRoutes:提供與機構相關的REST功能
- countryRoutes:提供與國家相關的REST功能
- locationRoutes:提供與位置相關的REST函數
- propertyRoutes:提供與屬性相關的REST函數
- bookingRoutes:提供與預訂相關的REST功能
- notificationRoutes:提供與通知相關的REST功能
- stripeRoutes:提供與Stripe支付網關相關的REST功能
我們不會一一解釋每條路線。例如,我們將以 propertyRoutes 為例,看看它是如何製作的。您可以瀏覽原始程式碼並查看所有路由。
這是 propertyRoutes.ts:
import express from 'express' import multer from 'multer' import routeNames from '../config/propertyRoutes.config' import authJwt from '../middlewares/authJwt' import * as propertyController from '../controllers/propertyController' const routes = express.Router() routes.route(routeNames.create).post(authJwt.verifyToken, propertyController.create) routes.route(routeNames.update).put(authJwt.verifyToken, propertyController.update) routes.route(routeNames.checkProperty).get(authJwt.verifyToken, propertyController.checkProperty) routes.route(routeNames.delete).delete(authJwt.verifyToken, propertyController.deleteProperty) routes.route(routeNames.uploadImage).post([authJwt.verifyToken, multer({ storage: multer.memoryStorage() }).single('image')], propertyController.uploadImage) routes.route(routeNames.deleteImage).post(authJwt.verifyToken, propertyController.deleteImage) routes.route(routeNames.deleteTempImage).post(authJwt.verifyToken, propertyController.deleteTempImage) routes.route(routeNames.getProperty).get(propertyController.getProperty) routes.route(routeNames.getProperties).post(authJwt.verifyToken, propertyController.getProperties) routes.route(routeNames.getBookingProperties).post(authJwt.verifyToken, propertyController.getBookingProperties) routes.route(routeNames.getFrontendProperties).post(propertyController.getFrontendProperties) export default routes
首先,我們建立一個 Express Router。然後,我們使用名稱、方法、中間件和控制器來建立路由。
routeNames 包含 propertyRoutes 路由名稱:
const routes = { create: '/api/create-property', update: '/api/update-property', delete: '/api/delete-property/:id', uploadImage: '/api/upload-property-image', deleteTempImage: '/api/delete-temp-property-image/:fileName', deleteImage: '/api/delete-property-image/:property/:image', getProperty: '/api/property/:id/:language', getProperties: '/api/properties/:page/:size', getBookingProperties: '/api/booking-properties/:page/:size', getFrontendProperties: '/api/frontend-properties/:page/:size', checkProperty: '/api/check-property/:id', } export default routes
propertyController 包含有關位置的主要業務邏輯。我們不會看到控制器的所有原始程式碼,因為它相當大,但我們將以創建控制器函數為例。
以下是房產模型:
import { Schema, model } from 'mongoose' import * as movininTypes from ':movinin-types' import * as env from '../config/env.config' const propertySchema = new Schema<env.property>( { name: { type: String, required: [true, "can't be blank"], }, type: { type: String, enum: [ movininTypes.PropertyType.House, movininTypes.PropertyType.Apartment, movininTypes.PropertyType.Townhouse, movininTypes.PropertyType.Plot, movininTypes.PropertyType.Farm, movininTypes.PropertyType.Commercial, movininTypes.PropertyType.Industrial, ], required: [true, "can't be blank"], }, agency: { type: Schema.Types.ObjectId, required: [true, "can't be blank"], ref: 'User', index: true, }, description: { type: String, required: [true, "can't be blank"], }, available: { type: Boolean, default: true, }, image: { type: String, }, images: { type: [String], }, bedrooms: { type: Number, required: [true, "can't be blank"], validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, bathrooms: { type: Number, required: [true, "can't be blank"], validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, kitchens: { type: Number, default: 1, validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, parkingSpaces: { type: Number, default: 0, validate: { validator: Number.isInteger, message: '{VALUE} is not an integer value', }, }, size: { type: Number, }, petsAllowed: { type: Boolean, required: [true, "can't be blank"], }, furnished: { type: Boolean, required: [true, "can't be blank"], }, minimumAge: { type: Number, required: [true, "can't be blank"], min: env.MINIMUM_AGE, max: 99, }, location: { type: Schema.Types.ObjectId, ref: 'Location', required: [true, "can't be blank"], }, address: { type: String, }, price: { type: Number, required: [true, "can't be blank"], }, hidden: { type: Boolean, default: false, }, cancellation: { type: Number, default: 0, }, aircon: { type: Boolean, default: false, }, rentalTerm: { type: String, enum: [ movininTypes.RentalTerm.Monthly, movininTypes.RentalTerm.Weekly, movininTypes.RentalTerm.Daily, movininTypes.RentalTerm.Yearly, ], required: [true, "can't be blank"], }, }, { timestamps: true, strict: true, collection: 'Property', }, ) const Property = model<env.property>('Property', propertySchema) export default Property </env.property></env.property>
以下是房產類型:
export interface Property extends Document { name: string type: movininTypes.PropertyType agency: Types.ObjectId description: string image: string images?: string[] bedrooms: number bathrooms: number kitchens?: number parkingSpaces?: number, size?: number petsAllowed: boolean furnished: boolean minimumAge: number location: Types.ObjectId address?: string price: number hidden?: boolean cancellation?: number aircon?: boolean available?: boolean rentalTerm: movininTypes.RentalTerm }
屬性由以下部分組成:
- 名字
- A 類型(公寓、商業、農場、住宅、工業、地塊、聯排別墅)
- 創建它的機構的參考
- 描述
- 主圖
- 其他圖片
- 臥室數量
- 浴室數量
- 廚房數量
- 停車位數量
- A 尺寸
- 租最低年齡
- 地點
- 位址(可選)
- 價格
- 租賃期間(每月、每週、每日、每年)
- 取消價格(設定為0免費包含,不想包含則留空,或設定取消價格)
- 表示是否允許攜帶寵物的標誌
- 指示房產是否配備家具的標誌
- 指示屬性是否隱藏的標誌
- 指示空調是否可用的標誌
- 指示該房產是否可供出租的標誌
以下是建立控制器函數:
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 app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' 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、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/movinin-types 中定義。
App.tsx 是主要的 React 應用程式:
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 agencyRoutes from './routes/agencyRoutes' import bookingRoutes from './routes/bookingRoutes' import locationRoutes from './routes/locationRoutes' import notificationRoutes from './routes/notificationRoutes' import propertyRoutes from './routes/propertyRoutes' 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('/', agencyRoutes) app.use('/', bookingRoutes) app.use('/', locationRoutes) app.use('/', notificationRoutes) app.use('/', propertyRoutes) app.use('/', userRoutes) app.use('/', stripeRoutes) app.use('/', countryRoutes) i18n.locale = env.DEFAULT_LANGUAGE helper.mkdir(env.CDN_USERS) helper.mkdir(env.CDN_TEMP_USERS) helper.mkdir(env.CDN_PROPERTIES) helper.mkdir(env.CDN_TEMP_PROPERTIES) helper.mkdir(env.CDN_LOCATIONS) helper.mkdir(env.CDN_TEMP_LOCATIONS) export default app
我們使用 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/movininTypes.ts
./mobile/types/ 載入到 ./mobile/tsconfig.json 中,如下所示:
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 app from './app' import * as databaseHelper from './common/databaseHelper' import * as env from './config/env.config' 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)) }
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({ handleNotification: 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/movinin-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中文網其他相關文章!

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

MinGW - Minimalist GNU for Windows
這個專案正在遷移到osdn.net/projects/mingw的過程中,你可以繼續在那裡關注我們。 MinGW:GNU編譯器集合(GCC)的本機Windows移植版本,可自由分發的導入函式庫和用於建置本機Windows應用程式的頭檔;包括對MSVC執行時間的擴展,以支援C99功能。 MinGW的所有軟體都可以在64位元Windows平台上運作。

PhpStorm Mac 版本
最新(2018.2.1 )專業的PHP整合開發工具

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

Safe Exam Browser
Safe Exam Browser是一個安全的瀏覽器環境,安全地進行線上考試。該軟體將任何電腦變成一個安全的工作站。它控制對任何實用工具的訪問,並防止學生使用未經授權的資源。

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器