optimize topic ui and logic (60%)

pull/273/head
Michael Li 2 years ago
parent 47a9d9e8b4
commit e58e069755
No known key found for this signature in database

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

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

@ -18,5 +18,8 @@ type TopicService interface {
CreateTag(tag *Tag) (*Tag, error) CreateTag(tag *Tag) (*Tag, error)
DeleteTag(tag *Tag) error DeleteTag(tag *Tag) error
GetTags(conditions *ConditionsT, offset, limit int) ([]*Tag, error) GetTags(conditions *ConditionsT, offset, limit int) ([]*Tag, error)
GetHotTags(limit int, offset int) ([]*TagFormated, error)
GetNewestTags(limit int, offset int) ([]*TagFormated, error)
GetFollowTags(limit int, offset int) ([]*TagFormated, error)
GetTagsByKeyword(keyword string) ([]*Tag, error) GetTagsByKeyword(keyword string) ([]*Tag, error)
} }

@ -22,6 +22,7 @@ type TagFormated struct {
User *UserFormated `json:"user"` User *UserFormated `json:"user"`
Tag string `json:"tag"` Tag string `json:"tag"`
QuoteNum int64 `json:"quote_num"` QuoteNum int64 `json:"quote_num"`
IsFollowing int8 `json:"is_following"`
} }
func (t *Tag) Format() *TagFormated { func (t *Tag) Format() *TagFormated {
@ -35,6 +36,7 @@ func (t *Tag) Format() *TagFormated {
User: &UserFormated{}, User: &UserFormated{},
Tag: t.Tag, Tag: t.Tag,
QuoteNum: t.QuoteNum, QuoteNum: t.QuoteNum,
IsFollowing: 0,
} }
} }

@ -49,6 +49,7 @@ func NewDataService() (core.DataService, core.VersionInfo) {
pvs := security.NewPhoneVerifyService() pvs := security.NewPhoneVerifyService()
ams := NewAuthorizationManageService() ams := NewAuthorizationManageService()
ths := newTweetHelpService(db) ths := newTweetHelpService(db)
ums := newUserManageService(db)
// initialize core.IndexPostsService // initialize core.IndexPostsService
if cfg.If("Friendship") { if cfg.If("Friendship") {
@ -86,7 +87,7 @@ func NewDataService() (core.DataService, core.VersionInfo) {
IndexPostsService: cis, IndexPostsService: cis,
WalletService: newWalletService(db), WalletService: newWalletService(db),
MessageService: newMessageService(db), MessageService: newMessageService(db),
TopicService: newTopicService(db), TopicService: newTopicService(db, ums),
TweetService: newTweetService(db), TweetService: newTweetService(db),
TweetManageService: newTweetManageService(db, cis), TweetManageService: newTweetManageService(db, cis),
TweetHelpService: newTweetHelpService(db), TweetHelpService: newTweetHelpService(db),

@ -18,11 +18,13 @@ var (
type topicServant struct { type topicServant struct {
db *gorm.DB db *gorm.DB
ums core.UserManageService
} }
func newTopicService(db *gorm.DB) core.TopicService { func newTopicService(db *gorm.DB, ums core.UserManageService) core.TopicService {
return &topicServant{ return &topicServant{
db: db, db: db,
ums: ums,
} }
} }
@ -38,6 +40,60 @@ func (s *topicServant) GetTags(conditions *core.ConditionsT, offset, limit int)
return (&dbr.Tag{}).List(s.db, conditions, offset, limit) return (&dbr.Tag{}).List(s.db, conditions, offset, limit)
} }
func (s *topicServant) GetHotTags(limit int, offset int) ([]*core.TagFormated, error) {
tags, err := (&dbr.Tag{}).List(s.db, &core.ConditionsT{
"ORDER": "quote_num DESC",
}, offset, limit)
if err != nil {
return nil, err
}
return s.tagsFormat(tags)
}
func (s *topicServant) GetNewestTags(limit int, offset int) ([]*core.TagFormated, error) {
tags, err := (&dbr.Tag{}).List(s.db, &core.ConditionsT{
"ORDER": "id DESC",
}, offset, limit)
if err != nil {
return nil, err
}
return s.tagsFormat(tags)
}
func (s *topicServant) GetFollowTags(limit int, offset int) ([]*core.TagFormated, error) {
// TODO具体逻辑稍后实现先用热门标签替换
tags, err := (&dbr.Tag{}).List(s.db, &core.ConditionsT{
"ORDER": "quote_num DESC",
}, offset, limit)
if err != nil {
return nil, err
}
return s.tagsFormat(tags)
}
func (s *topicServant) tagsFormat(tags []*core.Tag) ([]*core.TagFormated, error) {
// 获取创建者User IDs
userIds := []int64{}
for _, tag := range tags {
userIds = append(userIds, tag.UserID)
}
users, err := s.ums.GetUsersByIDs(userIds)
if err != nil {
return nil, err
}
tagsFormated := []*core.TagFormated{}
for _, tag := range tags {
tagFormated := tag.Format()
for _, user := range users {
if user.ID == tagFormated.UserID {
tagFormated.User = user.Format()
}
}
tagsFormated = append(tagsFormated, tagFormated)
}
return tagsFormated, nil
}
func (s *topicServant) GetTagsByKeyword(keyword string) ([]*core.Tag, error) { func (s *topicServant) GetTagsByKeyword(keyword string) ([]*core.Tag, error) {
tag := &dbr.Tag{} tag := &dbr.Tag{}

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

@ -10,13 +10,6 @@ import (
"github.com/rocboss/paopao-ce/pkg/version" "github.com/rocboss/paopao-ce/pkg/version"
) )
const (
TagTypeHot TagType = "hot"
TagTypeNew TagType = "new"
)
type TagType string
type TweetDetailReq struct { type TweetDetailReq struct {
TweetId int64 `form:"id"` TweetId int64 `form:"id"`
} }
@ -32,17 +25,6 @@ type TweetCommentsReq struct {
type TweetCommentsResp base.PageResp 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 []*core.TagFormated `json:"topics"`
}
type GetCaptchaResp struct { type GetCaptchaResp struct {
Id string `json:"id"` Id string `json:"id"`
Content string `json:"b64s"` Content string `json:"b64s"`

@ -8,6 +8,7 @@ import (
"github.com/alimy/mir/v3" "github.com/alimy/mir/v3"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
api "github.com/rocboss/paopao-ce/auto/api/v1" 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/core"
"github.com/rocboss/paopao-ce/internal/model/web" "github.com/rocboss/paopao-ce/internal/model/web"
"github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/internal/servants/base"
@ -149,6 +150,40 @@ func (s *looseSrv) GetUserProfile(req *web.GetUserProfileReq) (*web.GetUserProfi
}, nil }, nil
} }
func (s *looseSrv) TopicList(req *web.TopicListReq) (*web.TopicListResp, mir.Error) {
var (
tags, extralTags []*core.TagFormated
err error
)
num := req.Num
if num > conf.AppSetting.MaxPageSize {
num = conf.AppSetting.MaxPageSize
}
switch req.Type {
case web.TagTypeHot:
tags, err = s.Ds.GetHotTags(num, 0)
case web.TagTypeNew:
tags, err = s.Ds.GetNewestTags(num, 0)
case web.TagTypeFollow:
tags, err = s.Ds.GetFollowTags(num, 0)
case web.TagTypeHotExtral:
tags, err = s.Ds.GetHotTags(num, 0)
if err == nil {
extralTags, err = s.Ds.GetFollowTags(num, 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 { func newLooseSrv(s *base.DaoServant) api.Loose {
return &looseSrv{ return &looseSrv{
DaoServant: s, DaoServant: s,

@ -18,7 +18,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
api "github.com/rocboss/paopao-ce/auto/api/v1" 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/core"
"github.com/rocboss/paopao-ce/internal/model/web" "github.com/rocboss/paopao-ce/internal/model/web"
"github.com/rocboss/paopao-ce/internal/servants/base" "github.com/rocboss/paopao-ce/internal/servants/base"
@ -66,50 +65,6 @@ func (b *pubBinding) BindTweetComments(c *gin.Context) (*web.TweetCommentsReq, m
}, nil }, 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
}
conditions := &core.ConditionsT{}
if req.Type == web.TagTypeHot {
// 热门标签
conditions = &core.ConditionsT{
"ORDER": "quote_num DESC",
}
} else if req.Type == web.TagTypeNew {
// 热门标签
conditions = &core.ConditionsT{
"ORDER": "id DESC",
}
}
tags, err := s.Ds.GetTags(conditions, 0, num)
if err != nil {
return nil, _errGetPostTagsFailed
}
// 获取创建者User IDs
userIds := []int64{}
for _, tag := range tags {
userIds = append(userIds, tag.UserID)
}
users, _ := s.Ds.GetUsersByIDs(userIds)
tagsFormated := []*core.TagFormated{}
for _, tag := range tags {
tagFormated := tag.Format()
for _, user := range users {
if user.ID == tagFormated.UserID {
tagFormated.User = user.Format()
}
}
tagsFormated = append(tagsFormated, tagFormated)
}
return &web.TopicListResp{
Topics: tagsFormated,
}, nil
}
func (s *pubSrv) TweetComments(req *web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error) { func (s *pubSrv) TweetComments(req *web.TweetCommentsReq) (*web.TweetCommentsResp, mir.Error) {
sort := "id ASC" sort := "id ASC"
if req.SortStrategy == "newest" { if req.SortStrategy == "newest" {

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

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

@ -13,9 +13,31 @@
</template> </template>
</n-input> </n-input>
</div> </div>
<n-card v-if="store.state.userLogined" 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-card class="hottopic-wrap" title="热门话题" embedded :bordered="false" size="small">
<n-spin :show="loading"> <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 <router-link
class="hash-link" class="hash-link"
:to="{ :to="{
@ -66,7 +88,8 @@ import { useRouter } from 'vue-router';
import { getTags } from '@/api/post'; import { getTags } from '@/api/post';
import { Search } from '@vicons/ionicons5'; 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 loading = ref(false);
const keyword = ref(''); const keyword = ref('');
const store = useStore(); const store = useStore();
@ -77,14 +100,15 @@ const copyrightLeftLink = import.meta.env.VITE_COPYRIGHT_LEFT_LINK
const copyrightRight = import.meta.env.VITE_COPYRIGHT_RIGHT const copyrightRight = import.meta.env.VITE_COPYRIGHT_RIGHT
const copyrightRightLink = import.meta.env.VITE_COPYRIGHT_RIGHT_LINK const copyrightRightLink = import.meta.env.VITE_COPYRIGHT_RIGHT_LINK
const loadTags = () => { const loadHotTags = () => {
loading.value = true; loading.value = true;
getTags({ getTags({
type: 'hot', type: 'hot_extral',
num: 12, num: 12,
}) })
.then((res) => { .then((res) => {
tags.value = res.topics; hotTags.value = res.topics;
followTags.value = res.extral_topics;
loading.value = false; loading.value = false;
}) })
.catch((err) => { .catch((err) => {
@ -107,7 +131,7 @@ const handleSearch = () => {
}); });
}; };
onMounted(() => { onMounted(() => {
loadTags(); loadHotTags();
}); });
</script> </script>
@ -143,9 +167,11 @@ onMounted(() => {
} }
} }
.copyright-wrap { .hottopic-wrap {
margin-top: 10px; margin-bottom: 10px;
}
.copyright-wrap {
.copyright { .copyright {
font-size: 12px; font-size: 12px;
opacity: 0.75; opacity: 0.75;

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

@ -0,0 +1,133 @@
<template>
<div 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';
const hasFollowing= ref(false);
const props = withDefaults(
defineProps<{
tag: Item.TagProps;
showAction: boolean;
}>(),
{}
);
const tagOptions = computed(() => {
let options: DropdownOption[] = [];
if (props.tag.is_following == 0) {
options.push({
label: '',
key: 'follow',
});
} else {
options.push({
label: '',
key: 'unfollow',
});
}
return options;
});
const handleTagAction = (
item: 'follow' | 'unfollow'
) => {
switch (item) {
case 'follow':
window.$message.success(`关注话题 - ${props.tag.tag}`);
break;
case 'unfollow':
window.$message.success(`取消关注话题 - ${props.tag.tag}`);
break;
default:
break;
}
};
const execFollowAction = () => {
// TODO
window.$message.success('follow/unfllow');
};
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>

@ -11,6 +11,7 @@ export default createStore({
desktopModelShow: document.body.clientWidth > 821, desktopModelShow: document.body.clientWidth > 821,
authModalShow: false, authModalShow: false,
authModelTab: "signin", authModelTab: "signin",
userLogined: false,
userInfo: { userInfo: {
id: 0, id: 0,
username: "", username: "",
@ -40,10 +41,14 @@ export default createStore({
}, },
updateUserinfo(state, data) { updateUserinfo(state, data) {
state.userInfo = data; state.userInfo = data;
if (state.userInfo.id > 0) {
state.userLogined = true;
}
}, },
userLogout(state) { userLogout(state) {
localStorage.removeItem("PAOPAO_TOKEN"); localStorage.removeItem("PAOPAO_TOKEN");
state.userInfo = { id: 0, nickname: "", username: "" }; state.userInfo = { id: 0, nickname: "", username: "" };
state.userLogined = false;
}, },
}, },
actions: {}, actions: {},

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

@ -165,7 +165,7 @@ declare module NetParams {
} }
interface PostGetTags { interface PostGetTags {
type: "hot" | "new"; type: "hot" | "new" | "follow" | "hot_extral";
num: number; num: number;
} }

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

@ -6,33 +6,17 @@
<n-tabs type="line" animated @update:value="changeTab"> <n-tabs type="line" animated @update:value="changeTab">
<n-tab-pane name="hot" tab="热门" /> <n-tab-pane name="hot" tab="热门" />
<n-tab-pane name="new" tab="最新" /> <n-tab-pane name="new" tab="最新" />
<n-tab-pane v-if="store.state.userLogined"
name="follow" tab="关注" />
</n-tabs> </n-tabs>
<n-spin :show="loading"> <n-spin :show="loading">
<n-space> <n-space>
<n-tag <tag-item
class="tag-item"
type="success"
round
v-for="tag in tags" v-for="tag in tags"
:key="tag.id" :tag="tag"
:showAction="store.state.userLogined"
> >
<router-link </tag-item>
class="hash-link"
:to="{
name: 'home',
query: {
q: tag.tag,
t: 'tag',
},
}"
>
#{{ tag.tag }}
</router-link>
<span class="tag-hot">({{ tag.quote_num }})</span>
<template #avatar>
<n-avatar :src="tag.user.avatar" />
</template>
</n-tag>
</n-space> </n-space>
</n-spin> </n-spin>
</n-list> </n-list>
@ -40,11 +24,13 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onMounted } from 'vue'; import { ref, onMounted} from 'vue';
import { getTags } from '@/api/post'; import { getTags } from '@/api/post';
import { useStore } from 'vuex';
const store = useStore();
const tags = ref<Item.TagProps[]>([]); const tags = ref<Item.TagProps[]>([]);
const tagType = ref<"hot" | "new">('hot'); const tagType = ref<"hot" | "new" | "follow">('hot');
const loading = ref(false); const loading = ref(false);
const loadTags = () => { const loadTags = () => {
@ -61,8 +47,11 @@ const loadTags = () => {
loading.value = false; loading.value = false;
}); });
}; };
const changeTab = (tab: "hot" | "new") => { const changeTab = (tab: "hot" | "new" | "follow") => {
tagType.value = tab; tagType.value = tab;
if (tab == "follow") {
tagType.value = "hot"
}
loadTags(); loadTags();
}; };
onMounted(() => { onMounted(() => {
@ -73,13 +62,6 @@ onMounted(() => {
<style lang="less" scoped> <style lang="less" scoped>
.tags-wrap { .tags-wrap {
padding: 20px; padding: 20px;
.tag-item {
.tag-hot {
margin-left: 12px;
font-size: 12px;
opacity: 0.75;
}
}
} }
.dark { .dark {
.tags-wrap { .tags-wrap {

Loading…
Cancel
Save