merge from dev branch

pull/351/head
Michael Li 2 years ago
commit e715b0aecb
No known key found for this signature in database

@ -29,6 +29,27 @@ All notable changes to paopao-ce are documented in this file.
Default: ["Base", "Postgres", "Zinc", "LocalOSS", "LoggerZinc", "BigCacheIndex", "Friendship", "Service", "Web:DisallowUserRegister"]
...
```
- add topic follow feature support [#273](https://github.com/rocboss/paopao-ce/pull/273)
mirgration database first(sql ddl file in `scripts/migration/**/*_topic_follow.up.sql`):
```sql
CREATE TABLE `p_topic_user` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`topic_id` BIGINT UNSIGNED NOT NULL COMMENT '标签ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '创建者ID',
`alias_name` VARCHAR ( 255 ) COMMENT '别名',
`remark` VARCHAR ( 512 ) COMMENT '备注',
`quote_num` BIGINT UNSIGNED COMMENT '引用数',
`is_top` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否置顶 0 为未置顶、1 为已置顶',
`created_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间',
`modified_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '修改时间',
`deleted_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除时间',
`is_del` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除',
`reserve_a` VARCHAR ( 255 ) COMMENT '保留字段a',
`reserve_b` VARCHAR ( 255 ) COMMENT '保留字段b',
PRIMARY KEY ( `id` ) USING BTREE,
UNIQUE KEY `idx_topic_user_uid_tid` ( `topic_id`, `user_id` ) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户话题';
```
### Fixed

@ -16,6 +16,7 @@ type Loose interface {
// Chain provide handlers chain for gin
Chain() gin.HandlersChain
TopicList(*web.TopicListReq) (*web.TopicListResp, mir.Error)
GetUserProfile(*web.GetUserProfileReq) (*web.GetUserProfileResp, mir.Error)
GetUserTweets(*web.GetUserTweetsReq) (*web.GetUserTweetsResp, mir.Error)
Timeline(*web.TimelineReq) (*web.TimelineResp, mir.Error)
@ -24,6 +25,7 @@ type Loose interface {
}
type LooseBinding interface {
BindTopicList(*gin.Context) (*web.TopicListReq, mir.Error)
BindGetUserProfile(*gin.Context) (*web.GetUserProfileReq, mir.Error)
BindGetUserTweets(*gin.Context) (*web.GetUserTweetsReq, mir.Error)
BindTimeline(*gin.Context) (*web.TimelineReq, mir.Error)
@ -32,6 +34,7 @@ type LooseBinding interface {
}
type LooseRender interface {
RenderTopicList(*gin.Context, *web.TopicListResp, mir.Error)
RenderGetUserProfile(*gin.Context, *web.GetUserProfileResp, mir.Error)
RenderGetUserTweets(*gin.Context, *web.GetUserTweetsResp, mir.Error)
RenderTimeline(*gin.Context, *web.TimelineResp, mir.Error)
@ -47,6 +50,22 @@ func RegisterLooseServant(e *gin.Engine, s Loose, b LooseBinding, r LooseRender)
router.Use(middlewares...)
// register routes info to router
router.Handle("GET", "/tags", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindTopicList(c)
if err != nil {
r.RenderTopicList(c, nil, err)
return
}
resp, err := s.TopicList(req)
r.RenderTopicList(c, resp, err)
})
router.Handle("GET", "/user/profile", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
@ -105,6 +124,10 @@ func (UnimplementedLooseServant) Chain() gin.HandlersChain {
return nil
}
func (UnimplementedLooseServant) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedLooseServant) GetUserProfile(req *web.GetUserProfileReq) (*web.GetUserProfileResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
@ -124,6 +147,10 @@ type UnimplementedLooseRender struct {
RenderAny func(*gin.Context, any, mir.Error)
}
func (r *UnimplementedLooseRender) RenderTopicList(c *gin.Context, data *web.TopicListResp, err mir.Error) {
r.RenderAny(c, data, err)
}
func (r *UnimplementedLooseRender) RenderGetUserProfile(c *gin.Context, data *web.GetUserProfileResp, err mir.Error) {
r.RenderAny(c, data, err)
}
@ -143,6 +170,12 @@ type UnimplementedLooseBinding struct {
BindAny func(*gin.Context, any) mir.Error
}
func (b *UnimplementedLooseBinding) BindTopicList(c *gin.Context) (*web.TopicListReq, mir.Error) {
obj := new(web.TopicListReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedLooseBinding) BindGetUserProfile(c *gin.Context) (*web.GetUserProfileReq, mir.Error) {
obj := new(web.GetUserProfileReq)
err := b.BindAny(c, obj)

@ -16,6 +16,9 @@ type Priv interface {
// Chain provide handlers chain for gin
Chain() gin.HandlersChain
UnfollowTopic(*web.UnfollowTopicReq) mir.Error
FollowTopic(*web.FollowTopicReq) mir.Error
StickTopic(*web.StickTopicReq) (*web.StickTopicResp, mir.Error)
DeleteCommentReply(*web.DeleteCommentReplyReq) mir.Error
CreateCommentReply(*web.CreateCommentReplyReq) (*web.CreateCommentReplyResp, mir.Error)
DeleteComment(*web.DeleteCommentReq) mir.Error
@ -35,6 +38,9 @@ type Priv interface {
}
type PrivBinding interface {
BindUnfollowTopic(*gin.Context) (*web.UnfollowTopicReq, mir.Error)
BindFollowTopic(*gin.Context) (*web.FollowTopicReq, mir.Error)
BindStickTopic(*gin.Context) (*web.StickTopicReq, mir.Error)
BindDeleteCommentReply(*gin.Context) (*web.DeleteCommentReplyReq, mir.Error)
BindCreateCommentReply(*gin.Context) (*web.CreateCommentReplyReq, mir.Error)
BindDeleteComment(*gin.Context) (*web.DeleteCommentReq, mir.Error)
@ -54,6 +60,9 @@ type PrivBinding interface {
}
type PrivRender interface {
RenderUnfollowTopic(*gin.Context, mir.Error)
RenderFollowTopic(*gin.Context, mir.Error)
RenderStickTopic(*gin.Context, *web.StickTopicResp, mir.Error)
RenderDeleteCommentReply(*gin.Context, mir.Error)
RenderCreateCommentReply(*gin.Context, *web.CreateCommentReplyResp, mir.Error)
RenderDeleteComment(*gin.Context, mir.Error)
@ -80,6 +89,52 @@ func RegisterPrivServant(e *gin.Engine, s Priv, b PrivBinding, r PrivRender) {
router.Use(middlewares...)
// register routes info to router
router.Handle("POST", "/topic/unfollow", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindUnfollowTopic(c)
if err != nil {
r.RenderUnfollowTopic(c, err)
return
}
r.RenderUnfollowTopic(c, s.UnfollowTopic(req))
})
router.Handle("POST", "/topic/follow", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindFollowTopic(c)
if err != nil {
r.RenderFollowTopic(c, err)
return
}
r.RenderFollowTopic(c, s.FollowTopic(req))
})
router.Handle("POST", "/topic/stick", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindStickTopic(c)
if err != nil {
r.RenderStickTopic(c, nil, err)
return
}
resp, err := s.StickTopic(req)
r.RenderStickTopic(c, resp, err)
})
router.Handle("DELETE", "/post/comment/reply", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
@ -311,6 +366,18 @@ func (UnimplementedPrivServant) Chain() gin.HandlersChain {
return nil
}
func (UnimplementedPrivServant) UnfollowTopic(req *web.UnfollowTopicReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) FollowTopic(req *web.FollowTopicReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) StickTopic(req *web.StickTopicReq) (*web.StickTopicResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPrivServant) DeleteCommentReply(req *web.DeleteCommentReplyReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
@ -374,6 +441,18 @@ type UnimplementedPrivRender struct {
RenderAny func(*gin.Context, any, mir.Error)
}
func (r *UnimplementedPrivRender) RenderUnfollowTopic(c *gin.Context, err mir.Error) {
r.RenderAny(c, nil, err)
}
func (r *UnimplementedPrivRender) RenderFollowTopic(c *gin.Context, err mir.Error) {
r.RenderAny(c, nil, err)
}
func (r *UnimplementedPrivRender) RenderStickTopic(c *gin.Context, data *web.StickTopicResp, err mir.Error) {
r.RenderAny(c, data, err)
}
func (r *UnimplementedPrivRender) RenderDeleteCommentReply(c *gin.Context, err mir.Error) {
r.RenderAny(c, nil, err)
}
@ -437,6 +516,24 @@ type UnimplementedPrivBinding struct {
BindAny func(*gin.Context, any) mir.Error
}
func (b *UnimplementedPrivBinding) BindUnfollowTopic(c *gin.Context) (*web.UnfollowTopicReq, mir.Error) {
obj := new(web.UnfollowTopicReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPrivBinding) BindFollowTopic(c *gin.Context) (*web.FollowTopicReq, mir.Error) {
obj := new(web.FollowTopicReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPrivBinding) BindStickTopic(c *gin.Context) (*web.StickTopicReq, mir.Error) {
obj := new(web.StickTopicReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPrivBinding) BindDeleteCommentReply(c *gin.Context) (*web.DeleteCommentReplyReq, mir.Error) {
obj := new(web.DeleteCommentReplyReq)
err := b.BindAny(c, obj)

@ -13,7 +13,6 @@ import (
)
type Pub interface {
TopicList(*web.TopicListReq) (*web.TopicListResp, mir.Error)
TweetComments(*web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error)
TweetDetail(*web.TweetDetailReq) (*web.TweetDetailResp, mir.Error)
SendCaptcha(*web.SendCaptchaReq) mir.Error
@ -26,7 +25,6 @@ type Pub interface {
}
type PubBinding interface {
BindTopicList(*gin.Context) (*web.TopicListReq, mir.Error)
BindTweetComments(*gin.Context) (*web.TweetCommentsReq, mir.Error)
BindTweetDetail(*gin.Context) (*web.TweetDetailReq, mir.Error)
BindSendCaptcha(*gin.Context) (*web.SendCaptchaReq, mir.Error)
@ -37,7 +35,6 @@ type PubBinding interface {
}
type PubRender interface {
RenderTopicList(*gin.Context, *web.TopicListResp, mir.Error)
RenderTweetComments(*gin.Context, *web.TweetCommentsResp, mir.Error)
RenderTweetDetail(*gin.Context, *web.TweetDetailResp, mir.Error)
RenderSendCaptcha(*gin.Context, mir.Error)
@ -54,22 +51,6 @@ func RegisterPubServant(e *gin.Engine, s Pub, b PubBinding, r PubRender) {
router := e.Group("v1")
// register routes info to router
router.Handle("GET", "/tags", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req, err := b.BindTopicList(c)
if err != nil {
r.RenderTopicList(c, nil, err)
return
}
resp, err := s.TopicList(req)
r.RenderTopicList(c, resp, err)
})
router.Handle("GET", "/post/comments", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
@ -177,10 +158,6 @@ func RegisterPubServant(e *gin.Engine, s Pub, b PubBinding, r PubRender) {
type UnimplementedPubServant struct {
}
func (UnimplementedPubServant) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedPubServant) TweetComments(req *web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
@ -216,10 +193,6 @@ type UnimplementedPubRender struct {
RenderAny func(*gin.Context, any, mir.Error)
}
func (r *UnimplementedPubRender) RenderTopicList(c *gin.Context, data *web.TopicListResp, err mir.Error) {
r.RenderAny(c, data, err)
}
func (r *UnimplementedPubRender) RenderTweetComments(c *gin.Context, data *web.TweetCommentsResp, err mir.Error) {
r.RenderAny(c, data, err)
}
@ -255,12 +228,6 @@ type UnimplementedPubBinding struct {
BindAny func(*gin.Context, any) mir.Error
}
func (b *UnimplementedPubBinding) BindTopicList(c *gin.Context) (*web.TopicListReq, mir.Error) {
obj := new(web.TopicListReq)
err := b.BindAny(c, obj)
return obj, err
}
func (b *UnimplementedPubBinding) BindTweetComments(c *gin.Context) (*web.TweetCommentsReq, mir.Error) {
obj := new(web.TweetCommentsReq)
err := b.BindAny(c, obj)

@ -42,11 +42,11 @@ require (
gopkg.in/resty.v1 v1.12.0
gorm.io/driver/mysql v1.4.7
gorm.io/driver/postgres v1.5.0
gorm.io/driver/sqlite v1.5.0
gorm.io/driver/sqlite v1.4.4
gorm.io/gorm v1.25.0
gorm.io/plugin/dbresolver v1.4.1
gorm.io/plugin/soft_delete v1.2.0
modernc.org/sqlite v1.21.1
modernc.org/sqlite v1.21.2
)
require (
@ -130,7 +130,7 @@ require (
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.22.3 // indirect
modernc.org/libc v1.22.4 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/opt v0.1.3 // indirect

@ -2103,13 +2103,14 @@ gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5d
gorm.io/driver/postgres v1.5.0 h1:u2FXTy14l45qc3UeCJ7QaAXZmZfDDv0YrthvmRq1l0U=
gorm.io/driver/postgres v1.5.0/go.mod h1:FUZXzO+5Uqg5zzwzv4KK49R8lvGIyscBOqYrtI1Ce9A=
gorm.io/driver/sqlite v1.1.3/go.mod h1:AKDgRWk8lcSQSw+9kxCJnX/yySj8G3rdwYlU57cB45c=
gorm.io/driver/sqlite v1.5.0 h1:zKYbzRCpBrT1bNijRnxLDJWPjVfImGEn0lSnUY5gZ+c=
gorm.io/driver/sqlite v1.5.0/go.mod h1:kDMDfntV9u/vuMmz8APHtHF0b4nyBB7sfCieC6G8k8I=
gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc=
gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI=
gorm.io/gorm v1.20.1/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.21.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
gorm.io/gorm v1.23.0/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk=
gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.3/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA=
gorm.io/gorm v1.24.7-0.20230306060331-85eaf9eeda11/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k=
gorm.io/gorm v1.25.0 h1:+KtYtb2roDz14EQe4bla8CbQlmb9dN3VejSai3lprfU=
@ -2191,8 +2192,8 @@ modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/internal v1.0.0/go.mod h1:VUD/+JAkhCpvkUitlEOnhpVxCgsBI90oTzSCRcqQVSM=
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.9.5/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
modernc.org/libc v1.22.4 h1:wymSbZb0AlrjdAVX3cjreCHTPCpPARbQXNz6BHPzdwQ=
modernc.org/libc v1.22.4/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY=
modernc.org/lldb v1.0.0/go.mod h1:jcRvJGWfCGodDZz8BPwiKMJxGJngQ/5DrRapkQnLob8=
modernc.org/mathutil v1.0.0/go.mod h1:wU0vUrJsVWBZ4P6e7xtFJEhFSNsfRLJ8H458uRjg03k=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
@ -2208,8 +2209,8 @@ modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/ql v1.0.0/go.mod h1:xGVyrLIatPcO2C1JvI/Co8c0sr6y91HKFNy4pt9JXEY=
modernc.org/sortutil v1.1.0/go.mod h1:ZyL98OQHJgH9IEfN71VsamvJgrtRX9Dj2gX+vH86L1k=
modernc.org/sqlite v1.10.6/go.mod h1:Z9FEjUtZP4qFEg6/SiADg9XCER7aYy9a/j7Pg9P7CPs=
modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU=
modernc.org/sqlite v1.21.1/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
modernc.org/sqlite v1.21.2 h1:ixuUG0QS413Vfzyx6FWx6PYTmHaOegTY+hjzhn7L+a0=
modernc.org/sqlite v1.21.2/go.mod h1:cxbLkB5WS32DnQqeH4h4o1B0eMr8W/y8/RGuxQ3JsC0=
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=

@ -8,6 +8,8 @@ const (
// 标签类型
TagTypeHot TagType = "hot"
TagTypeNew TagType = "new"
TagTypeFollow TagType = "follow"
TagTypeHotExtral TagType = "hot_extral"
)
type (
@ -36,6 +38,8 @@ type TagItem struct {
User *UserInfo `json:"user" db:"u"`
Tag string `json:"tag"`
QuoteNum int64 `json:"quote_num"`
IsFollowing int8 `json:"is_following"`
IsTop int8 `json:"is_top"`
}
func (t *TagInfo) Format() *TagItem {
@ -45,5 +49,7 @@ func (t *TagInfo) Format() *TagItem {
User: &UserInfo{},
Tag: t.Tag,
QuoteNum: t.QuoteNum,
IsFollowing: 0,
IsTop: 0,
}
}

@ -14,6 +14,12 @@ type TopicService interface {
DecrTagsById(ids []int64) error
ListTags(typ cs.TagType, limit int, offset int) (cs.TagList, error)
TagsByKeyword(keyword string) (cs.TagInfoList, error)
GetHotTags(userId int64, limit int, offset int) (cs.TagList, error)
GetNewestTags(userId int64, limit int, offset int) (cs.TagList, error)
GetFollowTags(userId int64, limit int, offset int) (cs.TagList, error)
FollowTopic(userId int64, topicId int64) error
UnfollowTopic(userId int64, topicId int64) error
StickTopic(userId int64, topicId int64) (int8, error)
}
// TopicServantA 话题服务(版本A)

@ -16,12 +16,27 @@ type Tag struct {
Tag string `json:"tag"`
QuoteNum int64 `json:"quote_num"`
}
type TopicUser struct {
*Model
UserID int64 `json:"user_id"`
TopicID int64 `json:"topic_id"`
AliasName string `json:"-"`
Remark string `json:"-"`
QuoteNum int64 `json:"quote_num"`
IsTop int8 `json:"is_top"`
ReserveA string `json:"-"`
ReserveB string `json:"-"`
}
type TagFormated struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
User *UserFormated `json:"user"`
Tag string `json:"tag"`
QuoteNum int64 `json:"quote_num"`
IsFollowing int8 `json:"is_following"`
IsTop int8 `json:"is_top"`
}
func (t *Tag) Format() *TagFormated {
@ -35,6 +50,8 @@ func (t *Tag) Format() *TagFormated {
User: &UserFormated{},
Tag: t.Tag,
QuoteNum: t.QuoteNum,
IsFollowing: 0,
IsTop: 0,
}
}

@ -5,6 +5,7 @@
package jinzhu
import (
"errors"
"strings"
"github.com/rocboss/paopao-ce/internal/core"
@ -20,15 +21,24 @@ var (
type topicSrv struct {
db *gorm.DB
tnTopicUser string
tnDotTopicUser string
}
type topicSrvA struct {
db *gorm.DB
}
type topicInfo struct {
TopicId int64
IsTop int8
}
func newTopicService(db *gorm.DB) core.TopicService {
return &topicSrv{
db: db,
tnTopicUser: db.NamingStrategy.TableName("TopicUser"),
tnDotTopicUser: db.NamingStrategy.TableName("TopicUser") + ".",
}
}
@ -76,6 +86,70 @@ func (s *topicSrv) ListTags(typ cs.TagType, offset, limit int) (res cs.TagList,
"ORDER": "id DESC",
}
}
return s.listTags(conditions, limit, offset)
}
func (s *topicSrv) GetHotTags(userId int64, limit int, offset int) (cs.TagList, error) {
tags, err := s.listTags(&core.ConditionsT{
"ORDER": "quote_num DESC",
}, limit, offset)
if err != nil {
return nil, err
}
return s.tagsFormatA(userId, tags)
}
func (s *topicSrv) GetNewestTags(userId int64, limit int, offset int) (cs.TagList, error) {
tags, err := s.listTags(&core.ConditionsT{
"ORDER": "id DESC",
}, limit, offset)
if err != nil {
return nil, err
}
return s.tagsFormatA(userId, tags)
}
func (s *topicSrv) GetFollowTags(userId int64, limit int, offset int) (cs.TagList, error) {
if userId < 0 {
return nil, nil
}
userTopics := []*topicInfo{}
err := s.db.Model(&dbr.TopicUser{}).
Where("user_id=?", userId).
Order("is_top DESC").
Limit(limit).
Offset(offset).
Find(&userTopics).Error
if err != nil {
return nil, err
}
userTopicsMap := make(map[int64]*topicInfo, len(userTopics))
topicIds := make([]int64, 0, len(userTopics))
topicIdsMap := make(map[int64]int, len(userTopics))
for idx, info := range userTopics {
userTopicsMap[info.TopicId] = info
topicIds = append(topicIds, info.TopicId)
topicIdsMap[info.TopicId] = idx
}
var tags cs.TagInfoList
err = s.db.Model(&dbr.Tag{}).Where("quote_num > 0 and id in ?", topicIds).Order("quote_num DESC").Find(&tags).Error
if err != nil {
return nil, err
}
formtedTags, err := s.tagsFormatB(userTopicsMap, tags)
if err != nil {
return nil, err
}
// 置顶排序后处理
// TODO: 垃圾办法最好是topic_user join tag 一次查询但是gorm的join真他喵的别扭F*K
res := make(cs.TagList, len(topicIds), len(topicIds))
for _, tag := range formtedTags {
res[topicIdsMap[tag.ID]] = tag
}
return res, nil
}
func (s *topicSrv) listTags(conditions *core.ConditionsT, limit int, offset int) (res cs.TagList, err error) {
// TODO: 优化查询方式,直接返回[]*core.Tag, 目前保持先转换一下
var (
tags []*dbr.Tag
@ -113,6 +187,65 @@ func (s *topicSrv) ListTags(typ cs.TagType, offset, limit int) (res cs.TagList,
return
}
func (s *topicSrv) tagsFormatA(userId int64, tags cs.TagList) (cs.TagList, error) {
// 获取创建者User IDs
tagIds := []int64{}
for _, tag := range tags {
tagIds = append(tagIds, tag.ID)
}
// 填充话题follow信息
if userId > -1 {
userTopics := []*topicInfo{}
err := s.db.Model(&dbr.TopicUser{}).Where("is_del=0 and user_id=? and topic_id in ?", userId, tagIds).Find(&userTopics).Error
if err != nil {
return nil, err
}
userTopicsMap := make(map[int64]*topicInfo, len(userTopics))
for _, info := range userTopics {
userTopicsMap[info.TopicId] = info
}
for _, tag := range tags {
if info, exist := userTopicsMap[tag.ID]; exist {
tag.IsFollowing, tag.IsTop = 1, info.IsTop
}
}
}
return tags, nil
}
func (s *topicSrv) tagsFormatB(userTopicsMap map[int64]*topicInfo, tags cs.TagInfoList) (cs.TagList, error) {
// 获取创建者User IDs
userIds := []int64{}
tagIds := []int64{}
for _, tag := range tags {
userIds = append(userIds, tag.UserID)
tagIds = append(tagIds, tag.ID)
}
users, err := (&dbr.User{}).ListUserInfoById(s.db, userIds)
if err != nil {
return nil, err
}
tagList := cs.TagList{}
for _, tag := range tags {
tagFormated := tag.Format()
for _, user := range users {
if user.ID == tagFormated.UserID {
tagFormated.User = user
}
}
tagList = append(tagList, tagFormated)
}
// 填充话题follow信息
if len(userTopicsMap) > 0 {
for _, tag := range tagList {
if info, exist := userTopicsMap[tag.ID]; exist {
tag.IsFollowing, tag.IsTop = 1, info.IsTop
}
}
}
return tagList, nil
}
func (s *topicSrv) TagsByKeyword(keyword string) (res cs.TagInfoList, err error) {
keyword = "%" + strings.Trim(keyword, " ") + "%"
tag := &dbr.Tag{}
@ -241,3 +374,39 @@ func (s *topicSrvA) TagsByKeyword(keyword string) (res cs.TagInfoList, err error
}
return
}
func (s *topicSrv) FollowTopic(userId int64, topicId int64) (err error) {
return s.db.Create(&dbr.TopicUser{
UserID: userId,
TopicID: topicId,
IsTop: 0,
}).Error
}
func (s *topicSrv) UnfollowTopic(userId int64, topicId int64) error {
return s.db.Exec("DELETE FROM "+s.tnTopicUser+" WHERE user_id=? AND topic_id=?", userId, topicId).Error
}
func (s *topicSrv) StickTopic(userId int64, topicId int64) (status int8, err error) {
db := s.db.Begin()
defer db.Rollback()
m := &dbr.TopicUser{}
err = db.Model(m).
Where("user_id=? and topic_id=?", userId, topicId).
UpdateColumn("is_top", gorm.Expr("1-is_top")).Error
if err != nil {
return
}
status = -1
err = db.Model(m).Where("user_id=? and topic_id=?", userId, topicId).Select("is_top").Scan(&status).Error
if err != nil {
return
}
if status < 0 {
return -1, errors.New("topic not exist")
}
db.Commit()
return
}

@ -6,9 +6,19 @@ package web
import (
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/core/cs"
"github.com/rocboss/paopao-ce/internal/servants/base"
)
const (
TagTypeHot = cs.TagTypeHot
TagTypeNew = cs.TagTypeNew
TagTypeFollow = cs.TagTypeFollow
TagTypeHotExtral = cs.TagTypeHotExtral
)
type TagType = cs.TagType
type TimelineReq struct {
BaseInfo `form:"-" binding:"-"`
Query string `form:"query"`
@ -44,6 +54,20 @@ type GetUserProfileResp struct {
IsFriend bool `json:"is_friend"`
}
type TopicListReq struct {
SimpleInfo `form:"-" binding:"-"`
Type TagType `json:"type" form:"type" binding:"required"`
Num int `json:"num" form:"num" binding:"required"`
ExtralNum int `json:"extral_num" form:"extral_num"`
}
// TopicListResp 主题返回值
// TODO: 优化内容定义
type TopicListResp struct {
Topics cs.TagList `json:"topics"`
ExtralTopics cs.TagList `json:"extral_topics,omitempty"`
}
func (r *GetUserTweetsReq) SetPageInfo(page int, pageSize int) {
r.Page, r.PageSize = page, pageSize
}

@ -147,6 +147,25 @@ type DownloadAttachmentResp struct {
SignedURL string `json:"signed_url"`
}
type StickTopicReq struct {
SimpleInfo `json:"-" binding:"-"`
TopicId int64 `json:"topic_id" binding:"required"`
}
type StickTopicResp struct {
StickStatus int8 `json:"top_status"`
}
type FollowTopicReq struct {
SimpleInfo `json:"-" binding:"-"`
TopicId int64 `json:"topic_id" binding:"required"`
}
type UnfollowTopicReq struct {
SimpleInfo `json:"-" binding:"-"`
TopicId int64 `json:"topic_id" binding:"required"`
}
// Check 检查PostContentItem属性
func (p *PostContentItem) Check(acs core.AttachmentCheckService) error {
// 检查附件是否是本站资源

@ -6,13 +6,10 @@ package web
import (
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/core/cs"
"github.com/rocboss/paopao-ce/internal/servants/base"
"github.com/rocboss/paopao-ce/pkg/version"
)
type TagType = cs.TagType
type TweetDetailReq struct {
TweetId int64 `form:"id"`
}
@ -28,17 +25,6 @@ type TweetCommentsReq struct {
type TweetCommentsResp base.PageResp
type TopicListReq struct {
Type TagType `json:"type" form:"type" binding:"required"`
Num int `json:"num" form:"num" binding:"required"`
}
// TopicListResp 主题返回值
// TODO: 优化内容定义
type TopicListResp struct {
Topics cs.TagList `json:"topics"`
}
type GetCaptchaResp struct {
Id string `json:"id"`
Content string `json:"b64s"`

@ -9,6 +9,7 @@ import (
"github.com/gin-gonic/gin"
api "github.com/rocboss/paopao-ce/auto/api/v1"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/core/cs"
"github.com/rocboss/paopao-ce/internal/model/web"
"github.com/rocboss/paopao-ce/internal/servants/base"
"github.com/rocboss/paopao-ce/internal/servants/chain"
@ -149,6 +150,41 @@ func (s *looseSrv) GetUserProfile(req *web.GetUserProfileReq) (*web.GetUserProfi
}, nil
}
func (s *looseSrv) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Error) {
var (
tags, extralTags cs.TagList
err error
)
num := req.Num
switch req.Type {
case web.TagTypeHot:
tags, err = s.Ds.GetHotTags(req.Uid, num, 0)
case web.TagTypeNew:
tags, err = s.Ds.GetNewestTags(req.Uid, num, 0)
case web.TagTypeFollow:
tags, err = s.Ds.GetFollowTags(req.Uid, num, 0)
case web.TagTypeHotExtral:
extralNum := req.ExtralNum
if extralNum <= 0 {
extralNum = num
}
tags, err = s.Ds.GetHotTags(req.Uid, num, 0)
if err == nil {
extralTags, err = s.Ds.GetFollowTags(req.Uid, extralNum, 0)
}
default:
// TODO: return good error
err = _errGetPostTagsFailed
}
if err != nil {
return nil, _errGetPostTagsFailed
}
return &web.TopicListResp{
Topics: tags,
ExtralTopics: extralTags,
}, nil
}
func newLooseSrv(s *base.DaoServant) api.Loose {
return &looseSrv{
DaoServant: s,

@ -144,6 +144,33 @@ func (s *privSrv) Chain() gin.HandlersChain {
return gin.HandlersChain{chain.JWT(), chain.Priv()}
}
func (s *privSrv) UnfollowTopic(req *web.UnfollowTopicReq) mir.Error {
if err := s.Ds.UnfollowTopic(req.Uid, req.TopicId); err != nil {
logrus.Errorf("user(%d) unfollow topic(%d) failed: %s", req.Uid, req.TopicId, err)
return _errUnfollowTopicFailed
}
return nil
}
func (s *privSrv) FollowTopic(req *web.FollowTopicReq) mir.Error {
if err := s.Ds.FollowTopic(req.Uid, req.TopicId); err != nil {
logrus.Errorf("user(%d) follow topic(%d) failed: %s", req.Uid, req.TopicId, err)
return _errFollowTopicFailed
}
return nil
}
func (s *privSrv) StickTopic(req *web.StickTopicReq) (*web.StickTopicResp, mir.Error) {
status, err := s.Ds.StickTopic(req.Uid, req.TopicId)
if err != nil {
logrus.Errorf("user(%d) stick topic(%d) failed: %s", req.Uid, req.TopicId, err)
return nil, _errStickTopicFailed
}
return &web.StickTopicResp{
StickStatus: status,
}, nil
}
func (s *privSrv) UploadAttachment(req *web.UploadAttachmentReq) (*web.UploadAttachmentResp, mir.Error) {
defer req.File.Close()

@ -18,7 +18,6 @@ import (
"github.com/gin-gonic/gin"
"github.com/gofrs/uuid"
api "github.com/rocboss/paopao-ce/auto/api/v1"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/model/web"
"github.com/rocboss/paopao-ce/internal/servants/base"
@ -66,21 +65,6 @@ func (b *pubBinding) BindTweetComments(c *gin.Context) (*web.TweetCommentsReq, m
}, nil
}
func (s *pubSrv) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Error) {
// tags, err := broker.GetPostTags(&param)
num := req.Num
if num > conf.AppSetting.MaxPageSize {
num = conf.AppSetting.MaxPageSize
}
tags, err := s.Ds.ListTags(req.Type, num, 0)
if err != nil {
return nil, _errGetPostTagsFailed
}
return &web.TopicListResp{
Topics: tags,
}, nil
}
func (s *pubSrv) TweetComments(req *web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error) {
sort := "id ASC"
if req.SortStrategy == "newest" {

@ -78,6 +78,10 @@ var (
_errGetContactsFailed = xerror.NewError(80007, "获取联系人列表失败")
_errNoActionToSelf = xerror.NewError(80008, "不允许对自己操作")
_errFollowTopicFailed = xerror.NewError(90001, "关注话题失败")
_errUnfollowTopicFailed = xerror.NewError(90002, "取消关注话题失败")
_errStickTopicFailed = xerror.NewError(90003, "更行话题置顶状态失败")
_errFileUploadFailed = xerror.NewError(10200, "文件上传失败")
_errFileInvalidExt = xerror.NewError(10201, "文件类型不合法")
_errFileInvalidSize = xerror.NewError(10202, "文件大小超限")

@ -23,4 +23,7 @@ type Loose struct {
// GetUserProfile 获取用户基本信息
GetUserProfile func(Get, web.GetUserProfileReq) web.GetUserProfileResp `mir:"/user/profile"`
// TopicList 获取话题列表
TopicList func(Get, web.TopicListReq) web.TopicListResp `mir:"/tags"`
}

@ -56,4 +56,13 @@ type Priv struct {
// DeleteCommentReply 删除评论回复
DeleteCommentReply func(Delete, web.DeleteCommentReplyReq) `mir:"/post/comment/reply"`
// StickTopic 置顶动态
StickTopic func(Post, web.StickTopicReq) web.StickTopicResp `mir:"/topic/stick"`
// FollowTopic 关注话题
FollowTopic func(Post, web.FollowTopicReq) `mir:"/topic/follow"`
// UnfollowTopic 取消关注话题
UnfollowTopic func(Post, web.UnfollowTopicReq) `mir:"/topic/unfollow"`
}

@ -34,7 +34,4 @@ type Pub struct {
// TweetComments 获取动态评论
TweetComments func(Get, web.TweetCommentsReq) web.TweetCommentsResp `mir:"/post/comments"`
// TopicList 获取话题列表
TopicList func(Get, web.TopicListReq) web.TopicListResp `mir:"/tags"`
}

@ -0,0 +1 @@
DROP TABLE IF EXISTS `p_topic_user`;

@ -0,0 +1,17 @@
CREATE TABLE `p_topic_user` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`topic_id` BIGINT UNSIGNED NOT NULL COMMENT '标签ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '创建者ID',
`alias_name` VARCHAR ( 255 ) COMMENT '别名',
`remark` VARCHAR ( 512 ) COMMENT '备注',
`quote_num` BIGINT UNSIGNED COMMENT '引用数',
`is_top` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否置顶 0 为未置顶、1 为已置顶',
`created_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间',
`modified_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '修改时间',
`deleted_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除时间',
`is_del` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除',
`reserve_a` VARCHAR ( 255 ) COMMENT '保留字段a',
`reserve_b` VARCHAR ( 255 ) COMMENT '保留字段b',
PRIMARY KEY ( `id` ) USING BTREE,
UNIQUE KEY `idx_topic_user_uid_tid` ( `topic_id`, `user_id` ) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户话题';

@ -0,0 +1 @@
DROP TABLE IF EXISTS `p_topic_user`;

@ -0,0 +1,16 @@
CREATE TABLE p_topic_user (
ID BIGSERIAL PRIMARY KEY,
topic_id BIGINT NOT NULL,-- 标签ID
user_id BIGINT NOT NULL,-- 创建者ID
alias_name VARCHAR ( 255 ),-- 别名
remark VARCHAR ( 512 ),-- 备注
quote_num BIGINT,-- 引用数
is_top SMALLINT NOT NULL DEFAULT 0,-- 是否置顶 0 为未置顶、1 为已置顶
created_on BIGINT NOT NULL DEFAULT 0,-- 创建时间
modified_on BIGINT NOT NULL DEFAULT 0,-- 修改时间
deleted_on BIGINT NOT NULL DEFAULT 0,-- 删除时间
is_del SMALLINT NOT NULL DEFAULT 0,-- 是否删除 0 为未删除、1 为已删除
reserve_a VARCHAR ( 255 ),-- 保留字段a
reserve_b VARCHAR ( 255 ) -- 保留字段b
);
CREATE UNIQUE INDEX idx_topic_user_uid_tid ON p_topic_user USING btree ( topic_id, user_id );

@ -0,0 +1 @@
DROP TABLE IF EXISTS `p_topic_user`;

@ -0,0 +1,17 @@
CREATE TABLE "p_topic_user" (
"id" integer,
"topic_id" integer NOT NULL,-- 标签ID
"user_id" integer NOT NULL,-- 创建者ID
"alias_name" text ( 255 ),-- 别名
"remark" text ( 512 ),-- 备注
"quote_num" integer,-- 引用数
"is_top" integer NOT NULL DEFAULT 0,-- 是否置顶 0 为未置顶、1 为已置顶
"created_on" integer NOT NULL DEFAULT 0,-- 创建时间
"modified_on" integer NOT NULL DEFAULT 0,-- 修改时间
"deleted_on" integer NOT NULL DEFAULT 0,-- 删除时间
"is_del" integer NOT NULL DEFAULT 0,-- 是否删除 0 为未删除、1 为已删除
"reserve_a" text,-- 保留字段a
"reserve_b" text,-- 保留字段b
PRIMARY KEY ( "id" )
);
CREATE UNIQUE INDEX "idx_topic_user_uid_tid" ON "p_topic_user" ( "topic_id", "user_id" );

@ -247,6 +247,28 @@ CREATE TABLE `p_tag` (
KEY `idx_tag_quote_num` (`quote_num`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=9000065 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='标签';
-- ----------------------------
-- Table structure for p_topic_user
-- ----------------------------
DROP TABLE IF EXISTS `p_topic_user`;
CREATE TABLE `p_topic_user` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`topic_id` BIGINT UNSIGNED NOT NULL COMMENT '标签ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '创建者ID',
`alias_name` VARCHAR ( 255 ) COMMENT '别名',
`remark` VARCHAR ( 512 ) COMMENT '备注',
`quote_num` BIGINT UNSIGNED COMMENT '引用数',
`is_top` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否置顶 0 为未置顶、1 为已置顶',
`created_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '创建时间',
`modified_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '修改时间',
`deleted_on` BIGINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '删除时间',
`is_del` TINYINT UNSIGNED NOT NULL DEFAULT '0' COMMENT '是否删除 0 为未删除、1 为已删除',
`reserve_a` VARCHAR ( 255 ) COMMENT '保留字段a',
`reserve_b` VARCHAR ( 255 ) COMMENT '保留字段b',
PRIMARY KEY ( `id` ) USING BTREE,
UNIQUE KEY `idx_topic_user_uid_tid` ( `topic_id`, `user_id` ) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户话题';
-- ----------------------------
-- Table structure for p_user
-- ----------------------------

@ -204,9 +204,28 @@ CREATE UNIQUE INDEX idx_tag_tag ON p_tag USING btree (tag);
CREATE INDEX idx_tag_user_id ON p_tag USING btree (user_id);
CREATE INDEX idx_tag_quote_num ON p_tag USING btree (quote_num);
DROP TABLE IF EXISTS p_topic_user;
CREATE TABLE p_topic_user (
ID BIGSERIAL PRIMARY KEY,
topic_id BIGINT NOT NULL,-- 标签ID
user_id BIGINT NOT NULL,-- 创建者ID
alias_name VARCHAR ( 255 ),-- 别名
remark VARCHAR ( 512 ),-- 备注
quote_num BIGINT,-- 引用数
is_top SMALLINT NOT NULL DEFAULT 0,-- 是否置顶 0 为未置顶、1 为已置顶
created_on BIGINT NOT NULL DEFAULT 0,-- 创建时间
modified_on BIGINT NOT NULL DEFAULT 0,-- 修改时间
deleted_on BIGINT NOT NULL DEFAULT 0,-- 删除时间
is_del SMALLINT NOT NULL DEFAULT 0,-- 是否删除 0 为未删除、1 为已删除
reserve_a VARCHAR ( 255 ),-- 保留字段a
reserve_b VARCHAR ( 255 ) -- 保留字段b
);
CREATE UNIQUE INDEX idx_topic_user_uid_tid ON p_topic_user USING btree ( topic_id, user_id );
CREATE SEQUENCE IF NOT EXISTS user_id_seq AS BIGINT MINVALUE 100058 NO MAXVALUE;
DROP TABLE IF EXISTS p_user;
CREATE TABLE p_user (
id BIGSERIAL PRIMARY KEY,
id BIGINT NOT NULL DEFAULT nextval('user_id_seq'::regclass),
nickname VARCHAR(32) NOT NULL DEFAULT '',
username VARCHAR(32) NOT NULL DEFAULT '',
phone VARCHAR(16) NOT NULL DEFAULT '', -- 手机号
@ -219,7 +238,8 @@ CREATE TABLE p_user (
created_on BIGINT NOT NULL DEFAULT 0,
modified_on BIGINT NOT NULL DEFAULT 0,
deleted_on BIGINT NOT NULL DEFAULT 0,
is_del SMALLINT NOT NULL DEFAULT 0
is_del SMALLINT NOT NULL DEFAULT 0,
PRIMARY KEY (id)
);
CREATE UNIQUE INDEX idx_user_username ON p_user USING btree (username);
CREATE INDEX idx_user_phone ON p_user USING btree (phone);

@ -255,6 +255,27 @@ CREATE TABLE "p_tag" (
PRIMARY KEY ("id")
);
-- ----------------------------
-- Table structure for p_topic_user
-- ----------------------------
DROP TABLE IF EXISTS "p_topic_user";
CREATE TABLE "p_topic_user" (
"id" integer,
"topic_id" integer NOT NULL,-- 标签ID
"user_id" integer NOT NULL,-- 创建者ID
"alias_name" text ( 255 ),-- 别名
"remark" text ( 512 ),-- 备注
"quote_num" integer,-- 引用数
"is_top" integer NOT NULL DEFAULT 0,-- 是否置顶 0 为未置顶、1 为已置顶
"created_on" integer NOT NULL DEFAULT 0,-- 创建时间
"modified_on" integer NOT NULL DEFAULT 0,-- 修改时间
"deleted_on" integer NOT NULL DEFAULT 0,-- 删除时间
"is_del" integer NOT NULL DEFAULT 0,-- 是否删除 0 为未删除、1 为已删除
"reserve_a" text,-- 保留字段a
"reserve_b" text,-- 保留字段b
PRIMARY KEY ( "id" )
);
-- ----------------------------
-- Table structure for p_user
-- ----------------------------
@ -483,6 +504,15 @@ ON "p_tag" (
"user_id" ASC
);
-- ----------------------------
-- Indexes structure for table p_topic_user
-- ----------------------------
CREATE UNIQUE INDEX "idx_topic_user_uid_tid"
ON "p_topic_user" (
"topic_id",
"user_id"
);
-- ----------------------------
-- Indexes structure for table p_user
-- ----------------------------

@ -1 +1 @@
import{_ as s}from"./main-nav.vue_vue_type_style_index_0_lang-d6d2ed7f.js";import{u as a}from"./vue-router-29025daf.js";import{F as i,e as c,a2 as u}from"./naive-ui-ddb574dd.js";import{d as l,c as d,L as t,Y as o,o as f,e as x}from"./@vue-f70ab1bd.js";import{_ as g}from"./index-ce5b62d8.js";import"./vuex-cc1858c6.js";import"./vooks-dfdd6eef.js";import"./evtd-b614532e.js";import"./@vicons-fc06a0bb.js";import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./@css-render-66126308.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";import"./axios-707ed124.js";/* empty css */const v=l({__name:"404",setup(h){const e=a(),_=()=>{e.push({path:"/"})};return(k,w)=>{const n=s,p=c,r=u,m=i;return f(),d("div",null,[t(n,{title:"404"}),t(m,{class:"main-content-wrap wrap404",bordered:""},{default:o(()=>[t(r,{status:"404",title:"404 资源不存在",description:"再看看其他的吧"},{footer:o(()=>[t(p,{onClick:_},{default:o(()=>[x("回主页")]),_:1})]),_:1})]),_:1})])}}});const K=g(v,[["__scopeId","data-v-e62daa85"]]);export{K as default};
import{_ as s}from"./main-nav.vue_vue_type_style_index_0_lang-5f0e81a4.js";import{u as a}from"./vue-router-29025daf.js";import{F as i,e as c,a2 as u}from"./naive-ui-ddb574dd.js";import{d as l,c as d,L as t,Y as o,o as f,e as x}from"./@vue-f70ab1bd.js";import{_ as g}from"./index-f6017bc3.js";import"./vuex-cc1858c6.js";import"./vooks-dfdd6eef.js";import"./evtd-b614532e.js";import"./@vicons-2f3cb6b9.js";import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./@css-render-66126308.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";import"./axios-707ed124.js";/* empty css */const v=l({__name:"404",setup(h){const e=a(),_=()=>{e.push({path:"/"})};return(k,w)=>{const n=s,p=c,r=u,m=i;return f(),d("div",null,[t(n,{title:"404"}),t(m,{class:"main-content-wrap wrap404",bordered:""},{default:o(()=>[t(r,{status:"404",title:"404 资源不存在",description:"再看看其他的吧"},{footer:o(()=>[t(p,{onClick:_},{default:o(()=>[x("回主页")]),_:1})]),_:1})]),_:1})])}}});const K=g(v,[["__scopeId","data-v-e62daa85"]]);export{K as default};

File diff suppressed because one or more lines are too long

@ -1 +1 @@
import{_ as F}from"./post-skeleton-a5bf805a.js";import{_ as N}from"./main-nav.vue_vue_type_style_index_0_lang-d6d2ed7f.js";import{u as z}from"./vuex-cc1858c6.js";import{b as A}from"./vue-router-29025daf.js";import{a as R}from"./formatTime-936c40eb.js";import{d as S,r as n,j as V,c as o,L as a,Y as p,o as e,U as u,O as l,F as I,$ as L,K as M,a as s,M as _,a1 as O}from"./@vue-f70ab1bd.js";import{F as P,G as U,I as $,H as j}from"./naive-ui-ddb574dd.js";import{_ as q}from"./index-ce5b62d8.js";import"./vooks-dfdd6eef.js";import"./evtd-b614532e.js";import"./@vicons-fc06a0bb.js";import"./moment-b7869f98.js";import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./@css-render-66126308.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";import"./axios-707ed124.js";/* empty css */const D={key:0,class:"pagination-wrap"},E={key:0,class:"skeleton-wrap"},G={key:1},H={key:0,class:"empty-wrap"},K={class:"bill-line"},T=S({__name:"Anouncement",setup(Y){const d=z(),g=A(),v=n(!1),r=n([]),i=n(+g.query.p||1),f=n(20),c=n(0),h=m=>{i.value=m};return V(()=>{}),(m,J)=>{const y=N,k=U,x=F,w=$,B=j,C=P;return e(),o("div",null,[a(y,{title:"公告"}),a(C,{class:"main-content-wrap",bordered:""},{footer:p(()=>[c.value>1?(e(),o("div",D,[a(k,{page:i.value,"onUpdate:page":h,"page-slot":u(d).state.collapsedRight?5:8,"page-count":c.value},null,8,["page","page-slot","page-count"])])):l("",!0)]),default:p(()=>[v.value?(e(),o("div",E,[a(x,{num:f.value},null,8,["num"])])):(e(),o("div",G,[r.value.length===0?(e(),o("div",H,[a(w,{size:"large",description:"暂无数据"})])):l("",!0),(e(!0),o(I,null,L(r.value,t=>(e(),M(B,{key:t.id},{default:p(()=>[s("div",K,[s("div",null,"NO."+_(t.id),1),s("div",null,_(t.reason),1),s("div",{class:O({income:t.change_amount>=0,out:t.change_amount<0})},_((t.change_amount>0?"+":"")+(t.change_amount/100).toFixed(2)),3),s("div",null,_(u(R)(t.created_on)),1)])]),_:2},1024))),128))]))]),_:1})])}}});const kt=q(T,[["__scopeId","data-v-d4d04859"]]);export{kt as default};
import{_ as F}from"./post-skeleton-fdf95824.js";import{_ as N}from"./main-nav.vue_vue_type_style_index_0_lang-5f0e81a4.js";import{u as z}from"./vuex-cc1858c6.js";import{b as A}from"./vue-router-29025daf.js";import{a as R}from"./formatTime-936c40eb.js";import{d as S,r as n,j as V,c as o,L as a,Y as p,o as e,U as u,O as l,F as I,$ as L,K as M,a as s,M as _,a1 as O}from"./@vue-f70ab1bd.js";import{F as P,G as U,I as $,H as j}from"./naive-ui-ddb574dd.js";import{_ as q}from"./index-f6017bc3.js";import"./vooks-dfdd6eef.js";import"./evtd-b614532e.js";import"./@vicons-2f3cb6b9.js";import"./moment-b7869f98.js";import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./@css-render-66126308.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";import"./axios-707ed124.js";/* empty css */const D={key:0,class:"pagination-wrap"},E={key:0,class:"skeleton-wrap"},G={key:1},H={key:0,class:"empty-wrap"},K={class:"bill-line"},T=S({__name:"Anouncement",setup(Y){const d=z(),g=A(),v=n(!1),r=n([]),i=n(+g.query.p||1),f=n(20),c=n(0),h=m=>{i.value=m};return V(()=>{}),(m,J)=>{const y=N,k=U,x=F,w=$,B=j,C=P;return e(),o("div",null,[a(y,{title:"公告"}),a(C,{class:"main-content-wrap",bordered:""},{footer:p(()=>[c.value>1?(e(),o("div",D,[a(k,{page:i.value,"onUpdate:page":h,"page-slot":u(d).state.collapsedRight?5:8,"page-count":c.value},null,8,["page","page-slot","page-count"])])):l("",!0)]),default:p(()=>[v.value?(e(),o("div",E,[a(x,{num:f.value},null,8,["num"])])):(e(),o("div",G,[r.value.length===0?(e(),o("div",H,[a(w,{size:"large",description:"暂无数据"})])):l("",!0),(e(!0),o(I,null,L(r.value,t=>(e(),M(B,{key:t.id},{default:p(()=>[s("div",K,[s("div",null,"NO."+_(t.id),1),s("div",null,_(t.reason),1),s("div",{class:O({income:t.change_amount>=0,out:t.change_amount<0})},_((t.change_amount>0?"+":"")+(t.change_amount/100).toFixed(2)),3),s("div",null,_(u(R)(t.created_on)),1)])]),_:2},1024))),128))]))]),_:1})])}}});const kt=q(T,[["__scopeId","data-v-d4d04859"]]);export{kt as default};

@ -0,0 +1 @@
import{_ as z}from"./post-item.vue_vue_type_style_index_0_lang-c48fe5cf.js";import{_ as B}from"./post-skeleton-fdf95824.js";import{_ as F}from"./main-nav.vue_vue_type_style_index_0_lang-5f0e81a4.js";import{u as P}from"./vuex-cc1858c6.js";import{b as R,u as $}from"./vue-router-29025daf.js";import{G as b,_ as G}from"./index-f6017bc3.js";import{d as I,r as s,j as L,c as e,L as n,Y as m,U as M,O as u,o as t,F as N,$ as S,K as U}from"./@vue-f70ab1bd.js";import{F as V,G as j,I as q,H as E}from"./naive-ui-ddb574dd.js";import"./content-0b348d1e.js";import"./@vicons-2f3cb6b9.js";import"./nonesir-video-29a967e9.js";import"./formatTime-936c40eb.js";import"./moment-b7869f98.js";import"./vooks-dfdd6eef.js";import"./evtd-b614532e.js";import"./axios-707ed124.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./@css-render-66126308.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";const H={key:0,class:"skeleton-wrap"},K={key:1},O={key:0,class:"empty-wrap"},T={key:0,class:"pagination-wrap"},Y=I({__name:"Collection",setup(A){const d=P(),g=R();$();const a=s(!1),_=s([]),p=s(+g.query.p||1),i=s(20),r=s(0),l=()=>{a.value=!0,b({page:p.value,page_size:i.value}).then(o=>{a.value=!1,_.value=o.list,r.value=Math.ceil(o.pager.total_rows/i.value),window.scrollTo(0,0)}).catch(o=>{a.value=!1})},v=o=>{p.value=o,l()};return L(()=>{l()}),(o,D)=>{const f=F,h=B,k=q,y=z,w=E,C=V,x=j;return t(),e("div",null,[n(f,{title:"收藏"}),n(C,{class:"main-content-wrap",bordered:""},{default:m(()=>[a.value?(t(),e("div",H,[n(h,{num:i.value},null,8,["num"])])):(t(),e("div",K,[_.value.length===0?(t(),e("div",O,[n(k,{size:"large",description:"暂无数据"})])):u("",!0),(t(!0),e(N,null,S(_.value,c=>(t(),U(w,{key:c.id},{default:m(()=>[n(y,{post:c},null,8,["post"])]),_:2},1024))),128))]))]),_:1}),r.value>0?(t(),e("div",T,[n(x,{page:p.value,"onUpdate:page":v,"page-slot":M(d).state.collapsedRight?5:8,"page-count":r.value},null,8,["page","page-slot","page-count"])])):u("",!0)])}}});const xt=G(Y,[["__scopeId","data-v-1e709369"]]);export{xt as default};

@ -1 +0,0 @@
import{_ as z}from"./post-item.vue_vue_type_style_index_0_lang-02b1501b.js";import{_ as B}from"./post-skeleton-a5bf805a.js";import{_ as F}from"./main-nav.vue_vue_type_style_index_0_lang-d6d2ed7f.js";import{u as P}from"./vuex-cc1858c6.js";import{b as R,u as $}from"./vue-router-29025daf.js";import{D as b,_ as I}from"./index-ce5b62d8.js";import{d as L,r as s,j as M,c as e,L as n,Y as m,U as N,O as u,o as t,F as S,$ as U,K as V}from"./@vue-f70ab1bd.js";import{F as j,G as q,I as D,H as E}from"./naive-ui-ddb574dd.js";import"./content-16569a30.js";import"./@vicons-fc06a0bb.js";import"./nonesir-video-29a967e9.js";import"./formatTime-936c40eb.js";import"./moment-b7869f98.js";import"./vooks-dfdd6eef.js";import"./evtd-b614532e.js";import"./axios-707ed124.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./@css-render-66126308.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";const G={key:0,class:"skeleton-wrap"},H={key:1},K={key:0,class:"empty-wrap"},O={key:0,class:"pagination-wrap"},T=L({__name:"Collection",setup(Y){const d=P(),g=R();$();const a=s(!1),_=s([]),p=s(+g.query.p||1),i=s(20),r=s(0),l=()=>{a.value=!0,b({page:p.value,page_size:i.value}).then(o=>{a.value=!1,_.value=o.list,r.value=Math.ceil(o.pager.total_rows/i.value),window.scrollTo(0,0)}).catch(o=>{a.value=!1})},v=o=>{p.value=o,l()};return M(()=>{l()}),(o,A)=>{const f=F,h=B,k=D,y=z,w=E,C=j,x=q;return t(),e("div",null,[n(f,{title:"收藏"}),n(C,{class:"main-content-wrap",bordered:""},{default:m(()=>[a.value?(t(),e("div",G,[n(h,{num:i.value},null,8,["num"])])):(t(),e("div",H,[_.value.length===0?(t(),e("div",K,[n(k,{size:"large",description:"暂无数据"})])):u("",!0),(t(!0),e(S,null,U(_.value,c=>(t(),V(w,{key:c.id},{default:m(()=>[n(y,{post:c},null,8,["post"])]),_:2},1024))),128))]))]),_:1}),r.value>0?(t(),e("div",O,[n(x,{page:p.value,"onUpdate:page":v,"page-slot":N(d).state.collapsedRight?5:8,"page-count":r.value},null,8,["page","page-slot","page-count"])])):u("",!0)])}}});const xt=I(T,[["__scopeId","data-v-1e709369"]]);export{xt as default};

@ -0,0 +1 @@
import{u as M,b as N}from"./vue-router-29025daf.js";import{d as b,o as t,c as n,a as s,L as a,M as v,r as i,j as P,Y as h,U as R,O as y,F as k,$ as S,K as V}from"./@vue-f70ab1bd.js";import{o as q,F as D,G as L,I as T,H as j}from"./naive-ui-ddb574dd.js";import{_ as C,J as E}from"./index-f6017bc3.js";import{_ as G}from"./post-skeleton-fdf95824.js";import{_ as H}from"./main-nav.vue_vue_type_style_index_0_lang-5f0e81a4.js";import{u as J}from"./vuex-cc1858c6.js";import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./evtd-b614532e.js";import"./@css-render-66126308.js";import"./vooks-dfdd6eef.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";import"./axios-707ed124.js";import"./@vicons-2f3cb6b9.js";/* empty css */const K={class:"avatar"},O={class:"base-info"},Y={class:"username"},A={class:"uid"},Q=b({__name:"contact-item",props:{contact:null},setup(c){const p=M(),m=e=>{p.push({name:"user",query:{username:e}})};return(e,o)=>{const _=q;return t(),n("div",{class:"contact-item",onClick:o[0]||(o[0]=l=>m(c.contact.username))},[s("div",K,[a(_,{size:"large",src:c.contact.avatar},null,8,["src"])]),s("div",O,[s("div",Y,[s("strong",null,v(c.contact.nickname),1),s("span",null," @"+v(c.contact.username),1)]),s("div",A,"UID. "+v(c.contact.user_id),1)])])}}});const W=C(Q,[["__scopeId","data-v-08ee9b2e"]]),X={key:0,class:"skeleton-wrap"},Z={key:1},tt={key:0,class:"empty-wrap"},et={key:0,class:"pagination-wrap"},ot=b({__name:"Contacts",setup(c){const p=J(),m=N(),e=i(!1),o=i([]),_=i(+m.query.p||1),l=i(20),d=i(0),$=r=>{_.value=r,g()};P(()=>{g()});const g=(r=!1)=>{o.value.length===0&&(e.value=!0),E({page:_.value,page_size:l.value}).then(u=>{e.value=!1,o.value=u.list,d.value=Math.ceil(u.pager.total_rows/l.value),r&&setTimeout(()=>{window.scrollTo(0,99999)},50)}).catch(u=>{e.value=!1})};return(r,u)=>{const w=H,x=G,I=T,z=W,B=j,U=D,F=L;return t(),n(k,null,[s("div",null,[a(w,{title:"好友"}),a(U,{class:"main-content-wrap",bordered:""},{default:h(()=>[e.value?(t(),n("div",X,[a(x,{num:l.value},null,8,["num"])])):(t(),n("div",Z,[o.value.length===0?(t(),n("div",tt,[a(I,{size:"large",description:"暂无数据"})])):y("",!0),(t(!0),n(k,null,S(o.value,f=>(t(),V(B,{key:f.user_id},{default:h(()=>[a(z,{contact:f},null,8,["contact"])]),_:2},1024))),128))]))]),_:1})]),d.value>0?(t(),n("div",et,[a(F,{page:_.value,"onUpdate:page":$,"page-slot":R(p).state.collapsedRight?5:8,"page-count":d.value},null,8,["page","page-slot","page-count"])])):y("",!0)],64)}}});const It=C(ot,[["__scopeId","data-v-3b2bf978"]]);export{It as default};

@ -1 +0,0 @@
import{u as M,b as N}from"./vue-router-29025daf.js";import{d as b,o as t,c as n,a as s,L as a,M as v,r as i,j as P,Y as h,U as R,O as y,F as k,$ as S,K as V}from"./@vue-f70ab1bd.js";import{o as q,F as D,G,I as L,H as T}from"./naive-ui-ddb574dd.js";import{_ as C,G as j}from"./index-ce5b62d8.js";import{_ as E}from"./post-skeleton-a5bf805a.js";import{_ as H}from"./main-nav.vue_vue_type_style_index_0_lang-d6d2ed7f.js";import{u as K}from"./vuex-cc1858c6.js";import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./evtd-b614532e.js";import"./@css-render-66126308.js";import"./vooks-dfdd6eef.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";import"./axios-707ed124.js";import"./@vicons-fc06a0bb.js";/* empty css */const O={class:"avatar"},Y={class:"base-info"},A={class:"username"},J={class:"uid"},Q=b({__name:"contact-item",props:{contact:null},setup(c){const p=M(),m=e=>{p.push({name:"user",query:{username:e}})};return(e,o)=>{const _=q;return t(),n("div",{class:"contact-item",onClick:o[0]||(o[0]=l=>m(c.contact.username))},[s("div",O,[a(_,{size:"large",src:c.contact.avatar},null,8,["src"])]),s("div",Y,[s("div",A,[s("strong",null,v(c.contact.nickname),1),s("span",null," @"+v(c.contact.username),1)]),s("div",J,"UID. "+v(c.contact.user_id),1)])])}}});const W=C(Q,[["__scopeId","data-v-08ee9b2e"]]),X={key:0,class:"skeleton-wrap"},Z={key:1},tt={key:0,class:"empty-wrap"},et={key:0,class:"pagination-wrap"},ot=b({__name:"Contacts",setup(c){const p=K(),m=N(),e=i(!1),o=i([]),_=i(+m.query.p||1),l=i(20),d=i(0),$=r=>{_.value=r,g()};P(()=>{g()});const g=(r=!1)=>{o.value.length===0&&(e.value=!0),j({page:_.value,page_size:l.value}).then(u=>{e.value=!1,o.value=u.list,d.value=Math.ceil(u.pager.total_rows/l.value),r&&setTimeout(()=>{window.scrollTo(0,99999)},50)}).catch(u=>{e.value=!1})};return(r,u)=>{const w=H,x=E,I=L,z=W,B=T,U=D,F=G;return t(),n(k,null,[s("div",null,[a(w,{title:"好友"}),a(U,{class:"main-content-wrap",bordered:""},{default:h(()=>[e.value?(t(),n("div",X,[a(x,{num:l.value},null,8,["num"])])):(t(),n("div",Z,[o.value.length===0?(t(),n("div",tt,[a(I,{size:"large",description:"暂无数据"})])):y("",!0),(t(!0),n(k,null,S(o.value,f=>(t(),V(B,{key:f.user_id},{default:h(()=>[a(z,{contact:f},null,8,["contact"])]),_:2},1024))),128))]))]),_:1})]),d.value>0?(t(),n("div",et,[a(F,{page:_.value,"onUpdate:page":$,"page-slot":R(p).state.collapsedRight?5:8,"page-count":d.value},null,8,["page","page-slot","page-count"])])):y("",!0)],64)}}});const It=C(ot,[["__scopeId","data-v-3b2bf978"]]);export{It as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
import{_ as F}from"./post-item.vue_vue_type_style_index_0_lang-02b1501b.js";import{_ as M}from"./post-skeleton-a5bf805a.js";import{_ as N}from"./main-nav.vue_vue_type_style_index_0_lang-d6d2ed7f.js";import{u as S}from"./vuex-cc1858c6.js";import{b as V}from"./vue-router-29025daf.js";import{r as D,_ as L}from"./index-ce5b62d8.js";import{d as R,r,j,c as a,L as e,U as _,K as h,Y as m,O as d,o as t,a as s,M as f,F as q,$ as E}from"./@vue-f70ab1bd.js";import{F as G,G as H,o as K,f as O,g as T,I as Y,H as A}from"./naive-ui-ddb574dd.js";import"./content-16569a30.js";import"./@vicons-fc06a0bb.js";import"./nonesir-video-29a967e9.js";import"./formatTime-936c40eb.js";import"./moment-b7869f98.js";import"./vooks-dfdd6eef.js";import"./evtd-b614532e.js";import"./axios-707ed124.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./@css-render-66126308.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";const J={class:"profile-baseinfo"},Q={class:"avatar"},W={class:"base-info"},X={class:"username"},Z={class:"uid"},ee={key:0,class:"skeleton-wrap"},te={key:1},oe={key:0,class:"empty-wrap"},se={key:1,class:"pagination-wrap"},ne=R({__name:"Profile",setup(ae){const o=S(),k=V(),i=r(!1),p=r([]),l=r(+k.query.p||1),c=r(20),u=r(0),g=()=>{i.value=!0,D({username:o.state.userInfo.username,page:l.value,page_size:c.value}).then(n=>{i.value=!1,p.value=n.list,u.value=Math.ceil(n.pager.total_rows/c.value),window.scrollTo(0,0)}).catch(n=>{i.value=!1})},y=n=>{l.value=n,g()};return j(()=>{g()}),(n,_e)=>{const w=N,I=K,b=O,P=T,x=M,z=Y,B=F,U=A,$=G,C=H;return t(),a("div",null,[e(w,{title:"主页"}),_(o).state.userInfo.id>0?(t(),h($,{key:0,class:"main-content-wrap profile-wrap",bordered:""},{default:m(()=>[s("div",J,[s("div",Q,[e(I,{size:"large",src:_(o).state.userInfo.avatar},null,8,["src"])]),s("div",W,[s("div",X,[s("strong",null,f(_(o).state.userInfo.nickname),1),s("span",null," @"+f(_(o).state.userInfo.username),1)]),s("div",Z,"UID. "+f(_(o).state.userInfo.id),1)])]),e(P,{class:"profile-tabs-wrap",animated:""},{default:m(()=>[e(b,{name:"post",tab:"泡泡"})]),_:1}),i.value?(t(),a("div",ee,[e(x,{num:c.value},null,8,["num"])])):(t(),a("div",te,[p.value.length===0?(t(),a("div",oe,[e(z,{size:"large",description:"暂无数据"})])):d("",!0),(t(!0),a(q,null,E(p.value,v=>(t(),h(U,{key:v.id},{default:m(()=>[e(B,{post:v},null,8,["post"])]),_:2},1024))),128))]))]),_:1})):d("",!0),u.value>0?(t(),a("div",se,[e(C,{page:l.value,"onUpdate:page":y,"page-slot":_(o).state.collapsedRight?5:8,"page-count":u.value},null,8,["page","page-slot","page-count"])])):d("",!0)])}}});const Ve=L(ne,[["__scopeId","data-v-1d87d974"]]);export{Ve as default};
import{_ as F}from"./post-item.vue_vue_type_style_index_0_lang-c48fe5cf.js";import{_ as M}from"./post-skeleton-fdf95824.js";import{_ as N}from"./main-nav.vue_vue_type_style_index_0_lang-5f0e81a4.js";import{u as S}from"./vuex-cc1858c6.js";import{b as V}from"./vue-router-29025daf.js";import{w as D,_ as L}from"./index-f6017bc3.js";import{d as R,r,j,c as a,L as e,U as _,K as h,Y as m,O as d,o as t,a as s,M as f,F as q,$ as E}from"./@vue-f70ab1bd.js";import{F as G,G as H,o as K,f as O,g as T,I as Y,H as A}from"./naive-ui-ddb574dd.js";import"./content-0b348d1e.js";import"./@vicons-2f3cb6b9.js";import"./nonesir-video-29a967e9.js";import"./formatTime-936c40eb.js";import"./moment-b7869f98.js";import"./vooks-dfdd6eef.js";import"./evtd-b614532e.js";import"./axios-707ed124.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./@css-render-66126308.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";const J={class:"profile-baseinfo"},Q={class:"avatar"},W={class:"base-info"},X={class:"username"},Z={class:"uid"},ee={key:0,class:"skeleton-wrap"},te={key:1},oe={key:0,class:"empty-wrap"},se={key:1,class:"pagination-wrap"},ne=R({__name:"Profile",setup(ae){const o=S(),k=V(),i=r(!1),p=r([]),l=r(+k.query.p||1),c=r(20),u=r(0),g=()=>{i.value=!0,D({username:o.state.userInfo.username,page:l.value,page_size:c.value}).then(n=>{i.value=!1,p.value=n.list,u.value=Math.ceil(n.pager.total_rows/c.value),window.scrollTo(0,0)}).catch(n=>{i.value=!1})},y=n=>{l.value=n,g()};return j(()=>{g()}),(n,_e)=>{const w=N,I=K,b=O,P=T,x=M,z=Y,B=F,U=A,$=G,C=H;return t(),a("div",null,[e(w,{title:"主页"}),_(o).state.userInfo.id>0?(t(),h($,{key:0,class:"main-content-wrap profile-wrap",bordered:""},{default:m(()=>[s("div",J,[s("div",Q,[e(I,{size:"large",src:_(o).state.userInfo.avatar},null,8,["src"])]),s("div",W,[s("div",X,[s("strong",null,f(_(o).state.userInfo.nickname),1),s("span",null," @"+f(_(o).state.userInfo.username),1)]),s("div",Z,"UID. "+f(_(o).state.userInfo.id),1)])]),e(P,{class:"profile-tabs-wrap",animated:""},{default:m(()=>[e(b,{name:"post",tab:"泡泡"})]),_:1}),i.value?(t(),a("div",ee,[e(x,{num:c.value},null,8,["num"])])):(t(),a("div",te,[p.value.length===0?(t(),a("div",oe,[e(z,{size:"large",description:"暂无数据"})])):d("",!0),(t(!0),a(q,null,E(p.value,v=>(t(),h(U,{key:v.id},{default:m(()=>[e(B,{post:v},null,8,["post"])]),_:2},1024))),128))]))]),_:1})):d("",!0),u.value>0?(t(),a("div",se,[e(C,{page:l.value,"onUpdate:page":y,"page-slot":_(o).state.collapsedRight?5:8,"page-count":u.value},null,8,["page","page-slot","page-count"])])):d("",!0)])}}});const Ve=L(ne,[["__scopeId","data-v-1d87d974"]]);export{Ve as default};

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
.tags-wrap[data-v-c1908b4e]{padding:20px}.tags-wrap .tag-item .tag-hot[data-v-c1908b4e]{margin-left:12px;font-size:12px;opacity:.75}.dark .tags-wrap[data-v-c1908b4e]{background-color:#101014bf}

@ -0,0 +1 @@
import{q as x,u as S,r as I,t as U,_ as j}from"./index-f6017bc3.js";import{l as z}from"./@vicons-2f3cb6b9.js";import{d as F,r as _,n as $,j as q,_ as E,o as l,c as u,L as n,Y as a,K as T,e as A,M as w,O as m,U as r,w as D,a3 as K,F as Y,$ as G}from"./@vue-f70ab1bd.js";import{o as H,M as L,j as J,e as P,O as Q,L as R,F as W,f as X,g as Z,a as tt,k as et}from"./naive-ui-ddb574dd.js";import{_ as ot}from"./main-nav.vue_vue_type_style_index_0_lang-5f0e81a4.js";import{u as nt}from"./vuex-cc1858c6.js";import"./vue-router-29025daf.js";import"./axios-707ed124.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./evtd-b614532e.js";import"./@css-render-66126308.js";import"./vooks-dfdd6eef.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";const st={key:0,class:"tag-item"},at={key:0,class:"tag-quote"},ct={key:1,class:"tag-quote tag-follow"},lt={key:0,class:"options"},it=F({__name:"tag-item",props:{tag:null,showAction:{type:Boolean},checkFollowing:{type:Boolean}},setup(s){const e=s,g=_(!1),d=$(()=>{let o=[];return e.tag.is_following===0?o.push({label:"关注",key:"follow"}):(e.tag.is_top===0?o.push({label:"置顶",key:"stick"}):o.push({label:"取消置顶",key:"unstick"}),o.push({label:"取消关注",key:"unfollow"})),o}),i=o=>{switch(o){case"follow":I({topic_id:e.tag.id}).then(t=>{e.tag.is_following=1,window.$message.success("关注成功")}).catch(t=>{console.log(t)});break;case"unfollow":S({topic_id:e.tag.id}).then(t=>{e.tag.is_following=0,window.$message.success("取消关注")}).catch(t=>{console.log(t)});break;case"stick":x({topic_id:e.tag.id}).then(t=>{e.tag.is_top=t.top_status,window.$message.success("置顶成功")}).catch(t=>{console.log(t)});break;case"unstick":x({topic_id:e.tag.id}).then(t=>{e.tag.is_top=t.top_status,window.$message.success("取消置顶")}).catch(t=>{console.log(t)});break}};return q(()=>{g.value=!1}),(o,t)=>{const k=E("router-link"),f=H,v=L,c=J,h=P,y=Q,p=R;return!s.checkFollowing||s.checkFollowing&&s.tag.is_following===1?(l(),u("div",st,[n(p,null,{header:a(()=>[(l(),T(v,{type:"success",size:"large",round:"",key:s.tag.id},{avatar:a(()=>[n(f,{src:s.tag.user.avatar},null,8,["src"])]),default:a(()=>[n(k,{class:"hash-link",to:{name:"home",query:{q:s.tag.tag,t:"tag"}}},{default:a(()=>[A(" #"+w(s.tag.tag),1)]),_:1},8,["to"]),s.showAction?m("",!0):(l(),u("span",at,"("+w(s.tag.quote_num)+")",1)),s.showAction?(l(),u("span",ct,"("+w(s.tag.quote_num)+")",1)):m("",!0)]),_:1}))]),"header-extra":a(()=>[s.showAction?(l(),u("div",lt,[n(y,{placement:"bottom-end",trigger:"click",size:"small",options:r(d),onSelect:i},{default:a(()=>[n(h,{type:"success",quaternary:"",circle:"",block:""},{icon:a(()=>[n(c,null,{default:a(()=>[n(r(z))]),_:1})]),_:1})]),_:1},8,["options"])])):m("",!0)]),_:1})])):m("",!0)}}});const _t=F({__name:"Topic",setup(s){const e=nt(),g=_([]),d=_("hot"),i=_(!1),o=_(!1),t=_(!1);D(o,()=>{o.value||(window.$message.success("保存成功"),e.commit("refreshTopicFollow"))});const k=$({get:()=>{let c="编辑";return o.value&&(c="保存"),c},set:c=>{}}),f=()=>{i.value=!0,U({type:d.value,num:50}).then(c=>{g.value=c.topics,i.value=!1}).catch(c=>{console.log(c),i.value=!1})},v=c=>{d.value=c,c=="follow"?t.value=!0:t.value=!1,f()};return q(()=>{f()}),(c,h)=>{const y=ot,p=X,B=L,C=Z,V=it,M=tt,N=et,O=W;return l(),u("div",null,[n(y,{title:"话题"}),n(O,{class:"main-content-wrap tags-wrap",bordered:""},{default:a(()=>[n(C,{type:"line",animated:"","onUpdate:value":v},K({default:a(()=>[n(p,{name:"hot",tab:"热门"}),n(p,{name:"new",tab:"最新"}),r(e).state.userLogined?(l(),T(p,{key:0,name:"follow",tab:"关注"})):m("",!0)]),_:2},[r(e).state.userLogined?{name:"suffix",fn:a(()=>[n(B,{checked:o.value,"onUpdate:checked":h[0]||(h[0]=b=>o.value=b),checkable:""},{default:a(()=>[A(w(r(k)),1)]),_:1},8,["checked"])]),key:"0"}:void 0]),1024),n(N,{show:i.value},{default:a(()=>[n(M,null,{default:a(()=>[(l(!0),u(Y,null,G(g.value,b=>(l(),T(V,{tag:b,showAction:r(e).state.userLogined&&o.value,checkFollowing:t.value},null,8,["tag","showAction","checkFollowing"]))),256))]),_:1})]),_:1},8,["show"])]),_:1})])}}});const Vt=j(_t,[["__scopeId","data-v-15794a53"]]);export{Vt as default};

@ -0,0 +1 @@
.tag-item .tag-quote{margin-left:12px;font-size:14px;opacity:.75}.tag-item .tag-follow{margin-right:22px}.tag-item .options{margin-left:-32px;margin-bottom:4px;opacity:.55}.tag-item .n-thing .n-thing-header{margin-bottom:0}.tag-item .n-thing .n-thing-avatar-header-wrapper{align-items:center}.tags-wrap[data-v-15794a53]{padding:20px}.dark .tags-wrap[data-v-15794a53]{background-color:#101014bf}

@ -1 +0,0 @@
import{_ as T}from"./main-nav.vue_vue_type_style_index_0_lang-d6d2ed7f.js";import{q as w,_ as x}from"./index-ce5b62d8.js";import{d as q,r as s,j as B,c as i,L as t,Y as o,o as _,F as C,$ as F,_ as M,K as N,e as V,M as l,a as L}from"./@vue-f70ab1bd.js";import{F as $,f as j,g as D,a as E,k as I,o as K,M as S}from"./naive-ui-ddb574dd.js";import"./vuex-cc1858c6.js";import"./vue-router-29025daf.js";import"./vooks-dfdd6eef.js";import"./evtd-b614532e.js";import"./@vicons-fc06a0bb.js";import"./axios-707ed124.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-804c4158.js";import"./@css-render-66126308.js";import"./vdirs-b0483831.js";import"./@juggle-41516555.js";import"./css-render-6a5c5852.js";import"./@emotion-8a8e73f6.js";import"./lodash-es-8412e618.js";import"./treemate-25c27bff.js";import"./async-validator-dee29e8b.js";import"./date-fns-975a2d8f.js";const U={class:"tag-hot"},Y=q({__name:"Topic",setup(z){const p=s([]),c=s("hot"),a=s(!1),r=()=>{a.value=!0,w({type:c.value,num:50}).then(n=>{p.value=n.topics,a.value=!1}).catch(n=>{a.value=!1})},u=n=>{c.value=n,r()};return B(()=>{r()}),(n,A)=>{const d=T,m=j,g=D,f=M("router-link"),v=K,h=S,y=E,b=I,k=$;return _(),i("div",null,[t(d,{title:"话题"}),t(k,{class:"main-content-wrap tags-wrap",bordered:""},{default:o(()=>[t(g,{type:"line",animated:"","onUpdate:value":u},{default:o(()=>[t(m,{name:"hot",tab:"热门"}),t(m,{name:"new",tab:"最新"})]),_:1}),t(b,{show:a.value},{default:o(()=>[t(y,null,{default:o(()=>[(_(!0),i(C,null,F(p.value,e=>(_(),N(h,{class:"tag-item",type:"success",round:"",key:e.id},{avatar:o(()=>[t(v,{src:e.user.avatar},null,8,["src"])]),default:o(()=>[t(f,{class:"hash-link",to:{name:"home",query:{q:e.tag,t:"tag"}}},{default:o(()=>[V(" #"+l(e.tag),1)]),_:2},1032,["to"]),L("span",U,"("+l(e.quote_num)+")",1)]),_:2},1024))),128))]),_:1})]),_:1},8,["show"])]),_:1})])}}});const lt=x(Y,[["__scopeId","data-v-c1908b4e"]]);export{lt as default};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -0,0 +1 @@
.auth-wrap[data-v-52205ad0]{margin-top:-30px}.dark .auth-wrap[data-v-52205ad0]{background-color:#101014bf}.rightbar-wrap[data-v-200967dd]{width:240px;position:fixed;left:calc(50% + var(--content-main) / 2 + 10px)}.rightbar-wrap .search-wrap[data-v-200967dd]{margin:12px 0}.rightbar-wrap .hot-tag-item[data-v-200967dd]{line-height:2;position:relative}.rightbar-wrap .hot-tag-item .hash-link[data-v-200967dd]{width:calc(100% - 60px);text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block}.rightbar-wrap .hot-tag-item .post-num[data-v-200967dd]{position:absolute;right:0;top:0;width:60px;text-align:right;line-height:2;opacity:.5}.rightbar-wrap .hottopic-wrap[data-v-200967dd]{margin-bottom:10px}.rightbar-wrap .copyright-wrap .copyright[data-v-200967dd]{font-size:12px;opacity:.75}.rightbar-wrap .copyright-wrap .hash-link[data-v-200967dd]{font-size:12px}.dark .hottopic-wrap[data-v-200967dd],.dark .copyright-wrap[data-v-200967dd]{background-color:#18181c}.sidebar-wrap{z-index:99;width:200px;height:100vh;position:fixed;right:calc(50% + var(--content-main) / 2 + 10px);padding:12px 0;box-sizing:border-box}.sidebar-wrap .n-menu .n-menu-item-content:before{border-radius:21px}.logo-wrap{display:flex;justify-content:flex-start;margin-bottom:12px}.logo-wrap .logo-img{margin-left:24px}.logo-wrap .logo-img:hover{cursor:pointer}.user-wrap{display:flex;align-items:center;position:absolute;bottom:12px;left:12px;right:12px}.user-wrap .user-mini-wrap{display:none}.user-wrap .user-avatar{margin-right:8px}.user-wrap .user-info{display:flex;flex-direction:column}.user-wrap .user-info .nickname{font-size:16px;font-weight:700;line-height:16px;height:16px;margin-bottom:2px;display:flex;align-items:center}.user-wrap .user-info .nickname .nickname-txt{max-width:90px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.user-wrap .user-info .nickname .logout{margin-left:6px}.user-wrap .user-info .username{font-size:14px;line-height:16px;height:16px;width:120px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;opacity:.75}.user-wrap .login-only-wrap{display:flex;justify-content:center;width:100%}.user-wrap .login-only-wrap button{margin:0 4px;width:80%}.user-wrap .login-wrap{display:flex;justify-content:center;width:100%}.user-wrap .login-wrap button{margin:0 4px}.auth-card .n-card-header{z-index:999}@media screen and (max-width: 821px){.sidebar-wrap{width:200px;right:calc(100% - 200px)}.logo-wrap .logo-img{margin-left:12px!important}.user-wrap .user-avatar,.user-wrap .user-info,.user-wrap .login-only-wrap,.user-wrap .login-wrap{margin-bottom:32px}}:root{--content-main: 544px}.app-container{margin:0}.app-container .app-wrap{width:100%;margin:0 auto}.main-wrap{min-height:100vh;display:flex;flex-direction:row;justify-content:center}.main-wrap .content-wrap{width:100%;max-width:var(--content-main);position:relative}.main-wrap .main-content-wrap{margin:0;border-top:none;border-radius:0}.main-wrap .main-content-wrap .n-list-item{padding:0}.empty-wrap{min-height:300px;display:flex;align-items:center;justify-content:center}.hash-link,.user-link{color:#18a058;text-decoration:none;cursor:pointer}.hash-link:hover,.user-link:hover{opacity:.8}.beian-link{color:#333;text-decoration:none}.beian-link:hover{opacity:.75}.username-link{color:#000;color:none;text-decoration:none;cursor:pointer}.username-link:hover{text-decoration:underline}.dark .hash-link,.dark .user-link{color:#63e2b7}.dark .username-link{color:#eee}.dark .beian-link{color:#ddd}@media screen and (max-width: 821px){.content-wrap{top:0;position:absolute!important}}

@ -1 +0,0 @@
.auth-wrap[data-v-52205ad0]{margin-top:-30px}.dark .auth-wrap[data-v-52205ad0]{background-color:#101014bf}.rightbar-wrap[data-v-9c65d923]{width:240px;position:fixed;left:calc(50% + var(--content-main) / 2 + 10px)}.rightbar-wrap .search-wrap[data-v-9c65d923]{margin:12px 0}.rightbar-wrap .hot-tag-item[data-v-9c65d923]{line-height:2;position:relative}.rightbar-wrap .hot-tag-item .hash-link[data-v-9c65d923]{width:calc(100% - 60px);text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block}.rightbar-wrap .hot-tag-item .post-num[data-v-9c65d923]{position:absolute;right:0;top:0;width:60px;text-align:right;line-height:2;opacity:.5}.rightbar-wrap .copyright-wrap[data-v-9c65d923]{margin-top:10px}.rightbar-wrap .copyright-wrap .copyright[data-v-9c65d923]{font-size:12px;opacity:.75}.rightbar-wrap .copyright-wrap .hash-link[data-v-9c65d923]{font-size:12px}.dark .hottopic-wrap[data-v-9c65d923],.dark .copyright-wrap[data-v-9c65d923]{background-color:#18181c}.sidebar-wrap{z-index:99;width:200px;height:100vh;position:fixed;right:calc(50% + var(--content-main) / 2 + 10px);padding:12px 0;box-sizing:border-box}.sidebar-wrap .n-menu .n-menu-item-content:before{border-radius:21px}.logo-wrap{display:flex;justify-content:flex-start;margin-bottom:12px}.logo-wrap .logo-img{margin-left:24px}.logo-wrap .logo-img:hover{cursor:pointer}.user-wrap{display:flex;align-items:center;position:absolute;bottom:12px;left:12px;right:12px}.user-wrap .user-mini-wrap{display:none}.user-wrap .user-avatar{margin-right:8px}.user-wrap .user-info{display:flex;flex-direction:column}.user-wrap .user-info .nickname{font-size:16px;font-weight:700;line-height:16px;height:16px;margin-bottom:2px;display:flex;align-items:center}.user-wrap .user-info .nickname .nickname-txt{max-width:90px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.user-wrap .user-info .nickname .logout{margin-left:6px}.user-wrap .user-info .username{font-size:14px;line-height:16px;height:16px;width:120px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;opacity:.75}.user-wrap .login-only-wrap{display:flex;justify-content:center;width:100%}.user-wrap .login-only-wrap button{margin:0 4px;width:80%}.user-wrap .login-wrap{display:flex;justify-content:center;width:100%}.user-wrap .login-wrap button{margin:0 4px}.auth-card .n-card-header{z-index:999}@media screen and (max-width: 821px){.sidebar-wrap{width:200px;right:calc(100% - 200px)}.logo-wrap .logo-img{margin-left:12px!important}.user-wrap .user-avatar,.user-wrap .user-info,.user-wrap .login-only-wrap,.user-wrap .login-wrap{margin-bottom:32px}}:root{--content-main: 544px}.app-container{margin:0}.app-container .app-wrap{width:100%;margin:0 auto}.main-wrap{min-height:100vh;display:flex;flex-direction:row;justify-content:center}.main-wrap .content-wrap{width:100%;max-width:var(--content-main);position:relative}.main-wrap .main-content-wrap{margin:0;border-top:none;border-radius:0}.main-wrap .main-content-wrap .n-list-item{padding:0}.empty-wrap{min-height:300px;display:flex;align-items:center;justify-content:center}.hash-link,.user-link{color:#18a058;text-decoration:none;cursor:pointer}.hash-link:hover,.user-link:hover{opacity:.8}.beian-link{color:#333;text-decoration:none}.beian-link:hover{opacity:.75}.username-link{color:#000;color:none;text-decoration:none;cursor:pointer}.username-link:hover{text-decoration:underline}.dark .hash-link,.dark .user-link{color:#63e2b7}.dark .username-link{color:#eee}.dark .beian-link{color:#ddd}@media screen and (max-width: 821px){.content-wrap{top:0;position:absolute!important}}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
import{S as B}from"./index-ce5b62d8.js";import{u as E}from"./vuex-cc1858c6.js";import{u as z}from"./vue-router-29025daf.js";import{j as A}from"./vooks-dfdd6eef.js";import{D as C,t as D,u as N,v as P}from"./@vicons-fc06a0bb.js";import{a3 as x,a4 as R,j as I,e as V,a5 as j,h as H}from"./naive-ui-ddb574dd.js";import{d as L,r as f,j as U,o as a,c as g,U as o,L as e,Y as t,O as c,a as q,K as _,e as F,M as $,F as K}from"./@vue-f70ab1bd.js";const Y={key:0},G={class:"navbar"},oe=L({__name:"main-nav",props:{title:{default:""},back:{type:Boolean,default:!1},theme:{type:Boolean,default:!0}},setup(r){const i=r,n=E(),m=z(),l=f(!1),k=f("left"),u=d=>{d?(localStorage.setItem("PAOPAO_THEME","dark"),n.commit("triggerTheme","dark")):(localStorage.setItem("PAOPAO_THEME","light"),n.commit("triggerTheme","light"))},w=()=>{window.history.length<=1?m.push({path:"/"}):m.go(-1)},v=()=>{l.value=!0};return U(()=>{localStorage.getItem("PAOPAO_THEME")||u(A()==="dark")}),(d,p)=>{const y=B,b=x,O=R,s=I,h=V,M=j,S=H;return a(),g(K,null,[o(n).state.drawerModelShow?(a(),g("div",Y,[e(O,{show:l.value,"onUpdate:show":p[0]||(p[0]=T=>l.value=T),width:212,placement:k.value,resizable:""},{default:t(()=>[e(b,null,{default:t(()=>[e(y)]),_:1})]),_:1},8,["show","placement"])])):c("",!0),e(S,{size:"small",bordered:!0,class:"nav-title-card"},{header:t(()=>[q("div",G,[o(n).state.drawerModelShow&&!r.back?(a(),_(h,{key:0,class:"drawer-btn",onClick:v,quaternary:"",circle:"",size:"medium"},{icon:t(()=>[e(s,null,{default:t(()=>[e(o(C))]),_:1})]),_:1})):c("",!0),r.back?(a(),_(h,{key:1,class:"back-btn",onClick:w,quaternary:"",circle:"",size:"small"},{icon:t(()=>[e(s,null,{default:t(()=>[e(o(D))]),_:1})]),_:1})):c("",!0),F(" "+$(i.title)+" ",1),i.theme?(a(),_(M,{key:2,value:o(n).state.theme==="dark","onUpdate:value":u,size:"small",class:"theme-switch-wrap"},{"checked-icon":t(()=>[e(s,{component:o(N)},null,8,["component"])]),"unchecked-icon":t(()=>[e(s,{component:o(P)},null,8,["component"])]),_:1},8,["value"])):c("",!0)])]),_:1})],64)}}});export{oe as _};
import{V as E}from"./index-f6017bc3.js";import{u as S}from"./vuex-cc1858c6.js";import{u as z}from"./vue-router-29025daf.js";import{j as A}from"./vooks-dfdd6eef.js";import{D as C,u as D,v as N,w as P}from"./@vicons-2f3cb6b9.js";import{a3 as x,a4 as R,j as V,e as I,a5 as j,h as H}from"./naive-ui-ddb574dd.js";import{d as L,r as f,j as U,o as a,c as g,U as o,L as e,Y as t,O as c,a as q,K as _,e as F,M as $,F as K}from"./@vue-f70ab1bd.js";const Y={key:0},G={class:"navbar"},oe=L({__name:"main-nav",props:{title:{default:""},back:{type:Boolean,default:!1},theme:{type:Boolean,default:!0}},setup(r){const i=r,n=S(),m=z(),l=f(!1),k=f("left"),u=d=>{d?(localStorage.setItem("PAOPAO_THEME","dark"),n.commit("triggerTheme","dark")):(localStorage.setItem("PAOPAO_THEME","light"),n.commit("triggerTheme","light"))},w=()=>{window.history.length<=1?m.push({path:"/"}):m.go(-1)},v=()=>{l.value=!0};return U(()=>{localStorage.getItem("PAOPAO_THEME")||u(A()==="dark")}),(d,p)=>{const y=E,b=x,O=R,s=V,h=I,M=j,T=H;return a(),g(K,null,[o(n).state.drawerModelShow?(a(),g("div",Y,[e(O,{show:l.value,"onUpdate:show":p[0]||(p[0]=B=>l.value=B),width:212,placement:k.value,resizable:""},{default:t(()=>[e(b,null,{default:t(()=>[e(y)]),_:1})]),_:1},8,["show","placement"])])):c("",!0),e(T,{size:"small",bordered:!0,class:"nav-title-card"},{header:t(()=>[q("div",G,[o(n).state.drawerModelShow&&!r.back?(a(),_(h,{key:0,class:"drawer-btn",onClick:v,quaternary:"",circle:"",size:"medium"},{icon:t(()=>[e(s,null,{default:t(()=>[e(o(C))]),_:1})]),_:1})):c("",!0),r.back?(a(),_(h,{key:1,class:"back-btn",onClick:w,quaternary:"",circle:"",size:"small"},{icon:t(()=>[e(s,null,{default:t(()=>[e(o(D))]),_:1})]),_:1})):c("",!0),F(" "+$(i.title)+" ",1),i.theme?(a(),_(M,{key:2,value:o(n).state.theme==="dark","onUpdate:value":u,size:"small",class:"theme-switch-wrap"},{"checked-icon":t(()=>[e(s,{component:o(N)},null,8,["component"])]),"unchecked-icon":t(()=>[e(s,{component:o(P)},null,8,["component"])]),_:1},8,["value"])):c("",!0)])]),_:1})],64)}}});export{oe as _};

@ -1 +1 @@
import{p as N,a as S,_ as $,b as V,c as j}from"./content-16569a30.js";import{d as H,n as R,_ as D,o as i,c as f,L as a,a3 as F,U as t,Y as n,F as I,$ as P,Z as v,a as l,e as r,M as c,K as p,O as _}from"./@vue-f70ab1bd.js";import{u as E}from"./vuex-cc1858c6.js";import{b as K,u as U}from"./vue-router-29025daf.js";import{a as Y}from"./formatTime-936c40eb.js";import{f as Z,h as A,i as G,k as J}from"./@vicons-fc06a0bb.js";import{o as Q,M as W,j as X,a as tt,L as et}from"./naive-ui-ddb574dd.js";const st={class:"nickname-wrap"},nt={class:"username-wrap"},ot={class:"timestamp"},at=["innerHTML"],it={class:"opt-item"},rt={class:"opt-item"},ct={class:"opt-item"},pt={class:"opt-item"},ft=H({__name:"post-item",props:{post:null},setup(x){const C=x;K();const d=U(),z=E(),e=R(()=>{let o=Object.assign({texts:[],imgs:[],videos:[],links:[],attachments:[],charge_attachments:[]},C.post);return o.contents.map(s=>{(+s.type==1||+s.type==2)&&o.texts.push(s),+s.type==3&&o.imgs.push(s),+s.type==4&&o.videos.push(s),+s.type==6&&o.links.push(s),+s.type==7&&o.attachments.push(s),+s.type==8&&o.charge_attachments.push(s)}),o}),k=o=>{d.push({name:"post",query:{id:o}})},b=(o,s)=>{if(o.target.dataset.detail){const m=o.target.dataset.detail.split(":");if(m.length===2){z.commit("refresh"),m[0]==="tag"?d.push({name:"home",query:{q:m[1],t:"tag"}}):d.push({name:"user",query:{username:m[1]}});return}}k(s)};return(o,s)=>{const m=Q,w=D("router-link"),h=W,y=S,O=$,T=V,q=j,u=X,B=tt,L=et;return i(),f("div",{class:"post-item",onClick:s[2]||(s[2]=g=>k(t(e).id))},[a(L,{"content-indented":""},F({avatar:n(()=>[a(m,{round:"",size:30,src:t(e).user.avatar},null,8,["src"])]),header:n(()=>[l("span",st,[a(w,{onClick:s[0]||(s[0]=v(()=>{},["stop"])),class:"username-link",to:{name:"user",query:{username:t(e).user.username}}},{default:n(()=>[r(c(t(e).user.nickname),1)]),_:1},8,["to"])]),l("span",nt," @"+c(t(e).user.username),1),t(e).is_top?(i(),p(h,{key:0,class:"top-tag",type:"warning",size:"small",round:""},{default:n(()=>[r(" 置顶 ")]),_:1})):_("",!0),t(e).visibility==1?(i(),p(h,{key:1,class:"top-tag",type:"error",size:"small",round:""},{default:n(()=>[r(" 私密 ")]),_:1})):_("",!0),t(e).visibility==2?(i(),p(h,{key:2,class:"top-tag",type:"info",size:"small",round:""},{default:n(()=>[r(" 好友可见 ")]),_:1})):_("",!0)]),"header-extra":n(()=>[l("span",ot,c(t(e).ip_loc?t(e).ip_loc+" · ":t(e).ip_loc)+" "+c(t(Y)(t(e).created_on)),1)]),footer:n(()=>[t(e).attachments.length>0?(i(),p(y,{key:0,attachments:t(e).attachments},null,8,["attachments"])):_("",!0),t(e).charge_attachments.length>0?(i(),p(y,{key:1,attachments:t(e).charge_attachments,price:t(e).attachment_price},null,8,["attachments","price"])):_("",!0),t(e).imgs.length>0?(i(),p(O,{key:2,imgs:t(e).imgs},null,8,["imgs"])):_("",!0),t(e).videos.length>0?(i(),p(T,{key:3,videos:t(e).videos},null,8,["videos"])):_("",!0),t(e).links.length>0?(i(),p(q,{key:4,links:t(e).links},null,8,["links"])):_("",!0)]),action:n(()=>[a(B,{justify:"space-between"},{default:n(()=>[l("div",it,[a(u,{size:"18",class:"opt-item-icon"},{default:n(()=>[a(t(Z))]),_:1}),r(" "+c(t(e).upvote_count),1)]),l("div",rt,[a(u,{size:"18",class:"opt-item-icon"},{default:n(()=>[a(t(A))]),_:1}),r(" "+c(t(e).comment_count),1)]),l("div",ct,[a(u,{size:"18",class:"opt-item-icon"},{default:n(()=>[a(t(G))]),_:1}),r(" "+c(t(e).collection_count),1)]),l("div",pt,[a(u,{size:"18",class:"opt-item-icon"},{default:n(()=>[a(t(J))]),_:1}),r(" "+c(t(e).share_count),1)])]),_:1})]),_:2},[t(e).texts.length>0?{name:"description",fn:n(()=>[(i(!0),f(I,null,P(t(e).texts,g=>(i(),f("span",{key:g.id,class:"post-text",onClick:s[1]||(s[1]=v(M=>b(M,t(e).id),["stop"])),innerHTML:t(N)(g.content).content},null,8,at))),128))]),key:"0"}:void 0]),1024)])}}});export{ft as _};
import{p as N,a as S,_ as $,b as V,c as j}from"./content-0b348d1e.js";import{d as H,n as R,_ as D,o as i,c as f,L as a,a3 as F,U as t,Y as n,F as I,$ as P,Z as v,a as l,e as r,M as c,K as p,O as _}from"./@vue-f70ab1bd.js";import{u as E}from"./vuex-cc1858c6.js";import{b as K,u as U}from"./vue-router-29025daf.js";import{a as Y}from"./formatTime-936c40eb.js";import{f as Z,h as A,i as G,k as J}from"./@vicons-2f3cb6b9.js";import{o as Q,M as W,j as X,a as tt,L as et}from"./naive-ui-ddb574dd.js";const st={class:"nickname-wrap"},nt={class:"username-wrap"},ot={class:"timestamp"},at=["innerHTML"],it={class:"opt-item"},rt={class:"opt-item"},ct={class:"opt-item"},pt={class:"opt-item"},ft=H({__name:"post-item",props:{post:null},setup(x){const C=x;K();const d=U(),z=E(),e=R(()=>{let o=Object.assign({texts:[],imgs:[],videos:[],links:[],attachments:[],charge_attachments:[]},C.post);return o.contents.map(s=>{(+s.type==1||+s.type==2)&&o.texts.push(s),+s.type==3&&o.imgs.push(s),+s.type==4&&o.videos.push(s),+s.type==6&&o.links.push(s),+s.type==7&&o.attachments.push(s),+s.type==8&&o.charge_attachments.push(s)}),o}),k=o=>{d.push({name:"post",query:{id:o}})},b=(o,s)=>{if(o.target.dataset.detail){const m=o.target.dataset.detail.split(":");if(m.length===2){z.commit("refresh"),m[0]==="tag"?d.push({name:"home",query:{q:m[1],t:"tag"}}):d.push({name:"user",query:{username:m[1]}});return}}k(s)};return(o,s)=>{const m=Q,w=D("router-link"),h=W,y=S,O=$,T=V,q=j,u=X,B=tt,L=et;return i(),f("div",{class:"post-item",onClick:s[2]||(s[2]=g=>k(t(e).id))},[a(L,{"content-indented":""},F({avatar:n(()=>[a(m,{round:"",size:30,src:t(e).user.avatar},null,8,["src"])]),header:n(()=>[l("span",st,[a(w,{onClick:s[0]||(s[0]=v(()=>{},["stop"])),class:"username-link",to:{name:"user",query:{username:t(e).user.username}}},{default:n(()=>[r(c(t(e).user.nickname),1)]),_:1},8,["to"])]),l("span",nt," @"+c(t(e).user.username),1),t(e).is_top?(i(),p(h,{key:0,class:"top-tag",type:"warning",size:"small",round:""},{default:n(()=>[r(" 置顶 ")]),_:1})):_("",!0),t(e).visibility==1?(i(),p(h,{key:1,class:"top-tag",type:"error",size:"small",round:""},{default:n(()=>[r(" 私密 ")]),_:1})):_("",!0),t(e).visibility==2?(i(),p(h,{key:2,class:"top-tag",type:"info",size:"small",round:""},{default:n(()=>[r(" 好友可见 ")]),_:1})):_("",!0)]),"header-extra":n(()=>[l("span",ot,c(t(e).ip_loc?t(e).ip_loc+" · ":t(e).ip_loc)+" "+c(t(Y)(t(e).created_on)),1)]),footer:n(()=>[t(e).attachments.length>0?(i(),p(y,{key:0,attachments:t(e).attachments},null,8,["attachments"])):_("",!0),t(e).charge_attachments.length>0?(i(),p(y,{key:1,attachments:t(e).charge_attachments,price:t(e).attachment_price},null,8,["attachments","price"])):_("",!0),t(e).imgs.length>0?(i(),p(O,{key:2,imgs:t(e).imgs},null,8,["imgs"])):_("",!0),t(e).videos.length>0?(i(),p(T,{key:3,videos:t(e).videos},null,8,["videos"])):_("",!0),t(e).links.length>0?(i(),p(q,{key:4,links:t(e).links},null,8,["links"])):_("",!0)]),action:n(()=>[a(B,{justify:"space-between"},{default:n(()=>[l("div",it,[a(u,{size:"18",class:"opt-item-icon"},{default:n(()=>[a(t(Z))]),_:1}),r(" "+c(t(e).upvote_count),1)]),l("div",rt,[a(u,{size:"18",class:"opt-item-icon"},{default:n(()=>[a(t(A))]),_:1}),r(" "+c(t(e).comment_count),1)]),l("div",ct,[a(u,{size:"18",class:"opt-item-icon"},{default:n(()=>[a(t(G))]),_:1}),r(" "+c(t(e).collection_count),1)]),l("div",pt,[a(u,{size:"18",class:"opt-item-icon"},{default:n(()=>[a(t(J))]),_:1}),r(" "+c(t(e).share_count),1)])]),_:1})]),_:2},[t(e).texts.length>0?{name:"description",fn:n(()=>[(i(!0),f(I,null,P(t(e).texts,g=>(i(),f("span",{key:g.id,class:"post-text",onClick:s[1]||(s[1]=v(M=>b(M,t(e).id),["stop"])),innerHTML:t(N)(g.content).content},null,8,at))),128))]),key:"0"}:void 0]),1024)])}}});export{ft as _};

@ -1 +1 @@
import{U as c}from"./naive-ui-ddb574dd.js";import{d as r,o as s,c as n,$ as l,a as o,L as t,F as p}from"./@vue-f70ab1bd.js";import{_ as i}from"./index-ce5b62d8.js";const m={class:"user"},d={class:"content"},u=r({__name:"post-skeleton",props:{num:{default:1}},setup(_){return(f,k)=>{const e=c;return s(!0),n(p,null,l(new Array(_.num),a=>(s(),n("div",{class:"skeleton-item",key:a},[o("div",m,[t(e,{circle:"",size:"small"})]),o("div",d,[t(e,{text:"",repeat:3}),t(e,{text:"",style:{width:"60%"}})])]))),128)}}});const b=i(u,[["__scopeId","data-v-ab0015b4"]]);export{b as _};
import{U as c}from"./naive-ui-ddb574dd.js";import{d as r,o as s,c as n,$ as l,a as o,L as t,F as p}from"./@vue-f70ab1bd.js";import{_ as i}from"./index-f6017bc3.js";const m={class:"user"},d={class:"content"},u=r({__name:"post-skeleton",props:{num:{default:1}},setup(_){return(f,k)=>{const e=c;return s(!0),n(p,null,l(new Array(_.num),a=>(s(),n("div",{class:"skeleton-item",key:a},[o("div",m,[t(e,{circle:"",size:"small"})]),o("div",d,[t(e,{text:"",repeat:3}),t(e,{text:"",style:{width:"60%"}})])]))),128)}}});const b=i(u,[["__scopeId","data-v-ab0015b4"]]);export{b as _};

@ -8,7 +8,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0" />
<link rel="manifest" href="/manifest.json" />
<title></title>
<script type="module" crossorigin src="/assets/index-ce5b62d8.js"></script>
<script type="module" crossorigin src="/assets/index-f6017bc3.js"></script>
<link rel="modulepreload" crossorigin href="/assets/@vue-f70ab1bd.js">
<link rel="modulepreload" crossorigin href="/assets/vue-router-29025daf.js">
<link rel="modulepreload" crossorigin href="/assets/vuex-cc1858c6.js">
@ -27,8 +27,8 @@
<link rel="modulepreload" crossorigin href="/assets/async-validator-dee29e8b.js">
<link rel="modulepreload" crossorigin href="/assets/date-fns-975a2d8f.js">
<link rel="modulepreload" crossorigin href="/assets/naive-ui-ddb574dd.js">
<link rel="modulepreload" crossorigin href="/assets/@vicons-fc06a0bb.js">
<link rel="stylesheet" href="/assets/index-bea67790.css">
<link rel="modulepreload" crossorigin href="/assets/@vicons-2f3cb6b9.js">
<link rel="stylesheet" href="/assets/index-6c46cd46.css">
<link rel="stylesheet" href="/assets/vfonts-7afd136d.css">
</head>

@ -1,163 +1,232 @@
import { request } from '@/utils/request';
import { request } from "@/utils/request";
/** 获取动态列表 */
export const getPosts = (params: NetParams.PostGetPosts): Promise<NetReq.PostGetPosts> => {
export const getPosts = (
params: NetParams.PostGetPosts
): Promise<NetReq.PostGetPosts> => {
return request({
method: 'get',
url: '/v1/posts',
params
method: "get",
url: "/v1/posts",
params,
});
};
/** 获取标签列表 */
export const getTags = (params: NetParams.PostGetTags): Promise<NetReq.PostGetTags> => {
export const getTags = (
params: NetParams.PostGetTags
): Promise<NetReq.PostGetTags> => {
return request({
method: 'get',
url: '/v1/tags',
params
method: "get",
url: "/v1/tags",
params,
});
};
/** 获取动态详情 */
export const getPost = (params: NetParams.PostGetPost): Promise<NetReq.PostGetPost> => {
export const getPost = (
params: NetParams.PostGetPost
): Promise<NetReq.PostGetPost> => {
return request({
method: 'get',
url: '/v1/post',
params
method: "get",
url: "/v1/post",
params,
});
};
/** 获取动态点赞状态 */
export const getPostStar = (params: NetParams.PostPostStar): Promise<NetReq.PostGetPostStar> => {
export const getPostStar = (
params: NetParams.PostPostStar
): Promise<NetReq.PostGetPostStar> => {
return request({
method: 'get',
url: '/v1/post/star',
params
method: "get",
url: "/v1/post/star",
params,
});
};
/** 动态点赞 */
export const postStar = (data: NetParams.PostPostStar): Promise<NetReq.PostPostStar> => {
export const postStar = (
data: NetParams.PostPostStar
): Promise<NetReq.PostPostStar> => {
return request({
method: 'post',
url: '/v1/post/star',
data
method: "post",
url: "/v1/post/star",
data,
});
};
/** 获取动态收藏状态 */
export const getPostCollection = (params: NetParams.PostGetPostCollection): Promise<NetReq.PostGetPostCollection> => {
export const getPostCollection = (
params: NetParams.PostGetPostCollection
): Promise<NetReq.PostGetPostCollection> => {
return request({
method: 'get',
url: '/v1/post/collection',
params
method: "get",
url: "/v1/post/collection",
params,
});
};
/** 动态收藏 */
export const postCollection = (data: NetParams.PostPostCollection): Promise<NetReq.PostPostCollection> => {
export const postCollection = (
data: NetParams.PostPostCollection
): Promise<NetReq.PostPostCollection> => {
return request({
method: 'post',
url: '/v1/post/collection',
data
method: "post",
url: "/v1/post/collection",
data,
});
};
/** 获取动态评论列表 */
export const getPostComments = (params: NetParams.PostGetPostComments): Promise<NetReq.PostGetPostComments> => {
export const getPostComments = (
params: NetParams.PostGetPostComments
): Promise<NetReq.PostGetPostComments> => {
return request({
method: 'get',
url: '/v1/post/comments',
params
method: "get",
url: "/v1/post/comments",
params,
});
};
/** 获取联系人列表 */
export const getContacts = (params: NetParams.GetContacts): Promise<NetReq.GetContacts> => {
export const getContacts = (
params: NetParams.GetContacts
): Promise<NetReq.GetContacts> => {
return request({
method: 'get',
url: '/v1/user/contacts',
params
method: "get",
url: "/v1/user/contacts",
params,
});
};
/** 发布动态 */
export const createPost = (data: NetParams.PostCreatePost): Promise<NetReq.PostCreatePost> => {
export const createPost = (
data: NetParams.PostCreatePost
): Promise<NetReq.PostCreatePost> => {
return request({
method: 'post',
url: '/v1/post',
data
method: "post",
url: "/v1/post",
data,
});
};
/** 删除动态 */
export const deletePost = (data: NetParams.PostDeletePost): Promise<NetReq.PostDeletePost> => {
export const deletePost = (
data: NetParams.PostDeletePost
): Promise<NetReq.PostDeletePost> => {
return request({
method: 'delete',
url: '/v1/post',
data
method: "delete",
url: "/v1/post",
data,
});
};
/** 锁定/解锁动态 */
export const lockPost = (data: NetParams.PostLockPost): Promise<NetReq.PostLockPost> => {
export const lockPost = (
data: NetParams.PostLockPost
): Promise<NetReq.PostLockPost> => {
return request({
method: 'post',
url: '/v1/post/lock',
data
method: "post",
url: "/v1/post/lock",
data,
});
};
/** 置顶/取消置顶动态 */
export const stickPost = (data: NetParams.PostStickPost): Promise<NetReq.PostStickPost> => {
export const stickPost = (
data: NetParams.PostStickPost
): Promise<NetReq.PostStickPost> => {
return request({
method: 'post',
url: '/v1/post/stick',
data
method: "post",
url: "/v1/post/stick",
data,
});
};
/** 置顶/取消置顶动态 */
export const visibilityPost = (data: NetParams.PostVisibilityPost): Promise<NetReq.PostVisibilityPost> => {
export const visibilityPost = (
data: NetParams.PostVisibilityPost
): Promise<NetReq.PostVisibilityPost> => {
return request({
method: 'post',
url: '/v1/post/visibility',
data
method: "post",
url: "/v1/post/visibility",
data,
});
};
/** 发布动态评论 */
export const createComment = (data: NetParams.PostCreateComment): Promise<NetReq.PostCreateComment> => {
export const createComment = (
data: NetParams.PostCreateComment
): Promise<NetReq.PostCreateComment> => {
return request({
method: 'post',
url: '/v1/post/comment',
data
method: "post",
url: "/v1/post/comment",
data,
});
};
/** 删除评论 */
export const deleteComment = (data: NetParams.PostDeleteComment): Promise<NetReq.PostDeleteComment> => {
export const deleteComment = (
data: NetParams.PostDeleteComment
): Promise<NetReq.PostDeleteComment> => {
return request({
method: 'delete',
url: '/v1/post/comment',
data
method: "delete",
url: "/v1/post/comment",
data,
});
};
/** 发布评论回复 */
export const createCommentReply = (data: NetParams.PostCreateCommentReply): Promise<NetReq.PostCreateCommentReply> => {
export const createCommentReply = (
data: NetParams.PostCreateCommentReply
): Promise<NetReq.PostCreateCommentReply> => {
return request({
method: 'post',
url: '/v1/post/comment/reply',
data
method: "post",
url: "/v1/post/comment/reply",
data,
});
};
/** 删除评论回复 */
export const deleteCommentReply = (data: NetParams.PostDeleteCommentReply): Promise<NetReq.PostDeleteCommentReply> => {
export const deleteCommentReply = (
data: NetParams.PostDeleteCommentReply
): Promise<NetReq.PostDeleteCommentReply> => {
return request({
method: 'delete',
url: '/v1/post/comment/reply',
data
method: "delete",
url: "/v1/post/comment/reply",
data,
});
};
/** 置顶/取消置顶话题 */
export const stickTopic = (
data: NetParams.PostStickTopic
): Promise<NetReq.PostStickTopic> => {
return request({
method: "post",
url: "/v1/topic/stick",
data,
});
};
/** 关注话题 */
export const followTopic = (
data: NetParams.PostFollowTopic
): Promise<NetReq.PostFollowTopic> => {
return request({
method: "post",
url: "/v1/topic/follow",
data,
});
};
/** 取消关注话题 */
export const unfollowTopic = (
data: NetParams.PostUnfollowTopic
): Promise<NetReq.PostUnfollowTopic> => {
return request({
method: "post",
url: "/v1/topic/unfollow",
data,
});
};

@ -13,9 +13,31 @@
</template>
</n-input>
</div>
<n-card v-if="showFollowTopics" class="hottopic-wrap" title="关注话题" embedded :bordered="false" size="small">
<n-spin :show="loading">
<div class="hot-tag-item" v-for="tag in followTags" :key="tag.id">
<router-link
class="hash-link"
:to="{
name: 'home',
query: {
q: tag.tag,
t: 'tag',
},
}"
>
#{{ tag.tag }}
</router-link>
<div class="post-num">
{{ formatQuoteNum(tag.quote_num) }}
</div>
</div>
</n-spin>
</n-card>
<n-card class="hottopic-wrap" title="热门话题" embedded :bordered="false" size="small">
<n-spin :show="loading">
<div class="hot-tag-item" v-for="tag in tags" :key="tag.id">
<div class="hot-tag-item" v-for="tag in hotTags" :key="tag.id">
<router-link
class="hash-link"
:to="{
@ -60,13 +82,14 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ref, onMounted, computed, watch } from 'vue';
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { getTags } from '@/api/post';
import { Search } from '@vicons/ionicons5';
const tags = ref<Item.TagProps[]>([]);
const hotTags = ref<Item.TagProps[]>([]);
const followTags = ref<Item.TagProps[]>([]);
const loading = ref(false);
const keyword = ref('');
const store = useStore();
@ -77,14 +100,17 @@ const copyrightLeftLink = import.meta.env.VITE_COPYRIGHT_LEFT_LINK
const copyrightRight = import.meta.env.VITE_COPYRIGHT_RIGHT
const copyrightRightLink = import.meta.env.VITE_COPYRIGHT_RIGHT_LINK
const loadTags = () => {
const loadHotTags = () => {
loading.value = true;
getTags({
type: 'hot',
type: 'hot_extral',
num: 12,
extral_num: 8,
})
.then((res) => {
tags.value = res.topics;
hotTags.value = res.topics;
followTags.value = res.extral_topics??[];
showFollowTopics.value = true
loading.value = false;
})
.catch((err) => {
@ -106,8 +132,26 @@ const handleSearch = () => {
},
});
};
const showFollowTopics = computed({
get: () => {
return store.state.userLogined && followTags.value.length !==0;
},
set: (newVal) => {
// do nothing
},
});
watch(
() => ({
refreshTopicFollow: store.state.refreshTopicFollow,
}),
(to, from) => {
if (to.refreshTopicFollow !== from.refreshTopicFollow) {
loadHotTags();
}
}
);
onMounted(() => {
loadTags();
loadHotTags();
});
</script>
@ -143,9 +187,11 @@ onMounted(() => {
}
}
.copyright-wrap {
margin-top: 10px;
.hottopic-wrap {
margin-bottom: 10px;
}
.copyright-wrap {
.copyright {
font-size: 12px;
opacity: 0.75;

@ -239,7 +239,6 @@ const goHome = () => {
if (route.path === '/') {
store.commit('refresh');
}
goRouter('home');
};
const triggerAuth = (key: string) => {
@ -248,6 +247,7 @@ const triggerAuth = (key: string) => {
};
const handleLogout = () => {
store.commit('userLogout');
goHome()
};
window.$store = store;
window.$message = useMessage();

@ -0,0 +1,184 @@
<template>
<div v-if="!checkFollowing || (checkFollowing && tag.is_following === 1)" class="tag-item">
<n-thing>
<template #header>
<n-tag
type="success"
size="large"
round
:key="tag.id"
>
<router-link
class="hash-link"
:to="{
name: 'home',
query: {
q: tag.tag,
t: 'tag',
},
}"
>
#{{ tag.tag }}
</router-link>
<span v-if="!showAction" class="tag-quote">({{ tag.quote_num }})</span>
<span v-if="showAction" class="tag-quote tag-follow">({{ tag.quote_num }})</span>
<template #avatar>
<n-avatar :src="tag.user.avatar" />
</template>
</n-tag>
</template>
<template #header-extra>
<div
v-if="showAction"
class="options">
<n-dropdown
placement="bottom-end"
trigger="click"
size="small"
:options="tagOptions"
@select="handleTagAction"
>
<n-button type="success" quaternary circle block>
<template #icon>
<n-icon>
<more-vert-outlined />
</n-icon>
</template>
</n-button>
</n-dropdown>
</div>
</template>
</n-thing>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, computed } from 'vue';
import { MoreVertOutlined } from '@vicons/material';
import type { DropdownOption } from 'naive-ui';
import { stickTopic, followTopic, unfollowTopic } from '@/api/post';
const hasFollowing= ref(false);
const props = withDefaults(
defineProps<{
tag: Item.TagProps;
showAction: boolean;
checkFollowing: boolean;
}>(),
{}
);
const tagOptions = computed(() => {
let options: DropdownOption[] = [];
if (props.tag.is_following === 0) {
options.push({
label: '',
key: 'follow',
});
} else {
if (props.tag.is_top === 0) {
options.push({
label: '',
key: 'stick',
});
} else {
options.push({
label: '',
key: 'unstick',
});
}
options.push({
label: '',
key: 'unfollow',
});
}
return options;
});
const handleTagAction = (
item: 'follow' | 'unfollow' | 'stick' | 'unstick'
) => {
switch (item) {
case 'follow':
followTopic({
topic_id: props.tag.id
})
.then((res) => {
props.tag.is_following = 1
window.$message.success(`关注成功`);
})
.catch((err) => {
console.log(err);
});
break;
case 'unfollow':
unfollowTopic({
topic_id: props.tag.id
})
.then((res) => {
props.tag.is_following = 0
window.$message.success(`取消关注`);
})
.catch((err) => {
console.log(err);
});
break;
case 'stick':
stickTopic({
topic_id: props.tag.id
})
.then((res) => {
props.tag.is_top = res.top_status
window.$message.success(`置顶成功`);
})
.catch((err) => {
console.log(err);
});
break;
case 'unstick':
stickTopic({
topic_id: props.tag.id
})
.then((res) => {
props.tag.is_top = res.top_status
window.$message.success(`取消置顶`);
})
.catch((err) => {
console.log(err);
});
break;
default:
break;
}
};
onMounted(() => {
hasFollowing.value = false
});
</script>
<style lang="less">
.tag-item {
.tag-quote {
margin-left: 12px;
font-size: 14px;
opacity: 0.75;
}
.tag-follow {
margin-right: 22px;
}
.options {
margin-left: -32px;
margin-bottom: 4px;
opacity: 0.55;
}
.n-thing {
.n-thing-header {
margin-bottom: 0px;
}
.n-thing-avatar-header-wrapper {
align-items: center;
}
}
}
</style>

@ -4,6 +4,7 @@ import { createStore } from "vuex";
export default createStore({
state: {
refresh: Date.now(),
refreshTopicFollow: Date.now(),
theme: localStorage.getItem("PAOPAO_THEME"),
collapsedLeft: document.body.clientWidth <= 821,
collapsedRight: document.body.clientWidth <= 821,
@ -11,6 +12,7 @@ export default createStore({
desktopModelShow: document.body.clientWidth > 821,
authModalShow: false,
authModelTab: "signin",
userLogined: false,
userInfo: {
id: 0,
username: "",
@ -21,6 +23,9 @@ export default createStore({
refresh(state, refresh) {
state.refresh = refresh || Date.now();
},
refreshTopicFollow(state) {
state.refreshTopicFollow = Date.now();
},
triggerTheme(state, theme) {
state.theme = theme;
},
@ -40,10 +45,14 @@ export default createStore({
},
updateUserinfo(state, data) {
state.userInfo = data;
if (state.userInfo.id > 0) {
state.userLogined = true;
}
},
userLogout(state) {
localStorage.removeItem("PAOPAO_TOKEN");
state.userInfo = { id: 0, nickname: "", username: "" };
state.userLogined = false;
},
},
actions: {},

@ -290,6 +290,10 @@ declare module Item {
modified_on?: number;
/** 删除时间 */
deleted_on?: number;
/** 是否关注0为未关注1为已关注 */
is_following?: 0 | 1;
/** 是否置顶0为未置顶1为已置顶 */
is_top?: 0 | 1;
/** 是否删除0为未删除1为已删除 */
is_del?: 0 | 1;
}

@ -165,8 +165,9 @@ declare module NetParams {
}
interface PostGetTags {
type: "hot" | "new";
type: "hot" | "new" | "follow" | "hot_extral";
num: number;
extral_num?: number;
}
interface PostGetPostComments {
@ -218,4 +219,16 @@ declare module NetParams {
interface PostDeleteCommentReply {
id: number;
}
interface PostStickTopic {
topic_id: number;
}
interface PostFollowTopic {
topic_id: number;
}
interface PostUnfollowTopic {
topic_id: number;
}
}

@ -1,197 +1,181 @@
declare module NetReq {
interface AuthUserLogin {
token: string
token: string;
}
interface AuthUserRegister {
/** 用户UID */
id: number,
id: number;
/** 用户名 */
username: string
username: string;
}
type AuthUserInfo = Item.UserInfo
interface AuthUpdateUserPassword {
type AuthUserInfo = Item.UserInfo;
}
interface AuthUpdateUserPassword {}
interface UserGetCollections {
/** 帖子列表 */
list: Item.PostProps[],
list: Item.PostProps[];
/** 页码信息 */
pager: Item.PagerProps
pager: Item.PagerProps;
}
interface UserGetSuggestUsers {
suggest: string[]
suggest: string[];
}
interface UserGetSuggestTags {
suggest: string[]
suggest: string[];
}
interface UserPrecheckAttachment {
paid: number
paid: number;
}
interface UserGetAttachment {
signed_url: string
signed_url: string;
}
interface UserGetUnreadMsgCount {
count: number
count: number;
}
interface UserGetMessages {
/** 消息列表 */
list: Item.MessageProps[],
list: Item.MessageProps[];
/** 页码信息 */
pager: Item.PagerProps
pager: Item.PagerProps;
}
interface UserGetUserPosts {
/** 帖子列表 */
list: Item.PostProps[],
list: Item.PostProps[];
/** 页码信息 */
pager: Item.PagerProps
pager: Item.PagerProps;
}
type UserGetUserProfile = Item.UserInfo
type UserGetUserProfile = Item.UserInfo;
interface UserGetBills {
list: Item.BillProps[],
list: Item.BillProps[];
/** 页码信息 */
pager: Item.PagerProps
pager: Item.PagerProps;
}
interface UserReqRecharge {
id: number,
pay: string
id: number;
pay: string;
}
interface UserGetRecharge {
status: string
status: string;
}
interface UserBindUserPhone {
}
interface UserBindUserPhone {}
interface UserGetCaptcha {
id: string,
id: string;
/** 头像图片 base64 */
b64s: string
b64s: string;
}
interface UserChangeNickname {
}
interface UserChangeNickname {}
interface UserChangePassword {
interface UserChangePassword {}
}
interface UserChangeStatus {}
interface UserChangeStatus {
interface AddFriend {}
}
interface AddFriend {
}
interface DeleteFriend {
}
interface DeleteFriend {}
interface GetContacts {
contacts: Item.ContactsItemProps,
total: number
contacts: Item.ContactsItemProps;
total: number;
}
interface RejectFriend {
interface RejectFriend {}
}
interface RequestingFriend {
}
interface RequestingFriend {}
type PostGetPost = Item.PostProps
type PostGetPost = Item.PostProps;
interface PostGetPosts {
/** 帖子列表 */
list: Item.PostProps[],
list: Item.PostProps[];
/** 页码信息 */
pager: Item.PagerProps
pager: Item.PagerProps;
}
interface PostLockPost {
/** 锁定状态0为未锁定1为锁定 */
lock_status: 0 | 1
lock_status: 0 | 1;
}
interface PostStickPost {
/** 置顶状态0为未置顶1为置顶 */
top_status: 0 | 1
top_status: 0 | 1;
}
interface PostVisibilityPost {
/** 可见性0为公开1为私密2为好友可见 */
visibility_status: import('@/utils/IEnum').VisibilityEnum
visibility_status: import("@/utils/IEnum").VisibilityEnum;
}
interface PostGetPostStar {
status: boolean
status: boolean;
}
interface PostPostStar {
status: boolean
status: boolean;
}
interface PostGetPostCollection {
status: boolean
status: boolean;
}
interface PostPostCollection {
status: boolean
status: boolean;
}
interface PostGetTags {
topics: Item.TagProps[]
topics: Item.TagProps[];
extral_topics?: Item.TagProps[];
}
interface PostGetPostComments {
/** 评论列表 */
list: Item.CommentProps[],
list: Item.CommentProps[];
/** 页码信息 */
pager: Item.PagerProps
}
type PostCreatePost = Item.PostProps
interface PostDeletePost {
pager: Item.PagerProps;
}
type PostCreateComment = Item.CommentProps
type PostCreatePost = Item.PostProps;
interface PostDeleteComment {
interface PostDeletePost {}
}
type PostCreateComment = Item.CommentProps;
type PostCreateCommentReply = Item.ReplyProps
interface PostDeleteComment {}
interface PostDeleteCommentReply {
type PostCreateCommentReply = Item.ReplyProps;
}
interface PostDeleteCommentReply {}
interface GetContacts {
/** 评论列表 */
list: Item.ContactItemProps[],
list: Item.ContactItemProps[];
/** 页码信息 */
pager: Item.PagerProps
pager: Item.PagerProps;
}
interface PostStickTopic {
/** 置顶状态0为未置顶1为置顶 */
top_status: 0 | 1;
}
interface PostFollowTopic {}
interface PostUnfollowTopic {}
}

@ -14,18 +14,14 @@
</n-spin>
</n-list-item>
<div class="comment-opts-wrap" v-if="post.id > 0">
<n-space justify="space-between">
<div class="comment-title-item">
<span comment-title-item></span>
</div>
<div class="comment-opt-item ">
<n-tabs type="bar" size="small" animated @update:value="commentTab">
<n-tabs type="bar" justify-content="end" size="small" animated @update:value="commentTab">
<template #prefix>
<span class="comment-title-item"></span>
</template>
<n-tab-pane name="default" tab="默认" />
<n-tab-pane name="newest" tab="最新" />
</n-tabs>
</div>
</n-space>
</div>
<n-list-item v-if="post.id > 0">
<compose-comment
:lock="post.is_lock"
@ -131,22 +127,15 @@ watch(postId, () => {
.detail-wrap {
min-height: 100px;
}
.comment-opts-wrap {
margin-top: 6px;
.comment-opt-item {
display: flex;
padding-top: 6px;
padding-left: 16px;
padding-right: 16px;
align-items: center;
opacity: 0.75;
}
.comment-title-item {
padding-left: 16px;
padding-top: 4px;
font-size: 16px;
text-align: center;
opacity: 0.75;
}
}
.dark {

@ -6,33 +6,23 @@
<n-tabs type="line" animated @update:value="changeTab">
<n-tab-pane name="hot" tab="热门" />
<n-tab-pane name="new" tab="最新" />
<n-tab-pane v-if="store.state.userLogined"
name="follow" tab="关注" />
<template v-if="store.state.userLogined" #suffix>
<n-tag v-model:checked="tagsChecked" checkable>
{{tagsEditText}}
</n-tag>
</template>
</n-tabs>
<n-spin :show="loading">
<n-space>
<n-tag
class="tag-item"
type="success"
round
<tag-item
v-for="tag in tags"
:key="tag.id"
>
<router-link
class="hash-link"
:to="{
name: 'home',
query: {
q: tag.tag,
t: 'tag',
},
}"
:tag="tag"
:showAction="store.state.userLogined && tagsChecked"
:checkFollowing="inFollwTab"
>
#{{ tag.tag }}
</router-link>
<span class="tag-hot">({{ tag.quote_num }})</span>
<template #avatar>
<n-avatar :src="tag.user.avatar" />
</template>
</n-tag>
</tag-item>
</n-space>
</n-spin>
</n-list>
@ -40,13 +30,35 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ref, onMounted, computed, watch} from 'vue';
import { getTags } from '@/api/post';
import { useStore } from 'vuex';
const store = useStore();
const tags = ref<Item.TagProps[]>([]);
const tagType = ref<"hot" | "new">('hot');
const tagType = ref<"hot" | "new" | "follow">('hot');
const loading = ref(false);
const tagsChecked = ref(false)
const inFollwTab = ref(false)
watch(tagsChecked, () => {
if (!tagsChecked.value) {
window.$message.success("保存成功");
store.commit("refreshTopicFollow")
}
});
const tagsEditText = computed({
get: () => {
let text = "编辑";
if (tagsChecked.value) {
text = "保存";
}
return text;
},
set: (newVal) => {
// do nothing
},
});
const loadTags = () => {
loading.value = true;
getTags({
@ -58,11 +70,17 @@ const loadTags = () => {
loading.value = false;
})
.catch((err) => {
console.log(err);
loading.value = false;
});
};
const changeTab = (tab: "hot" | "new") => {
const changeTab = (tab: "hot" | "new" | "follow") => {
tagType.value = tab;
if (tab == "follow") {
inFollwTab.value = true
} else {
inFollwTab.value = false
}
loadTags();
};
onMounted(() => {
@ -73,13 +91,6 @@ onMounted(() => {
<style lang="less" scoped>
.tags-wrap {
padding: 20px;
.tag-item {
.tag-hot {
margin-left: 12px;
font-size: 12px;
opacity: 0.75;
}
}
}
.dark {
.tags-wrap {

Loading…
Cancel
Save