diff --git a/server/cmd/server/main.go b/server/cmd/server/main.go
index 835b467..7701289 100644
--- a/server/cmd/server/main.go
+++ b/server/cmd/server/main.go
@@ -33,6 +33,7 @@ func main() {
authHandler := handlers.NewAuthHandler(jwtManager)
commentHandler := handlers.NewCommentHandler()
likeHandler := handlers.NewLikeHandler()
+ microHandler := handlers.NewMicroHandler()
// 创建路由
r := gin.New()
@@ -70,6 +71,15 @@ func main() {
api.GET("/likes", likeHandler.GetLikeStatus)
api.POST("/likes", likeHandler.ToggleLike) // 允许访客点赞(基于 IP Hash)
+ // 微语公开接口
+ api.GET("/micro", microHandler.GetMicros)
+ api.GET("/micro/stats", microHandler.GetMicroStats)
+ api.GET("/micro/heatmap", microHandler.GetMicroHeatmap)
+ api.GET("/micro/tags", microHandler.GetMicroTags)
+ api.GET("/micro/:id", microHandler.GetMicro)
+ api.POST("/micro/:id/like", microHandler.ToggleMicroLike) // 允许访客点赞
+ api.GET("/micro-comments", microHandler.GetMicroComments) // 获取微语评论
+
// 需要认证的接口
authGroup := api.Group("")
authGroup.Use(middleware.AuthMiddleware(jwtManager))
@@ -81,6 +91,14 @@ func main() {
// 评论相关(需要登录才能评论)
authGroup.POST("/comments", commentHandler.CreateComment)
authGroup.DELETE("/comments/:id", commentHandler.DeleteComment)
+
+ // 微语相关(需要登录才能发布和删除)
+ authGroup.POST("/micro", microHandler.CreateMicro)
+ authGroup.DELETE("/micro/:id", microHandler.DeleteMicro)
+
+ // 微语评论相关
+ authGroup.POST("/micro-comments", microHandler.CreateMicroComment)
+ authGroup.DELETE("/micro-comments/:id", microHandler.DeleteMicroComment)
}
// 管理员接口
diff --git a/server/internal/database/database.go b/server/internal/database/database.go
index c7da001..f0c853f 100644
--- a/server/internal/database/database.go
+++ b/server/internal/database/database.go
@@ -54,6 +54,9 @@ func autoMigrate() error {
&models.Like{},
&models.LikeCount{},
&models.PostMeta{},
+ &models.Micro{},
+ &models.MicroLike{},
+ &models.MicroComment{},
)
}
diff --git a/server/internal/handlers/micro.go b/server/internal/handlers/micro.go
new file mode 100644
index 0000000..acc1220
--- /dev/null
+++ b/server/internal/handlers/micro.go
@@ -0,0 +1,595 @@
+package handlers
+
+import (
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "net/http"
+ "strconv"
+ "time"
+
+ "github.com/gin-gonic/gin"
+ "github.com/novablog/server/internal/database"
+ "github.com/novablog/server/internal/middleware"
+ "github.com/novablog/server/internal/models"
+ "gorm.io/gorm"
+)
+
+// MicroHandler 微语处理器
+type MicroHandler struct{}
+
+// NewMicroHandler 创建微语处理器
+func NewMicroHandler() *MicroHandler {
+ return &MicroHandler{}
+}
+
+// CreateMicroRequest 创建微语请求
+type CreateMicroRequest struct {
+ Content string `json:"content" binding:"required,min=1,max=2000"`
+ Images []string `json:"images"`
+ Tags []string `json:"tags"`
+}
+
+// CreateMicro 创建微语
+func (h *MicroHandler) CreateMicro(c *gin.Context) {
+ userID, isLoggedIn := middleware.GetUserID(c)
+ if !isLoggedIn || userID == 0 {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "请登录后再发布"})
+ return
+ }
+
+ var req CreateMicroRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ // 将图片数组转为 JSON 字符串
+ var imagesJSON string
+ if len(req.Images) > 0 {
+ imagesBytes, _ := json.Marshal(req.Images)
+ imagesJSON = string(imagesBytes)
+ }
+
+ // 将标签数组转为 JSON 字符串
+ var tagsJSON string
+ if len(req.Tags) > 0 {
+ tagsBytes, _ := json.Marshal(req.Tags)
+ tagsJSON = string(tagsBytes)
+ }
+
+ micro := models.Micro{
+ UserID: userID,
+ Content: req.Content,
+ Images: imagesJSON,
+ Tags: tagsJSON,
+ }
+
+ if err := database.DB.Create(µ).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "发布失败"})
+ return
+ }
+
+ // 加载用户信息
+ database.DB.Preload("User").First(µ, micro.ID)
+
+ c.JSON(http.StatusCreated, micro)
+}
+
+// GetMicros 获取微语列表
+func (h *MicroHandler) GetMicros(c *gin.Context) {
+ // 分页参数
+ page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
+ pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
+ if page < 1 {
+ page = 1
+ }
+ if pageSize < 1 || pageSize > 50 {
+ pageSize = 20
+ }
+
+ // 标签过滤
+ tag := c.Query("tag")
+
+ var micros []models.Micro
+ var total int64
+
+ query := database.DB.Model(&models.Micro{}).Where("deleted_at IS NULL")
+
+ // 如果有标签过滤
+ if tag != "" {
+ query = query.Where("tags LIKE ?", "%\""+tag+"\"%")
+ }
+
+ query.Count(&total)
+
+ if err := query.
+ Preload("User").
+ Order("created_at DESC").
+ Offset((page - 1) * pageSize).
+ Limit(pageSize).
+ Find(µs).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "获取失败"})
+ return
+ }
+
+ // 获取当前用户的点赞状态
+ userID, isLoggedIn := middleware.GetUserID(c)
+ userLikes := make(map[uint]bool)
+ if isLoggedIn && userID > 0 {
+ var likes []models.MicroLike
+ microIDs := make([]uint, len(micros))
+ for i, m := range micros {
+ microIDs[i] = m.ID
+ }
+ database.DB.Where("micro_id IN ? AND user_id = ?", microIDs, userID).Find(&likes)
+ for _, like := range likes {
+ userLikes[like.MicroID] = true
+ }
+ }
+
+ // 构建响应
+ type MicroResponse struct {
+ models.Micro
+ IsLiked bool `json:"is_liked"`
+ }
+
+ responses := make([]MicroResponse, len(micros))
+ for i, m := range micros {
+ responses[i] = MicroResponse{
+ Micro: m,
+ IsLiked: userLikes[m.ID],
+ }
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "data": responses,
+ "pagination": gin.H{
+ "page": page,
+ "page_size": pageSize,
+ "total": total,
+ "total_page": (total + int64(pageSize) - 1) / int64(pageSize),
+ },
+ })
+}
+
+// GetMicro 获取单条微语
+func (h *MicroHandler) GetMicro(c *gin.Context) {
+ idStr := c.Param("id")
+ id, err := strconv.ParseUint(idStr, 10, 32)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
+ return
+ }
+
+ var micro models.Micro
+ if err := database.DB.Preload("User").First(µ, id).Error; err != nil {
+ if err == gorm.ErrRecordNotFound {
+ c.JSON(http.StatusNotFound, gin.H{"error": "微语不存在"})
+ return
+ }
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "获取失败"})
+ return
+ }
+
+ // 获取点赞状态
+ userID, isLoggedIn := middleware.GetUserID(c)
+ isLiked := false
+ if isLoggedIn && userID > 0 {
+ var like models.MicroLike
+ if err := database.DB.Where("micro_id = ? AND user_id = ?", micro.ID, userID).First(&like).Error; err == nil {
+ isLiked = true
+ }
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "data": micro,
+ "is_liked": isLiked,
+ })
+}
+
+// DeleteMicro 删除微语
+func (h *MicroHandler) DeleteMicro(c *gin.Context) {
+ userID, _ := middleware.GetUserID(c)
+ role, _ := c.Get("role")
+
+ idStr := c.Param("id")
+ id, err := strconv.ParseUint(idStr, 10, 32)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
+ return
+ }
+
+ var micro models.Micro
+ if err := database.DB.First(µ, id).Error; err != nil {
+ if err == gorm.ErrRecordNotFound {
+ c.JSON(http.StatusNotFound, gin.H{"error": "微语不存在"})
+ return
+ }
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "获取失败"})
+ return
+ }
+
+ // 检查权限:本人或管理员可删除
+ if micro.UserID != userID && role != "admin" {
+ c.JSON(http.StatusForbidden, gin.H{"error": "无权限删除"})
+ return
+ }
+
+ // 软删除
+ if err := database.DB.Delete(µ).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"})
+ return
+ }
+
+ c.JSON(http.StatusOK, gin.H{"message": "删除成功"})
+}
+
+// ToggleMicroLike 切换点赞状态
+func (h *MicroHandler) ToggleMicroLike(c *gin.Context) {
+ idStr := c.Param("id")
+ id, err := strconv.ParseUint(idStr, 10, 32)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "无效的 ID"})
+ return
+ }
+
+ // 检查微语是否存在
+ var micro models.Micro
+ if err := database.DB.First(µ, id).Error; err != nil {
+ if err == gorm.ErrRecordNotFound {
+ c.JSON(http.StatusNotFound, gin.H{"error": "微语不存在"})
+ return
+ }
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "获取失败"})
+ return
+ }
+
+ userID, isLoggedIn := middleware.GetUserID(c)
+
+ var existingLike models.MicroLike
+ var likeErr error
+
+ if isLoggedIn && userID > 0 {
+ // 登录用户:按 user_id 查找
+ likeErr = database.DB.Where("micro_id = ? AND user_id = ?", id, userID).First(&existingLike).Error
+ } else {
+ // 访客:按 IP Hash 查找
+ ipHash := getIPHash(c)
+ likeErr = database.DB.Where("micro_id = ? AND ip_hash = ?", id, ipHash).First(&existingLike).Error
+ }
+
+ if likeErr == gorm.ErrRecordNotFound {
+ // 创建点赞
+ newLike := models.MicroLike{
+ MicroID: uint(id),
+ }
+ if isLoggedIn && userID > 0 {
+ newLike.UserID = &userID
+ } else {
+ newLike.IPHash = getIPHash(c)
+ }
+
+ if err := database.DB.Create(&newLike).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "点赞失败"})
+ return
+ }
+
+ // 更新点赞计数
+ database.DB.Model(µ).Update("like_count", gorm.Expr("like_count + 1"))
+
+ c.JSON(http.StatusOK, gin.H{
+ "liked": true,
+ "like_count": micro.LikeCount + 1,
+ })
+ } else if likeErr == nil {
+ // 取消点赞
+ if err := database.DB.Delete(&existingLike).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "取消点赞失败"})
+ return
+ }
+
+ // 更新点赞计数
+ newCount := micro.LikeCount - 1
+ if newCount < 0 {
+ newCount = 0
+ }
+ database.DB.Model(µ).Update("like_count", newCount)
+
+ c.JSON(http.StatusOK, gin.H{
+ "liked": false,
+ "like_count": newCount,
+ })
+ } else {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "查询失败"})
+ return
+ }
+}
+
+// GetMicroHeatmap 获取热力图数据
+func (h *MicroHandler) GetMicroHeatmap(c *gin.Context) {
+ yearStr := c.DefaultQuery("year", strconv.Itoa(time.Now().Year()))
+ year, err := strconv.Atoi(yearStr)
+ if err != nil {
+ year = time.Now().Year()
+ }
+
+ // 计算年份的起止日期
+ startDate := time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
+ endDate := time.Date(year, 12, 31, 23, 59, 59, 0, time.UTC)
+
+ // 按日期分组统计
+ type DayCount struct {
+ Date string `json:"date"`
+ Count int `json:"count"`
+ }
+
+ var results []DayCount
+
+ database.DB.Model(&models.Micro{}).
+ Select("DATE(created_at) as date, COUNT(*) as count").
+ Where("created_at >= ? AND created_at <= ? AND deleted_at IS NULL", startDate, endDate).
+ Group("DATE(created_at)").
+ Find(&results)
+
+ // 转换为 map
+ data := make(map[string]int)
+ for _, r := range results {
+ data[r.Date] = r.Count
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "year": year,
+ "data": data,
+ })
+}
+
+// GetMicroStats 获取统计数据
+func (h *MicroHandler) GetMicroStats(c *gin.Context) {
+ var totalMicros int64
+ var totalLikes int64
+ var totalComments int64
+ var monthMicros int64
+
+ // 总微语数
+ database.DB.Model(&models.Micro{}).Count(&totalMicros)
+
+ // 总点赞数
+ database.DB.Model(&models.MicroLike{}).Count(&totalLikes)
+
+ // 总评论数
+ database.DB.Model(&models.MicroComment{}).Count(&totalComments)
+
+ // 本月发布数
+ now := time.Now()
+ monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
+ database.DB.Model(&models.Micro{}).Where("created_at >= ? AND deleted_at IS NULL", monthStart).Count(&monthMicros)
+
+ c.JSON(http.StatusOK, gin.H{
+ "total_micros": totalMicros,
+ "total_likes": totalLikes,
+ "total_comments": totalComments,
+ "month_micros": monthMicros,
+ })
+}
+
+// GetMicroTags 获取热门标签
+func (h *MicroHandler) GetMicroTags(c *gin.Context) {
+ // 获取所有微语的标签
+ var micros []models.Micro
+ database.DB.Where("tags IS NOT NULL AND tags != '' AND deleted_at IS NULL").Select("tags").Find(µs)
+
+ // 统计标签使用次数
+ tagCount := make(map[string]int)
+ for _, micro := range micros {
+ if micro.Tags == "" {
+ continue
+ }
+ var tags []string
+ if err := json.Unmarshal([]byte(micro.Tags), &tags); err == nil {
+ for _, tag := range tags {
+ tagCount[tag]++
+ }
+ }
+ }
+
+ // 转换为排序后的列表
+ type TagItem struct {
+ Name string `json:"name"`
+ Count int `json:"count"`
+ }
+
+ var tags []TagItem
+ for name, count := range tagCount {
+ tags = append(tags, TagItem{Name: name, Count: count})
+ }
+
+ // 按使用次数排序
+ for i := 0; i < len(tags); i++ {
+ for j := i + 1; j < len(tags); j++ {
+ if tags[j].Count > tags[i].Count {
+ tags[i], tags[j] = tags[j], tags[i]
+ }
+ }
+ }
+
+ // 只返回前20个
+ if len(tags) > 20 {
+ tags = tags[:20]
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "tags": tags,
+ })
+}
+
+// getIPHash 获取 IP 的哈希值
+func getIPHash(c *gin.Context) string {
+ ip := c.ClientIP()
+ hash := sha256.Sum256([]byte(ip + "micro-salt"))
+ return hex.EncodeToString(hash[:])
+}
+
+// CreateMicroCommentRequest 创建微语评论请求
+type CreateMicroCommentRequest struct {
+ MicroID uint `json:"micro_id" binding:"required"`
+ ParentID *uint `json:"parent_id"`
+ Content string `json:"content" binding:"required,min=1,max=2000"`
+}
+
+// CreateMicroComment 创建微语评论
+func (h *MicroHandler) CreateMicroComment(c *gin.Context) {
+ userID, isLoggedIn := middleware.GetUserID(c)
+ if !isLoggedIn || userID == 0 {
+ c.JSON(http.StatusUnauthorized, gin.H{"error": "请登录后再评论"})
+ return
+ }
+
+ var req CreateMicroCommentRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ // 检查微语是否存在
+ var micro models.Micro
+ if err := database.DB.First(µ, req.MicroID).Error; err != nil {
+ c.JSON(http.StatusNotFound, gin.H{"error": "微语不存在"})
+ return
+ }
+
+ comment := models.MicroComment{
+ MicroID: req.MicroID,
+ UserID: userID,
+ ParentID: req.ParentID,
+ Content: req.Content,
+ }
+
+ if err := database.DB.Create(&comment).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "评论失败"})
+ return
+ }
+
+ // 更新评论计数
+ database.DB.Model(µ).Update("comment_count", gorm.Expr("comment_count + 1"))
+
+ // 加载用户信息
+ database.DB.Preload("User").First(&comment, comment.ID)
+
+ c.JSON(http.StatusCreated, comment)
+}
+
+// GetMicroComments 获取微语评论列表
+func (h *MicroHandler) GetMicroComments(c *gin.Context) {
+ microIDStr := c.Query("micro_id")
+ if microIDStr == "" {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "micro_id is required"})
+ return
+ }
+
+ microID, err := strconv.ParseUint(microIDStr, 10, 32)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "invalid micro_id"})
+ return
+ }
+
+ // 分页参数
+ page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
+ pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
+ if page < 1 {
+ page = 1
+ }
+ if pageSize < 1 || pageSize > 100 {
+ pageSize = 20
+ }
+
+ // 获取顶级评论(非回复)
+ var comments []models.MicroComment
+ var total int64
+
+ query := database.DB.Model(&models.MicroComment{}).
+ Where("micro_id = ? AND parent_id IS NULL", microID)
+
+ query.Count(&total)
+
+ if err := query.
+ Preload("User").
+ Preload("Replies.User").
+ Order("created_at DESC").
+ Offset((page - 1) * pageSize).
+ Limit(pageSize).
+ Find(&comments).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "获取评论失败"})
+ return
+ }
+
+ // 手动加载每个评论的用户信息
+ for i := range comments {
+ if comments[i].UserID > 0 {
+ var user models.User
+ if err := database.DB.First(&user, comments[i].UserID).Error; err == nil {
+ comments[i].User = user
+ }
+ }
+ // 加载回复的用户信息
+ for j := range comments[i].Replies {
+ if comments[i].Replies[j].UserID > 0 {
+ var replyUser models.User
+ if err := database.DB.First(&replyUser, comments[i].Replies[j].UserID).Error; err == nil {
+ comments[i].Replies[j].User = replyUser
+ }
+ }
+ }
+ }
+
+ c.JSON(http.StatusOK, gin.H{
+ "data": comments,
+ "pagination": gin.H{
+ "page": page,
+ "page_size": pageSize,
+ "total": total,
+ "total_page": (total + int64(pageSize) - 1) / int64(pageSize),
+ },
+ })
+}
+
+// DeleteMicroComment 删除微语评论
+func (h *MicroHandler) DeleteMicroComment(c *gin.Context) {
+ userID, _ := middleware.GetUserID(c)
+ role, _ := c.Get("role")
+
+ idStr := c.Param("id")
+ id, err := strconv.ParseUint(idStr, 10, 32)
+ if err != nil {
+ c.JSON(http.StatusBadRequest, gin.H{"error": "invalid comment id"})
+ return
+ }
+
+ var comment models.MicroComment
+ if err := database.DB.First(&comment, id).Error; err != nil {
+ if err == gorm.ErrRecordNotFound {
+ c.JSON(http.StatusNotFound, gin.H{"error": "评论不存在"})
+ return
+ }
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "database error"})
+ return
+ }
+
+ // 检查权限:本人或管理员可删除
+ if comment.UserID != userID && role != "admin" {
+ c.JSON(http.StatusForbidden, gin.H{"error": "permission denied"})
+ return
+ }
+
+ // 软删除
+ if err := database.DB.Delete(&comment).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "删除失败"})
+ return
+ }
+
+ // 更新评论计数
+ database.DB.Model(&models.Micro{}).Where("id = ?", comment.MicroID).
+ Update("comment_count", gorm.Expr("comment_count - 1"))
+
+ c.JSON(http.StatusOK, gin.H{"message": "评论已删除"})
+}
\ No newline at end of file
diff --git a/server/internal/models/models.go b/server/internal/models/models.go
index 87829e1..8e0769c 100644
--- a/server/internal/models/models.go
+++ b/server/internal/models/models.go
@@ -62,3 +62,41 @@ type PostMeta struct {
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
+
+// Micro 微语模型
+type Micro struct {
+ ID uint `json:"id" gorm:"primaryKey"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
+ UserID uint `json:"user_id" gorm:"index;not null"`
+ Content string `json:"content" gorm:"type:text;not null"`
+ Images string `json:"images" gorm:"type:text"` // JSON array of image URLs
+ Tags string `json:"tags" gorm:"type:text"` // JSON array of tags
+ LikeCount int `json:"like_count" gorm:"default:0"`
+ CommentCount int `json:"comment_count" gorm:"default:0"`
+ User User `json:"user" gorm:"foreignKey:UserID"`
+}
+
+// MicroLike 微语点赞模型
+type MicroLike struct {
+ ID uint `json:"id" gorm:"primaryKey"`
+ CreatedAt time.Time `json:"created_at"`
+ MicroID uint `json:"micro_id" gorm:"uniqueIndex:idx_micro_user;not null"`
+ UserID *uint `json:"user_id" gorm:"uniqueIndex:idx_micro_user;index"`
+ IPHash string `json:"-" gorm:"uniqueIndex:idx_micro_ip;size:64"`
+}
+
+// MicroComment 微语评论模型
+type MicroComment struct {
+ ID uint `json:"id" gorm:"primaryKey"`
+ CreatedAt time.Time `json:"created_at"`
+ UpdatedAt time.Time `json:"updated_at"`
+ DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
+ MicroID uint `json:"micro_id" gorm:"index;not null"`
+ UserID uint `json:"user_id" gorm:"index;not null"`
+ ParentID *uint `json:"parent_id" gorm:"index"`
+ Content string `json:"content" gorm:"type:text;not null"`
+ User User `json:"user" gorm:"foreignKey:UserID"`
+ Replies []MicroComment `json:"replies,omitempty" gorm:"foreignKey:ParentID"`
+}
diff --git a/src/components/HeatmapCalendar.vue b/src/components/HeatmapCalendar.vue
new file mode 100644
index 0000000..9be9a28
--- /dev/null
+++ b/src/components/HeatmapCalendar.vue
@@ -0,0 +1,230 @@
+
+ 加载中... 暂无微语,来发布第一条吧!{{ title }}
+
+
+
+
+
+
+
+
统计
+ 热门标签
+
+
分享生活点滴,记录灵感瞬间
+
评论
+ + ++ 登录 后参与评论 +
+暂无评论
+