个人网站开发记录-整体建站思路

4,414 阅读8分钟

初版demo效果

在线访问,欢迎提交友链

image.png

背景

很久以来我都在找一个能够聚合个人生活、工作、分享的网站,但是一直没找到合适的,所以一直打算自己写一个,但是工作时间太长且强度比较大,就一直没开始,后面cursor出现之后,我借助ai编辑器快速实现了第一版demo,可以在线访问,但ai实现的有较多的bug,会在后续会手动去优化并整理开发文档,开源出去

思路

我主要是想要是能够当一个:

  • 在线简历:能让别人知道我,知道我在做什么,知道我会什么,说不定有人看上这个网站,能免试呢,主要涉及简介、教育经历、工作经历、技术文档列表、技术栈
  • 灵感笔记:脑袋里总是经常蹦出一些想法,不记录下来很快就忘了,这是一个类似发消息的页面
  • 在线相册:我比较喜欢记录生活,经常用手机拍摄一些图片,最近刚买了相机,在学习,所以我需要一个瀑布流的相册
  • 工作空间:电子爱好者,上面会列举一些我正在用的产品
  • 导航站:记录书签,根据分类列出平时收集的网站工具等等,后续需要支持导入书签,导出书签,接入ai搜索给建议
  • 时间轴:用于记录大事件
  • 项目:自己写的项目,或者是参与过的一些开源项目
  • demo:记录平时写的demo动画,组件什么的,通过gif动态展示
  • 友链:交朋友,欢迎提交友链

系列文章

技术栈

nextjs+tailwind+pnpm+ts+mongodb+ai

详细介绍

首页&简介

基本功能

  • 主要展示社交链接,网站信息,教育经历,工作经历

  • 社交链接跟侧边栏同步

  • 信息存储在后端

  • 自动计算工作时间

  • 展示发布文章列表可以跳转文章详情

image.png

image-20250210151824139

数据模型

网站信息数据模型

import { Schema, model, models } from 'mongoose';
import { IEducation } from "./education";

export interface ISite {
  createdAt: Date;           // 网站创建时间
  visitCount: number;        // 访问人数
  likeCount: number;         // 点赞数量
  favicon: string;           // 网站图标链接
  qrcode: string;           // 二维码图片链接
  appreciationCode: string;  // 赞赏码图片链接
  wechatGroup: string;      // 微信公众号图片链接
  title: string;            // 网站标题
  description: string;      // 网站描述
  backgroundImage: string;   // 首页背景图链接
  author: {                 // 作者信息
    name: string;
    avatar: string;
    description: string;    // 作者一句话描述
    bio: string;           // 作者详细介绍
    education: IEducation[];  // 教育经历
  };
  icp?: string;           // 备案信息
  seo: {                  // SEO相关信息
    keywords: string[];
    description: string;
  };
}

const siteSchema = new Schema<ISite>({
  createdAt: { type: Date, default: Date.now },
  visitCount: { type: Number, default: 0 },
  likeCount: { type: Number, default: 0 },
  favicon: { type: String, required: true },
  qrcode: { type: String, required: true },
  appreciationCode: { type: String, required: true },
  wechatGroup: { type: String, required: true },
  title: { type: String, required: true },
  description: { type: String, required: true },
  backgroundImage: { type: String, required: true },
  author: {
    name: { type: String, required: true },
    avatar: { type: String, required: true },
    description: { type: String, required: true },
    bio: { type: String, required: true },
    education: [{ type: Schema.Types.ObjectId, ref: 'Education' }]
  },
  icp: String,
  seo: {
    keywords: [String],
    description: { type: String, required: true },
    ogImage: String
  },
});

export const Site = models.Site || model<ISite>('Site', siteSchema);

社交链接数据模型

import mongoose, { Schema, Document } from 'mongoose';

export interface ISocialLinkBase {
  name: string;
  icon: string;
  url: string;
  bgColor: string;
}

export interface ISocialLink extends Document, ISocialLinkBase {}

const socialLinkSchema = new Schema<ISocialLink>({
  name: { type: String, required: true },
  icon: { type: String, required: true },
  url: { type: String, required: true },
  bgColor: { type: String, required: true }
}, {
  timestamps: true
});

export const SocialLink = mongoose.models.SocialLink || mongoose.model<ISocialLink>('SocialLink', socialLinkSchema);

工作经历数据模型

import mongoose, { Schema, Document } from "mongoose";

export interface IWorkExperienceBase {
  _id?: string;
  company: string;
  companyUrl: string;
  position: string;
  description: string;
  startDate: string;
  endDate: string | null; // null means current position
}

export interface IWorkExperience extends Document, IWorkExperienceBase {
  _id: string;
}

const workExperienceSchema = new Schema<IWorkExperience>(
  {
    company: { type: String, required: true },
    companyUrl: { type: String, required: true },
    position: { type: String, required: true },
    description: { type: String, required: true },
    startDate: { type: String, required: true },
    endDate: { type: String, default: null },
  },
  {
    timestamps: true,
  }
);

export const WorkExperience =
  mongoose.models.WorkExperience ||
  mongoose.model<IWorkExperience>("WorkExperience", workExperienceSchema);

教育经历数据模型

export interface IEducation {
  school: string;     // 学校名称
  major: string;      // 专业
  degree: string;     // 学位
  certifications?: string[];  // 证书,如 CET6 等
  startDate: string;  // 入学时间
  endDate: string;    // 毕业时间
}

技术栈

基本功能

展示已经接触过的技术,跳转官网 | 技术文档

image-20250210152619636

数据模型

import mongoose, { Schema, Document } from 'mongoose';

export interface IStack extends Document {
  title: string;
  description: string;
  link: string;
  iconSrc: string;
}

const stackSchema = new Schema<IStack>({
  title: { type: String, required: true },
  description: { type: String, required: true },
  link: { type: String, required: true },
  iconSrc: { type: String, required: true }
}, {
  timestamps: true
});

export const Stack = mongoose.models.Stack || mongoose.model<IStack>('Stack', stackSchema);

灵感笔记

基本功能

ui类似微信聊天界面,能够展示气泡信息就行,支持快速发送灵感,支持嵌入b站视频,图片等。

image-20250210152813740

image-20250210152851523

数据模型

import { ObjectId } from "mongodb";

export interface IInspiration {
  _id?: ObjectId;
  title: string;
  content: string;
  images?: string[]; // 图片URL数组
  createdAt: Date;
  updatedAt: Date;
  likes: number;
  views: number;
  bilibili?: {
    bvid: string;      // B站视频的BV号
    title?: string;    // 视频标题
    cover?: string;    // 视频封面图片URL
    page?: number;     // 视频分P号,默认为1
  };
  links?: {
    title: string;
    url: string;
    icon?: string; // 可选的链接图标
  }[];
  tags?: string[]; // 可选的标签
  status: "draft" | "published"; // 草稿或已发布状态
}

export interface IInspirationCreate
  extends Omit<
    IInspiration,
    "_id" | "createdAt" | "updatedAt" | "likes" | "views"
  > {
  // 创建时不需要的字段都被省略
}

export interface IInspirationUpdate
  extends Partial<Omit<IInspiration, "_id" | "createdAt" | "updatedAt">> {
  // 更新时所有字段都是可选的
}

// 用于前端展示的灵感笔记类型
export interface InspirationDisplay
  extends Omit<IInspiration, "_id" | "createdAt" | "updatedAt"> {
  _id: string; // ObjectId 转为字符串
  createdAt: string; // Date 转为字符串
  updatedAt: string; // Date 转为字符串
}

// 数据库中的灵感笔记类型
export type InspirationDocument = IInspiration & {
  _id: ObjectId;
};

// 用于查询的过滤器类型
export interface InspirationFilter {
  status?: "draft" | "published";
  tags?: string[];
  createdAt?: {
    $gte?: Date;
    $lte?: Date;
  };
  searchText?: string; // 用于搜索标题和内容
}

技术文章

基本功能

可根据分类展示文章列表,跳转文章详情,交互式文档实现,可在网站运行自己写的组件等,mdx就可以,但是我想自己看看这个得实现思路,就自己写了个。

在线体验交互式md

image-20250210153136646

image-20250210153155760

image-20250210153236597

数据模型

export interface Article {
  // MongoDB ID
  _id?: string;

  // 文章标题
  title: string;

  // 文章链接(slug)
  url?: string;

  // 文章分类
  category?: string;

  // 文章分类ID
  categoryId?: string;

  // 文章标签(可以有多个)
  tags?: string[];

  // 文章内容(Markdown格式)
  content: string;

  // OSS存储路径
  ossPath: string;

  // 文章状态(draft-草稿/published-已发布)
  status: ArticleStatus;

  // 文章摘要
  summary?: string;

  // 封面图片URL
  coverImage?: string;

  // 点赞数
  likes?: number;

  // 阅读数
  views?: number;

  // 创建时间
  createdAt: string;

  // 更新时间(可选)
  updatedAt?: string;
}

// 文章状态枚举
export enum ArticleStatus {
  DRAFT = 'draft',
  PUBLISHED = 'published'
}

// 文章分类接口
export interface ArticleCategory {
  _id?: string;
  name: string;
  description?: string;
  createdAt: string;
  updatedAt: string;
}

生活相册

基本功能

通过瀑布流展示我平时拍摄的图片,支持预览,切图,喜欢摄影的带带弟弟,刚开始

image.png

image.png

数据模型

import mongoose, { Schema, Document } from "mongoose";
import { ObjectId } from "mongodb";

export interface IPhoto {
  src: string;
  width: number;
  height: number;
  title: string;
  location: string;
  date: string;
}

export interface IPhotoDB extends Omit<IPhoto, "_id"> {
  _id: ObjectId;
  createdAt: Date;
  updatedAt: Date;
}

const photoSchema = new Schema<IPhotoDB>(
  {
    src: { type: String, required: true },
    width: { type: Number, required: true },
    height: { type: Number, required: true },
    title: { type: String, required: true },
    location: { type: String, required: true },
    date: { type: String, required: true },
  },
  {
    timestamps: true,
  }
);

export const Photo =
  mongoose.models.Photo || mongoose.model<IPhotoDB>("Photo", photoSchema);

工作空间

基本功能

分享展示一些自己正在使用的电子产品

image.png

数据模型

import mongoose, { Schema, Document } from "mongoose";

export interface IWorkspaceItem extends Document {
  // _id?: string;  // 使用可选的 _id
  product: string;
  specs: string;
  buyAddress: string;
  buyLink: string;
}

const workspaceItemSchema = new Schema<IWorkspaceItem>(
  {
    product: { type: String, required: true },
    specs: { type: String, required: true },
    buyAddress: { type: String, required: true },
    buyLink: { type: String, required: true },
  },
  {
    timestamps: true,
  }
);

export const WorkspaceItem =
  mongoose.models.WorkspaceItem ||
  mongoose.model<IWorkspaceItem>("WorkspaceItem", workspaceItemSchema);

导航站

基本功能

根据分类记录网站,写了一个网站截图的服务,不需要上传图片,根据链接截取图片,后续会增加订阅 & ai搜索建议

image-20250210153726414

数据模型

import { ObjectId } from 'mongodb';

// API interfaces (for frontend use)
export interface IBookmark {
  _id?: ObjectId;
  title: string;
  url: string;
  description: string;
  imageUrl?: string;
  categoryId: ObjectId;
  createdAt: Date;
  updatedAt: Date;
}

export interface IBookmarkCategory {
  _id?: ObjectId;
  name: string;
  bookmarks: IBookmark[];
  createdAt: Date;
  updatedAt: Date;
}

// Database interfaces (for MongoDB)
export interface IBookmarkDB extends Omit<IBookmark, 'categoryId'> {
  categoryId: ObjectId;
}

export interface IBookmarkCategoryDB extends Omit<IBookmarkCategory, 'bookmarks'> {
  bookmarks: ObjectId[];
}

时间轴

基本功能

用来记录一些大事件,支持嵌入推特推文

image.png

数据模型

import mongoose, { Schema, Document } from 'mongoose';
import { ObjectId } from 'mongodb';

export interface ITimelineLink {
  text: string;
  url: string;
}

export interface ITimelineEvent {
  _id?: string | ObjectId;
  year: number;
  month: number;
  day: number;
  title: string;
  location?: string;
  description: string;
  tweetUrl?: string;
  imageUrl?: string;
  links?: ITimelineLink[];
  createdAt?: Date;
  updatedAt?: Date;
}

const timelineLinkSchema = new Schema<ITimelineLink>({
  text: { type: String, required: true },
  url: { type: String, required: true }
});

const timelineEventSchema = new Schema<ITimelineEvent>({
  year: { type: Number, required: true },
  month: { type: Number, required: true },
  day: { type: Number, required: true },
  title: { type: String, required: true },
  location: { type: String },
  description: { type: String, required: true },
  tweetUrl: { type: String },
  imageUrl: { type: String },
  links: [timelineLinkSchema]
}, {
  timestamps: true
});

export const TimelineEvent = mongoose.models.TimelineEvent || mongoose.model<ITimelineEvent>('TimelineEvent', timelineEventSchema);

项目

基本功能

展示一些自己参与 | 写过的项目,请求github,展示star,展示项目标签、项目状态等

image-20250210154108332

image.png

数据模型

import mongoose, { Schema, Document } from 'mongoose';
import { ObjectId } from "mongodb";

export interface Project {
  title: string;
  description: string;
  url?: string;
  github?: string;
  imageUrl?: string;
  tags: string[];
  status: "completed" | "in-progress" | "planned";
  categoryId: ObjectId;
}

export interface ProjectDB extends Omit<Project, "_id"> {
  _id?: ObjectId;
  createdAt: Date;
  updatedAt: Date;
}

export interface ProjectCategory {
  name: string;
  description: string;
  projects: ObjectId[];
}

export interface ProjectCategoryDB extends Omit<ProjectCategory, "_id"> {
  _id?: ObjectId;
  createdAt: Date;
  updatedAt: Date;
}

export interface IProject extends Document {
  _id: ObjectId;
  title: string;
  description: string;
  url?: string;
  github?: string;
  imageUrl?: string;
  tags: string[];
  status: "completed" | "in-progress" | "planned";
  categoryId: ObjectId;
  createdAt: Date;
  updatedAt: Date;
}

export interface IProjectCategory extends Document {
  _id: ObjectId;
  name: string;
  description: string;
  projects: ObjectId[];
  createdAt: Date;
  updatedAt: Date;
}

const projectSchema = new Schema<IProject>({
  title: { type: String, required: true },
  description: { type: String, required: true },
  url: { type: String },
  github: { type: String },
  imageUrl: { type: String },
  tags: [{ type: String }],
  status: { 
    type: String, 
    enum: ["completed", "in-progress", "planned"],
    required: true 
  },
  categoryId: { type: Schema.Types.ObjectId, ref: 'ProjectCategory', required: true }
}, {
  timestamps: true
});

const projectCategorySchema = new Schema<IProjectCategory>({
  name: { type: String, required: true },
  description: { type: String, required: true },
  projects: [{ type: Schema.Types.ObjectId, ref: 'Project' }]
}, {
  timestamps: true
});

// Check if models exist before creating new ones
export const Project = mongoose.models.Project || mongoose.model<IProject>('Project', projectSchema);
export const ProjectCategory = mongoose.models.ProjectCategory || mongoose.model<IProjectCategory>('ProjectCategory', projectCategorySchema);

demo

基本功能

展示平时写的一些demo,也算个作品集吧

image-20250210154306499

数据模型

import { ObjectId } from '../utils/objectId';

// API interfaces (for frontend use)
export interface WithTimestamps {
  createdAt?: Date | string;
  updatedAt?: Date | string;
}

export interface IDemo extends WithTimestamps {
  _id?: string | ObjectId;
  name: string;
  description: string;
  url?: string;
  gifUrl?: string;
  categoryId: string | ObjectId;
  tags?: string[];
  views: number;
  likes: number;
  completed: boolean;
}

export interface IDemoCategory extends WithTimestamps {
  _id?: string | ObjectId;
  name: string;
  description?: string;
  order?: number;
  demos?: IDemo[];  
}

// Database interfaces (for MongoDB)
export interface IDemoDB extends Omit<IDemo, '_id'> {
  _id?: ObjectId;
}

export interface IDemoCategoryDB extends Omit<IDemoCategory, '_id'> {
  _id?: ObjectId;
}

友链

基本功能

展示朋友头像和基本信息

image.png

数据模型

import mongoose, { Schema, Document } from 'mongoose';

export interface IFriend extends Document {
  avatar: string;
  name: string;
  title: string;
  description: string;
  link: string;
  position?: string;
  location?: string;
  isApproved: boolean;
}

const friendSchema = new Schema<IFriend>({
  avatar: { type: String, required: true },
  name: { type: String, required: true },
  title: { type: String, required: true },
  description: { type: String, required: true },
  link: { type: String, required: true },
  position: { type: String },
  location: { type: String },
  isApproved: { type: Boolean, default: false }
}, {
  timestamps: true
});

export const Friend = mongoose.models.Friend || mongoose.model<IFriend>('Friend', friendSchema);