实践 100 个 React 应用挑战 —— 06 个人资料 ——
首先
这篇文章是为了参与@Sicut_study的【React应用挑战百本】系列并进行输出而写的。
-
- 実装ルールや成果物の達成条件は元記事に従うものとします。
- 元記事との差別点として、具体的に自分がどんな実装を行ったのか(と必要に応じて解説)を記載します。
我的目标是跟随Sicut_study的100天React教程,并学习100天React。
这篇原文在这里。
上一篇文章
请解释这个事情
创建能够生成个人资料卡的服务
规则
引自原文文章
-
- 主要なライブラリやフレームワークはReactである必要がありますが、その他のツールやライブラリ(例: Redux, Next.js, Styled Componentsなど)を組み合わせて使用することは自由
-
- TypeScriptを利用する
- 要件をみたせばデザインなどは自由
达到要求
从原文中引用
-
- ユーザーが名前、誕生日、電話番号を入力し、プロフィール写真をアップロードできる
-
- アップロードされた画像がプレビューとして表示される
- ユーザーが生成したプロフィールカードをPNG形式でダウンロードできる
实施
本文将按照以下方针进行实施。
-
- 各UIパーツをコンポーネント化する
- ダウンロード機能の実装にはhtml2canvasとjspdfを活用する
程式碼如下所示。
/** @jsxImportSource @emotion/react */
import { css } from “@emotion/react”;
interface LabelProps {
htmlFor: string;
children: React.ReactNode;
}
const labelStyle = css`
margin-bottom: 5px;
font-weight: bold;
`;
const Label = ({ htmlFor, children }: LabelProps) => {
return (
);
};
export default Label;
src/components/Label.tsx
src/components/Label.tsx
/** @jsxImportSource @emotion/react */
import { css } from “@emotion/react”;
interface LabelProps {
htmlFor: string;
children: React.ReactNode;
}
const labelStyle = css`
margin-bottom: 5px;
font-weight: bold;
`;
const Label = ({ htmlFor, children }: LabelProps) => {
return (
);
};
export default Label;
/** @jsxImportSource @emotion/react */
import { css } from “@emotion/react”;
import Label from “./Label”;
interface InputFieldProps {
label: string;
type: string;
name: string;
value: string;
onChange: (e: React.ChangeEvent) => void;
}
const inputStyle = css`
width: 100%;
padding: 8px 12px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
&:focus {
border-color: #007bff;
outline: none;
}
`;
const InputField = ({
label,
type,
name,
value,
onChange,
}: InputFieldProps) => {
return (
);
};
export default InputField;
src/components/InputField.tsx
/** @jsxImportSource @emotion/react */
import { css } from “@emotion/react”;
import Label from “./Label”;
interface InputFieldProps {
label: string;
type: string;
name: string;
value: string;
onChange: (e: React.ChangeEvent) => void;
}
const inputStyle = css`
width: 100%;
padding: 8px 12px;
margin-bottom: 10px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
&:focus {
border-color: #007bff;
outline: none;
}
`;
const InputField = ({ label, type, name, value, onChange }: InputFieldProps) => {
return (
);
};
export default InputField;
/** @jsxImportSource @emotion/react */
import { useState } from “react”;
import { css } from “@emotion/react”;
import Label from “./Label”;
interface ImageUploadProps {
label: string;
onImageChange: (file: File | null) => void;
}
const containerStyle = css`
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: 20px;
`;
const ImageUpload = ({ label, onImageChange }: ImageUploadProps) => {
const [previewUrl, setPreviewUrl] = useState(“”);
const handleImageChange = (e: React.ChangeEvent) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
setPreviewUrl(URL.createObjectURL(file));
onImageChange(file);
} else {
onImageChange(null);
}
};
return (
);
};
export default ImageUpload;
src/components/ImageUpload.tsx的内容如下:
src/components/ImageUpload.tsx
/** @jsxImportSource @emotion/react */
import { useState } from “react”;
import { css } from “@emotion/react”;
import Label from “./Label”;
interface ImageUploadProps {
label: string;
onImageChange: (file: File | null) => void;
}
const containerStyle = css`
display: flex;
flex-direction: column;
align-items: flex-start;
margin-bottom: 20px;
`;
const ImageUpload = ({ label, onImageChange }: ImageUploadProps) => {
const [previewUrl, setPreviewUrl] = useState(“”);
const handleImageChange = (e: React.ChangeEvent) => {
if (e.target.files && e.target.files[0]) {
const file = e.target.files[0];
setPreviewUrl(URL.createObjectURL(file));
onImageChange(file);
} else {
onImageChange(null);
}
};
return (
);
};
export default ImageUpload;
/** @jsxImportSource @emotion/react */
import { css } from “@emotion/react”;
interface ProfileCardProps {
name: string;
birthday: string;
phoneNumber: string;
photo: string;
}
const cardStyle = css`
padding: 20px;
width: 100%;
`;
const photoContainerStyle = css`
display: flex;
justify-content: center;
width: 100%;
`;
const photoStyle = css`
width: 250px;
height: 250px;
border-radius: 50%;
object-fit: cover;
`;
const textStyle = css`
text-align: center;
margin: 10px 0;
`;
const ProfileCard = ({
name,
birthday,
phoneNumber,
photo,
}: ProfileCardProps) => {
return (
{name}
{birthday}
{phoneNumber}
);
};
export default ProfileCard;
/** @jsxImportSource @emotion/react */
import { useState } from “react”;
import { css } from “@emotion/react”;
import InputField from “./components/InputField”;
import ImageUpload from “./components/ImageUpload”;
import ProfileCard from “./components/ProfileCard”;
import html2canvas from “html2canvas”;
import jsPDF from “jspdf”;
interface UserProfile {
name: string;
birthday: string;
phoneNumber: string;
photo: string;
}
const appContainerStyle = css`
display: flex;
justify-content: center;
align-items: flex-start;
padding: 20px;
max-width: 900px;
margin: 0 auto;
`;
const formContainerStyle = css`
width: 35%;
margin-right: 20px;
`;
const cardContainerStyle = css`
width: 45%;
height: 400px;
display: flex;
justify-content: center;
align-items: center;
border: 1px solid #ddd;
border-radius: 8px;
overflow: hidden;
`;
const downloadButtonStyle = css`
background-color: #4caf50;
color: white;
padding: 10px 20px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
cursor: pointer;
border: none;
border-radius: 4px;
width: 100%;
`;
const App = () => {
const [userProfile, setUserProfile] = useState({
name: “”,
birthday: “”,
phoneNumber: “”,
photo: “”,
});
const handleInputChange = (e: React.ChangeEvent) => {
setUserProfile({ …userProfile, [e.target.name]: e.target.value });
};
const handleImageChange = (file: File | null) => {
if (file) {
setUserProfile({ …userProfile, photo: URL.createObjectURL(file) });
} else {
setUserProfile({ …userProfile, photo: “” });
}
};
const handleDownload = async () => {
const card = document.getElementById(“profile-card”);
if (card) {
const canvas = await html2canvas(card);
const imgData = canvas.toDataURL(“image/png”);
const pdf = new jsPDF();
const imgWidth = 210;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
pdf.addImage(imgData, “PNG”, 0, 0, imgWidth, imgHeight);
pdf.save(“profile-card.pdf”);
}
};
return (
);
};
export default App;
做好
完成的形式如下。
下载也能正常运行。
最后
随着时间的推移,实施变得越来越耗时。我在考虑下次是否引入UI库。
我将继续努力,以完成100次比赛为目标。希望能得到支持的朋友们能够关注我,非常欢迎。希望能获得点赞和收藏。
再见。
下一篇文章