feat: enhance super article features and UI updates
- Integrated AI writing capabilities in the super article editor, allowing users to upload multiple reference files and generate drafts using prompts. - Updated the reading page to unify the display of book chapters and super articles, improving user navigation and experience. - Enhanced the "My" page with new UI elements, including gradient icons and improved layout for better visual appeal. - Added backend support for new article management endpoints, ensuring seamless integration with the front end. This update aims to streamline the article creation process and enhance the overall user interface across the application.
This commit is contained in:
@@ -58,6 +58,7 @@ func AdminSuperArticlesList(c *gin.Context) {
|
||||
list := make([]gin.H, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
a := authorMap[strings.TrimSpace(r.UserID)]
|
||||
an, aa := resolveSuperArticleAuthorDisplay(a)
|
||||
list = append(list, gin.H{
|
||||
"id": r.ID,
|
||||
"userId": r.UserID,
|
||||
@@ -66,8 +67,8 @@ func AdminSuperArticlesList(c *gin.Context) {
|
||||
"images": parseSuperArticleImagesJSON(r.Images),
|
||||
"auditStatus": strings.TrimSpace(r.AuditStatus),
|
||||
"rejectReason": strings.TrimSpace(r.RejectReason),
|
||||
"authorNickname": strings.TrimSpace(a.Nickname),
|
||||
"authorAvatar": strings.TrimSpace(a.Avatar),
|
||||
"authorNickname": an,
|
||||
"authorAvatar": aa,
|
||||
"createdAt": r.CreatedAt,
|
||||
"updatedAt": r.UpdatedAt,
|
||||
})
|
||||
|
||||
@@ -98,8 +98,9 @@ func H5SuperArticlePage(c *gin.Context) {
|
||||
}
|
||||
|
||||
authorMap := loadSuperArticleAuthorMap([]string{row.UserID})
|
||||
author := authorMap[row.UserID]
|
||||
nick := strings.TrimSpace(author.Nickname)
|
||||
author := authorMap[strings.TrimSpace(row.UserID)]
|
||||
an, aa := resolveSuperArticleAuthorDisplay(author)
|
||||
nick := an
|
||||
if nick == "" {
|
||||
nick = "超级个体"
|
||||
}
|
||||
@@ -141,7 +142,7 @@ func H5SuperArticlePage(c *gin.Context) {
|
||||
ogImage = h5AbsolutizeImage(strings.TrimSpace(articleImages[0]), cfg.BaseURL)
|
||||
}
|
||||
if ogImage == "" {
|
||||
ogImage = strings.TrimSpace(author.Avatar)
|
||||
ogImage = aa
|
||||
if ogImage != "" {
|
||||
ogImage = h5AbsolutizeImage(ogImage, cfg.BaseURL)
|
||||
}
|
||||
|
||||
@@ -22,15 +22,47 @@ import (
|
||||
|
||||
const aiArticleSystemPrompt = `你是「卡若创业派对」的内容创作助手,帮助超级个体写出真实、有深度的商业与创业内容文章。
|
||||
|
||||
写作要求:
|
||||
- 标题:精炼有力,≤30字,有吸引力,点明核心价值
|
||||
- 正文:600-1000字,有真实故事或案例,有观点洞察,语气自然真诚
|
||||
- 风格:像朋友分享经验,而非说教;有画面感,接地气
|
||||
- 不要使用"首先、其次、最后"等模板化结构词
|
||||
若用户提供的素材末尾附有「写文章规则提示词」或以「---」分隔的写作规范段落,你必须优先、严格按该段落成稿(体裁、字数、人称、版式、Markdown 交付与自检等)。
|
||||
|
||||
严格输出 JSON,不要有任何额外文字:
|
||||
输出封装(始终遵守):只输出一段合法 JSON,不要有任何 JSON 外的文字。title 为主标题纯文本(可与正文首行 Markdown 标题语义一致,但不要带「#」前缀);content 为正文全文,可为 Markdown(含 **加粗**、分段、---、HTML 注释等),字符串内需合法 JSON 转义。
|
||||
|
||||
若用户未附带上述写作规范段落,则默认:标题精炼有力≤30字;正文约600~1000字;语气自然真诚,像朋友分享经验;不要使用"首先、其次、最后"等模板化结构词。
|
||||
|
||||
严格输出 JSON:
|
||||
{"title":"文章标题","content":"文章正文"}`
|
||||
|
||||
// superArticleWritingRulesSuffix 在服务端拼接到用户素材末尾(与 开发文档/ai开发文档.txt 写作规则段落保持一致)。
|
||||
const superArticleWritingRulesSuffix = `写文章规则提示词:
|
||||
|
||||
写文章。严格按以下规则成稿。
|
||||
|
||||
【材料】用户提供:本场聊天记录/纪要/妙记要点、场次号、主题短句;写作前先按规范阅读飞书运营报表,把报表上有的时长、场观、进房等具体数字写进开篇(只写一次)。
|
||||
|
||||
【人称与禁用】叙述全程用「我」,禁止「房主」。禁止「这边」「那边」。禁止「回答说,……」——问句后直接接我的陈述。语气偏强势:干脆、笃定,少「可能/也许」。「卡若」每篇最多 1 次,可完全不出现。
|
||||
|
||||
【刺点三对齐】正文第一行标题「# 第X场|短句」、磁盘文件名、开篇刺激性金句三者同一刺点;不写空泛抒情。
|
||||
|
||||
【结构与比例】
|
||||
- 默认单场 采用「2026 每日派对干货 / 派对体」:三块约 40% / 20%~30% / 余下约 30%;块与块之间必须有过渡口语;每一块结束必须有一段「段末小结」单独成段,30~50 个汉字一句,禁止出现「干货」等标签字。
|
||||
|
||||
【开篇】先有冲突或世俗金句,再切入场景。固定数据句式(报表有则写具体数,没有的项不写):「第 X 场,X 分钟;场观 XXXXX,进房 XXX 人。」不写「约」、不写「见报表」。时长/场观/进房仅在开篇出现一次。
|
||||
|
||||
【中段】推进用时间线或事件线;多写对话(「X 号问」「有人问」);我的话直接陈述。约在全文 50% 处插入一段浓缩要点 3~6 条(20~40 字/条),前后用 --- 分隔,条目中禁止「干货」二字。
|
||||
|
||||
【分享句】约在 20% 处一句、结尾一句,各 ≤50 字,紧扣本节,禁止「干货:」字样。
|
||||
|
||||
【强调·派对体成稿必选】Markdown **加粗**:至少 2 句完整立场/结论句(分布在不同段落);至少 3 个不同的重点关键词加粗。禁止堆砌无意义加粗。
|
||||
|
||||
【结尾】升华主题;派对体可有文末小结(3~6 条短金句)+ 总结段落 + 一句收尾金句。
|
||||
|
||||
【字数与真实性】小节 3000~5000 字;忠于来源不改原意;所有数字必须有依据、写具体算法/金额/人次等,Soul 侧常用口径可参考规范中的曝光/进房/成本写法。
|
||||
|
||||
【隐晦植入】文中自然散布 2~4 处:联系管理、做切片、做副业相关一句,不单段喊话,随机10%加率不用每篇文章都加
|
||||
|
||||
【版式强制】一句一行,句间空一行;分号也拆成行;段落之间空行;大段堆砌禁止。
|
||||
|
||||
【交付】输出 Markdown;标题一行「# 第X场|…」;成稿末尾按模板追加 HTML 注释自检:「我」字数/次数、本场关键字频次、关键字加粗处数、重点句/词加粗是否达标。`
|
||||
|
||||
const (
|
||||
maxArticleMaterialFetchBody = 512 * 1024 // 单素材最大读取
|
||||
maxCombinedReferenceRunes = 60000 // 合并后截断上限(含提示与素材摘要)
|
||||
@@ -40,11 +72,11 @@ const (
|
||||
// 根据提示词 + 可选参考文本 / 素材文件 URL(本站上传链接) / 图片 URL,调用 AI 生成草稿(仅超级个体)。
|
||||
func MiniprogramSuperArticleGenerate(c *gin.Context) {
|
||||
var req struct {
|
||||
UserID string `json:"userId"`
|
||||
Description string `json:"description"` // 提示词(与原字段兼容)
|
||||
ReferenceText string `json:"referenceText"` // 小程序端可读出的纯文本(多文件拼接)
|
||||
MaterialURLs []string `json:"materialUrls"` // 上传后的文件 URL(服务端在安全域名内抓取文本)
|
||||
ImageURLs []string `json:"imageUrls"`
|
||||
UserID string `json:"userId"`
|
||||
Description string `json:"description"` // 提示词(与原字段兼容)
|
||||
ReferenceText string `json:"referenceText"` // 小程序端可读出的纯文本(多文件拼接)
|
||||
MaterialURLs []string `json:"materialUrls"` // 上传后的文件 URL(服务端在安全域名内抓取文本)
|
||||
ImageURLs []string `json:"imageUrls"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "参数错误"})
|
||||
@@ -86,8 +118,8 @@ func MiniprogramSuperArticleGenerate(c *gin.Context) {
|
||||
}
|
||||
|
||||
cfg := config.Get()
|
||||
if cfg == nil || strings.TrimSpace(cfg.OpenAIAPIKey) == "" {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "AI 功能暂未开放,请联系管理员配置"})
|
||||
if cfg == nil || strings.TrimSpace(cfg.SuperArticleAIAPIKey) == "" {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": "AI 功能暂未开放,请在服务端配置 SUPER_ARTICLE_AI_API_KEY 或 OPENAI_API_KEY"})
|
||||
return
|
||||
}
|
||||
|
||||
@@ -102,6 +134,7 @@ func MiniprogramSuperArticleGenerate(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, gin.H{"success": false, "error": errMsg})
|
||||
return
|
||||
}
|
||||
combined = appendSuperArticleWritingRules(combined)
|
||||
|
||||
title, content, err := aiGenerateArticle(cfg, combined, validImageURLs)
|
||||
if err != nil {
|
||||
@@ -156,6 +189,32 @@ func assembleSuperArticlePrompt(cfg *config.Config, prompt, refText string, mate
|
||||
return combined, ""
|
||||
}
|
||||
|
||||
// appendSuperArticleWritingRules 在用户提示词与参考素材组装结果末尾追加固定写作规则(保证规则段不被 maxCombinedReferenceRunes 截断)。
|
||||
func appendSuperArticleWritingRules(combined string) string {
|
||||
suffix := strings.TrimSpace(superArticleWritingRulesSuffix)
|
||||
if suffix == "" {
|
||||
return strings.TrimSpace(combined)
|
||||
}
|
||||
suffixRunes := len([]rune(suffix))
|
||||
sep := "\n\n---\n\n"
|
||||
sepLen := len([]rune(sep))
|
||||
maxBody := maxCombinedReferenceRunes - suffixRunes - sepLen
|
||||
if maxBody < 0 {
|
||||
maxBody = 0
|
||||
}
|
||||
base := strings.TrimSpace(combined)
|
||||
if base != "" {
|
||||
rs := []rune(base)
|
||||
if len(rs) > maxBody {
|
||||
base = string(rs[:maxBody]) + "\n\n(上文参考已截断)"
|
||||
}
|
||||
}
|
||||
if base == "" {
|
||||
return suffix
|
||||
}
|
||||
return base + sep + suffix
|
||||
}
|
||||
|
||||
func snippetNameFromURL(raw string) string {
|
||||
u, err := url.Parse(strings.TrimSpace(raw))
|
||||
if err != nil {
|
||||
@@ -281,34 +340,66 @@ func looksLikeUTF8ArticleRef(b []byte) bool {
|
||||
return float64(printable)/float64(total) > 0.90
|
||||
}
|
||||
|
||||
// superArticleModelLikelySupportsVision 粗略判断 chat/completions 模型是否适合传 image_url(智增增/OpenAI 等多网关)。
|
||||
func superArticleModelLikelySupportsVision(model string) bool {
|
||||
m := strings.ToLower(strings.TrimSpace(model))
|
||||
if m == "" {
|
||||
return false
|
||||
}
|
||||
if strings.Contains(m, "vision") || strings.Contains(m, "-vl") || strings.Contains(m, "vqa") {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(m, "gpt-4") || strings.Contains(m, "gpt-5") || strings.Contains(m, "gpt-4o") {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(m, "claude-3") || strings.Contains(m, "claude-4") {
|
||||
return true
|
||||
}
|
||||
if strings.Contains(m, "glm-4v") || strings.Contains(m, "qwen-vl") || strings.Contains(m, "gemini") {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// aiGenerateArticle 调用 OpenAI 兼容 API 生成文章标题和正文。
|
||||
// 若有图片 URL 且模型支持视觉,以多模态格式传入;否则退化为纯文本。
|
||||
// 若有参考图片 URL:使用多模态 user content(text + image_url);当前主模型不支持 vision 时自动改用 SUPER_ARTICLE_AI_VISION_MODEL(默认同为 gpt-4o-mini)。
|
||||
func aiGenerateArticle(cfg *config.Config, description string, imageURLs []string) (title, content string, err error) {
|
||||
apiKey := strings.TrimSpace(cfg.OpenAIAPIKey)
|
||||
baseURL := strings.TrimSuffix(strings.TrimSpace(cfg.OpenAIBaseURL), "/")
|
||||
apiKey := strings.TrimSpace(cfg.SuperArticleAIAPIKey)
|
||||
baseURL := strings.TrimSuffix(strings.TrimSpace(cfg.SuperArticleAIBaseURL), "/")
|
||||
if baseURL == "" {
|
||||
baseURL = "https://api.openai.com/v1"
|
||||
}
|
||||
model := strings.TrimSpace(cfg.OpenAIModel)
|
||||
if model == "" {
|
||||
model = "gpt-4o-mini"
|
||||
baseModel := strings.TrimSpace(cfg.SuperArticleAIModel)
|
||||
if baseModel == "" {
|
||||
baseModel = "gpt-4o-mini"
|
||||
}
|
||||
|
||||
// 判断模型是否支持视觉(OpenAI gpt-4 系列 / claude-3 系列)
|
||||
supportsVision := len(imageURLs) > 0 && (
|
||||
strings.Contains(model, "gpt-4") ||
|
||||
strings.Contains(model, "claude-3") ||
|
||||
strings.Contains(model, "vision"))
|
||||
requestModel := baseModel
|
||||
useVision := len(imageURLs) > 0
|
||||
if useVision {
|
||||
if superArticleModelLikelySupportsVision(baseModel) {
|
||||
requestModel = baseModel
|
||||
} else {
|
||||
vm := strings.TrimSpace(cfg.SuperArticleAIVisionModel)
|
||||
if vm == "" {
|
||||
vm = "gpt-4o-mini"
|
||||
}
|
||||
requestModel = vm
|
||||
}
|
||||
}
|
||||
|
||||
var userContent interface{}
|
||||
if supportsVision {
|
||||
if useVision {
|
||||
parts := []map[string]interface{}{
|
||||
{"type": "text", "text": buildUserPrompt(description, imageURLs, true)},
|
||||
}
|
||||
for _, u := range imageURLs {
|
||||
parts = append(parts, map[string]interface{}{
|
||||
"type": "image_url",
|
||||
"image_url": map[string]string{"url": u, "detail": "low"},
|
||||
"type": "image_url",
|
||||
"image_url": map[string]string{
|
||||
"url": u,
|
||||
"detail": "low",
|
||||
},
|
||||
})
|
||||
}
|
||||
userContent = parts
|
||||
@@ -317,16 +408,16 @@ func aiGenerateArticle(cfg *config.Config, description string, imageURLs []strin
|
||||
}
|
||||
|
||||
reqBody, _ := json.Marshal(map[string]interface{}{
|
||||
"model": model,
|
||||
"model": requestModel,
|
||||
"messages": []map[string]interface{}{
|
||||
{"role": "system", "content": aiArticleSystemPrompt},
|
||||
{"role": "user", "content": userContent},
|
||||
},
|
||||
"temperature": 0.8,
|
||||
"max_tokens": 2000,
|
||||
"max_tokens": 8192,
|
||||
})
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second)
|
||||
defer cancel()
|
||||
|
||||
httpReq, err := http.NewRequestWithContext(ctx, "POST", baseURL+"/chat/completions", bytes.NewReader(reqBody))
|
||||
@@ -341,7 +432,10 @@ func aiGenerateArticle(cfg *config.Config, description string, imageURLs []strin
|
||||
return "", "", fmt.Errorf("请求 AI 服务失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
body, errRead := io.ReadAll(resp.Body)
|
||||
if errRead != nil {
|
||||
return "", "", fmt.Errorf("读取 AI 响应失败: %v", errRead)
|
||||
}
|
||||
|
||||
var apiResp struct {
|
||||
Choices []struct {
|
||||
@@ -354,10 +448,23 @@ func aiGenerateArticle(cfg *config.Config, description string, imageURLs []strin
|
||||
} `json:"error"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &apiResp); err != nil {
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
return "", "", fmt.Errorf("AI 服务 HTTP %d", resp.StatusCode)
|
||||
}
|
||||
return "", "", fmt.Errorf("解析 AI 响应失败")
|
||||
}
|
||||
if apiResp.Error != nil {
|
||||
return "", "", fmt.Errorf("%s", apiResp.Error.Message)
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||
if apiResp.Error != nil && strings.TrimSpace(apiResp.Error.Message) != "" {
|
||||
return "", "", fmt.Errorf("%s", strings.TrimSpace(apiResp.Error.Message))
|
||||
}
|
||||
snippet := strings.TrimSpace(string(body))
|
||||
if len(snippet) > 400 {
|
||||
snippet = snippet[:400] + "…"
|
||||
}
|
||||
return "", "", fmt.Errorf("AI 服务 HTTP %d %s", resp.StatusCode, snippet)
|
||||
}
|
||||
if apiResp.Error != nil && strings.TrimSpace(apiResp.Error.Message) != "" {
|
||||
return "", "", fmt.Errorf("%s", strings.TrimSpace(apiResp.Error.Message))
|
||||
}
|
||||
if len(apiResp.Choices) == 0 || apiResp.Choices[0].Message.Content == "" {
|
||||
return "", "", fmt.Errorf("AI 无返回内容")
|
||||
@@ -410,12 +517,12 @@ func parseArticleJSON(raw string) (title, content string, err error) {
|
||||
return "", "", fmt.Errorf("AI 生成内容不完整,请重试")
|
||||
}
|
||||
|
||||
// 截断超限内容
|
||||
if tr := []rune(title); len(tr) > 40 {
|
||||
title = string(tr[:40])
|
||||
// 截断超限内容(派对体长文可到数千字,预留上限避免极端响应撑爆存储)
|
||||
if tr := []rune(title); len(tr) > 120 {
|
||||
title = string(tr[:120])
|
||||
}
|
||||
if cr := []rune(content); len(cr) > 5000 {
|
||||
content = string(cr[:5000])
|
||||
if cr := []rune(content); len(cr) > 20000 {
|
||||
content = string(cr[:20000])
|
||||
}
|
||||
|
||||
return title, content, nil
|
||||
|
||||
@@ -14,9 +14,24 @@ import (
|
||||
)
|
||||
|
||||
type superArticleAuthorRow struct {
|
||||
ID string `gorm:"column:id"`
|
||||
Nickname string `gorm:"column:nickname"`
|
||||
Avatar string `gorm:"column:avatar"`
|
||||
ID string `gorm:"column:id"`
|
||||
Nickname string `gorm:"column:nickname"`
|
||||
Avatar string `gorm:"column:avatar"`
|
||||
VipName string `gorm:"column:vip_name"`
|
||||
VipAvatar string `gorm:"column:vip_avatar"`
|
||||
}
|
||||
|
||||
// resolveSuperArticleAuthorDisplay 展示用昵称/头像:普通资料优先,超级个体常用 vip_* 兜底。
|
||||
func resolveSuperArticleAuthorDisplay(a superArticleAuthorRow) (nickname, avatar string) {
|
||||
nickname = strings.TrimSpace(a.Nickname)
|
||||
if nickname == "" {
|
||||
nickname = strings.TrimSpace(a.VipName)
|
||||
}
|
||||
avatar = strings.TrimSpace(a.Avatar)
|
||||
if avatar == "" {
|
||||
avatar = strings.TrimSpace(a.VipAvatar)
|
||||
}
|
||||
return nickname, avatar
|
||||
}
|
||||
|
||||
func loadSuperArticleAuthorMap(ids []string) map[string]superArticleAuthorRow {
|
||||
@@ -26,7 +41,7 @@ func loadSuperArticleAuthorMap(ids []string) map[string]superArticleAuthorRow {
|
||||
}
|
||||
var rows []superArticleAuthorRow
|
||||
db := database.DB()
|
||||
_ = db.Table("users").Select("id", "nickname", "avatar").Where("id IN ?", ids).Find(&rows).Error
|
||||
_ = db.Table("users").Select("id", "nickname", "avatar", "vip_name", "vip_avatar").Where("id IN ?", ids).Find(&rows).Error
|
||||
for _, r := range rows {
|
||||
key := strings.TrimSpace(r.ID)
|
||||
if key == "" {
|
||||
@@ -142,6 +157,7 @@ func MiniprogramSuperArticleFeed(c *gin.Context) {
|
||||
preview = string([]rune(preview)[:120]) + "..."
|
||||
}
|
||||
a := authorMap[strings.TrimSpace(r.UserID)]
|
||||
an, aa := resolveSuperArticleAuthorDisplay(a)
|
||||
list = append(list, gin.H{
|
||||
"id": r.ID,
|
||||
"userId": r.UserID,
|
||||
@@ -149,8 +165,8 @@ func MiniprogramSuperArticleFeed(c *gin.Context) {
|
||||
"content": r.Content,
|
||||
"preview": preview,
|
||||
"images": parseSuperArticleImagesJSON(r.Images),
|
||||
"authorNickname": strings.TrimSpace(a.Nickname),
|
||||
"authorAvatar": strings.TrimSpace(a.Avatar),
|
||||
"authorNickname": an,
|
||||
"authorAvatar": aa,
|
||||
"createdAt": r.CreatedAt,
|
||||
})
|
||||
}
|
||||
@@ -343,6 +359,7 @@ func MiniprogramSuperArticleList(c *gin.Context) {
|
||||
|
||||
authorMap := loadSuperArticleAuthorMap([]string{authorUserID})
|
||||
author := authorMap[authorUserID]
|
||||
an, aa := resolveSuperArticleAuthorDisplay(author)
|
||||
list := make([]gin.H, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
content := strings.TrimSpace(r.Content)
|
||||
@@ -357,8 +374,8 @@ func MiniprogramSuperArticleList(c *gin.Context) {
|
||||
"content": r.Content,
|
||||
"preview": preview,
|
||||
"images": parseSuperArticleImagesJSON(r.Images),
|
||||
"authorNickname": strings.TrimSpace(author.Nickname),
|
||||
"authorAvatar": strings.TrimSpace(author.Avatar),
|
||||
"authorNickname": an,
|
||||
"authorAvatar": aa,
|
||||
"createdAt": r.CreatedAt,
|
||||
})
|
||||
}
|
||||
@@ -409,6 +426,7 @@ func MiniprogramSuperArticleMine(c *gin.Context) {
|
||||
|
||||
authorMap := loadSuperArticleAuthorMap([]string{userID})
|
||||
author := authorMap[userID]
|
||||
an, aa := resolveSuperArticleAuthorDisplay(author)
|
||||
list := make([]gin.H, 0, len(rows))
|
||||
for _, r := range rows {
|
||||
content := strings.TrimSpace(r.Content)
|
||||
@@ -427,8 +445,8 @@ func MiniprogramSuperArticleMine(c *gin.Context) {
|
||||
"content": r.Content,
|
||||
"preview": preview,
|
||||
"images": parseSuperArticleImagesJSON(r.Images),
|
||||
"authorNickname": strings.TrimSpace(author.Nickname),
|
||||
"authorAvatar": strings.TrimSpace(author.Avatar),
|
||||
"authorNickname": an,
|
||||
"authorAvatar": aa,
|
||||
"createdAt": r.CreatedAt,
|
||||
"auditStatus": ast,
|
||||
"rejectReason": strings.TrimSpace(r.RejectReason),
|
||||
@@ -474,7 +492,8 @@ func MiniprogramSuperArticleDetail(c *gin.Context) {
|
||||
ast = model.SuperArticleAuditApproved
|
||||
}
|
||||
authorMap := loadSuperArticleAuthorMap([]string{row.UserID})
|
||||
author := authorMap[row.UserID]
|
||||
author := authorMap[strings.TrimSpace(row.UserID)]
|
||||
an, aa := resolveSuperArticleAuthorDisplay(author)
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"data": gin.H{
|
||||
@@ -483,8 +502,8 @@ func MiniprogramSuperArticleDetail(c *gin.Context) {
|
||||
"title": row.Title,
|
||||
"content": row.Content,
|
||||
"images": parseSuperArticleImagesJSON(row.Images),
|
||||
"authorNickname": strings.TrimSpace(author.Nickname),
|
||||
"authorAvatar": strings.TrimSpace(author.Avatar),
|
||||
"authorNickname": an,
|
||||
"authorAvatar": aa,
|
||||
"createdAt": row.CreatedAt,
|
||||
"auditStatus": ast,
|
||||
"rejectReason": strings.TrimSpace(row.RejectReason),
|
||||
|
||||
Reference in New Issue
Block a user