feat: 新增个人页
This commit is contained in:
parent
36e8f251e2
commit
f47a3b2ce4
@ -634,3 +634,86 @@ result := &dto.GetMyExhibitedAssetsResponseDTO{
|
||||
|
||||
response.Success(c, result)
|
||||
}
|
||||
|
||||
// GetInspirationFlow 获取灵感瀑布藏品列表
|
||||
// @Summary 获取灵感瀑布藏品列表
|
||||
// @Description 获取灵感瀑布藏品列表,支持随机展示和双向滚动
|
||||
// @Tags galleries
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param cursor query string false "游标"
|
||||
// @Param direction query string false "滚动方向:right(加载新数据)/ left(加载历史)" default(right)
|
||||
// @Param limit query int false "每页数量" default(10)
|
||||
// @Param type query string false "过滤类型:badge/poster/original/all" default(all)
|
||||
// @Param session_id query string false "会话ID"
|
||||
// @Success 200 {object} response.Response{data=dto.GetInspirationFlowResponseDTO}
|
||||
// @Router /api/v1/inspiration-flow [get]
|
||||
func (ctrl *GalleryController) GetInspirationFlow(c *gin.Context) {
|
||||
// 从上下文获取用户信息(中间件已验证)
|
||||
userID, exists := c.Get("user_id")
|
||||
if !exists {
|
||||
response.Error(c, http.StatusUnauthorized, "用户未认证")
|
||||
return
|
||||
}
|
||||
|
||||
starID, exists := c.Get("star_id")
|
||||
if !exists {
|
||||
response.Error(c, http.StatusUnauthorized, "明星身份未设置")
|
||||
return
|
||||
}
|
||||
|
||||
// 解析请求参数
|
||||
cursor := c.Query("cursor")
|
||||
direction := c.DefaultQuery("direction", "right")
|
||||
limitStr := c.DefaultQuery("limit", "10")
|
||||
limit, _ := strconv.Atoi(limitStr)
|
||||
materialType := c.DefaultQuery("type", "all")
|
||||
sessionID := c.Query("session_id")
|
||||
|
||||
// 创建带有附加信息的context(Triple协议要求attachments值为string类型)
|
||||
ctx := context.WithValue(context.Background(), constant.AttachmentKey, map[string]interface{}{
|
||||
"user_id": strconv.FormatInt(userID.(int64), 10),
|
||||
"star_id": strconv.FormatInt(starID.(int64), 10),
|
||||
})
|
||||
|
||||
// 调用RPC服务
|
||||
resp, err := ctrl.galleryService.GetInspirationFlow(ctx, &pbGallery.GetInspirationFlowRequest{
|
||||
Cursor: cursor,
|
||||
Direction: direction,
|
||||
Limit: int32(limit),
|
||||
Type: materialType,
|
||||
SessionId: sessionID,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Logger.Error("GetInspirationFlow RPC failed",
|
||||
zap.Any("user_id", userID),
|
||||
zap.Any("star_id", starID),
|
||||
zap.Error(err),
|
||||
)
|
||||
_, msg := parseRPCError(err)
|
||||
errorMsg := "获取灵感瀑布列表失败"
|
||||
if msg != "" {
|
||||
errorMsg += "," + msg
|
||||
}
|
||||
response.Error(c, http.StatusInternalServerError, errorMsg)
|
||||
return
|
||||
}
|
||||
|
||||
// 检查业务错误
|
||||
if resp.Base.Code != pbCommon.StatusCode_STATUS_OK {
|
||||
response.Error(c, http.StatusBadRequest, resp.Base.Message)
|
||||
return
|
||||
}
|
||||
|
||||
// 转换为DTO
|
||||
flowDTO := dto.ConvertInspirationFlowData(resp.Data)
|
||||
|
||||
logger.Logger.Info("GetInspirationFlow success",
|
||||
zap.Any("user_id", userID),
|
||||
zap.Any("star_id", starID),
|
||||
zap.Int("count", len(flowDTO.Items)),
|
||||
)
|
||||
|
||||
response.Success(c, flowDTO)
|
||||
}
|
||||
@ -150,3 +150,29 @@ func ConvertPlaceAssetRequest(dto *PlaceAssetRequestDTO) *pbGallery.PlaceAssetRe
|
||||
SlotId: dto.SlotID,
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertInspirationFlowData 转换灵感瀑布数据
|
||||
func ConvertInspirationFlowData(pbData *pbGallery.InspirationFlowData) *GetInspirationFlowResponseDTO {
|
||||
if pbData == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
dto := &GetInspirationFlowResponseDTO{
|
||||
Cursor: pbData.Cursor,
|
||||
HasMore: pbData.HasMore,
|
||||
SessionID: pbData.SessionId,
|
||||
Items: make([]*InspirationFlowItemDTO, 0, len(pbData.Items)),
|
||||
}
|
||||
|
||||
for _, item := range pbData.Items {
|
||||
dto.Items = append(dto.Items, &InspirationFlowItemDTO{
|
||||
AssetID: item.AssetId,
|
||||
Name: item.Name,
|
||||
CoverURL: item.CoverUrl,
|
||||
LikeCount: item.LikeCount,
|
||||
OwnerNickname: item.OwnerNickname,
|
||||
})
|
||||
}
|
||||
|
||||
return dto
|
||||
}
|
||||
|
||||
@ -90,3 +90,22 @@ type GetMyExhibitedAssetsResponseDTO struct {
|
||||
Total int64 `json:"total"` // 总数量
|
||||
HasMore bool `json:"has_more"` // 是否有更多
|
||||
}
|
||||
|
||||
// ========== 灵感瀑布相关 ==========
|
||||
|
||||
// InspirationFlowItemDTO 灵感瀑布藏品项
|
||||
type InspirationFlowItemDTO struct {
|
||||
AssetID int64 `json:"asset_id"` // 资产ID
|
||||
Name string `json:"name"` // 藏品名称
|
||||
CoverURL string `json:"cover_url"` // 封面图URL
|
||||
LikeCount int32 `json:"like_count"` // 点赞数
|
||||
OwnerNickname string `json:"owner_nickname"` // 展出者昵称
|
||||
}
|
||||
|
||||
// GetInspirationFlowResponseDTO 获取灵感瀑布藏品列表响应
|
||||
type GetInspirationFlowResponseDTO struct {
|
||||
Items []*InspirationFlowItemDTO `json:"items"` // 藏品列表
|
||||
Cursor string `json:"cursor"` // 下次请求的游标
|
||||
HasMore bool `json:"has_more"` // 是否有更多
|
||||
SessionID string `json:"session_id"` // 会话ID
|
||||
}
|
||||
|
||||
@ -203,6 +203,13 @@ func SetupRouter(userClient *client.Client, socialClient *client.Client, assetCl
|
||||
galleries.POST("/slots_unlock", galleryCtrl.UnlockSlot) // 解锁/购买新展位
|
||||
}
|
||||
|
||||
// 灵感瀑布相关路由(需要认证)
|
||||
inspirationFlow := v1.Group("/inspiration-flow")
|
||||
inspirationFlow.Use(middleware.AuthMiddleware())
|
||||
{
|
||||
inspirationFlow.GET("", galleryCtrl.GetInspirationFlow) // 获取灵感瀑布藏品列表
|
||||
}
|
||||
|
||||
// 我的展馆路由(需要认证)
|
||||
mygalleries := v1.Group("/mygalleries")
|
||||
mygalleries.Use(middleware.AuthMiddleware())
|
||||
|
||||
@ -0,0 +1,284 @@
|
||||
// Code generated by protoc-gen-triple. DO NOT EDIT.
|
||||
//
|
||||
// Source: gallery.proto
|
||||
package gallery
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
import (
|
||||
"dubbo.apache.org/dubbo-go/v3"
|
||||
"dubbo.apache.org/dubbo-go/v3/client"
|
||||
"dubbo.apache.org/dubbo-go/v3/common"
|
||||
"dubbo.apache.org/dubbo-go/v3/common/constant"
|
||||
"dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
|
||||
"dubbo.apache.org/dubbo-go/v3/server"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file and the Triple package
|
||||
// are compatible. If you get a compiler error that this constant is not defined, this code was
|
||||
// generated with a version of Triple newer than the one compiled into your binary. You can fix the
|
||||
// problem by either regenerating this code with an older version of Triple or updating the Triple
|
||||
// version compiled into your binary.
|
||||
const _ = triple_protocol.IsAtLeastVersion0_1_0
|
||||
|
||||
const (
|
||||
// GalleryServiceName is the fully-qualified name of the GalleryService service.
|
||||
GalleryServiceName = "topfans.gallery.GalleryService"
|
||||
)
|
||||
|
||||
// These constants are the fully-qualified names of the RPCs defined in this package. They're
|
||||
// exposed at runtime as procedure and as the final two segments of the HTTP route.
|
||||
//
|
||||
// Note that these are different from the fully-qualified method names used by
|
||||
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
|
||||
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
||||
// period.
|
||||
const (
|
||||
// GalleryServiceGetMyGalleryProcedure is the fully-qualified name of the GalleryService's GetMyGallery RPC.
|
||||
GalleryServiceGetMyGalleryProcedure = "/topfans.gallery.GalleryService/GetMyGallery"
|
||||
// GalleryServiceGetUserGalleryProcedure is the fully-qualified name of the GalleryService's GetUserGallery RPC.
|
||||
GalleryServiceGetUserGalleryProcedure = "/topfans.gallery.GalleryService/GetUserGallery"
|
||||
// GalleryServicePlaceAssetProcedure is the fully-qualified name of the GalleryService's PlaceAsset RPC.
|
||||
GalleryServicePlaceAssetProcedure = "/topfans.gallery.GalleryService/PlaceAsset"
|
||||
// GalleryServiceUnlockSlotProcedure is the fully-qualified name of the GalleryService's UnlockSlot RPC.
|
||||
GalleryServiceUnlockSlotProcedure = "/topfans.gallery.GalleryService/UnlockSlot"
|
||||
// GalleryServiceRemoveFromSlotProcedure is the fully-qualified name of the GalleryService's RemoveFromSlot RPC.
|
||||
GalleryServiceRemoveFromSlotProcedure = "/topfans.gallery.GalleryService/RemoveFromSlot"
|
||||
// GalleryServiceGetMyExhibitedAssetsProcedure is the fully-qualified name of the GalleryService's GetMyExhibitedAssets RPC.
|
||||
GalleryServiceGetMyExhibitedAssetsProcedure = "/topfans.gallery.GalleryService/GetMyExhibitedAssets"
|
||||
// GalleryServiceGetInspirationFlowProcedure is the fully-qualified name of the GalleryService's GetInspirationFlow RPC.
|
||||
GalleryServiceGetInspirationFlowProcedure = "/topfans.gallery.GalleryService/GetInspirationFlow"
|
||||
)
|
||||
|
||||
var (
|
||||
_ GalleryService = (*GalleryServiceImpl)(nil)
|
||||
)
|
||||
|
||||
// GalleryService is a client for the topfans.gallery.GalleryService service.
|
||||
type GalleryService interface {
|
||||
GetMyGallery(ctx context.Context, req *GetMyGalleryRequest, opts ...client.CallOption) (*GetMyGalleryResponse, error)
|
||||
GetUserGallery(ctx context.Context, req *GetUserGalleryRequest, opts ...client.CallOption) (*GetUserGalleryResponse, error)
|
||||
PlaceAsset(ctx context.Context, req *PlaceAssetRequest, opts ...client.CallOption) (*PlaceAssetResponse, error)
|
||||
UnlockSlot(ctx context.Context, req *UnlockSlotRequest, opts ...client.CallOption) (*UnlockSlotResponse, error)
|
||||
RemoveFromSlot(ctx context.Context, req *RemoveFromSlotRequest, opts ...client.CallOption) (*RemoveFromSlotResponse, error)
|
||||
GetMyExhibitedAssets(ctx context.Context, req *GetMyExhibitedAssetsRequest, opts ...client.CallOption) (*GetMyExhibitedAssetsResponse, error)
|
||||
GetInspirationFlow(ctx context.Context, req *GetInspirationFlowRequest, opts ...client.CallOption) (*GetInspirationFlowResponse, error)
|
||||
}
|
||||
|
||||
// NewGalleryService constructs a client for the gallery.GalleryService service.
|
||||
func NewGalleryService(cli *client.Client, opts ...client.ReferenceOption) (GalleryService, error) {
|
||||
conn, err := cli.DialWithInfo("topfans.gallery.GalleryService", &GalleryService_ClientInfo, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &GalleryServiceImpl{
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SetConsumerGalleryService(srv common.RPCService) {
|
||||
dubbo.SetConsumerServiceWithInfo(srv, &GalleryService_ClientInfo)
|
||||
}
|
||||
|
||||
// GalleryServiceImpl implements GalleryService.
|
||||
type GalleryServiceImpl struct {
|
||||
conn *client.Connection
|
||||
}
|
||||
|
||||
func (c *GalleryServiceImpl) GetMyGallery(ctx context.Context, req *GetMyGalleryRequest, opts ...client.CallOption) (*GetMyGalleryResponse, error) {
|
||||
resp := new(GetMyGalleryResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetMyGallery", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *GalleryServiceImpl) GetUserGallery(ctx context.Context, req *GetUserGalleryRequest, opts ...client.CallOption) (*GetUserGalleryResponse, error) {
|
||||
resp := new(GetUserGalleryResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetUserGallery", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *GalleryServiceImpl) PlaceAsset(ctx context.Context, req *PlaceAssetRequest, opts ...client.CallOption) (*PlaceAssetResponse, error) {
|
||||
resp := new(PlaceAssetResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "PlaceAsset", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *GalleryServiceImpl) UnlockSlot(ctx context.Context, req *UnlockSlotRequest, opts ...client.CallOption) (*UnlockSlotResponse, error) {
|
||||
resp := new(UnlockSlotResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "UnlockSlot", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *GalleryServiceImpl) RemoveFromSlot(ctx context.Context, req *RemoveFromSlotRequest, opts ...client.CallOption) (*RemoveFromSlotResponse, error) {
|
||||
resp := new(RemoveFromSlotResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "RemoveFromSlot", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *GalleryServiceImpl) GetMyExhibitedAssets(ctx context.Context, req *GetMyExhibitedAssetsRequest, opts ...client.CallOption) (*GetMyExhibitedAssetsResponse, error) {
|
||||
resp := new(GetMyExhibitedAssetsResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetMyExhibitedAssets", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *GalleryServiceImpl) GetInspirationFlow(ctx context.Context, req *GetInspirationFlowRequest, opts ...client.CallOption) (*GetInspirationFlowResponse, error) {
|
||||
resp := new(GetInspirationFlowResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetInspirationFlow", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var GalleryService_ClientInfo = client.ClientInfo{
|
||||
InterfaceName: "topfans.gallery.GalleryService",
|
||||
MethodNames: []string{"GetMyGallery", "GetUserGallery", "PlaceAsset", "UnlockSlot", "RemoveFromSlot", "GetMyExhibitedAssets", "GetInspirationFlow"},
|
||||
ConnectionInjectFunc: func(dubboCliRaw interface{}, conn *client.Connection) {
|
||||
dubboCli := dubboCliRaw.(*GalleryServiceImpl)
|
||||
dubboCli.conn = conn
|
||||
},
|
||||
}
|
||||
|
||||
// GalleryServiceHandler is an implementation of the topfans.gallery.GalleryService service.
|
||||
type GalleryServiceHandler interface {
|
||||
GetMyGallery(context.Context, *GetMyGalleryRequest) (*GetMyGalleryResponse, error)
|
||||
GetUserGallery(context.Context, *GetUserGalleryRequest) (*GetUserGalleryResponse, error)
|
||||
PlaceAsset(context.Context, *PlaceAssetRequest) (*PlaceAssetResponse, error)
|
||||
UnlockSlot(context.Context, *UnlockSlotRequest) (*UnlockSlotResponse, error)
|
||||
RemoveFromSlot(context.Context, *RemoveFromSlotRequest) (*RemoveFromSlotResponse, error)
|
||||
GetMyExhibitedAssets(context.Context, *GetMyExhibitedAssetsRequest) (*GetMyExhibitedAssetsResponse, error)
|
||||
GetInspirationFlow(context.Context, *GetInspirationFlowRequest) (*GetInspirationFlowResponse, error)
|
||||
}
|
||||
|
||||
func RegisterGalleryServiceHandler(srv *server.Server, hdlr GalleryServiceHandler, opts ...server.ServiceOption) error {
|
||||
return srv.Register(hdlr, &GalleryService_ServiceInfo, opts...)
|
||||
}
|
||||
|
||||
func SetProviderGalleryService(srv common.RPCService) {
|
||||
dubbo.SetProviderServiceWithInfo(srv, &GalleryService_ServiceInfo)
|
||||
}
|
||||
|
||||
var GalleryService_ServiceInfo = server.ServiceInfo{
|
||||
InterfaceName: "topfans.gallery.GalleryService",
|
||||
ServiceType: (*GalleryServiceHandler)(nil),
|
||||
Methods: []server.MethodInfo{
|
||||
{
|
||||
Name: "GetMyGallery",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetMyGalleryRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetMyGalleryRequest)
|
||||
res, err := handler.(GalleryServiceHandler).GetMyGallery(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetUserGallery",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetUserGalleryRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetUserGalleryRequest)
|
||||
res, err := handler.(GalleryServiceHandler).GetUserGallery(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "PlaceAsset",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(PlaceAssetRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*PlaceAssetRequest)
|
||||
res, err := handler.(GalleryServiceHandler).PlaceAsset(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "UnlockSlot",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(UnlockSlotRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*UnlockSlotRequest)
|
||||
res, err := handler.(GalleryServiceHandler).UnlockSlot(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "RemoveFromSlot",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(RemoveFromSlotRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*RemoveFromSlotRequest)
|
||||
res, err := handler.(GalleryServiceHandler).RemoveFromSlot(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetMyExhibitedAssets",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetMyExhibitedAssetsRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetMyExhibitedAssetsRequest)
|
||||
res, err := handler.(GalleryServiceHandler).GetMyExhibitedAssets(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetInspirationFlow",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetInspirationFlowRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetInspirationFlowRequest)
|
||||
res, err := handler.(GalleryServiceHandler).GetInspirationFlow(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -0,0 +1,554 @@
|
||||
// Code generated by protoc-gen-triple. DO NOT EDIT.
|
||||
//
|
||||
// Source: social.proto
|
||||
package social
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
import (
|
||||
"dubbo.apache.org/dubbo-go/v3"
|
||||
"dubbo.apache.org/dubbo-go/v3/client"
|
||||
"dubbo.apache.org/dubbo-go/v3/common"
|
||||
"dubbo.apache.org/dubbo-go/v3/common/constant"
|
||||
"dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
|
||||
"dubbo.apache.org/dubbo-go/v3/server"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file and the Triple package
|
||||
// are compatible. If you get a compiler error that this constant is not defined, this code was
|
||||
// generated with a version of Triple newer than the one compiled into your binary. You can fix the
|
||||
// problem by either regenerating this code with an older version of Triple or updating the Triple
|
||||
// version compiled into your binary.
|
||||
const _ = triple_protocol.IsAtLeastVersion0_1_0
|
||||
|
||||
const (
|
||||
// SocialServiceName is the fully-qualified name of the SocialService service.
|
||||
SocialServiceName = "topfans.social.SocialService"
|
||||
)
|
||||
|
||||
// These constants are the fully-qualified names of the RPCs defined in this package. They're
|
||||
// exposed at runtime as procedure and as the final two segments of the HTTP route.
|
||||
//
|
||||
// Note that these are different from the fully-qualified method names used by
|
||||
// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to
|
||||
// reflection-formatted method names, remove the leading slash and convert the remaining slash to a
|
||||
// period.
|
||||
const (
|
||||
// SocialServiceSendFriendRequestProcedure is the fully-qualified name of the SocialService's SendFriendRequest RPC.
|
||||
SocialServiceSendFriendRequestProcedure = "/topfans.social.SocialService/SendFriendRequest"
|
||||
// SocialServiceGetFriendRequestsProcedure is the fully-qualified name of the SocialService's GetFriendRequests RPC.
|
||||
SocialServiceGetFriendRequestsProcedure = "/topfans.social.SocialService/GetFriendRequests"
|
||||
// SocialServiceHandleFriendRequestProcedure is the fully-qualified name of the SocialService's HandleFriendRequest RPC.
|
||||
SocialServiceHandleFriendRequestProcedure = "/topfans.social.SocialService/HandleFriendRequest"
|
||||
// SocialServiceGetFriendListProcedure is the fully-qualified name of the SocialService's GetFriendList RPC.
|
||||
SocialServiceGetFriendListProcedure = "/topfans.social.SocialService/GetFriendList"
|
||||
// SocialServiceDeleteFriendProcedure is the fully-qualified name of the SocialService's DeleteFriend RPC.
|
||||
SocialServiceDeleteFriendProcedure = "/topfans.social.SocialService/DeleteFriend"
|
||||
// SocialServiceSetFriendRemarkProcedure is the fully-qualified name of the SocialService's SetFriendRemark RPC.
|
||||
SocialServiceSetFriendRemarkProcedure = "/topfans.social.SocialService/SetFriendRemark"
|
||||
// SocialServiceCheckFriendshipProcedure is the fully-qualified name of the SocialService's CheckFriendship RPC.
|
||||
SocialServiceCheckFriendshipProcedure = "/topfans.social.SocialService/CheckFriendship"
|
||||
// SocialServiceGetFriendCountProcedure is the fully-qualified name of the SocialService's GetFriendCount RPC.
|
||||
SocialServiceGetFriendCountProcedure = "/topfans.social.SocialService/GetFriendCount"
|
||||
// SocialServiceSearchUserForFriendProcedure is the fully-qualified name of the SocialService's SearchUserForFriend RPC.
|
||||
SocialServiceSearchUserForFriendProcedure = "/topfans.social.SocialService/SearchUserForFriend"
|
||||
// SocialServiceGetRandomUsersProcedure is the fully-qualified name of the SocialService's GetRandomUsers RPC.
|
||||
SocialServiceGetRandomUsersProcedure = "/topfans.social.SocialService/GetRandomUsers"
|
||||
// SocialServiceGetUsersPagedProcedure is the fully-qualified name of the SocialService's GetUsersPaged RPC.
|
||||
SocialServiceGetUsersPagedProcedure = "/topfans.social.SocialService/GetUsersPaged"
|
||||
// SocialServiceLikeAssetProcedure is the fully-qualified name of the SocialService's LikeAsset RPC.
|
||||
SocialServiceLikeAssetProcedure = "/topfans.social.SocialService/LikeAsset"
|
||||
// SocialServiceUnlikeAssetProcedure is the fully-qualified name of the SocialService's UnlikeAsset RPC.
|
||||
SocialServiceUnlikeAssetProcedure = "/topfans.social.SocialService/UnlikeAsset"
|
||||
// SocialServiceCheckAssetLikeProcedure is the fully-qualified name of the SocialService's CheckAssetLike RPC.
|
||||
SocialServiceCheckAssetLikeProcedure = "/topfans.social.SocialService/CheckAssetLike"
|
||||
// SocialServiceGetMyLikedAssetsProcedure is the fully-qualified name of the SocialService's GetMyLikedAssets RPC.
|
||||
SocialServiceGetMyLikedAssetsProcedure = "/topfans.social.SocialService/GetMyLikedAssets"
|
||||
// SocialServiceGetMyTodayLikedAssetsProcedure is the fully-qualified name of the SocialService's GetMyTodayLikedAssets RPC.
|
||||
SocialServiceGetMyTodayLikedAssetsProcedure = "/topfans.social.SocialService/GetMyTodayLikedAssets"
|
||||
// SocialServiceGetMyWeekLikedAssetsProcedure is the fully-qualified name of the SocialService's GetMyWeekLikedAssets RPC.
|
||||
SocialServiceGetMyWeekLikedAssetsProcedure = "/topfans.social.SocialService/GetMyWeekLikedAssets"
|
||||
)
|
||||
|
||||
var (
|
||||
_ SocialService = (*SocialServiceImpl)(nil)
|
||||
)
|
||||
|
||||
// SocialService is a client for the topfans.social.SocialService service.
|
||||
type SocialService interface {
|
||||
SendFriendRequest(ctx context.Context, req *SendFriendRequestRequest, opts ...client.CallOption) (*SendFriendRequestResponse, error)
|
||||
GetFriendRequests(ctx context.Context, req *GetFriendRequestsRequest, opts ...client.CallOption) (*GetFriendRequestsResponse, error)
|
||||
HandleFriendRequest(ctx context.Context, req *HandleFriendRequestRequest, opts ...client.CallOption) (*HandleFriendRequestResponse, error)
|
||||
GetFriendList(ctx context.Context, req *GetFriendListRequest, opts ...client.CallOption) (*GetFriendListResponse, error)
|
||||
DeleteFriend(ctx context.Context, req *DeleteFriendRequest, opts ...client.CallOption) (*DeleteFriendResponse, error)
|
||||
SetFriendRemark(ctx context.Context, req *SetFriendRemarkRequest, opts ...client.CallOption) (*SetFriendRemarkResponse, error)
|
||||
CheckFriendship(ctx context.Context, req *CheckFriendshipRequest, opts ...client.CallOption) (*CheckFriendshipResponse, error)
|
||||
GetFriendCount(ctx context.Context, req *GetFriendCountRequest, opts ...client.CallOption) (*GetFriendCountResponse, error)
|
||||
SearchUserForFriend(ctx context.Context, req *SearchUserForFriendRequest, opts ...client.CallOption) (*SearchUserForFriendResponse, error)
|
||||
GetRandomUsers(ctx context.Context, req *GetRandomUsersRequest, opts ...client.CallOption) (*GetRandomUsersResponse, error)
|
||||
GetUsersPaged(ctx context.Context, req *GetUsersPagedRequest, opts ...client.CallOption) (*GetUsersPagedResponse, error)
|
||||
LikeAsset(ctx context.Context, req *LikeAssetRequest, opts ...client.CallOption) (*LikeAssetResponse, error)
|
||||
UnlikeAsset(ctx context.Context, req *UnlikeAssetRequest, opts ...client.CallOption) (*UnlikeAssetResponse, error)
|
||||
CheckAssetLike(ctx context.Context, req *CheckAssetLikeRequest, opts ...client.CallOption) (*CheckAssetLikeResponse, error)
|
||||
GetMyLikedAssets(ctx context.Context, req *GetMyLikedAssetsRequest, opts ...client.CallOption) (*GetMyLikedAssetsResponse, error)
|
||||
GetMyTodayLikedAssets(ctx context.Context, req *GetMyTodayLikedAssetsRequest, opts ...client.CallOption) (*GetMyTodayLikedAssetsResponse, error)
|
||||
GetMyWeekLikedAssets(ctx context.Context, req *GetMyWeekLikedAssetsRequest, opts ...client.CallOption) (*GetMyWeekLikedAssetsResponse, error)
|
||||
}
|
||||
|
||||
// NewSocialService constructs a client for the social.SocialService service.
|
||||
func NewSocialService(cli *client.Client, opts ...client.ReferenceOption) (SocialService, error) {
|
||||
conn, err := cli.DialWithInfo("topfans.social.SocialService", &SocialService_ClientInfo, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SocialServiceImpl{
|
||||
conn: conn,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func SetConsumerSocialService(srv common.RPCService) {
|
||||
dubbo.SetConsumerServiceWithInfo(srv, &SocialService_ClientInfo)
|
||||
}
|
||||
|
||||
// SocialServiceImpl implements SocialService.
|
||||
type SocialServiceImpl struct {
|
||||
conn *client.Connection
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) SendFriendRequest(ctx context.Context, req *SendFriendRequestRequest, opts ...client.CallOption) (*SendFriendRequestResponse, error) {
|
||||
resp := new(SendFriendRequestResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "SendFriendRequest", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) GetFriendRequests(ctx context.Context, req *GetFriendRequestsRequest, opts ...client.CallOption) (*GetFriendRequestsResponse, error) {
|
||||
resp := new(GetFriendRequestsResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetFriendRequests", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) HandleFriendRequest(ctx context.Context, req *HandleFriendRequestRequest, opts ...client.CallOption) (*HandleFriendRequestResponse, error) {
|
||||
resp := new(HandleFriendRequestResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "HandleFriendRequest", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) GetFriendList(ctx context.Context, req *GetFriendListRequest, opts ...client.CallOption) (*GetFriendListResponse, error) {
|
||||
resp := new(GetFriendListResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetFriendList", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) DeleteFriend(ctx context.Context, req *DeleteFriendRequest, opts ...client.CallOption) (*DeleteFriendResponse, error) {
|
||||
resp := new(DeleteFriendResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "DeleteFriend", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) SetFriendRemark(ctx context.Context, req *SetFriendRemarkRequest, opts ...client.CallOption) (*SetFriendRemarkResponse, error) {
|
||||
resp := new(SetFriendRemarkResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "SetFriendRemark", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) CheckFriendship(ctx context.Context, req *CheckFriendshipRequest, opts ...client.CallOption) (*CheckFriendshipResponse, error) {
|
||||
resp := new(CheckFriendshipResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "CheckFriendship", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) GetFriendCount(ctx context.Context, req *GetFriendCountRequest, opts ...client.CallOption) (*GetFriendCountResponse, error) {
|
||||
resp := new(GetFriendCountResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetFriendCount", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) SearchUserForFriend(ctx context.Context, req *SearchUserForFriendRequest, opts ...client.CallOption) (*SearchUserForFriendResponse, error) {
|
||||
resp := new(SearchUserForFriendResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "SearchUserForFriend", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) GetRandomUsers(ctx context.Context, req *GetRandomUsersRequest, opts ...client.CallOption) (*GetRandomUsersResponse, error) {
|
||||
resp := new(GetRandomUsersResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetRandomUsers", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) GetUsersPaged(ctx context.Context, req *GetUsersPagedRequest, opts ...client.CallOption) (*GetUsersPagedResponse, error) {
|
||||
resp := new(GetUsersPagedResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetUsersPaged", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) LikeAsset(ctx context.Context, req *LikeAssetRequest, opts ...client.CallOption) (*LikeAssetResponse, error) {
|
||||
resp := new(LikeAssetResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "LikeAsset", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) UnlikeAsset(ctx context.Context, req *UnlikeAssetRequest, opts ...client.CallOption) (*UnlikeAssetResponse, error) {
|
||||
resp := new(UnlikeAssetResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "UnlikeAsset", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) CheckAssetLike(ctx context.Context, req *CheckAssetLikeRequest, opts ...client.CallOption) (*CheckAssetLikeResponse, error) {
|
||||
resp := new(CheckAssetLikeResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "CheckAssetLike", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) GetMyLikedAssets(ctx context.Context, req *GetMyLikedAssetsRequest, opts ...client.CallOption) (*GetMyLikedAssetsResponse, error) {
|
||||
resp := new(GetMyLikedAssetsResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetMyLikedAssets", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) GetMyTodayLikedAssets(ctx context.Context, req *GetMyTodayLikedAssetsRequest, opts ...client.CallOption) (*GetMyTodayLikedAssetsResponse, error) {
|
||||
resp := new(GetMyTodayLikedAssetsResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetMyTodayLikedAssets", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *SocialServiceImpl) GetMyWeekLikedAssets(ctx context.Context, req *GetMyWeekLikedAssetsRequest, opts ...client.CallOption) (*GetMyWeekLikedAssetsResponse, error) {
|
||||
resp := new(GetMyWeekLikedAssetsResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetMyWeekLikedAssets", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var SocialService_ClientInfo = client.ClientInfo{
|
||||
InterfaceName: "topfans.social.SocialService",
|
||||
MethodNames: []string{"SendFriendRequest", "GetFriendRequests", "HandleFriendRequest", "GetFriendList", "DeleteFriend", "SetFriendRemark", "CheckFriendship", "GetFriendCount", "SearchUserForFriend", "GetRandomUsers", "GetUsersPaged", "LikeAsset", "UnlikeAsset", "CheckAssetLike", "GetMyLikedAssets", "GetMyTodayLikedAssets", "GetMyWeekLikedAssets"},
|
||||
ConnectionInjectFunc: func(dubboCliRaw interface{}, conn *client.Connection) {
|
||||
dubboCli := dubboCliRaw.(*SocialServiceImpl)
|
||||
dubboCli.conn = conn
|
||||
},
|
||||
}
|
||||
|
||||
// SocialServiceHandler is an implementation of the topfans.social.SocialService service.
|
||||
type SocialServiceHandler interface {
|
||||
SendFriendRequest(context.Context, *SendFriendRequestRequest) (*SendFriendRequestResponse, error)
|
||||
GetFriendRequests(context.Context, *GetFriendRequestsRequest) (*GetFriendRequestsResponse, error)
|
||||
HandleFriendRequest(context.Context, *HandleFriendRequestRequest) (*HandleFriendRequestResponse, error)
|
||||
GetFriendList(context.Context, *GetFriendListRequest) (*GetFriendListResponse, error)
|
||||
DeleteFriend(context.Context, *DeleteFriendRequest) (*DeleteFriendResponse, error)
|
||||
SetFriendRemark(context.Context, *SetFriendRemarkRequest) (*SetFriendRemarkResponse, error)
|
||||
CheckFriendship(context.Context, *CheckFriendshipRequest) (*CheckFriendshipResponse, error)
|
||||
GetFriendCount(context.Context, *GetFriendCountRequest) (*GetFriendCountResponse, error)
|
||||
SearchUserForFriend(context.Context, *SearchUserForFriendRequest) (*SearchUserForFriendResponse, error)
|
||||
GetRandomUsers(context.Context, *GetRandomUsersRequest) (*GetRandomUsersResponse, error)
|
||||
GetUsersPaged(context.Context, *GetUsersPagedRequest) (*GetUsersPagedResponse, error)
|
||||
LikeAsset(context.Context, *LikeAssetRequest) (*LikeAssetResponse, error)
|
||||
UnlikeAsset(context.Context, *UnlikeAssetRequest) (*UnlikeAssetResponse, error)
|
||||
CheckAssetLike(context.Context, *CheckAssetLikeRequest) (*CheckAssetLikeResponse, error)
|
||||
GetMyLikedAssets(context.Context, *GetMyLikedAssetsRequest) (*GetMyLikedAssetsResponse, error)
|
||||
GetMyTodayLikedAssets(context.Context, *GetMyTodayLikedAssetsRequest) (*GetMyTodayLikedAssetsResponse, error)
|
||||
GetMyWeekLikedAssets(context.Context, *GetMyWeekLikedAssetsRequest) (*GetMyWeekLikedAssetsResponse, error)
|
||||
}
|
||||
|
||||
func RegisterSocialServiceHandler(srv *server.Server, hdlr SocialServiceHandler, opts ...server.ServiceOption) error {
|
||||
return srv.Register(hdlr, &SocialService_ServiceInfo, opts...)
|
||||
}
|
||||
|
||||
func SetProviderSocialService(srv common.RPCService) {
|
||||
dubbo.SetProviderServiceWithInfo(srv, &SocialService_ServiceInfo)
|
||||
}
|
||||
|
||||
var SocialService_ServiceInfo = server.ServiceInfo{
|
||||
InterfaceName: "topfans.social.SocialService",
|
||||
ServiceType: (*SocialServiceHandler)(nil),
|
||||
Methods: []server.MethodInfo{
|
||||
{
|
||||
Name: "SendFriendRequest",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(SendFriendRequestRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*SendFriendRequestRequest)
|
||||
res, err := handler.(SocialServiceHandler).SendFriendRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetFriendRequests",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetFriendRequestsRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetFriendRequestsRequest)
|
||||
res, err := handler.(SocialServiceHandler).GetFriendRequests(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "HandleFriendRequest",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(HandleFriendRequestRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*HandleFriendRequestRequest)
|
||||
res, err := handler.(SocialServiceHandler).HandleFriendRequest(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetFriendList",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetFriendListRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetFriendListRequest)
|
||||
res, err := handler.(SocialServiceHandler).GetFriendList(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "DeleteFriend",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(DeleteFriendRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*DeleteFriendRequest)
|
||||
res, err := handler.(SocialServiceHandler).DeleteFriend(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "SetFriendRemark",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(SetFriendRemarkRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*SetFriendRemarkRequest)
|
||||
res, err := handler.(SocialServiceHandler).SetFriendRemark(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CheckFriendship",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(CheckFriendshipRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*CheckFriendshipRequest)
|
||||
res, err := handler.(SocialServiceHandler).CheckFriendship(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetFriendCount",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetFriendCountRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetFriendCountRequest)
|
||||
res, err := handler.(SocialServiceHandler).GetFriendCount(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "SearchUserForFriend",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(SearchUserForFriendRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*SearchUserForFriendRequest)
|
||||
res, err := handler.(SocialServiceHandler).SearchUserForFriend(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetRandomUsers",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetRandomUsersRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetRandomUsersRequest)
|
||||
res, err := handler.(SocialServiceHandler).GetRandomUsers(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetUsersPaged",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetUsersPagedRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetUsersPagedRequest)
|
||||
res, err := handler.(SocialServiceHandler).GetUsersPaged(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "LikeAsset",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(LikeAssetRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*LikeAssetRequest)
|
||||
res, err := handler.(SocialServiceHandler).LikeAsset(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "UnlikeAsset",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(UnlikeAssetRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*UnlikeAssetRequest)
|
||||
res, err := handler.(SocialServiceHandler).UnlikeAsset(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CheckAssetLike",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(CheckAssetLikeRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*CheckAssetLikeRequest)
|
||||
res, err := handler.(SocialServiceHandler).CheckAssetLike(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetMyLikedAssets",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetMyLikedAssetsRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetMyLikedAssetsRequest)
|
||||
res, err := handler.(SocialServiceHandler).GetMyLikedAssets(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetMyTodayLikedAssets",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetMyTodayLikedAssetsRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetMyTodayLikedAssetsRequest)
|
||||
res, err := handler.(SocialServiceHandler).GetMyTodayLikedAssets(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetMyWeekLikedAssets",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetMyWeekLikedAssetsRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetMyWeekLikedAssetsRequest)
|
||||
res, err := handler.(SocialServiceHandler).GetMyWeekLikedAssets(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -1215,6 +1215,282 @@ func (x *ExhibitedAssetItem) GetEarnings() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 获取灵感瀑布藏品列表请求
|
||||
type GetInspirationFlowRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Cursor string `protobuf:"bytes,1,opt,name=cursor,proto3" json:"cursor,omitempty"` // 游标(首次请求为空)
|
||||
Direction string `protobuf:"bytes,2,opt,name=direction,proto3" json:"direction,omitempty"` // 滚动方向:right(加载新数据)/ left(加载历史)
|
||||
Limit int32 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` // 每页数量(默认10,最大20)
|
||||
Type string `protobuf:"bytes,4,opt,name=type,proto3" json:"type,omitempty"` // 过滤类型:badge/poster/original/all(默认all)
|
||||
SessionId string `protobuf:"bytes,5,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // 会话ID(首次请求时为空,后端返回新的session_id)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowRequest) Reset() {
|
||||
*x = GetInspirationFlowRequest{}
|
||||
mi := &file_gallery_proto_msgTypes[20]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetInspirationFlowRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetInspirationFlowRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[20]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetInspirationFlowRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetInspirationFlowRequest) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{20}
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowRequest) GetCursor() string {
|
||||
if x != nil {
|
||||
return x.Cursor
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowRequest) GetDirection() string {
|
||||
if x != nil {
|
||||
return x.Direction
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowRequest) GetLimit() int32 {
|
||||
if x != nil {
|
||||
return x.Limit
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowRequest) GetType() string {
|
||||
if x != nil {
|
||||
return x.Type
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowRequest) GetSessionId() string {
|
||||
if x != nil {
|
||||
return x.SessionId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 获取灵感瀑布藏品列表响应
|
||||
type GetInspirationFlowResponse struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Base *common.BaseResponse `protobuf:"bytes,1,opt,name=base,proto3" json:"base,omitempty"`
|
||||
Data *InspirationFlowData `protobuf:"bytes,2,opt,name=data,proto3" json:"data,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowResponse) Reset() {
|
||||
*x = GetInspirationFlowResponse{}
|
||||
mi := &file_gallery_proto_msgTypes[21]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetInspirationFlowResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetInspirationFlowResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[21]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetInspirationFlowResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetInspirationFlowResponse) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{21}
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowResponse) GetBase() *common.BaseResponse {
|
||||
if x != nil {
|
||||
return x.Base
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *GetInspirationFlowResponse) GetData() *InspirationFlowData {
|
||||
if x != nil {
|
||||
return x.Data
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 灵感瀑布数据
|
||||
type InspirationFlowData struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Items []*InspirationFlowItem `protobuf:"bytes,1,rep,name=items,proto3" json:"items,omitempty"` // 藏品列表
|
||||
Cursor string `protobuf:"bytes,2,opt,name=cursor,proto3" json:"cursor,omitempty"` // 下次请求的游标
|
||||
HasMore bool `protobuf:"varint,3,opt,name=has_more,json=hasMore,proto3" json:"has_more,omitempty"` // 是否有更多
|
||||
SessionId string `protobuf:"bytes,4,opt,name=session_id,json=sessionId,proto3" json:"session_id,omitempty"` // 会话ID(首次请求时返回)
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *InspirationFlowData) Reset() {
|
||||
*x = InspirationFlowData{}
|
||||
mi := &file_gallery_proto_msgTypes[22]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *InspirationFlowData) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*InspirationFlowData) ProtoMessage() {}
|
||||
|
||||
func (x *InspirationFlowData) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[22]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use InspirationFlowData.ProtoReflect.Descriptor instead.
|
||||
func (*InspirationFlowData) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{22}
|
||||
}
|
||||
|
||||
func (x *InspirationFlowData) GetItems() []*InspirationFlowItem {
|
||||
if x != nil {
|
||||
return x.Items
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *InspirationFlowData) GetCursor() string {
|
||||
if x != nil {
|
||||
return x.Cursor
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *InspirationFlowData) GetHasMore() bool {
|
||||
if x != nil {
|
||||
return x.HasMore
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *InspirationFlowData) GetSessionId() string {
|
||||
if x != nil {
|
||||
return x.SessionId
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 灵感瀑布藏品项
|
||||
type InspirationFlowItem struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
AssetId int64 `protobuf:"varint,1,opt,name=asset_id,json=assetId,proto3" json:"asset_id,omitempty"` // 资产ID
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"` // 藏品名称
|
||||
CoverUrl string `protobuf:"bytes,3,opt,name=cover_url,json=coverUrl,proto3" json:"cover_url,omitempty"` // 封面图URL
|
||||
LikeCount int32 `protobuf:"varint,4,opt,name=like_count,json=likeCount,proto3" json:"like_count,omitempty"` // 点赞数
|
||||
OwnerNickname string `protobuf:"bytes,5,opt,name=owner_nickname,json=ownerNickname,proto3" json:"owner_nickname,omitempty"` // 展出者昵称
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *InspirationFlowItem) Reset() {
|
||||
*x = InspirationFlowItem{}
|
||||
mi := &file_gallery_proto_msgTypes[23]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *InspirationFlowItem) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*InspirationFlowItem) ProtoMessage() {}
|
||||
|
||||
func (x *InspirationFlowItem) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_gallery_proto_msgTypes[23]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use InspirationFlowItem.ProtoReflect.Descriptor instead.
|
||||
func (*InspirationFlowItem) Descriptor() ([]byte, []int) {
|
||||
return file_gallery_proto_rawDescGZIP(), []int{23}
|
||||
}
|
||||
|
||||
func (x *InspirationFlowItem) GetAssetId() int64 {
|
||||
if x != nil {
|
||||
return x.AssetId
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *InspirationFlowItem) GetName() string {
|
||||
if x != nil {
|
||||
return x.Name
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *InspirationFlowItem) GetCoverUrl() string {
|
||||
if x != nil {
|
||||
return x.CoverUrl
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *InspirationFlowItem) GetLikeCount() int32 {
|
||||
if x != nil {
|
||||
return x.LikeCount
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *InspirationFlowItem) GetOwnerNickname() string {
|
||||
if x != nil {
|
||||
return x.OwnerNickname
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_gallery_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_gallery_proto_rawDesc = "" +
|
||||
@ -1310,7 +1586,30 @@ const file_gallery_proto_rawDesc = "" +
|
||||
"like_count\x18\x04 \x01(\x05R\tlikeCount\x12!\n" +
|
||||
"\fexhibited_at\x18\x05 \x01(\x03R\vexhibitedAt\x12\x1b\n" +
|
||||
"\texpire_at\x18\x06 \x01(\x03R\bexpireAt\x12\x1a\n" +
|
||||
"\bearnings\x18\a \x01(\x03R\bearnings2\xb4\x06\n" +
|
||||
"\bearnings\x18\a \x01(\x03R\bearnings\"\x9a\x01\n" +
|
||||
"\x19GetInspirationFlowRequest\x12\x16\n" +
|
||||
"\x06cursor\x18\x01 \x01(\tR\x06cursor\x12\x1c\n" +
|
||||
"\tdirection\x18\x02 \x01(\tR\tdirection\x12\x14\n" +
|
||||
"\x05limit\x18\x03 \x01(\x05R\x05limit\x12\x12\n" +
|
||||
"\x04type\x18\x04 \x01(\tR\x04type\x12\x1d\n" +
|
||||
"\n" +
|
||||
"session_id\x18\x05 \x01(\tR\tsessionId\"\x88\x01\n" +
|
||||
"\x1aGetInspirationFlowResponse\x120\n" +
|
||||
"\x04base\x18\x01 \x01(\v2\x1c.topfans.common.BaseResponseR\x04base\x128\n" +
|
||||
"\x04data\x18\x02 \x01(\v2$.topfans.gallery.InspirationFlowDataR\x04data\"\xa3\x01\n" +
|
||||
"\x13InspirationFlowData\x12:\n" +
|
||||
"\x05items\x18\x01 \x03(\v2$.topfans.gallery.InspirationFlowItemR\x05items\x12\x16\n" +
|
||||
"\x06cursor\x18\x02 \x01(\tR\x06cursor\x12\x19\n" +
|
||||
"\bhas_more\x18\x03 \x01(\bR\ahasMore\x12\x1d\n" +
|
||||
"\n" +
|
||||
"session_id\x18\x04 \x01(\tR\tsessionId\"\xa7\x01\n" +
|
||||
"\x13InspirationFlowItem\x12\x19\n" +
|
||||
"\basset_id\x18\x01 \x01(\x03R\aassetId\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12\x1b\n" +
|
||||
"\tcover_url\x18\x03 \x01(\tR\bcoverUrl\x12\x1d\n" +
|
||||
"\n" +
|
||||
"like_count\x18\x04 \x01(\x05R\tlikeCount\x12%\n" +
|
||||
"\x0eowner_nickname\x18\x05 \x01(\tR\rownerNickname2\xc6\a\n" +
|
||||
"\x0eGalleryService\x12u\n" +
|
||||
"\fGetMyGallery\x12$.topfans.gallery.GetMyGalleryRequest\x1a%.topfans.gallery.GetMyGalleryResponse\"\x18\x82\xd3\xe4\x93\x02\x12\x12\x10/api/mygalleries\x12\x86\x01\n" +
|
||||
"\x0eGetUserGallery\x12&.topfans.gallery.GetUserGalleryRequest\x1a'.topfans.gallery.GetUserGalleryResponse\"#\x82\xd3\xe4\x93\x02\x1d\x12\x1b/api/galleries/{target_uid}\x12v\n" +
|
||||
@ -1319,7 +1618,8 @@ const file_gallery_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"UnlockSlot\x12\".topfans.gallery.UnlockSlotRequest\x1a#.topfans.gallery.UnlockSlotResponse\"&\x82\xd3\xe4\x93\x02 :\x01*\"\x1b/api/galleries/slots_unlock\x12\x8f\x01\n" +
|
||||
"\x0eRemoveFromSlot\x12&.topfans.gallery.RemoveFromSlotRequest\x1a'.topfans.gallery.RemoveFromSlotResponse\",\x82\xd3\xe4\x93\x02&*$/api/galleries/slots/{slot_id}/asset\x12\x98\x01\n" +
|
||||
"\x14GetMyExhibitedAssets\x12,.topfans.gallery.GetMyExhibitedAssetsRequest\x1a-.topfans.gallery.GetMyExhibitedAssetsResponse\"#\x82\xd3\xe4\x93\x02\x1d\x12\x1b/api/v1/me/exhibited-assetsB6Z4github.com/topfans/backend/pkg/proto/gallery;galleryb\x06proto3"
|
||||
"\x14GetMyExhibitedAssets\x12,.topfans.gallery.GetMyExhibitedAssetsRequest\x1a-.topfans.gallery.GetMyExhibitedAssetsResponse\"#\x82\xd3\xe4\x93\x02\x1d\x12\x1b/api/v1/me/exhibited-assets\x12\x8f\x01\n" +
|
||||
"\x12GetInspirationFlow\x12*.topfans.gallery.GetInspirationFlowRequest\x1a+.topfans.gallery.GetInspirationFlowResponse\" \x82\xd3\xe4\x93\x02\x1a\x12\x18/api/v1/inspiration-flowB6Z4github.com/topfans/backend/pkg/proto/gallery;galleryb\x06proto3"
|
||||
|
||||
var (
|
||||
file_gallery_proto_rawDescOnce sync.Once
|
||||
@ -1333,7 +1633,7 @@ func file_gallery_proto_rawDescGZIP() []byte {
|
||||
return file_gallery_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_gallery_proto_msgTypes = make([]protoimpl.MessageInfo, 20)
|
||||
var file_gallery_proto_msgTypes = make([]protoimpl.MessageInfo, 24)
|
||||
var file_gallery_proto_goTypes = []any{
|
||||
(*GetMyGalleryRequest)(nil), // 0: topfans.gallery.GetMyGalleryRequest
|
||||
(*GetMyGalleryResponse)(nil), // 1: topfans.gallery.GetMyGalleryResponse
|
||||
@ -1355,41 +1655,50 @@ var file_gallery_proto_goTypes = []any{
|
||||
(*GetMyExhibitedAssetsResponse)(nil), // 17: topfans.gallery.GetMyExhibitedAssetsResponse
|
||||
(*ExhibitedAssetsData)(nil), // 18: topfans.gallery.ExhibitedAssetsData
|
||||
(*ExhibitedAssetItem)(nil), // 19: topfans.gallery.ExhibitedAssetItem
|
||||
(*common.BaseResponse)(nil), // 20: topfans.common.BaseResponse
|
||||
(*GetInspirationFlowRequest)(nil), // 20: topfans.gallery.GetInspirationFlowRequest
|
||||
(*GetInspirationFlowResponse)(nil), // 21: topfans.gallery.GetInspirationFlowResponse
|
||||
(*InspirationFlowData)(nil), // 22: topfans.gallery.InspirationFlowData
|
||||
(*InspirationFlowItem)(nil), // 23: topfans.gallery.InspirationFlowItem
|
||||
(*common.BaseResponse)(nil), // 24: topfans.common.BaseResponse
|
||||
}
|
||||
var file_gallery_proto_depIdxs = []int32{
|
||||
20, // 0: topfans.gallery.GetMyGalleryResponse.base:type_name -> topfans.common.BaseResponse
|
||||
24, // 0: topfans.gallery.GetMyGalleryResponse.base:type_name -> topfans.common.BaseResponse
|
||||
8, // 1: topfans.gallery.GetMyGalleryResponse.data:type_name -> topfans.gallery.GalleryData
|
||||
20, // 2: topfans.gallery.GetUserGalleryResponse.base:type_name -> topfans.common.BaseResponse
|
||||
24, // 2: topfans.gallery.GetUserGalleryResponse.base:type_name -> topfans.common.BaseResponse
|
||||
8, // 3: topfans.gallery.GetUserGalleryResponse.data:type_name -> topfans.gallery.GalleryData
|
||||
20, // 4: topfans.gallery.PlaceAssetResponse.base:type_name -> topfans.common.BaseResponse
|
||||
24, // 4: topfans.gallery.PlaceAssetResponse.base:type_name -> topfans.common.BaseResponse
|
||||
12, // 5: topfans.gallery.PlaceAssetResponse.data:type_name -> topfans.gallery.PlaceAssetData
|
||||
20, // 6: topfans.gallery.UnlockSlotResponse.base:type_name -> topfans.common.BaseResponse
|
||||
24, // 6: topfans.gallery.UnlockSlotResponse.base:type_name -> topfans.common.BaseResponse
|
||||
13, // 7: topfans.gallery.UnlockSlotResponse.data:type_name -> topfans.gallery.UnlockSlotData
|
||||
9, // 8: topfans.gallery.GalleryData.slots:type_name -> topfans.gallery.SlotInfo
|
||||
10, // 9: topfans.gallery.SlotInfo.asset:type_name -> topfans.gallery.AssetInfo
|
||||
11, // 10: topfans.gallery.SlotInfo.unlock_condition:type_name -> topfans.gallery.UnlockCondition
|
||||
20, // 11: topfans.gallery.RemoveFromSlotResponse.base:type_name -> topfans.common.BaseResponse
|
||||
20, // 12: topfans.gallery.GetMyExhibitedAssetsResponse.base:type_name -> topfans.common.BaseResponse
|
||||
24, // 11: topfans.gallery.RemoveFromSlotResponse.base:type_name -> topfans.common.BaseResponse
|
||||
24, // 12: topfans.gallery.GetMyExhibitedAssetsResponse.base:type_name -> topfans.common.BaseResponse
|
||||
18, // 13: topfans.gallery.GetMyExhibitedAssetsResponse.data:type_name -> topfans.gallery.ExhibitedAssetsData
|
||||
19, // 14: topfans.gallery.ExhibitedAssetsData.items:type_name -> topfans.gallery.ExhibitedAssetItem
|
||||
0, // 15: topfans.gallery.GalleryService.GetMyGallery:input_type -> topfans.gallery.GetMyGalleryRequest
|
||||
2, // 16: topfans.gallery.GalleryService.GetUserGallery:input_type -> topfans.gallery.GetUserGalleryRequest
|
||||
4, // 17: topfans.gallery.GalleryService.PlaceAsset:input_type -> topfans.gallery.PlaceAssetRequest
|
||||
6, // 18: topfans.gallery.GalleryService.UnlockSlot:input_type -> topfans.gallery.UnlockSlotRequest
|
||||
14, // 19: topfans.gallery.GalleryService.RemoveFromSlot:input_type -> topfans.gallery.RemoveFromSlotRequest
|
||||
16, // 20: topfans.gallery.GalleryService.GetMyExhibitedAssets:input_type -> topfans.gallery.GetMyExhibitedAssetsRequest
|
||||
1, // 21: topfans.gallery.GalleryService.GetMyGallery:output_type -> topfans.gallery.GetMyGalleryResponse
|
||||
3, // 22: topfans.gallery.GalleryService.GetUserGallery:output_type -> topfans.gallery.GetUserGalleryResponse
|
||||
5, // 23: topfans.gallery.GalleryService.PlaceAsset:output_type -> topfans.gallery.PlaceAssetResponse
|
||||
7, // 24: topfans.gallery.GalleryService.UnlockSlot:output_type -> topfans.gallery.UnlockSlotResponse
|
||||
15, // 25: topfans.gallery.GalleryService.RemoveFromSlot:output_type -> topfans.gallery.RemoveFromSlotResponse
|
||||
17, // 26: topfans.gallery.GalleryService.GetMyExhibitedAssets:output_type -> topfans.gallery.GetMyExhibitedAssetsResponse
|
||||
21, // [21:27] is the sub-list for method output_type
|
||||
15, // [15:21] is the sub-list for method input_type
|
||||
15, // [15:15] is the sub-list for extension type_name
|
||||
15, // [15:15] is the sub-list for extension extendee
|
||||
0, // [0:15] is the sub-list for field type_name
|
||||
24, // 15: topfans.gallery.GetInspirationFlowResponse.base:type_name -> topfans.common.BaseResponse
|
||||
22, // 16: topfans.gallery.GetInspirationFlowResponse.data:type_name -> topfans.gallery.InspirationFlowData
|
||||
23, // 17: topfans.gallery.InspirationFlowData.items:type_name -> topfans.gallery.InspirationFlowItem
|
||||
0, // 18: topfans.gallery.GalleryService.GetMyGallery:input_type -> topfans.gallery.GetMyGalleryRequest
|
||||
2, // 19: topfans.gallery.GalleryService.GetUserGallery:input_type -> topfans.gallery.GetUserGalleryRequest
|
||||
4, // 20: topfans.gallery.GalleryService.PlaceAsset:input_type -> topfans.gallery.PlaceAssetRequest
|
||||
6, // 21: topfans.gallery.GalleryService.UnlockSlot:input_type -> topfans.gallery.UnlockSlotRequest
|
||||
14, // 22: topfans.gallery.GalleryService.RemoveFromSlot:input_type -> topfans.gallery.RemoveFromSlotRequest
|
||||
16, // 23: topfans.gallery.GalleryService.GetMyExhibitedAssets:input_type -> topfans.gallery.GetMyExhibitedAssetsRequest
|
||||
20, // 24: topfans.gallery.GalleryService.GetInspirationFlow:input_type -> topfans.gallery.GetInspirationFlowRequest
|
||||
1, // 25: topfans.gallery.GalleryService.GetMyGallery:output_type -> topfans.gallery.GetMyGalleryResponse
|
||||
3, // 26: topfans.gallery.GalleryService.GetUserGallery:output_type -> topfans.gallery.GetUserGalleryResponse
|
||||
5, // 27: topfans.gallery.GalleryService.PlaceAsset:output_type -> topfans.gallery.PlaceAssetResponse
|
||||
7, // 28: topfans.gallery.GalleryService.UnlockSlot:output_type -> topfans.gallery.UnlockSlotResponse
|
||||
15, // 29: topfans.gallery.GalleryService.RemoveFromSlot:output_type -> topfans.gallery.RemoveFromSlotResponse
|
||||
17, // 30: topfans.gallery.GalleryService.GetMyExhibitedAssets:output_type -> topfans.gallery.GetMyExhibitedAssetsResponse
|
||||
21, // 31: topfans.gallery.GalleryService.GetInspirationFlow:output_type -> topfans.gallery.GetInspirationFlowResponse
|
||||
25, // [25:32] is the sub-list for method output_type
|
||||
18, // [18:25] is the sub-list for method input_type
|
||||
18, // [18:18] is the sub-list for extension type_name
|
||||
18, // [18:18] is the sub-list for extension extendee
|
||||
0, // [0:18] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_gallery_proto_init() }
|
||||
@ -1403,7 +1712,7 @@ func file_gallery_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_gallery_proto_rawDesc), len(file_gallery_proto_rawDesc)),
|
||||
NumEnums: 0,
|
||||
NumMessages: 20,
|
||||
NumMessages: 24,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@ -48,6 +48,8 @@ const (
|
||||
GalleryServiceRemoveFromSlotProcedure = "/topfans.gallery.GalleryService/RemoveFromSlot"
|
||||
// GalleryServiceGetMyExhibitedAssetsProcedure is the fully-qualified name of the GalleryService's GetMyExhibitedAssets RPC.
|
||||
GalleryServiceGetMyExhibitedAssetsProcedure = "/topfans.gallery.GalleryService/GetMyExhibitedAssets"
|
||||
// GalleryServiceGetInspirationFlowProcedure is the fully-qualified name of the GalleryService's GetInspirationFlow RPC.
|
||||
GalleryServiceGetInspirationFlowProcedure = "/topfans.gallery.GalleryService/GetInspirationFlow"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -62,6 +64,7 @@ type GalleryService interface {
|
||||
UnlockSlot(ctx context.Context, req *UnlockSlotRequest, opts ...client.CallOption) (*UnlockSlotResponse, error)
|
||||
RemoveFromSlot(ctx context.Context, req *RemoveFromSlotRequest, opts ...client.CallOption) (*RemoveFromSlotResponse, error)
|
||||
GetMyExhibitedAssets(ctx context.Context, req *GetMyExhibitedAssetsRequest, opts ...client.CallOption) (*GetMyExhibitedAssetsResponse, error)
|
||||
GetInspirationFlow(ctx context.Context, req *GetInspirationFlowRequest, opts ...client.CallOption) (*GetInspirationFlowResponse, error)
|
||||
}
|
||||
|
||||
// NewGalleryService constructs a client for the gallery.GalleryService service.
|
||||
@ -132,9 +135,17 @@ func (c *GalleryServiceImpl) GetMyExhibitedAssets(ctx context.Context, req *GetM
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *GalleryServiceImpl) GetInspirationFlow(ctx context.Context, req *GetInspirationFlowRequest, opts ...client.CallOption) (*GetInspirationFlowResponse, error) {
|
||||
resp := new(GetInspirationFlowResponse)
|
||||
if err := c.conn.CallUnary(ctx, []interface{}{req}, resp, "GetInspirationFlow", opts...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var GalleryService_ClientInfo = client.ClientInfo{
|
||||
InterfaceName: "topfans.gallery.GalleryService",
|
||||
MethodNames: []string{"GetMyGallery", "GetUserGallery", "PlaceAsset", "UnlockSlot", "RemoveFromSlot", "GetMyExhibitedAssets"},
|
||||
MethodNames: []string{"GetMyGallery", "GetUserGallery", "PlaceAsset", "UnlockSlot", "RemoveFromSlot", "GetMyExhibitedAssets", "GetInspirationFlow"},
|
||||
ConnectionInjectFunc: func(dubboCliRaw interface{}, conn *client.Connection) {
|
||||
dubboCli := dubboCliRaw.(*GalleryServiceImpl)
|
||||
dubboCli.conn = conn
|
||||
@ -149,6 +160,7 @@ type GalleryServiceHandler interface {
|
||||
UnlockSlot(context.Context, *UnlockSlotRequest) (*UnlockSlotResponse, error)
|
||||
RemoveFromSlot(context.Context, *RemoveFromSlotRequest) (*RemoveFromSlotResponse, error)
|
||||
GetMyExhibitedAssets(context.Context, *GetMyExhibitedAssetsRequest) (*GetMyExhibitedAssetsResponse, error)
|
||||
GetInspirationFlow(context.Context, *GetInspirationFlowRequest) (*GetInspirationFlowResponse, error)
|
||||
}
|
||||
|
||||
func RegisterGalleryServiceHandler(srv *server.Server, hdlr GalleryServiceHandler, opts ...server.ServiceOption) error {
|
||||
@ -253,5 +265,20 @@ var GalleryService_ServiceInfo = server.ServiceInfo{
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "GetInspirationFlow",
|
||||
Type: constant.CallUnary,
|
||||
ReqInitFunc: func() interface{} {
|
||||
return new(GetInspirationFlowRequest)
|
||||
},
|
||||
MethodFunc: func(ctx context.Context, args []interface{}, handler interface{}) (interface{}, error) {
|
||||
req := args[0].(*GetInspirationFlowRequest)
|
||||
res, err := handler.(GalleryServiceHandler).GetInspirationFlow(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return triple_protocol.NewResponse(res), nil
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -605,6 +605,7 @@ type OnboardingStage struct {
|
||||
Status string `protobuf:"bytes,6,opt,name=status,proto3" json:"status,omitempty"` // pending/completed/in_progress
|
||||
IsCurrent bool `protobuf:"varint,7,opt,name=is_current,json=isCurrent,proto3" json:"is_current,omitempty"`
|
||||
AllTasksCompleted bool `protobuf:"varint,8,opt,name=all_tasks_completed,json=allTasksCompleted,proto3" json:"all_tasks_completed,omitempty"` // 该阶段所有任务是否完成
|
||||
IsRewardClaimed bool `protobuf:"varint,9,opt,name=is_reward_claimed,json=isRewardClaimed,proto3" json:"is_reward_claimed,omitempty"` // 该阶段奖励是否已领取
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@ -695,6 +696,13 @@ func (x *OnboardingStage) GetAllTasksCompleted() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (x *OnboardingStage) GetIsRewardClaimed() bool {
|
||||
if x != nil {
|
||||
return x.IsRewardClaimed
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type CompleteGuideRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
TaskKey string `protobuf:"bytes,1,opt,name=task_key,json=taskKey,proto3" json:"task_key,omitempty"`
|
||||
@ -1942,7 +1950,7 @@ const file_task_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"experience\x18\x04 \x01(\x03R\n" +
|
||||
"experience\x12*\n" +
|
||||
"\x11claimed_task_keys\x18\x05 \x03(\tR\x0fclaimedTaskKeys\"\x96\x02\n" +
|
||||
"\x11claimed_task_keys\x18\x05 \x03(\tR\x0fclaimedTaskKeys\"\xc2\x02\n" +
|
||||
"\x0fOnboardingStage\x12\x14\n" +
|
||||
"\x05stage\x18\x01 \x01(\x05R\x05stage\x12\x12\n" +
|
||||
"\x04name\x18\x02 \x01(\tR\x04name\x12,\n" +
|
||||
@ -1953,7 +1961,8 @@ const file_task_proto_rawDesc = "" +
|
||||
"\x06status\x18\x06 \x01(\tR\x06status\x12\x1d\n" +
|
||||
"\n" +
|
||||
"is_current\x18\a \x01(\bR\tisCurrent\x12.\n" +
|
||||
"\x13all_tasks_completed\x18\b \x01(\bR\x11allTasksCompleted\"h\n" +
|
||||
"\x13all_tasks_completed\x18\b \x01(\bR\x11allTasksCompleted\x12*\n" +
|
||||
"\x11is_reward_claimed\x18\t \x01(\bR\x0fisRewardClaimed\"h\n" +
|
||||
"\x14CompleteGuideRequest\x12\x19\n" +
|
||||
"\btask_key\x18\x01 \x01(\tR\ataskKey\x125\n" +
|
||||
"\x06stages\x18\x02 \x03(\v2\x1d.topfans.task.OnboardingStageR\x06stages\"\xd6\x01\n" +
|
||||
|
||||
@ -54,6 +54,13 @@ service GalleryService {
|
||||
get: "/api/v1/me/exhibited-assets"
|
||||
};
|
||||
}
|
||||
|
||||
// 获取灵感瀑布藏品列表
|
||||
rpc GetInspirationFlow(GetInspirationFlowRequest) returns (GetInspirationFlowResponse) {
|
||||
option (google.api.http) = {
|
||||
get: "/api/v1/inspiration-flow"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 请求和响应消息定义
|
||||
@ -181,3 +188,37 @@ message ExhibitedAssetItem {
|
||||
int64 expire_at = 6; // 展出过期时间(毫秒时间戳)
|
||||
int64 earnings = 7; // 当前可领取收益
|
||||
}
|
||||
|
||||
// ==================== 灵感瀑布相关消息 ====================
|
||||
|
||||
// 获取灵感瀑布藏品列表请求
|
||||
message GetInspirationFlowRequest {
|
||||
string cursor = 1; // 游标(首次请求为空)
|
||||
string direction = 2; // 滚动方向:right(加载新数据)/ left(加载历史)
|
||||
int32 limit = 3; // 每页数量(默认10,最大20)
|
||||
string type = 4; // 过滤类型:badge/poster/original/all(默认all)
|
||||
string session_id = 5; // 会话ID(首次请求时为空,后端返回新的session_id)
|
||||
}
|
||||
|
||||
// 获取灵感瀑布藏品列表响应
|
||||
message GetInspirationFlowResponse {
|
||||
topfans.common.BaseResponse base = 1;
|
||||
InspirationFlowData data = 2;
|
||||
}
|
||||
|
||||
// 灵感瀑布数据
|
||||
message InspirationFlowData {
|
||||
repeated InspirationFlowItem items = 1; // 藏品列表
|
||||
string cursor = 2; // 下次请求的游标
|
||||
bool has_more = 3; // 是否有更多
|
||||
string session_id = 4; // 会话ID(首次请求时返回)
|
||||
}
|
||||
|
||||
// 灵感瀑布藏品项
|
||||
message InspirationFlowItem {
|
||||
int64 asset_id = 1; // 资产ID
|
||||
string name = 2; // 藏品名称
|
||||
string cover_url = 3; // 封面图URL
|
||||
int32 like_count = 4; // 点赞数
|
||||
string owner_nickname = 5; // 展出者昵称
|
||||
}
|
||||
|
||||
@ -338,6 +338,64 @@ func (p *GalleryProvider) GetMyExhibitedAssets(ctx context.Context, req *pb.GetM
|
||||
return p.exhibitionService.GetMyExhibitedAssets(ctx, userID, starID, req)
|
||||
}
|
||||
|
||||
// GetInspirationFlow 获取灵感瀑布藏品列表
|
||||
func (p *GalleryProvider) GetInspirationFlow(ctx context.Context, req *pb.GetInspirationFlowRequest) (*pb.GetInspirationFlowResponse, error) {
|
||||
logger.Logger.Info("Received GetInspirationFlow request",
|
||||
zap.String("cursor", req.Cursor),
|
||||
zap.String("direction", req.Direction),
|
||||
zap.Int32("limit", req.Limit),
|
||||
zap.String("type", req.Type),
|
||||
)
|
||||
|
||||
// 从 Dubbo attachments 获取用户信息(网关已验证并传递)
|
||||
userID, starID, err := extractUserInfoFromDubboAttachments(ctx)
|
||||
if err != nil {
|
||||
logger.Logger.Error("Failed to extract user info from attachments",
|
||||
zap.Error(err),
|
||||
)
|
||||
return &pb.GetInspirationFlowResponse{
|
||||
Base: &pbCommon.BaseResponse{
|
||||
Code: pbCommon.StatusCode_STATUS_UNAUTHORIZED,
|
||||
Message: "user authentication required",
|
||||
Timestamp: 0,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
// 调用Service层
|
||||
data, _, err := p.galleryService.GetInspirationFlow(userID, starID, req.Cursor, req.Direction, req.Limit, req.Type, req.SessionId)
|
||||
if err != nil {
|
||||
logger.Logger.Error("GetInspirationFlow failed",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Int64("star_id", starID),
|
||||
zap.String("direction", req.Direction),
|
||||
zap.Error(err),
|
||||
)
|
||||
return &pb.GetInspirationFlowResponse{
|
||||
Base: &pbCommon.BaseResponse{
|
||||
Code: appErrors.ToStatusCode(err),
|
||||
Message: err.Error(),
|
||||
Timestamp: 0,
|
||||
},
|
||||
}, err
|
||||
}
|
||||
|
||||
logger.Logger.Info("GetInspirationFlow successful",
|
||||
zap.Int64("user_id", userID),
|
||||
zap.Int64("star_id", starID),
|
||||
zap.Int("item_count", len(data.Items)),
|
||||
)
|
||||
|
||||
return &pb.GetInspirationFlowResponse{
|
||||
Base: &pbCommon.BaseResponse{
|
||||
Code: pbCommon.StatusCode_STATUS_OK,
|
||||
Message: "success",
|
||||
Timestamp: 0,
|
||||
},
|
||||
Data: data,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ==================== 辅助函数 ====================
|
||||
|
||||
// extractUserInfoFromDubboAttachments 从Dubbo attachments提取用户信息
|
||||
|
||||
@ -41,6 +41,30 @@ type GalleryRepository interface {
|
||||
// pageSize: 每页数量
|
||||
// 返回: 作品列表、总数量
|
||||
GetMyExhibitedAssets(userID, starID int64, page, pageSize int) ([]*ExhibitedAssetInfo, int64, error)
|
||||
|
||||
// ========== 灵感瀑布相关 ==========
|
||||
|
||||
// CountValidExhibitions 统计有效展品数量
|
||||
// starID: 明星ID
|
||||
// materialType: 素材类型过滤(空字符串表示不过滤)
|
||||
CountValidExhibitions(starID int64, materialType string) (int64, error)
|
||||
|
||||
// GetRandomExhibitions 获取随机展品列表
|
||||
// starID: 明星ID
|
||||
// materialType: 素材类型过滤(空字符串表示不过滤)
|
||||
// excludeIDs: 排除的展品ID列表(用于去重)
|
||||
// limit: 返回数量
|
||||
// offset: 偏移量(随机生成)
|
||||
GetRandomExhibitions(starID int64, materialType string, excludeIDs []int64, limit, offset int) ([]*InspirationFlowItem, error)
|
||||
}
|
||||
|
||||
// InspirationFlowItem 灵感瀑布展品项
|
||||
type InspirationFlowItem struct {
|
||||
AssetID int64
|
||||
Name string
|
||||
CoverURL string
|
||||
LikeCount int32
|
||||
OwnerNickname string
|
||||
}
|
||||
|
||||
// ExhibitedAssetInfo 我展出的作品信息
|
||||
@ -255,18 +279,19 @@ func (r *galleryRepository) GetMyExhibitedAssets(userID, starID int64, page, pag
|
||||
// 数据查询
|
||||
offset := (page - 1) * pageSize
|
||||
err = r.db.Model(&models.Exhibition{}).
|
||||
Select(`exhibitions.asset_id, a.name, a.cover_url, a.like_count,
|
||||
Raw(`
|
||||
SELECT exhibitions.asset_id, a.name, a.cover_url, a.like_count,
|
||||
exhibitions.start_time as exhibited_at, exhibitions.expire_at,
|
||||
COALESCE(SUM(err.crystal_amount), 0) as earnings`).
|
||||
Joins("JOIN assets a ON a.id = exhibitions.asset_id").
|
||||
Joins("LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'").
|
||||
Where("exhibitions.occupier_uid = ? AND exhibitions.occupier_star_id = ?", userID, starID).
|
||||
Where("exhibitions.deleted_at IS NULL AND exhibitions.expire_at > ?", now).
|
||||
Group("exhibitions.asset_id, a.name, a.cover_url, a.like_count, exhibitions.start_time, exhibitions.expire_at").
|
||||
Order("exhibitions.start_time DESC").
|
||||
Limit(pageSize).
|
||||
Offset(offset).
|
||||
Scan(&items).Error
|
||||
COALESCE(CAST(SUM(err.crystal_amount) / 10 AS bigint), 0) as earnings
|
||||
FROM exhibitions
|
||||
JOIN assets a ON a.id = exhibitions.asset_id
|
||||
LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'
|
||||
WHERE exhibitions.occupier_uid = ? AND exhibitions.occupier_star_id = ?
|
||||
AND exhibitions.deleted_at IS NULL AND exhibitions.expire_at > ?
|
||||
GROUP BY exhibitions.asset_id, a.name, a.cover_url, a.like_count, exhibitions.start_time, exhibitions.expire_at
|
||||
ORDER BY exhibitions.start_time DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`, userID, starID, now, pageSize, offset).Scan(&items).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
@ -275,6 +300,62 @@ func (r *galleryRepository) GetMyExhibitedAssets(userID, starID int64, page, pag
|
||||
return items, total, nil
|
||||
}
|
||||
|
||||
// ========== 灵感瀑布相关实现 ==========
|
||||
|
||||
// CountValidExhibitions 统计有效展品数量
|
||||
func (r *galleryRepository) CountValidExhibitions(starID int64, materialType string) (int64, error) {
|
||||
var count int64
|
||||
now := time.Now().UnixMilli()
|
||||
|
||||
query := r.db.Model(&models.Exhibition{}).
|
||||
Where("occupier_star_id = ? AND expire_at > ? AND deleted_at IS NULL", starID, now)
|
||||
|
||||
if materialType != "" && materialType != "all" {
|
||||
query = query.Joins("JOIN assets a ON a.id = exhibitions.asset_id").
|
||||
Where("a.material_type = ?", materialType)
|
||||
}
|
||||
|
||||
err := query.Count(&count).Error
|
||||
return count, err
|
||||
}
|
||||
|
||||
// GetRandomExhibitions 获取随机展品列表
|
||||
func (r *galleryRepository) GetRandomExhibitions(starID int64, materialType string, excludeIDs []int64, limit, offset int) ([]*InspirationFlowItem, error) {
|
||||
var items []*InspirationFlowItem
|
||||
now := time.Now().UnixMilli()
|
||||
|
||||
// 构建基础查询
|
||||
baseQuery := r.db.Model(&models.Exhibition{}).
|
||||
Where("exhibitions.occupier_star_id = ? AND exhibitions.expire_at > ? AND exhibitions.deleted_at IS NULL", starID, now)
|
||||
|
||||
if materialType != "" && materialType != "all" {
|
||||
baseQuery = baseQuery.Joins("JOIN assets a ON a.id = exhibitions.asset_id").
|
||||
Where("a.material_type = ?", materialType)
|
||||
}
|
||||
|
||||
// 排除已展示的ID
|
||||
if len(excludeIDs) > 0 {
|
||||
baseQuery = baseQuery.Where("exhibitions.id NOT IN ?", excludeIDs)
|
||||
}
|
||||
|
||||
// 执行随机排序查询
|
||||
err := baseQuery.
|
||||
Select(`exhibitions.asset_id, a.name, a.cover_url, a.like_count, fp.nickname as owner_nickname`).
|
||||
Joins("JOIN assets a ON a.id = exhibitions.asset_id").
|
||||
Joins("JOIN fan_profiles fp ON exhibitions.occupier_uid = fp.user_id AND exhibitions.occupier_star_id = fp.star_id").
|
||||
Where("a.status = 1 AND a.is_active = true").
|
||||
Order("RANDOM()").
|
||||
Limit(limit).
|
||||
Offset(offset).
|
||||
Scan(&items).Error
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// ==================== 辅助函数 ====================
|
||||
|
||||
// generateHostProfileID 生成 host_profile_id
|
||||
|
||||
@ -695,3 +695,104 @@ onDataLoaded() {
|
||||
| 2026-04-29 | 改用随机 offset 方案(方案 B),每次请求都是独立随机,数据变化无影响 |
|
||||
| 2026-04-29 | 新增双向滚动支持:向右加载新数据,向左加载历史数据,后端维护会话级缓存 |
|
||||
| 2026-04-29 | 修复:修正重复的 10.4 章节、RangeSamplingStrategy 签名、游标结构说明 |
|
||||
| 2026-04-29 | 新增 Redis 会话级缓存实现方案:支持双向滚动去重 |
|
||||
|
||||
---
|
||||
|
||||
## 十三、Redis 会话级缓存实现
|
||||
|
||||
### 13.1 技术选型
|
||||
|
||||
- **客户端**: `github.com/redis/go-redis/v9`
|
||||
- **连接信息**: `localhost:6379`,无密码
|
||||
- **TTL**: 30分钟(无操作自动清理)
|
||||
|
||||
### 13.2 缓存结构
|
||||
|
||||
```
|
||||
Key: inspiration_flow:{star_id}:{session_id}
|
||||
Type: Hash
|
||||
Fields:
|
||||
- displayed_ids: ["id1", "id2", ...] # 已展示ID列表(用于去重)
|
||||
- history: {"id1": json_data1, "id2": json_data2, ...} # 历史数据详情
|
||||
TTL: 1800秒(30分钟)
|
||||
```
|
||||
|
||||
### 13.3 环境变量配置
|
||||
|
||||
| 变量名 | 说明 | 默认值 |
|
||||
|--------|------|--------|
|
||||
| REDIS_HOST | Redis 主机地址 | 127.0.0.1 |
|
||||
| REDIS_PORT | Redis 端口 | 6379 |
|
||||
| REDIS_PASSWORD | Redis 密码 | (空) |
|
||||
| REDIS_DB | Redis 数据库编号 | 0 |
|
||||
|
||||
### 13.4 核心逻辑
|
||||
|
||||
| 方向 | 行为 |
|
||||
|------|------|
|
||||
| `direction=right` | 随机查询新数据(排除已展示ID),返回并更新缓存 |
|
||||
| `direction=left` | 从缓存的历史数据中分页返回 |
|
||||
|
||||
### 13.5 实现文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `backend/pkg/database/redis.go`(新建) | Redis 客户端初始化 |
|
||||
| `backend/services/socialService/repository/social_repository.go` | 新增 `GetRandomUsersExcludeIDs` |
|
||||
| `backend/services/socialService/service/friend_service.go` | 修改 `GetRandomUsers` 支持 direction + 缓存 |
|
||||
| `backend/services/socialService/provider/social_provider.go` | 透传 direction 参数 |
|
||||
| `backend/gateway/dto/social_converter.go` | 转换 exclude_ids |
|
||||
| `backend/gateway/config/config.go` | 新增 Redis 配置 |
|
||||
|
||||
### 13.6 session_id 生成
|
||||
|
||||
- 由后端生成(UUID)
|
||||
- 首次请求时返回给前端,前端后续请求携带
|
||||
|
||||
### 13.7 向左滚动实现
|
||||
|
||||
向左滚动时,后端从 Redis 缓存的 `history` 字段读取已展示数据,按 offset 分页返回。前端在左侧插入展示。
|
||||
|
||||
### 13.8 预签名 URL 批量获取优化
|
||||
|
||||
**问题**:前端逐个获取预签名 URL,N 个卡片产生 N 次请求。
|
||||
|
||||
**解决方案**:新增批量接口,前端一次性获取所有 URL。
|
||||
|
||||
**接口设计**:
|
||||
```
|
||||
POST /api/v1/assets/oss/batch-presigned-urls
|
||||
Content-Type: application/json
|
||||
|
||||
Request:
|
||||
{
|
||||
"files": ["path/to/img1.png", "path/to/img2.png", ...],
|
||||
"expires": 3600,
|
||||
"type": "asset"
|
||||
}
|
||||
|
||||
Response:
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"urls": {
|
||||
"path/to/img1.png": "https://xxx?signature=...",
|
||||
"path/to/img2.png": "https://xxx?signature=..."
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**前端逻辑**:
|
||||
1. 加载用户数据后,收集所有 `cover_url`
|
||||
2. 批量调用接口获取全部预签名 URL
|
||||
3. 存入 Map 缓存,后续直接使用
|
||||
|
||||
**实现文件**:
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `backend/gateway/controller/asset_controller.go` | 新增 batch presigned urls 路由 |
|
||||
| `backend/gateway/dto/asset_dto.go` | 新增 BatchPresignedUrlsRequest/Response |
|
||||
| `frontend/utils/api.js` | 新增 `getBatchOssPresignedUrlsApi` |
|
||||
| `frontend/pages/square/components/WaterfallGrid.vue` | 使用批量接口替代逐个调用 |
|
||||
|
||||
@ -22,18 +22,18 @@
|
||||
</view>
|
||||
|
||||
<!-- 新手引导 -->
|
||||
<view v-if="showGuideIcon" class="daily-task-group" @click="handleGuideClick">
|
||||
<!-- 1. 上层:新手引导(悬浮在上面) -->
|
||||
<!-- <view v-if="showGuideIcon" class="daily-task-group" @click="handleGuideClick">
|
||||
1. 上层:新手引导(悬浮在上面)
|
||||
<view class="task-icon-box">
|
||||
<image class="task-icon-img" src="/static/icon/onboarding-bg.png" mode="aspectFit"></image>
|
||||
<image class="task-red-dot" src="/static/square/tishi.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
<!-- 2. 下层:文字背景块 -->
|
||||
2. 下层:文字背景块
|
||||
<view class="task-text-box">
|
||||
<text class="task-text-label">新手引导</text>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
|
||||
<!-- 星援活动 -->
|
||||
<view v-if="showStarActivityIcon" class="daily-task-group" @click="handleStarActivityClick">
|
||||
@ -388,13 +388,13 @@ const handleAvatarClick = () => {
|
||||
if (pages.length > 0) {
|
||||
const currentPage = pages[pages.length - 1];
|
||||
// 检查当前页面是否是个人信息页面
|
||||
if (currentPage.route === 'pages/profile/profile') {
|
||||
if (currentPage.route === 'pages/profile/myWorks') {
|
||||
// 已经在个人信息页面,不执行跳转
|
||||
return;
|
||||
}
|
||||
}
|
||||
uni.navigateTo({
|
||||
url: '/pages/profile/profile'
|
||||
url: '/pages/profile/myWorks'
|
||||
});
|
||||
};
|
||||
|
||||
@ -570,7 +570,7 @@ defineExpose({
|
||||
}
|
||||
|
||||
.balance-number {
|
||||
font-size: 22rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
color: #FFB800;
|
||||
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
|
||||
|
||||
@ -10,6 +10,9 @@
|
||||
</view>
|
||||
<!-- <text class="nav-title">我的作品</text> -->
|
||||
<view class="nav-placeholder"></view>
|
||||
<view class="nav-settings" @tap="goToSettings">
|
||||
<text class="nav-settings-text">个人设置</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="scroll-content">
|
||||
@ -22,15 +25,13 @@
|
||||
</view>
|
||||
|
||||
<view class="exhibition-grid">
|
||||
<view
|
||||
v-for="(item, index) in exhibitionWorks"
|
||||
:key="item.id"
|
||||
class="exhibition-card"
|
||||
<view v-for="(item, index) in exhibitionWorks" :key="item.id" class="exhibition-card"
|
||||
:class="index % 2 === 0 ? 'card-tilt-left' : 'card-tilt-right'"
|
||||
@tap="handleExhibitionCardTap(item, index)"
|
||||
>
|
||||
<image class="card-image" :src="item.cover_url || '/static/nft/placeholder.png'" mode="aspectFill"></image>
|
||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill"></image>
|
||||
@tap="handleExhibitionCardTap(item, index)">
|
||||
<image class="card-image" :src="item.cover_url || '/static/nft/placeholder.png'"
|
||||
mode="aspectFill"></image>
|
||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill">
|
||||
</image>
|
||||
<!-- 点赞数 -->
|
||||
<view class="card-rate-badge">
|
||||
<image class="heart-icon" src="/static/icon/heart-icon.png" mode="aspectFit"></image>
|
||||
@ -39,10 +40,11 @@
|
||||
</view>
|
||||
</view>
|
||||
<!-- 图片下方收益 -->
|
||||
<view class="card-income-row" :class="index % 2 === 0 ? 'income-tilt-right' : 'income-tilt-left'">
|
||||
<view class="card-income-row"
|
||||
:class="index % 2 === 0 ? 'income-tilt-right' : 'income-tilt-left'">
|
||||
<image class="topfans-icon" src="/static/icon/crystal.png" mode="aspectFit"></image>
|
||||
<view class="card-income-text-wrap">
|
||||
<text class="card-income-text">{{ item.earnings || 0 }}</text>
|
||||
<text class="card-income-text">{{ item.earnings || 0 }}/时</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@ -50,9 +52,11 @@
|
||||
<!-- 空状态占位:显示剩余空展位卡片 -->
|
||||
<view v-if="exhibitionWorks.length < 2" class="empty-exhibition">
|
||||
<!-- 根据已展出数量决定显示几个空卡片 -->
|
||||
<view v-if="exhibitionWorks.length === 0" class="empty-card empty-card-left" @tap="openAssetSelector(0)">
|
||||
<view v-if="exhibitionWorks.length === 0" class="empty-card empty-card-left"
|
||||
@tap="openAssetSelector(0)">
|
||||
<image class="empty-cover" src="/static/nft/placeholder.png" mode="aspectFill"></image>
|
||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill"></image>
|
||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png"
|
||||
mode="aspectFill"></image>
|
||||
<view class="empty-add-btn">
|
||||
<text class="empty-add-icon">+</text>
|
||||
</view>
|
||||
@ -60,7 +64,8 @@
|
||||
|
||||
<view class="empty-card empty-card-right" @tap="openAssetSelector(1)">
|
||||
<image class="empty-cover" src="/static/nft/placeholder.png" mode="aspectFill"></image>
|
||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png" mode="aspectFill"></image>
|
||||
<image class="card-frame" src="/static/square/gerenzhongxincangpinkuang.png"
|
||||
mode="aspectFill"></image>
|
||||
<view class="empty-add-btn">
|
||||
<text class="empty-add-icon">+</text>
|
||||
</view>
|
||||
@ -69,34 +74,27 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 今日点赞作品 -->
|
||||
<!-- 当前点赞作品 -->
|
||||
<view class="section-block">
|
||||
<view class="section-label">
|
||||
<image class="section-label-bg" src="/static/nft/dingbutubiao_liang.png" mode="aspectFill"></image>
|
||||
<text class="section-label-text">今日点赞作品</text>
|
||||
<text class="section-label-text">点赞作品</text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="liked-list" scroll-y="true" :show-scrollbar="false">
|
||||
<view
|
||||
v-for="(item, index) in likedWorks"
|
||||
:key="item.id"
|
||||
class="liked-row"
|
||||
@tap="goToAssetDetail(item.id)"
|
||||
>
|
||||
<view v-for="(item, index) in likedWorks" :key="item.id" class="liked-row"
|
||||
@tap="goToAssetDetail(item.id)">
|
||||
<!-- 排名图标,绝对定位在卡片左侧 -->
|
||||
<image
|
||||
v-if="index < 3"
|
||||
:src="rankIcons[index]"
|
||||
class="rank-icon-img"
|
||||
mode="aspectFit"
|
||||
></image>
|
||||
<image v-if="index < 3" :src="rankIcons[index]" class="rank-icon-img" mode="aspectFit"></image>
|
||||
|
||||
<!-- 卡片主体 -->
|
||||
<view class="liked-item" :class="index === 0 ? 'liked-item-first' : ''">
|
||||
<!-- 作品封面 -->
|
||||
<view class="liked-cover-wrap" :class="index === 0 ? 'liked-cover-wrap-first' : ''">
|
||||
<image class="liked-cover" :src="item.cover_url || '/static/nft/placeholder.png'" mode="aspectFill"></image>
|
||||
<image class="liked-cover-frame" src="/static/square/cangpinkuang1.png" mode="aspectFill"></image>
|
||||
<image class="liked-cover" :src="item.cover_url || '/static/nft/placeholder.png'"
|
||||
mode="aspectFill"></image>
|
||||
<image class="liked-cover-frame" src="/static/square/cangpinkuang1.png"
|
||||
mode="aspectFill"></image>
|
||||
</view>
|
||||
|
||||
<!-- 作品信息 -->
|
||||
@ -104,13 +102,15 @@
|
||||
<text class="liked-status">{{ item.status_text }}</text>
|
||||
<view class="liked-score-row">
|
||||
<text class="liked-score">{{ formatScore(item.score) }}</text>
|
||||
<image class="fire-icon" src="/static/square/rementubiao.png" mode="aspectFit"></image>
|
||||
<image class="fire-icon" src="/static/square/rementubiao.png" mode="aspectFit">
|
||||
</image>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 右侧奖励 -->
|
||||
<view class="liked-reward">
|
||||
<image class="reward-token-icon" src="/static/icon/crystal.png" mode="aspectFit"></image>
|
||||
<image class="reward-token-icon" src="/static/icon/crystal.png" mode="aspectFit">
|
||||
</image>
|
||||
<text class="reward-amount">+{{ item.reward }}</text>
|
||||
</view>
|
||||
</view>
|
||||
@ -118,7 +118,7 @@
|
||||
|
||||
<!-- 空状态 -->
|
||||
<view v-if="likedWorks.length === 0" class="empty-liked">
|
||||
<text class="empty-text">今日暂无点赞作品</text>
|
||||
<text class="empty-text">当前暂无点赞作品</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
@ -127,12 +127,8 @@
|
||||
</view>
|
||||
|
||||
<!-- 藏品选择器组件 -->
|
||||
<AssetSelector
|
||||
:visible="showAssetSelector"
|
||||
:replace-asset="assetToReplace"
|
||||
@close="closeAssetSelector"
|
||||
@select="handleAssetSelect"
|
||||
/>
|
||||
<AssetSelector :visible="showAssetSelector" :replace-asset="assetToReplace" @close="closeAssetSelector"
|
||||
@select="handleAssetSelect" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
@ -144,7 +140,24 @@ import { onShow } from '@dcloudio/uni-app';
|
||||
import { doubleTapLike } from '@/utils/likeHelper.js';
|
||||
|
||||
const goBack = () => {
|
||||
// 获取页面栈
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
// 有上一页,执行返回
|
||||
uni.navigateBack();
|
||||
} else {
|
||||
// 没有上一页,跳转到square页面
|
||||
uni.reLaunch({
|
||||
url: '/pages/square/square'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const goToSettings = () => {
|
||||
|
||||
uni.navigateTo({
|
||||
url: '/pages/profile/profile'
|
||||
});
|
||||
};
|
||||
|
||||
const goToCastlove = () => {
|
||||
@ -237,7 +250,7 @@ const handleExhibitionCardTap = (item, index) => {
|
||||
if (success) {
|
||||
// 更新在展作品的点赞数
|
||||
exhibitionWorks.value[index].like_count = (exhibitionWorks.value[index].like_count || 0) + 1;
|
||||
// 将作品添加到今日点赞列表
|
||||
// 将作品添加到当前点赞列表
|
||||
const likedItem = {
|
||||
id: item.id,
|
||||
cover_url: item.cover_url,
|
||||
@ -275,7 +288,7 @@ const formatScore = (score) => {
|
||||
// 在展作品列表
|
||||
const exhibitionWorks = ref([]);
|
||||
|
||||
// 今日点赞作品列表
|
||||
// 当前点赞作品列表
|
||||
const likedWorks = ref([]);
|
||||
|
||||
// 加载我的展出作品
|
||||
@ -383,6 +396,28 @@ onShow(() => {
|
||||
width: 64rpx;
|
||||
}
|
||||
|
||||
.nav-settings {
|
||||
height: 64rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(to bottom right,
|
||||
#F0E4B1 0%,
|
||||
#F08399 50%,
|
||||
#B94E73 100%);
|
||||
border-radius: 24rpx;
|
||||
padding: 8rpx 20rpx 8rpx 20rpx;
|
||||
box-shadow:
|
||||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
|
||||
}
|
||||
|
||||
.nav-settings-text {
|
||||
font-size: 24rpx;
|
||||
color: #fff;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.scroll-content {
|
||||
position: relative;
|
||||
@ -492,7 +527,7 @@ onShow(() => {
|
||||
.card-user-text {
|
||||
font-size: 20rpx;
|
||||
color: #fff;
|
||||
background: rgba(0,0,0,0.45);
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
@ -532,11 +567,11 @@ onShow(() => {
|
||||
}
|
||||
|
||||
.card-income-text-wrap {
|
||||
width: 64rpx;
|
||||
background: linear-gradient(to bottom right,
|
||||
#F0E4B1 0%,
|
||||
#F08399 50%,
|
||||
#B94E73 100%
|
||||
);
|
||||
#B94E73 100%);
|
||||
border-radius: 999rpx;
|
||||
padding: 8rpx 20rpx 8rpx 40rpx;
|
||||
box-shadow:
|
||||
@ -564,13 +599,13 @@ onShow(() => {
|
||||
background: linear-gradient(to bottom right,
|
||||
#F0E4B1 0%,
|
||||
#F08399 50%,
|
||||
#B94E73 100%
|
||||
);
|
||||
#B94E73 100%);
|
||||
border-radius: 999rpx;
|
||||
padding: 2rpx 12rpx;
|
||||
padding: 2rpx 16rpx;
|
||||
box-shadow:
|
||||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.card-rate-text {
|
||||
@ -643,7 +678,7 @@ onShow(() => {
|
||||
color: #b09cc0;
|
||||
}
|
||||
|
||||
/* 今日点赞列表 */
|
||||
/* 当前点赞列表 */
|
||||
.liked-list {
|
||||
max-height: 732rpx;
|
||||
}
|
||||
@ -686,7 +721,7 @@ onShow(() => {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
border-radius: 50%;
|
||||
background: rgba(180,140,220,0.3);
|
||||
background: rgba(180, 140, 220, 0.3);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
@ -1,117 +0,0 @@
|
||||
<template>
|
||||
<view
|
||||
:id="'cabin-' + cabin.key"
|
||||
class="cabin-wrapper"
|
||||
:class="{
|
||||
'cabin-nickname-mine': cabin.isMine || cabin.nickname === currentUserNickname,
|
||||
'cabin-slots-zero': cabin.sharedBoothSlotsRemaining === 0
|
||||
}"
|
||||
:style="{ left: cabin.x + 'px', top: cabin.y + 'px', width: cabin.w + 'px' }"
|
||||
@click="handleClick"
|
||||
>
|
||||
<image
|
||||
class="cabin-icon"
|
||||
:src="cabin.src"
|
||||
:style="{ width: cabin.w + 'px', height: cabin.h + 'px' }"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
|
||||
<text v-if="cabin.showNickname && cabin.nickname" class="cabin-nickname">
|
||||
{{ cabin.nickname === currentUserNickname ? '我的小屋' : cabin.nickname }}
|
||||
</text>
|
||||
<text v-else class="cabin-nickname cabin-nickname--empty">小屋暂无人居住</text>
|
||||
|
||||
<view v-if="cabin.showDialog" class="cabin-slots-dialog">
|
||||
<text class="cabin-slots-text text-white">
|
||||
剩余 <text class="text-orange">{{ cabin.sharedBoothSlotsRemaining }}</text> 个展位
|
||||
</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
cabin: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
currentUserNickname: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['click'])
|
||||
|
||||
const handleClick = () => {
|
||||
emit('click', props.cabin)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.cabin-wrapper {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.cabin-icon {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cabin-slots-zero .cabin-icon {
|
||||
filter: grayscale(100%);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.cabin-nickname {
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
text-shadow: 0 2rpx 4rpx rgba(0, 0, 0, 0.6);
|
||||
margin-top: 4rpx;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cabin-nickname--empty {
|
||||
color: rgba(255, 255, 255, 0.75);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.cabin-slots-dialog {
|
||||
position: absolute;
|
||||
top: -50rpx;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-image: url('/static/icon/tips-bg.png');
|
||||
background-size: 100% 100%;
|
||||
background-repeat: no-repeat;
|
||||
padding: 8rpx 24rpx 18rpx;
|
||||
width: max-content;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.cabin-slots-text {
|
||||
font-size: 18rpx;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.text-white {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.text-orange {
|
||||
color: #FFB800;
|
||||
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
|
||||
text-shadow:
|
||||
0 0 10rpx rgba(255, 184, 0, 0.8),
|
||||
0 2rpx 4rpx rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
</style>
|
||||
@ -39,11 +39,10 @@ defineProps({
|
||||
defineEmits(['update:modelValue'])
|
||||
|
||||
const tabs = [
|
||||
{ key: 'hot', label: '热门作品', emoji: null, icon: '/static/square/rementubiao.png', iconWidth: 104, iconHeight: 104 },
|
||||
{ key: 'xingka', label: '星卡', emoji: null, icon: '/static/square/xingka.png', iconWidth: 80, iconHeight: 80 },
|
||||
{ key: 'baji', label: '把爱', emoji: null, icon: '/static/square/baji.png', iconWidth: 80, iconHeight: 80 },
|
||||
{ key: 'haibao', label: '海报', emoji: null, icon: '/static/square/haibao.png', iconWidth: 96, iconHeight: 96 },
|
||||
]
|
||||
{ key: 'hot', label: '人气王者', emoji: null, icon: '/static/square/rementubiao.png', iconWidth: 104, iconHeight: 104 },
|
||||
{ key: 'xingka', label: '潜力之星', emoji: null, icon: '/static/square/xingka.png', iconWidth: 80, iconHeight: 80 },
|
||||
{ key: 'baji', label: '新鲜上架', emoji: null, icon: '/static/square/baji.png', iconWidth: 80, iconHeight: 80 },
|
||||
{ key: 'haibao', label: '随机寻宝', emoji: null, icon: '/static/square/haibao.png', iconWidth: 96, iconHeight: 96 },]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -57,13 +57,16 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
||||
import { getRandomUsersApi, getOssPresignedUrlApi } from '@/utils/api.js'
|
||||
import { getInspirationFlowApi, getBatchOssPresignedUrlsApi } from '@/utils/api.js'
|
||||
import { doubleTapLike } from '@/utils/likeHelper.js'
|
||||
import { USE_MOCK_DATA, getMockDataByCategory, generateMockItems, calcSpan } from '../config/mockData.js'
|
||||
|
||||
const props = defineProps({
|
||||
screenWidth: { type: Number, default: 375 },
|
||||
screenHeight: { type: Number, default: 812 },
|
||||
bannerBottom: { type: Number, default: 200 },
|
||||
useMockData: { type: Boolean, default: false }, // 是否使用模拟数据
|
||||
category: { type: String, default: 'hot' }, // 当前分类
|
||||
})
|
||||
|
||||
const emit = defineEmits(['cardClick'])
|
||||
@ -76,7 +79,7 @@ const SCALE = 0.9
|
||||
const ROWS = 4
|
||||
const AUTO_SCROLL_SPEED = 1.2
|
||||
const AUTO_RESUME_DELAY = 2500
|
||||
const PRELOAD_THRESHOLD = rpx2px(1200)
|
||||
const PRELOAD_THRESHOLD = rpx2px(300)
|
||||
|
||||
// ========== 状态 ==========
|
||||
const cards = ref([])
|
||||
@ -86,6 +89,8 @@ const scrollLeft = ref(0)
|
||||
const likingMap = ref({}) // 记录正在播放点赞动画的卡片ID
|
||||
let currentScrollLeft = 0
|
||||
let idCounter = 0
|
||||
let isComponentMounted = false // 标记组件是否已卸载
|
||||
let mockDataOffset = 0 // 模拟数据循环偏移量
|
||||
|
||||
// ========== RAF 兼容 ==========
|
||||
const rafFn = (cb) => {
|
||||
@ -103,16 +108,56 @@ const cafFn = (id) => {
|
||||
let rafId = null
|
||||
let userInteracting = false
|
||||
let resumeTimer = null
|
||||
let appendTimer = null // 防抖定时器
|
||||
|
||||
const startAutoScroll = () => {
|
||||
if (rafId) return
|
||||
const step = () => {
|
||||
if (!userInteracting) {
|
||||
currentScrollLeft += AUTO_SCROLL_SPEED
|
||||
scrollLeft.value = currentScrollLeft
|
||||
if (totalWidth.value - currentScrollLeft - props.screenWidth < PRELOAD_THRESHOLD) {
|
||||
if (!isComponentMounted) {
|
||||
console.log('[WaterfallGrid] startAutoScroll blocked: not mounted')
|
||||
return
|
||||
}
|
||||
if (rafId) cancelAnimationFrame(rafId)
|
||||
console.log('[WaterfallGrid] startAutoScroll started')
|
||||
|
||||
// 启动时先追加一次数据(使用模拟数据时直接追加)
|
||||
if (!isLoadingMore && props.useMockData) {
|
||||
console.log('[WaterfallGrid] startAutoScroll calling appendMore')
|
||||
appendMore()
|
||||
}
|
||||
|
||||
let lastScrollLeft = 0 // 用于检测手动滚动
|
||||
|
||||
const step = () => {
|
||||
if (!isComponentMounted) {
|
||||
rafId = null
|
||||
console.log('[WaterfallGrid] RAF stopped: not mounted')
|
||||
return
|
||||
}
|
||||
|
||||
if (!userInteracting && !isLoadingMore) {
|
||||
// 检测是否被手动滚动过(如果实际滚动位置和我们的不一致,说明用户手动滚了)
|
||||
const actualScroll = scrollLeft.value
|
||||
if (Math.abs(actualScroll - lastScrollLeft) > 5) {
|
||||
// 用户手动滚了,同步位置
|
||||
currentScrollLeft = actualScroll
|
||||
} else {
|
||||
// 正常累加
|
||||
currentScrollLeft += AUTO_SCROLL_SPEED
|
||||
scrollLeft.value = currentScrollLeft
|
||||
lastScrollLeft = currentScrollLeft
|
||||
}
|
||||
|
||||
// 预加载:剩余可滚动距离小于一半屏幕宽度时,触发追加
|
||||
const remainingScroll = totalWidth.value - currentScrollLeft - props.screenWidth
|
||||
if (remainingScroll < Math.max(totalWidth.value / 2, props.screenWidth)) {
|
||||
// 直接调用 appendMore(不需要防抖)
|
||||
if (!isLoadingMore) {
|
||||
appendMore()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 用户交互中,记录当前滚动位置
|
||||
lastScrollLeft = scrollLeft.value
|
||||
currentScrollLeft = lastScrollLeft
|
||||
}
|
||||
rafId = rafFn(step)
|
||||
}
|
||||
@ -120,7 +165,16 @@ const startAutoScroll = () => {
|
||||
}
|
||||
|
||||
const stopAutoScroll = () => {
|
||||
if (rafId) { cafFn(rafId); rafId = null }
|
||||
console.log('[WaterfallGrid] stopAutoScroll called')
|
||||
if (rafId) {
|
||||
cafFn(rafId)
|
||||
rafId = null
|
||||
}
|
||||
clearTimeout(appendTimer)
|
||||
appendTimer = null
|
||||
clearTimeout(resumeTimer)
|
||||
resumeTimer = null
|
||||
userInteracting = false // 重置用户交互状态
|
||||
}
|
||||
|
||||
const pauseForUser = () => {
|
||||
@ -129,14 +183,29 @@ const pauseForUser = () => {
|
||||
resumeTimer = setTimeout(() => { userInteracting = false }, AUTO_RESUME_DELAY)
|
||||
}
|
||||
|
||||
// 防抖:延迟 500ms 后再执行追加,避免频繁调用
|
||||
const scheduleAppend = () => {
|
||||
if (!isComponentMounted) return
|
||||
if (appendTimer) clearTimeout(appendTimer)
|
||||
appendTimer = setTimeout(() => {
|
||||
appendTimer = null
|
||||
if (isComponentMounted) {
|
||||
appendMore()
|
||||
}
|
||||
}, 500)
|
||||
}
|
||||
|
||||
// ========== 触摸 / 滚动事件 ==========
|
||||
const onTouchStart = () => pauseForUser()
|
||||
const onTouchEnd = () => {}
|
||||
|
||||
const onScroll = (e) => {
|
||||
if (!isComponentMounted) return
|
||||
currentScrollLeft = e.detail.scrollLeft
|
||||
if (totalWidth.value - currentScrollLeft - props.screenWidth < PRELOAD_THRESHOLD) {
|
||||
appendMore()
|
||||
// 预加载:剩余可滚动距离小于一半屏幕宽度时,触发追加
|
||||
const remainingScroll = totalWidth.value - currentScrollLeft - props.screenWidth
|
||||
if (remainingScroll < Math.max(totalWidth.value / 2, props.screenWidth)) {
|
||||
scheduleAppend()
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,10 +225,11 @@ const scrollStyle = computed(() => ({
|
||||
// 同一列内所有卡片宽度统一 = 整列宽(span=ROWS 对应的宽),高度各自按 span 计算
|
||||
// 支持两种模式:
|
||||
// 1. 后端传 span:直接按 span 分组成列
|
||||
// 2. 后端不传 span:前端随机选列模板,每次打开都不同
|
||||
// 2. 后端不传 span:使用分类特定的 span 阈值计算
|
||||
class WaterfallLayout {
|
||||
constructor(containerH, gap = GAP) {
|
||||
constructor(containerH, category = 'hot', gap = GAP) {
|
||||
this.containerH = containerH
|
||||
this.category = category
|
||||
this.gap = gap
|
||||
this.rowH = Math.floor((containerH - gap * (ROWS - 1)) / ROWS)
|
||||
// 列宽固定 = rowH × 9/16,严格竖长 9:16,span 只影响高度
|
||||
@ -173,30 +243,57 @@ class WaterfallLayout {
|
||||
return { w, h }
|
||||
}
|
||||
|
||||
// 点赞数 → span
|
||||
// 点赞数 → span(使用分类特定的阈值)
|
||||
_span(likes) {
|
||||
if (likes < 500) return 1
|
||||
if (likes < 2000) return 2
|
||||
if (likes < 8000) return 2
|
||||
if (likes < 50000) return 3
|
||||
return 4
|
||||
return calcSpan(this.category, likes)
|
||||
}
|
||||
|
||||
// 将用户列表按点赞数决定 span,分组成列,每列 span 之和 = ROWS
|
||||
_groupIntoColumns(users) {
|
||||
// 复制一份避免修改原数组
|
||||
const remaining = users.map((u, idx) => ({
|
||||
...u,
|
||||
originalIndex: idx,
|
||||
span: u.span != null ? u.span : this._span(u.likes || 0)
|
||||
}))
|
||||
|
||||
const columns = []
|
||||
let i = 0
|
||||
while (i < users.length) {
|
||||
let colIndex = 0
|
||||
|
||||
while (remaining.length > 0) {
|
||||
const col = []
|
||||
let sum = 0
|
||||
while (i < users.length && sum < ROWS) {
|
||||
// 后端有 span 用后端的,否则按点赞数算
|
||||
const rawSpan = users[i].span != null ? users[i].span : this._span(users[i].likes || 0)
|
||||
const span = Math.min(rawSpan, ROWS - sum)
|
||||
col.push({ ...users[i], span })
|
||||
sum += span
|
||||
const colStart = users.length - remaining.length
|
||||
|
||||
// 尝试放入能填满当前列的卡片
|
||||
let i = 0
|
||||
while (i < remaining.length) {
|
||||
const rawSpan = remaining[i].span
|
||||
|
||||
// 如果当前卡片加入会导致超出 ROWS,尝试下一个
|
||||
if (sum + rawSpan > ROWS) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// 可以放入,从 remaining 中移除并加入 col
|
||||
const item = remaining.splice(i, 1)[0]
|
||||
col.push(item)
|
||||
sum += rawSpan
|
||||
|
||||
// 如果已经达到 ROWS,结束这列
|
||||
if (sum >= ROWS) break
|
||||
}
|
||||
|
||||
// 如果放不下任何卡片(剩余卡片都太大),强制放入最小的
|
||||
if (col.length === 0 && remaining.length > 0) {
|
||||
const item = remaining.shift()
|
||||
col.push(item)
|
||||
sum = item.span
|
||||
}
|
||||
|
||||
colIndex++
|
||||
|
||||
// 凑不满则补 _pad
|
||||
while (sum < ROWS) {
|
||||
col.push({ id: idCounter++, span: 1, _pad: true, likes: 0 })
|
||||
@ -331,18 +428,71 @@ const mapUser = async (u) => {
|
||||
return {
|
||||
id: idCounter++,
|
||||
userId: u.user_id,
|
||||
nickname: u.nickname,
|
||||
nickname: u.owner_nickname || u.nickname,
|
||||
coverUrl,
|
||||
likes: u.likes ?? randomLikes(),
|
||||
likes: u.like_count ?? u.likes ?? randomLikes(),
|
||||
span: u.span ?? null, // 后端控制字段,null = 前端随机模板决定
|
||||
}
|
||||
}
|
||||
|
||||
const loadUsers = async () => {
|
||||
// 批量获取预签名URL
|
||||
const batchGetPresignedUrls = async (urls) => {
|
||||
if (!urls || urls.length === 0) return {}
|
||||
try {
|
||||
const res = await getRandomUsersApi(1, 40)
|
||||
if (res.code === 200 && res.data?.users) {
|
||||
const withData = await Promise.all(res.data.users.map(mapUser))
|
||||
const files = urls.filter(u => u)
|
||||
const res = await getBatchOssPresignedUrlsApi(files, 3600, 'asset')
|
||||
if (res?.code === 200 && res.data?.files) {
|
||||
// 构建 key -> presigned_url 的映射
|
||||
const map = {}
|
||||
for (const file of res.data.files) {
|
||||
map[file.key] = file.presigned_url
|
||||
}
|
||||
return map
|
||||
}
|
||||
} catch (_) {}
|
||||
return {}
|
||||
}
|
||||
|
||||
const loadUsers = async () => {
|
||||
console.log('[WaterfallGrid] loadUsers called, isComponentMounted:', isComponentMounted, 'useMockData:', props.useMockData, 'category:', props.category)
|
||||
if (!isComponentMounted) return
|
||||
|
||||
// 切换分类时重置偏移量
|
||||
mockDataOffset = 0
|
||||
|
||||
try {
|
||||
let items
|
||||
|
||||
if (props.useMockData) {
|
||||
// 使用分类对应的模拟数据
|
||||
console.log('[WaterfallGrid] 使用模拟数据, category:', props.category)
|
||||
const mockData = getMockDataByCategory(props.category)
|
||||
items = mockData.items
|
||||
// 更新偏移量
|
||||
mockDataOffset = items.length
|
||||
console.log('[WaterfallGrid] loadUsers 模拟数据加载完成, items:', items.length, 'offset:', mockDataOffset)
|
||||
} else {
|
||||
// 使用真实API
|
||||
const res = await getInspirationFlowApi({ limit: 40, type: props.category })
|
||||
console.log('[WaterfallGrid] loadUsers got response, isComponentMounted:', isComponentMounted)
|
||||
if (!isComponentMounted) return
|
||||
if (res.code === 200 && res.data?.items) {
|
||||
items = res.data.items
|
||||
}
|
||||
}
|
||||
|
||||
if (items && items.length > 0) {
|
||||
const withData = items.map((item) => {
|
||||
return {
|
||||
id: item.asset_id,
|
||||
userId: item.asset_id,
|
||||
nickname: item.owner_nickname || item.name,
|
||||
coverUrl: item.cover_url || MOCK_IMAGES[idCounter % MOCK_IMAGES.length],
|
||||
likes: item.likes || item.like_count || 0,
|
||||
span: item.span ?? null,
|
||||
}
|
||||
})
|
||||
console.log('[WaterfallGrid] loadUsers withData:', withData.length, 'sample likes:', withData.slice(0, 3).map(d => d.likes))
|
||||
allUsers.value = withData
|
||||
cards.value = layout.compute(withData)
|
||||
totalWidth.value = layout.getTotalWidth()
|
||||
@ -353,19 +503,92 @@ const loadUsers = async () => {
|
||||
}
|
||||
|
||||
const appendMore = async () => {
|
||||
if (isLoadingMore) return
|
||||
console.log('[WaterfallGrid] appendMore called', {
|
||||
isLoadingMore,
|
||||
isComponentMounted,
|
||||
useMockData: props.useMockData,
|
||||
category: props.category,
|
||||
cardsLength: cards.value.length,
|
||||
totalWidth: totalWidth.value
|
||||
})
|
||||
if (!isComponentMounted) {
|
||||
console.log('[WaterfallGrid] appendMore blocked: not mounted')
|
||||
return
|
||||
}
|
||||
if (isLoadingMore) {
|
||||
console.log('[WaterfallGrid] appendMore blocked: already loading, waiting...')
|
||||
// 等待一下再试
|
||||
setTimeout(() => {
|
||||
if (isComponentMounted && !isLoadingMore) {
|
||||
console.log('[WaterfallGrid] appendMore retry after wait')
|
||||
appendMore()
|
||||
}
|
||||
}, 500)
|
||||
return
|
||||
}
|
||||
isLoadingMore = true
|
||||
try {
|
||||
const res = await getRandomUsersApi(1, 20)
|
||||
if (res.code === 200 && res.data?.users) {
|
||||
const withData = await Promise.all(res.data.users.map(mapUser))
|
||||
let items
|
||||
|
||||
if (props.useMockData) {
|
||||
// 使用模拟数据:循环原始数据
|
||||
const mockData = getMockDataByCategory(props.category)
|
||||
const allItems = mockData.items
|
||||
const batchSize = 20
|
||||
|
||||
console.log('[WaterfallGrid] 模拟数据追加, offset:', mockDataOffset, 'items:', allItems.length)
|
||||
|
||||
// 从 mockDataOffset 位置开始取 batchSize 个
|
||||
const itemsToAdd = []
|
||||
for (let i = 0; i < batchSize; i++) {
|
||||
const sourceIndex = (mockDataOffset + i) % allItems.length
|
||||
const sourceItem = allItems[sourceIndex]
|
||||
// 创建新对象,确保每次都有唯一的 asset_id
|
||||
const newItem = {
|
||||
...sourceItem,
|
||||
asset_id: sourceItem.asset_id * 100 + mockDataOffset + i, // 确保唯一
|
||||
likes: sourceItem.like_count, // 使用原始点赞数
|
||||
}
|
||||
itemsToAdd.push(newItem)
|
||||
}
|
||||
|
||||
// 更新偏移量
|
||||
mockDataOffset = mockDataOffset + batchSize
|
||||
|
||||
items = itemsToAdd
|
||||
console.log('[WaterfallGrid] 模拟数据追加完成, items:', items.length, 'next offset:', mockDataOffset)
|
||||
} else {
|
||||
// 使用真实API
|
||||
const res = await getInspirationFlowApi({ limit: 20, type: props.category })
|
||||
if (!isComponentMounted) return
|
||||
if (res.code === 200 && res.data?.items) {
|
||||
items = res.data.items
|
||||
}
|
||||
}
|
||||
|
||||
if (items && items.length > 0) {
|
||||
console.log('[WaterfallGrid] appendMore before:', 'cards.length:', cards.value.length, 'totalWidth:', totalWidth.value)
|
||||
const withData = items.map((item) => {
|
||||
return {
|
||||
id: item.asset_id, // 直接使用 asset_id(已经在上面保证唯一)
|
||||
userId: item.asset_id,
|
||||
nickname: item.owner_nickname || item.name,
|
||||
coverUrl: item.cover_url || MOCK_IMAGES[idCounter % MOCK_IMAGES.length],
|
||||
likes: item.likes || item.like_count || 0,
|
||||
span: item.span ?? null,
|
||||
}
|
||||
})
|
||||
console.log('[WaterfallGrid] appendMore withData:', withData.length)
|
||||
const placed = layout.addCards(withData)
|
||||
console.log('[WaterfallGrid] appendMore placed:', placed.length, 'layout.curX:', layout.curX)
|
||||
cards.value = [...cards.value, ...placed]
|
||||
totalWidth.value = layout.getTotalWidth()
|
||||
console.log('[WaterfallGrid] appendMore after:', 'cards.length:', cards.value.length, 'totalWidth:', totalWidth.value)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[WaterfallGrid] 追加用户失败', e?.message ?? e)
|
||||
} finally {
|
||||
console.log('[WaterfallGrid] appendMore finally, resetting isLoadingMore')
|
||||
isLoadingMore = false
|
||||
}
|
||||
}
|
||||
@ -405,24 +628,56 @@ const handleCardClick = (card) => {
|
||||
|
||||
// ========== 初始化 ==========
|
||||
onMounted(() => {
|
||||
isComponentMounted = true
|
||||
const containerH = props.screenHeight - props.bannerBottom
|
||||
layout = new WaterfallLayout(containerH)
|
||||
layout = new WaterfallLayout(containerH, props.category)
|
||||
console.log('[WaterfallGrid] onMounted, starting...')
|
||||
loadUsers().then(() => startAutoScroll())
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
console.log('[WaterfallGrid] onUnmounted called!')
|
||||
isComponentMounted = false
|
||||
stopAutoScroll()
|
||||
clearTimeout(resumeTimer)
|
||||
clearTimeout(appendTimer)
|
||||
})
|
||||
|
||||
watch(() => [props.screenHeight, props.bannerBottom], () => {
|
||||
const containerH = props.screenHeight - props.bannerBottom
|
||||
layout = new WaterfallLayout(containerH)
|
||||
layout = new WaterfallLayout(containerH, props.category)
|
||||
if (allUsers.value.length) {
|
||||
cards.value = layout.compute(allUsers.value)
|
||||
totalWidth.value = layout.getTotalWidth()
|
||||
}
|
||||
})
|
||||
|
||||
// 监听分类变化,重新加载数据
|
||||
watch(() => props.category, (newCategory) => {
|
||||
console.log('[WaterfallGrid] category changed to:', newCategory)
|
||||
if (isComponentMounted) {
|
||||
// 先停止当前的自动滚动
|
||||
stopAutoScroll()
|
||||
|
||||
// 取消待执行的追加
|
||||
if (appendTimer) {
|
||||
clearTimeout(appendTimer)
|
||||
appendTimer = null
|
||||
}
|
||||
isLoadingMore = false // 重置加载状态
|
||||
|
||||
// 重新创建布局(使用新的 span 阈值)
|
||||
const containerH = props.screenHeight - props.bannerBottom
|
||||
layout = new WaterfallLayout(containerH, newCategory)
|
||||
cards.value = []
|
||||
allUsers.value = []
|
||||
totalWidth.value = 0
|
||||
loadUsers().then(() => {
|
||||
// 加载完成后启动自动滚动
|
||||
startAutoScroll()
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@ -1,248 +0,0 @@
|
||||
import { ref, reactive, watch, shallowRef } from 'vue'
|
||||
import { generateGridCoordinates, CABIN_DEFS, cabinTypeByLevel } from '../config/cabin.js'
|
||||
import { getRandomUsersApi } from '@/utils/api.js'
|
||||
|
||||
export function useCabin() {
|
||||
const visibleCabins = ref([])
|
||||
const currentPage = ref(1)
|
||||
const totalUsers = ref(0)
|
||||
const scaledCoords = ref([])
|
||||
const pageCache = shallowRef(new Map())
|
||||
const pageCacheVersion = ref(0)
|
||||
const pendingPages = new Set()
|
||||
|
||||
let cabinRenderDefs = []
|
||||
let tileWidth = 375
|
||||
let screenWidth = 375
|
||||
let bannerBottom = 0
|
||||
|
||||
const PAGE_SIZE = generateGridCoordinates().length
|
||||
|
||||
const maxPage = () => Math.max(1, Math.ceil(totalUsers.value / PAGE_SIZE))
|
||||
|
||||
const wrapPage = (p) => {
|
||||
const max = maxPage()
|
||||
return ((p - 1 + max) % max) + 1
|
||||
}
|
||||
|
||||
const fetchPage = async (page) => {
|
||||
if (pageCache.value.has(page) || pendingPages.has(page)) {
|
||||
return
|
||||
}
|
||||
|
||||
pendingPages.add(page)
|
||||
try {
|
||||
const res = await getRandomUsersApi(page, PAGE_SIZE)
|
||||
if (res.code === 200 && res.data) {
|
||||
if (totalUsers.value === 0) totalUsers.value = res.data.total || 0
|
||||
|
||||
const users = res.data.users || []
|
||||
// 交换第 1 和第 3 个用户位置
|
||||
if (users.length >= 3) {
|
||||
[users[0], users[20]] = [users[20], users[0]]
|
||||
}
|
||||
|
||||
pageCache.value.set(page, users)
|
||||
pageCacheVersion.value++
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`[useCabin] fetchPage: page=${page} 请求失败`, e?.message ?? e)
|
||||
} finally {
|
||||
pendingPages.delete(page)
|
||||
}
|
||||
}
|
||||
|
||||
const ensurePages = (center, isInertia = false) => {
|
||||
const keep = new Set()
|
||||
for (let n = 0; n <= 4; n++) {
|
||||
keep.add(wrapPage(center + n - 1))
|
||||
}
|
||||
|
||||
const pages = [...keep]
|
||||
pages.forEach(fetchPage)
|
||||
|
||||
// 清除不在渲染范围内的页缓存
|
||||
const evicted = []
|
||||
for (const key of pageCache.value.keys()) {
|
||||
if (!keep.has(key) && !pendingPages.has(key)) {
|
||||
evicted.push(key)
|
||||
pageCache.value.delete(key)
|
||||
}
|
||||
}
|
||||
if (evicted.length) {
|
||||
pageCacheVersion.value++
|
||||
}
|
||||
}
|
||||
|
||||
const buildVisibleCabins = (page, cache, coords) => {
|
||||
if (!coords.length || !cabinRenderDefs.length) return []
|
||||
|
||||
const w = tileWidth
|
||||
const result = []
|
||||
|
||||
// 第一遍:收集上方有用户数据的 cabin(待移位),以及下方空位坐标
|
||||
const toRelocate = []
|
||||
const emptySlots = []
|
||||
|
||||
for (let i = 0; i < coords.length; i++) {
|
||||
const { sx, sy } = coords[i]
|
||||
for (let n = 0; n <= 4; n++) {
|
||||
const p = wrapPage(page + n - 1)
|
||||
const users = cache.get(p) || []
|
||||
const user = users[i] || null
|
||||
const isAbove = sy < bannerBottom
|
||||
|
||||
if (isAbove && user) {
|
||||
const typeIdx = cabinTypeByLevel(user.level)
|
||||
toRelocate.push({ i, n, user, typeIdx, sx, sy })
|
||||
} else if (!isAbove && !user) {
|
||||
emptySlots.push({ sx, sy, n })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 第二遍:正常渲染,跳过"被移位占用的空位"和"已移位的上方 cabin"
|
||||
const occupiedSlots = new Set()
|
||||
for (let k = 0; k < toRelocate.length && k < emptySlots.length; k++) {
|
||||
occupiedSlots.add(k)
|
||||
}
|
||||
|
||||
for (let i = 0; i < coords.length; i++) {
|
||||
const { sx, sy } = coords[i]
|
||||
for (let n = 0; n <= 4; n++) {
|
||||
const p = wrapPage(page + n - 1)
|
||||
const users = cache.get(p) || []
|
||||
const user = users[i] || null
|
||||
const isAbove = sy < bannerBottom
|
||||
|
||||
// 跳过上方有数据的(会被移位渲染)
|
||||
if (isAbove && user) continue
|
||||
|
||||
// 检查这个空位是否被移位 cabin 占用
|
||||
const slotIdx = emptySlots.findIndex(s => s.sx === sx && s.sy === sy && s.n === n)
|
||||
if (slotIdx !== -1 && occupiedSlots.has(slotIdx)) continue
|
||||
|
||||
const typeIdx = cabinTypeByLevel(user ? user.level : 0)
|
||||
const def = CABIN_DEFS[typeIdx]
|
||||
const render = cabinRenderDefs[typeIdx]
|
||||
const cabinY = sy - render.offsetY
|
||||
|
||||
result.push(reactive({
|
||||
key: `${i}-${n}`,
|
||||
x: sx + n * w - render.offsetX,
|
||||
y: cabinY,
|
||||
w: render.renderedW,
|
||||
h: render.renderedH,
|
||||
src: def.src,
|
||||
userId: user ? user.user_id : null,
|
||||
galleryOwnerId: user ? user.gallery_owner_id : null,
|
||||
nickname: user ? user.nickname : null,
|
||||
sharedBoothSlotsRemaining: user ? user.shared_booth_slots_remaining : null,
|
||||
showNickname: !isAbove,
|
||||
isMine: false,
|
||||
showDialog: false,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
// 第三遍:把移位的 cabin 放到对应空位坐标上
|
||||
for (let k = 0; k < toRelocate.length; k++) {
|
||||
const { n, user, typeIdx } = toRelocate[k]
|
||||
const def = CABIN_DEFS[typeIdx]
|
||||
const render = cabinRenderDefs[typeIdx]
|
||||
|
||||
if (k >= emptySlots.length) continue
|
||||
|
||||
const targetSx = emptySlots[k].sx
|
||||
const targetSy = emptySlots[k].sy
|
||||
|
||||
result.push(reactive({
|
||||
key: `reloc-${k}-${n}`,
|
||||
x: targetSx + n * w - render.offsetX,
|
||||
y: targetSy - render.offsetY,
|
||||
w: render.renderedW,
|
||||
h: render.renderedH,
|
||||
src: def.src,
|
||||
userId: user.user_id,
|
||||
galleryOwnerId: user.gallery_owner_id,
|
||||
nickname: user.nickname,
|
||||
sharedBoothSlotsRemaining: user.shared_booth_slots_remaining,
|
||||
showNickname: true,
|
||||
isMine: false,
|
||||
showDialog: false,
|
||||
}))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// 翻页或坐标初始化时全量重建
|
||||
watch([currentPage, scaledCoords], () => {
|
||||
visibleCabins.value = buildVisibleCabins(currentPage.value, pageCache.value, scaledCoords.value)
|
||||
}, { immediate: true })
|
||||
|
||||
// 数据加载完成时重建
|
||||
watch(pageCacheVersion, () => {
|
||||
visibleCabins.value = buildVisibleCabins(currentPage.value, pageCache.value, scaledCoords.value)
|
||||
})
|
||||
|
||||
const initCabin = ({ screenW, tileW, imageH, currentUserNickname }) => {
|
||||
screenWidth = screenW
|
||||
tileWidth = tileW
|
||||
|
||||
// 计算 banner 底部边界
|
||||
const rpxToPx = screenWidth / 750
|
||||
bannerBottom = 496 * rpxToPx
|
||||
|
||||
// 计算 cabin 渲染尺寸
|
||||
const baseH = Math.round(screenWidth * 0.2 * 1.3)
|
||||
cabinRenderDefs = CABIN_DEFS.map(({ imgW, imgH, anchorX, anchorY }) => {
|
||||
const renderedH = baseH
|
||||
const renderedW = Math.round(renderedH * (imgW / imgH))
|
||||
return {
|
||||
renderedW,
|
||||
renderedH,
|
||||
offsetX: Math.round(renderedW * (anchorX / imgW)),
|
||||
offsetY: Math.round(renderedH * (anchorY / imgH)),
|
||||
}
|
||||
})
|
||||
|
||||
// 生成并缩放坐标
|
||||
const scale = imageH / 1918
|
||||
const coords = generateGridCoordinates()
|
||||
scaledCoords.value = coords.map(({ x, y }) => ({
|
||||
sx: x * scale,
|
||||
sy: y * scale,
|
||||
}))
|
||||
}
|
||||
|
||||
const updateCurrentUserNickname = (nickname) => {
|
||||
visibleCabins.value.forEach(cabin => {
|
||||
if (cabin.nickname === nickname) {
|
||||
cabin.isMine = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const resetSquare = async () => {
|
||||
currentPage.value = 1
|
||||
totalUsers.value = 0
|
||||
pageCache.value = new Map()
|
||||
pageCacheVersion.value++
|
||||
await fetchPage(1)
|
||||
ensurePages(1)
|
||||
}
|
||||
|
||||
return {
|
||||
visibleCabins,
|
||||
currentPage,
|
||||
scaledCoords,
|
||||
cabinRenderDefs,
|
||||
fetchPage,
|
||||
ensurePages,
|
||||
initCabin,
|
||||
updateCurrentUserNickname,
|
||||
resetSquare,
|
||||
wrapPage,
|
||||
}
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
import { ref } from 'vue'
|
||||
|
||||
export function useDialogRotation() {
|
||||
let timer = null
|
||||
let screenWidth = 375
|
||||
let screenHeight = 812
|
||||
|
||||
const initDialogRotation = ({ screenW, screenH }) => {
|
||||
screenWidth = screenW
|
||||
screenHeight = screenH
|
||||
}
|
||||
|
||||
const isCabinInViewport = (cabin) => {
|
||||
return new Promise((resolve) => {
|
||||
const query = uni.createSelectorQuery()
|
||||
query.select(`#cabin-${cabin.key}`).boundingClientRect((rect) => {
|
||||
if (!rect) {
|
||||
resolve(false)
|
||||
return
|
||||
}
|
||||
|
||||
const visibleTop = Math.max(0, rect.top)
|
||||
const visibleBottom = Math.min(screenHeight, rect.bottom)
|
||||
const visibleLeft = Math.max(0, rect.left)
|
||||
const visibleRight = Math.min(screenWidth, rect.right)
|
||||
const visibleWidth = Math.max(0, visibleRight - visibleLeft)
|
||||
const visibleHeight = Math.max(0, visibleBottom - visibleTop)
|
||||
const visibleArea = visibleWidth * visibleHeight
|
||||
const totalArea = rect.width * rect.height
|
||||
const visiblePercent = totalArea > 0 ? visibleArea / totalArea : 0
|
||||
|
||||
resolve(visiblePercent > 0.7)
|
||||
}).exec()
|
||||
})
|
||||
}
|
||||
|
||||
const rotateDialogVisibility = async (cabins) => {
|
||||
if (!cabins.length) return
|
||||
|
||||
// 先全部重置为 false
|
||||
cabins.forEach(c => c.showDialog = false)
|
||||
|
||||
// 筛选符合条件的 cabin:有昵称、有展位剩余
|
||||
const hasData = cabins.filter(c => c.nickname && c.sharedBoothSlotsRemaining !== null)
|
||||
|
||||
// 批量检查所有 cabin 是否在可视区域内
|
||||
const viewportChecks = await Promise.all(hasData.map(c => isCabinInViewport(c)))
|
||||
const eligible = hasData.filter((c, i) => viewportChecks[i])
|
||||
|
||||
if (eligible.length === 0) return
|
||||
|
||||
// 随机选择 2-3 个
|
||||
const count = Math.min(Math.floor(Math.random() * 2) + 2, eligible.length, 3)
|
||||
const shuffled = eligible.sort(() => Math.random() - 0.5)
|
||||
for (let i = 0; i < count; i++) {
|
||||
shuffled[i].showDialog = true
|
||||
}
|
||||
}
|
||||
|
||||
const startDialogRotation = (cabins) => {
|
||||
stopDialogRotation()
|
||||
|
||||
const rotate = async () => {
|
||||
await rotateDialogVisibility(cabins)
|
||||
const interval = Math.floor(Math.random() * 1000) + 2000
|
||||
timer = setTimeout(rotate, interval)
|
||||
}
|
||||
|
||||
rotate()
|
||||
}
|
||||
|
||||
const stopDialogRotation = () => {
|
||||
if (timer) {
|
||||
clearTimeout(timer)
|
||||
timer = null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
initDialogRotation,
|
||||
startDialogRotation,
|
||||
stopDialogRotation,
|
||||
}
|
||||
}
|
||||
@ -1,181 +0,0 @@
|
||||
import { ref, computed } from 'vue'
|
||||
|
||||
// RAF 封装
|
||||
const rafFn = (cb) => uni.requestAnimationFrame ? uni.requestAnimationFrame(cb) : setTimeout(cb, 16)
|
||||
const cafFn = (id) => uni.cancelAnimationFrame ? uni.cancelAnimationFrame(id) : clearTimeout(id)
|
||||
|
||||
export function useSwipe() {
|
||||
const bgOffsetX = ref(0)
|
||||
const screenWidth = ref(375)
|
||||
const tileWidth = ref(375)
|
||||
|
||||
let rawOffsetX = 0
|
||||
let touchStartX = 0
|
||||
let lastMoveX = 0
|
||||
let lastMoveTime = 0
|
||||
let velocity = 0
|
||||
let inertiaRaf = null
|
||||
let isInertiaPhase = false
|
||||
let touchInBanner = false
|
||||
|
||||
let onTileChange = null
|
||||
|
||||
const cabinLayerStyle = computed(() => ({
|
||||
transform: `translateX(${-tileWidth.value + bgOffsetX.value}px)`
|
||||
}))
|
||||
|
||||
const backgroundStripStyle = computed(() => ({
|
||||
width: `${tileWidth.value * 3}px`,
|
||||
transform: `translateX(${-tileWidth.value + bgOffsetX.value}px)`
|
||||
}))
|
||||
|
||||
const clampOffset = (offset) => {
|
||||
const w = tileWidth.value
|
||||
return (((offset % w) + w) % w) - w
|
||||
}
|
||||
|
||||
const normalizeOffset = (offset) => {
|
||||
const w = tileWidth.value
|
||||
const normalized = clampOffset(offset)
|
||||
const prevTileN = Math.floor(-rawOffsetX / w)
|
||||
rawOffsetX += offset - bgOffsetX.value
|
||||
const nextTileN = Math.floor(-rawOffsetX / w)
|
||||
const delta = nextTileN - prevTileN
|
||||
|
||||
if (delta !== 0 && onTileChange) {
|
||||
onTileChange(delta, isInertiaPhase)
|
||||
}
|
||||
|
||||
return normalized
|
||||
}
|
||||
|
||||
const stopInertia = () => {
|
||||
if (inertiaRaf) {
|
||||
cafFn(inertiaRaf)
|
||||
inertiaRaf = null
|
||||
}
|
||||
isInertiaPhase = false
|
||||
}
|
||||
|
||||
const scrollPage = (direction) => {
|
||||
stopInertia()
|
||||
|
||||
if (onTileChange) {
|
||||
onTileChange(direction, false)
|
||||
}
|
||||
|
||||
const DURATION = 300
|
||||
const FRAME = 16
|
||||
const totalFrames = Math.round(DURATION / FRAME)
|
||||
const totalDelta = tileWidth.value * direction * -1
|
||||
let frame = 0
|
||||
|
||||
const step = () => {
|
||||
frame++
|
||||
const progress = frame / totalFrames
|
||||
const eased = 1 - Math.pow(1 - progress, 3)
|
||||
const prevEased = frame === 1 ? 0 : 1 - Math.pow(1 - (frame - 1) / totalFrames, 3)
|
||||
const delta = totalDelta * (eased - prevEased)
|
||||
|
||||
bgOffsetX.value = clampOffset(bgOffsetX.value + delta)
|
||||
rawOffsetX += delta
|
||||
|
||||
if (frame < totalFrames) {
|
||||
inertiaRaf = rafFn(step)
|
||||
}
|
||||
}
|
||||
|
||||
inertiaRaf = rafFn(step)
|
||||
}
|
||||
|
||||
const getBannerBottom = () => (screenWidth.value / 750) * 632
|
||||
|
||||
const onBgTouchStart = (e) => {
|
||||
const touchY = e.touches[0].clientY
|
||||
touchInBanner = touchY < getBannerBottom()
|
||||
if (touchInBanner) return
|
||||
|
||||
stopInertia()
|
||||
touchStartX = e.touches[0].clientX
|
||||
lastMoveX = touchStartX
|
||||
lastMoveTime = Date.now()
|
||||
velocity = 0
|
||||
}
|
||||
|
||||
const onBgTouchMove = (e) => {
|
||||
if (touchInBanner) return
|
||||
e.preventDefault()
|
||||
|
||||
const currentX = e.touches[0].clientX
|
||||
const now = Date.now()
|
||||
const dt = now - lastMoveTime || 1
|
||||
velocity = (currentX - lastMoveX) / dt
|
||||
lastMoveX = currentX
|
||||
lastMoveTime = now
|
||||
|
||||
bgOffsetX.value = normalizeOffset(bgOffsetX.value + (currentX - touchStartX))
|
||||
touchStartX = currentX
|
||||
}
|
||||
|
||||
const onBgTouchEnd = () => {
|
||||
if (touchInBanner) {
|
||||
touchInBanner = false
|
||||
return
|
||||
}
|
||||
touchInBanner = false
|
||||
isInertiaPhase = true
|
||||
|
||||
const FRICTION = 0.8
|
||||
const MIN_VELOCITY = 0.2
|
||||
|
||||
const step = () => {
|
||||
velocity *= FRICTION
|
||||
if (Math.abs(velocity) < MIN_VELOCITY) {
|
||||
isInertiaPhase = false
|
||||
return
|
||||
}
|
||||
bgOffsetX.value = normalizeOffset(bgOffsetX.value + velocity * 16)
|
||||
inertiaRaf = rafFn(step)
|
||||
}
|
||||
|
||||
inertiaRaf = rafFn(step)
|
||||
}
|
||||
|
||||
const onBgTouchCancel = () => {
|
||||
touchInBanner = false
|
||||
stopInertia()
|
||||
velocity = 0
|
||||
}
|
||||
|
||||
const initSwipe = ({ screenW, tileW, onTileChangeCallback }) => {
|
||||
screenWidth.value = screenW
|
||||
tileWidth.value = tileW
|
||||
onTileChange = onTileChangeCallback
|
||||
bgOffsetX.value = 0
|
||||
rawOffsetX = 0
|
||||
velocity = 0
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
stopInertia()
|
||||
bgOffsetX.value = 0
|
||||
rawOffsetX = 0
|
||||
velocity = 0
|
||||
}
|
||||
|
||||
return {
|
||||
bgOffsetX,
|
||||
rawOffsetX,
|
||||
velocity,
|
||||
cabinLayerStyle,
|
||||
backgroundStripStyle,
|
||||
scrollPage,
|
||||
stopInertia,
|
||||
initSwipe,
|
||||
reset,
|
||||
onBgTouchStart,
|
||||
onBgTouchMove,
|
||||
onBgTouchEnd,
|
||||
onBgTouchCancel,
|
||||
}
|
||||
}
|
||||
@ -1,116 +0,0 @@
|
||||
// ========== 图片原始尺寸 ==========
|
||||
export const IMAGE_W = 2012
|
||||
export const IMAGE_H = 1918
|
||||
|
||||
// ========== 小屋类型定义 ==========
|
||||
export const CABIN_DEFS = [
|
||||
{ src: '/static/components/cabin1.png', imgW: 1000, imgH: 1000, anchorX: 500, anchorY: 680 },
|
||||
{ src: '/static/components/cabin2.png', imgW: 1000, imgH: 1000, anchorX: 500, anchorY: 680 },
|
||||
{ src: '/static/components/cabin3.png', imgW: 1000, imgH: 1351, anchorX: 500, anchorY: 965 },
|
||||
{ src: '/static/components/cabin4.png', imgW: 1000, imgH: 1223, anchorX: 500, anchorY: 875 },
|
||||
]
|
||||
|
||||
// ========== 根据等级返回 cabin 类型索引 ==========
|
||||
export const cabinTypeByLevel = (level) => {
|
||||
if (level >= 7) return 3
|
||||
if (level >= 5) return 2
|
||||
if (level >= 3) return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
// ========== 背景网格配置 ==========
|
||||
export const GRID_CONFIG = {
|
||||
backgroundWidth: 2012,
|
||||
backgroundHeight: 1918,
|
||||
grid: {
|
||||
rows: 11,
|
||||
cols: 4,
|
||||
startX: -260,
|
||||
startY: -20,
|
||||
spacingX: 515,
|
||||
spacingY: 180, // 减小垂直间距
|
||||
staggered: true,
|
||||
staggerOffsetX: 260,
|
||||
cellWidth: 200,
|
||||
cellHeight: 150,
|
||||
excludeRows: [0, 1, 2],
|
||||
},
|
||||
// 手动微调个别位置
|
||||
manualAdjustments: {
|
||||
// 可以在这里添加需要微调的坐标
|
||||
// 格式: index: { x: newX, y: newY }
|
||||
16: { x: -255, y: 740 },
|
||||
17: { x: 255, y: 740 },
|
||||
18: { x: 750, y: 740 },
|
||||
19: { x: 1265, y: 740 },
|
||||
20: { x: 0, y: 900 },
|
||||
21: { x: 505, y: 900 },
|
||||
22: { x: 1010, y: 900 },
|
||||
23: { x: 1515, y: 900 },
|
||||
24: { x: -245, y: 1120 },
|
||||
25: { x: 255, y: 1120 },
|
||||
26: { x: 750, y: 1120 },
|
||||
27: { x: 1265, y: 1120 },
|
||||
28: { x: 0, y: 1300 },
|
||||
29: { x: 505, y: 1300 },
|
||||
30: { x: 1010, y: 1300 },
|
||||
31: { x: 1515, y: 1300 },
|
||||
32: { x: -245, y: 1520 },
|
||||
33: { x: 255, y: 1520 },
|
||||
34: { x: 750, y: 1520 },
|
||||
35: { x: 1265, y: 1520 },
|
||||
36: { x: 0, y: 1690 },
|
||||
37: { x: 505, y: 1690 },
|
||||
38: { x: 1010, y: 1690 },
|
||||
39: { x: 1515, y: 1690 },
|
||||
40: { x: -245, y: 1880 },
|
||||
41: { x: 255, y: 1880 },
|
||||
42: { x: 750, y: 1880 },
|
||||
43: { x: 1265, y: 1880 },
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// ========== 网格坐标生成函数 ==========
|
||||
export function generateGridCoordinates(config = GRID_CONFIG) {
|
||||
const { rows, cols, startX, startY, spacingX, spacingY, staggered, staggerOffsetX, excludeRows } = config.grid
|
||||
const coords = []
|
||||
|
||||
// 先填充被排除行的占位符
|
||||
for (let row = 0; row < rows; row++) {
|
||||
if (excludeRows.includes(row)) {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
coords.push({
|
||||
x: 0,
|
||||
y: 0,
|
||||
row,
|
||||
col,
|
||||
index: coords.length
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for (let col = 0; col < cols; col++) {
|
||||
const offsetX = staggered && row % 2 === 1 ? staggerOffsetX : 0
|
||||
coords.push({
|
||||
x: startX + col * spacingX + offsetX,
|
||||
y: startY + row * spacingY,
|
||||
row,
|
||||
col,
|
||||
index: coords.length
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 应用手动微调
|
||||
Object.entries(config.manualAdjustments || {}).forEach(([index, adjustment]) => {
|
||||
const idx = parseInt(index)
|
||||
if (coords[idx]) {
|
||||
coords[idx].x = adjustment.x ?? coords[idx].x
|
||||
coords[idx].y = adjustment.y ?? coords[idx].y
|
||||
}
|
||||
})
|
||||
|
||||
return coords
|
||||
}
|
||||
231
frontend/pages/square/config/mockData.js
Normal file
231
frontend/pages/square/config/mockData.js
Normal file
@ -0,0 +1,231 @@
|
||||
// ========== 模拟数据配置 ==========
|
||||
|
||||
// 是否使用模拟数据(开发调试时设为 true,上线后改为 false)
|
||||
export const USE_MOCK_DATA = true
|
||||
|
||||
// 模拟图片列表
|
||||
const MOCK_IMAGES = [
|
||||
'/static/sucai/image-01.png',
|
||||
'/static/sucai/image-02.png',
|
||||
'/static/sucai/image-03.png',
|
||||
'/static/sucai/image-04.png',
|
||||
'/static/sucai/image-05.png',
|
||||
'/static/sucai/image-06.png',
|
||||
'/static/sucai/image-07.png',
|
||||
'/static/sucai/image-08.png',
|
||||
'/static/sucai/image-09.png',
|
||||
'/static/sucai/image-10.png',
|
||||
'/static/sucai/image-11.png',
|
||||
'/static/sucai/image-12.png',
|
||||
'/static/sucai/image-13.png',
|
||||
'/static/sucai/image-14.png',
|
||||
'/static/sucai/image-15.png',
|
||||
'/static/sucai/image-16.png',
|
||||
]
|
||||
|
||||
// 模拟昵称列表
|
||||
const NICKNAMES = [
|
||||
'小明', '小红', '小刚', '小芳', '小强', '小美', '小华', '小丽',
|
||||
'小杰', '小婷', '小宇', '小雪', '小晨', '小曦', '小雷', '小雯',
|
||||
'小风', '小月', '小星', '小云', '小河', '小涛', '小琳', '小瑶',
|
||||
]
|
||||
|
||||
// ========== 分类 span 阈值配置 ==========
|
||||
// 每个分类有不同的 span 计算规则
|
||||
export const SPAN_CONFIG = {
|
||||
// 人气王者:高点赞为主,大卡片多
|
||||
hot: {
|
||||
thresholds: [
|
||||
{ max: 30000, span: 1 }, // 3w 以下 → span 1
|
||||
{ max: 60000, span: 2 }, // 3w-6w → span 2
|
||||
{ max: 100000, span: 3 }, // 6w-10w → span 3
|
||||
{ max: Infinity, span: 4 }, // 10w+ → span 4
|
||||
]
|
||||
},
|
||||
// 潜力之星:中等点赞,中小卡片
|
||||
xingka: {
|
||||
thresholds: [
|
||||
{ max: 5000, span: 1 }, // 5k 以下 → span 1
|
||||
{ max: 10000, span: 2 }, // 5k-1w → span 2
|
||||
{ max: 15000, span: 3 }, // 1w-1.5w → span 3
|
||||
{ max: Infinity, span: 4 },// 1.5w+ → span 4
|
||||
]
|
||||
},
|
||||
// 新鲜上架:低点赞,小卡片为主
|
||||
baji: {
|
||||
thresholds: [
|
||||
{ max: 200, span: 1 }, // 200 以下 → span 1
|
||||
{ max: 500, span: 2 }, // 200-500 → span 2
|
||||
{ max: Infinity, span: 3 },// 500+ → span 3
|
||||
]
|
||||
},
|
||||
// 随机寻宝:混合
|
||||
haibao: {
|
||||
thresholds: [
|
||||
{ max: 10000, span: 1 }, // 1w 以下 → span 1
|
||||
{ max: 30000, span: 2 }, // 1w-3w → span 2
|
||||
{ max: 60000, span: 3 }, // 3w-6w → span 3
|
||||
{ max: Infinity, span: 4 },// 6w+ → span 4
|
||||
]
|
||||
},
|
||||
}
|
||||
|
||||
// 根据分类和点赞数计算 span
|
||||
export function calcSpan(category, likes) {
|
||||
const config = SPAN_CONFIG[category] || SPAN_CONFIG.hot
|
||||
for (const t of config.thresholds) {
|
||||
if (likes < t.max) return t.span
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
// ========== 人气王者 - 高点赞热门作品 ==========
|
||||
// span 由 WaterfallGrid._span() 根据 like_count 计算
|
||||
export const MOCK_RENQIWANG = {
|
||||
items: [
|
||||
{ asset_id: 10001, name: '星光璀璨', cover_url: MOCK_IMAGES[0], like_count: 125800, owner_nickname: '小甜心' },
|
||||
{ asset_id: 10002, name: '爱的绽放', cover_url: MOCK_IMAGES[1], like_count: 98600, owner_nickname: '爱豆粉' },
|
||||
{ asset_id: 10003, name: '温暖守护', cover_url: MOCK_IMAGES[2], like_count: 87200, owner_nickname: '星星控' },
|
||||
{ asset_id: 10004, name: '甜蜜暴击', cover_url: MOCK_IMAGES[3], like_count: 76800, owner_nickname: '追星族' },
|
||||
{ asset_id: 10005, name: '闪耀舞台', cover_url: MOCK_IMAGES[4], like_count: 65400, owner_nickname: '小太阳' },
|
||||
{ asset_id: 10006, name: '为你疯狂', cover_url: MOCK_IMAGES[5], like_count: 58900, owner_nickname: '小可爱' },
|
||||
{ asset_id: 10007, name: '心动时刻', cover_url: MOCK_IMAGES[6], like_count: 52100, owner_nickname: '小天使' },
|
||||
{ asset_id: 10008, name: '永相随', cover_url: MOCK_IMAGES[7], like_count: 48700, owner_nickname: '小甜豆' },
|
||||
{ asset_id: 10009, name: '粉红泡泡', cover_url: MOCK_IMAGES[8], like_count: 45600, owner_nickname: '小迷糊' },
|
||||
{ asset_id: 10010, name: '爱的力量', cover_url: MOCK_IMAGES[9], like_count: 42300, owner_nickname: '小幸运' },
|
||||
{ asset_id: 10011, name: '璀璨星河', cover_url: MOCK_IMAGES[10], like_count: 39800, owner_nickname: '小浪漫' },
|
||||
{ asset_id: 10012, name: '甜蜜日常', cover_url: MOCK_IMAGES[11], like_count: 37500, owner_nickname: '小清新' },
|
||||
{ asset_id: 10013, name: '爱心发射', cover_url: MOCK_IMAGES[12], like_count: 35200, owner_nickname: '小活力' },
|
||||
{ asset_id: 10014, name: '超级喜欢', cover_url: MOCK_IMAGES[13], like_count: 32900, owner_nickname: '小呆萌' },
|
||||
{ asset_id: 10015, name: '温暖拥抱', cover_url: MOCK_IMAGES[14], like_count: 30600, owner_nickname: '小棉花' },
|
||||
{ asset_id: 10016, name: '今日份心动', cover_url: MOCK_IMAGES[15], like_count: 28500, owner_nickname: '小牛奶' },
|
||||
],
|
||||
cursor: 'renqiwang_cursor_001',
|
||||
has_more: true,
|
||||
session_id: 'renqiwang_session',
|
||||
}
|
||||
|
||||
// ========== 潜力之星 - 中等点赞有潜力的作品 ==========
|
||||
export const MOCK_QIANLIXING = {
|
||||
items: [
|
||||
{ asset_id: 20001, name: '初露锋芒', cover_url: MOCK_IMAGES[0], like_count: 12800, owner_nickname: '小新芽' },
|
||||
{ asset_id: 20002, name: '蓄势待发', cover_url: MOCK_IMAGES[1], like_count: 11500, owner_nickname: '小嫩草' },
|
||||
{ asset_id: 20003, name: '冉冉升起', cover_url: MOCK_IMAGES[2], like_count: 10200, owner_nickname: '小泡泡' },
|
||||
{ asset_id: 20004, name: '明日之星', cover_url: MOCK_IMAGES[3], like_count: 9800, owner_nickname: '小火苗' },
|
||||
{ asset_id: 20005, name: '潜力无限', cover_url: MOCK_IMAGES[4], like_count: 8900, owner_nickname: '小萌芽' },
|
||||
{ asset_id: 20006, name: '闪耀新星', cover_url: MOCK_IMAGES[5], like_count: 8200, owner_nickname: '小水滴' },
|
||||
{ asset_id: 20007, name: '小荷才露', cover_url: MOCK_IMAGES[6], like_count: 7600, owner_nickname: '小竹笋' },
|
||||
{ asset_id: 20008, name: '锋芒初现', cover_url: MOCK_IMAGES[7], like_count: 7100, owner_nickname: '小鸽子' },
|
||||
{ asset_id: 20009, name: '闪闪发光', cover_url: MOCK_IMAGES[8], like_count: 6500, owner_nickname: '小萤火' },
|
||||
{ asset_id: 20010, name: '未来可期', cover_url: MOCK_IMAGES[9], like_count: 5900, owner_nickname: '小芽芽' },
|
||||
{ asset_id: 20011, name: '新秀登场', cover_url: MOCK_IMAGES[10], like_count: 5400, owner_nickname: '小藤蔓' },
|
||||
{ asset_id: 20012, name: '蒸蒸日上', cover_url: MOCK_IMAGES[11], like_count: 4900, owner_nickname: '小葵花' },
|
||||
{ asset_id: 20013, name: '茁壮成长', cover_url: MOCK_IMAGES[12], like_count: 4500, owner_nickname: '小苗苗' },
|
||||
{ asset_id: 20014, name: '初绽光芒', cover_url: MOCK_IMAGES[13], like_count: 4100, owner_nickname: '小花花' },
|
||||
{ asset_id: 20015, name: '星火燎原', cover_url: MOCK_IMAGES[14], like_count: 3800, owner_nickname: '小豆芽' },
|
||||
{ asset_id: 20016, name: '蓄力中...', cover_url: MOCK_IMAGES[15], like_count: 3500, owner_nickname: '小冰晶' },
|
||||
],
|
||||
cursor: 'qianlixing_cursor_001',
|
||||
has_more: true,
|
||||
session_id: 'qianlixing_session',
|
||||
}
|
||||
|
||||
// ========== 新鲜上架 - 新发布作品,点赞较低 ==========
|
||||
export const MOCK_XINXIANSHANG = {
|
||||
items: [
|
||||
{ asset_id: 30001, name: '刚刚发布', cover_url: MOCK_IMAGES[0], like_count: 128, owner_nickname: '新手小白' },
|
||||
{ asset_id: 30002, name: '今日新鲜', cover_url: MOCK_IMAGES[1], like_count: 256, owner_nickname: '小萌新' },
|
||||
{ asset_id: 30003, name: '刚出锅', cover_url: MOCK_IMAGES[2], like_count: 89, owner_nickname: '新来的' },
|
||||
{ asset_id: 30004, name: '热乎的', cover_url: MOCK_IMAGES[3], like_count: 167, owner_nickname: '小试牛刀' },
|
||||
{ asset_id: 30005, name: '新品上市', cover_url: MOCK_IMAGES[4], like_count: 234, owner_nickname: '初来乍到' },
|
||||
{ asset_id: 30006, name: '今日首发', cover_url: MOCK_IMAGES[5], like_count: 178, owner_nickname: '小透明' },
|
||||
{ asset_id: 30007, name: '刚出炉', cover_url: MOCK_IMAGES[6], like_count: 145, owner_nickname: '新手村' },
|
||||
{ asset_id: 30008, name: '最新创作', cover_url: MOCK_IMAGES[7], like_count: 312, owner_nickname: '小画师' },
|
||||
{ asset_id: 30009, name: '新鲜出炉', cover_url: MOCK_IMAGES[8], like_count: 98, owner_nickname: '小创作者' },
|
||||
{ asset_id: 30010, name: '首发作品', cover_url: MOCK_IMAGES[9], like_count: 267, owner_nickname: '小练手' },
|
||||
{ asset_id: 30011, name: '全新上线', cover_url: MOCK_IMAGES[10], like_count: 189, owner_nickname: '新起步' },
|
||||
{ asset_id: 30012, name: '今日份新', cover_url: MOCK_IMAGES[11], like_count: 156, owner_nickname: '小萌娃' },
|
||||
{ asset_id: 30013, name: '新新人类', cover_url: MOCK_IMAGES[12], like_count: 223, owner_nickname: '小新潮' },
|
||||
{ asset_id: 30014, name: '新鲜血液', cover_url: MOCK_IMAGES[13], like_count: 134, owner_nickname: '小白白' },
|
||||
{ asset_id: 30015, name: '新晋选手', cover_url: MOCK_IMAGES[14], like_count: 278, owner_nickname: '小小新' },
|
||||
{ asset_id: 30016, name: '首发新动态', cover_url: MOCK_IMAGES[15], like_count: 201, owner_nickname: '小清新' },
|
||||
],
|
||||
cursor: 'xinxianshang_cursor_001',
|
||||
has_more: true,
|
||||
session_id: 'xinxianshang_session',
|
||||
}
|
||||
|
||||
// ========== 随机寻宝 - 随机混合数据 ==========
|
||||
export const MOCK_SUIJIXUNBAO = {
|
||||
items: [
|
||||
{ asset_id: 40001, name: '神秘宝藏1', cover_url: MOCK_IMAGES[0], like_count: 45000, owner_nickname: '寻宝达人' },
|
||||
{ asset_id: 40002, name: '神秘宝藏2', cover_url: MOCK_IMAGES[1], like_count: 32000, owner_nickname: '探险家' },
|
||||
{ asset_id: 40003, name: '神秘宝藏3', cover_url: MOCK_IMAGES[2], like_count: 78000, owner_nickname: '淘宝高手' },
|
||||
{ asset_id: 40004, name: '神秘宝藏4', cover_url: MOCK_IMAGES[3], like_count: 1500, owner_nickname: '捡漏王' },
|
||||
{ asset_id: 40005, name: '神秘宝藏5', cover_url: MOCK_IMAGES[4], like_count: 8500, owner_nickname: '挖宝专家' },
|
||||
{ asset_id: 40006, name: '神秘宝藏6', cover_url: MOCK_IMAGES[5], like_count: 55000, owner_nickname: '收藏家' },
|
||||
{ asset_id: 40007, name: '神秘宝藏7', cover_url: MOCK_IMAGES[6], like_count: 200, owner_nickname: '淘宝达人' },
|
||||
{ asset_id: 40008, name: '神秘宝藏8', cover_url: MOCK_IMAGES[7], like_count: 12000, owner_nickname: '猎奇者' },
|
||||
{ asset_id: 40009, name: '神秘宝藏9', cover_url: MOCK_IMAGES[8], like_count: 62000, owner_nickname: '寻宝奇兵' },
|
||||
{ asset_id: 40010, name: '神秘宝藏10', cover_url: MOCK_IMAGES[9], like_count: 800, owner_nickname: '淘宝猎人' },
|
||||
{ asset_id: 40011, name: '神秘宝藏11', cover_url: MOCK_IMAGES[10], like_count: 9500, owner_nickname: '挖宝小分队' },
|
||||
{ asset_id: 40012, name: '神秘宝藏12', cover_url: MOCK_IMAGES[11], like_count: 72000, owner_nickname: '淘宝小能手' },
|
||||
{ asset_id: 40013, name: '神秘宝藏13', cover_url: MOCK_IMAGES[12], like_count: 350, owner_nickname: '宝藏猎人' },
|
||||
{ asset_id: 40014, name: '神秘宝藏14', cover_url: MOCK_IMAGES[13], like_count: 48000, owner_nickname: '寻宝小天才' },
|
||||
{ asset_id: 40015, name: '神秘宝藏15', cover_url: MOCK_IMAGES[14], like_count: 11000, owner_nickname: '淘宝小精灵' },
|
||||
{ asset_id: 40016, name: '神秘宝藏16', cover_url: MOCK_IMAGES[15], like_count: 600, owner_nickname: '挖宝小专家' },
|
||||
],
|
||||
cursor: 'suijixunbao_cursor_001',
|
||||
has_more: true,
|
||||
session_id: 'suijixunbao_session',
|
||||
}
|
||||
|
||||
// ========== 分类映射 ==========
|
||||
export const MOCK_DATA_MAP = {
|
||||
hot: MOCK_RENQIWANG,
|
||||
xingka: MOCK_QIANLIXING,
|
||||
baji: MOCK_XINXIANSHANG,
|
||||
haibao: MOCK_SUIJIXUNBAO,
|
||||
}
|
||||
|
||||
// 根据分类获取模拟数据
|
||||
export function getMockDataByCategory(category) {
|
||||
return MOCK_DATA_MAP[category] || MOCK_RENQIWANG
|
||||
}
|
||||
|
||||
// 随机生成更多模拟数据(用于追加)
|
||||
export function generateMockItems(category = 'hot', count = 20, startId = 50000) {
|
||||
const items = []
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const id = startId + i
|
||||
let like_count
|
||||
|
||||
// 根据分类生成不同的点赞数范围
|
||||
switch (category) {
|
||||
case 'hot':
|
||||
like_count = 20000 + Math.floor(Math.random() * 100000) // 2w-12w
|
||||
break
|
||||
case 'xingka':
|
||||
like_count = 3000 + Math.floor(Math.random() * 15000) // 3k-18k
|
||||
break
|
||||
case 'baji':
|
||||
like_count = 50 + Math.floor(Math.random() * 500) // 50-550
|
||||
break
|
||||
case 'haibao':
|
||||
like_count = Math.floor(Math.random() * 80000) // 0-8w
|
||||
break
|
||||
default:
|
||||
like_count = Math.floor(Math.random() * 50000)
|
||||
}
|
||||
|
||||
items.push({
|
||||
asset_id: id,
|
||||
name: `${category}_作品${id}`,
|
||||
cover_url: MOCK_IMAGES[i % MOCK_IMAGES.length],
|
||||
like_count,
|
||||
owner_nickname: NICKNAMES[i % NICKNAMES.length],
|
||||
})
|
||||
}
|
||||
return items
|
||||
}
|
||||
@ -1,748 +0,0 @@
|
||||
<template>
|
||||
<view
|
||||
class="debug-container"
|
||||
@touchstart="onTouchStart"
|
||||
@touchmove="onTouchMove"
|
||||
@touchend="onTouchEnd"
|
||||
>
|
||||
<!-- 背景条(3张图并排,无限滚动) -->
|
||||
<view class="background-strip" :style="backgroundStripStyle">
|
||||
<image
|
||||
v-for="i in 3"
|
||||
:key="i"
|
||||
class="background-tile"
|
||||
:style="{ width: tileWidth + 'px', height: '100%' }"
|
||||
src="/static/background/mainbg.png"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<!-- 网格点层 -->
|
||||
<view class="grid-layer" :style="gridLayerStyle">
|
||||
<!-- 渲染 5 个 tile 的网格点(n=0,1,2,3,4) -->
|
||||
<view
|
||||
v-for="point in visiblePoints"
|
||||
:key="point.key"
|
||||
class="grid-point"
|
||||
:class="{
|
||||
'grid-point--excluded': point.isExcluded,
|
||||
'grid-point--selected': selectedIndex === point.originalIndex
|
||||
}"
|
||||
:style="{
|
||||
left: point.x + 'px',
|
||||
top: point.y + 'px',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}"
|
||||
@click="selectPoint(point.originalIndex)"
|
||||
>
|
||||
<view class="grid-dot"></view>
|
||||
<text class="grid-label">{{ point.originalIndex }}</text>
|
||||
<text class="grid-coords">({{ Math.round(point.originalX) }}, {{ Math.round(point.originalY) }})</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 控制面板 -->
|
||||
<view class="control-panel" @click.stop>
|
||||
<view class="panel-header">
|
||||
<text class="panel-title">网格调试工具</text>
|
||||
<button class="btn-close" @click="goBack">返回</button>
|
||||
</view>
|
||||
|
||||
<!-- 单点调试模式 -->
|
||||
<view v-if="selectedIndex !== null" class="point-edit-section">
|
||||
<view class="point-edit-header">
|
||||
<text class="point-edit-title">编辑点位 #{{ selectedIndex }}</text>
|
||||
<button class="btn-deselect" @click="deselectPoint">取消选择</button>
|
||||
</view>
|
||||
|
||||
<view class="point-edit-controls">
|
||||
<view class="point-edit-row">
|
||||
<text class="point-edit-label">X: {{ currentPoint.x }}</text>
|
||||
<view class="point-edit-buttons">
|
||||
<button class="btn-adjust" @click="adjustPoint('x', -10)">-10</button>
|
||||
<button class="btn-adjust" @click="adjustPoint('x', -1)">-1</button>
|
||||
<button class="btn-adjust" @click="adjustPoint('x', 1)">+1</button>
|
||||
<button class="btn-adjust" @click="adjustPoint('x', 10)">+10</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="point-edit-row">
|
||||
<text class="point-edit-label">Y: {{ currentPoint.y }}</text>
|
||||
<view class="point-edit-buttons">
|
||||
<button class="btn-adjust" @click="adjustPoint('y', -10)">-10</button>
|
||||
<button class="btn-adjust" @click="adjustPoint('y', -1)">-1</button>
|
||||
<button class="btn-adjust" @click="adjustPoint('y', 1)">+1</button>
|
||||
<button class="btn-adjust" @click="adjustPoint('y', 10)">+10</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="point-edit-row">
|
||||
<button class="btn-save-point" @click="savePointAdjustment">保存此点位调整</button>
|
||||
<button class="btn-reset-point" @click="resetPoint">重置此点位</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 全局参数调整 -->
|
||||
<view v-else class="global-controls">
|
||||
<view class="control-section">
|
||||
<text class="control-label">起始Y (startY): {{ config.grid.startY }}</text>
|
||||
<slider
|
||||
:value="config.grid.startY"
|
||||
@change="updateStartY"
|
||||
min="-200"
|
||||
max="100"
|
||||
step="10"
|
||||
show-value
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="control-section">
|
||||
<text class="control-label">垂直间距 (spacingY): {{ config.grid.spacingY }}</text>
|
||||
<slider
|
||||
:value="config.grid.spacingY"
|
||||
@change="updateSpacingY"
|
||||
min="150"
|
||||
max="250"
|
||||
step="5"
|
||||
show-value
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="control-section">
|
||||
<text class="control-label">水平间距 (spacingX): {{ config.grid.spacingX }}</text>
|
||||
<slider
|
||||
:value="config.grid.spacingX"
|
||||
@change="updateSpacingX"
|
||||
min="450"
|
||||
max="600"
|
||||
step="5"
|
||||
show-value
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view class="control-section">
|
||||
<text class="control-label">交错偏移 (staggerOffsetX): {{ config.grid.staggerOffsetX }}</text>
|
||||
<slider
|
||||
:value="config.grid.staggerOffsetX"
|
||||
@change="updateStaggerOffsetX"
|
||||
min="200"
|
||||
max="350"
|
||||
step="5"
|
||||
show-value
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="control-section">
|
||||
<button class="btn-export" @click="exportConfig">导出配置</button>
|
||||
<button class="btn-reset" @click="resetConfig">重置全部</button>
|
||||
</view>
|
||||
|
||||
<view class="coords-list">
|
||||
<text class="coords-title">生成的坐标列表(点击点位编辑):</text>
|
||||
<scroll-view class="coords-scroll" scroll-y>
|
||||
<view
|
||||
v-for="(coord, i) in originalCoords"
|
||||
:key="i"
|
||||
class="coord-item"
|
||||
:class="{ 'coord-item--selected': selectedIndex === i }"
|
||||
@click="selectPoint(i)"
|
||||
>
|
||||
<text class="coord-index">{{ i }}:</text>
|
||||
<text class="coord-value">[{{ coord.x }}, {{ coord.y }}]</text>
|
||||
<text v-if="coord.x === 0 && coord.y === 0" class="coord-excluded">(排除)</text>
|
||||
<text v-if="hasAdjustment(i)" class="coord-adjusted">(已调整)</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 通用确认弹窗 -->
|
||||
<ConfirmModal
|
||||
:visible="confirmModal.visible"
|
||||
:title="confirmModal.title"
|
||||
:content="confirmModal.content"
|
||||
:confirmText="confirmModal.confirmText"
|
||||
:cancelText="confirmModal.cancelText"
|
||||
:showCancel="confirmModal.showCancel"
|
||||
@confirm="onConfirmModal"
|
||||
@cancel="onCancelModal"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { GRID_CONFIG, generateGridCoordinates, IMAGE_W, IMAGE_H } from './config/cabin.js'
|
||||
import ConfirmModal from '@/components/ConfirmModal.vue'
|
||||
|
||||
const config = ref(JSON.parse(JSON.stringify(GRID_CONFIG)))
|
||||
const selectedIndex = ref(null)
|
||||
|
||||
// 屏幕和背景尺寸
|
||||
const screenWidth = ref(375)
|
||||
const screenHeight = ref(812)
|
||||
const tileWidth = ref(375)
|
||||
const scale = ref(1)
|
||||
|
||||
// 滑动相关
|
||||
const bgOffsetX = ref(0)
|
||||
let touchStartX = 0
|
||||
|
||||
// 通用确认弹窗状态
|
||||
const confirmModal = ref({
|
||||
visible: false,
|
||||
title: '',
|
||||
content: '',
|
||||
confirmText: '确认',
|
||||
cancelText: '取消',
|
||||
showCancel: true,
|
||||
confirmCallback: null
|
||||
})
|
||||
|
||||
// 弹窗确认回调
|
||||
const onConfirmModal = () => {
|
||||
if (confirmModal.value.confirmCallback) {
|
||||
confirmModal.value.confirmCallback({ confirm: true })
|
||||
}
|
||||
confirmModal.value.visible = false
|
||||
}
|
||||
|
||||
// 弹窗取消回调
|
||||
const onCancelModal = () => {
|
||||
if (confirmModal.value.confirmCallback) {
|
||||
confirmModal.value.confirmCallback({ confirm: false })
|
||||
}
|
||||
confirmModal.value.visible = false
|
||||
}
|
||||
|
||||
// 显示通用确认弹窗
|
||||
const showConfirmModal = (options) => {
|
||||
confirmModal.value = {
|
||||
visible: true,
|
||||
title: options.title || '',
|
||||
content: options.content || '',
|
||||
confirmText: options.confirmText || '确认',
|
||||
cancelText: options.cancelText || '取消',
|
||||
showCancel: options.showCancel !== false,
|
||||
confirmCallback: options.success || null
|
||||
}
|
||||
}
|
||||
|
||||
// 原始坐标(未缩放)
|
||||
const originalCoords = computed(() => {
|
||||
return generateGridCoordinates(config.value)
|
||||
})
|
||||
|
||||
// 缩放后的坐标
|
||||
const scaledCoords = computed(() => {
|
||||
return originalCoords.value.map(coord => ({
|
||||
x: coord.x * scale.value,
|
||||
y: coord.y * scale.value,
|
||||
originalX: coord.x,
|
||||
originalY: coord.y,
|
||||
isExcluded: coord.x === 0 && coord.y === 0
|
||||
}))
|
||||
})
|
||||
|
||||
// 可见的网格点(5个tile)
|
||||
const visiblePoints = computed(() => {
|
||||
const w = tileWidth.value
|
||||
const points = []
|
||||
|
||||
for (let n = 0; n <= 4; n++) {
|
||||
scaledCoords.value.forEach((coord, i) => {
|
||||
points.push({
|
||||
key: `${i}-${n}`,
|
||||
x: coord.x + n * w,
|
||||
y: coord.y,
|
||||
originalX: coord.originalX,
|
||||
originalY: coord.originalY,
|
||||
originalIndex: i,
|
||||
isExcluded: coord.isExcluded
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return points
|
||||
})
|
||||
|
||||
// 背景条样式
|
||||
const backgroundStripStyle = computed(() => {
|
||||
const centerOffset = (screenWidth.value - tileWidth.value) / 2
|
||||
return {
|
||||
width: `${tileWidth.value * 3}px`,
|
||||
transform: `translateX(${-tileWidth.value + centerOffset + bgOffsetX.value}px)`
|
||||
}
|
||||
})
|
||||
|
||||
// 网格层样式
|
||||
const gridLayerStyle = computed(() => {
|
||||
const centerOffset = (screenWidth.value - tileWidth.value) / 2
|
||||
return {
|
||||
transform: `translateX(${-tileWidth.value + centerOffset + bgOffsetX.value}px)`
|
||||
}
|
||||
})
|
||||
|
||||
// 当前选中点的坐标
|
||||
const currentPoint = computed(() => {
|
||||
if (selectedIndex.value === null) return { x: 0, y: 0 }
|
||||
return originalCoords.value[selectedIndex.value] || { x: 0, y: 0 }
|
||||
})
|
||||
|
||||
// 归一化偏移量
|
||||
const clampOffset = (offset) => {
|
||||
const w = tileWidth.value
|
||||
return (((offset % w) + w) % w) - w
|
||||
}
|
||||
|
||||
// 触摸事件
|
||||
const onTouchStart = (e) => {
|
||||
touchStartX = e.touches[0].clientX
|
||||
lastMoveX = touchStartX
|
||||
}
|
||||
|
||||
const onTouchMove = (e) => {
|
||||
e.preventDefault()
|
||||
const currentX = e.touches[0].clientX
|
||||
const delta = currentX - lastMoveX
|
||||
lastMoveX = currentX
|
||||
|
||||
bgOffsetX.value = clampOffset(bgOffsetX.value + delta)
|
||||
}
|
||||
|
||||
const onTouchEnd = () => {
|
||||
// 简化处理,不添加惯性
|
||||
}
|
||||
|
||||
const selectPoint = (index) => {
|
||||
selectedIndex.value = index
|
||||
console.log('选中点位:', index, originalCoords.value[index])
|
||||
}
|
||||
|
||||
const deselectPoint = () => {
|
||||
selectedIndex.value = null
|
||||
}
|
||||
|
||||
const adjustPoint = (axis, delta) => {
|
||||
if (selectedIndex.value === null) return
|
||||
|
||||
const idx = selectedIndex.value
|
||||
if (!config.value.manualAdjustments) {
|
||||
config.value.manualAdjustments = {}
|
||||
}
|
||||
|
||||
const current = originalCoords.value[idx]
|
||||
|
||||
if (!config.value.manualAdjustments[idx]) {
|
||||
config.value.manualAdjustments[idx] = { x: current.x, y: current.y }
|
||||
}
|
||||
|
||||
if (axis === 'x') {
|
||||
config.value.manualAdjustments[idx].x += delta
|
||||
} else {
|
||||
config.value.manualAdjustments[idx].y += delta
|
||||
}
|
||||
|
||||
config.value = { ...config.value }
|
||||
}
|
||||
|
||||
const savePointAdjustment = () => {
|
||||
uni.showToast({
|
||||
title: `点位 #${selectedIndex.value} 已保存`,
|
||||
icon: 'success'
|
||||
})
|
||||
console.log('保存点位调整:', selectedIndex.value, config.value.manualAdjustments[selectedIndex.value])
|
||||
}
|
||||
|
||||
const resetPoint = () => {
|
||||
if (selectedIndex.value === null) return
|
||||
|
||||
if (config.value.manualAdjustments && config.value.manualAdjustments[selectedIndex.value]) {
|
||||
delete config.value.manualAdjustments[selectedIndex.value]
|
||||
config.value = { ...config.value }
|
||||
uni.showToast({
|
||||
title: '点位已重置',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const hasAdjustment = (index) => {
|
||||
return config.value.manualAdjustments && config.value.manualAdjustments[index]
|
||||
}
|
||||
|
||||
const updateStartY = (e) => {
|
||||
config.value.grid.startY = e.detail.value
|
||||
}
|
||||
|
||||
const updateSpacingY = (e) => {
|
||||
config.value.grid.spacingY = e.detail.value
|
||||
}
|
||||
|
||||
const updateSpacingX = (e) => {
|
||||
config.value.grid.spacingX = e.detail.value
|
||||
}
|
||||
|
||||
const updateStaggerOffsetX = (e) => {
|
||||
config.value.grid.staggerOffsetX = e.detail.value
|
||||
}
|
||||
|
||||
const exportConfig = () => {
|
||||
const configStr = JSON.stringify(config.value, null, 2)
|
||||
console.log('========== 当前配置 ==========')
|
||||
console.log(configStr)
|
||||
|
||||
const coordsCode = originalCoords.value.map(c => ` [${c.x}, ${c.y}]`).join(',\n')
|
||||
console.log('\n========== 坐标数组 ==========')
|
||||
console.log('[\n' + coordsCode + '\n]')
|
||||
|
||||
if (config.value.manualAdjustments && Object.keys(config.value.manualAdjustments).length > 0) {
|
||||
console.log('\n========== 手动调整 ==========')
|
||||
console.log('manualAdjustments: {')
|
||||
Object.entries(config.value.manualAdjustments).forEach(([idx, adj]) => {
|
||||
console.log(` ${idx}: { x: ${adj.x}, y: ${adj.y} },`)
|
||||
})
|
||||
console.log('}')
|
||||
}
|
||||
|
||||
showConfirmModal({
|
||||
title: '配置已导出',
|
||||
content: '请查看控制台输出,复制到 config/cabin.js',
|
||||
showCancel: false
|
||||
})
|
||||
}
|
||||
|
||||
const resetConfig = () => {
|
||||
showConfirmModal({
|
||||
title: '确认重置',
|
||||
content: '将重置所有参数和调整,是否继续?',
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
config.value = JSON.parse(JSON.stringify(GRID_CONFIG))
|
||||
selectedIndex.value = null
|
||||
uni.showToast({
|
||||
title: '已重置',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const goBack = () => {
|
||||
uni.navigateBack()
|
||||
}
|
||||
|
||||
// 初始化
|
||||
const init = () => {
|
||||
const info = uni.getSystemInfoSync()
|
||||
screenWidth.value = info.windowWidth
|
||||
screenHeight.value = info.windowHeight
|
||||
tileWidth.value = Math.round(info.windowHeight * (IMAGE_W / IMAGE_H))
|
||||
scale.value = info.windowHeight / IMAGE_H
|
||||
|
||||
console.log('调试页面初始化:', {
|
||||
screenWidth: screenWidth.value,
|
||||
screenHeight: screenHeight.value,
|
||||
tileWidth: tileWidth.value,
|
||||
scale: scale.value
|
||||
})
|
||||
}
|
||||
|
||||
init()
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.debug-container {
|
||||
position: relative;
|
||||
width: 100vw;
|
||||
min-height: calc(100vh + 650rpx);
|
||||
overflow: hidden;
|
||||
background: #1a1a1a;
|
||||
padding-bottom: 650rpx;
|
||||
}
|
||||
|
||||
.background-strip {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
z-index: 0;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.background-tile {
|
||||
flex-shrink: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.grid-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.grid-point {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 10;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.grid-point--excluded {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.grid-dot {
|
||||
width: 20rpx;
|
||||
height: 20rpx;
|
||||
background: #ff0000;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #ffffff;
|
||||
box-shadow: 0 0 10rpx rgba(255, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.grid-point--selected .grid-dot {
|
||||
background: #00ff00;
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
box-shadow: 0 0 20rpx rgba(0, 255, 0, 1);
|
||||
}
|
||||
|
||||
.grid-label {
|
||||
font-size: 20rpx;
|
||||
color: #ffffff;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 2rpx 8rpx;
|
||||
border-radius: 4rpx;
|
||||
margin-top: 4rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.grid-point--selected .grid-label {
|
||||
background: rgba(0, 255, 0, 0.9);
|
||||
color: #000000;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.grid-coords {
|
||||
font-size: 18rpx;
|
||||
color: #00ff00;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 2rpx 6rpx;
|
||||
border-radius: 4rpx;
|
||||
margin-top: 2rpx;
|
||||
}
|
||||
|
||||
.control-panel {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: #2a2a2a;
|
||||
border-top: 2rpx solid #444;
|
||||
padding: 20rpx;
|
||||
max-height: 600rpx;
|
||||
overflow-y: auto;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
font-size: 32rpx;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
padding: 10rpx 20rpx;
|
||||
background: #ff4444;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.control-section {
|
||||
margin-bottom: 30rpx;
|
||||
}
|
||||
|
||||
.control-label {
|
||||
display: block;
|
||||
font-size: 26rpx;
|
||||
color: #ffffff;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.btn-export,
|
||||
.btn-reset {
|
||||
width: 48%;
|
||||
padding: 20rpx;
|
||||
background: #4CAF50;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
margin-right: 4%;
|
||||
}
|
||||
|
||||
.btn-reset {
|
||||
background: #ff9800;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.coords-list {
|
||||
margin-top: 30rpx;
|
||||
background: #1a1a1a;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.coords-title {
|
||||
display: block;
|
||||
font-size: 28rpx;
|
||||
color: #ffffff;
|
||||
margin-bottom: 10rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.coords-scroll {
|
||||
max-height: 400rpx;
|
||||
}
|
||||
|
||||
.coord-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 8rpx 0;
|
||||
border-bottom: 1rpx solid #333;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.coord-item--selected {
|
||||
background: rgba(0, 255, 0, 0.2);
|
||||
}
|
||||
|
||||
.coord-index {
|
||||
font-size: 24rpx;
|
||||
color: #00ff00;
|
||||
width: 80rpx;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.coord-value {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.coord-excluded {
|
||||
font-size: 20rpx;
|
||||
color: #ff0000;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.coord-adjusted {
|
||||
font-size: 20rpx;
|
||||
color: #00ff00;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.point-edit-section {
|
||||
background: #1a1a1a;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.point-edit-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.point-edit-title {
|
||||
font-size: 28rpx;
|
||||
color: #00ff00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.btn-deselect {
|
||||
padding: 8rpx 16rpx;
|
||||
background: #666;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 6rpx;
|
||||
font-size: 22rpx;
|
||||
}
|
||||
|
||||
.point-edit-controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 15rpx;
|
||||
}
|
||||
|
||||
.point-edit-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.point-edit-label {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.point-edit-buttons {
|
||||
display: flex;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.btn-adjust {
|
||||
flex: 1;
|
||||
padding: 15rpx;
|
||||
background: #4CAF50;
|
||||
color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 6rpx;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.btn-save-point,
|
||||
.btn-reset-point {
|
||||
width: 48%;
|
||||
padding: 20rpx;
|
||||
border: none;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.btn-save-point {
|
||||
background: #2196F3;
|
||||
margin-right: 4%;
|
||||
}
|
||||
|
||||
.btn-reset-point {
|
||||
background: #ff5722;
|
||||
}
|
||||
|
||||
.global-controls {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
</style>
|
||||
@ -8,6 +8,8 @@
|
||||
:screenWidth="screenWidth"
|
||||
:screenHeight="screenHeight"
|
||||
:bannerBottom="bannerBottomPx"
|
||||
:useMockData="USE_MOCK_DATA"
|
||||
:category="activeContentTab"
|
||||
@cardClick="handleCardClick"
|
||||
class="fall-bg"
|
||||
/>
|
||||
@ -68,6 +70,7 @@ import WaterfallGrid from './components/WaterfallGrid.vue'
|
||||
import ContentTabs from './components/ContentTabs.vue'
|
||||
import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
|
||||
import { useBanner } from './composables/useBanner.js'
|
||||
import { USE_MOCK_DATA } from './config/mockData.js'
|
||||
|
||||
// ========== Store & User Info ==========
|
||||
const store = useStore()
|
||||
@ -78,7 +81,6 @@ const currentStarId = ref(uni.getStorageSync('star_id') || null)
|
||||
const activeContentTab = ref('hot')
|
||||
const navExpanded = ref(false)
|
||||
const showRankingModal = ref(false)
|
||||
const isDev = ref(false) // 开发模式,显示调试按钮
|
||||
|
||||
// ========== Screen Info ==========
|
||||
const screenWidth = ref(375)
|
||||
@ -143,12 +145,6 @@ const handleTabChange = (newTab) => {
|
||||
}
|
||||
}
|
||||
|
||||
const openDebugGrid = () => {
|
||||
uni.navigateTo({
|
||||
url: '/pages/square/debug-grid'
|
||||
})
|
||||
}
|
||||
|
||||
// ========== Tile Change Callback ==========
|
||||
const handleTileChange = () => {}
|
||||
|
||||
@ -282,25 +278,4 @@ onUnmounted(() => {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.debug-btn {
|
||||
position: fixed;
|
||||
bottom: 200rpx;
|
||||
right: 20rpx;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background: rgba(255, 0, 0, 0.8);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 999;
|
||||
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.debug-text {
|
||||
font-size: 24rpx;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// API 基础配置
|
||||
const baseURL = 'http://101.132.250.62:8080'
|
||||
// const baseURL = 'http://192.168.110.60:8080'
|
||||
// const baseURL = 'http://101.132.250.62:8080'
|
||||
const baseURL = 'http://192.168.110.60:8080'
|
||||
// const baseURL = 'http://localhost:8080'
|
||||
|
||||
// 是否使用模拟数据(开发调试时设为 true,后端API准备好后改为 false)
|
||||
@ -607,3 +607,22 @@ export function getMyLikedAssetsApi(page = 1, pageSize = 20) {
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
|
||||
// ==================== 灵感瀑布相关接口 ====================
|
||||
|
||||
// 获取灵感瀑布藏品列表
|
||||
export function getInspirationFlowApi(params) {
|
||||
return request({
|
||||
url: '/api/v1/inspiration-flow',
|
||||
method: 'GET',
|
||||
data: params
|
||||
})
|
||||
}
|
||||
|
||||
// 批量获取OSS预签名URL(用于读取目录下的图片)
|
||||
export function getBatchOssPresignedUrlsApi(files, expires = 3600, type = 'asset') {
|
||||
return request({
|
||||
url: `/api/v1/assets/oss/batch-presigned-urls?files=${encodeURIComponent(JSON.stringify(files))}&expires=${expires}&type=${type}`,
|
||||
method: 'GET'
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user