更新个人资料页和文章类型需求分析,增加增值版与普通版的计价逻辑及相关字段展示。优化小程序页面,确保用户在选择导师顾问时的跳转逻辑一致性,提升用户体验。调整文档以反映最新开发进展,增强功能可用性与团队协作效率。

This commit is contained in:
Alex-larget
2026-03-03 16:11:12 +08:00
parent 3097d796e0
commit 7064f82126
24 changed files with 1083 additions and 642 deletions

View File

@@ -64,6 +64,7 @@ var defaultCORSOrigins = []string{
"http://127.0.0.1:5174",
"https://soul.quwanzhi.com",
"http://soul.quwanzhi.com",
"https://souladmin.quwanzhi.com",
"http://souladmin.quwanzhi.com",
}
@@ -95,12 +96,49 @@ func parseCORSOrigins() []string {
return origins
}
// Load 加载配置,端口等从 .env 读取。优先从可执行文件同目录加载 .env再试当前目录
// Load 加载配置,端口等从 .env 读取。
// 环境区分APP_ENV=development 加载 .env.developmentAPP_ENV=production 加载 .env.production
// air 运行时通过 env_files 或 full_bin 设置 APP_ENV开发用 .env.development部署用 .env.production。
func Load() (*Config, error) {
workDir, _ := os.Getwd()
execDir := ""
if execPath, err := os.Executable(); err == nil {
_ = godotenv.Load(filepath.Join(filepath.Dir(execPath), ".env"))
execDir = filepath.Dir(execPath)
}
loadEnv := func(name string) {
for _, dir := range []string{execDir, workDir, "."} {
if dir == "" {
continue
}
p := filepath.Join(dir, name)
if _, err := os.Stat(p); err == nil {
_ = godotenv.Load(p)
break
}
}
}
overloadEnv := func(name string) {
for _, dir := range []string{execDir, workDir, "."} {
if dir == "" {
continue
}
p := filepath.Join(dir, name)
if _, err := os.Stat(p); err == nil {
_ = godotenv.Overload(p)
break
}
}
}
// 1. 加载 .env 作为基础
loadEnv(".env")
// 2. 按 APP_ENV 覆盖(优先读已设置的 APP_ENV如 air 的 env_files 已注入)
appEnv := strings.ToLower(strings.TrimSpace(os.Getenv("APP_ENV")))
if appEnv == "development" {
overloadEnv(".env.development")
} else if appEnv == "production" {
overloadEnv(".env.production")
}
_ = godotenv.Load(".env")
port := os.Getenv("PORT")
if port == "" {

View File

@@ -17,11 +17,13 @@ func AdminChaptersList(c *gin.Context) {
return
}
type section struct {
ID string `json:"id"`
Title string `json:"title"`
Price float64 `json:"price"`
IsFree bool `json:"isFree"`
Status string `json:"status"`
ID string `json:"id"`
Title string `json:"title"`
Price float64 `json:"price"`
IsFree bool `json:"isFree"`
Status string `json:"status"`
EditionStandard *bool `json:"editionStandard,omitempty"`
EditionPremium *bool `json:"editionPremium,omitempty"`
}
type chapter struct {
ID string `json:"id"`
@@ -60,7 +62,10 @@ func AdminChaptersList(c *gin.Context) {
if row.Status != nil {
st = *row.Status
}
ch.Sections = append(ch.Sections, section{ID: row.ID, Title: row.SectionTitle, Price: price, IsFree: isFree, Status: st})
ch.Sections = append(ch.Sections, section{
ID: row.ID, Title: row.SectionTitle, Price: price, IsFree: isFree, Status: st,
EditionStandard: row.EditionStandard, EditionPremium: row.EditionPremium,
})
}
structure := make([]part, 0, len(partMap))
for _, p := range partMap {
@@ -77,11 +82,13 @@ func AdminChaptersList(c *gin.Context) {
// AdminChaptersAction POST/PUT/DELETE /api/admin/chapters
func AdminChaptersAction(c *gin.Context) {
var body struct {
Action string `json:"action"`
ID string `json:"id"`
Price *float64 `json:"price"`
IsFree *bool `json:"isFree"`
Status *string `json:"status"`
Action string `json:"action"`
ID string `json:"id"`
Price *float64 `json:"price"`
IsFree *bool `json:"isFree"`
Status *string `json:"status"`
EditionStandard *bool `json:"editionStandard"`
EditionPremium *bool `json:"editionPremium"`
}
if err := c.ShouldBindJSON(&body); err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "请求体无效"})
@@ -97,5 +104,17 @@ func AdminChaptersAction(c *gin.Context) {
if body.Action == "updateStatus" && body.ID != "" && body.Status != nil {
db.Model(&model.Chapter{}).Where("id = ?", body.ID).Update("status", *body.Status)
}
if body.Action == "updateEdition" && body.ID != "" {
updates := make(map[string]interface{})
if body.EditionStandard != nil {
updates["edition_standard"] = *body.EditionStandard
}
if body.EditionPremium != nil {
updates["edition_premium"] = *body.EditionPremium
}
if len(updates) > 0 {
db.Model(&model.Chapter{}).Where("id = ?", body.ID).Updates(updates)
}
}
c.JSON(http.StatusOK, gin.H{"success": true})
}

View File

@@ -151,11 +151,18 @@ func BookChapters(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"success": false, "error": "缺少 id"})
return
}
if err := db.Model(&model.Chapter{}).Where("id = ?", body.ID).Updates(map[string]interface{}{
updates := map[string]interface{}{
"part_title": body.PartTitle, "chapter_title": body.ChapterTitle, "section_title": body.SectionTitle,
"content": body.Content, "word_count": body.WordCount, "is_free": body.IsFree, "price": body.Price,
"sort_order": body.SortOrder, "status": body.Status,
}).Error; err != nil {
}
if body.EditionStandard != nil {
updates["edition_standard"] = body.EditionStandard
}
if body.EditionPremium != nil {
updates["edition_premium"] = body.EditionPremium
}
if err := db.Model(&model.Chapter{}).Where("id = ?", body.ID).Updates(updates).Error; err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})
return
}

View File

@@ -179,12 +179,14 @@ func DBBookAction(c *gin.Context) {
}
case http.MethodPut:
var body struct {
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Price *float64 `json:"price"`
IsFree *bool `json:"isFree"`
IsNew *bool `json:"isNew"` // stitch_soul标记最新新增
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Price *float64 `json:"price"`
IsFree *bool `json:"isFree"`
IsNew *bool `json:"isNew"` // stitch_soul标记最新新增
EditionStandard *bool `json:"editionStandard"` // 是否属于普通版
EditionPremium *bool `json:"editionPremium"` // 是否属于增值版
}
if err := c.ShouldBindJSON(&body); err != nil || body.ID == "" {
c.JSON(http.StatusOK, gin.H{"success": false, "error": "缺少 id 或请求体无效"})
@@ -209,6 +211,12 @@ func DBBookAction(c *gin.Context) {
if body.IsNew != nil {
updates["is_new"] = *body.IsNew
}
if body.EditionStandard != nil {
updates["edition_standard"] = *body.EditionStandard
}
if body.EditionPremium != nil {
updates["edition_premium"] = *body.EditionPremium
}
err := db.Model(&model.Chapter{}).Where("id = ?", body.ID).Updates(updates).Error
if err != nil {
c.JSON(http.StatusOK, gin.H{"success": false, "error": err.Error()})

View File

@@ -757,17 +757,31 @@ func MiniprogramUsers(c *gin.Context) {
var cnt int64
db.Model(&model.Order{}).Where("user_id = ? AND status = ? AND (product_type = ? OR product_type = ?)",
id, "paid", "fullbook", "vip").Count(&cnt)
// 用户信息nickname/avatar与会员资料vip*)区分,字段与 model 一致camelCase
// 用户信息与会员资料vip*、P3 资料扩展,供会员详情页完整展示
item := gin.H{
"id": user.ID,
"nickname": getStringValue(user.Nickname),
"avatar": getStringValue(user.Avatar),
"vipName": getStringValue(user.VipName),
"vipAvatar": getStringValue(user.VipAvatar),
"vipContact": getStringValue(user.VipContact),
"vipProject": getStringValue(user.VipProject),
"vipBio": getStringValue(user.VipBio),
"is_vip": cnt > 0,
"id": user.ID,
"nickname": getStringValue(user.Nickname),
"avatar": getStringValue(user.Avatar),
"phone": getStringValue(user.Phone),
"wechatId": getStringValue(user.WechatID),
"vipName": getStringValue(user.VipName),
"vipAvatar": getStringValue(user.VipAvatar),
"vipContact": getStringValue(user.VipContact),
"vipProject": getStringValue(user.VipProject),
"vipBio": getStringValue(user.VipBio),
"mbti": getStringValue(user.Mbti),
"region": getStringValue(user.Region),
"industry": getStringValue(user.Industry),
"position": getStringValue(user.Position),
"businessScale": getStringValue(user.BusinessScale),
"skills": getStringValue(user.Skills),
"storyBestMonth": getStringValue(user.StoryBestMonth),
"storyAchievement": getStringValue(user.StoryAchievement),
"storyTurning": getStringValue(user.StoryTurning),
"helpOffer": getStringValue(user.HelpOffer),
"helpNeed": getStringValue(user.HelpNeed),
"projectIntro": getStringValue(user.ProjectIntro),
"is_vip": cnt > 0,
}
c.JSON(http.StatusOK, gin.H{"success": true, "data": item})
return

View File

@@ -18,8 +18,11 @@ type Chapter struct {
SortOrder *int `gorm:"column:sort_order" json:"sortOrder,omitempty"`
Status *string `gorm:"column:status;size:20" json:"status,omitempty"`
IsNew *bool `gorm:"column:is_new" json:"isNew,omitempty"` // stitch_soul目录/首页「最新新增」标记
CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`
// 普通版/增值版:两者分开互斥,添加文章时勾选归属
EditionStandard *bool `gorm:"column:edition_standard" json:"editionStandard,omitempty"` // 是否属于普通版
EditionPremium *bool `gorm:"column:edition_premium" json:"editionPremium,omitempty"` // 是否属于增值版
CreatedAt time.Time `gorm:"column:created_at" json:"createdAt"`
UpdatedAt time.Time `gorm:"column:updated_at" json:"updatedAt"`
}
func (Chapter) TableName() string { return "chapters" }