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() 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)

@ -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)

@ -18,5 +18,8 @@ type TopicService interface {
CreateTag(tag *Tag) (*Tag, error)
DeleteTag(tag *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)
}

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

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

@ -18,11 +18,13 @@ var (
type topicServant struct {
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{
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)
}
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) {
tag := &dbr.Tag{}

@ -9,6 +9,15 @@ import (
"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 {
BaseInfo `form:"-" binding:"-"`
Query string `form:"query"`
@ -44,6 +53,20 @@ type GetUserProfileResp struct {
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) {
r.Page, r.PageSize = page, pageSize
}

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

@ -8,6 +8,7 @@ import (
"github.com/alimy/mir/v3"
"github.com/gin-gonic/gin"
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"
@ -149,6 +150,40 @@ func (s *looseSrv) GetUserProfile(req *web.GetUserProfileReq) (*web.GetUserProfi
}, 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 {
return &looseSrv{
DaoServant: s,

@ -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,50 +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
}
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) {
sort := "id ASC"
if req.SortStrategy == "newest" {

@ -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"`
}

@ -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"`
}

@ -13,9 +13,31 @@
</template>
</n-input>
</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-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="{
@ -66,7 +88,8 @@ 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,15 @@ 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,
})
.then((res) => {
tags.value = res.topics;
hotTags.value = res.topics;
followTags.value = res.extral_topics;
loading.value = false;
})
.catch((err) => {
@ -107,7 +131,7 @@ const handleSearch = () => {
});
};
onMounted(() => {
loadTags();
loadHotTags();
});
</script>
@ -143,9 +167,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,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,
authModalShow: false,
authModelTab: "signin",
userLogined: false,
userInfo: {
id: 0,
username: "",
@ -40,10 +41,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,8 @@ declare module Item {
modified_on?: number;
/** 删除时间 */
deleted_on?: number;
/** 是否关注0为未关注1为已关注 */
is_following?: 0 | 1;
/** 是否删除0为未删除1为已删除 */
is_del?: 0 | 1;
}

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

@ -1,197 +1,172 @@
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 UserChangePassword {
}
interface UserChangeStatus {
}
interface UserChangeNickname {}
interface AddFriend {
interface UserChangePassword {}
}
interface UserChangeStatus {}
interface DeleteFriend {
interface AddFriend {}
}
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
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;
}
type PostCreateCommentReply = Item.ReplyProps
interface PostDeleteCommentReply {
}
interface PostDeleteCommentReply {}
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-tab-pane name="hot" tab="热门" />
<n-tab-pane name="new" tab="最新" />
<n-tab-pane v-if="store.state.userLogined"
name="follow" tab="关注" />
</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"
:tag="tag"
:showAction="store.state.userLogined"
>
<router-link
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>
</tag-item>
</n-space>
</n-spin>
</n-list>
@ -40,11 +24,13 @@
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { ref, onMounted} 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 loadTags = () => {
@ -61,8 +47,11 @@ const loadTags = () => {
loading.value = false;
});
};
const changeTab = (tab: "hot" | "new") => {
const changeTab = (tab: "hot" | "new" | "follow") => {
tagType.value = tab;
if (tab == "follow") {
tagType.value = "hot"
}
loadTags();
};
onMounted(() => {
@ -73,13 +62,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