获取站点信息接口 添加 历史最高在线人数/站点上线时间 信息

pull/406/head
HXY 2 years ago
commit d75b6d3471

@ -31,6 +31,7 @@ type Admin interface {
// Chain provide handlers chain for gin // Chain provide handlers chain for gin
Chain() gin.HandlersChain Chain() gin.HandlersChain
SiteInfo(*web.SiteInfoReq) (*web.SiteInfoResp, mir.Error)
ChangeUserStatus(*web.ChangeUserStatusReq) mir.Error ChangeUserStatus(*web.ChangeUserStatusReq) mir.Error
mustEmbedUnimplementedAdminServant() mustEmbedUnimplementedAdminServant()
@ -44,6 +45,20 @@ func RegisterAdminServant(e *gin.Engine, s Admin) {
router.Use(middlewares...) router.Use(middlewares...)
// register routes info to router // register routes info to router
router.Handle("GET", "/admin/site/status", func(c *gin.Context) {
select {
case <-c.Request.Context().Done():
return
default:
}
req := new(web.SiteInfoReq)
if err := s.Bind(c, req); err != nil {
s.Render(c, nil, err)
return
}
resp, err := s.SiteInfo(req)
s.Render(c, resp, err)
})
router.Handle("POST", "/admin/user/status", func(c *gin.Context) { router.Handle("POST", "/admin/user/status", func(c *gin.Context) {
select { select {
case <-c.Request.Context().Done(): case <-c.Request.Context().Done():
@ -66,6 +81,10 @@ func (UnimplementedAdminServant) Chain() gin.HandlersChain {
return nil return nil
} }
func (UnimplementedAdminServant) SiteInfo(req *web.SiteInfoReq) (*web.SiteInfoResp, mir.Error) {
return nil, mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
}
func (UnimplementedAdminServant) ChangeUserStatus(req *web.ChangeUserStatusReq) mir.Error { func (UnimplementedAdminServant) ChangeUserStatus(req *web.ChangeUserStatusReq) mir.Error {
return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented)) return mir.Errorln(http.StatusNotImplemented, http.StatusText(http.StatusNotImplemented))
} }

@ -23,15 +23,27 @@ type Relax interface {
mustEmbedUnimplementedRelaxServant() mustEmbedUnimplementedRelaxServant()
} }
type RelaxChain interface {
ChainGetUnreadMsgCount() gin.HandlersChain
mustEmbedUnimplementedRelaxChain()
}
// RegisterRelaxServant register Relax servant to gin // RegisterRelaxServant register Relax servant to gin
func RegisterRelaxServant(e *gin.Engine, s Relax) { func RegisterRelaxServant(e *gin.Engine, s Relax, m ...RelaxChain) {
var cc RelaxChain
if len(m) > 0 {
cc = m[0]
} else {
cc = &UnimplementedRelaxChain{}
}
router := e.Group("v1") router := e.Group("v1")
// use chain for router // use chain for router
middlewares := s.Chain() middlewares := s.Chain()
router.Use(middlewares...) router.Use(middlewares...)
// register routes info to router // register routes info to router
router.Handle("GET", "/user/msgcount/unread", func(c *gin.Context) { router.Handle("GET", "/user/msgcount/unread", append(cc.ChainGetUnreadMsgCount(), func(c *gin.Context) {
select { select {
case <-c.Request.Context().Done(): case <-c.Request.Context().Done():
return return
@ -49,7 +61,7 @@ func RegisterRelaxServant(e *gin.Engine, s Relax) {
} }
var rv _render_ = resp var rv _render_ = resp
rv.Render(c) rv.Render(c)
}) })...)
} }
// UnimplementedRelaxServant can be embedded to have forward compatible implementations. // UnimplementedRelaxServant can be embedded to have forward compatible implementations.
@ -64,3 +76,12 @@ func (UnimplementedRelaxServant) GetUnreadMsgCount(req *web.GetUnreadMsgCountReq
} }
func (UnimplementedRelaxServant) mustEmbedUnimplementedRelaxServant() {} func (UnimplementedRelaxServant) mustEmbedUnimplementedRelaxServant() {}
// UnimplementedRelaxChain can be embedded to have forward compatible implementations.
type UnimplementedRelaxChain struct{}
func (b *UnimplementedRelaxChain) ChainGetUnreadMsgCount() gin.HandlersChain {
return nil
}
func (b *UnimplementedRelaxChain) mustEmbedUnimplementedRelaxChain() {}

@ -6,7 +6,7 @@ require (
github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/semver/v3 v3.2.1
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868 github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868
github.com/alimy/mir/v4 v4.0.0 github.com/alimy/mir/v4 v4.0.0
github.com/alimy/tryst v0.8.2 github.com/alimy/tryst v0.8.3
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible
github.com/allegro/bigcache/v3 v3.1.0 github.com/allegro/bigcache/v3 v3.1.0
github.com/bufbuild/connect-go v1.10.0 github.com/bufbuild/connect-go v1.10.0

@ -125,8 +125,8 @@ github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:C
github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk= github.com/alexflint/go-filemutex v1.1.0/go.mod h1:7P4iRhttt/nUvUOrYIhcpMzv2G6CY9UnI16Z+UJqRyk=
github.com/alimy/mir/v4 v4.0.0 h1:MzGfmoLjjvR69jbZEmpKJO3tUuqB0RGRv1UWPbtukBg= github.com/alimy/mir/v4 v4.0.0 h1:MzGfmoLjjvR69jbZEmpKJO3tUuqB0RGRv1UWPbtukBg=
github.com/alimy/mir/v4 v4.0.0/go.mod h1:d58dBvw2KImcVbAUANrciEV/of0arMNsI9c/5UNCMMc= github.com/alimy/mir/v4 v4.0.0/go.mod h1:d58dBvw2KImcVbAUANrciEV/of0arMNsI9c/5UNCMMc=
github.com/alimy/tryst v0.8.2 h1:azu5B58vS6m/ZeHovYGWjVvEOJN2llDIBLxuN3qtMtk= github.com/alimy/tryst v0.8.3 h1:k54a9YesCGUTqfyDp9NL55TI8CxIj8HNJZyzbIoNab8=
github.com/alimy/tryst v0.8.2/go.mod h1:ua2eJbFrisHPh7z93Bgc0jNBE8Khu1SCx2p/6t3OzZI= github.com/alimy/tryst v0.8.3/go.mod h1:ua2eJbFrisHPh7z93Bgc0jNBE8Khu1SCx2p/6t3OzZI=
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiwbXTpUEinBpHsN7mG21Rc2k= github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible h1:Sg/2xHwDrioHpxTN6WMiwbXTpUEinBpHsN7mG21Rc2k=
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk= github.com/allegro/bigcache/v3 v3.1.0 h1:H2Vp8VOvxcrB91o86fUSVJFqeuz8kpyyB02eH3bSzwk=

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
"github.com/alimy/tryst/cache" "github.com/alimy/tryst/cache"
"github.com/rocboss/paopao-ce/pkg/types"
) )
const ( const (
@ -16,12 +17,23 @@ const (
// 以下包含一些在cache中会用到的key的前缀 // 以下包含一些在cache中会用到的key的前缀
const ( const (
PrefixUserTweets = "paopao:usertweets:" PrefixNewestTweets = "paopao:newesttweets:"
PrefixHotsTweets = "paopao:hotstweets:"
PrefixFollowingTweets = "paopao:followingtweets:"
PrefixUserTweets = "paopao:usertweets:"
PrefixUnreadmsg = "paopao:unreadmsg:"
PrefixOnlineUser = "paopao:onlineuser:"
KeySiteStatus = "paopao:sitestatus"
KeyHistoryMaxOnline = "history.max.online"
) )
// 以下包含一些在cache中会用到的池化后的key // 以下包含一些在cache中会用到的池化后的key
var ( var (
KeyUnreadMsg cache.KeyPool[int64] KeyNewestTweets cache.KeyPool[int]
KeyHotsTweets cache.KeyPool[int]
KeyFollowingTweets cache.KeyPool[string]
KeyUnreadMsg cache.KeyPool[int64]
KeyOnlineUser cache.KeyPool[int64]
) )
func initCacheKeyPool() { func initCacheKeyPool() {
@ -29,7 +41,25 @@ func initCacheKeyPool() {
if poolSize < CacheSetting.KeyPoolSize { if poolSize < CacheSetting.KeyPoolSize {
poolSize = CacheSetting.KeyPoolSize poolSize = CacheSetting.KeyPoolSize
} }
KeyUnreadMsg = cache.MustKeyPool[int64](poolSize, func(key int64) string { KeyNewestTweets = intKeyPool[int](poolSize, PrefixNewestTweets)
return fmt.Sprintf("paopao:unreadmsg:%d", key) KeyHotsTweets = intKeyPool[int](poolSize, PrefixHotsTweets)
KeyFollowingTweets = strKeyPool(poolSize, PrefixFollowingTweets)
KeyUnreadMsg = intKeyPool[int64](poolSize, PrefixUnreadmsg)
KeyOnlineUser = intKeyPool[int64](poolSize, PrefixOnlineUser)
}
func strKeyPool(size int, prefix string) cache.KeyPool[string] {
return cache.MustKeyPool(size, func(key string) string {
return fmt.Sprintf("%s%s", prefix, key)
}) })
} }
func intKeyPool[T types.Integer](size int, prefix string) cache.KeyPool[T] {
return cache.MustKeyPool[T](size, intKey[T](prefix))
}
func intKey[T types.Integer](prefix string) func(T) string {
return func(key T) string {
return fmt.Sprintf("%s%d", prefix, key)
}
}

@ -37,6 +37,7 @@ var (
AppSetting *appConf AppSetting *appConf
CacheSetting *cacheConf CacheSetting *cacheConf
EventManagerSetting *eventManagerConf EventManagerSetting *eventManagerConf
MetricManagerSetting *metricManagerConf
CacheIndexSetting *cacheIndexConf CacheIndexSetting *cacheIndexConf
SimpleCacheIndexSetting *simpleCacheIndexConf SimpleCacheIndexSetting *simpleCacheIndexConf
BigCacheIndexSetting *bigCacheIndexConf BigCacheIndexSetting *bigCacheIndexConf
@ -73,6 +74,7 @@ func setupSetting(suite []string, noDefault bool) error {
"App": &AppSetting, "App": &AppSetting,
"Cache": &CacheSetting, "Cache": &CacheSetting,
"EventManager": &EventManagerSetting, "EventManager": &EventManagerSetting,
"MetricManager": &MetricManagerSetting,
"PprofServer": &PprofServerSetting, "PprofServer": &PprofServerSetting,
"WebServer": &WebServerSetting, "WebServer": &WebServerSetting,
"AdminServer": &AdminServerSetting, "AdminServer": &AdminServerSetting,
@ -121,6 +123,7 @@ func setupSetting(suite []string, noDefault bool) error {
CacheSetting.CientSideCacheExpire *= time.Second CacheSetting.CientSideCacheExpire *= time.Second
EventManagerSetting.TickWaitTime *= time.Second EventManagerSetting.TickWaitTime *= time.Second
MetricManagerSetting.TickWaitTime *= time.Second
JWTSetting.Expire *= time.Second JWTSetting.Expire *= time.Second
SimpleCacheIndexSetting.CheckTickDuration *= time.Second SimpleCacheIndexSetting.CheckTickDuration *= time.Second
SimpleCacheIndexSetting.ExpireTickDuration *= time.Second SimpleCacheIndexSetting.ExpireTickDuration *= time.Second

@ -10,12 +10,19 @@ Cache:
CientSideCacheExpire: 60 # 客户端缓存过期时间 默认60s CientSideCacheExpire: 60 # 客户端缓存过期时间 默认60s
UnreadMsgExpire: 60 # 未读消息过期时间,单位秒, 默认60s UnreadMsgExpire: 60 # 未读消息过期时间,单位秒, 默认60s
UserTweetsExpire: 60 # 获取用户推文列表过期时间,单位秒, 默认60s UserTweetsExpire: 60 # 获取用户推文列表过期时间,单位秒, 默认60s
OnlineUserExpire: 300 # 标记在线用户 过期时间,单位秒, 默认300s
EventManager: # 事件管理器的配置参数 EventManager: # 事件管理器的配置参数
MinWorker: 64 # 最小后台工作者, 设置范围[5, ++], 默认64 MinWorker: 64 # 最小后台工作者, 设置范围[5, ++], 默认64
MaxEventBuf: 128 # 最大log缓存条数, 设置范围[10, ++], 默认128 MaxEventBuf: 128 # 最大log缓存条数, 设置范围[10, ++], 默认128
MaxTempEventBuf: 256 # 最大log缓存条数, 设置范围[10, ++], 默认256 MaxTempEventBuf: 256 # 最大log缓存条数, 设置范围[10, ++], 默认256
MaxTickCount: 60 # 最大的循环周期, 设置范围[60, ++], 默认60 MaxTickCount: 60 # 最大的循环周期, 设置范围[60, ++], 默认60
TickWaitTime: 1 # 一个周期的等待时间,单位:秒 默认1s TickWaitTime: 1 # 一个周期的等待时间,单位:秒 默认1s
MetricManager: # 指标监控管理器的配置参数
MinWorker: 32 # 最小后台工作者, 设置范围[5, ++], 默认32
MaxEventBuf: 128 # 最大log缓存条数, 设置范围[10, ++], 默认128
MaxTempEventBuf: 256 # 最大log缓存条数, 设置范围[10, ++], 默认256
MaxTickCount: 60 # 最大的循环周期, 设置范围[60, ++], 默认60
TickWaitTime: 1 # 一个周期的等待时间,单位:秒 默认1s
Features: Features:
Default: [] Default: []
WebServer: # Web服务 WebServer: # Web服务

@ -101,6 +101,7 @@ type cacheConf struct {
CientSideCacheExpire time.Duration CientSideCacheExpire time.Duration
UnreadMsgExpire int64 UnreadMsgExpire int64
UserTweetsExpire int64 UserTweetsExpire int64
OnlineUserExpire int64
} }
type eventManagerConf struct { type eventManagerConf struct {
@ -111,6 +112,14 @@ type eventManagerConf struct {
TickWaitTime time.Duration TickWaitTime time.Duration
} }
type metricManagerConf struct {
MinWorker int
MaxEventBuf int
MaxTempEventBuf int
MaxTickCount int
TickWaitTime time.Duration
}
type cacheIndexConf struct { type cacheIndexConf struct {
MaxUpdateQPS int MaxUpdateQPS int
MinWorker int MinWorker int

@ -101,9 +101,11 @@ type RedisCache interface {
type AppCache interface { type AppCache interface {
Get(key string) ([]byte, error) Get(key string) ([]byte, error)
Set(key string, data []byte, ex int64) error Set(key string, data []byte, ex int64) error
SetNx(key string, data []byte, ex int64) error
Delete(key ...string) error Delete(key ...string) error
DelAny(pattern string) error DelAny(pattern string) error
Exist(key string) bool Exist(key string) bool
Keys(pattern string) ([]string, error)
} }
type WebCache interface { type WebCache interface {
@ -112,4 +114,5 @@ type WebCache interface {
PutUnreadMsgCountResp(uid int64, data []byte) error PutUnreadMsgCountResp(uid int64, data []byte) error
DelUnreadMsgCountResp(uid int64) error DelUnreadMsgCountResp(uid int64) error
ExistUnreadMsgCountResp(uid int64) bool ExistUnreadMsgCountResp(uid int64) bool
PutHistoryMaxOnline(newScore int) (int, error)
} }

@ -15,6 +15,7 @@ type UserManageService interface {
GetUsersByKeyword(keyword string) ([]*ms.User, error) GetUsersByKeyword(keyword string) ([]*ms.User, error)
CreateUser(user *ms.User) (*ms.User, error) CreateUser(user *ms.User) (*ms.User, error)
UpdateUser(user *ms.User) error UpdateUser(user *ms.User) error
GetRegisterUserCount() (int64, error)
} }
// ContactManageService 联系人管理服务 // ContactManageService 联系人管理服务

@ -60,10 +60,10 @@ func (s *redisCacheTweetsCache) delTweets(keys []string) error {
} }
func (s *redisCacheTweetsCache) allKeys() (res []string, err error) { func (s *redisCacheTweetsCache) allKeys() (res []string, err error) {
cursor := uint64(0) ctx, cursor := context.Background(), uint64(0)
for { for {
cmd := s.c.B().Scan().Cursor(cursor).Match(_cacheIndexKeyPattern).Count(50).Build() cmd := s.c.B().Scan().Cursor(cursor).Match(_cacheIndexKeyPattern).Count(50).Build()
entry, err := s.c.Do(context.Background(), cmd).AsScanEntry() entry, err := s.c.Do(ctx, cmd).AsScanEntry()
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -26,6 +26,7 @@ type appCache struct {
type webCache struct { type webCache struct {
core.AppCache core.AppCache
c rueidis.Client
unreadMsgExpire int64 unreadMsgExpire int64
} }
@ -39,12 +40,21 @@ func (s *appCache) Get(key string) ([]byte, error) {
} }
func (s *appCache) Set(key string, data []byte, ex int64) error { func (s *appCache) Set(key string, data []byte, ex int64) error {
return s.c.Do(context.Background(), s.c.B().Set(). ctx := context.Background()
Key(key). cmd := s.c.B().Set().Key(key).Value(utils.String(data))
Value(utils.String(data)). if ex > 0 {
ExSeconds(ex). return s.c.Do(ctx, cmd.ExSeconds(ex).Build()).Error()
Build()). }
Error() return s.c.Do(ctx, cmd.Build()).Error()
}
func (s *appCache) SetNx(key string, data []byte, ex int64) error {
ctx := context.Background()
cmd := s.c.B().Set().Key(key).Value(utils.String(data)).Nx()
if ex > 0 {
return s.c.Do(ctx, cmd.ExSeconds(ex).Build()).Error()
}
return s.c.Do(ctx, cmd.Build()).Error()
} }
func (s *appCache) Delete(keys ...string) (err error) { func (s *appCache) Delete(keys ...string) (err error) {
@ -85,6 +95,24 @@ func (s *appCache) Exist(key string) bool {
return count > 0 return count > 0
} }
func (s *appCache) Keys(pattern string) (res []string, err error) {
ctx, cursor := context.Background(), uint64(0)
for {
cmd := s.c.B().Scan().Cursor(cursor).Match(pattern).Count(50).Build()
entry, err := s.c.Do(ctx, cmd).AsScanEntry()
if err != nil {
return nil, err
}
res = append(res, entry.Elements...)
if entry.Cursor != 0 {
cursor = entry.Cursor
continue
}
break
}
return
}
func (s *webCache) GetUnreadMsgCountResp(uid int64) ([]byte, error) { func (s *webCache) GetUnreadMsgCountResp(uid int64) ([]byte, error) {
key := conf.KeyUnreadMsg.Get(uid) key := conf.KeyUnreadMsg.Get(uid)
return s.Get(key) return s.Get(key)
@ -102,6 +130,23 @@ func (s *webCache) ExistUnreadMsgCountResp(uid int64) bool {
return s.Exist(conf.KeyUnreadMsg.Get(uid)) return s.Exist(conf.KeyUnreadMsg.Get(uid))
} }
func (s *webCache) PutHistoryMaxOnline(newScore int) (int, error) {
ctx := context.Background()
cmd := s.c.B().Zadd().
Key(conf.KeySiteStatus).
Gt().ScoreMember().
ScoreMember(float64(newScore), conf.KeyHistoryMaxOnline).Build()
if err := s.c.Do(ctx, cmd).Error(); err != nil {
return 0, err
}
cmd = s.c.B().Zscore().Key(conf.KeySiteStatus).Member(conf.KeyHistoryMaxOnline).Build()
if score, err := s.c.Do(ctx, cmd).ToFloat64(); err == nil {
return int(score), nil
} else {
return 0, err
}
}
func newAppCache() *appCache { func newAppCache() *appCache {
return &appCache{ return &appCache{
cscExpire: conf.CacheSetting.CientSideCacheExpire, cscExpire: conf.CacheSetting.CientSideCacheExpire,
@ -112,6 +157,7 @@ func newAppCache() *appCache {
func newWebCache(ac core.AppCache) *webCache { func newWebCache(ac core.AppCache) *webCache {
return &webCache{ return &webCache{
AppCache: ac, AppCache: ac,
c: conf.MustRedisClient(),
unreadMsgExpire: conf.CacheSetting.UnreadMsgExpire, unreadMsgExpire: conf.CacheSetting.UnreadMsgExpire,
} }
} }

@ -97,7 +97,6 @@ func (u *User) ListUserInfoById(db *gorm.DB, ids []int64) (res cs.UserInfoList,
func (u *User) Create(db *gorm.DB) (*User, error) { func (u *User) Create(db *gorm.DB) (*User, error) {
err := db.Create(&u).Error err := db.Create(&u).Error
return u, err return u, err
} }

@ -126,3 +126,8 @@ func (s *userManageSrv) UpdateUser(user *ms.User) error {
return nil return nil
}) })
} }
func (s *userManageSrv) GetRegisterUserCount() (res int64, err error) {
err = s.db.Model(&dbr.User{}).Count(&res).Error
return
}

@ -8,13 +8,68 @@ import (
"sync" "sync"
"github.com/alimy/tryst/cfg" "github.com/alimy/tryst/cfg"
"github.com/alimy/tryst/pool"
"github.com/robfig/cron/v3"
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
var ( var (
_onceInitial sync.Once _defaultEventManager EventManager
_defaultJobManager JobManager
_onceInitial sync.Once
) )
func StartEventManager() {
_defaultEventManager.Start()
}
func StopEventManager() {
_defaultEventManager.Stop()
}
// OnEvent push event to gorotine pool then handled automatic.
func OnEvent(event Event) {
_defaultEventManager.OnEvent(event)
}
func StartJobManager() {
_defaultJobManager.Start()
}
func StopJobManager() {
_defaultJobManager.Stop()
}
// NewJob create new Job instance
func NewJob(s cron.Schedule, fn JobFn) Job {
return &simpleJob{
Schedule: s,
Job: fn,
}
}
// RemoveJob an entry from being run in the future.
func RemoveJob(id EntryID) {
_defaultJobManager.Remove(id)
}
// ScheduleJob adds a Job to the Cron to be run on the given schedule.
// The job is wrapped with the configured Chain.
func ScheduleJob(job Job) EntryID {
return _defaultJobManager.Schedule(job)
}
// Schedule adds a Job to the Cron to be run on the given schedule.
// The job is wrapped with the configured Chain.
func Schedule(s cron.Schedule, fn JobFn) EntryID {
job := &simpleJob{
Schedule: s,
Job: fn,
}
return _defaultJobManager.Schedule(job)
}
func Initial() { func Initial() {
_onceInitial.Do(func() { _onceInitial.Do(func() {
initEventManager() initEventManager()
@ -24,3 +79,34 @@ func Initial() {
} }
}) })
} }
func initJobManager() {
_defaultJobManager = NewJobManager()
StartJobManager()
}
func initEventManager() {
var opts []pool.Option
s := conf.EventManagerSetting
if s.MinWorker > 5 {
opts = append(opts, pool.MinWorkerOpt(s.MinWorker))
} else {
opts = append(opts, pool.MinWorkerOpt(5))
}
if s.MaxEventBuf > 10 {
opts = append(opts, pool.MaxRequestBufOpt(s.MaxEventBuf))
} else {
opts = append(opts, pool.MaxRequestBufOpt(10))
}
if s.MaxTempEventBuf > 10 {
opts = append(opts, pool.MaxRequestTempBufOpt(s.MaxTempEventBuf))
} else {
opts = append(opts, pool.MaxRequestTempBufOpt(10))
}
opts = append(opts, pool.MaxTickCountOpt(s.MaxTickCount), pool.TickWaitTimeOpt(s.TickWaitTime))
_defaultEventManager = NewEventManager(func(req Event, err error) {
if err != nil {
logrus.Errorf("handle event[%s] occurs error: %s", req.Name(), err)
}
}, opts...)
}

@ -0,0 +1,40 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package events
import (
"github.com/alimy/tryst/event"
"github.com/alimy/tryst/pool"
)
type Event = event.Event
type EventManager interface {
Start()
Stop()
OnEvent(event Event)
}
type simpleEventManager struct {
em event.EventManager
}
func (s *simpleEventManager) Start() {
s.em.Start()
}
func (s *simpleEventManager) Stop() {
s.em.Stop()
}
func (s *simpleEventManager) OnEvent(event Event) {
s.em.OnEvent(event)
}
func NewEventManager(fn pool.RespFn[Event], opts ...pool.Option) EventManager {
return &simpleEventManager{
em: event.NewEventManager(fn, opts...),
}
}

@ -8,10 +8,6 @@ import (
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
) )
var (
_defaultJobManager JobManager = (*jobManager)(nil)
)
type ( type (
EntryID = cron.EntryID EntryID = cron.EntryID
) )
@ -65,46 +61,8 @@ func (j *jobManager) Schedule(job Job) EntryID {
return j.m.Schedule(job, job) return j.m.Schedule(job, job)
} }
func initJobManager() { func NewJobManager() JobManager {
_defaultJobManager = &jobManager{ return &jobManager{
m: cron.New(), m: cron.New(),
} }
StartJobManager()
}
func StartJobManager() {
_defaultJobManager.Start()
}
func StopJobManager() {
_defaultJobManager.Stop()
}
// NewJob create new Job instance
func NewJob(s cron.Schedule, fn JobFn) Job {
return &simpleJob{
Schedule: s,
Job: fn,
}
}
// RemoveJob an entry from being run in the future.
func RemoveJob(id EntryID) {
_defaultJobManager.Remove(id)
}
// ScheduleJob adds a Job to the Cron to be run on the given schedule.
// The job is wrapped with the configured Chain.
func ScheduleJob(job Job) EntryID {
return _defaultJobManager.Schedule(job)
}
// Schedule adds a Job to the Cron to be run on the given schedule.
// The job is wrapped with the configured Chain.
func Schedule(s cron.Schedule, fn JobFn) EntryID {
job := &simpleJob{
Schedule: s,
Job: fn,
}
return _defaultJobManager.Schedule(job)
} }

@ -6,6 +6,7 @@ package internal
import ( import (
"github.com/rocboss/paopao-ce/internal/events" "github.com/rocboss/paopao-ce/internal/events"
"github.com/rocboss/paopao-ce/internal/metrics"
"github.com/rocboss/paopao-ce/internal/migration" "github.com/rocboss/paopao-ce/internal/migration"
) )
@ -14,4 +15,6 @@ func Initial() {
migration.Run() migration.Run()
// event manager system initialize // event manager system initialize
events.Initial() events.Initial()
// metric manager system initialize
metrics.Initial()
} }

@ -2,9 +2,11 @@
// Use of this source code is governed by a MIT style // Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package events package metrics
import ( import (
"sync"
"github.com/alimy/tryst/event" "github.com/alimy/tryst/event"
"github.com/alimy/tryst/pool" "github.com/alimy/tryst/pool"
"github.com/rocboss/paopao-ce/internal/conf" "github.com/rocboss/paopao-ce/internal/conf"
@ -12,10 +14,40 @@ import (
) )
var ( var (
_defaultEventManager event.EventManager _defaultMetricManager event.EventManager
_onceInitial sync.Once
) )
func initEventManager() { type Metric = event.Event
type BaseMetric = event.UnimplementedEvent
type MetricManager interface {
Start()
Stop()
OnMeasure(metric Metric)
}
func StartMetricManager() {
_defaultMetricManager.Start()
}
func StopMetricManager() {
_defaultMetricManager.Stop()
}
// OnMeasure push Metric to gorotine pool then handled automatic.
func OnMeasure(metric Metric) {
_defaultMetricManager.OnEvent(metric)
}
func Initial() {
_onceInitial.Do(func() {
initMetricManager()
})
}
func initMetricManager() {
var opts []pool.Option var opts []pool.Option
s := conf.EventManagerSetting s := conf.EventManagerSetting
if s.MinWorker > 5 { if s.MinWorker > 5 {
@ -34,22 +66,9 @@ func initEventManager() {
opts = append(opts, pool.MaxRequestTempBufOpt(10)) opts = append(opts, pool.MaxRequestTempBufOpt(10))
} }
opts = append(opts, pool.MaxTickCountOpt(s.MaxTickCount), pool.TickWaitTimeOpt(s.TickWaitTime)) opts = append(opts, pool.MaxTickCountOpt(s.MaxTickCount), pool.TickWaitTimeOpt(s.TickWaitTime))
_defaultEventManager = event.NewEventManager(func(req event.Event, err error) { _defaultMetricManager = event.NewEventManager(func(req Metric, err error) {
if err != nil { if err != nil {
logrus.Errorf("handle event[%s] occurs error: %s", req.Name(), err) logrus.Errorf("handle event[%s] occurs error: %s", req.Name(), err)
} }
}, opts...) }, opts...)
} }
func StartEventManager() {
_defaultEventManager.Start()
}
func StopEventManager() {
_defaultEventManager.Stop()
}
// OnEvent push event to gorotine pool then handled automatic.
func OnEvent(event event.Event) {
_defaultEventManager.OnEvent(event)
}

@ -0,0 +1,32 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package metrics
import (
"github.com/alimy/tryst/event"
"github.com/alimy/tryst/pool"
)
type simpleMetricManager struct {
mm event.EventManager
}
func (s *simpleMetricManager) Start() {
s.mm.Start()
}
func (s *simpleMetricManager) Stop() {
s.mm.Stop()
}
func (s *simpleMetricManager) OnMeasure(metric Metric) {
s.mm.OnEvent(metric)
}
func NewMetricManager(fn pool.RespFn[Metric], opts ...pool.Option) MetricManager {
return &simpleMetricManager{
mm: event.NewEventManager(fn, opts...),
}
}

@ -9,3 +9,14 @@ type ChangeUserStatusReq struct {
ID int64 `json:"id" form:"id" binding:"required"` ID int64 `json:"id" form:"id" binding:"required"`
Status int `json:"status" form:"status" binding:"required,oneof=1 2"` Status int `json:"status" form:"status" binding:"required,oneof=1 2"`
} }
type SiteInfoReq struct {
SimpleInfo `json:"-" binding:"-"`
}
type SiteInfoResp struct {
RegisterUserCount int64 `json:"register_user_count"`
OnlineUserCount int `json:"online_user_count"`
HistoryMaxOnline int `json:"history_max_online"`
ServerUpTime int64 `json:"server_up_time"`
}

@ -12,7 +12,8 @@ const (
) )
const ( const (
AuditHookCtxKey = "audit_ctx_key" AuditHookCtxKey = "audit_ctx_key"
OnlineUserCtxKey = "online_user_ctx_key"
) )
type AuditStyle uint8 type AuditStyle uint8

@ -99,7 +99,7 @@ func (p *ExpireRespEvent) Name() string {
return "servants.base.ExpireRespEvent" return "servants.base.ExpireRespEvent"
} }
func (p *ExpireRespEvent) Action() (err error) { func (p *ExpireRespEvent) Action() error {
return p.ac.Delete(p.keys...) return p.ac.Delete(p.keys...)
} }
@ -107,7 +107,7 @@ func (p *ExpireAnyRespEvent) Name() string {
return "servants.base.ExpireAnyRespEvent" return "servants.base.ExpireAnyRespEvent"
} }
func (p *ExpireAnyRespEvent) Action() (err error) { func (p *ExpireAnyRespEvent) Action() error {
return p.ac.DelAny(p.pattern) return p.ac.DelAny(p.pattern)
} }

@ -9,16 +9,19 @@ import (
"github.com/rocboss/paopao-ce/internal/core" "github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/dao" "github.com/rocboss/paopao-ce/internal/dao"
"github.com/rocboss/paopao-ce/internal/dao/cache"
) )
var ( var (
_ums core.UserManageService _ums core.UserManageService
_ac core.AppCache
_onceUms sync.Once _onceUms sync.Once
) )
func userManageService() core.UserManageService { func userManageService() core.UserManageService {
_onceUms.Do(func() { _onceUms.Do(func() {
_ums = dao.DataService() _ums = dao.DataService()
_ac = cache.NewAppCache()
}) })
return _ums return _ums
} }

@ -0,0 +1,21 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package chain
import (
"github.com/gin-gonic/gin"
"github.com/rocboss/paopao-ce/internal/servants/base"
)
func OnlineUserMeasure() gin.HandlerFunc {
return func(c *gin.Context) {
// 此midleware后面是真正的http handlder让handler先执行
c.Next()
// 更新用户在线状态
if uid, ok := base.UserIdFrom(c); ok {
OnUserOnlineMetric(_ac, uid)
}
}
}

@ -0,0 +1,36 @@
// Copyright 2023 ROC. All rights reserved.
// Use of this source code is governed by a MIT style
// license that can be found in the LICENSE file.
package chain
import (
"github.com/rocboss/paopao-ce/internal/conf"
"github.com/rocboss/paopao-ce/internal/core"
"github.com/rocboss/paopao-ce/internal/metrics"
)
type OnlineUserMetric struct {
metrics.BaseMetric
ac core.AppCache
uid int64
expire int64
}
func OnUserOnlineMetric(ac core.AppCache, uid int64) {
metrics.OnMeasure(&OnlineUserMetric{
ac: ac,
uid: uid,
expire: conf.CacheSetting.OnlineUserExpire,
})
}
func (m *OnlineUserMetric) Name() string {
return "OnlineUserMetric"
}
func (m *OnlineUserMetric) Action() (err error) {
// 暂时仅做标记,不存储其他相关信息
m.ac.Set(conf.KeyOnlineUser.Get(m.uid), []byte{}, m.expire)
return
}

@ -5,13 +5,18 @@
package web package web
import ( import (
"time"
"github.com/alimy/mir/v4" "github.com/alimy/mir/v4"
"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/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"
"github.com/rocboss/paopao-ce/internal/servants/chain" "github.com/rocboss/paopao-ce/internal/servants/chain"
"github.com/rocboss/paopao-ce/pkg/xerror" "github.com/rocboss/paopao-ce/pkg/xerror"
"github.com/sirupsen/logrus"
) )
var ( var (
@ -21,6 +26,8 @@ var (
type adminSrv struct { type adminSrv struct {
api.UnimplementedAdminServant api.UnimplementedAdminServant
*base.DaoServant *base.DaoServant
wc core.WebCache
serverUpTime int64
} }
func (s *adminSrv) Chain() gin.HandlersChain { func (s *adminSrv) Chain() gin.HandlersChain {
@ -40,8 +47,29 @@ func (s *adminSrv) ChangeUserStatus(req *web.ChangeUserStatusReq) mir.Error {
return nil return nil
} }
func newAdminSrv(s *base.DaoServant) api.Admin { func (s *adminSrv) SiteInfo(req *web.SiteInfoReq) (*web.SiteInfoResp, mir.Error) {
res, err := &web.SiteInfoResp{ServerUpTime: s.serverUpTime}, error(nil)
res.RegisterUserCount, err = s.Ds.GetRegisterUserCount()
if err != nil {
logrus.Errorf("get SiteInfo[1] occurs error: %s", err)
}
onlineUserKeys, xerr := s.wc.Keys(conf.PrefixOnlineUser + "*")
if xerr == nil {
res.OnlineUserCount = len(onlineUserKeys)
if res.HistoryMaxOnline, err = s.wc.PutHistoryMaxOnline(res.OnlineUserCount); err != nil {
logrus.Errorf("get Siteinfo[3] occurs error: %s", err)
}
} else {
logrus.Errorf("get Siteinfo[2] occurs error: %s", err)
}
// 错误进行宽松赦免处理
return res, nil
}
func newAdminSrv(s *base.DaoServant, wc core.WebCache) api.Admin {
return &adminSrv{ return &adminSrv{
DaoServant: s, DaoServant: s,
wc: wc,
serverUpTime: time.Now().Unix(),
} }
} }

@ -26,6 +26,14 @@ type relaxSrv struct {
wc core.WebCache wc core.WebCache
} }
type relaxChain struct {
api.UnimplementedRelaxChain
}
func (s *relaxChain) ChainGetUnreadMsgCount() gin.HandlersChain {
return gin.HandlersChain{chain.OnlineUserMeasure()}
}
func (s *relaxSrv) Chain() gin.HandlersChain { func (s *relaxSrv) Chain() gin.HandlersChain {
return gin.HandlersChain{chain.JwtSurely()} return gin.HandlersChain{chain.JwtSurely()}
} }
@ -50,3 +58,7 @@ func newRelaxSrv(s *base.DaoServant, wc core.WebCache) api.Relax {
wc: wc, wc: wc,
} }
} }
func newRelaxChain() api.RelaxChain {
return &relaxChain{}
}

@ -32,9 +32,9 @@ func RouteWeb(e *gin.Engine) {
lazyInitial() lazyInitial()
ds := base.NewDaoServant() ds := base.NewDaoServant()
// aways register servants // aways register servants
api.RegisterAdminServant(e, newAdminSrv(ds)) api.RegisterAdminServant(e, newAdminSrv(ds, _wc))
api.RegisterCoreServant(e, newCoreSrv(ds, _oss, _wc)) api.RegisterCoreServant(e, newCoreSrv(ds, _oss, _wc))
api.RegisterRelaxServant(e, newRelaxSrv(ds, _wc)) api.RegisterRelaxServant(e, newRelaxSrv(ds, _wc), newRelaxChain())
api.RegisterLooseServant(e, newLooseSrv(ds, _ac)) api.RegisterLooseServant(e, newLooseSrv(ds, _ac))
api.RegisterPrivServant(e, newPrivSrv(ds, _oss), newPrivChain()) api.RegisterPrivServant(e, newPrivSrv(ds, _oss), newPrivChain())
api.RegisterPubServant(e, newPubSrv(ds, _oss)) api.RegisterPubServant(e, newPubSrv(ds, _oss))

@ -16,5 +16,6 @@ type Admin struct {
Group `mir:"v1"` Group `mir:"v1"`
// ChangeUserStatus 管理·禁言/解封用户 // ChangeUserStatus 管理·禁言/解封用户
ChangeUserStatus func(Post, web.ChangeUserStatusReq) `mir:"/admin/user/status"` ChangeUserStatus func(Post, web.ChangeUserStatusReq) `mir:"/admin/user/status"`
SiteInfo func(Get, web.SiteInfoReq) web.SiteInfoResp `mir:"/admin/site/status"`
} }

@ -16,5 +16,5 @@ type Relax struct {
Group `mir:"v1"` Group `mir:"v1"`
// GetUnreadMsgCount 获取当前用户未读消息数量 // GetUnreadMsgCount 获取当前用户未读消息数量
GetUnreadMsgCount func(Get, web.GetUnreadMsgCountReq) web.GetUnreadMsgCountResp `mir:"/user/msgcount/unread"` GetUnreadMsgCount func(Get, Chain, web.GetUnreadMsgCountReq) web.GetUnreadMsgCountResp `mir:"/user/msgcount/unread"`
} }

@ -23,3 +23,7 @@ type Boxes[T any] interface {
Box(t T) Box(t T)
Unbox() T Unbox() T
} }
type Integer interface {
~int8 | ~int16 | ~int32 | ~int64 | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~int | ~uint
}

@ -1 +0,0 @@
import{_ as s}from"./main-nav.vue_vue_type_style_index_0_lang-5497f713.js";import{u as a}from"./vue-router-e5a2430e.js";import{F as i,e as c,a2 as u}from"./naive-ui-d8de3dda.js";import{d as l,f as d,k as t,w as o,e as f,A as x}from"./@vue-a481fc63.js";import{_ as g}from"./index-d9671de1.js";import"./vuex-44de225f.js";import"./vooks-6d99783e.js";import"./evtd-b614532e.js";import"./@vicons-7a4ef312.js";import"./seemly-76b7b838.js";import"./vueuc-39372edb.js";import"./@css-render-7124a1a5.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-4a70c6fc.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 M=g(v,[["__scopeId","data-v-e62daa85"]]);export{M as default};

@ -0,0 +1 @@
import{_ as s}from"./main-nav.vue_vue_type_style_index_0_lang-f8e6a4c1.js";import{u as i}from"./vue-router-e5a2430e.js";import{F as a,e as c,a2 as u}from"./naive-ui-d8de3dda.js";import{d as l,f as d,k as t,w as o,e as f,A as x}from"./@vue-a481fc63.js";import{_ as g}from"./index-f37b0729.js";import"./vuex-44de225f.js";import"./vooks-6d99783e.js";import"./evtd-b614532e.js";import"./@vicons-7a4ef312.js";import"./seemly-76b7b838.js";import"./vueuc-39372edb.js";import"./@css-render-7124a1a5.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-4a70c6fc.js";import"./moment-2ab8298d.js";/* empty css */const v=l({__name:"404",setup(h){const e=i(),_=()=>{e.push({path:"/"})};return(k,w)=>{const n=s,p=c,r=u,m=a;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 O=g(v,[["__scopeId","data-v-e62daa85"]]);export{O as default};

@ -1 +0,0 @@
import{_ as F}from"./post-skeleton-73b6be04.js";import{_ as N}from"./main-nav.vue_vue_type_style_index_0_lang-5497f713.js";import{u as z}from"./vuex-44de225f.js";import{b as A}from"./vue-router-e5a2430e.js";import{a as R}from"./formatTime-4210fcd1.js";import{F as S,Q as V,I as q,G as I}from"./naive-ui-d8de3dda.js";import{d as P,H as n,b as j,f as o,k as a,w as p,e,bf as u,Y as l,F as D,u as E,q as G,j as s,x as _,l as H}from"./@vue-a481fc63.js";import{_ as L}from"./index-d9671de1.js";import"./vooks-6d99783e.js";import"./evtd-b614532e.js";import"./@vicons-7a4ef312.js";import"./moment-2ab8298d.js";import"./seemly-76b7b838.js";import"./vueuc-39372edb.js";import"./@css-render-7124a1a5.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-4a70c6fc.js";/* empty css */const M={key:0,class:"pagination-wrap"},O={key:0,class:"skeleton-wrap"},Q={key:1},T={key:0,class:"empty-wrap"},U={class:"bill-line"},Y=P({__name:"Anouncement",setup($){const d=z(),g=A(),v=n(!1),r=n([]),i=n(+g.query.p||1),f=n(20),m=n(0),h=c=>{i.value=c};return j(()=>{}),(c,J)=>{const k=N,y=V,x=F,w=q,B=I,C=S;return e(),o("div",null,[a(k,{title:"公告"}),a(C,{class:"main-content-wrap",bordered:""},{footer:p(()=>[m.value>1?(e(),o("div",M,[a(y,{page:i.value,"onUpdate:page":h,"page-slot":u(d).state.collapsedRight?5:8,"page-count":m.value},null,8,["page","page-slot","page-count"])])):l("",!0)]),default:p(()=>[v.value?(e(),o("div",O,[a(x,{num:f.value},null,8,["num"])])):(e(),o("div",Q,[r.value.length===0?(e(),o("div",T,[a(w,{size:"large",description:"暂无数据"})])):l("",!0),(e(!0),o(D,null,E(r.value,t=>(e(),G(B,{key:t.id},{default:p(()=>[s("div",U,[s("div",null,"NO."+_(t.id),1),s("div",null,_(t.reason),1),s("div",{class:H({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 yt=L(Y,[["__scopeId","data-v-d4d04859"]]);export{yt as default};

@ -0,0 +1 @@
import{_ as F}from"./post-skeleton-2ccfac69.js";import{_ as N}from"./main-nav.vue_vue_type_style_index_0_lang-f8e6a4c1.js";import{u as z}from"./vuex-44de225f.js";import{b as A}from"./vue-router-e5a2430e.js";import{E as R,_ as S}from"./index-f37b0729.js";import{F as V,Q as q,I as E,G as I}from"./naive-ui-d8de3dda.js";import{d as P,H as n,b as j,f as o,k as a,w as p,e as t,bf as u,Y as l,F as D,u as G,q as H,j as s,x as _,l as L}from"./@vue-a481fc63.js";import"./vooks-6d99783e.js";import"./evtd-b614532e.js";import"./@vicons-7a4ef312.js";import"./axios-4a70c6fc.js";import"./moment-2ab8298d.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-39372edb.js";import"./@css-render-7124a1a5.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 M={key:0,class:"pagination-wrap"},O={key:0,class:"skeleton-wrap"},Q={key:1},T={key:0,class:"empty-wrap"},U={class:"bill-line"},Y=P({__name:"Anouncement",setup($){const d=z(),g=A(),v=n(!1),i=n([]),r=n(+g.query.p||1),f=n(20),c=n(0),h=m=>{r.value=m};return j(()=>{}),(m,J)=>{const k=N,y=q,x=F,w=E,B=I,C=V;return t(),o("div",null,[a(k,{title:"公告"}),a(C,{class:"main-content-wrap",bordered:""},{footer:p(()=>[c.value>1?(t(),o("div",M,[a(y,{page:r.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?(t(),o("div",O,[a(x,{num:f.value},null,8,["num"])])):(t(),o("div",Q,[i.value.length===0?(t(),o("div",T,[a(w,{size:"large",description:"暂无数据"})])):l("",!0),(t(!0),o(D,null,G(i.value,e=>(t(),H(B,{key:e.id},{default:p(()=>[s("div",U,[s("div",null,"NO."+_(e.id),1),s("div",null,_(e.reason),1),s("div",{class:L({income:e.change_amount>=0,out:e.change_amount<0})},_((e.change_amount>0?"+":"")+(e.change_amount/100).toFixed(2)),3),s("div",null,_(u(R)(e.created_on)),1)])]),_:2},1024))),128))]))]),_:1})])}}});const ke=S(Y,[["__scopeId","data-v-d4d04859"]]);export{ke as default};

@ -1 +0,0 @@
import{_ as q}from"./whisper-24717711.js";import{_ as I,a as V}from"./post-item.vue_vue_type_style_index_0_lang-369aa3bd.js";import{_ as W}from"./post-skeleton-73b6be04.js";import{_ as E}from"./main-nav.vue_vue_type_style_index_0_lang-5497f713.js";import{u as G}from"./vuex-44de225f.js";import{b as H}from"./vue-router-e5a2430e.js";import{N as L,_ as Q}from"./index-d9671de1.js";import{d as T,H as s,b as U,f as o,k as n,w as u,bf as h,Y as w,e,F as k,u as y,q as C}from"./@vue-a481fc63.js";import{F as Y,Q as j,I as A,G as D}from"./naive-ui-d8de3dda.js";import"./content-cbd53e51.js";import"./@vicons-7a4ef312.js";import"./paopao-video-player-2fe58954.js";import"./formatTime-4210fcd1.js";import"./moment-2ab8298d.js";import"./copy-to-clipboard-4ef7d3eb.js";import"./@babel-725317a4.js";import"./toggle-selection-93f4ad84.js";import"./vooks-6d99783e.js";import"./evtd-b614532e.js";import"./axios-4a70c6fc.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-39372edb.js";import"./@css-render-7124a1a5.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={key:0,class:"skeleton-wrap"},K={key:1},O={key:0,class:"empty-wrap"},X={key:1},Z={key:2},ee={key:0,class:"pagination-wrap"},oe=T({__name:"Collection",setup(te){const m=G(),S=H(),_=s(!1),i=s([]),l=s(+S.query.p||1),p=s(20),r=s(0),c=s(!1),d=s({id:0,avatar:"",username:"",nickname:"",is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1}),f=t=>{d.value=t,c.value=!0},b=()=>{c.value=!1},v=()=>{_.value=!0,L({page:l.value,page_size:p.value}).then(t=>{_.value=!1,i.value=t.list,r.value=Math.ceil(t.pager.total_rows/p.value),window.scrollTo(0,0)}).catch(t=>{_.value=!1})},x=t=>{l.value=t,v()};return U(()=>{v()}),(t,ne)=>{const $=E,z=W,B=A,F=I,g=D,M=V,N=q,P=Y,R=j;return e(),o("div",null,[n($,{title:"收藏"}),n(P,{class:"main-content-wrap",bordered:""},{default:u(()=>[_.value?(e(),o("div",J,[n(z,{num:p.value},null,8,["num"])])):(e(),o("div",K,[i.value.length===0?(e(),o("div",O,[n(B,{size:"large",description:"暂无数据"})])):w("",!0),h(m).state.desktopModelShow?(e(),o("div",X,[(e(!0),o(k,null,y(i.value,a=>(e(),C(g,{key:a.id},{default:u(()=>[n(F,{post:a,onSendWhisper:f},null,8,["post"])]),_:2},1024))),128))])):(e(),o("div",Z,[(e(!0),o(k,null,y(i.value,a=>(e(),C(g,{key:a.id},{default:u(()=>[n(M,{post:a,onSendWhisper:f},null,8,["post"])]),_:2},1024))),128))]))])),n(N,{show:c.value,user:d.value,onSuccess:b},null,8,["show","user"])]),_:1}),r.value>0?(e(),o("div",ee,[n(R,{page:l.value,"onUpdate:page":x,"page-slot":h(m).state.collapsedRight?5:8,"page-count":r.value},null,8,["page","page-slot","page-count"])])):w("",!0)])}}});const Ve=Q(oe,[["__scopeId","data-v-760779af"]]);export{Ve as default};

@ -0,0 +1 @@
import{_ as I}from"./whisper-41c78cd2.js";import{_ as N,a as Q}from"./post-item.vue_vue_type_style_index_0_lang-75f1af94.js";import{_ as V}from"./post-skeleton-2ccfac69.js";import{_ as W}from"./main-nav.vue_vue_type_style_index_0_lang-f8e6a4c1.js";import{u as E}from"./vuex-44de225f.js";import{b as G}from"./vue-router-e5a2430e.js";import{Q as H,_ as L}from"./index-f37b0729.js";import{d as T,H as s,b as U,f as o,k as n,w as u,bf as h,Y as w,e,F as k,u as y,q as C}from"./@vue-a481fc63.js";import{F as Y,Q as j,I as A,G as D}from"./naive-ui-d8de3dda.js";import"./content-60b75b00.js";import"./@vicons-7a4ef312.js";import"./paopao-video-player-2fe58954.js";import"./copy-to-clipboard-4ef7d3eb.js";import"./@babel-725317a4.js";import"./toggle-selection-93f4ad84.js";import"./vooks-6d99783e.js";import"./evtd-b614532e.js";import"./axios-4a70c6fc.js";import"./moment-2ab8298d.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-39372edb.js";import"./@css-render-7124a1a5.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={key:0,class:"skeleton-wrap"},K={key:1},O={key:0,class:"empty-wrap"},X={key:1},Z={key:2},ee={key:0,class:"pagination-wrap"},oe=T({__name:"Collection",setup(te){const m=E(),S=G(),_=s(!1),i=s([]),l=s(+S.query.p||1),p=s(20),r=s(0),c=s(!1),d=s({id:0,avatar:"",username:"",nickname:"",is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1}),f=t=>{d.value=t,c.value=!0},b=()=>{c.value=!1},v=()=>{_.value=!0,H({page:l.value,page_size:p.value}).then(t=>{_.value=!1,i.value=t.list,r.value=Math.ceil(t.pager.total_rows/p.value),window.scrollTo(0,0)}).catch(t=>{_.value=!1})},x=t=>{l.value=t,v()};return U(()=>{v()}),(t,ne)=>{const $=W,z=V,B=A,F=N,g=D,M=Q,P=I,R=Y,q=j;return e(),o("div",null,[n($,{title:"收藏"}),n(R,{class:"main-content-wrap",bordered:""},{default:u(()=>[_.value?(e(),o("div",J,[n(z,{num:p.value},null,8,["num"])])):(e(),o("div",K,[i.value.length===0?(e(),o("div",O,[n(B,{size:"large",description:"暂无数据"})])):w("",!0),h(m).state.desktopModelShow?(e(),o("div",X,[(e(!0),o(k,null,y(i.value,a=>(e(),C(g,{key:a.id},{default:u(()=>[n(F,{post:a,onSendWhisper:f},null,8,["post"])]),_:2},1024))),128))])):(e(),o("div",Z,[(e(!0),o(k,null,y(i.value,a=>(e(),C(g,{key:a.id},{default:u(()=>[n(M,{post:a,onSendWhisper:f},null,8,["post"])]),_:2},1024))),128))]))])),n(P,{show:c.value,user:d.value,onSuccess:b},null,8,["show","user"])]),_:1}),r.value>0?(e(),o("div",ee,[n(q,{page:l.value,"onUpdate:page":x,"page-slot":h(m).state.collapsedRight?5:8,"page-count":r.value},null,8,["page","page-slot","page-count"])])):w("",!0)])}}});const Ne=L(oe,[["__scopeId","data-v-760779af"]]);export{Ne as default};

@ -1 +0,0 @@
import{_ as T}from"./whisper-24717711.js";import{d as N,c as j,r as A,e as s,f as c,k as t,w as n,j as i,y as H,A as L,x as v,bf as g,h as I,H as a,b as U,Y as S,F as z,u as W,q as E}from"./@vue-a481fc63.js";import{b as G}from"./formatTime-4210fcd1.js";import{i as Q,p as Y}from"./@vicons-7a4ef312.js";import{j as x,o as J,e as K,O as X,L as Z,F as ee,Q as te,I as ne,G as oe}from"./naive-ui-d8de3dda.js";import{_ as q,b as se}from"./index-d9671de1.js";import{_ as ae}from"./post-skeleton-73b6be04.js";import{_ as ce}from"./main-nav.vue_vue_type_style_index_0_lang-5497f713.js";import{u as ie}from"./vuex-44de225f.js";import{b as _e}from"./vue-router-e5a2430e.js";import"./moment-2ab8298d.js";import"./seemly-76b7b838.js";import"./vueuc-39372edb.js";import"./evtd-b614532e.js";import"./@css-render-7124a1a5.js";import"./vooks-6d99783e.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-4a70c6fc.js";/* empty css */const re={class:"contact-item"},le={class:"nickname-wrap"},pe={class:"username-wrap"},ue={class:"user-info"},me={class:"info-item"},de={class:"info-item"},fe={class:"item-header-extra"},ve=N({__name:"contact-item",props:{contact:{}},emits:["send-whisper"],setup(b,{emit:h}){const _=b,r=e=>()=>I(x,null,{default:()=>I(e)}),l=j(()=>[{label:"私信",key:"whisper",icon:r(Y)}]),u=e=>{switch(e){case"whisper":const o={id:_.contact.user_id,avatar:_.contact.avatar,username:_.contact.username,nickname:_.contact.nickname,is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1};h("send-whisper",o);break}};return(e,o)=>{const m=J,d=A("router-link"),w=K,k=X,y=Z;return s(),c("div",re,[t(y,{"content-indented":""},{avatar:n(()=>[t(m,{size:54,src:e.contact.avatar},null,8,["src"])]),header:n(()=>[i("span",le,[t(d,{onClick:o[0]||(o[0]=H(()=>{},["stop"])),class:"username-link",to:{name:"user",query:{s:e.contact.username}}},{default:n(()=>[L(v(e.contact.nickname),1)]),_:1},8,["to"])]),i("span",pe," @"+v(e.contact.username),1),i("div",ue,[i("span",me," UID. "+v(e.contact.user_id),1),i("span",de,v(g(G)(e.contact.created_on))+" 加入 ",1)])]),"header-extra":n(()=>[i("div",fe,[t(k,{placement:"bottom-end",trigger:"click",size:"small",options:l.value,onSelect:u},{default:n(()=>[t(w,{quaternary:"",circle:""},{icon:n(()=>[t(g(x),null,{default:n(()=>[t(g(Q))]),_:1})]),_:1})]),_:1},8,["options"])])]),_:1})])}}});const ge=q(ve,[["__scopeId","data-v-d62f19da"]]),he={key:0,class:"skeleton-wrap"},we={key:1},ke={key:0,class:"empty-wrap"},ye={key:0,class:"pagination-wrap"},be=N({__name:"Contacts",setup(b){const h=ie(),_=_e(),r=a(!1),l=a([]),u=a(+_.query.p||1),e=a(20),o=a(0),m=a(!1),d=a({id:0,avatar:"",username:"",nickname:"",is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1}),w=p=>{d.value=p,m.value=!0},k=()=>{m.value=!1},y=p=>{u.value=p,C()};U(()=>{C()});const C=(p=!1)=>{l.value.length===0&&(r.value=!0),se({page:u.value,page_size:e.value}).then(f=>{r.value=!1,l.value=f.list,o.value=Math.ceil(f.pager.total_rows/e.value),p&&setTimeout(()=>{window.scrollTo(0,99999)},50)}).catch(f=>{r.value=!1})};return(p,f)=>{const B=ce,F=ae,M=ne,P=ge,V=oe,D=T,O=ee,R=te;return s(),c(z,null,[i("div",null,[t(B,{title:"好友"}),t(O,{class:"main-content-wrap",bordered:""},{default:n(()=>[r.value?(s(),c("div",he,[t(F,{num:e.value},null,8,["num"])])):(s(),c("div",we,[l.value.length===0?(s(),c("div",ke,[t(M,{size:"large",description:"暂无数据"})])):S("",!0),(s(!0),c(z,null,W(l.value,$=>(s(),E(V,{class:"list-item",key:$.user_id},{default:n(()=>[t(P,{contact:$,onSendWhisper:w},null,8,["contact"])]),_:2},1024))),128))])),t(D,{show:m.value,user:d.value,onSuccess:k},null,8,["show","user"])]),_:1})]),o.value>0?(s(),c("div",ye,[t(R,{page:u.value,"onUpdate:page":y,"page-slot":g(h).state.collapsedRight?5:8,"page-count":o.value},null,8,["page","page-slot","page-count"])])):S("",!0)],64)}}});const Ye=q(be,[["__scopeId","data-v-e20fef94"]]);export{Ye as default};

@ -0,0 +1 @@
import{_ as T}from"./whisper-41c78cd2.js";import{d as F,c as j,r as A,e as s,f as c,k as t,w as n,j as i,y as H,A as L,x as v,bf as g,h as I,H as a,b as U,Y as S,F as z,u as W,q as E}from"./@vue-a481fc63.js";import{F as G,_ as N,b as Q}from"./index-f37b0729.js";import{i as Y,p as J}from"./@vicons-7a4ef312.js";import{j as x,o as K,e as X,O as Z,L as ee,F as te,Q as ne,I as oe,G as se}from"./naive-ui-d8de3dda.js";import{_ as ae}from"./post-skeleton-2ccfac69.js";import{_ as ce}from"./main-nav.vue_vue_type_style_index_0_lang-f8e6a4c1.js";import{u as ie}from"./vuex-44de225f.js";import{b as _e}from"./vue-router-e5a2430e.js";import"./axios-4a70c6fc.js";import"./moment-2ab8298d.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-39372edb.js";import"./evtd-b614532e.js";import"./@css-render-7124a1a5.js";import"./vooks-6d99783e.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 re={class:"contact-item"},le={class:"nickname-wrap"},pe={class:"username-wrap"},ue={class:"user-info"},me={class:"info-item"},de={class:"info-item"},fe={class:"item-header-extra"},ve=F({__name:"contact-item",props:{contact:{}},emits:["send-whisper"],setup(C,{emit:h}){const _=C,r=e=>()=>I(x,null,{default:()=>I(e)}),l=j(()=>[{label:"私信",key:"whisper",icon:r(J)}]),u=e=>{switch(e){case"whisper":const o={id:_.contact.user_id,avatar:_.contact.avatar,username:_.contact.username,nickname:_.contact.nickname,is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1};h("send-whisper",o);break}};return(e,o)=>{const m=K,d=A("router-link"),w=X,k=Z,y=ee;return s(),c("div",re,[t(y,{"content-indented":""},{avatar:n(()=>[t(m,{size:54,src:e.contact.avatar},null,8,["src"])]),header:n(()=>[i("span",le,[t(d,{onClick:o[0]||(o[0]=H(()=>{},["stop"])),class:"username-link",to:{name:"user",query:{s:e.contact.username}}},{default:n(()=>[L(v(e.contact.nickname),1)]),_:1},8,["to"])]),i("span",pe," @"+v(e.contact.username),1),i("div",ue,[i("span",me," UID. "+v(e.contact.user_id),1),i("span",de,v(g(G)(e.contact.created_on))+" 加入 ",1)])]),"header-extra":n(()=>[i("div",fe,[t(k,{placement:"bottom-end",trigger:"click",size:"small",options:l.value,onSelect:u},{default:n(()=>[t(w,{quaternary:"",circle:""},{icon:n(()=>[t(g(x),null,{default:n(()=>[t(g(Y))]),_:1})]),_:1})]),_:1},8,["options"])])]),_:1})])}}});const ge=N(ve,[["__scopeId","data-v-d62f19da"]]),he={key:0,class:"skeleton-wrap"},we={key:1},ke={key:0,class:"empty-wrap"},ye={key:0,class:"pagination-wrap"},Ce=F({__name:"Contacts",setup(C){const h=ie(),_=_e(),r=a(!1),l=a([]),u=a(+_.query.p||1),e=a(20),o=a(0),m=a(!1),d=a({id:0,avatar:"",username:"",nickname:"",is_admin:!1,is_friend:!0,is_following:!1,created_on:0,follows:0,followings:0,status:1}),w=p=>{d.value=p,m.value=!0},k=()=>{m.value=!1},y=p=>{u.value=p,$()};U(()=>{$()});const $=(p=!1)=>{l.value.length===0&&(r.value=!0),Q({page:u.value,page_size:e.value}).then(f=>{r.value=!1,l.value=f.list,o.value=Math.ceil(f.pager.total_rows/e.value),p&&setTimeout(()=>{window.scrollTo(0,99999)},50)}).catch(f=>{r.value=!1})};return(p,f)=>{const q=ce,B=ae,M=oe,P=ge,V=se,D=T,O=te,R=ne;return s(),c(z,null,[i("div",null,[t(q,{title:"好友"}),t(O,{class:"main-content-wrap",bordered:""},{default:n(()=>[r.value?(s(),c("div",he,[t(B,{num:e.value},null,8,["num"])])):(s(),c("div",we,[l.value.length===0?(s(),c("div",ke,[t(M,{size:"large",description:"暂无数据"})])):S("",!0),(s(!0),c(z,null,W(l.value,b=>(s(),E(V,{class:"list-item",key:b.user_id},{default:n(()=>[t(P,{contact:b,onSendWhisper:w},null,8,["contact"])]),_:2},1024))),128))])),t(D,{show:m.value,user:d.value,onSuccess:k},null,8,["show","user"])]),_:1})]),o.value>0?(s(),c("div",ye,[t(R,{page:u.value,"onUpdate:page":y,"page-slot":g(h).state.collapsedRight?5:8,"page-count":o.value},null,8,["page","page-slot","page-count"])])):S("",!0)],64)}}});const Qe=N(Ce,[["__scopeId","data-v-e20fef94"]]);export{Qe 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

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
import{z as $,A as I,B as M,C as O,_ as x}from"./index-d9671de1.js";import{x as U}from"./@vicons-7a4ef312.js";import{d as F,H as i,c as A,b as q,r as j,e as c,f as _,k as n,w as s,q as b,A as B,x as f,Y as p,bf as h,E as D,al as H,F as Y,u as G}from"./@vue-a481fc63.js";import{o as J,M as C,j as K,e as P,O as Q,L as R,F as W,f as X,g as Z,a as ee,k as oe}from"./naive-ui-d8de3dda.js";import{_ as te}from"./main-nav.vue_vue_type_style_index_0_lang-5497f713.js";import{u as ne}from"./vuex-44de225f.js";import"./vue-router-e5a2430e.js";import"./axios-4a70c6fc.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-39372edb.js";import"./evtd-b614532e.js";import"./@css-render-7124a1a5.js";import"./vooks-6d99783e.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 se={key:0,class:"tag-item"},ae={key:0,class:"tag-quote"},ce={key:1,class:"tag-quote tag-follow"},le={key:0,class:"options"},ie=F({__name:"tag-item",props:{tag:{},showAction:{type:Boolean},checkFollowing:{type:Boolean}},setup(T){const t=T,r=i(!1),m=A(()=>{let e=[];return t.tag.is_following===0?e.push({label:"关注",key:"follow"}):(t.tag.is_top===0?e.push({label:"置顶",key:"stick"}):e.push({label:"取消置顶",key:"unstick"}),e.push({label:"取消关注",key:"unfollow"})),e}),l=e=>{switch(e){case"follow":M({topic_id:t.tag.id}).then(o=>{t.tag.is_following=1,window.$message.success("关注成功")}).catch(o=>{console.log(o)});break;case"unfollow":I({topic_id:t.tag.id}).then(o=>{t.tag.is_following=0,window.$message.success("取消关注")}).catch(o=>{console.log(o)});break;case"stick":$({topic_id:t.tag.id}).then(o=>{t.tag.is_top=o.top_status,window.$message.success("置顶成功")}).catch(o=>{console.log(o)});break;case"unstick":$({topic_id:t.tag.id}).then(o=>{t.tag.is_top=o.top_status,window.$message.success("取消置顶")}).catch(o=>{console.log(o)});break}};return q(()=>{r.value=!1}),(e,o)=>{const w=j("router-link"),g=J,k=C,a=K,d=P,v=Q,u=R;return!e.checkFollowing||e.checkFollowing&&e.tag.is_following===1?(c(),_("div",se,[n(u,null,{header:s(()=>[(c(),b(k,{type:"success",size:"large",round:"",key:e.tag.id},{avatar:s(()=>[n(g,{src:e.tag.user.avatar},null,8,["src"])]),default:s(()=>[n(w,{class:"hash-link",to:{name:"home",query:{q:e.tag.tag,t:"tag"}}},{default:s(()=>[B(" #"+f(e.tag.tag),1)]),_:1},8,["to"]),e.showAction?p("",!0):(c(),_("span",ae,"("+f(e.tag.quote_num)+")",1)),e.showAction?(c(),_("span",ce,"("+f(e.tag.quote_num)+")",1)):p("",!0)]),_:1}))]),"header-extra":s(()=>[e.showAction?(c(),_("div",le,[n(v,{placement:"bottom-end",trigger:"click",size:"small",options:m.value,onSelect:l},{default:s(()=>[n(d,{type:"success",quaternary:"",circle:"",block:""},{icon:s(()=>[n(a,null,{default:s(()=>[n(h(U))]),_:1})]),_:1})]),_:1},8,["options"])])):p("",!0)]),_:1})])):p("",!0)}}});const _e=F({__name:"Topic",setup(T){const t=ne(),r=i([]),m=i("hot"),l=i(!1),e=i(!1),o=i(!1);D(e,()=>{e.value||(window.$message.success("保存成功"),t.commit("refreshTopicFollow"))});const w=A({get:()=>{let a="编辑";return e.value&&(a="保存"),a},set:a=>{}}),g=()=>{l.value=!0,O({type:m.value,num:50}).then(a=>{r.value=a.topics,l.value=!1}).catch(a=>{console.log(a),l.value=!1})},k=a=>{m.value=a,a=="follow"?o.value=!0:o.value=!1,g()};return q(()=>{g()}),(a,d)=>{const v=te,u=X,L=C,V=Z,N=ie,S=ee,z=oe,E=W;return c(),_("div",null,[n(v,{title:"话题"}),n(E,{class:"main-content-wrap tags-wrap",bordered:""},{default:s(()=>[n(V,{type:"line",animated:"","onUpdate:value":k},H({default:s(()=>[n(u,{name:"hot",tab:"热门"}),n(u,{name:"new",tab:"最新"}),h(t).state.userLogined?(c(),b(u,{key:0,name:"follow",tab:"关注"})):p("",!0)]),_:2},[h(t).state.userLogined?{name:"suffix",fn:s(()=>[n(L,{checked:e.value,"onUpdate:checked":d[0]||(d[0]=y=>e.value=y),checkable:""},{default:s(()=>[B(f(w.value),1)]),_:1},8,["checked"])]),key:"0"}:void 0]),1024),n(z,{show:l.value},{default:s(()=>[n(S,null,{default:s(()=>[(c(!0),_(Y,null,G(r.value,y=>(c(),b(N,{tag:y,showAction:h(t).state.userLogined&&e.value,checkFollowing:o.value},null,8,["tag","showAction","checkFollowing"]))),256))]),_:1})]),_:1},8,["show"])]),_:1})])}}});const Ne=x(_e,[["__scopeId","data-v-1fb31ecf"]]);export{Ne as default};

@ -0,0 +1 @@
import{A as $,B as M,C as O,D as x,_ as z}from"./index-f37b0729.js";import{x as D}from"./@vicons-7a4ef312.js";import{d as F,H as i,c as A,b as q,r as U,e as c,f as _,k as n,w as s,q as b,A as B,x as f,Y as u,bf as h,E as j,al as H,F as Y,u as G}from"./@vue-a481fc63.js";import{o as J,M as C,j as K,e as P,O as Q,L as R,F as W,f as X,g as Z,a as ee,k as oe}from"./naive-ui-d8de3dda.js";import{_ as te}from"./main-nav.vue_vue_type_style_index_0_lang-f8e6a4c1.js";import{u as ne}from"./vuex-44de225f.js";import"./vue-router-e5a2430e.js";import"./axios-4a70c6fc.js";import"./moment-2ab8298d.js";/* empty css */import"./seemly-76b7b838.js";import"./vueuc-39372edb.js";import"./evtd-b614532e.js";import"./@css-render-7124a1a5.js";import"./vooks-6d99783e.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 se={key:0,class:"tag-item"},ae={key:0,class:"tag-quote"},ce={key:1,class:"tag-quote tag-follow"},le={key:0,class:"options"},ie=F({__name:"tag-item",props:{tag:{},showAction:{type:Boolean},checkFollowing:{type:Boolean}},setup(T){const t=T,r=i(!1),m=A(()=>{let e=[];return t.tag.is_following===0?e.push({label:"关注",key:"follow"}):(t.tag.is_top===0?e.push({label:"置顶",key:"stick"}):e.push({label:"取消置顶",key:"unstick"}),e.push({label:"取消关注",key:"unfollow"})),e}),l=e=>{switch(e){case"follow":O({topic_id:t.tag.id}).then(o=>{t.tag.is_following=1,window.$message.success("关注成功")}).catch(o=>{console.log(o)});break;case"unfollow":M({topic_id:t.tag.id}).then(o=>{t.tag.is_following=0,window.$message.success("取消关注")}).catch(o=>{console.log(o)});break;case"stick":$({topic_id:t.tag.id}).then(o=>{t.tag.is_top=o.top_status,window.$message.success("置顶成功")}).catch(o=>{console.log(o)});break;case"unstick":$({topic_id:t.tag.id}).then(o=>{t.tag.is_top=o.top_status,window.$message.success("取消置顶")}).catch(o=>{console.log(o)});break}};return q(()=>{r.value=!1}),(e,o)=>{const w=U("router-link"),g=J,k=C,a=K,d=P,v=Q,p=R;return!e.checkFollowing||e.checkFollowing&&e.tag.is_following===1?(c(),_("div",se,[n(p,null,{header:s(()=>[(c(),b(k,{type:"success",size:"large",round:"",key:e.tag.id},{avatar:s(()=>[n(g,{src:e.tag.user.avatar},null,8,["src"])]),default:s(()=>[n(w,{class:"hash-link",to:{name:"home",query:{q:e.tag.tag,t:"tag"}}},{default:s(()=>[B(" #"+f(e.tag.tag),1)]),_:1},8,["to"]),e.showAction?u("",!0):(c(),_("span",ae,"("+f(e.tag.quote_num)+")",1)),e.showAction?(c(),_("span",ce,"("+f(e.tag.quote_num)+")",1)):u("",!0)]),_:1}))]),"header-extra":s(()=>[e.showAction?(c(),_("div",le,[n(v,{placement:"bottom-end",trigger:"click",size:"small",options:m.value,onSelect:l},{default:s(()=>[n(d,{type:"success",quaternary:"",circle:"",block:""},{icon:s(()=>[n(a,null,{default:s(()=>[n(h(D))]),_:1})]),_:1})]),_:1},8,["options"])])):u("",!0)]),_:1})])):u("",!0)}}});const _e=F({__name:"Topic",setup(T){const t=ne(),r=i([]),m=i("hot"),l=i(!1),e=i(!1),o=i(!1);j(e,()=>{e.value||(window.$message.success("保存成功"),t.commit("refreshTopicFollow"))});const w=A({get:()=>{let a="编辑";return e.value&&(a="保存"),a},set:a=>{}}),g=()=>{l.value=!0,x({type:m.value,num:50}).then(a=>{r.value=a.topics,l.value=!1}).catch(a=>{console.log(a),l.value=!1})},k=a=>{m.value=a,a=="follow"?o.value=!0:o.value=!1,g()};return q(()=>{g()}),(a,d)=>{const v=te,p=X,L=C,V=Z,N=ie,S=ee,E=oe,I=W;return c(),_("div",null,[n(v,{title:"话题"}),n(I,{class:"main-content-wrap tags-wrap",bordered:""},{default:s(()=>[n(V,{type:"line",animated:"","onUpdate:value":k},H({default:s(()=>[n(p,{name:"hot",tab:"热门"}),n(p,{name:"new",tab:"最新"}),h(t).state.userLogined?(c(),b(p,{key:0,name:"follow",tab:"关注"})):u("",!0)]),_:2},[h(t).state.userLogined?{name:"suffix",fn:s(()=>[n(L,{checked:e.value,"onUpdate:checked":d[0]||(d[0]=y=>e.value=y),checkable:""},{default:s(()=>[B(f(w.value),1)]),_:1},8,["checked"])]),key:"0"}:void 0]),1024),n(E,{show:l.value},{default:s(()=>[n(S,null,{default:s(()=>[(c(!0),_(Y,null,G(r.value,y=>(c(),b(N,{tag:y,showAction:h(t).state.userLogined&&e.value,checkFollowing:o.value},null,8,["tag","showAction","checkFollowing"]))),256))]),_:1})]),_:1},8,["show"])]),_:1})])}}});const Se=z(_e,[["__scopeId","data-v-1fb31ecf"]]);export{Se 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

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
import{h as r}from"./moment-2ab8298d.js";r.locale("zh-cn");const a=e=>r.unix(e).fromNow(),f=e=>{let t=r.unix(e),o=r();return t.year()!=o.year()?t.utc(!0).format("YYYY-MM-DD HH:mm"):r().diff(t,"month")>3?t.utc(!0).format("MM-DD HH:mm"):t.fromNow()},u=e=>{let t=r.unix(e),o=r();return t.year()!=o.year()?t.utc(!0).format("YYYY-MM-DD"):r().diff(t,"month")>3?t.utc(!0).format("MM-DD"):t.fromNow()},n=e=>r.unix(e).utc(!0).format("YYYY年MM月");export{a,n as b,u as c,f};

@ -1 +0,0 @@
.auth-wrap[data-v-053dfa44]{margin-top:-30px}.dark .auth-wrap[data-v-053dfa44]{background-color:#101014bf}.rightbar-wrap[data-v-52b86ac0]::-webkit-scrollbar{width:0;height:0}.rightbar-wrap[data-v-52b86ac0]{width:240px;position:fixed;left:calc(50% + var(--content-main) / 2 + 10px);max-height:100vh;overflow:auto}.rightbar-wrap .search-wrap[data-v-52b86ac0]{margin:12px 0}.rightbar-wrap .hot-tag-item[data-v-52b86ac0]{line-height:2;position:relative}.rightbar-wrap .hot-tag-item .hash-link[data-v-52b86ac0]{width:calc(100% - 60px);text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block}.rightbar-wrap .hot-tag-item .post-num[data-v-52b86ac0]{position:absolute;right:0;top:0;width:60px;text-align:right;line-height:2;opacity:.5}.rightbar-wrap .hottopic-wrap[data-v-52b86ac0]{margin-bottom:10px}.rightbar-wrap .copyright-wrap .copyright[data-v-52b86ac0]{font-size:12px;opacity:.75}.rightbar-wrap .copyright-wrap .hash-link[data-v-52b86ac0]{font-size:12px}.dark .hottopic-wrap[data-v-52b86ac0],.dark .copyright-wrap[data-v-52b86ac0]{background-color:#18181c}.sidebar-wrap::-webkit-scrollbar{width:0;height:0}.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;max-height:100vh;overflow:auto}.sidebar-wrap .n-menu .n-menu-item-content:before{border-radius:21px}.sidebar-wrap .logo-wrap{display:flex;justify-content:flex-start;margin-bottom:12px}.sidebar-wrap .logo-wrap .logo-img{margin-left:24px}.sidebar-wrap .logo-wrap .logo-img:hover{cursor:pointer}.sidebar-wrap .user-wrap{display:flex;align-items:center;position:absolute;bottom:12px;left:12px;right:12px}.sidebar-wrap .user-wrap .user-mini-wrap{display:none}.sidebar-wrap .user-wrap .user-avatar{margin-right:8px}.sidebar-wrap .user-wrap .user-info{display:flex;flex-direction:column}.sidebar-wrap .user-wrap .user-info .nickname{font-size:16px;font-weight:700;line-height:16px;height:16px;margin-bottom:2px;display:flex;align-items:center}.sidebar-wrap .user-wrap .user-info .nickname .nickname-txt{max-width:90px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.sidebar-wrap .user-wrap .user-info .nickname .logout{margin-left:6px}.sidebar-wrap .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}.sidebar-wrap .user-wrap .login-only-wrap{display:flex;justify-content:center;width:100%}.sidebar-wrap .user-wrap .login-only-wrap button{margin:0 4px;width:80%}.sidebar-wrap .user-wrap .login-wrap{display:flex;justify-content:center;width:100%}.sidebar-wrap .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: 600px}.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}.following-link{color:#000;color:none;text-decoration:none;cursor:pointer;opacity:.75}.following-link:hover{opacity:.8}.slide-bar-user-link{text-decoration:none;cursor:pointer}.slide-bar-user-link:hover{color:#18a058;opacity:.8}.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 .following-link,.dark .username-link{color:#eee}.dark .beian-link{color:#ddd}@media screen and (max-width: 821px){.content-wrap{top:0;position:absolute!important}}

@ -0,0 +1 @@
.auth-wrap[data-v-053dfa44]{margin-top:-30px}.dark .auth-wrap[data-v-053dfa44]{background-color:#101014bf}.rightbar-wrap[data-v-0a6cd0b6]::-webkit-scrollbar{width:0;height:0}.rightbar-wrap[data-v-0a6cd0b6]{width:240px;position:fixed;left:calc(50% + var(--content-main) / 2 + 10px);max-height:100vh;overflow:auto}.rightbar-wrap .search-wrap[data-v-0a6cd0b6]{margin:12px 0}.rightbar-wrap .hot-tag-item[data-v-0a6cd0b6]{line-height:2;position:relative}.rightbar-wrap .hot-tag-item .hash-link[data-v-0a6cd0b6]{width:calc(100% - 60px);text-overflow:ellipsis;white-space:nowrap;overflow:hidden;display:block}.rightbar-wrap .hot-tag-item .post-num[data-v-0a6cd0b6]{position:absolute;right:0;top:0;width:60px;text-align:right;line-height:2;opacity:.5}.rightbar-wrap .hottopic-wrap[data-v-0a6cd0b6]{margin-bottom:10px}.rightbar-wrap .site-info[data-v-0a6cd0b6]{margin-top:8px;padding-left:16px;padding-right:16px}.rightbar-wrap .site-info .site-info-item[data-v-0a6cd0b6]{font-size:10px;opacity:.75}.rightbar-wrap .copyright-wrap .copyright[data-v-0a6cd0b6]{font-size:12px;opacity:.75}.rightbar-wrap .copyright-wrap .hash-link[data-v-0a6cd0b6]{font-size:12px}.dark .hottopic-wrap[data-v-0a6cd0b6],.dark .copyright-wrap[data-v-0a6cd0b6]{background-color:#18181c}.sidebar-wrap::-webkit-scrollbar{width:0;height:0}.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;max-height:100vh;overflow:auto}.sidebar-wrap .n-menu .n-menu-item-content:before{border-radius:21px}.sidebar-wrap .logo-wrap{display:flex;justify-content:flex-start;margin-bottom:12px}.sidebar-wrap .logo-wrap .logo-img{margin-left:24px}.sidebar-wrap .logo-wrap .logo-img:hover{cursor:pointer}.sidebar-wrap .user-wrap{display:flex;align-items:center;position:absolute;bottom:12px;left:12px;right:12px}.sidebar-wrap .user-wrap .user-mini-wrap{display:none}.sidebar-wrap .user-wrap .user-avatar{margin-right:8px}.sidebar-wrap .user-wrap .user-info{display:flex;flex-direction:column}.sidebar-wrap .user-wrap .user-info .nickname{font-size:16px;font-weight:700;line-height:16px;height:16px;margin-bottom:2px;display:flex;align-items:center}.sidebar-wrap .user-wrap .user-info .nickname .nickname-txt{max-width:90px;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.sidebar-wrap .user-wrap .user-info .nickname .logout{margin-left:6px}.sidebar-wrap .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}.sidebar-wrap .user-wrap .login-only-wrap{display:flex;justify-content:center;width:100%}.sidebar-wrap .user-wrap .login-only-wrap button{margin:0 4px;width:80%}.sidebar-wrap .user-wrap .login-wrap{display:flex;justify-content:center;width:100%}.sidebar-wrap .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: 600px}.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}.following-link{color:#000;color:none;text-decoration:none;cursor:pointer;opacity:.75}.following-link:hover{opacity:.8}.slide-bar-user-link{text-decoration:none;cursor:pointer}.slide-bar-user-link:hover{color:#18a058;opacity:.8}.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 .following-link,.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{a3 as A}from"./index-d9671de1.js";import{u as B}from"./vuex-44de225f.js";import{u as E}from"./vue-router-e5a2430e.js";import{j as z}from"./vooks-6d99783e.js";import{Z as C,_ as N,$ as P,a0 as D}from"./@vicons-7a4ef312.js";import{u as R,a3 as $,a4 as x,j as H,e as I,a5 as V,h as j}from"./naive-ui-d8de3dda.js";import{d as q,H as h,b as F,e as n,f,bf as a,k as e,w as t,Y as c,j as L,q as _,A as U,x as Y,F as Z}from"./@vue-a481fc63.js";const G={key:0},J={class:"navbar"},ae=q({__name:"main-nav",props:{title:{default:""},back:{type:Boolean,default:!1},theme:{type:Boolean,default:!0}},setup(w){const i=w,o=B(),m=E(),l=h(!1),g=h("left"),u=s=>{s?(localStorage.setItem("PAOPAO_THEME","dark"),o.commit("triggerTheme","dark")):(localStorage.setItem("PAOPAO_THEME","light"),o.commit("triggerTheme","light"))},k=()=>{window.history.length<=1?m.push({path:"/"}):m.go(-1)},v=()=>{l.value=!0};return F(()=>{localStorage.getItem("PAOPAO_THEME")||u(z()==="dark"),o.state.desktopModelShow||(window.$store=o,window.$message=R())}),(s,d)=>{const b=A,y=$,M=x,r=H,p=I,O=V,S=j;return n(),f(Z,null,[a(o).state.drawerModelShow?(n(),f("div",G,[e(M,{show:l.value,"onUpdate:show":d[0]||(d[0]=T=>l.value=T),width:212,placement:g.value,resizable:""},{default:t(()=>[e(y,null,{default:t(()=>[e(b)]),_:1})]),_:1},8,["show","placement"])])):c("",!0),e(S,{size:"small",bordered:!0,class:"nav-title-card"},{header:t(()=>[L("div",J,[a(o).state.drawerModelShow&&!s.back?(n(),_(p,{key:0,class:"drawer-btn",onClick:v,quaternary:"",circle:"",size:"medium"},{icon:t(()=>[e(r,null,{default:t(()=>[e(a(C))]),_:1})]),_:1})):c("",!0),s.back?(n(),_(p,{key:1,class:"back-btn",onClick:k,quaternary:"",circle:"",size:"small"},{icon:t(()=>[e(r,null,{default:t(()=>[e(a(N))]),_:1})]),_:1})):c("",!0),U(" "+Y(i.title)+" ",1),i.theme?(n(),_(O,{key:2,value:a(o).state.theme==="dark","onUpdate:value":u,size:"small",class:"theme-switch-wrap"},{"checked-icon":t(()=>[e(r,{component:a(P)},null,8,["component"])]),"unchecked-icon":t(()=>[e(r,{component:a(D)},null,8,["component"])]),_:1},8,["value"])):c("",!0)])]),_:1})],64)}}});export{ae as _}; import{a7 as A}from"./index-f37b0729.js";import{u as B}from"./vuex-44de225f.js";import{u as E}from"./vue-router-e5a2430e.js";import{j as z}from"./vooks-6d99783e.js";import{Z as C,_ as N,$ as P,a0 as D}from"./@vicons-7a4ef312.js";import{u as R,a3 as $,a4 as x,j as H,e as I,a5 as V,h as j}from"./naive-ui-d8de3dda.js";import{d as q,H as h,b as F,e as n,f,bf as a,k as e,w as t,Y as c,j as L,q as _,A as U,x as Y,F as Z}from"./@vue-a481fc63.js";const G={key:0},J={class:"navbar"},ae=q({__name:"main-nav",props:{title:{default:""},back:{type:Boolean,default:!1},theme:{type:Boolean,default:!0}},setup(w){const i=w,o=B(),m=E(),l=h(!1),g=h("left"),u=s=>{s?(localStorage.setItem("PAOPAO_THEME","dark"),o.commit("triggerTheme","dark")):(localStorage.setItem("PAOPAO_THEME","light"),o.commit("triggerTheme","light"))},k=()=>{window.history.length<=1?m.push({path:"/"}):m.go(-1)},v=()=>{l.value=!0};return F(()=>{localStorage.getItem("PAOPAO_THEME")||u(z()==="dark"),o.state.desktopModelShow||(window.$store=o,window.$message=R())}),(s,d)=>{const b=A,y=$,M=x,r=H,p=I,O=V,S=j;return n(),f(Z,null,[a(o).state.drawerModelShow?(n(),f("div",G,[e(M,{show:l.value,"onUpdate:show":d[0]||(d[0]=T=>l.value=T),width:212,placement:g.value,resizable:""},{default:t(()=>[e(y,null,{default:t(()=>[e(b)]),_:1})]),_:1},8,["show","placement"])])):c("",!0),e(S,{size:"small",bordered:!0,class:"nav-title-card"},{header:t(()=>[L("div",J,[a(o).state.drawerModelShow&&!s.back?(n(),_(p,{key:0,class:"drawer-btn",onClick:v,quaternary:"",circle:"",size:"medium"},{icon:t(()=>[e(r,null,{default:t(()=>[e(a(C))]),_:1})]),_:1})):c("",!0),s.back?(n(),_(p,{key:1,class:"back-btn",onClick:k,quaternary:"",circle:"",size:"small"},{icon:t(()=>[e(r,null,{default:t(()=>[e(a(N))]),_:1})]),_:1})):c("",!0),U(" "+Y(i.title)+" ",1),i.theme?(n(),_(O,{key:2,value:a(o).state.theme==="dark","onUpdate:value":u,size:"small",class:"theme-switch-wrap"},{"checked-icon":t(()=>[e(r,{component:a(P)},null,8,["component"])]),"unchecked-icon":t(()=>[e(r,{component:a(D)},null,8,["component"])]),_:1},8,["value"])):c("",!0)])]),_:1})],64)}}});export{ae as _};

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

@ -1 +1 @@
import{S as b,_ as k}from"./index-d9671de1.js";import{R as B,H as C,S as N,b as R,e as S,i as U}from"./naive-ui-d8de3dda.js";import{d as V,H as p,e as $,q as z,w as s,j as a,k as n,A as _,x as i}from"./@vue-a481fc63.js";const H={class:"whisper-wrap"},W={class:"whisper-line"},j={class:"whisper-line send-wrap"},q=V({__name:"whisper",props:{show:{type:Boolean,default:!1},user:{}},emits:["success"],setup(r,{emit:u}){const d=r,o=p(""),t=p(!1),c=()=>{u("success")},m=()=>{t.value=!0,b({user_id:d.user.id,content:o.value}).then(e=>{window.$message.success("发送成功"),t.value=!1,o.value="",c()}).catch(e=>{t.value=!1})};return(e,l)=>{const h=B,w=C,f=N,v=R,g=S,y=U;return $(),z(y,{show:e.show,"onUpdate:show":c,class:"whisper-card",preset:"card",size:"small",title:"私信","mask-closable":!1,bordered:!1,style:{width:"360px"}},{default:s(()=>[a("div",H,[n(f,{"show-icon":!1},{default:s(()=>[_(" 即将发送私信给: "),n(w,{style:{"max-width":"100%"}},{default:s(()=>[n(h,{type:"success"},{default:s(()=>[_(i(e.user.nickname)+"@"+i(e.user.username),1)]),_:1})]),_:1})]),_:1}),a("div",W,[n(v,{type:"textarea",placeholder:"请输入私信内容(请勿发送不和谐内容,否则将会被封号)",autosize:{minRows:5,maxRows:10},value:o.value,"onUpdate:value":l[0]||(l[0]=x=>o.value=x),maxlength:"200","show-count":""},null,8,["value"])]),a("div",j,[n(g,{strong:"",secondary:"",type:"primary",loading:t.value,onClick:m},{default:s(()=>[_(" 发送 ")]),_:1},8,["loading"])])])]),_:1},8,["show"])}}});const M=k(q,[["__scopeId","data-v-0cbfe47c"]]);export{M as _}; import{W as b,_ as k}from"./index-f37b0729.js";import{R as B,H as C,S as N,b as R,e as U,i as V}from"./naive-ui-d8de3dda.js";import{d as W,H as p,e as $,q as z,w as s,j as a,k as n,A as _,x as i}from"./@vue-a481fc63.js";const H={class:"whisper-wrap"},S={class:"whisper-line"},j={class:"whisper-line send-wrap"},q=W({__name:"whisper",props:{show:{type:Boolean,default:!1},user:{}},emits:["success"],setup(r,{emit:u}){const d=r,o=p(""),t=p(!1),c=()=>{u("success")},m=()=>{t.value=!0,b({user_id:d.user.id,content:o.value}).then(e=>{window.$message.success("发送成功"),t.value=!1,o.value="",c()}).catch(e=>{t.value=!1})};return(e,l)=>{const h=B,w=C,f=N,v=R,g=U,y=V;return $(),z(y,{show:e.show,"onUpdate:show":c,class:"whisper-card",preset:"card",size:"small",title:"私信","mask-closable":!1,bordered:!1,style:{width:"360px"}},{default:s(()=>[a("div",H,[n(f,{"show-icon":!1},{default:s(()=>[_(" 即将发送私信给: "),n(w,{style:{"max-width":"100%"}},{default:s(()=>[n(h,{type:"success"},{default:s(()=>[_(i(e.user.nickname)+"@"+i(e.user.username),1)]),_:1})]),_:1})]),_:1}),a("div",S,[n(v,{type:"textarea",placeholder:"请输入私信内容(请勿发送不和谐内容,否则将会被封号)",autosize:{minRows:5,maxRows:10},value:o.value,"onUpdate:value":l[0]||(l[0]=x=>o.value=x),maxlength:"200","show-count":""},null,8,["value"])]),a("div",j,[n(g,{strong:"",secondary:"",type:"primary",loading:t.value,onClick:m},{default:s(()=>[_(" 发送 ")]),_:1},8,["loading"])])])]),_:1},8,["show"])}}});const M=k(q,[["__scopeId","data-v-0cbfe47c"]]);export{M as _};

@ -47,8 +47,9 @@
<link rel="modulepreload" crossorigin href="/assets/async-validator-dee29e8b.js"> <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/date-fns-975a2d8f.js">
<link rel="modulepreload" crossorigin href="/assets/naive-ui-d8de3dda.js"> <link rel="modulepreload" crossorigin href="/assets/naive-ui-d8de3dda.js">
<link rel="modulepreload" crossorigin href="/assets/moment-2ab8298d.js">
<link rel="modulepreload" crossorigin href="/assets/@vicons-7a4ef312.js"> <link rel="modulepreload" crossorigin href="/assets/@vicons-7a4ef312.js">
<link rel="stylesheet" href="/assets/index-75351f40.css"> <link rel="stylesheet" href="/assets/index-c337d1db.css">
<link rel="stylesheet" href="/assets/vfonts-7afd136d.css"> <link rel="stylesheet" href="/assets/vfonts-7afd136d.css">
</head> </head>

@ -414,3 +414,14 @@ export const changeUserStatus = (
data, data,
}); });
}; };
/**
*
* @returns Promise
*/
export const getSiteInfo = (): Promise<NetReq.SiteInfoResp> => {
return request({
method: "get",
url: "/v1/admin/site/status",
});
};

@ -83,16 +83,12 @@
</n-space> </n-space>
</div> </div>
</n-card> </n-card>
<n-card <n-card class="hottopic-wrap" embedded :bordered="false" size="small">
class="hottopic-wrap" <div class="ranking-header">
embedded
:bordered="false"
size="small"
>
<div class="ranking-header">
<div class="ranking-title">{{ rankingTitles[currentRankingType] }}</div> <div class="ranking-title">{{ rankingTitles[currentRankingType] }}</div>
<div class="toggle-button" @click="toggleRankingType"> <div class="toggle-button" @click="toggleRankingType">
{{ rankingTitles[NextRankingType] }} <n-icon :component="ChevronForward" /> {{ rankingTitles[NextRankingType] }}
<n-icon :component="ChevronForward" />
</div> </div>
</div> </div>
<n-spin :show="rankloading"> <n-spin :show="rankloading">
@ -105,7 +101,7 @@
<!-- --> <!-- -->
<div class="ranking-avatar"> <div class="ranking-avatar">
<!-- --> <!-- -->
<img :src="item.avatar"/> <img :src="item.avatar" />
</div> </div>
<div class="ranking-info"> <div class="ranking-info">
@ -124,10 +120,16 @@
</router-link> </router-link>
<div class="score"> <div class="score">
<div class="score-value" v-if="currentRankingType === 'highQuality'"> <div
class="score-value"
v-if="currentRankingType === 'highQuality'"
>
{{ item.comprehensive_score }} {{ item.comprehensive_score }}
</div> </div>
<div class="score-value" v-else-if="currentRankingType !== 'highQuality'"> <div
class="score-value"
v-else-if="currentRankingType !== 'highQuality'"
>
{{ item.download }} {{ item.download }}
</div> </div>
</div> </div>
@ -136,12 +138,18 @@
<div class="name-stats"> <div class="name-stats">
<div class="stats" v-if="currentRankingType === 'highQuality'"> <div class="stats" v-if="currentRankingType === 'highQuality'">
<div class="stat-item"> <div class="stat-item">
<div class="stat-value">📃{{ formatQuoteNumStats(item.post_count || item.download) }}</div> <div class="stat-value">
📃{{
formatQuoteNumStats(item.post_count || item.download)
}}
</div>
</div> </div>
<div class="stat-drop">·</div> <div class="stat-drop">·</div>
<!-- --> <!-- -->
<div class="stat-item"> <div class="stat-item">
<div class="stat-value">{{ formatQuoteNumStats(item.likes) }}</div> <div class="stat-value">
{{ formatQuoteNumStats(item.likes) }}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -150,6 +158,14 @@
</div> </div>
</n-spin> </n-spin>
</n-card> </n-card>
<div
class="site-info"
v-if="store.state.userInfo.is_admin"
ref="userInfoElement"
>
<span class="site-info-item">{{ registerUserCount }} {{onlineUserCount}}线
线 {{ historyMaxOnline }} 线{{ formatRelativeTime(serverUpTime) }}</span>
</div>
</div> </div>
</template> </template>
@ -158,9 +174,11 @@ import { ref, onMounted, computed, watch } from "vue";
import { useStore } from "vuex"; import { useStore } from "vuex";
import { useRouter } from "vue-router"; import { useRouter } from "vue-router";
import { getDownloadRank, getHighQuailty, getTags } from "@/api/post"; import { getDownloadRank, getHighQuailty, getTags } from "@/api/post";
import { getSiteInfo } from "@/api/user";
import { Search } from "@vicons/ionicons5"; import { Search } from "@vicons/ionicons5";
import { ChevronForward } from "@vicons/ionicons5"; import { ChevronForward } from "@vicons/ionicons5";
import { Ref } from 'vue'; import { Ref } from "vue";
import { formatRelativeTime } from "@/utils/formatTime";
const hotTags = ref<Item.TagProps[]>([]); const hotTags = ref<Item.TagProps[]>([]);
const followTags = ref<Item.TagProps[]>([]); const followTags = ref<Item.TagProps[]>([]);
@ -169,6 +187,11 @@ const rankloading = ref(false);
const keyword = ref(""); const keyword = ref("");
const store = useStore(); const store = useStore();
const router = useRouter(); const router = useRouter();
const registerUserCount = ref(0);
const onlineUserCount = ref(0);
const historyMaxOnline = ref(0);
const serverUpTime = ref(0);
const userInfoElement = ref<HTMLElement | null>(null);
const copyrightTop = import.meta.env.VITE_COPYRIGHT_TOP; const copyrightTop = import.meta.env.VITE_COPYRIGHT_TOP;
const copyrightLeft = import.meta.env.VITE_COPYRIGHT_LEFT; const copyrightLeft = import.meta.env.VITE_COPYRIGHT_LEFT;
const copyrightLeftLink = import.meta.env.VITE_COPYRIGHT_LEFT_LINK; const copyrightLeftLink = import.meta.env.VITE_COPYRIGHT_LEFT_LINK;
@ -220,7 +243,12 @@ const rankingTitles: { [key: string]: string } = {
downloadAll: "下载总榜", downloadAll: "下载总榜",
}; };
const rankingTypes = ['highQuality', 'downloadPreWeek', 'downloadPreMonth', 'downloadAll']; const rankingTypes = [
"highQuality",
"downloadPreWeek",
"downloadPreMonth",
"downloadAll",
];
let currentRankingTypeIndex = 0; let currentRankingTypeIndex = 0;
const currentRankingType = ref("highQuality"); const currentRankingType = ref("highQuality");
const NextRankingType = ref("downloadPreWeek"); const NextRankingType = ref("downloadPreWeek");
@ -228,28 +256,37 @@ const NextRankingType = ref("downloadPreWeek");
const toggleRankingType = () => { const toggleRankingType = () => {
currentRankingTypeIndex = (currentRankingTypeIndex + 1) % rankingTypes.length; currentRankingTypeIndex = (currentRankingTypeIndex + 1) % rankingTypes.length;
currentRankingType.value = rankingTypes[currentRankingTypeIndex]; currentRankingType.value = rankingTypes[currentRankingTypeIndex];
NextRankingType.value = rankingTypes[(currentRankingTypeIndex + 1) % rankingTypes.length]; NextRankingType.value =
rankingTypes[(currentRankingTypeIndex + 1) % rankingTypes.length];
}; };
const rankingTypeToFunctionMap: { [key: string]: Ref<Item.RankingDataProps[]> } = { const rankingTypeToFunctionMap: {
[key: string]: Ref<Item.RankingDataProps[]>;
} = {
highQuality: rankingList, highQuality: rankingList,
downloadAll: allDownloadRankingList, downloadAll: allDownloadRankingList,
downloadPreWeek: DownloadPreWeekRankingList, downloadPreWeek: DownloadPreWeekRankingList,
downloadPreMonth: DownloadPreMonthRankingList downloadPreMonth: DownloadPreMonthRankingList,
}; };
const rankingTypeToLoadFunctionMap: { [key: string]: () => void } = { const rankingTypeToLoadFunctionMap: { [key: string]: () => void } = {
downloadAll: () => loadDownloadRankingByType(1), downloadAll: () => loadDownloadRankingByType(1),
downloadPreWeek: () => loadDownloadRankingByType(2), downloadPreWeek: () => loadDownloadRankingByType(2),
downloadPreMonth: () => loadDownloadRankingByType(3) downloadPreMonth: () => loadDownloadRankingByType(3),
}; };
const getCurrentRankingList = computed(() => { const getCurrentRankingList = computed(() => {
const currentType = currentRankingType.value; const currentType = currentRankingType.value;
const rankingValue = rankingTypeToFunctionMap[currentType as keyof typeof rankingTypeToFunctionMap]?.value; const rankingValue =
rankingTypeToFunctionMap[
currentType as keyof typeof rankingTypeToFunctionMap
]?.value;
if (rankingValue !== undefined) { if (rankingValue !== undefined) {
if (rankingValue.length === 0 && rankingTypeToLoadFunctionMap[currentType]) { if (
rankingValue.length === 0 &&
rankingTypeToLoadFunctionMap[currentType]
) {
rankingTypeToLoadFunctionMap[currentType](); rankingTypeToLoadFunctionMap[currentType]();
} }
return rankingValue; return rankingValue;
@ -257,6 +294,19 @@ const getCurrentRankingList = computed(() => {
return []; return [];
}); });
const loadSiteInfo = () => {
getSiteInfo()
.then((res) => {
registerUserCount.value = res.register_user_count;
onlineUserCount.value = res.online_user_count;
historyMaxOnline.value = res.history_max_online;
serverUpTime.value = res.server_up_time;
})
.catch((_err) => {
// do nothing
});
observer.disconnect();
};
const loadHotTags = () => { const loadHotTags = () => {
loading.value = true; loading.value = true;
getTags({ getTags({
@ -270,7 +320,7 @@ const loadHotTags = () => {
showFollowTopics.value = true; showFollowTopics.value = true;
loading.value = false; loading.value = false;
}) })
.catch((err) => { .catch((_err) => {
loading.value = false; loading.value = false;
}); });
}; };
@ -278,17 +328,16 @@ const formatQuoteNum = (num: number) => {
if (num >= 1000) { if (num >= 1000) {
return (num / 1000).toFixed(1) + "k"; return (num / 1000).toFixed(1) + "k";
} }
return num; return num;
}; };
const formatQuoteNumStats = (num: number) => { const formatQuoteNumStats = (num: number) => {
if (num >= 1000) { if (num >= 1000) {
const formattedNum = (num / 1000).toFixed(1); // Get one decimal place const formattedNum = (num / 1000).toFixed(1); // Get one decimal place
return formattedNum + 'k'; return formattedNum + "k";
} else if (num >= 10) { } else if (num >= 10) {
return num.toString(); // Display two digits for two-digit numbers return num.toString(); // Display two digits for two-digit numbers
} else { } else {
return '0' + num.toString(); // Display two digits for one-digit numbers return "0" + num.toString(); // Display two digits for one-digit numbers
} }
}; };
@ -321,9 +370,30 @@ watch(
if (to.refreshTopicFollow !== from.refreshTopicFollow || to.userLogined) { if (to.refreshTopicFollow !== from.refreshTopicFollow || to.userLogined) {
loadHotTags(); loadHotTags();
} }
if (store.state.userInfo.is_admin) {
loadSiteInfo();
}
}
);
const observer = new IntersectionObserver(
(entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadSiteInfo();
}
});
},
{
root: null,
rootMargin: "0px",
threshold: 1,
} }
); );
onMounted(() => { onMounted(() => {
// 不知道为什么 store.state.userInfo.is_admin 在这里就是不起作用f*k所以才用这么一种蹩脚的法子来凑合
if (userInfoElement.value) {
observer.observe(userInfoElement.value);
}
loadHotTags(); loadHotTags();
loadHeighQuailtyRankingList(); loadHeighQuailtyRankingList();
}); });
@ -334,14 +404,12 @@ onMounted(() => {
width: 0; /* 隐藏滚动条的宽度 */ width: 0; /* 隐藏滚动条的宽度 */
height: 0; /* 隐藏滚动条的高度 */ height: 0; /* 隐藏滚动条的高度 */
} }
.rightbar-wrap { .rightbar-wrap {
width: 240px; width: 240px;
position: fixed; position: fixed;
left: calc(50% + var(--content-main) / 2 + 10px); left: calc(50% + var(--content-main) / 2 + 10px);
max-height: calc(100vh); /* 调整高度 */ max-height: calc(100vh); /* 调整高度 */
overflow: auto; overflow: auto;
.search-wrap { .search-wrap {
margin: 12px 0; margin: 12px 0;
} }
@ -374,6 +442,16 @@ onMounted(() => {
margin-top: 10px; margin-top: 10px;
} }
.site-info {
margin-top: 8px;
padding-left: 16px;
padding-right: 16px;
.site-info-item {
font-size: 10px;
opacity: 0.75;
}
}
.copyright-wrap { .copyright-wrap {
.copyright { .copyright {
font-size: 12px; font-size: 12px;

@ -1,4 +1,3 @@
import { stat } from "fs";
import { createStore } from "vuex"; import { createStore } from "vuex";
export default createStore({ export default createStore({
@ -20,6 +19,7 @@ export default createStore({
created_on: 0, created_on: 0,
follows: 0, follows: 0,
followings: 0, followings: 0,
is_admin: false,
}, },
}, },
mutations: { mutations: {
@ -61,6 +61,7 @@ export default createStore({
created_on: 0, created_on: 0,
follows: 0, follows: 0,
followings: 0, followings: 0,
is_admin: false,
}; };
state.userLogined = false; state.userLogined = false;
}, },

@ -74,6 +74,8 @@ declare module NetParams {
status: number; status: number;
} }
interface SiteInfoReq {}
interface FollowUserReq { interface FollowUserReq {
user_id: number; user_id: number;
} }

@ -98,6 +98,13 @@ declare module NetReq {
interface UserChangeStatus {} interface UserChangeStatus {}
interface SiteInfoResp {
register_user_count: number;
online_user_count: number;
history_max_online: number;
server_up_time: number;
}
interface FollowUserResp {} interface FollowUserResp {}
interface UnfollowUserResp {} interface UnfollowUserResp {}

@ -10,6 +10,10 @@ export const formatTime = (time: number) => {
return moment.unix(time).utc(true).format("YYYY-MM-DD HH:mm"); return moment.unix(time).utc(true).format("YYYY-MM-DD HH:mm");
}; };
export const formatHumanTime = (time: number) => {
return moment().from(moment.unix(time));
};
export const formatRelativeTime = (time: number) => { export const formatRelativeTime = (time: number) => {
return moment.unix(time).fromNow(); return moment.unix(time).fromNow();
}; };

Loading…
Cancel
Save