前端框架:React 18 + TypeScript(含Vite构建工具)
后端架构:NestJS 10(Node.js 20 LTS)
数据库:PostgreSQL 15 + Redis 7
地图服务:Mapbox GL JS 3.0
部署环境:Docker Compose + Kubernetes
bash
复制
下载
# Node环境安装(以nvm为例) curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash nvm install 20 nvm use 20 # PostgreSQL容器部署 docker run --name文旅数据库 -e POSTGRES_PASSWORD=yourpassword -d postgres:15-alpine # 创建项目目录结构 mkdir cultural-travel-app cd cultural-travel-app npx create-react-app frontend --template typescript npx @nestjs/cli new backend
sql
复制
下载
-- 创建文旅点空间数据表 CREATE TABLE poi ( id SERIAL PRIMARY KEY, name VARCHAR(255), description TEXT, location GEOGRAPHY(Point,4326), cultural_properties JSONB ); -- 创建空间索引 CREATE INDEX poi_location_idx ON poi USING GIST(location);
typescript
复制
下载
// 后端服务层实现 @Injectable() export class RecommendationService { async getRecommendations(userId: string, location: Point) { const culturalFactors = await this.getCulturalPreferences(userId); return this.spatialQuery(location, culturalFactors); } private spatialQuery(center: Point, filters: CulturalFilter) { return this.entityManager.query(` SELECT *, ST_Distance(location, ST_SetSRID(ST_MakePoint($1,$2),4326)) as distance FROM poi WHERE cultural_properties @> $3 ORDER BY distance LIMIT 20 `, [center.x, center.y, filters]); } }
tsx
复制
下载
// 前端实时聊天组件 const TravelChat = ({ poiId }) => { const [messages, setMessages] = useState([]); const ws = useRef (); useEffect(() => { ws.current = new WebSocket(`wss://api.example.com/chat/${poiId}`); ws.current.onmessage = (event) => { const msg = JSON.parse(event.data); setMessages(prev => [...prev, msg]); }; return () => ws.current?.close(); }, [poiId]); const sendMessage = (text: string) => { ws.current?.send(JSON.stringify({ userId: currentUser.id, content: text, timestamp: new Date().toISOString() })); }; return {/* 聊天界面实现 */}; };
javascript
复制
下载
// 使用Douglas-Peucker算法简化轨迹 function simplifyPath(points, tolerance=0.0001) { let maxDist = 0; let index = 0; for(let i=1; imaxDist) { maxDist = dist; index = i; } } if(maxDist > tolerance) { return [ ...simplifyPath(points.slice(0, index+1), tolerance), ...simplifyPath(points.slice(index), tolerance).slice(1) ]; } return [points[0], points[points.length-1]]; }
yaml
复制
下载
# 文化属性元数据标准 cultural_properties: era: type: enum values: [史前, 古代, 近代, 现代] category: type: multi-select options: [建筑, 遗址, 民俗, 艺术] protection_level: type: number min: 1 max: 5 authenticity_score: type: float precision: 0.1
typescript
复制
下载
// E2E测试示例(使用Cypress) describe('文旅点搜索流程', () => { it('应正确显示搜索结果', () => { cy.intercept('GET', '/api/poi*').as('searchRequest'); cy.visit('/'); cy.get('.search-input').type('古城遗址'); cy.get('.search-button').click(); cy.wait('@searchRequest').then((interception) => { expect(interception.request.query).to.include({ q: '古城遗址', range: '5000' }); }); cy.get('.result-item').should('have.length.at.least', 3); }); });
dockerfile
复制
下载
# 前端Dockerfile示例 FROM node:20-alpine as builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM nginx:alpine COPY --from=builder /app/build /usr/share/nginx/html COPY nginx.conf /etc/nginx/conf.d/default.conf
javascript
复制
下载
// 虚拟滚动优化长列表 const VirtualList = ({ items, itemHeight, renderItem }) => { const [scrollTop, setScrollTop] = useState(0); const containerHeight = useRef(0); const handleScroll = (e) => { setScrollTop(e.target.scrollTop); }; const startIdx = Math.floor(scrollTop / itemHeight); const endIdx = Math.min( items.length - 1, startIdx + Math.ceil(containerHeight.current / itemHeight) ); return ({ if(el) containerHeight.current = el.clientHeight; }} >); };{items.slice(startIdx, endIdx).map((item, i) => ({renderItem(item)}))}
(完整实现需包含:用户认证系统、文化标签体系、智能推荐引擎、AR导航模块、UGC内容审核系统等20+核心模块,每个模块提供可运行的代码示例与架构图)