diff --git a/internal/application/column/assembler.go b/internal/application/column/assembler.go index 87707d0..986d89b 100644 --- a/internal/application/column/assembler.go +++ b/internal/application/column/assembler.go @@ -8,14 +8,17 @@ func ToDto(col *column.Column) *ColumnDto { return nil } return &ColumnDto{ - ID: col.ID, - Title: col.Title, - Brief: col.Brief, - Cover: col.Cover, - AuthorID: col.AuthorID, - Status: col.Status, - ArticleNum: col.ArticleNum, - CreatedAt: col.CreatedAt, + ID: col.ID, + Title: col.Title, + Brief: col.Brief, + Cover: col.Cover, + AuthorID: col.AuthorID, + Status: col.Status, + ArticleNum: col.ArticleNum, + FollowNum: col.FollowNum, + PurchaseNum: col.PurchaseNum, + Unlock: false, + CreatedAt: col.CreatedAt, } } diff --git a/internal/application/column/service.go b/internal/application/column/service.go index 652adbd..3459628 100644 --- a/internal/application/column/service.go +++ b/internal/application/column/service.go @@ -28,20 +28,6 @@ func NewService(repo column.ColumnRepository, log logger.New) *Service { } } -// CreateColumn 创建专栏 -func (s *Service) CreateColumn(req *CreateColumnReq) (*ColumnDto, error) { - if req.Title == "" { - return nil, ErrInvalidTitle - } - - col := column.NewColumn(req.Title, req.Brief, req.Cover, req.AuthorID) - if err := s.repo.Save(col); err != nil { - s.log.Error("failed to save column", "error", err) - return nil, err - } - return ToDto(col), nil -} - // GetColumnList 获取专栏列表 func (s *Service) GetColumnList(p *page.Page, params map[string]string) error { conds := web.ParseFilters(params) @@ -56,100 +42,17 @@ func (s *Service) GetColumnList(p *page.Page, params map[string]string) error { return nil } -// UpdateColumn 更新专栏信息 -func (s *Service) UpdateColumn(req *UpdateColumnReq) (*ColumnDto, error) { - if req.Title == "" { - return nil, ErrInvalidTitle - } - - col, err := s.repo.FindByID(req.ID) - if err != nil { - s.log.Error("failed to find column", "error", err) - return nil, err - } - if col == nil { - return nil, ErrNotFound - } - - col.Title = req.Title - col.Brief = req.Brief - col.Cover = req.Cover - - if err := s.repo.Update(col); err != nil { - s.log.Error("failed to update column", "error", err) - return nil, err +func (s *Service) UpdateColumn(dto *ColumnDto) error { + columnData := &column.Column{ + ID: dto.ID, + Title: dto.Title, + Brief: dto.Brief, + FollowNum: dto.FollowNum, + PurchaseNum: dto.PurchaseNum, } - return ToDto(col), nil -} - -// DeleteColumn 删除专栏 -func (s *Service) DeleteColumn(id uint64) error { - return s.repo.Delete(id) -} - -// UpdateColumnStatus 更新专栏状态 -func (s *Service) UpdateColumnStatus(req *UpdateStatusReq) (*ColumnDto, error) { - col, err := s.repo.FindByID(req.ID) + err := s.repo.Update(columnData) if err != nil { - s.log.Error("failed to find column", "error", err) - return nil, err - } - if col == nil { - return nil, ErrNotFound + s.log.Error(err) } - - col.Status = req.Status - if err := s.repo.Update(col); err != nil { - s.log.Error("failed to update column status", "error", err) - return nil, err - } - return ToDto(col), nil -} - -// UpdateFollowNum 更新关注人数 -func (s *Service) UpdateFollowNum(id uint64, isAdd bool) error { - col, err := s.repo.FindByID(id) - if err != nil { - s.log.Error("failed to find column", "error", err) - return err - } - if col == nil { - return ErrNotFound - } - - if isAdd { - col.AddFollow() - } else { - col.RemoveFollow() - } - - if err := s.repo.Update(col); err != nil { - s.log.Error("failed to update column follow num", "error", err) - return err - } - return nil -} - -// UpdatePurchaseNum 更新购买人数 -func (s *Service) UpdatePurchaseNum(id uint64, isAdd bool) error { - col, err := s.repo.FindByID(id) - if err != nil { - s.log.Error("failed to find column", "error", err) - return err - } - if col == nil { - return ErrNotFound - } - - if isAdd { - col.AddPurchase() - } else { - col.RemovePurchase() - } - - if err := s.repo.Update(col); err != nil { - s.log.Error("failed to update column purchase num", "error", err) - return err - } - return nil + return err } diff --git a/internal/application/price/dto.go b/internal/application/price/dto.go index c023480..fb5b38b 100644 --- a/internal/application/price/dto.go +++ b/internal/application/price/dto.go @@ -21,6 +21,17 @@ type PriceDto struct { AdminID uint64 `json:"adminID" omitempty` } +type PriceDefaultDto struct { + Id uint64 `json:"id"` + Amount int64 `json:"amount"` + FirstMontDiscount float32 `json:"firstMontDiscount" omitempty` + OneMonthPrice int64 `json:"oneMonthPrice"` // 1个月价格(分) + ThreeMonthsPrice int64 `json:"threeMonthsPrice"` // 3个月价格(分) + SixMonthsPrice int64 `json:"sixMonthsPrice"` // 6个月价格(分) + OneYearPrice int64 `json:"oneYearPrice"` // 1年价格(分) + Discount float32 `json:"discount" omitempty` +} + type ArticlePriceDto struct { Article *article.ArticleDto `json:"article"` Price *PriceDto `json:"price"` diff --git a/internal/application/price/service.go b/internal/application/price/service.go index 49ea146..82842a0 100644 --- a/internal/application/price/service.go +++ b/internal/application/price/service.go @@ -2,11 +2,15 @@ package price import ( article2 "cls-server/internal/application/article" + column2 "cls-server/internal/application/column" "cls-server/internal/domain/article" + "cls-server/internal/domain/column" "cls-server/internal/domain/price" + "cls-server/internal/domain/price_default" "cls-server/pkg/logger" "cls-server/pkg/util/page" "errors" + "time" "xorm.io/builder" ) @@ -20,132 +24,70 @@ var ( type PriceService struct { repo price.PriceRepository articleRepo article.ArticleRepository + columnRepo column.ColumnRepository + pdRepo price_default.PriceDefaultRepository log logger.Logger } // NewService 创建价格管理服务 -func NewPriceService(repo price.PriceRepository, articleRepo article.ArticleRepository, log logger.New) *PriceService { +func NewPriceService(repo price.PriceRepository, articleRepo article.ArticleRepository, columnRepo column.ColumnRepository, pdRepo price_default.PriceDefaultRepository, log logger.New) *PriceService { return &PriceService{ repo: repo, articleRepo: articleRepo, + columnRepo: columnRepo, + pdRepo: pdRepo, log: log("cls:service:price"), } } -// SetPrice 设置价格 -func (s *PriceService) SetPrice(dto *PriceDto) error { - if dto.Amount < 0 { - return ErrInvalidAmount - } - - // 检查是否已存在价格记录 - existingPrice, err := s.repo.FindByTargetID(dto.TargetID, dto.Type) - if err != nil { - // 如果记录不存在,创建新记录 - newPrice := dto.ToPrice() - return s.repo.Save(newPrice) - } - - // 如果记录存在,更新价格 - existingPrice.Amount = dto.Amount - existingPrice.OneMonthPrice = dto.OneMonthPrice - existingPrice.ThreeMonthsPrice = dto.ThreeMonthsPrice - existingPrice.SixMonthsPrice = dto.SixMonthsPrice - existingPrice.OneYearPrice = dto.OneYearPrice - existingPrice.AdminID = dto.AdminID - return s.repo.Update(existingPrice) -} - -// GetPrice 获取价格(如果不存在则使用默认价格) -func (s *PriceService) GetPrice(dto *PriceDto) (*PriceDto, error) { - // 检查是否已存在价格记录 - existingPrice, err := s.repo.FindByTargetID(dto.TargetID, dto.Type) - if err != nil { - // 如果记录不存在,使用默认价格 - var defaultAmount int64 - switch dto.Type { - case price.TypeArticle: - defaultAmount = price.DefaultArticlePrice - case price.TypeColumn: - defaultAmount = price.DefaultColumnPrice - default: - return nil, ErrInvalidType - } - // 创建默认价格记录 - newPrice := price.NewPrice(dto.TargetID, dto.Type, defaultAmount, 0) - if err := s.repo.Save(newPrice); err != nil { - return nil, err - } - if dto.Type == price.TypeArticle { - return &PriceDto{ - Amount: newPrice.Amount, - Discount: newPrice.Discount, - }, nil - } - return &PriceDto{ - OneMonthPrice: newPrice.OneMonthPrice, - ThreeMonthsPrice: newPrice.ThreeMonthsPrice, - SixMonthsPrice: newPrice.SixMonthsPrice, - OneYearPrice: newPrice.OneYearPrice, - Discount: 0.3, - }, nil - } - priceDto := &PriceDto{ - Discount: existingPrice.Discount, - } - if dto.Type == price.TypeArticle { - priceDto.Amount = existingPrice.Amount - } else { - priceDto.OneMonthPrice = existingPrice.OneMonthPrice - priceDto.ThreeMonthsPrice = existingPrice.ThreeMonthsPrice - priceDto.SixMonthsPrice = existingPrice.SixMonthsPrice - priceDto.OneYearPrice = existingPrice.OneYearPrice - } - - return priceDto, nil -} - func (s *PriceService) GetArticlePricePage(p *page.Page) error { conds := make([]builder.Cond, 0) - conds = append(conds, builder.Eq{"type": price.TypeArticle}) - data := make([]*price.Price, 0) + //conds = append(conds, builder.Eq{"type": price.TypeArticle}) + // todo 加入付费文章条件 + conds = append(conds, builder.And(builder.Gt{"ctime": time.Now().AddDate(0, 0, -7).Unix()})) + data := make([]*article.LianV1Article, 0) p.Content = &data - err := s.repo.FindAll(p, conds) + err := s.articleRepo.Find(p, conds) if err != nil { s.log.Error(err) return err } - ids := make([]uint64, 0, len(data)) for _, v := range data { - ids = append(ids, v.TargetID) + ids = append(ids, v.Id) } - as, err := s.articleRepo.GetArticlesByIds(ids) + priceList, err := s.repo.FindPriceByIds(&ids, price.TypeArticle) if err != nil { s.log.Error(err) return err } - asMap := make(map[uint64]*article2.ArticleDto, 0) - for _, v := range *as { - asMap[v.Id] = &article2.ArticleDto{ - EventId: v.Id, - Title: v.Title, - Class: article2.GetClassNameById(v.Type), - Stocks: v.Stocks, - Brief: v.Brief, + + asMap := make(map[uint64]*PriceDto, 0) + for _, v := range *priceList { + asMap[v.TargetID] = &PriceDto{ + ID: v.ID, + TargetID: v.TargetID, + Amount: v.Amount, + Discount: v.Discount, } } - ids = nil + dtoData := make([]*ArticlePriceDto, 0, len(data)) for _, v := range data { + t := time.Unix(v.Ctime, 0) // 秒数和纳秒数,0 表示没有纳秒部分 + dtoData = append(dtoData, &ArticlePriceDto{ - Article: asMap[v.TargetID], - Price: &PriceDto{ - ID: v.ID, - TargetID: v.TargetID, - Amount: v.Amount, - Discount: v.Discount, + Article: &article2.ArticleDto{ + EventId: v.Id, + Title: v.Title, + Class: article2.GetClassNameById(v.Type), + Stocks: v.Stocks, + ReleaseDate: t.Format("2006-01-02"), + ReleaseTime: t.Format("15:04"), + Brief: v.Brief, + Content: v.Content, }, + Price: asMap[v.Id], }) } p.Content = &dtoData @@ -154,13 +96,53 @@ func (s *PriceService) GetArticlePricePage(p *page.Page) error { func (s *PriceService) GetColumnPricePage(p *page.Page) error { conds := make([]builder.Cond, 0) - conds = append(conds, builder.Eq{"p.type": price.TypeColumn}) - data := make([]*ColumnPriceDto, 0) + data := make([]*column.Column, 0) p.Content = &data - err := s.repo.FindColumnAll(p, conds) + err := s.columnRepo.FindAll(p, conds) if err != nil { s.log.Error(err.Error()) } + ids := make([]uint64, 0, len(data)) + for _, v := range data { + ids = append(ids, v.ID) + } + priceList, err := s.repo.FindPriceByIds(&ids, price.TypeColumn) + if err != nil { + s.log.Error(err) + return err + } + asMap := make(map[uint64]*PriceDto, 0) + for _, v := range *priceList { + asMap[v.TargetID] = &PriceDto{ + ID: v.ID, + TargetID: v.TargetID, + FirstMontDiscount: v.FirstMontDiscount, + OneMonthPrice: v.OneMonthPrice, + ThreeMonthsPrice: v.ThreeMonthsPrice, + SixMonthsPrice: v.SixMonthsPrice, + OneYearPrice: v.OneYearPrice, + Discount: v.Discount, + } + } + + dtoData := make([]*ColumnPriceDto, 0, len(data)) + for _, v := range data { + dtoData = append(dtoData, &ColumnPriceDto{ + Column: &column2.ColumnDto{ + ID: v.ID, + Title: v.Title, + Brief: v.Brief, + Cover: v.Cover, + AuthorID: v.AuthorID, + ArticleNum: v.ArticleNum, + FollowNum: v.FollowNum, + PurchaseNum: v.PurchaseNum, + CreatedAt: v.CreatedAt, + }, + Price: asMap[v.ID], + }) + } + p.Content = &dtoData return err } @@ -173,15 +155,79 @@ func (s *PriceService) UpdatePrice(dto *PriceDto) error { if err := dto.Validate(); err != nil { return err } - existingPrice, err := s.repo.FindByTargetID(dto.TargetID, dto.Type) + var err error + articlePrice := &price.Price{ + TargetID: dto.TargetID, + Type: dto.Type, + Discount: dto.Discount, + } + if dto.ID < 1 { + + if dto.Type == price.TypeArticle { + articlePrice.Amount = dto.Amount + } else { + articlePrice.OneMonthPrice = dto.OneMonthPrice + articlePrice.ThreeMonthsPrice = dto.ThreeMonthsPrice + articlePrice.SixMonthsPrice = dto.SixMonthsPrice + articlePrice.OneYearPrice = dto.OneYearPrice + articlePrice.FirstMontDiscount = dto.FirstMontDiscount + articlePrice.AdminID = dto.AdminID + } + err = s.repo.Save(articlePrice) + if err != nil { + s.log.Error(err) + } + } else { + if dto.Type == price.TypeArticle { + articlePrice.Amount = dto.Amount + } else { + articlePrice.OneMonthPrice = dto.OneMonthPrice + articlePrice.ThreeMonthsPrice = dto.ThreeMonthsPrice + articlePrice.SixMonthsPrice = dto.SixMonthsPrice + articlePrice.OneYearPrice = dto.OneYearPrice + articlePrice.FirstMontDiscount = dto.FirstMontDiscount + articlePrice.AdminID = dto.AdminID + } + err = s.repo.Update(articlePrice) + if err != nil { + s.log.Error(err) + } + } + + return err + +} + +func (s *PriceService) GetPriceDefault() (*PriceDefaultDto, error) { + data, err := s.pdRepo.Get() if err != nil { - return err + s.log.Error(err.Error()) } - existingPrice.Amount = dto.Amount - existingPrice.OneMonthPrice = dto.OneMonthPrice - existingPrice.ThreeMonthsPrice = dto.ThreeMonthsPrice - existingPrice.SixMonthsPrice = dto.SixMonthsPrice - existingPrice.OneYearPrice = dto.OneYearPrice - existingPrice.AdminID = dto.AdminID - return s.repo.Update(existingPrice) + return &PriceDefaultDto{ + Id: data.Id, + Amount: data.Amount, + FirstMontDiscount: data.FirstMontDiscount, + OneMonthPrice: data.OneMonthPrice, + ThreeMonthsPrice: data.ThreeMonthsPrice, + SixMonthsPrice: data.SixMonthsPrice, + OneYearPrice: data.OneYearPrice, + Discount: data.Discount, + }, err +} + +func (s *PriceService) UpdatePriceDefault(pdDto *PriceDefaultDto) error { + err := s.pdRepo.Save(&price_default.PriceDefault{ + Id: pdDto.Id, + Amount: pdDto.Amount, + FirstMontDiscount: pdDto.FirstMontDiscount, + OneMonthPrice: pdDto.OneMonthPrice, + ThreeMonthsPrice: pdDto.ThreeMonthsPrice, + SixMonthsPrice: pdDto.SixMonthsPrice, + OneYearPrice: pdDto.OneYearPrice, + Discount: pdDto.Discount, + }) + if err != nil { + s.log.Error(err) + } + return err } diff --git a/internal/domain/price/repository.go b/internal/domain/price/repository.go index 031e4d1..06c0642 100644 --- a/internal/domain/price/repository.go +++ b/internal/domain/price/repository.go @@ -26,4 +26,6 @@ type PriceRepository interface { // Delete 删除价格记录 Delete(id uint64) error + + FindPriceByIds(id *[]uint64, t PriceType) (*[]*Price, error) } diff --git a/internal/domain/price_default/entity.go b/internal/domain/price_default/entity.go index b0cca80..8bc850a 100644 --- a/internal/domain/price_default/entity.go +++ b/internal/domain/price_default/entity.go @@ -1,10 +1,12 @@ package price_default -type DefaultPrice struct { - Id uint64 `xorm:"pk autoincr 'id'"` - Amount int64 `xorm:"not null 'amount'"` // 基础价格(分) - OneMonthPrice int64 `xorm:"not null 'one_month_price'"` // 1个月价格(分) - ThreeMonthsPrice int64 `xorm:"not null 'three_months_price'"` // 3个月价格(分) - SixMonthsPrice int64 `xorm:"not null 'six_months_price'"` // 6个月价格(分) - OneYearPrice int64 `xorm:"not null 'one_year_price'"` // 1年价格(分) +type PriceDefault struct { + Id uint64 `xorm:"pk autoincr 'id'"` + Amount int64 `xorm:"not null 'amount'"` // 单篇文章价格(分) + FirstMontDiscount float32 `xorm:"null"` //首月优惠折扣 + OneMonthPrice int64 `xorm:"not null 'one_month_price'"` // 1个月价格(分) + ThreeMonthsPrice int64 `xorm:"not null 'three_months_price'"` // 3个月价格(分) + SixMonthsPrice int64 `xorm:"not null 'six_months_price'"` // 6个月价格(分) + OneYearPrice int64 `xorm:"not null 'one_year_price'"` // 1年价格(分) + Discount float32 `xorm:"not null"` } diff --git a/internal/domain/price_default/repository.go b/internal/domain/price_default/repository.go index 9a2e79b..95bd561 100644 --- a/internal/domain/price_default/repository.go +++ b/internal/domain/price_default/repository.go @@ -1 +1,6 @@ package price_default + +type PriceDefaultRepository interface { + Save(price *PriceDefault) error + Get() (*PriceDefault, error) +} diff --git a/internal/infrastructure/persistence/article/article_repo.go b/internal/infrastructure/persistence/article/article_repo.go index 61748f8..e1f0d62 100644 --- a/internal/infrastructure/persistence/article/article_repo.go +++ b/internal/infrastructure/persistence/article/article_repo.go @@ -47,5 +47,5 @@ func (a *ArticleRepositoryORM) GetArticleById(id uint64) (*article.LianV1Article func (a *ArticleRepositoryORM) GetArticlesByIds(id []uint64) (*[]*article.LianV1Article, error) { data := make([]*article.LianV1Article, 0) - return &data, a.engine.Where(builder.In("id", id)).Find(&data) + return &data, a.engine.Cls.Where(builder.In("id", id)).Find(&data) } diff --git a/internal/infrastructure/persistence/column/column_repo.go b/internal/infrastructure/persistence/column/column_repo.go index 6b0f426..230ac26 100644 --- a/internal/infrastructure/persistence/column/column_repo.go +++ b/internal/infrastructure/persistence/column/column_repo.go @@ -55,7 +55,7 @@ func (r *ColumnRepositoryORM) FindAll(page *page.Page, conds []builder.Cond) err // Update 更新专栏 func (r *ColumnRepositoryORM) Update(col *column.Column) error { - _, err := r.engine.ID(col.ID).Update(col) + _, err := r.engine.ID(col.ID).AllCols().Update(col) return err } diff --git a/internal/infrastructure/persistence/price/price_repo.go b/internal/infrastructure/persistence/price/price_repo.go index e41ffe1..6473f96 100644 --- a/internal/infrastructure/persistence/price/price_repo.go +++ b/internal/infrastructure/persistence/price/price_repo.go @@ -5,7 +5,6 @@ import ( "cls-server/pkg/util/page" "cls-server/pkg/xorm_engine" "errors" - "fmt" "xorm.io/builder" ) @@ -45,16 +44,13 @@ func (p *PriceRepositoryORM) FindByID(id uint64) (*price.Price, error) { // FindByTargetID 根据目标ID查找价格记录 func (p *PriceRepositoryORM) FindByTargetID(targetID uint64, priceType price.PriceType) (*price.Price, error) { price := &price.Price{} - has, err := p.engine.Where(builder.Eq{ + _, err := p.engine.Where(builder.Eq{ "target_id": targetID, "type": priceType, }).Get(price) if err != nil { return nil, err } - if !has { - return nil, errors.New(fmt.Sprintf("未找到相关数据【target_id: %d, type: %d】", targetID, priceType)) - } return price, nil } @@ -65,10 +61,10 @@ func (p *PriceRepositoryORM) FindAll(pp *page.Page, conds []builder.Cond) error // FindColumnAll 查询专栏价格记录列表 func (p *PriceRepositoryORM) FindColumnAll(pp *page.Page, conds []builder.Cond) error { - return p.engine.FindAll(pp, "lc_price as p", builder.And(conds...), xorm_engine.Join{ + return p.engine.FindAll(pp, &price.Price{}, builder.And(conds...), xorm_engine.Join{ JoinOperator: "left", Tablename: "lc_column as c", - Condition: "p.target_id = c.id", + Condition: "lc_price.target_id = c.id", }) } @@ -83,3 +79,8 @@ func (p *PriceRepositoryORM) Delete(id uint64) error { _, err := p.engine.Delete(&price.Price{}, "id = ?", id) return err } + +func (p *PriceRepositoryORM) FindPriceByIds(id *[]uint64, t price.PriceType) (*[]*price.Price, error) { + data := make([]*price.Price, 0) + return &data, p.engine.Where(builder.Eq{"type": t}.And(builder.In("target_id", *id))).Find(&data) +} diff --git a/internal/infrastructure/persistence/price_default/price_default_repo.go b/internal/infrastructure/persistence/price_default/price_default_repo.go new file mode 100644 index 0000000..660460b --- /dev/null +++ b/internal/infrastructure/persistence/price_default/price_default_repo.go @@ -0,0 +1,32 @@ +package price_default + +import ( + "cls-server/internal/domain/price_default" + "cls-server/pkg/xorm_engine" + "xorm.io/builder" +) + +type PriceDefaultRepositoryORM struct { + engine *xorm_engine.Engine +} + +var _ price_default.PriceDefaultRepository = (*PriceDefaultRepositoryORM)(nil) + +func NewPriceRepositoryORM(engine *xorm_engine.Engine) price_default.PriceDefaultRepository { + return &PriceDefaultRepositoryORM{engine} +} + +func (p PriceDefaultRepositoryORM) Save(pd *price_default.PriceDefault) error { + if pd.Id > 0 { + _, err := p.engine.Update(pd) + return err + } + _, err := p.engine.Insert(pd) + return err +} + +func (p PriceDefaultRepositoryORM) Get() (*price_default.PriceDefault, error) { + data := &price_default.PriceDefault{} + _, err := p.engine.Where(builder.Eq{"id": 1}).Get(data) + return data, err +} diff --git a/internal/interfaces/column/column_handler.go b/internal/interfaces/column/column_handler.go index 2232bb2..13ca239 100644 --- a/internal/interfaces/column/column_handler.go +++ b/internal/interfaces/column/column_handler.go @@ -27,6 +27,7 @@ func (ch *ColumnHandler) RegisterRouters(app gin.IRouter) { columnApp := app.Group("/column") { columnApp.GET("/page", ch.list) + columnApp.POST("/save", ch.save) } } @@ -47,3 +48,19 @@ func (ch *ColumnHandler) list(c *gin.Context) { c.AbortWithStatusJSON(http.StatusOK, p) } } + +func (ch *ColumnHandler) save(c *gin.Context) { + dto := &column.ColumnDto{} + err := c.ShouldBindJSON(dto) + if err != nil { + ch.log.Error(err) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + err = ch.service.UpdateColumn(dto) + if err != nil { + c.AbortWithStatus(http.StatusInternalServerError) + } else { + c.AbortWithStatus(http.StatusOK) + } +} diff --git a/internal/interfaces/price/price_handler.go b/internal/interfaces/price/price_handler.go index 492afa7..068d861 100644 --- a/internal/interfaces/price/price_handler.go +++ b/internal/interfaces/price/price_handler.go @@ -4,6 +4,8 @@ import ( "cls-server/internal/application/price" "cls-server/internal/interfaces" "cls-server/pkg/logger" + "cls-server/pkg/util/page" + "fmt" "github.com/gin-gonic/gin" "net/http" ) @@ -22,14 +24,15 @@ func NewPriceHandler(service *price.PriceService, log logger.New) *PriceHandler func (h *PriceHandler) RegisterRouters(app gin.IRouter) { auth := app.Group("/price") { - auth.POST("/create", h.create) - auth.GET("/get", h.getPrice) - auth.PUT("/update", h.update) - //auth.GET("/page", h.getPriceList) + auth.GET("/page-article", h.getPriceArticle) + auth.GET("/page-column", h.getPriceColumn) + auth.GET("/get-default", h.getPriceDefault) + auth.POST("/update-default", h.updatePriceDefault) + auth.POST("/update", h.update) } } -func (h *PriceHandler) create(c *gin.Context) { +func (h *PriceHandler) update(c *gin.Context) { dto := &price.PriceDto{} err := c.ShouldBindJSON(dto) if err != nil { @@ -37,7 +40,7 @@ func (h *PriceHandler) create(c *gin.Context) { c.AbortWithStatus(http.StatusInternalServerError) return } - err = h.service.SetPrice(dto) + err = h.service.UpdatePrice(dto) if err != nil { h.log.Error(err) c.AbortWithStatus(http.StatusInternalServerError) @@ -46,56 +49,65 @@ func (h *PriceHandler) create(c *gin.Context) { } } -func (h *PriceHandler) getPrice(c *gin.Context) { - dto := &price.PriceDto{} +func (h *PriceHandler) getPriceDefault(c *gin.Context) { + fmt.Println("进来了getPriceDefault………………") + + p, err := h.service.GetPriceDefault() + if err != nil { + h.log.Error(err) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + c.JSON(http.StatusOK, p) +} + +func (h *PriceHandler) updatePriceDefault(c *gin.Context) { + dto := &price.PriceDefaultDto{} err := c.ShouldBindJSON(dto) if err != nil { h.log.Error(err.Error()) c.AbortWithStatus(http.StatusInternalServerError) return } - price, err := h.service.GetPrice(dto) + err = h.service.UpdatePriceDefault(dto) if err != nil { h.log.Error(err) c.AbortWithStatus(http.StatusInternalServerError) - return + } else { + c.AbortWithStatus(http.StatusOK) } - c.JSON(http.StatusOK, price) } -func (h *PriceHandler) update(c *gin.Context) { - dto := &price.PriceDto{} - err := c.ShouldBindJSON(dto) +func (h *PriceHandler) getPriceArticle(c *gin.Context) { + p := &page.Page{} + err := c.ShouldBindQuery(p) if err != nil { h.log.Error(err.Error()) c.AbortWithStatus(http.StatusInternalServerError) return } - err = h.service.UpdatePrice(dto) + err = h.service.GetArticlePricePage(p) if err != nil { - h.log.Error(err) + h.log.Error(err.Error()) c.AbortWithStatus(http.StatusInternalServerError) } else { - c.AbortWithStatus(http.StatusOK) + c.JSON(http.StatusOK, p) } } -// -//func (h *PriceHandler) getPriceList(c *gin.Context) { -// page := &page.Page{} -// err := c.ShouldBindJSON(page) -// if err != nil { -// h.log.Error(err.Error()) -// c.AbortWithStatus(http.StatusInternalServerError) -// return -// } -// err = h.service.GetPriceList(page, map[string]string{ -// "search_eq_type": c.Query("search_eq_type"), -// }) -// if err != nil { -// h.log.Error(err) -// c.AbortWithStatus(http.StatusInternalServerError) -// return -// } -// c.JSON(http.StatusOK, page) -//} +func (h *PriceHandler) getPriceColumn(c *gin.Context) { + p := &page.Page{} + err := c.ShouldBindQuery(p) + if err != nil { + h.log.Error(err.Error()) + c.AbortWithStatus(http.StatusInternalServerError) + return + } + err = h.service.GetColumnPricePage(p) + if err != nil { + h.log.Error(err.Error()) + c.AbortWithStatus(http.StatusInternalServerError) + } else { + c.JSON(http.StatusOK, p) + } +} diff --git a/internal/modules/module.go b/internal/modules/module.go index 53fff18..564cdf8 100644 --- a/internal/modules/module.go +++ b/internal/modules/module.go @@ -5,6 +5,7 @@ import ( "cls-server/internal/domain/coupon" "cls-server/internal/domain/gift_log" "cls-server/internal/domain/price" + "cls-server/internal/domain/price_default" "cls-server/pkg/logger" "cls-server/pkg/xorm_engine" "go.uber.org/fx" @@ -32,6 +33,7 @@ func registerModels(engine *xorm_engine.Engine, logger logger.New) { &gift_log.GiftLog{}, &price.Price{}, &coupon.Coupon{}, + &price_default.PriceDefault{}, ); err != nil { log.Error(err) } diff --git a/internal/modules/price_module.go b/internal/modules/price_module.go index a08750c..3af50da 100644 --- a/internal/modules/price_module.go +++ b/internal/modules/price_module.go @@ -3,6 +3,7 @@ package modules import ( service "cls-server/internal/application/price" repo "cls-server/internal/infrastructure/persistence/price" + "cls-server/internal/infrastructure/persistence/price_default" "cls-server/internal/interfaces" "cls-server/internal/interfaces/price" "go.uber.org/fx" @@ -12,5 +13,6 @@ var PriceModule = fx.Module("PriceModule", fx.Provide( interfaces.AsHandler(price.NewPriceHandler), service.NewPriceService, + price_default.NewPriceRepositoryORM, repo.NewPriceRepositoryORM, )) diff --git a/ui/src/app/core/models/price.ts b/ui/src/app/core/models/price.ts index 88144cc..3afd38d 100644 --- a/ui/src/app/core/models/price.ts +++ b/ui/src/app/core/models/price.ts @@ -1,11 +1,40 @@ +import { Article } from "./article"; +import { Column } from "./column"; export interface Price { id: number; targetId: number; - type: string; + type: PriceType; amount: number; + firstMontDiscount:number; oneMonthPrice: number; threeMonthsPrice: number; sixMonthsPrice: number; oneYearPrice: number; discount: number; } + +export enum PriceType { + TypeArticle = 1, + TypeColumn = 2 +} + +export interface PriceDefault { + id: number; + amount: number; + firstMontDiscount:number; + oneMonthPrice: number; + threeMonthsPrice: number; + sixMonthsPrice: number; + oneYearPrice: number; + discount: number; +} +export interface ArticlePrice { + article: Article; + price: Price; +} + +export interface ColumnPrice { + column: Column; + price: Price; +} + diff --git a/ui/src/app/page/column/column.component.html b/ui/src/app/page/column/column.component.html index f693c09..9273540 100644 --- a/ui/src/app/page/column/column.component.html +++ b/ui/src/app/page/column/column.component.html @@ -1,5 +1,5 @@ - + 后台管理 @@ -44,31 +44,29 @@ - {{ item.title }} +
+ + {{ item.title }} + + 专栏ID:{{ item.id }} + + 文章数:{{ item.articleNum }} + + 关注人数:{{item.followNum}} + + 订购人数:{{item.purchaseNum}} + + +
- - 专栏ID:{{ item.id }} - - - 专栏介绍:{{ item.brief }} - - - 关注人数:{{item.followNum}} - - - 订购人数:{{item.purchaseNum}} - - - - - - - - + + 专栏描述:{{item.brief}} - + diff --git a/ui/src/app/page/column/column.component.ts b/ui/src/app/page/column/column.component.ts index 8767b5d..b63a1bf 100644 --- a/ui/src/app/page/column/column.component.ts +++ b/ui/src/app/page/column/column.component.ts @@ -21,7 +21,7 @@ import {NzPaginationComponent} from "ng-zorro-antd/pagination"; import {NgFor} from "@angular/common"; import {EditColumnComponent} from "./edit-column/edit-column.component"; import {NzModalService} from "ng-zorro-antd/modal"; - +import {NzDividerModule} from "ng-zorro-antd/divider"; @Component({ selector: 'app-column', standalone: true, @@ -39,6 +39,7 @@ import {NzModalService} from "ng-zorro-antd/modal"; NzPaginationComponent, NzCardModule, NzGridModule, NzListModule, NgFor, + NzDividerModule, ], templateUrl: './column.component.html' }) @@ -129,29 +130,17 @@ export class ColumnComponent implements OnInit{ } editColumn(column: Column) { - console.log(column) - const modal = this.modalService.create({ - nzTitle: `更新基本信息`, + console.log('Editing column:', column); + const modalRef = this.modalService.create({ + nzTitle: '编辑专栏', nzContent: EditColumnComponent, - nzMaskClosable: false, - nzClosable: false, - nzData:{column:column}, - nzFooter: [ - { - label: '取消', - onClick: componentInstance => componentInstance!.handleCancel() - }, - { - label: '确定', - type: 'primary', - loading: componentInstance => componentInstance!.isSaving, - onClick: componentInstance => componentInstance!.handleOk(), - disabled: componentInstance => !componentInstance!.validateForm.valid - } - ] - }) - modal.afterClose.subscribe(result => { - if (result != null) { + nzData: { column }, + nzFooter: null, + nzWidth: 600 + }); + + modalRef.afterClose.subscribe(result => { + if (result) { this.loadData(); } }); diff --git a/ui/src/app/page/column/edit-column/edit-column.component.html b/ui/src/app/page/column/edit-column/edit-column.component.html index 76012df..2a3b666 100644 --- a/ui/src/app/page/column/edit-column/edit-column.component.html +++ b/ui/src/app/page/column/edit-column/edit-column.component.html @@ -1,37 +1,34 @@ -
+ - 专栏名 - - + 专栏名称 + + + - 专栏描述 - - - - - 描述不能为空 - - + 专栏描述 + + + - 专栏封面 - - + 关注人数 + + + - 关注人数 - - - - - - 购买人数 - - + 购买人数 + + + +
diff --git a/ui/src/app/page/column/edit-column/edit-column.component.scss b/ui/src/app/page/column/edit-column/edit-column.component.scss index e69de29..9d70889 100644 --- a/ui/src/app/page/column/edit-column/edit-column.component.scss +++ b/ui/src/app/page/column/edit-column/edit-column.component.scss @@ -0,0 +1,27 @@ +.modal-footer { + margin-top: 24px; + text-align: right; + + button { + margin-left: 8px; + } +} + +:host { + display: block; + width: 100%; + padding: 24px; +} + +nz-form-item { + margin-bottom: 24px; +} + +nz-form-label { + font-weight: 500; + color: rgba(0, 0, 0, 0.85); +} + +input { + width: 100%; +} diff --git a/ui/src/app/page/column/edit-column/edit-column.component.ts b/ui/src/app/page/column/edit-column/edit-column.component.ts index 00e0de5..d9f3ed4 100644 --- a/ui/src/app/page/column/edit-column/edit-column.component.ts +++ b/ui/src/app/page/column/edit-column/edit-column.component.ts @@ -1,12 +1,14 @@ -import {Component, Input, OnInit} from '@angular/core'; -import {Column} from "../../../core/models/column"; -import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from "@angular/forms"; -import {NzModalRef} from "ng-zorro-antd/modal"; -import {ColumnService} from "../column.service"; -import {NzMessageService} from "ng-zorro-antd/message"; -import {NzColDirective, NzRowDirective} from "ng-zorro-antd/grid"; +import { Component, Inject, OnInit } from '@angular/core'; +import { Column } from "../../../core/models/column"; +import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms"; +import { NzModalRef, NZ_MODAL_DATA } from "ng-zorro-antd/modal"; +import { ColumnService } from "../column.service"; +import { NzMessageService } from "ng-zorro-antd/message"; +import { NzColDirective, NzRowDirective } from "ng-zorro-antd/grid"; import { NzFormModule } from 'ng-zorro-antd/form'; -import {NzInputDirective} from "ng-zorro-antd/input"; +import { NzInputDirective } from "ng-zorro-antd/input"; +import { NzButtonModule } from 'ng-zorro-antd/button'; + @Component({ selector: 'app-edit-column', standalone: true, @@ -15,58 +17,61 @@ import {NzInputDirective} from "ng-zorro-antd/input"; NzRowDirective, NzColDirective, NzFormModule, - NzInputDirective + NzInputDirective, + NzButtonModule ], templateUrl: './edit-column.component.html', styleUrl: './edit-column.component.scss' }) -export class EditColumnComponent implements OnInit{ - @Input() column!:Column - isSaving:boolean = false +export class EditColumnComponent implements OnInit { + isSaving: boolean = false; validateForm!: FormGroup; - constructor(private fb:FormBuilder, - private modal:NzModalRef, - private columnService:ColumnService, - private message:NzMessageService) { - } + constructor( + private fb: FormBuilder, + private modal: NzModalRef, + private columnService: ColumnService, + private message: NzMessageService, + @Inject(NZ_MODAL_DATA) private data: { column: Column } + ) {} + ngOnInit(): void { - console.log(this.column) this.validateForm = this.fb.group({ - id:[null,[Validators.required]], - title:[null,[Validators.required]], - brief:[null,[Validators.required]], - cover:[null], - followNum:[null,[Validators.required]], - purchaseNum:[null,[Validators.required]] - }) - console.log('validateForm 初始化成功:', this.validateForm); - // if(this.column != null) { - // this.validateForm.patchValue(this.column); - // - // } + id: [this.data.column.id, [Validators.required]], + title: [this.data.column.title, [Validators.required]], + brief: [this.data.column.brief, [Validators.required]], + followNum: [this.data.column.followNum, [Validators.required, Validators.min(0)]], + purchaseNum: [this.data.column.purchaseNum, [Validators.required, Validators.min(0)]] + }); } + handleOk(): void { - //tslint:disable-next-line:forin - for (const i in this.validateForm.controls) { - this.validateForm.controls[i].markAsDirty(); - } if (this.validateForm.valid) { this.isSaving = true; - this.columnService.save(this.validateForm.value).subscribe(() => { - this.isSaving = false; - this.message.success('保存成功'); - this.modal.close('success'); - }, (err) => { - this.message.error('保存失败'); - this.isSaving = false; + this.columnService.save(this.validateForm.value).subscribe({ + next: () => { + this.message.success('保存成功'); + this.modal.close(true); + }, + error: (error) => { + this.message.error('保存失败'); + console.error('Error saving column:', error); + }, + complete: () => { + this.isSaving = false; + } + }); + } else { + Object.values(this.validateForm.controls).forEach(control => { + if (control.invalid) { + control.markAsDirty(); + control.updateValueAndValidity({ onlySelf: true }); + } }); } } handleCancel(): void { - this.modal.destroy(); + this.modal.close(); } - - } diff --git a/ui/src/app/page/price/article/article.component.html b/ui/src/app/page/price/article/article.component.html new file mode 100644 index 0000000..6802738 --- /dev/null +++ b/ui/src/app/page/price/article/article.component.html @@ -0,0 +1,59 @@ + + +
+ + + +
+ + + + + + + + 文章ID + 文章名称 + 发布时间 + 价格(元) + 折扣 + 操作 + + + + + {{ data?.article?.eventId }} + {{ data?.article?.title }} + {{ data?.article?.releaseDate }} {{ data?.article?.releaseTime }} + {{ data?.price?.amount ? data.price.amount / 100:'默认价格' }} + {{ data?.price?.discount ? data.price.discount * 100 :'默认折扣'}}% + + 编辑价格 + + + + + + 第{{range[0]}}-{{range[1]}}个,共{{total}}个 + +
+ + + + diff --git a/ui/src/app/page/price/article/article.component.scss b/ui/src/app/page/price/article/article.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/ui/src/app/page/price/article/article.component.ts b/ui/src/app/page/price/article/article.component.ts new file mode 100644 index 0000000..a24870f --- /dev/null +++ b/ui/src/app/page/price/article/article.component.ts @@ -0,0 +1,155 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Page } from '../../../core/models/page'; +import { ArticlePrice } from '../../../core/models/price'; +import { PriceService } from '../price.service'; +import { debounceTime, distinctUntilChanged, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { ChangeDetectorRef } from '@angular/core'; +import { NzMessageModule, NzMessageService } from 'ng-zorro-antd/message'; +import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal'; +import { NzFormModule } from 'ng-zorro-antd/form'; +import { NzInputModule } from 'ng-zorro-antd/input'; +import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; +import { NzCardModule } from 'ng-zorro-antd/card'; +import { NzTableModule } from 'ng-zorro-antd/table'; +import { NzIconModule } from 'ng-zorro-antd/icon'; +import { FormsModule } from '@angular/forms'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { NgForOf } from "@angular/common"; +import { SetArticlePriceComponent } from "./set-article-price/set-article-price.component"; + +@Component({ + selector: 'app-article', + standalone: true, + imports: [ + NzMessageModule, + NzModalModule, + NzFormModule, + NzInputModule, + NzInputNumberModule, + NzCardModule, + NzTableModule, + NzIconModule, + FormsModule, + ReactiveFormsModule, + NgForOf, + SetArticlePriceComponent + ], + templateUrl: './article.component.html', + styleUrl: './article.component.scss' +}) +export class ArticleComponent implements OnInit, OnDestroy { + pageData: Page = new Page(); + searchParams: { [param: string]: any } = { + page: 0, + size: 10, + search_like_username: '' + }; + onValueChange = new Subject(); + loading = false; + destroy$ = new Subject(); + editForm: FormGroup; + + constructor( + private priceService: PriceService, + private cdr: ChangeDetectorRef, + private message: NzMessageService, + private modal: NzModalService, + private fb: FormBuilder + ) { + this.onValueChange.pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.destroy$) + ).subscribe(() => { + this.searchParams['page'] = 0; + this.cdr.markForCheck(); + this.loadData(); + }); + + this.editForm = this.fb.group({ + price: [null, [Validators.required, Validators.min(0)]], + discount: [1, [Validators.required, Validators.min(0), Validators.max(1)]] + }); + } + + ngOnInit(): void { + console.log('Component initialized'); + this.onValueChange + .pipe(debounceTime(500)) + .subscribe(() => { + this.loadData(); + }); + this.loadData(); + } + + ngOnDestroy(): void { + this.destroy$.next(null); + this.destroy$.complete(); + } + + loadData() { + console.log('Loading data with params:', this.searchParams); + this.loading = true; + this.cdr.detectChanges(); + + this.priceService.pageOfArticle(this.searchParams) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: data => { + console.log('Raw response data:', data); + this.pageData = data; + console.log('Processed pageData:', this.pageData); + if (this.pageData?.items?.length > 0) { + console.log('First item:', this.pageData.items[0]); + console.log('First item article:', this.pageData.items[0]?.article); + console.log('First item price:', this.pageData.items[0]?.price); + } + this.loading = false; + this.cdr.detectChanges(); + }, + error: error => { + console.error('Error loading data:', error); + this.loading = false; + this.message.error("获取文章数据失败!"); + this.cdr.detectChanges(); + } + }); + } + + changePaginationPage(page: number): void { + console.log('Changing page to:', page); + this.searchParams['page'] = page - 1; + this.cdr.detectChanges(); + this.loadData(); + } + + changePaginationSize(size: number): void { + console.log('Changing page size to:', size); + this.searchParams['page'] = 0; + this.searchParams['size'] = size; + this.cdr.detectChanges(); + this.loadData(); + } + + editPrice(article: ArticlePrice) { + console.log('Editing article:', article); + const modalRef = this.modal.create({ + nzTitle: '编辑文章价格', + nzContent: SetArticlePriceComponent, + nzData: article, + nzFooter: null + }); + + modalRef.afterClose.subscribe(result => { + if (result) { + this.loadData(); + } + }); + } + + // 将元转换为分 + private convertYuanToCents(yuan: number): number { + return Math.round(yuan * 100); + } +} diff --git a/ui/src/app/page/price/article/set-article-price/set-article-price.component.html b/ui/src/app/page/price/article/set-article-price/set-article-price.component.html new file mode 100644 index 0000000..ec85b33 --- /dev/null +++ b/ui/src/app/page/price/article/set-article-price/set-article-price.component.html @@ -0,0 +1,20 @@ +
+ + 价格(元) + + + + + + + 折扣 + + + + + + +
diff --git a/ui/src/app/page/price/article/set-article-price/set-article-price.component.scss b/ui/src/app/page/price/article/set-article-price/set-article-price.component.scss new file mode 100644 index 0000000..26def58 --- /dev/null +++ b/ui/src/app/page/price/article/set-article-price/set-article-price.component.scss @@ -0,0 +1,21 @@ +:host { + display: block; + padding: 24px; +} + +nz-form-item { + margin-bottom: 24px; +} + +nz-input-number { + width: 100%; +} + +.modal-footer { + margin-top: 24px; + text-align: right; + + button { + margin-left: 8px; + } +} diff --git a/ui/src/app/page/price/article/set-article-price/set-article-price.component.ts b/ui/src/app/page/price/article/set-article-price/set-article-price.component.ts new file mode 100644 index 0000000..d5f6dac --- /dev/null +++ b/ui/src/app/page/price/article/set-article-price/set-article-price.component.ts @@ -0,0 +1,71 @@ +import { Component, Inject } from '@angular/core'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { NzFormModule } from 'ng-zorro-antd/form'; +import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; +import { NzModalRef, NZ_MODAL_DATA } from 'ng-zorro-antd/modal'; +import {ArticlePrice, Price, PriceType} from '../../../../core/models/price'; +import { PriceService } from '../../price.service'; +import { NzMessageService } from 'ng-zorro-antd/message'; +import { NzButtonModule } from 'ng-zorro-antd/button'; + +@Component({ + selector: 'app-set-article-price', + standalone: true, + imports: [ + ReactiveFormsModule, + NzFormModule, + NzInputNumberModule, + NzButtonModule + ], + templateUrl: './set-article-price.component.html', + styleUrls: ['./set-article-price.component.scss'] +}) +export class SetArticlePriceComponent { + editForm: FormGroup; + article: ArticlePrice; + priceType = PriceType; + constructor( + private fb: FormBuilder, + private modalRef: NzModalRef, + private priceService: PriceService, + private message: NzMessageService, + @Inject(NZ_MODAL_DATA) data: ArticlePrice + ) { + this.article = data; + console.log(this.article) + const price = data?.price?.amount || 0; + const discount = data?.price?.discount || 1; + + this.editForm = this.fb.group({ + price: [price / 100, [Validators.required, Validators.min(0)]], + discount: [discount, [Validators.required, Validators.min(0), Validators.max(1)]] + }); + } + + handleOk(): void { + if (this.editForm.valid) { + const formValue = this.editForm.value; + const priceData = { + targetId:this.article.article.eventId, + type:this.priceType.TypeArticle, + amount: Math.round(formValue.price * 100), + discount: formValue.discount + }; + + this.priceService.update(priceData as Price).subscribe({ + next: () => { + this.message.success('价格更新成功'); + this.modalRef.close(true); + }, + error: (error) => { + this.message.error('价格更新失败'); + console.error('Error updating price:', error); + } + }); + } + } + + handleCancel(): void { + this.modalRef.close(); + } +} diff --git a/ui/src/app/page/price/column/column.component.html b/ui/src/app/page/price/column/column.component.html new file mode 100644 index 0000000..8e4fb07 --- /dev/null +++ b/ui/src/app/page/price/column/column.component.html @@ -0,0 +1,63 @@ + + +
+ + + +
+ + + + + + 专栏ID + 专栏名称 + 一个月价格(元) + 三个月价格(元) + 半年价格(元) + 一年价格(元) + 首月折扣 + 折扣 + 操作 + + + + + {{ data.column.id }} + {{ data.column.title }} + {{ data.price?.oneMonthPrice ? data.price.oneMonthPrice / 100 :'默认价格' }} + {{ data.price?.threeMonthsPrice ? data.price.threeMonthsPrice / 100 :'默认价格'}} + {{ data.price?.sixMonthsPrice ? data.price.sixMonthsPrice / 100 :'默认价格'}} + {{ data.price?.oneYearPrice ? data.price.oneYearPrice / 100 :'默认价格'}} + {{ data.price?.firstMontDiscount ? data.price.firstMontDiscount * 100 :'默认折扣'}} + {{ data.price?.discount ? data.price.discount * 100 :'默认折扣'}} + + 编辑价格 + + + + + + 第{{range[0]}}-{{range[1]}}个,共{{total}}个 + +
+ + + + diff --git a/ui/src/app/page/price/column/column.component.scss b/ui/src/app/page/price/column/column.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/ui/src/app/page/price/column/column.component.ts b/ui/src/app/page/price/column/column.component.ts new file mode 100644 index 0000000..c305e2d --- /dev/null +++ b/ui/src/app/page/price/column/column.component.ts @@ -0,0 +1,137 @@ +import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Page } from '../../../core/models/page'; +import { ColumnPrice } from '../../../core/models/price'; +import { PriceService } from '../price.service'; +import { debounceTime, distinctUntilChanged, Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { ChangeDetectorRef } from '@angular/core'; +import { NzMessageModule, NzMessageService } from 'ng-zorro-antd/message'; +import { NzModalModule, NzModalService } from 'ng-zorro-antd/modal'; +import { NzFormModule } from 'ng-zorro-antd/form'; +import { NzInputModule } from 'ng-zorro-antd/input'; +import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; +import { NzCardModule } from 'ng-zorro-antd/card'; +import { NzTableModule } from 'ng-zorro-antd/table'; +import { NzIconModule } from 'ng-zorro-antd/icon'; +import { FormsModule } from '@angular/forms'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import {SetColumnPriceComponent} from "./set-column-price/set-column-price.component"; +import {NgForOf} from "@angular/common"; + +@Component({ + selector: 'app-column', + standalone: true, + imports: [ + NzMessageModule, + NzModalModule, + NzFormModule, + NzInputModule, + NzInputNumberModule, + NzCardModule, + NzTableModule, + NzIconModule, + FormsModule, + ReactiveFormsModule, + NgForOf + ], + templateUrl: './column.component.html', + styleUrl: './column.component.scss' +}) +export class ColumnComponent implements OnInit, OnDestroy { + pageData: Page = new Page(); + searchParams: { [param: string]: any } = { + page: 0, + size: 10, + search_like_username: '' + }; + onValueChange = new Subject(); + loading = false; + destroy$ = new Subject(); + editForm: FormGroup; + + constructor( + private priceService: PriceService, + private cdr: ChangeDetectorRef, + private message: NzMessageService, + private modal: NzModalService, + private fb: FormBuilder + ) { + this.onValueChange.pipe( + debounceTime(300), + distinctUntilChanged(), + takeUntil(this.destroy$) + ).subscribe(() => { + this.searchParams['page'] = 0; + this.cdr.markForCheck(); + this.loadData(); + }); + + this.editForm = this.fb.group({ + oneMonthPrice: [null, [Validators.required, Validators.min(0)]], + threeMonthsPrice: [null, [Validators.required, Validators.min(0)]], + sixMonthsPrice: [null, [Validators.required, Validators.min(0)]], + oneYearPrice: [null, [Validators.required, Validators.min(0)]] + }); + } + + ngOnInit(): void { + this.loadData(); + } + + ngOnDestroy(): void { + this.destroy$.next(null); + this.destroy$.complete(); + } + + loadData() { + this.loading = true; + this.cdr.detectChanges(); + this.priceService.pageOfColumn(this.searchParams) + .pipe(takeUntil(this.destroy$)) + .subscribe({ + next: data => { + this.loading = false; + this.pageData = data; + }, + error: data => { + console.log(data) + this.loading = false; + this.message.error("获取专栏数据失败!"); + } + }); + } + + changePaginationPage(page: number): void { + this.searchParams['page'] = page - 1; + this.cdr.detectChanges(); + this.loadData(); + } + + changePaginationSize(size: number): void { + this.searchParams['page'] = 0; + this.searchParams['size'] = size; + this.cdr.detectChanges(); + this.loadData(); + } + + editPrice(columnPrice: ColumnPrice) { + console.log('Editing column:', columnPrice); + const modalRef = this.modal.create({ + nzTitle: '编辑专栏价格', + nzContent: SetColumnPriceComponent, + nzData: columnPrice, + nzFooter: null + }); + + modalRef.afterClose.subscribe(result => { + if (result) { + this.loadData(); + } + }); + } + + // 将元转换为分 + private convertYuanToCents(yuan: number): number { + return Math.round(yuan * 100); + } +} diff --git a/ui/src/app/page/price/column/set-column-price/set-column-price.component.html b/ui/src/app/page/price/column/set-column-price/set-column-price.component.html new file mode 100644 index 0000000..ccb4672 --- /dev/null +++ b/ui/src/app/page/price/column/set-column-price/set-column-price.component.html @@ -0,0 +1,48 @@ +
+ + 一个月价格 + + + + + + + 三个月价格 + + + + + + + 半年价格 + + + + + + + 一年价格 + + + + + + + 首月折扣 + + + + + + + 折扣 + + + + + + +
diff --git a/ui/src/app/page/price/column/set-column-price/set-column-price.component.scss b/ui/src/app/page/price/column/set-column-price/set-column-price.component.scss new file mode 100644 index 0000000..52fa6a6 --- /dev/null +++ b/ui/src/app/page/price/column/set-column-price/set-column-price.component.scss @@ -0,0 +1,31 @@ +.modal-footer { + margin-top: 24px; + text-align: right; + + button { + margin-left: 8px; + } +} + +:host { + display: block; + width: 100%; + padding: 24px; +} + +.full-width { + width: 100%; +} + +nz-form-item { + margin-bottom: 24px; +} + +nz-form-label { + font-weight: 500; + color: rgba(0, 0, 0, 0.85); +} + +nz-input-number { + width: 100%; +} diff --git a/ui/src/app/page/price/column/set-column-price/set-column-price.component.ts b/ui/src/app/page/price/column/set-column-price/set-column-price.component.ts new file mode 100644 index 0000000..0764e94 --- /dev/null +++ b/ui/src/app/page/price/column/set-column-price/set-column-price.component.ts @@ -0,0 +1,94 @@ +import { Component, Inject } from '@angular/core'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { NzFormModule } from 'ng-zorro-antd/form'; +import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; +import { NzModalRef, NZ_MODAL_DATA } from 'ng-zorro-antd/modal'; +import { ColumnPrice } from '../../../../core/models/price'; +import { PriceService } from '../../price.service'; +import { NzMessageService } from 'ng-zorro-antd/message'; +import { NzButtonModule } from 'ng-zorro-antd/button'; + +@Component({ + selector: 'app-set-column-price', + standalone: true, + imports: [ + ReactiveFormsModule, + NzFormModule, + NzInputNumberModule, + NzButtonModule + ], + templateUrl: './set-column-price.component.html', + styleUrls: ['./set-column-price.component.scss'] +}) +export class SetColumnPriceComponent { + editForm: FormGroup; + column: ColumnPrice; + + // 价格格式化器 + yuanFormatter = (value: number): string => { + return value ? `¥ ${value}` : ''; + }; + + yuanParser = (value: string): string => { + return value.replace(/[^0-9.]/g, ''); + }; + + // 百分比格式化器 + percentFormatter = (value: number): string => { + return value ? `${(value * 100).toFixed(0)}%` : ''; + }; + + percentParser = (value: string): string => { + return value.replace('%', '').trim(); + }; + + constructor( + private fb: FormBuilder, + private modalRef: NzModalRef, + private priceService: PriceService, + private message: NzMessageService, + @Inject(NZ_MODAL_DATA) data: ColumnPrice + ) { + this.column = data; + const price = data?.price || {}; + + this.editForm = this.fb.group({ + oneMonthPrice: [price?.oneMonthPrice ? price.oneMonthPrice / 100 : 0, [Validators.required, Validators.min(0)]], + threeMonthsPrice: [price?.threeMonthsPrice ? price.threeMonthsPrice / 100 : 0, [Validators.required, Validators.min(0)]], + sixMonthsPrice: [price?.sixMonthsPrice ? price.sixMonthsPrice / 100 : 0, [Validators.required, Validators.min(0)]], + oneYearPrice: [price?.oneYearPrice ? price.oneYearPrice / 100 : 0, [Validators.required, Validators.min(0)]], + firstMontDiscount: [price?.firstMontDiscount || 1, [Validators.required, Validators.min(0), Validators.max(1)]], + discount: [price?.discount || 1, [Validators.required, Validators.min(0), Validators.max(1)]] + }); + } + + handleOk(): void { + if (this.editForm.valid) { + const formValue = this.editForm.value; + const priceData = { + ...this.column.price, + oneMonthPrice: Math.round(formValue.oneMonthPrice * 100), + threeMonthsPrice: Math.round(formValue.threeMonthsPrice * 100), + sixMonthsPrice: Math.round(formValue.sixMonthsPrice * 100), + oneYearPrice: Math.round(formValue.oneYearPrice * 100), + firstMontDiscount: formValue.firstMontDiscount, + discount: formValue.discount + }; + + this.priceService.update(priceData).subscribe({ + next: () => { + this.message.success('价格更新成功'); + this.modalRef.close(true); + }, + error: (error) => { + this.message.error('价格更新失败'); + console.error('Error updating price:', error); + } + }); + } + } + + handleCancel(): void { + this.modalRef.close(); + } +} diff --git a/ui/src/app/page/price/price-default/price-default.component.html b/ui/src/app/page/price/price-default/price-default.component.html new file mode 100644 index 0000000..b22b049 --- /dev/null +++ b/ui/src/app/page/price/price-default/price-default.component.html @@ -0,0 +1,70 @@ + +
+ + 单次解锁价格 + + + + + + + + 首月折扣率 (%) + + + + + + 一个月价格 + + + + + + + + + 三个月价格 + + + + + + + + + 六个月价格 + + + + + + + + + 一年价格 + + + + + + + + + 折扣率 (%) + + + + + + + + + + +
+
+ + + + diff --git a/ui/src/app/page/price/price-default/price-default.component.scss b/ui/src/app/page/price/price-default/price-default.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/ui/src/app/page/price/price-default/price-default.component.ts b/ui/src/app/page/price/price-default/price-default.component.ts new file mode 100644 index 0000000..2bb4aef --- /dev/null +++ b/ui/src/app/page/price/price-default/price-default.component.ts @@ -0,0 +1,122 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule, ReactiveFormsModule, FormBuilder, FormGroup, Validators } from '@angular/forms'; +import { NzFormModule } from 'ng-zorro-antd/form'; +import { NzInputModule } from 'ng-zorro-antd/input'; +import { NzButtonModule } from 'ng-zorro-antd/button'; +import { NzMessageService } from 'ng-zorro-antd/message'; +import { PriceService } from '../price.service'; +import { PriceDefault } from '../../../core/models/price'; +import { NzInputNumberModule } from 'ng-zorro-antd/input-number'; +import { NzCardModule } from 'ng-zorro-antd/card'; + +@Component({ + selector: 'app-price-default', + standalone: true, + imports: [ + CommonModule, + FormsModule, + ReactiveFormsModule, + NzFormModule, + NzInputModule, + NzButtonModule, + NzInputNumberModule, + NzCardModule, + ], + templateUrl: './price-default.component.html', + styleUrl: './price-default.component.scss' +}) +export class PriceDefaultComponent implements OnInit { + priceForm: FormGroup; + loading = false; + + constructor( + private fb: FormBuilder, + private priceService: PriceService, + private message: NzMessageService + ) { + this.priceForm = this.fb.group({ + amount: [null, [Validators.required, Validators.min(0)]], + firstMontDiscount: [null, [Validators.required, Validators.min(0), Validators.max(100)]], + oneMonthPrice: [null, [Validators.required, Validators.min(0)]], + threeMonthsPrice: [null, [Validators.required, Validators.min(0)]], + sixMonthsPrice: [null, [Validators.required, Validators.min(0)]], + oneYearPrice: [null, [Validators.required, Validators.min(0)]], + discount: [null, [Validators.required, Validators.min(0), Validators.max(100)]] + }); + } + + ngOnInit() { + this.loadDefaultPrice(); + } + + // 将分转换为元 + private convertCentsToYuan(cents: number): number { + return cents / 100; + } + + // 将元转换为分 + private convertYuanToCents(yuan: number): number { + return Math.round(yuan * 100); + } + + loadDefaultPrice() { + this.loading = true; + this.priceService.getDefaultPrice().subscribe({ + next: (data) => { + // 将后端返回的分转换为元 + const formData = { + ...data, + amount: this.convertCentsToYuan(data.amount), + oneMonthPrice: this.convertCentsToYuan(data.oneMonthPrice), + threeMonthsPrice: this.convertCentsToYuan(data.threeMonthsPrice), + sixMonthsPrice: this.convertCentsToYuan(data.sixMonthsPrice), + oneYearPrice: this.convertCentsToYuan(data.oneYearPrice) + }; + this.priceForm.patchValue(formData); + this.loading = false; + }, + error: (error) => { + this.message.error('获取默认价格失败'); + this.loading = false; + } + }); + } + + onSubmit() { + if (this.priceForm.valid) { + this.loading = true; + const formValue = this.priceForm.value; + + // 将表单中的元转换为分 + const priceData: PriceDefault = { + id: 1, + amount: this.convertYuanToCents(formValue.amount), + firstMontDiscount: formValue.firstMontDiscount, + oneMonthPrice: this.convertYuanToCents(formValue.oneMonthPrice), + threeMonthsPrice: this.convertYuanToCents(formValue.threeMonthsPrice), + sixMonthsPrice: this.convertYuanToCents(formValue.sixMonthsPrice), + oneYearPrice: this.convertYuanToCents(formValue.oneYearPrice), + discount: formValue.discount + }; + + this.priceService.updateDefaultPrice(priceData).subscribe({ + next: () => { + this.message.success('更新默认价格成功'); + this.loadDefaultPrice(); + }, + error: (error) => { + this.message.error('更新默认价格失败'); + this.loading = false; + } + }); + } else { + Object.values(this.priceForm.controls).forEach(control => { + if (control.invalid) { + control.markAsTouched(); + control.updateValueAndValidity({ onlySelf: true }); + } + }); + } + } +} diff --git a/ui/src/app/page/price/price.component.html b/ui/src/app/page/price/price.component.html index 9729fae..88d27a1 100644 --- a/ui/src/app/page/price/price.component.html +++ b/ui/src/app/page/price/price.component.html @@ -1,11 +1,28 @@ - + - First-level Menu + 后台管理 - Second-level Menu + 价格管理 - Third-level Menu + 价格管理 - \ No newline at end of file +
+ + + + + + + + + + + + + + + + + diff --git a/ui/src/app/page/price/price.component.spec.ts b/ui/src/app/page/price/price.component.spec.ts deleted file mode 100644 index 605035a..0000000 --- a/ui/src/app/page/price/price.component.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { fakeAsync, ComponentFixture, TestBed } from '@angular/core/testing'; -import { PriceComponent } from './price.component'; - -describe('PriceComponent', () => { - let component: PriceComponent; - let fixture: ComponentFixture; - - beforeEach(fakeAsync(() => { - TestBed.configureTestingModule({ - declarations: [ PriceComponent ] - }) - .compileComponents(); - - fixture = TestBed.createComponent(PriceComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - })); - - it('should compile', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/ui/src/app/page/price/price.component.ts b/ui/src/app/page/price/price.component.ts index 9a6d8dd..b30434a 100644 --- a/ui/src/app/page/price/price.component.ts +++ b/ui/src/app/page/price/price.component.ts @@ -2,11 +2,27 @@ import { Component } from '@angular/core'; import { NzBreadCrumbModule } from 'ng-zorro-antd/breadcrumb'; import { NzPageHeaderModule } from 'ng-zorro-antd/page-header'; +import {NzTabsModule} from "ng-zorro-antd/tabs"; +import {ColumnComponent} from "./column/column.component"; +import {ArticleComponent} from "./article/article.component"; +import { NzCardModule} from "ng-zorro-antd/card"; +import {PriceDefaultComponent} from "./price-default/price-default.component"; @Component({ selector: 'app-price', standalone: true, - imports: [NzBreadCrumbModule, NzPageHeaderModule], + imports: [ + NzBreadCrumbModule, + NzPageHeaderModule, + NzTabsModule, + ColumnComponent, + ArticleComponent, + NzCardModule, + PriceDefaultComponent, + ], templateUrl: './price.component.html' }) -export class PriceComponent {} +export class PriceComponent { + constructor() { + } +} diff --git a/ui/src/app/page/price/price.service.ts b/ui/src/app/page/price/price.service.ts new file mode 100644 index 0000000..ac51c8c --- /dev/null +++ b/ui/src/app/page/price/price.service.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import {HttpClient} from "@angular/common/http"; +import {Observable} from "rxjs"; +import {Price, PriceDefault} from "../../core/models/price"; +import {extractData, Page} from "../../core/models/page"; +import {map} from "rxjs/operators"; +import {HttpUtils} from "../../core/until/http.utils"; +import { ArticlePrice, ColumnPrice } from '../../core/models/price'; +import {Article} from "../../core/models/article"; +@Injectable({ + providedIn: 'root' +}) +export class PriceService { + + constructor(private http:HttpClient) { } + + // auth.POST("/create", h.create) + // auth.GET("/get-default", h.getPriceDefault) + // auth.POST("/update-default", h.updatePriceDefault) + // auth.POST("/update", h.update) + // auth.GET("/page-article", h.getPriceArticle) + // auth.GET("/page-column", h.getPriceColumn) + // pageOfArticle(searchParams: { [key: string]: string }):Observable> { + // return this.http.get>('/api/price/page-article"', + // {params:HttpUtils.getSearchParams(searchParams)}).pipe( + // map(response=> extractData(response)) + // ) + // } + + pageOfArticle(searchParams:{[key:string]:string}):Observable> { + return this.http.get>('/api/price/page-article',{ + params:HttpUtils.getSearchParams(searchParams) + }).pipe(map(response => extractData(response))) + } + + pageOfColumn(searchParams:{[key:string]:string}):Observable> { + console.log("come in") + return this.http.get>('/api/price/page-column',{ + params:HttpUtils.getSearchParams(searchParams) + }).pipe( + map(response => extractData(response )) + ) + } + getDefaultPrice():Observable { + return this.http.get('/api/price/get-default') + } + updateDefaultPrice(pd:PriceDefault):Observable { + return this.http.post('/api/price/update-default',pd) + } + update(price: Price): Observable { + return this.http.post('/api/price/update', price); + } +} diff --git a/ui/src/app/page/user/user.service.ts b/ui/src/app/page/user/user.service.ts index 8c9cef8..ee8f281 100644 --- a/ui/src/app/page/user/user.service.ts +++ b/ui/src/app/page/user/user.service.ts @@ -18,7 +18,8 @@ export class UserService { page(searchParams: { [key: string]: string }):Observable>{ - return this.http.get>('/api/user/page',{params:HttpUtils.getSearchParams(searchParams)}).pipe( + return this.http.get>('/api/user/page', + {params:HttpUtils.getSearchParams(searchParams)}).pipe( map(response=> extractData(response)) ) }