国产av日韩一区二区三区精品,成人性爱视频在线观看,国产,欧美,日韩,一区,www.成色av久久成人,2222eeee成人天堂

首頁(yè) web前端 js教程 從零到英雄:我建立房產(chǎn)租賃網(wǎng)站和移動(dòng)應(yīng)用程序的旅程

從零到英雄:我建立房產(chǎn)租賃網(wǎng)站和移動(dòng)應(yīng)用程序的旅程

Nov 11, 2024 pm 04:09 PM

內(nèi)容

  1. 簡(jiǎn)介
  2. 技術(shù)堆棧
  3. 快速概述
  4. 現(xiàn)場(chǎng)演示
  5. API
  6. 前端
  7. 移動(dòng)應(yīng)用程序
  8. 管理儀表板
  9. 興趣點(diǎn)
  10. 資源

源代碼:https://github.com/aelassas/movinin

演示:https://movinin.dynv6.net:3004

介紹

這個(gè)想法源于建立無(wú)邊界的愿望 - 一個(gè)完全可定制和可操作的房產(chǎn)租賃網(wǎng)站和移動(dòng)應(yīng)用程序,其中每個(gè)方面都在您的控制之下:

  • 擁有 UI/UX:設(shè)計(jì)獨(dú)特的客戶體驗(yàn),而無(wú)需克服模板限制
  • 控制后端:實(shí)現(xiàn)完美匹配需求的自定義業(yè)務(wù)邏輯和數(shù)據(jù)結(jié)構(gòu)
  • 掌握 DevOps:使用首選工具和工作流程部署、擴(kuò)展和監(jiān)控應(yīng)用程序
  • 自由擴(kuò)展:添加新功能和集成,無(wú)需平臺(tái)限制或額外費(fèi)用

技術(shù)要求:

  • 支付網(wǎng)關(guān)
    • 實(shí)施安全、國(guó)際支持的支付網(wǎng)關(guān)
    • 確??缍鄠€(gè)國(guó)家/地區(qū)和貨幣的兼容性
  • DevOps
    • 使用 Docker 容器進(jìn)行部署以實(shí)現(xiàn)一致性和可擴(kuò)展性
    • 托管在最小的基礎(chǔ)設(shè)施上(1GB RAM 服務(wù)器)
    • 使用 Hetzner 或 DigitalOcean 等提供商將每月托管成本維持在 5 美元以下
    • 優(yōu)化資源使用以實(shí)現(xiàn)高效運(yùn)營(yíng)

技術(shù)堆棧

這是使其成為可能的技術(shù)堆棧:

  • 打字稿
  • Node.js
  • MongoDB
  • 反應(yīng)
  • MUI
  • React Native
  • 世博會(huì)
  • 條紋
  • 碼頭工人

由于 TypeScript 具有眾多優(yōu)點(diǎn),因此做出了使用 TypeScript 的關(guān)鍵設(shè)計(jì)決定。 TypeScript 提供強(qiáng)大的類(lèi)型、工具和集成,從而產(chǎn)生高質(zhì)量、可擴(kuò)展、更具可讀性和可維護(hù)性的代碼,并且易于調(diào)試和測(cè)試。

我選擇React是因?yàn)樗鼜?qiáng)大的渲染能力,MongoDB是為了靈活的數(shù)據(jù)建模,而Stripe是為了安全的支付處理。

通過(guò)選擇此堆棧,您不僅僅是在構(gòu)建網(wǎng)站和移動(dòng)應(yīng)用程序 - 您正在投資一個(gè)可以根據(jù)您的需求不斷發(fā)展的基礎(chǔ),并得到強(qiáng)大的開(kāi)源技術(shù)和不斷發(fā)展的開(kāi)發(fā)者社區(qū)的支持。

React 因其以下優(yōu)點(diǎn)而成為絕佳選擇:

  1. 基于組件的架構(gòu)
    • 讓您將復(fù)雜的 UI 分解為更小的、可重復(fù)使用的部分
    • 使代碼更易于維護(hù)且更易于測(cè)試
    • 實(shí)現(xiàn)更好的代碼組織和可重用性
  2. 虛擬 DOM 性能
    • React 的虛擬 DOM 高效地僅更新必要的內(nèi)容
    • 帶來(lái)更快的頁(yè)面加載和更好的用戶體驗(yàn)
    • 減少不必要的重新渲染
  3. 豐富的生態(tài)系統(tǒng)
    • 龐大的預(yù)建組件庫(kù)
    • 豐富的工具
    • 提供支持和資源的大型社區(qū)
  4. 豐富的開(kāi)發(fā)人員經(jīng)驗(yàn)
    • 熱重載以獲取即時(shí)反饋
    • 優(yōu)秀的調(diào)試工具
    • JSX 讓編寫(xiě) UI 代碼更加直觀
  5. 行業(yè)支持
    • 由 Meta(以前的 Facebook)支持
    • 被許多大公司使用
    • 持續(xù)開(kāi)發(fā)和改進(jìn)
  6. 靈活性
    • 適用于小型和大型應(yīng)用程序
    • 可以逐步集成到現(xiàn)有項(xiàng)目中
    • 支持多種渲染策略(客戶端、服務(wù)端、靜態(tài))

快速概覽

在本部分中,您將看到前端、管理儀表板和移動(dòng)應(yīng)用程序的主頁(yè)。

前端

在前端,客戶可以搜索可用房產(chǎn)、選擇房產(chǎn)并結(jié)賬。

下面是前端的主頁(yè),客戶可以在其中輸入位置點(diǎn)和時(shí)間,并搜索可用的屬性。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是主頁(yè)的搜索結(jié)果,客戶可以在其中選擇出租房產(chǎn)。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是客戶可以查看房產(chǎn)詳情的頁(yè)面:

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是該物業(yè)的圖片視圖:

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

下面是結(jié)帳頁(yè)面,客戶可以在其中設(shè)置租賃選項(xiàng)和結(jié)帳。如果顧客未注冊(cè),可以同時(shí)結(jié)賬和注冊(cè)。如果他尚未注冊(cè),他將收到一封確認(rèn)和激活電子郵件以設(shè)置密碼。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是登錄頁(yè)面。在生產(chǎn)中,身份驗(yàn)證 cookie 是 httpOnly、簽名的、安全且嚴(yán)格的 sameSite。這些選項(xiàng)可防止 XSS、CSRF 和 MITM 攻擊。身份驗(yàn)證 cookie 也可以通過(guò)自定義中間件免受 XST 攻擊。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是注冊(cè)頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

下面是客戶可以查看和管理他的預(yù)訂的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是客戶可以查看預(yù)訂詳細(xì)信息的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

下面是客戶可以看到他的通知的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

下面是客戶可以管理其設(shè)置的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是客戶可以更改密碼的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

就是這樣。這是前端的主要頁(yè)面。

管理儀表板

三類(lèi)用戶:

  • 管理員:他們擁有對(duì)管理儀表板的完全訪問(wèn)權(quán)限。他們什么都能做。
  • 機(jī)構(gòu):他們對(duì)管理儀表板的訪問(wèn)權(quán)限有限。他們只能管理自己的財(cái)產(chǎn)、預(yù)訂和客戶。
  • 客戶:他們只能訪問(wèn)前端和移動(dòng)應(yīng)用程序。他們無(wú)法訪問(wèn)管理儀表板。

該平臺(tái)旨在與多個(gè)機(jī)構(gòu)合作。每個(gè)機(jī)構(gòu)都可以通過(guò)管理儀表板管理其財(cái)產(chǎn)、客戶和預(yù)訂。該平臺(tái)也可以只與一個(gè)機(jī)構(gòu)合作。

管理員可以從后端創(chuàng)建和管理代理機(jī)構(gòu)、酒店、地點(diǎn)、客戶和預(yù)訂。

創(chuàng)建新代理機(jī)構(gòu)時(shí),他們會(huì)收到一封電子郵件,提示他們創(chuàng)建帳戶以訪問(wèn)管理儀表板,以便他們可以管理其財(cái)產(chǎn)、客戶和預(yù)訂。

下面是管理儀表板的登錄頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

下面是儀表板頁(yè)面,管理員和代理機(jī)構(gòu)可以在其中查看和管理預(yù)訂。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

如果預(yù)訂狀態(tài)發(fā)生變化,相關(guān)客戶將收到通知和電子郵件。

下面是顯示和管理屬性的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

下面是管理員和機(jī)構(gòu)可以通過(guò)提供圖像和房產(chǎn)信息來(lái)創(chuàng)建新房產(chǎn)的頁(yè)面。如需免費(fèi)取消,請(qǐng)將其設(shè)置為 0。否則,請(qǐng)?jiān)O(shè)置該選項(xiàng)的價(jià)格,或者如果您不想包含它,則將其留空。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

下面是管理員和機(jī)構(gòu)可以編輯屬性的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是管理員可以管理客戶的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

如果代理機(jī)構(gòu)想要從管理儀表板創(chuàng)建預(yù)訂,下面是創(chuàng)建預(yù)訂的頁(yè)面。否則,當(dāng)從前端或移動(dòng)應(yīng)用程序完成結(jié)帳過(guò)程時(shí),會(huì)自動(dòng)創(chuàng)建預(yù)訂。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是編輯預(yù)訂的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是管理代理機(jī)構(gòu)的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是創(chuàng)建新代理機(jī)構(gòu)的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是編輯機(jī)構(gòu)的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是查看代理機(jī)構(gòu)屬性的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

以下是查看客戶預(yù)訂的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

下面是管理員和機(jī)構(gòu)可以管理其設(shè)置的頁(yè)面。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

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

就是這樣。這是管理儀表板的主要頁(yè)面。

現(xiàn)場(chǎng)演示

前端

  • 網(wǎng)址:https://movinin.dynv6.net:3004/
  • 登錄:jdoe@movinin.io
  • 密碼:M00vinin

管理儀表板

  • 網(wǎng)址:https://movinin.dynv6.net:3003/
  • 登錄:admin@movinin.io
  • 密碼:M00vinin

手機(jī)應(yīng)用程序

您可以在任何 Android 設(shè)備上安裝 Android 應(yīng)用程序。

使用設(shè)備掃描此代碼

打開(kāi)相機(jī)應(yīng)用程序并將其指向此代碼。然后點(diǎn)擊出現(xiàn)的通知。

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

如何在 Android 上安裝移動(dòng)應(yīng)用程序

  • 在運(yùn)行 Android 8.0(API 級(jí)別 26)及更高版本的設(shè)備上,您必須導(dǎo)航到“安裝未知應(yīng)用程序”系統(tǒng)設(shè)置屏幕,才能從特定位置(即您下載應(yīng)用程序的網(wǎng)絡(luò)瀏覽器)啟用應(yīng)用程序安裝.

  • 在運(yùn)行 Android 7.1.1(API 級(jí)別 25)及更低版本的設(shè)備上,您應(yīng)該啟用“未知來(lái)源”系統(tǒng)設(shè)置,可在“設(shè)置”>“設(shè)置”中找到。您設(shè)備的安全性。

另類(lèi)方式

您還可以通過(guò)直接下載APK并將其安裝在任何Android設(shè)備上來(lái)安裝Android應(yīng)用程序。

  • 下載APK
  • 登錄:jdoe@movinin.io
  • 密碼:M00vinin

應(yīng)用程序編程接口

From Zero to Hero: My Journey Building a Property Rental Website and Mobile App

API 公開(kāi)了管理儀表板、前端和移動(dòng)應(yīng)用程序所需的所有功能。 API遵循MVC設(shè)計(jì)模式。 JWT 用于身份驗(yàn)證。有些功能需要身份驗(yàn)證,例如與管理屬性、預(yù)訂和客戶相關(guān)的功能,而其他功能則不需要身份驗(yàn)證,例如檢索未經(jīng)身份驗(yàn)證的用戶的位置和可用屬性:

  • ./api/src/models/ 文件夾包含 MongoDB 模型。
  • ./api/src/routes/ 文件夾包含 Express 路線。
  • ./api/src/controllers/ 文件夾包含控制器。
  • ./api/src/middlewares/ 文件夾包含中間件。
  • ./api/src/config/env.config.ts 包含配置和 TypeScript 類(lèi)型定義。
  • ./api/src/lang/ 文件夾包含本地化內(nèi)容。
  • ./api/src/app.ts 是加載路由的主服務(wù)器。
  • ./api/index.ts 是 API 的主要入口點(diǎn)。

index.ts 是 API 的主要入口點(diǎn):

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))
}

這是一個(gè)使用 Node.js 和 Express 啟動(dòng)服務(wù)器的 TypeScript 文件。它導(dǎo)入了多個(gè)模塊,包括 dotenv、process、fs、http、https、mongoose 和 app。然后,它檢查 HTTPS 環(huán)境變量是否設(shè)置為 true,如果是,則使用 https 模塊以及提供的私鑰和證書(shū)創(chuàng)建 HTTPS 服務(wù)器。否則,它使用 http 模塊創(chuàng)建一個(gè) HTTP 服務(wù)器。服務(wù)器監(jiān)聽(tīng) PORT 環(huán)境變量中指定的端口。

close 函數(shù)被定義為在收到終止信號(hào)時(shí)優(yōu)雅地停止服務(wù)器。它關(guān)閉服務(wù)器和 MongoDB 連接,然后以狀態(tài)碼 0 退出進(jìn)程。最后,它注冊(cè)當(dāng)進(jìn)程收到 SIGINT、SIGTERM 或 SIGQUIT 信號(hào)時(shí)要調(diào)用的 close 函數(shù)。

app.ts 是 api 的主要入口點(diǎn):

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 數(shù)據(jù)庫(kù)建立連接。然后我們創(chuàng)建一個(gè) Express 應(yīng)用并加載 cors、compression、helmet 和 nocache 等中間件。我們使用頭盔中間件庫(kù)設(shè)置了各種安全措施。我們還為應(yīng)用程序的不同部分導(dǎo)入各種路由文件,例如:supplierRoutes、bookingRoutes、locationRoutes、notificationRoutes、propertyRoutes 和 userRoutes。最后,我們加載 Express 路線并導(dǎo)出應(yīng)用程序。

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

  • userRoutes:提供與用戶相關(guān)的REST功能
  • agencyRoutes:提供與機(jī)構(gòu)相關(guān)的REST功能
  • countryRoutes:提供與國(guó)家相關(guān)的REST功能
  • locationRoutes:提供與位置相關(guān)的REST函數(shù)
  • propertyRoutes:提供與屬性相關(guān)的REST函數(shù)
  • bookingRoutes:提供與預(yù)訂相關(guān)的REST功能
  • notificationRoutes:提供與通知相關(guān)的REST功能
  • stripeRoutes:提供與Stripe支付網(wǎng)關(guān)相關(guān)的REST功能

我們不會(huì)一一解釋每條路線。例如,我們將以 propertyRoutes 為例,看看它是如何制作的。您可以瀏覽源代碼并查看所有路由。

這是 propertyRoutes.ts:

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))
}

首先,我們創(chuàng)建一個(gè) Express Router。然后,我們使用名稱、方法、中間件和控制器創(chuàng)建路由。

routeNames 包含 propertyRoutes 路由名稱:

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

propertyController 包含有關(guān)位置的主要業(yè)務(wù)邏輯。我們不會(huì)看到控制器的所有源代碼,因?yàn)樗喈?dāng)大,但我們將以創(chuàng)建控制器函數(shù)為例。

以下是房產(chǎn)模型:

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

以下是房產(chǎn)類(lèi)型:

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

屬性由以下部分組成:

  • 名字
  • A 類(lèi)型(公寓、商業(yè)、農(nóng)場(chǎng)、住宅、工業(yè)、地塊、聯(lián)排別墅)
  • 創(chuàng)建它的機(jī)構(gòu)的參考
  • 描述
  • 主圖
  • 其他圖片
  • 臥室數(shù)量
  • 浴室數(shù)量
  • 廚房數(shù)量
  • 停車(chē)位數(shù)量
  • A 尺寸
  • 租賃最低年齡
  • 地點(diǎn)
  • 地址(可選)
  • 價(jià)格
  • 租賃期限(每月、每周、每日、每年)
  • 取消價(jià)格(設(shè)置為0免費(fèi)包含,不想包含則留空,或者設(shè)置取消價(jià)格)
  • 表示是否允許攜帶寵物的標(biāo)志
  • 指示房產(chǎn)是否配備家具的標(biāo)志
  • 指示屬性是否隱藏的標(biāo)志
  • 指示空調(diào)是否可用的標(biāo)志
  • 指示該房產(chǎn)是否可供出租的標(biāo)志

下面是創(chuàng)建控制器函數(shù):

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

前端

前端是一個(gè)使用 Node.js、React、MUI 和 TypeScript 構(gòu)建的 Web 應(yīng)用程序。在前端,客戶可以根據(jù)接送點(diǎn)和時(shí)間搜索可用的汽車(chē),選擇汽車(chē)并繼續(xù)結(jié)賬:

  • ./frontend/src/assets/ 文件夾包含 CSS 和圖像。
  • ./frontend/src/pages/ 文件夾包含 React 頁(yè)面。
  • ./frontend/src/components/ 文件夾包含 React 組件。
  • ./frontend/src/services/ 包含 api 客戶端服務(wù)。
  • ./frontend/src/App.tsx 是包含路由的主要 React 應(yīng)用程序。
  • ./frontend/src/index.tsx 是前端的主要入口點(diǎn)。

TypeScript 類(lèi)型定義在包 ./packages/movinin-types 中定義。

App.tsx 是主要的 React 應(yīng)用程序:

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))
}

我們使用 React 延遲加載來(lái)加載每個(gè)路由。

我們不會(huì)涵蓋前端的每一頁(yè),但您可以瀏覽源代碼并查看每一頁(yè)。

手機(jī)應(yīng)用程序

該平臺(tái)提供適用于 Android 和 iOS 的本機(jī)移動(dòng)應(yīng)用程序。該移動(dòng)應(yīng)用程序是使用 React Native、Expo 和 TypeScript 構(gòu)建的。與前端一樣,移動(dòng)應(yīng)用程序允許客戶根據(jù)接送點(diǎn)和時(shí)間搜索可用的汽車(chē),選擇汽車(chē)并繼續(xù)結(jié)賬。

如果他的預(yù)訂從后端更新,客戶會(huì)收到推送通知。推送通知是使用 Node.js、Expo Server SDK 和 Firebase 構(gòu)建的。

  • ./mobile/assets/ 文件夾包含圖像。
  • ./mobile/screens/ 文件夾包含主要的 React Native 屏幕。
  • ./mobile/components/ 文件夾包含 React Native 組件。
  • ./mobile/services/ 包含 api 客戶端服務(wù)。
  • ./mobile/App.tsx 是主要的 React Native 應(yīng)用程序。

TypeScript 類(lèi)型定義定義于:

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

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

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

App.tsx 是 React Native 應(yīng)用程序的主要入口點(diǎn):

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

通知.setNotificationHandler({
  handleNotification: async() =>; ({
    應(yīng)該顯示警報(bào):真,
    應(yīng)該播放聲音:真,
    應(yīng)該設(shè)置徽章:真,
  }),
})

//
// 防止本機(jī)啟動(dòng)畫(huà)面在應(yīng)用程序組件聲明之前自動(dòng)隱藏
//
SplashScreen.preventAutoHideAsync()
  .then((結(jié)果) => console.log(`SplashScreen.preventAutoHideAsync() 成功:${result}`))
  .catch(console.warn) // 最好顯式捕獲并檢查任何錯(cuò)誤

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 (當(dāng)前用戶?._id) {
          等待 helper.registerPushToken(currentUser._id)
        } 別的 {
          helper.error()
        }
      }
    }

    //
    // 注冊(cè)推送通知令牌
    //
    登記()

    //
    // 每當(dāng)用戶點(diǎn)擊通知或與通知交互時(shí)就會(huì)觸發(fā)此偵聽(tīng)器(當(dāng)應(yīng)用程序處于前臺(tái)、后臺(tái)或終止時(shí)有效)
    //
    responseListener.current = Notifications.addNotificationResponseReceivedListener(async (response) => {
      嘗試 {
        如果(navigationRef.current){
          const { 數(shù)據(jù) } = 響應(yīng).通知.請(qǐng)求.內(nèi)容

          如果(數(shù)據(jù).預(yù)訂){
            if (data.user && data.notification) {
              等待NotificationService.markAsRead(data.user, [data.notification])
            }
            navigationRef.current.navigate('預(yù)訂', { id: data.booking })
          } 別的 {
            navigationRef.current.navigate('通知', {})
          }
        }
      } 捕獲(錯(cuò)誤){
        helper.error(錯(cuò)誤,錯(cuò)誤)
      }
    })

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

  setTimeout(() => {
    設(shè)置應(yīng)用程序已就緒(true)
  }, 500)

  const onReady = useCallback(async () => {
    如果(應(yīng)用程序已就緒){
      //
      // 這告訴啟動(dòng)屏幕立即隱藏!如果我們之后調(diào)用這個(gè)
      // `setAppIsReady`,那么當(dāng)應(yīng)用程序運(yùn)行時(shí)我們可能會(huì)看到一個(gè)空白屏幕
      // 加載其初始狀態(tài)并渲染其第一個(gè)像素。所以相反,
      // 一旦我們知道根視圖已經(jīng)隱藏了啟動(dòng)屏幕
      // 執(zhí)行布局。
      //
      等待 SplashScreen.hideAsync()
    }
  }, [應(yīng)用程序已就緒])

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

  返回 (
    <全球供應(yīng)商>
      
        <提供商>
          <stripeproviderpublishablekey>;
            <rootsiblingparent>
              <NavigationContainer ref={navigationRef} onReady={onReady}>
                



<p>我們不會(huì)涵蓋移動(dòng)應(yīng)用程序的每個(gè)屏幕,但您可以瀏覽源代碼并查看每個(gè)屏幕。</p>

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

<p>管理儀表板是一個(gè)使用 Node.js、React、MUI 和 TypeScript 構(gòu)建的 Web 應(yīng)用程序。管理員可以從后端創(chuàng)建和管理供應(yīng)商、汽車(chē)、位置、客戶和預(yù)訂。當(dāng)從后端創(chuàng)建新的供應(yīng)商時(shí),他們將收到一封電子郵件,提示他們創(chuàng)建一個(gè)帳戶,以便訪問(wèn)管理儀表板并管理他們的車(chē)隊(duì)和預(yù)訂。</p>

<ul>
<li>./backend/assets/ 文件夾包含 CSS 和圖像。</li>
<li>./backend/pages/ 文件夾包含 React 頁(yè)面。</li>
<li>./backend/components/ 文件夾包含 React 組件。</li>
<li>./backend/services/ 包含 api 客戶端服務(wù)。</li>
<li>./backend/App.tsx 是包含路由的主要 React 應(yīng)用程序。</li>
<li>./backend/index.tsx 是管理儀表板的主要入口點(diǎn)。</li>
</ul>

<p>TypeScript 類(lèi)型定義在包 ./packages/movinin-types 中定義。</p>

<p>管理儀表板的 App.tsx 遵循與前端的 App.tsx 類(lèi)似的邏輯。</p>

<p>我們不會(huì)涵蓋管理儀表板的每一頁(yè),但您可以瀏覽源代碼并查看每一頁(yè)。</p>

<h2>
  
  
  興趣點(diǎn)
</h2>

<p>使用 React Native 和 Expo 構(gòu)建移動(dòng)應(yīng)用程序非常簡(jiǎn)單。 Expo 讓使用 React Native 進(jìn)行移動(dòng)開(kāi)發(fā)變得非常簡(jiǎn)單。</p>

<p>后端、前端和移動(dòng)端開(kāi)發(fā)使用同一種語(yǔ)言(TypeScript)非常方便。</p>

<p>TypeScript 是一門(mén)非常有趣的語(yǔ)言,并且有很多優(yōu)點(diǎn)。通過(guò)向 JavaScript 添加靜態(tài)類(lèi)型,我們可以避免許多錯(cuò)誤并生成高質(zhì)量、可擴(kuò)展、更具可讀性和可維護(hù)性的代碼,并且易于調(diào)試和測(cè)試。</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>設(shè)置條紋</li>

<li>構(gòu)建移動(dòng)應(yīng)用程序</li>

<li>

演示數(shù)據(jù)庫(kù)

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


</li>

<li>從源頭運(yùn)行</li>

<li>

運(yùn)行移動(dòng)應(yīng)用程序

<ol>
<li>先決條件</li>
<li>使用說(shuō)明</li>
<li>推送通知</li>
</ol>


</li>

<li>更改貨幣</li>

<li>添加新語(yǔ)言</li>

<li>單元測(cè)試和覆蓋率</li>

<li>日志</li>

</ol>


          

            
        </rootsiblingparent></stripeproviderpublishablekey></navigationcontainerref></notifications.subscription>

以上是從零到英雄:我建立房產(chǎn)租賃網(wǎng)站和移動(dòng)應(yīng)用程序的旅程的詳細(xì)內(nèi)容。更多信息請(qǐng)關(guān)注PHP中文網(wǎng)其他相關(guān)文章!

本站聲明
本文內(nèi)容由網(wǎng)友自發(fā)貢獻(xiàn),版權(quán)歸原作者所有,本站不承擔(dān)相應(yīng)法律責(zé)任。如您發(fā)現(xiàn)有涉嫌抄襲侵權(quán)的內(nèi)容,請(qǐng)聯(lián)系admin@php.cn

熱AI工具

Undress AI Tool

Undress AI Tool

免費(fèi)脫衣服圖片

Undresser.AI Undress

Undresser.AI Undress

人工智能驅(qū)動(dòng)的應(yīng)用程序,用于創(chuàng)建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用于從照片中去除衣服的在線人工智能工具。

Clothoff.io

Clothoff.io

AI脫衣機(jī)

Video Face Swap

Video Face Swap

使用我們完全免費(fèi)的人工智能換臉工具輕松在任何視頻中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費(fèi)的代碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

功能強(qiáng)大的PHP集成開(kāi)發(fā)環(huán)境

Dreamweaver CS6

Dreamweaver CS6

視覺(jué)化網(wǎng)頁(yè)開(kāi)發(fā)工具

SublimeText3 Mac版

SublimeText3 Mac版

神級(jí)代碼編輯軟件(SublimeText3)

熱門(mén)話題

Java vs. JavaScript:清除混亂 Java vs. JavaScript:清除混亂 Jun 20, 2025 am 12:27 AM

Java和JavaScript是不同的編程語(yǔ)言,各自適用于不同的應(yīng)用場(chǎng)景。Java用于大型企業(yè)和移動(dòng)應(yīng)用開(kāi)發(fā),而JavaScript主要用于網(wǎng)頁(yè)開(kāi)發(fā)。

JavaScript評(píng)論:簡(jiǎn)短說(shuō)明 JavaScript評(píng)論:簡(jiǎn)短說(shuō)明 Jun 19, 2025 am 12:40 AM

JavascriptconcommentsenceenceEncorenceEnterential gransimenting,reading and guidingCodeeXecution.1)單inecommentsareusedforquickexplanations.2)多l(xiāng)inecommentsexplaincomplexlogicorprovideDocumentation.3)

如何在JS中與日期和時(shí)間合作? 如何在JS中與日期和時(shí)間合作? Jul 01, 2025 am 01:27 AM

JavaScript中的日期和時(shí)間處理需注意以下幾點(diǎn):1.創(chuàng)建Date對(duì)象有多種方式,推薦使用ISO格式字符串以保證兼容性;2.獲取和設(shè)置時(shí)間信息可用get和set方法,注意月份從0開(kāi)始;3.手動(dòng)格式化日期需拼接字符串,也可使用第三方庫(kù);4.處理時(shí)區(qū)問(wèn)題建議使用支持時(shí)區(qū)的庫(kù),如Luxon。掌握這些要點(diǎn)能有效避免常見(jiàn)錯(cuò)誤。

為什么要將標(biāo)簽放在的底部? 為什么要將標(biāo)簽放在的底部? Jul 02, 2025 am 01:22 AM

PlacingtagsatthebottomofablogpostorwebpageservespracticalpurposesforSEO,userexperience,anddesign.1.IthelpswithSEObyallowingsearchenginestoaccesskeyword-relevanttagswithoutclutteringthemaincontent.2.Itimprovesuserexperiencebykeepingthefocusonthearticl

JavaScript與Java:開(kāi)發(fā)人員的全面比較 JavaScript與Java:開(kāi)發(fā)人員的全面比較 Jun 20, 2025 am 12:21 AM

JavaScriptIspreferredforredforwebdevelverment,而Javaisbetterforlarge-ScalebackendsystystemsandSandAndRoidApps.1)JavascriptexcelcelsincreatingInteractiveWebexperienceswebexperienceswithitswithitsdynamicnnamicnnamicnnamicnnamicnemicnemicnemicnemicnemicnemicnemicnemicnddommanipulation.2)

JavaScript:探索用于高效編碼的數(shù)據(jù)類(lèi)型 JavaScript:探索用于高效編碼的數(shù)據(jù)類(lèi)型 Jun 20, 2025 am 12:46 AM

javascripthassevenfundaMentalDatatypes:數(shù)字,弦,布爾值,未定義,null,object和symbol.1)numberSeadUble-eaduble-ecisionFormat,forwidevaluerangesbutbecautious.2)

什么是在DOM中冒泡和捕獲的事件? 什么是在DOM中冒泡和捕獲的事件? Jul 02, 2025 am 01:19 AM

事件捕獲和冒泡是DOM中事件傳播的兩個(gè)階段,捕獲是從頂層向下到目標(biāo)元素,冒泡是從目標(biāo)元素向上傳播到頂層。1.事件捕獲通過(guò)addEventListener的useCapture參數(shù)設(shè)為true實(shí)現(xiàn);2.事件冒泡是默認(rèn)行為,useCapture設(shè)為false或省略;3.可使用event.stopPropagation()阻止事件傳播;4.冒泡支持事件委托,提高動(dòng)態(tài)內(nèi)容處理效率;5.捕獲可用于提前攔截事件,如日志記錄或錯(cuò)誤處理。了解這兩個(gè)階段有助于精確控制JavaScript響應(yīng)用戶操作的時(shí)機(jī)和方式。

Java和JavaScript有什么區(qū)別? Java和JavaScript有什么區(qū)別? Jun 17, 2025 am 09:17 AM

Java和JavaScript是不同的編程語(yǔ)言。1.Java是靜態(tài)類(lèi)型、編譯型語(yǔ)言,適用于企業(yè)應(yīng)用和大型系統(tǒng)。2.JavaScript是動(dòng)態(tài)類(lèi)型、解釋型語(yǔ)言,主要用于網(wǎng)頁(yè)交互和前端開(kāi)發(fā)。

See all articles