fastapi+angular朋友圈

说明:
朋友圈

1.内容列表
2.对内容进行评论
3.发表和删除内容
效果图:
在这里插入图片描述

step1:sql


-- 用户表(存储用户基本信息)
CREATE TABLE users (
    user_id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    avatar_url VARCHAR(255) COMMENT '头像地址',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 朋友圈内容表(核心内容存储)
CREATE TABLE posts (
    post_id INT AUTO_INCREMENT PRIMARY KEY,
    user_id INT NOT NULL,
    content TEXT NOT NULL COMMENT '动态内容',
    image_urls JSON COMMENT '图片地址数组',
    like_count INT DEFAULT 0 COMMENT '点赞数',
    comment_count INT DEFAULT 0 COMMENT '评论数',
    is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否删除',
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 评论表(评论系统核心)
CREATE TABLE comments (
    comment_id INT AUTO_INCREMENT PRIMARY KEY,
    post_id INT NOT NULL,
    user_id INT NOT NULL,
    parent_id INT DEFAULT 0 COMMENT '父评论ID(支持二级评论)',
    content VARCHAR(1000) NOT NULL,
    is_deleted TINYINT(1) DEFAULT 0,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- 点赞关系表(可选)
CREATE TABLE likes (
    like_id INT AUTO_INCREMENT PRIMARY KEY,
    post_id INT NOT NULL,
    user_id INT NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
    UNIQUE KEY uniq_post_user (post_id, user_id),
    FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
    FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;


INSERT INTO users (username, avatar_url) VALUES
('张三', 'https://randomuser.me/api/portraits/men/1.jpg'),
('李四', 'https://randomuser.me/api/portraits/men/2.jpg'),
('王五', 'https://randomuser.me/api/portraits/men/3.jpg'),
('赵六', 'https://randomuser.me/api/portraits/men/4.jpg'),
('陈七', 'https://randomuser.me/api/portraits/men/5.jpg'),
('周八', 'https://randomuser.me/api/portraits/men/6.jpg'),
('吴九', 'https://randomuser.me/api/portraits/men/1.jpg'),
('郑十', 'https://randomuser.me/api/portraits/men/2.jpg'),
('孙十一', 'https://randomuser.me/api/portraits/men/3.jpg'),
('刘十二', 'https://randomuser.me/api/portraits/men/4.jpg');



INSERT INTO posts (user_id, content, image_urls) VALUES
(1, '今天天气真好!', '["https://randomuser.me/api/portraits/men/1.jpg","https://randomuser.me/api/portraits/men/2.jpg"]'),
(2, '打卡新开的咖啡店☕️', '["https://randomuser.me/api/portraits/men/3.jpg","https://randomuser.me/api/portraits/men/4.jpg"]'),
(3, '周末爬山,累并快乐着🏔️', '["https://randomuser.me/api/portraits/men/5.jpg","https://randomuser.me/api/portraits/men/6.jpg"]'),
(4, '深夜放毒,自制火锅🍲', '["https://randomuser.me/api/portraits/men/4.jpg","https://randomuser.me/api/portraits/men/2.jpg"]'),
(5, '新书到货,开读!📚', NULL),
(6, '健身打卡 Day 30💪', '["https://randomuser.me/api/portraits/men/3.jpg"]'),
(7, '旅行回忆 #北海', '["https://randomuser.me/api/portraits/men/4.jpg","https://randomuser.me/api/portraits/men/5.jpg"]'),
(8, '加班到凌晨,晚安🌙', NULL),
(9, '今日份早餐🥐', '["https://randomuser.me/api/portraits/men/4.jpg"]'),
(10, '宠物日常🐶', '["https://randomuser.me/api/portraits/men/6.jpg"]');

INSERT INTO comments (post_id, user_id, parent_id, content) VALUES
(1, 2, 0, '确实不错!'),
(1, 3, 0, '一起出去玩吧~'),
(2, 1, 0, '这家咖啡好喝吗?'),
(2, 4, 3, '超好喝!推荐拿铁!'),
(3, 5, 0, '风景太美了!'),
(4, 6, 0, '饿了……'),
(5, 7, 0, '什么书呀?'),
(6, 8, 0, '坚持就是胜利!'),
(7, 9, 0, '我也想去西藏!'),
(10, 10, 0, '狗狗好可爱!');


INSERT INTO likes (post_id, user_id) VALUES
(1, 2), (1, 3), (1, 4),
(2, 1), (2, 5),
(3, 6), (3, 7),
(4, 8), (4, 9),
(5, 10);

step2:fastapi

from fastapi import FastAPI, HTTPException, Depends
import pymysql.cursors
from enum import Enum
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
from typing import Optional, List
import json

app = FastAPI()

# CORS配置
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:4200"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 数据库配置
DB_CONFIG = {
    'host': 'localhost',
    'user': 'root',
    'password': '123456',
    'db': 'db_school',
    'charset': 'utf8mb4',
    'cursorclass': pymysql.cursors.DictCursor
}


def db_execute(query: str, params=None, fetch=True):
    try:
        # 转换所有JSON参数
        processed_params = []
        for param in params:
            if isinstance(param, (list, dict)):
                processed_params.append(json.dumps(param))
            else:
                processed_params.append(param)

        connection = pymysql.connect(**DB_CONFIG)
        with connection.cursor() as cursor:
            print(f"Executing query: {query % tuple(processed_params)}")  # 调试输出
            cursor.execute(query, params or ())
            if fetch:
                result = cursor.fetchall()
            else:
                result = cursor.lastrowid
            connection.commit()
        connection.close()
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Database error: {str(e)}")


# 数据模型
class PostCreate(BaseModel):
    user_id: int
    content: str
    image_urls: Optional[List[str]] = None


class CommentCreate(BaseModel):
    user_id: int
    content: str
    parent_id: Optional[int] = 0


class PostStatus(str, Enum):
    available = "available"
    deleted = "deleted"

class PostCreate(BaseModel):
    user_id: int
    content: str
    image_urls: Optional[List[str]] = Field(
        default=None,
        example=["img1.jpg", "img2.jpg"],
        description="图片URL列表"
    )

# 接口实现
@app.get("/posts")
async def get_posts(
        user_id: Optional[int] = None,
        page: int = 1,
        page_size: int = 10,
        order_by: str = "newest"
):
    """获取朋友圈列表"""
    offset = (page - 1) * page_size
    base_query = """
    SELECT 
        p.*, 
        u.username,
        u.avatar_url,
        COUNT(c.comment_id) as comment_count
    FROM posts p
    JOIN users u ON p.user_id = u.user_id
    LEFT JOIN comments c ON p.post_id = c.post_id
    WHERE p.is_deleted = 0
    """
    params = []

    if user_id:
        base_query += " AND p.user_id = %s"
        params.append(user_id)

    base_query += " GROUP BY p.post_id"

    # 排序方式
    order_mapping = {
        "newest": "p.created_at DESC",
        "hottest": "p.like_count DESC"
    }
    base_query += f" ORDER BY {order_mapping.get(order_by, 'p.created_at DESC')}"

    base_query += " LIMIT %s OFFSET %s"
    params.extend([page_size, offset])

    return {"data": db_execute(base_query, params)}


@app.post("/posts")
async def create_post(post: PostCreate):
    # 验证用户存在
    user_exists = db_execute("SELECT user_id FROM users WHERE user_id = %s", (post.user_id,))
    if not user_exists:
        raise HTTPException(status_code=404, detail="User not found")

    # 转换图片列表为JSON字符串
    image_json = json.dumps(post.image_urls) if post.image_urls else None

    query = """
    INSERT INTO posts 
    (user_id, content, image_urls)
    VALUES (%s, %s, %s)
    """
    try:
        post_id = db_execute(
            query,
            (post.user_id, post.content, image_json),  # 使用转换后的值
            fetch=False
        )
        return {"post_id": post_id, "message": "Post created successfully"}
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))

@app.delete("/posts/{post_id}")
async def delete_post(post_id: int, user_id: int):
    """删除朋友圈(软删除)"""
    # 验证权限
    post_owner = db_execute(
        "SELECT user_id FROM posts WHERE post_id = %s",
        (post_id,)
    )
    if not post_owner or post_owner[0]['user_id'] != user_id:
        raise HTTPException(status_code=403, detail="No permission to delete this post")

    db_execute(
        "UPDATE posts SET is_deleted = 1 WHERE post_id = %s",
        (post_id,),
        fetch=False
    )
    return {"message": "Post deleted successfully"}


@app.post("/posts/{post_id}/comments")
async def create_comment(post_id: int, comment: CommentCreate):
    """添加评论"""
    # 验证帖子存在
    post_exists = db_execute(
        "SELECT post_id FROM posts WHERE post_id = %s AND is_deleted = 0",
        (post_id,)
    )
    if not post_exists:
        raise HTTPException(status_code=404, detail="Post not found")

    # 验证用户存在
    user_exists = db_execute(
        "SELECT user_id FROM users WHERE user_id = %s",
        (comment.user_id,)
    )
    if not user_exists:
        raise HTTPException(status_code=404, detail="User not found")

    # 验证父评论
    if comment.parent_id != 0:
        parent_exists = db_execute(
            "SELECT comment_id FROM comments WHERE comment_id = %s",
            (comment.parent_id,)
        )
        if not parent_exists:
            raise HTTPException(status_code=404, detail="Parent comment not found")

    # 插入评论
    query = """
    INSERT INTO comments 
    (post_id, user_id, parent_id, content)
    VALUES (%s, %s, %s, %s)
    """
    try:
        db_execute(
            query,
            (post_id, comment.user_id, comment.parent_id, comment.content),
            fetch=False
        )
        # 更新评论计数
        db_execute(
            "UPDATE posts SET comment_count = comment_count + 1 WHERE post_id = %s",
            (post_id,),
            fetch=False
        )
        return {"message": "Comment added successfully"}
    except Exception as e:
        raise HTTPException(status_code=400, detail=str(e))


@app.get("/posts/{post_id}/comments")
async def get_comments(post_id: int, page: int = 1, page_size: int = 10):
    """获取评论列表"""
    offset = (page - 1) * page_size
    query = """
    SELECT 
        c.*,
        u.username,
        u.avatar_url,
        COUNT(r.comment_id) as reply_count
    FROM comments c
    JOIN users u ON c.user_id = u.user_id
    LEFT JOIN comments r ON c.comment_id = r.parent_id
    WHERE c.post_id = %s AND c.is_deleted = 0
    GROUP BY c.comment_id
    ORDER BY c.created_at DESC
    LIMIT %s OFFSET %s
    """
    return {"data": db_execute(query, (post_id, page_size, offset))}


@app.get("/users/{user_id}/posts")
async def get_user_posts(
        user_id: int,
        page: int = 1,
        page_size: int = 10
):
    """获取用户个人朋友圈"""
    offset = (page - 1) * page_size
    query = """
    SELECT 
        p.*,
        u.username,
        u.avatar_url
    FROM posts p
    JOIN users u ON p.user_id = u.user_id
    WHERE p.user_id = %s AND p.is_deleted = 0
    ORDER BY p.created_at DESC
    LIMIT %s OFFSET %s
    """
    return {"data": db_execute(query, (user_id, page_size, offset))}


if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="0.0.0.0", port=8000)

step3:postman

get : http://localhost:8000/posts?page=1&page_size=5&order_by=hottest


{
    "data": [
        {
            "post_id": 1,
            "user_id": 1,
            "content": "今天天气真好!",
            "image_urls": "[\"https://example.com/posts/1_1.jpg\"]",
            "like_count": 0,
            "comment_count": 0,
            "is_deleted": 0,
            "created_at": "2025-03-19T03:50:22",
            "updated_at": "2025-03-19T03:50:22",
            "username": "张三",
            "avatar_url": "https://example.com/avatars/1.jpg",
            ".comment_count": 2
        },
        {
            "post_id": 2,
            "user_id": 2,
            "content": "打卡新开的咖啡店☕️",
            "image_urls": "[\"https://example.com/posts/2_1.jpg\", \"https://example.com/posts/2_2.jpg\"]",
            "like_count": 0,
            "comment_count": 0,
            "is_deleted": 0,
            "created_at": "2025-03-19T03:50:22",
            "updated_at": "2025-03-19T03:50:22",
            "username": "李四",
            "avatar_url": "https://example.com/avatars/2.jpg",
            ".comment_count": 2
        },
        {
            "post_id": 3,
            "user_id": 3,
            "content": "周末爬山,累并快乐着🏔️",
            "image_urls": "[\"https://example.com/posts/3_1.jpg\"]",
            "like_count": 0,
            "comment_count": 0,
            "is_deleted": 0,
            "created_at": "2025-03-19T03:50:22",
            "updated_at": "2025-03-19T03:50:22",
            "username": "王五",
            "avatar_url": "https://example.com/avatars/3.jpg",
            ".comment_count": 1
        },
        {
            "post_id": 4,
            "user_id": 4,
            "content": "深夜放毒,自制火锅🍲",
            "image_urls": "[\"https://example.com/posts/4_1.jpg\", \"https://example.com/posts/4_2.jpg\"]",
            "like_count": 0,
            "comment_count": 0,
            "is_deleted": 0,
            "created_at": "2025-03-19T03:50:22",
            "updated_at": "2025-03-19T03:50:22",
            "username": "赵六",
            "avatar_url": "https://example.com/avatars/4.jpg",
            ".comment_count": 1
        },
        {
            "post_id": 5,
            "user_id": 5,
            "content": "新书到货,开读!📚",
            "image_urls": null,
            "like_count": 0,
            "comment_count": 0,
            "is_deleted": 0,
            "created_at": "2025-03-19T03:50:22",
            "updated_at": "2025-03-19T03:50:22",
            "username": "陈七",
            "avatar_url": "https://example.com/avatars/5.jpg",
            ".comment_count": 1
        }
    ]
}

POST http://localhost:8000/posts
Content-Type: application/json

{
    "user_id": 1,
    "content": "周末去爬山!",
    "image_urls": ["mountain.jpg", "sunset.jpg"]
}

{
    "post_id": 12,
    "message": "Post created successfully"
}

DELETE http://localhost:8000/posts/123?user_id=1

{
    "message": "Post deleted successfully"
}


POST http://localhost:8000/posts/1/comments
Body (JSON):
{
    "user_id": 2,
    "content": "风景真美!",
    "parent_id": 0
}

get http://localhost:8000/posts/2/comments?page=1&page_size=3



{
    "user_id": 2,
    "content": "风景真美!",
    "parent_id": 0
}

{
    "data": [
        {
            "comment_id": 3,
            "post_id": 2,
            "user_id": 1,
            "parent_id": 0,
            "content": "这家咖啡好喝吗?",
            "is_deleted": 0,
            "created_at": "2025-03-19T03:50:24",
            "updated_at": "2025-03-19T03:50:24",
            "username": "张三",
            "avatar_url": "https://example.com/avatars/1.jpg",
            "reply_count": 1
        },
        {
            "comment_id": 4,
            "post_id": 2,
            "user_id": 4,
            "parent_id": 3,
            "content": "超好喝!推荐拿铁!",
            "is_deleted": 0,
            "created_at": "2025-03-19T03:50:24",
            "updated_at": "2025-03-19T03:50:24",
            "username": "赵六",
            "avatar_url": "https://example.com/avatars/4.jpg",
            "reply_count": 0
        }
    ]
}



 get : http://localhost:8000/users/1/posts?page=1&page_size=10


 {
    "data": [
        {
            "post_id": 1,
            "user_id": 1,
            "content": "今天天气真好!",
            "image_urls": "[\"https://example.com/posts/1_1.jpg\"]",
            "like_count": 0,
            "comment_count": 1,
            "is_deleted": 0,
            "created_at": "2025-03-19T03:50:22",
            "updated_at": "2025-03-19T04:18:12",
            "username": "张三",
            "avatar_url": "https://example.com/avatars/1.jpg"
        }
    ]
}

step4:service

import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable, catchError, throwError } from 'rxjs';

// firend.service.ts
export interface Post {
  post_id: number;
  user_id: number;
  content: string;
  image_urls: string;  // 修正为string类型
  like_count: number;
  comment_count: number;
  is_deleted: boolean;
  created_at: string;
  updated_at: string;
  username: string;
  avatar_url: string;
}

export interface DisplayPost extends Post {
  images: string[];  // 转换后的图片数组
}


export interface Comment {
  comment_id: number;
  post_id: number;
  user_id: number;
  parent_id: number;
  content: string;
  is_deleted: boolean;
  created_at: string;
  updated_at: string;
  username: string;
  avatar_url: string;
  reply_count: number;
}



@Injectable({
  providedIn: 'root'
})
export class FirendService {
  private apiUrl = 'http://localhost:8000';

  constructor(private http: HttpClient) { }

  // 获取朋友圈列表
  getPosts(params: {
    page?: number;
    page_size?: number;
    order_by?: 'newest' | 'hottest';
    user_id?: number;
  }): Observable<{ data: Post[] }> {
    let httpParams = new HttpParams();

    Object.entries(params).forEach(([key, value]) => {
      if (value !== undefined) {
        httpParams = httpParams.set(key, value.toString());
      }
    });

    return this.http.get<{ data: Post[] }>(`${this.apiUrl}/posts`, { params: httpParams })
      .pipe(
        catchError(this.handleError)
      );
  }

  // 创建新帖子
  createPost(postData: {
    user_id: number;
    content: string;
    image_urls?: string[];
  }): Observable<{ post_id: number; message: string }> {
    return this.http.post<{ post_id: number; message: string }>(
      `${this.apiUrl}/posts`,
      postData
    ).pipe(
      catchError(this.handleError)
    );
  }

  // 删除帖子
  deletePost(postId: number, userId: number): Observable<{ message: string }> {
    return this.http.delete<{ message: string }>(
      `${this.apiUrl}/posts/${postId}`,
      { params: { user_id: userId.toString() } }
    ).pipe(
      catchError(this.handleError)
    );
  }

  // 添加评论
  addComment(postId: number, commentData: {
    user_id: number;
    content: string;
    parent_id?: number;
  }): Observable<{ message: string }> {
    return this.http.post<{ message: string }>(
      `${this.apiUrl}/posts/${postId}/comments`,
      commentData
    ).pipe(
      catchError(this.handleError)
    );
  }

  // 获取评论列表
  getComments(postId: number, params: {
    page?: number;
    page_size?: number;
  }): Observable<{ data: Comment[] }> {
    let httpParams = new HttpParams();

    Object.entries(params).forEach(([key, value]) => {
      if (value !== undefined) {
        httpParams = httpParams.set(key, value.toString());
      }
    });

    return this.http.get<{ data: Comment[] }>(
      `${this.apiUrl}/posts/${postId}/comments`,
      { params: httpParams }
    ).pipe(
      catchError(this.handleError)
    );
  }

  // 获取用户帖子
  getUserPosts(userId: number, params: {
    page?: number;
    page_size?: number;
  }): Observable<{ data: Post[] }> {
    let httpParams = new HttpParams();

    Object.entries(params).forEach(([key, value]) => {
      if (value !== undefined) {
        httpParams = httpParams.set(key, value.toString());
      }
    });

    return this.http.get<{ data: Post[] }>(
      `${this.apiUrl}/users/${userId}/posts`,
      { params: httpParams }
    ).pipe(
      catchError(this.handleError)
    );
  }

  private handleError(error: any) {
    console.error('API Error:', error);
    let errorMessage = 'An unknown error occurred!';
    if (error.error instanceof ErrorEvent) {
      errorMessage = `Client-side error: ${error.error.message}`;
    } else if (error.status) {
      errorMessage = `Server error ${error.status}: ${error.error?.detail || error.message}`;
    }
    return throwError(() => new Error(errorMessage));
  }
}

step5:ts == C:\Users\wangrusheng\WebstormProjects\untitled5\src\app\user\user.component.ts

import { Component, OnInit  } from '@angular/core';
import { FirendService,Post,Comment,DisplayPost } from '../service/firend.service';
import {FormsModule} from '@angular/forms';
import {NgForOf, NgIf} from '@angular/common';




@Component({
  selector: 'app-post',
  templateUrl: './user.component.html',
  imports: [
    FormsModule,
    NgForOf,
    NgIf
  ],
  styleUrl: './user.component.css'
})
export class UserComponent  implements OnInit {

  posts: DisplayPost[] = [];  // 使用扩展后的类型

  // 输入绑定参数
  inputPostId: number = 1;  // 1 or 2
  inputUserId: number = 1;  //1 or 2
  commentPostId: number = 3;  // 3 or  4

  // 控制台输出显示
  consoleOutput: string = '';

  // 在原有方法中添加日志记录
  private log(message: string) {
    this.consoleOutput += `${new Date().toLocaleTimeString()}: ${message}\n`;
  }



  constructor(private firendService: FirendService) {}

  ngOnInit() {
    this.firendService.getPosts({ order_by: 'newest' }).subscribe({
      next: (res) => {
        console.error(res)
        this.posts = res.data.map(post => ({
          ...post,
          images: post.image_urls ? JSON.parse(post.image_urls) : []
        }));
      },
      error: (err) => console.error('加载失败:', err)
    });
  }

  // 新增时间格式化方法
  formatTime(timeString: string): string {
    const options: Intl.DateTimeFormatOptions = {
      year: 'numeric',
      month: '2-digit',
      day: '2-digit',
      hour: '2-digit',
      minute: '2-digit'
    };
    return new Date(timeString).toLocaleString('zh-CN', options);
  }


  // 获取热门帖子
  loadHotPosts(): void {
    this.firendService.getPosts({
      page: 1,
      page_size: 5,
      order_by: 'hottest'
    }).subscribe({
      next: (res) => console.log('热门帖子:', res.data),
      error: (err) => console.error(err)
    });
  }

  // 创建新帖子
  createNewPost(): void {
    const newPost = {
      user_id: 1,
      content: '周末去爬山!',
      image_urls: ['mountain.jpg', 'sunset.jpg']
    };

    this.firendService.createPost(newPost).subscribe({
      next: (res) => console.log('创建成功:', res.post_id),
      error: (err) => console.error(err)
    });
  }

  // 删除帖子
  deletePost(postId: number): void {
    this.firendService.deletePost(postId, 1).subscribe({
      next: () => console.log('删除成功'),
      error: (err) => console.error(err)
    });
  }

  // 添加评论
  addComment(postId: number): void {
    const newComment = {
      user_id: 2,
      content: '风景真美!',
      parent_id: 0
    };

    this.firendService.addComment(postId, newComment).subscribe({
      next: () => console.log('评论成功'),
      error: (err) => console.error(err)
    });
  }

  // 获取评论
  loadComments(postId: number): void {
    this.firendService.getComments(postId, {
      page: 1,
      page_size: 3
    }).subscribe({
      next: (res) => console.log('评论列表:', res.data),
      error: (err) => console.error(err)
    });
  }

  // 获取用户帖子
  loadUserPosts(userId: number): void {
    this.firendService.getUserPosts(userId, {
      page: 1,
      page_size: 10
    }).subscribe({
      next: (res) => console.log('用户帖子:', res.data),
      error: (err) => console.error(err)
    });
  }
}

step6:html

<div class="test-container">

  <!-- 朋友圈内容列表 -->
  <div class="post-grid">
    <div *ngFor="let post of posts" class="post-card">
      <div class="post-header">
        <img [src]="post.avatar_url" alt="头像" class="avatar">
        <div class="user-info">
          <h3>{{ post.username }}</h3>
          <span class="post-time">{{ formatTime(post.created_at) }}</span>
        </div>
      </div>

      <div class="post-content">
        <p>{{ post.content }}</p>
        <div class="image-grid" *ngIf="post.images.length">
          <img *ngFor="let img of post.images" [src]="img" alt="动态图片">
        </div>
      </div>

      <div class="interaction">
        <span class="likes">❤ {{ post.like_count }} 赞</span>
        <span class="comments">💬 {{ post.comment_count }} 评论</span>
      </div>
    </div>
  </div>

  <!-- 测试按钮区域 -->
  <div class="test-section">
    <h3>帖子操作测试</h3>

    <div class="test-group">
      <button (click)="loadHotPosts()">加载热门帖子</button>
      <button (click)="createNewPost()">创建新帖子</button>
    </div>

    <div class="test-group">
      <input type="number" [(ngModel)]="inputPostId" placeholder="帖子ID">
      <button (click)="deletePost(inputPostId)">删除帖子</button>
      <button (click)="loadComments(inputPostId)">加载评论</button>
    </div>

    <div class="test-group">
      <input type="number" [(ngModel)]="inputUserId" placeholder="用户ID">
      <button (click)="loadUserPosts(inputUserId)">加载用户帖子</button>
    </div>

    <div class="test-group">
      <input type="number" [(ngModel)]="commentPostId" placeholder="帖子ID">
      <button (click)="addComment(commentPostId)">添加评论</button>
    </div>
  </div>

  <!-- 结果显示区域 -->
  <div class="result-area">
    <h4>控制台输出:</h4>
    <pre>{{ consoleOutput }}</pre>
  </div>
</div>

step7:css

.test-container {
  padding: 20px;
  font-family: Arial, serif;
}

.test-section {
  margin-bottom: 30px;
  padding: 15px;
  border: 1px solid #eee;
}

.test-group {
  margin: 10px 0;
  display: flex;
  gap: 10px;
  align-items: center;
}

button {
  padding: 8px 15px;
  background: #007bff;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: opacity 0.3s;
}

button:hover {
  opacity: 0.8;
}

input {
  padding: 8px;
  border: 1px solid #ddd;
  border-radius: 4px;
  width: 120px;
}

.result-area {
  margin-top: 20px;
  padding: 15px;
  background: #f5f5f5;
  border-radius: 6px;
}

pre {
  white-space: pre-wrap;
  word-wrap: break-word;
}
.content-container {
  display: grid;
  grid-template-columns: 3fr 1fr;
  gap: 2rem;
  padding: 2rem;
}

.post-grid {
  display: grid;
  gap: 1.5rem;
}

.post-card {
  background: white;
  border-radius: 12px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  padding: 1.5rem;
  transition: transform 0.2s;
}

.post-card:hover {
  transform: translateY(-3px);
}

.post-header {
  display: flex;
  align-items: center;
  margin-bottom: 1rem;
}

.avatar {
  width: 45px;
  height: 45px;
  border-radius: 50%;
  margin-right: 1rem;
}

.user-info h3 {
  margin: 0;
  font-size: 1.1rem;
  color: #2d3748;
}

.post-time {
  font-size: 0.85rem;
  color: #718096;
}

.post-content p {
  margin: 0 0 1rem 0;
  line-height: 1.6;
  color: #4a5568;
}

.image-grid {
  display: grid;
  gap: 0.5rem;
  margin-top: 1rem;
}

/* 不同图片数量的布局 */
.image-grid img {
  width: 100%;
  border-radius: 8px;
  object-fit: cover;
}

.image-grid img:only-child {
  max-height: 400px;
}

.image-grid img:nth-child(2) {
  grid-template-columns: repeat(2, 1fr);
}

.image-grid img:nth-child(3) {
  grid-template-columns: repeat(3, 1fr);
}

.interaction {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid #eee;
  display: flex;
  gap: 1.5rem;
}

.likes, .comments {
  display: flex;
  align-items: center;
  gap: 0.3rem;
  color: #718096;
  cursor: pointer;
  transition: color 0.2s;
}

.likes:hover { color: #f56565; }
.comments:hover { color: #48bb78; }



end

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值