NASA项目一些关键代码展示
2026年1月17日 09:08
client 文件夹下
结构
![]()
app.js
import {
BrowserRouter as Router,
} from "react-router-dom";
import {
Arwes,
SoundsProvider,
ThemeProvider,
createSounds,
createTheme,
} from "arwes";
import AppLayout from "./pages/AppLayout";
import { theme, resources, sounds } from "./settings";
const App = () => {
return <ThemeProvider theme={createTheme(theme)}>
<SoundsProvider sounds={createSounds(sounds)}>
<Arwes animate background={resources.background.large} pattern={resources.pattern}>
{anim => (
<Router>
<AppLayout show={anim.entered} />
</Router>
)}
</Arwes>
</SoundsProvider>
</ThemeProvider>;
};
export default App;
setting.js
const resources = {
background: {
small: "/img/background-small.jpg",
medium: "/img/background-medium.jpg",
large: "/img/background-large.jpg",
},
pattern: "/img/glow.png",
};
const sounds = {
shared: {
volume: 0.5,
},
players: {
click: {
sound: { src: ["/sound/click.mp3"] },
settings: { oneAtATime: true }
},
typing: {
sound: { src: ["/sound/typing.mp3"] },
settings: { oneAtATime: true }
},
deploy: {
sound: { src: ["/sound/deploy.mp3"] },
settings: { oneAtATime: true }
},
success: {
sound: {
src: ["/sound/success.mp3"],
volume: 0.2,
},
settings: { oneAtATime: true }
},
abort: {
sound: { src: ["/sound/abort.mp3"] },
settings: { oneAtATime: true }
},
warning: {
sound: { src: ["/sound/warning.mp3"] },
settings: { oneAtATime: true }
},
}
};
const theme = {
color: {
content: "#a1ecfb",
},
padding: 20,
responsive: {
small: 600,
medium: 800,
large: 1200
},
typography: {
headerFontFamily: '"Titillium Web", "sans-serif"',
},
};
export {
resources,
sounds,
theme,
};
pages/Launch.js
import { useMemo } from "react";
import { Appear, Button, Loading, Paragraph } from "arwes";
import Clickable from "../components/Clickable";
const Launch = props => {
const selectorBody = useMemo(() => {
return props.planets?.map(planet =>
<option value={planet.keplerName} key={planet.keplerName}>{planet.keplerName}</option>
);
}, [props.planets]);
const today = new Date().toISOString().split("T")[0];
return <Appear id="launch" animate show={props.entered}>
<Paragraph>Schedule a mission launch for interstellar travel to one of the Kepler Exoplanets.</Paragraph>
<Paragraph>Only confirmed planets matching the following criteria are available for the earliest scheduled missions:</Paragraph>
<ul>
<li>Planetary radius < 1.6 times Earth's radius</li>
<li>Effective stellar flux > 0.36 times Earth's value and < 1.11 times Earth's value</li>
</ul>
<form onSubmit={props.submitLaunch} style={{display: "inline-grid", gridTemplateColumns: "auto auto", gridGap: "10px 20px"}}>
<label htmlFor="launch-day">Launch Date</label>
<input type="date" id="launch-day" name="launch-day" min={today} max="2040-12-31" defaultValue={today} />
<label htmlFor="mission-name">Mission Name</label>
<input type="text" id="mission-name" name="mission-name" />
<label htmlFor="rocket-name">Rocket Type</label>
<input type="text" id="rocket-name" name="rocket-name" defaultValue="Explorer IS1" />
<label htmlFor="planets-selector">Destination Exoplanet</label>
<select id="planets-selector" name="planets-selector">
{selectorBody}
</select>
<Clickable>
<Button animate
show={props.entered}
type="submit"
layer="success"
disabled={props.isPendingLaunch}>
Launch Mission ✔
</Button>
</Clickable>
{props.isPendingLaunch &&
<Loading animate small />
}
</form>
</Appear>
};
export default Launch;
pages/AppLayout.js
import {
useState,
} from "react";
import {
Switch,
Route,
} from "react-router-dom";
import {
Frame,
withSounds,
withStyles,
} from "arwes";
import usePlanets from "../hooks/usePlanets";
import useLaunches from "../hooks/useLaunches";
import Centered from "../components/Centered";
import Header from "../components/Header";
import Footer from "../components/Footer";
import Launch from "./Launch";
import History from "./History";
import Upcoming from "./Upcoming";
const styles = () => ({
content: {
display: "flex",
flexDirection: "column",
height: "100vh",
margin: "auto",
},
centered: {
flex: 1,
paddingTop: "20px",
paddingBottom: "10px",
},
});
const AppLayout = props => {
const { sounds, classes } = props;
const [frameVisible, setFrameVisible] = useState(true);
const animateFrame = () => {
setFrameVisible(false);
setTimeout(() => {
setFrameVisible(true);
}, 600);
};
const onSuccessSound = () => sounds.success && sounds.success.play();
const onAbortSound = () => sounds.abort && sounds.abort.play();
const onFailureSound = () => sounds.warning && sounds.warning.play();
const {
launches,
isPendingLaunch,
submitLaunch,
abortLaunch,
} = useLaunches(onSuccessSound, onAbortSound, onFailureSound);
const planets = usePlanets();
return <div className={classes.content}>
<Header onNav={animateFrame} />
<Centered className={classes.centered}>
<Frame animate
show={frameVisible}
corners={4}
style={{visibility: frameVisible ? "visible" : "hidden"}}>
{anim => (
<div style={{padding: "20px"}}>
<Switch>
<Route exact path="/">
<Launch
entered={anim.entered}
planets={planets}
submitLaunch={submitLaunch}
isPendingLaunch={isPendingLaunch} />
</Route>
<Route exact path="/launch">
<Launch
entered={anim.entered}
planets={planets}
submitLaunch={submitLaunch}
isPendingLaunch={isPendingLaunch} />
</Route>
<Route exact path="/upcoming">
<Upcoming
entered={anim.entered}
launches={launches}
abortLaunch={abortLaunch} />
</Route>
<Route exact path="/history">
<History entered={anim.entered} launches={launches} />
</Route>
</Switch>
</div>
)}
</Frame>
</Centered>
<Footer />
</div>;
};
export default withSounds()(withStyles(styles)(AppLayout));
pages/History.js
import { useMemo } from "react";
import { Appear, Table, Paragraph } from "arwes";
const History = props => {
const tableBody = useMemo(() => {
return props.launches?.filter((launch) => !launch.upcoming)
.map((launch) => {
return <tr key={String(launch.flightNumber)}>
<td>
<span style={
{color: launch.success ? "greenyellow" : "red"}
}>█</span>
</td>
<td>{launch.flightNumber}</td>
<td>{new Date(launch.launchDate).toDateString()}</td>
<td>{launch.mission}</td>
<td>{launch.rocket}</td>
<td>{launch.customers?.join(", ")}</td>
</tr>;
});
}, [props.launches]);
return <article id="history">
<Appear animate show={props.entered}>
<Paragraph>History of mission launches including SpaceX launches starting from the year 2006.</Paragraph>
<Table animate>
<table style={{tableLayout: "fixed"}}>
<thead>
<tr>
<th style={{width: "2rem"}}></th>
<th style={{width: "3rem"}}>No.</th>
<th style={{width: "9rem"}}>Date</th>
<th>Mission</th>
<th style={{width: "7rem"}}>Rocket</th>
<th>Customers</th>
</tr>
</thead>
<tbody>
{tableBody}
</tbody>
</table>
</Table>
</Appear>
</article>;
}
export default History;
pages/Upcoming.js
import { useMemo } from "react";
import {
withStyles,
Appear,
Link,
Paragraph,
Table,
Words,
} from "arwes";
import Clickable from "../components/Clickable";
const styles = () => ({
link: {
color: "red",
textDecoration: "none",
},
});
const Upcoming = props => {
const {
entered,
launches,
classes,
abortLaunch,
} = props;
const tableBody = useMemo(() => {
return launches?.filter((launch) => launch.upcoming)
.map((launch) => {
return <tr key={String(launch.flightNumber)}>
<td>
<Clickable style={{color:"red"}}>
<Link className={classes.link} onClick={() => abortLaunch(launch.flightNumber)}>
✖
</Link>
</Clickable>
</td>
<td>{launch.flightNumber}</td>
<td>{new Date(launch.launchDate).toDateString()}</td>
<td>{launch.mission}</td>
<td>{launch.rocket}</td>
<td>{launch.target}</td>
</tr>;
});
}, [launches, abortLaunch, classes.link]);
return <Appear id="upcoming" animate show={entered}>
<Paragraph>Upcoming missions including both SpaceX launches and newly scheduled Zero to Mastery rockets.</Paragraph>
<Words animate>Warning! Clicking on the ✖ aborts the mission.</Words>
<Table animate show={entered}>
<table style={{tableLayout: "fixed"}}>
<thead>
<tr>
<th style={{width: "3rem"}}></th>
<th style={{width: "3rem"}}>No.</th>
<th style={{width: "10rem"}}>Date</th>
<th style={{width: "11rem"}}>Mission</th>
<th style={{width: "11rem"}}>Rocket</th>
<th>Destination</th>
</tr>
</thead>
<tbody>
{tableBody}
</tbody>
</table>
</Table>
</Appear>;
}
export default withStyles(styles)(Upcoming);
hooks/usePlanets.js
import { useCallback, useEffect, useState } from "react";
import { httpGetPlanets } from "./requests";
function usePlanets() {
const [planets, savePlanets] = useState([]);
const getPlanets = useCallback(async () => {
const fetchedPlanets = await httpGetPlanets();
savePlanets(fetchedPlanets);
}, []);
useEffect(() => {
getPlanets();
}, [getPlanets]);
return planets;
}
export default usePlanets;
其他可以看仓库
server 文件夹下
![]()
app.js
const express = require('express')
const cors = require('cors');
const path = require('path');
const morgan = require('morgan');
const api = require('./routes/api')
const app = express()
// 日志记录位置尽量 早
app.use(morgan('combined'))
app.use(cors({
origin: 'http://localhost:3000',
}));
app.use(express.json())
app.use(express.static(path.join(__dirname, '..', 'public')))
app.use('/v1', api)
// 确保第一页打开就是 index.html 内容
app.get('/*', (req, res) => {
res.sendFile(path.join(__dirname, '..','public','index.html'));
} )
module.exports = app
server.js
const http = require('http');
require('dotenv').config()
const {mongoConnect} = require('./services/mongo')
const app = require('./app')
const {loadPlanetsData} = require('./models/planets.model')
const {loadLaunchData} = require('./models/launches.model')
const PORT = process.env.PORT || 8000
const server = http.createServer(app)
async function startServer() {
await mongoConnect()
await loadPlanetsData()
await loadLaunchData()
server.listen(PORT,() => {
console.log(`Listening on ${PORT}`);
});
}
startServer()
models/launches.model.js
const axios = require('axios');
const launchesDatabase = require('./launches.mongo')
const planets = require('./planets.mongo')
const DEFAULT_FLIGHT_NUMBER = 100
const launch = {
flightNumber: 100, // flight_number
mission: 'Kepler Exploration X', // name
rocket: 'Explorer IS1', // rocket.name
launchDate: new Date('December 27, 2030'), // date_local
target: 'Kepler-442 b', // not applicable
customers:['ZTM','NASA'], // payloads.customers for each payload
upcoming:true, // upcoming
success: true // success
}
saveLaunch(launch)
async function findLaunch(filter){
return await launchesDatabase.findOne(filter)
}
async function existsLaunchWithId(launchId) {
return await launchesDatabase.findOne({
flightNumber: launchId
})
}
async function getLatestFlightNumber(){
// findOne()用于查找匹配查询条件的第一条记录
// sort('-flightNumber')用于按照flightNumber字段降序排列结果
const latestLaunch = await launchesDatabase.findOne().sort('-flightNumber')
if(!latestLaunch) return DEFAULT_FLIGHT_NUMBER
return latestLaunch.flightNumber
}
async function getAllLaunches(skip, limit) {
return await launchesDatabase
.find(
{},{
"_id":0,
"__v":0
})
.skip(skip)
.limit(limit)
}
async function saveLaunch(launch) {
await launchesDatabase.findOneAndUpdate({
flightNumber: launch.flightNumber,
}, launch, {
upsert: true,
})
}
async function scheduleNewLaunch(launch) {
const planet = await planets.findOne({
keplerName: launch.target
})
if(!planet){
throw new Error('Not matching planet found')
}
const newFlightNumber = await getLatestFlightNumber() + 1
const newLaunch = Object.assign(launch, {
success: true,
upcoming: true,
customers:['ZTM','NASA'],
flightNumber: newFlightNumber
})
await saveLaunch(newLaunch)
}
async function abortLaunchById(launchId) {
const aborted = await launchesDatabase.updateOne({
flightNumber: launchId
},{
upcoming: false,
success: false,
})
return aborted.modifiedCount === 1
// const aborted = launches.get(launchId)
// aborted.success = false
// aborted.upcoming = false
// return aborted
}
async function populateLaunches(){
const response = await axios.post(SPACEX_API_URL,{
query: {},
options:{
// 不分页 拿到所有数据
pagination:false,
populate:[
{
path: 'rocket',
select:{
name:1
}
},
{
path: 'payloads',
select:{
customers:1
}
}
]
}
})
const launchDocs = response.data.docs
for(const launchDoc of launchDocs){
const payloads = launchDoc.payloads
// 使用 flatMap 将嵌套数组扁平化
const customers = payloads.flatMap(payload => payload.customers)
const launch = {
flightNumber: launchDoc.flight_number,
mission:launchDoc.name,
rocket: launchDoc.rocket.name,
launchDate: launchDoc.date_local,
customers,
upcoming: launchDoc.upcoming,
success: launchDoc.success
}
// console.log('launch',`${launch.flightNumber} ${launch.mission}`);
await saveLaunch(launch);
}
if(response.status !== 200) {
console.log('Problem downloading launch data');
}
}
const SPACEX_API_URL = 'https://api.spacexdata.com/v4/launches/query'
async function loadLaunchData(){
const firstLaunch = await findLaunch({
flightNumber:1,
rocket:'Falcon 1',
mission:'FalconSat'
})
if(firstLaunch){
console.log('Launch data already loaded');
}else{
await populateLaunches()
}
}
module.exports = {
getAllLaunches,
scheduleNewLaunch,
existsLaunchWithId,
abortLaunchById,
loadLaunchData,
}
routes/launches/launches.router.js
const express = require('express');
const {httpGetAllLaunch, httpAddLaunch, httpAbortLaunch} = require('./launches.controller')
const launchesRouter = express.Router();
launchesRouter.get('/', httpGetAllLaunch);
launchesRouter.post('/', httpAddLaunch);
launchesRouter.delete('/:id', httpAbortLaunch);
module.exports = launchesRouter;
仓库