feat: 新增个人页
This commit is contained in:
parent
36e8f251e2
commit
f47a3b2ce4
@ -634,3 +634,86 @@ result := &dto.GetMyExhibitedAssetsResponseDTO{
|
|||||||
|
|
||||||
response.Success(c, result)
|
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,
|
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"` // 总数量
|
Total int64 `json:"total"` // 总数量
|
||||||
HasMore bool `json:"has_more"` // 是否有更多
|
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) // 解锁/购买新展位
|
galleries.POST("/slots_unlock", galleryCtrl.UnlockSlot) // 解锁/购买新展位
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 灵感瀑布相关路由(需要认证)
|
||||||
|
inspirationFlow := v1.Group("/inspiration-flow")
|
||||||
|
inspirationFlow.Use(middleware.AuthMiddleware())
|
||||||
|
{
|
||||||
|
inspirationFlow.GET("", galleryCtrl.GetInspirationFlow) // 获取灵感瀑布藏品列表
|
||||||
|
}
|
||||||
|
|
||||||
// 我的展馆路由(需要认证)
|
// 我的展馆路由(需要认证)
|
||||||
mygalleries := v1.Group("/mygalleries")
|
mygalleries := v1.Group("/mygalleries")
|
||||||
mygalleries.Use(middleware.AuthMiddleware())
|
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
|
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
|
var File_gallery_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_gallery_proto_rawDesc = "" +
|
const file_gallery_proto_rawDesc = "" +
|
||||||
@ -1310,7 +1586,30 @@ const file_gallery_proto_rawDesc = "" +
|
|||||||
"like_count\x18\x04 \x01(\x05R\tlikeCount\x12!\n" +
|
"like_count\x18\x04 \x01(\x05R\tlikeCount\x12!\n" +
|
||||||
"\fexhibited_at\x18\x05 \x01(\x03R\vexhibitedAt\x12\x1b\n" +
|
"\fexhibited_at\x18\x05 \x01(\x03R\vexhibitedAt\x12\x1b\n" +
|
||||||
"\texpire_at\x18\x06 \x01(\x03R\bexpireAt\x12\x1a\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" +
|
"\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" +
|
"\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" +
|
"\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" +
|
"\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" +
|
"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" +
|
"\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 (
|
var (
|
||||||
file_gallery_proto_rawDescOnce sync.Once
|
file_gallery_proto_rawDescOnce sync.Once
|
||||||
@ -1333,7 +1633,7 @@ func file_gallery_proto_rawDescGZIP() []byte {
|
|||||||
return file_gallery_proto_rawDescData
|
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{
|
var file_gallery_proto_goTypes = []any{
|
||||||
(*GetMyGalleryRequest)(nil), // 0: topfans.gallery.GetMyGalleryRequest
|
(*GetMyGalleryRequest)(nil), // 0: topfans.gallery.GetMyGalleryRequest
|
||||||
(*GetMyGalleryResponse)(nil), // 1: topfans.gallery.GetMyGalleryResponse
|
(*GetMyGalleryResponse)(nil), // 1: topfans.gallery.GetMyGalleryResponse
|
||||||
@ -1355,41 +1655,50 @@ var file_gallery_proto_goTypes = []any{
|
|||||||
(*GetMyExhibitedAssetsResponse)(nil), // 17: topfans.gallery.GetMyExhibitedAssetsResponse
|
(*GetMyExhibitedAssetsResponse)(nil), // 17: topfans.gallery.GetMyExhibitedAssetsResponse
|
||||||
(*ExhibitedAssetsData)(nil), // 18: topfans.gallery.ExhibitedAssetsData
|
(*ExhibitedAssetsData)(nil), // 18: topfans.gallery.ExhibitedAssetsData
|
||||||
(*ExhibitedAssetItem)(nil), // 19: topfans.gallery.ExhibitedAssetItem
|
(*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{
|
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
|
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
|
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
|
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
|
13, // 7: topfans.gallery.UnlockSlotResponse.data:type_name -> topfans.gallery.UnlockSlotData
|
||||||
9, // 8: topfans.gallery.GalleryData.slots:type_name -> topfans.gallery.SlotInfo
|
9, // 8: topfans.gallery.GalleryData.slots:type_name -> topfans.gallery.SlotInfo
|
||||||
10, // 9: topfans.gallery.SlotInfo.asset:type_name -> topfans.gallery.AssetInfo
|
10, // 9: topfans.gallery.SlotInfo.asset:type_name -> topfans.gallery.AssetInfo
|
||||||
11, // 10: topfans.gallery.SlotInfo.unlock_condition:type_name -> topfans.gallery.UnlockCondition
|
11, // 10: topfans.gallery.SlotInfo.unlock_condition:type_name -> topfans.gallery.UnlockCondition
|
||||||
20, // 11: topfans.gallery.RemoveFromSlotResponse.base:type_name -> topfans.common.BaseResponse
|
24, // 11: topfans.gallery.RemoveFromSlotResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
20, // 12: topfans.gallery.GetMyExhibitedAssetsResponse.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
|
18, // 13: topfans.gallery.GetMyExhibitedAssetsResponse.data:type_name -> topfans.gallery.ExhibitedAssetsData
|
||||||
19, // 14: topfans.gallery.ExhibitedAssetsData.items:type_name -> topfans.gallery.ExhibitedAssetItem
|
19, // 14: topfans.gallery.ExhibitedAssetsData.items:type_name -> topfans.gallery.ExhibitedAssetItem
|
||||||
0, // 15: topfans.gallery.GalleryService.GetMyGallery:input_type -> topfans.gallery.GetMyGalleryRequest
|
24, // 15: topfans.gallery.GetInspirationFlowResponse.base:type_name -> topfans.common.BaseResponse
|
||||||
2, // 16: topfans.gallery.GalleryService.GetUserGallery:input_type -> topfans.gallery.GetUserGalleryRequest
|
22, // 16: topfans.gallery.GetInspirationFlowResponse.data:type_name -> topfans.gallery.InspirationFlowData
|
||||||
4, // 17: topfans.gallery.GalleryService.PlaceAsset:input_type -> topfans.gallery.PlaceAssetRequest
|
23, // 17: topfans.gallery.InspirationFlowData.items:type_name -> topfans.gallery.InspirationFlowItem
|
||||||
6, // 18: topfans.gallery.GalleryService.UnlockSlot:input_type -> topfans.gallery.UnlockSlotRequest
|
0, // 18: topfans.gallery.GalleryService.GetMyGallery:input_type -> topfans.gallery.GetMyGalleryRequest
|
||||||
14, // 19: topfans.gallery.GalleryService.RemoveFromSlot:input_type -> topfans.gallery.RemoveFromSlotRequest
|
2, // 19: topfans.gallery.GalleryService.GetUserGallery:input_type -> topfans.gallery.GetUserGalleryRequest
|
||||||
16, // 20: topfans.gallery.GalleryService.GetMyExhibitedAssets:input_type -> topfans.gallery.GetMyExhibitedAssetsRequest
|
4, // 20: topfans.gallery.GalleryService.PlaceAsset:input_type -> topfans.gallery.PlaceAssetRequest
|
||||||
1, // 21: topfans.gallery.GalleryService.GetMyGallery:output_type -> topfans.gallery.GetMyGalleryResponse
|
6, // 21: topfans.gallery.GalleryService.UnlockSlot:input_type -> topfans.gallery.UnlockSlotRequest
|
||||||
3, // 22: topfans.gallery.GalleryService.GetUserGallery:output_type -> topfans.gallery.GetUserGalleryResponse
|
14, // 22: topfans.gallery.GalleryService.RemoveFromSlot:input_type -> topfans.gallery.RemoveFromSlotRequest
|
||||||
5, // 23: topfans.gallery.GalleryService.PlaceAsset:output_type -> topfans.gallery.PlaceAssetResponse
|
16, // 23: topfans.gallery.GalleryService.GetMyExhibitedAssets:input_type -> topfans.gallery.GetMyExhibitedAssetsRequest
|
||||||
7, // 24: topfans.gallery.GalleryService.UnlockSlot:output_type -> topfans.gallery.UnlockSlotResponse
|
20, // 24: topfans.gallery.GalleryService.GetInspirationFlow:input_type -> topfans.gallery.GetInspirationFlowRequest
|
||||||
15, // 25: topfans.gallery.GalleryService.RemoveFromSlot:output_type -> topfans.gallery.RemoveFromSlotResponse
|
1, // 25: topfans.gallery.GalleryService.GetMyGallery:output_type -> topfans.gallery.GetMyGalleryResponse
|
||||||
17, // 26: topfans.gallery.GalleryService.GetMyExhibitedAssets:output_type -> topfans.gallery.GetMyExhibitedAssetsResponse
|
3, // 26: topfans.gallery.GalleryService.GetUserGallery:output_type -> topfans.gallery.GetUserGalleryResponse
|
||||||
21, // [21:27] is the sub-list for method output_type
|
5, // 27: topfans.gallery.GalleryService.PlaceAsset:output_type -> topfans.gallery.PlaceAssetResponse
|
||||||
15, // [15:21] is the sub-list for method input_type
|
7, // 28: topfans.gallery.GalleryService.UnlockSlot:output_type -> topfans.gallery.UnlockSlotResponse
|
||||||
15, // [15:15] is the sub-list for extension type_name
|
15, // 29: topfans.gallery.GalleryService.RemoveFromSlot:output_type -> topfans.gallery.RemoveFromSlotResponse
|
||||||
15, // [15:15] is the sub-list for extension extendee
|
17, // 30: topfans.gallery.GalleryService.GetMyExhibitedAssets:output_type -> topfans.gallery.GetMyExhibitedAssetsResponse
|
||||||
0, // [0:15] is the sub-list for field type_name
|
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() }
|
func init() { file_gallery_proto_init() }
|
||||||
@ -1403,7 +1712,7 @@ func file_gallery_proto_init() {
|
|||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_gallery_proto_rawDesc), len(file_gallery_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_gallery_proto_rawDesc), len(file_gallery_proto_rawDesc)),
|
||||||
NumEnums: 0,
|
NumEnums: 0,
|
||||||
NumMessages: 20,
|
NumMessages: 24,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 1,
|
NumServices: 1,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -48,6 +48,8 @@ const (
|
|||||||
GalleryServiceRemoveFromSlotProcedure = "/topfans.gallery.GalleryService/RemoveFromSlot"
|
GalleryServiceRemoveFromSlotProcedure = "/topfans.gallery.GalleryService/RemoveFromSlot"
|
||||||
// GalleryServiceGetMyExhibitedAssetsProcedure is the fully-qualified name of the GalleryService's GetMyExhibitedAssets RPC.
|
// GalleryServiceGetMyExhibitedAssetsProcedure is the fully-qualified name of the GalleryService's GetMyExhibitedAssets RPC.
|
||||||
GalleryServiceGetMyExhibitedAssetsProcedure = "/topfans.gallery.GalleryService/GetMyExhibitedAssets"
|
GalleryServiceGetMyExhibitedAssetsProcedure = "/topfans.gallery.GalleryService/GetMyExhibitedAssets"
|
||||||
|
// GalleryServiceGetInspirationFlowProcedure is the fully-qualified name of the GalleryService's GetInspirationFlow RPC.
|
||||||
|
GalleryServiceGetInspirationFlowProcedure = "/topfans.gallery.GalleryService/GetInspirationFlow"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -62,6 +64,7 @@ type GalleryService interface {
|
|||||||
UnlockSlot(ctx context.Context, req *UnlockSlotRequest, opts ...client.CallOption) (*UnlockSlotResponse, error)
|
UnlockSlot(ctx context.Context, req *UnlockSlotRequest, opts ...client.CallOption) (*UnlockSlotResponse, error)
|
||||||
RemoveFromSlot(ctx context.Context, req *RemoveFromSlotRequest, opts ...client.CallOption) (*RemoveFromSlotResponse, error)
|
RemoveFromSlot(ctx context.Context, req *RemoveFromSlotRequest, opts ...client.CallOption) (*RemoveFromSlotResponse, error)
|
||||||
GetMyExhibitedAssets(ctx context.Context, req *GetMyExhibitedAssetsRequest, opts ...client.CallOption) (*GetMyExhibitedAssetsResponse, 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.
|
// 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
|
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{
|
var GalleryService_ClientInfo = client.ClientInfo{
|
||||||
InterfaceName: "topfans.gallery.GalleryService",
|
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) {
|
ConnectionInjectFunc: func(dubboCliRaw interface{}, conn *client.Connection) {
|
||||||
dubboCli := dubboCliRaw.(*GalleryServiceImpl)
|
dubboCli := dubboCliRaw.(*GalleryServiceImpl)
|
||||||
dubboCli.conn = conn
|
dubboCli.conn = conn
|
||||||
@ -149,6 +160,7 @@ type GalleryServiceHandler interface {
|
|||||||
UnlockSlot(context.Context, *UnlockSlotRequest) (*UnlockSlotResponse, error)
|
UnlockSlot(context.Context, *UnlockSlotRequest) (*UnlockSlotResponse, error)
|
||||||
RemoveFromSlot(context.Context, *RemoveFromSlotRequest) (*RemoveFromSlotResponse, error)
|
RemoveFromSlot(context.Context, *RemoveFromSlotRequest) (*RemoveFromSlotResponse, error)
|
||||||
GetMyExhibitedAssets(context.Context, *GetMyExhibitedAssetsRequest) (*GetMyExhibitedAssetsResponse, error)
|
GetMyExhibitedAssets(context.Context, *GetMyExhibitedAssetsRequest) (*GetMyExhibitedAssetsResponse, error)
|
||||||
|
GetInspirationFlow(context.Context, *GetInspirationFlowRequest) (*GetInspirationFlowResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterGalleryServiceHandler(srv *server.Server, hdlr GalleryServiceHandler, opts ...server.ServiceOption) 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
|
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
|
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"`
|
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"` // 该阶段所有任务是否完成
|
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
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@ -695,6 +696,13 @@ func (x *OnboardingStage) GetAllTasksCompleted() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *OnboardingStage) GetIsRewardClaimed() bool {
|
||||||
|
if x != nil {
|
||||||
|
return x.IsRewardClaimed
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
type CompleteGuideRequest struct {
|
type CompleteGuideRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
TaskKey string `protobuf:"bytes,1,opt,name=task_key,json=taskKey,proto3" json:"task_key,omitempty"`
|
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" +
|
"\n" +
|
||||||
"experience\x18\x04 \x01(\x03R\n" +
|
"experience\x18\x04 \x01(\x03R\n" +
|
||||||
"experience\x12*\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" +
|
"\x0fOnboardingStage\x12\x14\n" +
|
||||||
"\x05stage\x18\x01 \x01(\x05R\x05stage\x12\x12\n" +
|
"\x05stage\x18\x01 \x01(\x05R\x05stage\x12\x12\n" +
|
||||||
"\x04name\x18\x02 \x01(\tR\x04name\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" +
|
"\x06status\x18\x06 \x01(\tR\x06status\x12\x1d\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"is_current\x18\a \x01(\bR\tisCurrent\x12.\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" +
|
"\x14CompleteGuideRequest\x12\x19\n" +
|
||||||
"\btask_key\x18\x01 \x01(\tR\ataskKey\x125\n" +
|
"\btask_key\x18\x01 \x01(\tR\ataskKey\x125\n" +
|
||||||
"\x06stages\x18\x02 \x03(\v2\x1d.topfans.task.OnboardingStageR\x06stages\"\xd6\x01\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"
|
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 expire_at = 6; // 展出过期时间(毫秒时间戳)
|
||||||
int64 earnings = 7; // 当前可领取收益
|
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)
|
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提取用户信息
|
// extractUserInfoFromDubboAttachments 从Dubbo attachments提取用户信息
|
||||||
|
|||||||
@ -41,6 +41,30 @@ type GalleryRepository interface {
|
|||||||
// pageSize: 每页数量
|
// pageSize: 每页数量
|
||||||
// 返回: 作品列表、总数量
|
// 返回: 作品列表、总数量
|
||||||
GetMyExhibitedAssets(userID, starID int64, page, pageSize int) ([]*ExhibitedAssetInfo, int64, error)
|
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 我展出的作品信息
|
// ExhibitedAssetInfo 我展出的作品信息
|
||||||
@ -255,18 +279,19 @@ func (r *galleryRepository) GetMyExhibitedAssets(userID, starID int64, page, pag
|
|||||||
// 数据查询
|
// 数据查询
|
||||||
offset := (page - 1) * pageSize
|
offset := (page - 1) * pageSize
|
||||||
err = r.db.Model(&models.Exhibition{}).
|
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,
|
exhibitions.start_time as exhibited_at, exhibitions.expire_at,
|
||||||
COALESCE(SUM(err.crystal_amount), 0) as earnings`).
|
COALESCE(CAST(SUM(err.crystal_amount) / 10 AS bigint), 0) as earnings
|
||||||
Joins("JOIN assets a ON a.id = exhibitions.asset_id").
|
FROM exhibitions
|
||||||
Joins("LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'").
|
JOIN assets a ON a.id = exhibitions.asset_id
|
||||||
Where("exhibitions.occupier_uid = ? AND exhibitions.occupier_star_id = ?", userID, starID).
|
LEFT JOIN exhibition_revenue_records err ON err.asset_id = a.id AND err.status = 'claimable'
|
||||||
Where("exhibitions.deleted_at IS NULL AND exhibitions.expire_at > ?", now).
|
WHERE exhibitions.occupier_uid = ? AND exhibitions.occupier_star_id = ?
|
||||||
Group("exhibitions.asset_id, a.name, a.cover_url, a.like_count, exhibitions.start_time, exhibitions.expire_at").
|
AND exhibitions.deleted_at IS NULL AND exhibitions.expire_at > ?
|
||||||
Order("exhibitions.start_time DESC").
|
GROUP BY exhibitions.asset_id, a.name, a.cover_url, a.like_count, exhibitions.start_time, exhibitions.expire_at
|
||||||
Limit(pageSize).
|
ORDER BY exhibitions.start_time DESC
|
||||||
Offset(offset).
|
LIMIT ? OFFSET ?
|
||||||
Scan(&items).Error
|
`, userID, starID, now, pageSize, offset).Scan(&items).Error
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
@ -275,6 +300,62 @@ func (r *galleryRepository) GetMyExhibitedAssets(userID, starID int64, page, pag
|
|||||||
return items, total, nil
|
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
|
// generateHostProfileID 生成 host_profile_id
|
||||||
|
|||||||
@ -695,3 +695,104 @@ onDataLoaded() {
|
|||||||
| 2026-04-29 | 改用随机 offset 方案(方案 B),每次请求都是独立随机,数据变化无影响 |
|
| 2026-04-29 | 改用随机 offset 方案(方案 B),每次请求都是独立随机,数据变化无影响 |
|
||||||
| 2026-04-29 | 新增双向滚动支持:向右加载新数据,向左加载历史数据,后端维护会话级缓存 |
|
| 2026-04-29 | 新增双向滚动支持:向右加载新数据,向左加载历史数据,后端维护会话级缓存 |
|
||||||
| 2026-04-29 | 修复:修正重复的 10.4 章节、RangeSamplingStrategy 签名、游标结构说明 |
|
| 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>
|
||||||
|
|
||||||
<!-- 新手引导 -->
|
<!-- 新手引导 -->
|
||||||
<view v-if="showGuideIcon" class="daily-task-group" @click="handleGuideClick">
|
<!-- <view v-if="showGuideIcon" class="daily-task-group" @click="handleGuideClick">
|
||||||
<!-- 1. 上层:新手引导(悬浮在上面) -->
|
1. 上层:新手引导(悬浮在上面)
|
||||||
<view class="task-icon-box">
|
<view class="task-icon-box">
|
||||||
<image class="task-icon-img" src="/static/icon/onboarding-bg.png" mode="aspectFit"></image>
|
<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>
|
<image class="task-red-dot" src="/static/square/tishi.png" mode="aspectFit"></image>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 2. 下层:文字背景块 -->
|
2. 下层:文字背景块
|
||||||
<view class="task-text-box">
|
<view class="task-text-box">
|
||||||
<text class="task-text-label">新手引导</text>
|
<text class="task-text-label">新手引导</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view> -->
|
||||||
|
|
||||||
<!-- 星援活动 -->
|
<!-- 星援活动 -->
|
||||||
<view v-if="showStarActivityIcon" class="daily-task-group" @click="handleStarActivityClick">
|
<view v-if="showStarActivityIcon" class="daily-task-group" @click="handleStarActivityClick">
|
||||||
@ -388,13 +388,13 @@ const handleAvatarClick = () => {
|
|||||||
if (pages.length > 0) {
|
if (pages.length > 0) {
|
||||||
const currentPage = pages[pages.length - 1];
|
const currentPage = pages[pages.length - 1];
|
||||||
// 检查当前页面是否是个人信息页面
|
// 检查当前页面是否是个人信息页面
|
||||||
if (currentPage.route === 'pages/profile/profile') {
|
if (currentPage.route === 'pages/profile/myWorks') {
|
||||||
// 已经在个人信息页面,不执行跳转
|
// 已经在个人信息页面,不执行跳转
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
uni.navigateTo({
|
uni.navigateTo({
|
||||||
url: '/pages/profile/profile'
|
url: '/pages/profile/myWorks'
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -570,7 +570,7 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
|
|
||||||
.balance-number {
|
.balance-number {
|
||||||
font-size: 22rpx;
|
font-size: 24rpx;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
color: #FFB800;
|
color: #FFB800;
|
||||||
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
|
font-family: 'ZaoZiGongFangJianHei-1', sans-serif;
|
||||||
|
|||||||
@ -10,6 +10,9 @@
|
|||||||
</view>
|
</view>
|
||||||
<!-- <text class="nav-title">我的作品</text> -->
|
<!-- <text class="nav-title">我的作品</text> -->
|
||||||
<view class="nav-placeholder"></view>
|
<view class="nav-placeholder"></view>
|
||||||
|
<view class="nav-settings" @tap="goToSettings">
|
||||||
|
<text class="nav-settings-text">个人设置</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="scroll-content">
|
<view class="scroll-content">
|
||||||
@ -22,15 +25,13 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view class="exhibition-grid">
|
<view class="exhibition-grid">
|
||||||
<view
|
<view v-for="(item, index) in exhibitionWorks" :key="item.id" class="exhibition-card"
|
||||||
v-for="(item, index) in exhibitionWorks"
|
|
||||||
:key="item.id"
|
|
||||||
class="exhibition-card"
|
|
||||||
:class="index % 2 === 0 ? 'card-tilt-left' : 'card-tilt-right'"
|
:class="index % 2 === 0 ? 'card-tilt-left' : 'card-tilt-right'"
|
||||||
@tap="handleExhibitionCardTap(item, index)"
|
@tap="handleExhibitionCardTap(item, index)">
|
||||||
>
|
<image class="card-image" :src="item.cover_url || '/static/nft/placeholder.png'"
|
||||||
<image class="card-image" :src="item.cover_url || '/static/nft/placeholder.png'" mode="aspectFill"></image>
|
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="card-rate-badge">
|
<view class="card-rate-badge">
|
||||||
<image class="heart-icon" src="/static/icon/heart-icon.png" mode="aspectFit"></image>
|
<image class="heart-icon" src="/static/icon/heart-icon.png" mode="aspectFit"></image>
|
||||||
@ -39,10 +40,11 @@
|
|||||||
</view>
|
</view>
|
||||||
</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>
|
<image class="topfans-icon" src="/static/icon/crystal.png" mode="aspectFit"></image>
|
||||||
<view class="card-income-text-wrap">
|
<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>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -50,9 +52,11 @@
|
|||||||
<!-- 空状态占位:显示剩余空展位卡片 -->
|
<!-- 空状态占位:显示剩余空展位卡片 -->
|
||||||
<view v-if="exhibitionWorks.length < 2" class="empty-exhibition">
|
<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="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">
|
<view class="empty-add-btn">
|
||||||
<text class="empty-add-icon">+</text>
|
<text class="empty-add-icon">+</text>
|
||||||
</view>
|
</view>
|
||||||
@ -60,7 +64,8 @@
|
|||||||
|
|
||||||
<view class="empty-card empty-card-right" @tap="openAssetSelector(1)">
|
<view class="empty-card empty-card-right" @tap="openAssetSelector(1)">
|
||||||
<image class="empty-cover" src="/static/nft/placeholder.png" mode="aspectFill"></image>
|
<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">
|
<view class="empty-add-btn">
|
||||||
<text class="empty-add-icon">+</text>
|
<text class="empty-add-icon">+</text>
|
||||||
</view>
|
</view>
|
||||||
@ -69,34 +74,27 @@
|
|||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 今日点赞作品 -->
|
<!-- 当前点赞作品 -->
|
||||||
<view class="section-block">
|
<view class="section-block">
|
||||||
<view class="section-label">
|
<view class="section-label">
|
||||||
<image class="section-label-bg" src="/static/nft/dingbutubiao_liang.png" mode="aspectFill"></image>
|
<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>
|
</view>
|
||||||
|
|
||||||
<scroll-view class="liked-list" scroll-y="true" :show-scrollbar="false">
|
<scroll-view class="liked-list" scroll-y="true" :show-scrollbar="false">
|
||||||
<view
|
<view v-for="(item, index) in likedWorks" :key="item.id" class="liked-row"
|
||||||
v-for="(item, index) in likedWorks"
|
@tap="goToAssetDetail(item.id)">
|
||||||
:key="item.id"
|
|
||||||
class="liked-row"
|
|
||||||
@tap="goToAssetDetail(item.id)"
|
|
||||||
>
|
|
||||||
<!-- 排名图标,绝对定位在卡片左侧 -->
|
<!-- 排名图标,绝对定位在卡片左侧 -->
|
||||||
<image
|
<image v-if="index < 3" :src="rankIcons[index]" class="rank-icon-img" mode="aspectFit"></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-item" :class="index === 0 ? 'liked-item-first' : ''">
|
||||||
<!-- 作品封面 -->
|
<!-- 作品封面 -->
|
||||||
<view class="liked-cover-wrap" :class="index === 0 ? 'liked-cover-wrap-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" :src="item.cover_url || '/static/nft/placeholder.png'"
|
||||||
<image class="liked-cover-frame" src="/static/square/cangpinkuang1.png" mode="aspectFill"></image>
|
mode="aspectFill"></image>
|
||||||
|
<image class="liked-cover-frame" src="/static/square/cangpinkuang1.png"
|
||||||
|
mode="aspectFill"></image>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 作品信息 -->
|
<!-- 作品信息 -->
|
||||||
@ -104,13 +102,15 @@
|
|||||||
<text class="liked-status">{{ item.status_text }}</text>
|
<text class="liked-status">{{ item.status_text }}</text>
|
||||||
<view class="liked-score-row">
|
<view class="liked-score-row">
|
||||||
<text class="liked-score">{{ formatScore(item.score) }}</text>
|
<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>
|
</view>
|
||||||
|
|
||||||
<!-- 右侧奖励 -->
|
<!-- 右侧奖励 -->
|
||||||
<view class="liked-reward">
|
<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>
|
<text class="reward-amount">+{{ item.reward }}</text>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
@ -118,7 +118,7 @@
|
|||||||
|
|
||||||
<!-- 空状态 -->
|
<!-- 空状态 -->
|
||||||
<view v-if="likedWorks.length === 0" class="empty-liked">
|
<view v-if="likedWorks.length === 0" class="empty-liked">
|
||||||
<text class="empty-text">今日暂无点赞作品</text>
|
<text class="empty-text">当前暂无点赞作品</text>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
</view>
|
</view>
|
||||||
@ -127,12 +127,8 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 藏品选择器组件 -->
|
<!-- 藏品选择器组件 -->
|
||||||
<AssetSelector
|
<AssetSelector :visible="showAssetSelector" :replace-asset="assetToReplace" @close="closeAssetSelector"
|
||||||
:visible="showAssetSelector"
|
@select="handleAssetSelect" />
|
||||||
:replace-asset="assetToReplace"
|
|
||||||
@close="closeAssetSelector"
|
|
||||||
@select="handleAssetSelect"
|
|
||||||
/>
|
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -144,7 +140,24 @@ import { onShow } from '@dcloudio/uni-app';
|
|||||||
import { doubleTapLike } from '@/utils/likeHelper.js';
|
import { doubleTapLike } from '@/utils/likeHelper.js';
|
||||||
|
|
||||||
const goBack = () => {
|
const goBack = () => {
|
||||||
|
// 获取页面栈
|
||||||
|
const pages = getCurrentPages();
|
||||||
|
if (pages.length > 1) {
|
||||||
|
// 有上一页,执行返回
|
||||||
uni.navigateBack();
|
uni.navigateBack();
|
||||||
|
} else {
|
||||||
|
// 没有上一页,跳转到square页面
|
||||||
|
uni.reLaunch({
|
||||||
|
url: '/pages/square/square'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToSettings = () => {
|
||||||
|
|
||||||
|
uni.navigateTo({
|
||||||
|
url: '/pages/profile/profile'
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToCastlove = () => {
|
const goToCastlove = () => {
|
||||||
@ -237,7 +250,7 @@ const handleExhibitionCardTap = (item, index) => {
|
|||||||
if (success) {
|
if (success) {
|
||||||
// 更新在展作品的点赞数
|
// 更新在展作品的点赞数
|
||||||
exhibitionWorks.value[index].like_count = (exhibitionWorks.value[index].like_count || 0) + 1;
|
exhibitionWorks.value[index].like_count = (exhibitionWorks.value[index].like_count || 0) + 1;
|
||||||
// 将作品添加到今日点赞列表
|
// 将作品添加到当前点赞列表
|
||||||
const likedItem = {
|
const likedItem = {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
cover_url: item.cover_url,
|
cover_url: item.cover_url,
|
||||||
@ -275,7 +288,7 @@ const formatScore = (score) => {
|
|||||||
// 在展作品列表
|
// 在展作品列表
|
||||||
const exhibitionWorks = ref([]);
|
const exhibitionWorks = ref([]);
|
||||||
|
|
||||||
// 今日点赞作品列表
|
// 当前点赞作品列表
|
||||||
const likedWorks = ref([]);
|
const likedWorks = ref([]);
|
||||||
|
|
||||||
// 加载我的展出作品
|
// 加载我的展出作品
|
||||||
@ -383,6 +396,28 @@ onShow(() => {
|
|||||||
width: 64rpx;
|
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 {
|
.scroll-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -492,7 +527,7 @@ onShow(() => {
|
|||||||
.card-user-text {
|
.card-user-text {
|
||||||
font-size: 20rpx;
|
font-size: 20rpx;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
background: rgba(0,0,0,0.45);
|
background: rgba(0, 0, 0, 0.45);
|
||||||
padding: 4rpx 14rpx;
|
padding: 4rpx 14rpx;
|
||||||
border-radius: 20rpx;
|
border-radius: 20rpx;
|
||||||
}
|
}
|
||||||
@ -532,11 +567,11 @@ onShow(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.card-income-text-wrap {
|
.card-income-text-wrap {
|
||||||
|
width: 64rpx;
|
||||||
background: linear-gradient(to bottom right,
|
background: linear-gradient(to bottom right,
|
||||||
#F0E4B1 0%,
|
#F0E4B1 0%,
|
||||||
#F08399 50%,
|
#F08399 50%,
|
||||||
#B94E73 100%
|
#B94E73 100%);
|
||||||
);
|
|
||||||
border-radius: 999rpx;
|
border-radius: 999rpx;
|
||||||
padding: 8rpx 20rpx 8rpx 40rpx;
|
padding: 8rpx 20rpx 8rpx 40rpx;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
@ -564,13 +599,13 @@ onShow(() => {
|
|||||||
background: linear-gradient(to bottom right,
|
background: linear-gradient(to bottom right,
|
||||||
#F0E4B1 0%,
|
#F0E4B1 0%,
|
||||||
#F08399 50%,
|
#F08399 50%,
|
||||||
#B94E73 100%
|
#B94E73 100%);
|
||||||
);
|
|
||||||
border-radius: 999rpx;
|
border-radius: 999rpx;
|
||||||
padding: 2rpx 12rpx;
|
padding: 2rpx 16rpx;
|
||||||
box-shadow:
|
box-shadow:
|
||||||
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
0 4rpx 12rpx rgba(255, 143, 158, 0.2),
|
||||||
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
|
inset 0 2rpx 4rpx rgba(255, 255, 255, 0.4);
|
||||||
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-rate-text {
|
.card-rate-text {
|
||||||
@ -643,7 +678,7 @@ onShow(() => {
|
|||||||
color: #b09cc0;
|
color: #b09cc0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 今日点赞列表 */
|
/* 当前点赞列表 */
|
||||||
.liked-list {
|
.liked-list {
|
||||||
max-height: 732rpx;
|
max-height: 732rpx;
|
||||||
}
|
}
|
||||||
@ -686,7 +721,7 @@ onShow(() => {
|
|||||||
width: 48rpx;
|
width: 48rpx;
|
||||||
height: 48rpx;
|
height: 48rpx;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: rgba(180,140,220,0.3);
|
background: rgba(180, 140, 220, 0.3);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: 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'])
|
defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ key: 'hot', label: '热门作品', emoji: null, icon: '/static/square/rementubiao.png', iconWidth: 104, iconHeight: 104 },
|
{ 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: '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: '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: 'haibao', label: '随机寻宝', emoji: null, icon: '/static/square/haibao.png', iconWidth: 96, iconHeight: 96 },]
|
||||||
]
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@ -57,13 +57,16 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
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 { doubleTapLike } from '@/utils/likeHelper.js'
|
||||||
|
import { USE_MOCK_DATA, getMockDataByCategory, generateMockItems, calcSpan } from '../config/mockData.js'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
screenWidth: { type: Number, default: 375 },
|
screenWidth: { type: Number, default: 375 },
|
||||||
screenHeight: { type: Number, default: 812 },
|
screenHeight: { type: Number, default: 812 },
|
||||||
bannerBottom: { type: Number, default: 200 },
|
bannerBottom: { type: Number, default: 200 },
|
||||||
|
useMockData: { type: Boolean, default: false }, // 是否使用模拟数据
|
||||||
|
category: { type: String, default: 'hot' }, // 当前分类
|
||||||
})
|
})
|
||||||
|
|
||||||
const emit = defineEmits(['cardClick'])
|
const emit = defineEmits(['cardClick'])
|
||||||
@ -76,7 +79,7 @@ const SCALE = 0.9
|
|||||||
const ROWS = 4
|
const ROWS = 4
|
||||||
const AUTO_SCROLL_SPEED = 1.2
|
const AUTO_SCROLL_SPEED = 1.2
|
||||||
const AUTO_RESUME_DELAY = 2500
|
const AUTO_RESUME_DELAY = 2500
|
||||||
const PRELOAD_THRESHOLD = rpx2px(1200)
|
const PRELOAD_THRESHOLD = rpx2px(300)
|
||||||
|
|
||||||
// ========== 状态 ==========
|
// ========== 状态 ==========
|
||||||
const cards = ref([])
|
const cards = ref([])
|
||||||
@ -86,6 +89,8 @@ const scrollLeft = ref(0)
|
|||||||
const likingMap = ref({}) // 记录正在播放点赞动画的卡片ID
|
const likingMap = ref({}) // 记录正在播放点赞动画的卡片ID
|
||||||
let currentScrollLeft = 0
|
let currentScrollLeft = 0
|
||||||
let idCounter = 0
|
let idCounter = 0
|
||||||
|
let isComponentMounted = false // 标记组件是否已卸载
|
||||||
|
let mockDataOffset = 0 // 模拟数据循环偏移量
|
||||||
|
|
||||||
// ========== RAF 兼容 ==========
|
// ========== RAF 兼容 ==========
|
||||||
const rafFn = (cb) => {
|
const rafFn = (cb) => {
|
||||||
@ -103,16 +108,56 @@ const cafFn = (id) => {
|
|||||||
let rafId = null
|
let rafId = null
|
||||||
let userInteracting = false
|
let userInteracting = false
|
||||||
let resumeTimer = null
|
let resumeTimer = null
|
||||||
|
let appendTimer = null // 防抖定时器
|
||||||
|
|
||||||
const startAutoScroll = () => {
|
const startAutoScroll = () => {
|
||||||
if (rafId) return
|
if (!isComponentMounted) {
|
||||||
const step = () => {
|
console.log('[WaterfallGrid] startAutoScroll blocked: not mounted')
|
||||||
if (!userInteracting) {
|
return
|
||||||
currentScrollLeft += AUTO_SCROLL_SPEED
|
}
|
||||||
scrollLeft.value = currentScrollLeft
|
if (rafId) cancelAnimationFrame(rafId)
|
||||||
if (totalWidth.value - currentScrollLeft - props.screenWidth < PRELOAD_THRESHOLD) {
|
console.log('[WaterfallGrid] startAutoScroll started')
|
||||||
|
|
||||||
|
// 启动时先追加一次数据(使用模拟数据时直接追加)
|
||||||
|
if (!isLoadingMore && props.useMockData) {
|
||||||
|
console.log('[WaterfallGrid] startAutoScroll calling appendMore')
|
||||||
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)
|
rafId = rafFn(step)
|
||||||
}
|
}
|
||||||
@ -120,7 +165,16 @@ const startAutoScroll = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const stopAutoScroll = () => {
|
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 = () => {
|
const pauseForUser = () => {
|
||||||
@ -129,14 +183,29 @@ const pauseForUser = () => {
|
|||||||
resumeTimer = setTimeout(() => { userInteracting = false }, AUTO_RESUME_DELAY)
|
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 onTouchStart = () => pauseForUser()
|
||||||
const onTouchEnd = () => {}
|
const onTouchEnd = () => {}
|
||||||
|
|
||||||
const onScroll = (e) => {
|
const onScroll = (e) => {
|
||||||
|
if (!isComponentMounted) return
|
||||||
currentScrollLeft = e.detail.scrollLeft
|
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 计算
|
// 同一列内所有卡片宽度统一 = 整列宽(span=ROWS 对应的宽),高度各自按 span 计算
|
||||||
// 支持两种模式:
|
// 支持两种模式:
|
||||||
// 1. 后端传 span:直接按 span 分组成列
|
// 1. 后端传 span:直接按 span 分组成列
|
||||||
// 2. 后端不传 span:前端随机选列模板,每次打开都不同
|
// 2. 后端不传 span:使用分类特定的 span 阈值计算
|
||||||
class WaterfallLayout {
|
class WaterfallLayout {
|
||||||
constructor(containerH, gap = GAP) {
|
constructor(containerH, category = 'hot', gap = GAP) {
|
||||||
this.containerH = containerH
|
this.containerH = containerH
|
||||||
|
this.category = category
|
||||||
this.gap = gap
|
this.gap = gap
|
||||||
this.rowH = Math.floor((containerH - gap * (ROWS - 1)) / ROWS)
|
this.rowH = Math.floor((containerH - gap * (ROWS - 1)) / ROWS)
|
||||||
// 列宽固定 = rowH × 9/16,严格竖长 9:16,span 只影响高度
|
// 列宽固定 = rowH × 9/16,严格竖长 9:16,span 只影响高度
|
||||||
@ -173,30 +243,57 @@ class WaterfallLayout {
|
|||||||
return { w, h }
|
return { w, h }
|
||||||
}
|
}
|
||||||
|
|
||||||
// 点赞数 → span
|
// 点赞数 → span(使用分类特定的阈值)
|
||||||
_span(likes) {
|
_span(likes) {
|
||||||
if (likes < 500) return 1
|
return calcSpan(this.category, likes)
|
||||||
if (likes < 2000) return 2
|
|
||||||
if (likes < 8000) return 2
|
|
||||||
if (likes < 50000) return 3
|
|
||||||
return 4
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将用户列表按点赞数决定 span,分组成列,每列 span 之和 = ROWS
|
// 将用户列表按点赞数决定 span,分组成列,每列 span 之和 = ROWS
|
||||||
_groupIntoColumns(users) {
|
_groupIntoColumns(users) {
|
||||||
|
// 复制一份避免修改原数组
|
||||||
|
const remaining = users.map((u, idx) => ({
|
||||||
|
...u,
|
||||||
|
originalIndex: idx,
|
||||||
|
span: u.span != null ? u.span : this._span(u.likes || 0)
|
||||||
|
}))
|
||||||
|
|
||||||
const columns = []
|
const columns = []
|
||||||
let i = 0
|
let colIndex = 0
|
||||||
while (i < users.length) {
|
|
||||||
|
while (remaining.length > 0) {
|
||||||
const col = []
|
const col = []
|
||||||
let sum = 0
|
let sum = 0
|
||||||
while (i < users.length && sum < ROWS) {
|
const colStart = users.length - remaining.length
|
||||||
// 后端有 span 用后端的,否则按点赞数算
|
|
||||||
const rawSpan = users[i].span != null ? users[i].span : this._span(users[i].likes || 0)
|
// 尝试放入能填满当前列的卡片
|
||||||
const span = Math.min(rawSpan, ROWS - sum)
|
let i = 0
|
||||||
col.push({ ...users[i], span })
|
while (i < remaining.length) {
|
||||||
sum += span
|
const rawSpan = remaining[i].span
|
||||||
|
|
||||||
|
// 如果当前卡片加入会导致超出 ROWS,尝试下一个
|
||||||
|
if (sum + rawSpan > ROWS) {
|
||||||
i++
|
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
|
// 凑不满则补 _pad
|
||||||
while (sum < ROWS) {
|
while (sum < ROWS) {
|
||||||
col.push({ id: idCounter++, span: 1, _pad: true, likes: 0 })
|
col.push({ id: idCounter++, span: 1, _pad: true, likes: 0 })
|
||||||
@ -331,18 +428,71 @@ const mapUser = async (u) => {
|
|||||||
return {
|
return {
|
||||||
id: idCounter++,
|
id: idCounter++,
|
||||||
userId: u.user_id,
|
userId: u.user_id,
|
||||||
nickname: u.nickname,
|
nickname: u.owner_nickname || u.nickname,
|
||||||
coverUrl,
|
coverUrl,
|
||||||
likes: u.likes ?? randomLikes(),
|
likes: u.like_count ?? u.likes ?? randomLikes(),
|
||||||
span: u.span ?? null, // 后端控制字段,null = 前端随机模板决定
|
span: u.span ?? null, // 后端控制字段,null = 前端随机模板决定
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadUsers = async () => {
|
// 批量获取预签名URL
|
||||||
|
const batchGetPresignedUrls = async (urls) => {
|
||||||
|
if (!urls || urls.length === 0) return {}
|
||||||
try {
|
try {
|
||||||
const res = await getRandomUsersApi(1, 40)
|
const files = urls.filter(u => u)
|
||||||
if (res.code === 200 && res.data?.users) {
|
const res = await getBatchOssPresignedUrlsApi(files, 3600, 'asset')
|
||||||
const withData = await Promise.all(res.data.users.map(mapUser))
|
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
|
allUsers.value = withData
|
||||||
cards.value = layout.compute(withData)
|
cards.value = layout.compute(withData)
|
||||||
totalWidth.value = layout.getTotalWidth()
|
totalWidth.value = layout.getTotalWidth()
|
||||||
@ -353,19 +503,92 @@ const loadUsers = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appendMore = 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
|
isLoadingMore = true
|
||||||
try {
|
try {
|
||||||
const res = await getRandomUsersApi(1, 20)
|
let items
|
||||||
if (res.code === 200 && res.data?.users) {
|
|
||||||
const withData = await Promise.all(res.data.users.map(mapUser))
|
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)
|
const placed = layout.addCards(withData)
|
||||||
|
console.log('[WaterfallGrid] appendMore placed:', placed.length, 'layout.curX:', layout.curX)
|
||||||
cards.value = [...cards.value, ...placed]
|
cards.value = [...cards.value, ...placed]
|
||||||
totalWidth.value = layout.getTotalWidth()
|
totalWidth.value = layout.getTotalWidth()
|
||||||
|
console.log('[WaterfallGrid] appendMore after:', 'cards.length:', cards.value.length, 'totalWidth:', totalWidth.value)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[WaterfallGrid] 追加用户失败', e?.message ?? e)
|
console.error('[WaterfallGrid] 追加用户失败', e?.message ?? e)
|
||||||
} finally {
|
} finally {
|
||||||
|
console.log('[WaterfallGrid] appendMore finally, resetting isLoadingMore')
|
||||||
isLoadingMore = false
|
isLoadingMore = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -405,24 +628,56 @@ const handleCardClick = (card) => {
|
|||||||
|
|
||||||
// ========== 初始化 ==========
|
// ========== 初始化 ==========
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
isComponentMounted = true
|
||||||
const containerH = props.screenHeight - props.bannerBottom
|
const containerH = props.screenHeight - props.bannerBottom
|
||||||
layout = new WaterfallLayout(containerH)
|
layout = new WaterfallLayout(containerH, props.category)
|
||||||
|
console.log('[WaterfallGrid] onMounted, starting...')
|
||||||
loadUsers().then(() => startAutoScroll())
|
loadUsers().then(() => startAutoScroll())
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
|
console.log('[WaterfallGrid] onUnmounted called!')
|
||||||
|
isComponentMounted = false
|
||||||
stopAutoScroll()
|
stopAutoScroll()
|
||||||
clearTimeout(resumeTimer)
|
clearTimeout(resumeTimer)
|
||||||
|
clearTimeout(appendTimer)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => [props.screenHeight, props.bannerBottom], () => {
|
watch(() => [props.screenHeight, props.bannerBottom], () => {
|
||||||
const containerH = props.screenHeight - props.bannerBottom
|
const containerH = props.screenHeight - props.bannerBottom
|
||||||
layout = new WaterfallLayout(containerH)
|
layout = new WaterfallLayout(containerH, props.category)
|
||||||
if (allUsers.value.length) {
|
if (allUsers.value.length) {
|
||||||
cards.value = layout.compute(allUsers.value)
|
cards.value = layout.compute(allUsers.value)
|
||||||
totalWidth.value = layout.getTotalWidth()
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<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"
|
:screenWidth="screenWidth"
|
||||||
:screenHeight="screenHeight"
|
:screenHeight="screenHeight"
|
||||||
:bannerBottom="bannerBottomPx"
|
:bannerBottom="bannerBottomPx"
|
||||||
|
:useMockData="USE_MOCK_DATA"
|
||||||
|
:category="activeContentTab"
|
||||||
@cardClick="handleCardClick"
|
@cardClick="handleCardClick"
|
||||||
class="fall-bg"
|
class="fall-bg"
|
||||||
/>
|
/>
|
||||||
@ -68,6 +70,7 @@ import WaterfallGrid from './components/WaterfallGrid.vue'
|
|||||||
import ContentTabs from './components/ContentTabs.vue'
|
import ContentTabs from './components/ContentTabs.vue'
|
||||||
import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
|
import { clearSubStepProgress, shouldShowGuideStartModal } from '@/utils/guideConfig.js'
|
||||||
import { useBanner } from './composables/useBanner.js'
|
import { useBanner } from './composables/useBanner.js'
|
||||||
|
import { USE_MOCK_DATA } from './config/mockData.js'
|
||||||
|
|
||||||
// ========== Store & User Info ==========
|
// ========== Store & User Info ==========
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
@ -78,7 +81,6 @@ const currentStarId = ref(uni.getStorageSync('star_id') || null)
|
|||||||
const activeContentTab = ref('hot')
|
const activeContentTab = ref('hot')
|
||||||
const navExpanded = ref(false)
|
const navExpanded = ref(false)
|
||||||
const showRankingModal = ref(false)
|
const showRankingModal = ref(false)
|
||||||
const isDev = ref(false) // 开发模式,显示调试按钮
|
|
||||||
|
|
||||||
// ========== Screen Info ==========
|
// ========== Screen Info ==========
|
||||||
const screenWidth = ref(375)
|
const screenWidth = ref(375)
|
||||||
@ -143,12 +145,6 @@ const handleTabChange = (newTab) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openDebugGrid = () => {
|
|
||||||
uni.navigateTo({
|
|
||||||
url: '/pages/square/debug-grid'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========== Tile Change Callback ==========
|
// ========== Tile Change Callback ==========
|
||||||
const handleTileChange = () => {}
|
const handleTileChange = () => {}
|
||||||
|
|
||||||
@ -282,25 +278,4 @@ onUnmounted(() => {
|
|||||||
opacity: 1;
|
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>
|
</style>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
// API 基础配置
|
// API 基础配置
|
||||||
const baseURL = 'http://101.132.250.62:8080'
|
// const baseURL = 'http://101.132.250.62:8080'
|
||||||
// const baseURL = 'http://192.168.110.60:8080'
|
const baseURL = 'http://192.168.110.60:8080'
|
||||||
// const baseURL = 'http://localhost:8080'
|
// const baseURL = 'http://localhost:8080'
|
||||||
|
|
||||||
// 是否使用模拟数据(开发调试时设为 true,后端API准备好后改为 false)
|
// 是否使用模拟数据(开发调试时设为 true,后端API准备好后改为 false)
|
||||||
@ -607,3 +607,22 @@ export function getMyLikedAssetsApi(page = 1, pageSize = 20) {
|
|||||||
method: 'GET'
|
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