package handler import ( "encoding/json" "html" "regexp" "strings" "unicode/utf8" "soul-api/internal/model" "gorm.io/gorm" ) var ( reHTMLScript = regexp.MustCompile(`(?is)]*>.*?`) reHTMLStyle = regexp.MustCompile(`(?is)]*>.*?`) reHTMLBr = regexp.MustCompile(`(?i)<\s*br\s*/?>`) reHTMLPClose = regexp.MustCompile(`(?i)`) reHTMLTags = regexp.MustCompile(`<[^>]+>`) ) // stripHTMLToPlainPreview 将正文 HTML 转为纯文本再截取,避免预览截在 = 1 && p <= 100 { return p } } return g } // previewContent 取内容的前 percent%(按纯文本 rune 计;原 HTML 先剥标签),与 h5 落地页一致 func previewContent(content string, percent int) string { work := content if strings.Contains(content, "<") && strings.Contains(content, ">") { work = stripHTMLToPlainPreview(content) } total := utf8.RuneCountInString(work) if total == 0 { return "" } if percent < 1 { percent = 1 } if percent > 100 { percent = 100 } limit := total * percent / 100 if limit < 100 { limit = 100 } const maxPreview = 500 if limit > maxPreview { limit = maxPreview } if limit > total { limit = total } runes := []rune(work) return string(runes[:limit]) + "\n\n……" } func defaultReadPreviewUI() map[string]string { return map[string]string{ "singlePageUnlockTitle": "解锁完整内容", "singlePagePayButtonText": "支付 ¥{price} 解锁全文", "singlePageExpandedHint": "预览页不能直接付款,务必先点底栏「前往小程序」。", "payTapModalTitle": "解锁说明", "payTapModalContent": "全文 ¥{price}。预览里无法完成支付:请先点屏幕底部「前往小程序」进入完整版,登录后再付款解锁。", "fullUnlockTitle": "解锁完整内容", "fullUnlockDesc": "可先上滑阅读预览;需要全文时,点下方「支付¥{price}」查看说明", "fullLockedProgressText": "已阅读约 {percent}% ,购买后继续阅读", "fullPaywallTip": "转发给需要的人,一起学习还能赚佣金", "notLoginUnlockDesc": "已预览约 {percent}% 内容,登录并支付 ¥{price} 后阅读全文", "notLoginPaywallTip": "分享给好友一起学习,还能赚取佣金", "shareTipLine": "好友经你分享购买,你可获得约 90% 收益", "momentsModalTitle": "分享到朋友圈", "momentsModalContent": "已复制发圈文案(非分享给好友)。\n\n请点击右上角「···」→「分享到朋友圈」粘贴发布。", "momentsClipboardFooter": "\n\n—— 以上为正文预览约 {percent}% ,搜「卡若创业派对」小程序阅读全文 ——", "timelineTitleSuffix": "(预览{percent}%)", } } // mergeReadPreviewUI 合并 system_config.read_preview_ui(JSON 对象)与默认文案;占位符 {percent}/{price} 由小程序替换 func mergeReadPreviewUI(db *gorm.DB) map[string]string { out := defaultReadPreviewUI() var row model.SystemConfig if err := db.Where("config_key = ?", "read_preview_ui").First(&row).Error; err != nil || len(row.ConfigValue) == 0 { return out } var raw map[string]interface{} if err := json.Unmarshal(row.ConfigValue, &raw); err != nil { return out } for k, v := range raw { switch t := v.(type) { case string: if strings.TrimSpace(t) != "" { out[k] = strings.TrimSpace(t) } default: // 兼容数字等被误存:忽略非字符串 } } return out }