article-gateway.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. import { get, post } from '@/core/http/http-client'
  2. import { buildRestPath } from '@/core/config/api-paths'
  3. import parseHtml from '@/core/html/html-parser'
  4. const ROUTE_DETAILS = buildRestPath('article/details')
  5. const ROUTE_AD_DETAILS = buildRestPath('article/adDetails')
  6. const ROUTE_LIST = buildRestPath('article/getList')
  7. function unwrapListPageBody(data, raw) {
  8. const candidates = [data, raw?.data, raw?.res?.data]
  9. for (const c of candidates) {
  10. if (!c || typeof c !== 'object' || Array.isArray(c)) continue
  11. if (Array.isArray(c.data) || c.current_page != null || c.last_page != null) {
  12. return c
  13. }
  14. if (c.data && typeof c.data === 'object' && !Array.isArray(c.data)) {
  15. const inner = c.data
  16. if (Array.isArray(inner.data) || inner.current_page != null) return inner
  17. }
  18. }
  19. return { data: [], current_page: 1, last_page: 1, total: 0 }
  20. }
  21. function normalizeImages(images) {
  22. if (Array.isArray(images)) return images.filter(Boolean)
  23. if (typeof images === 'string') {
  24. try {
  25. const parsed = JSON.parse(images)
  26. return Array.isArray(parsed) ? parsed.filter(Boolean) : []
  27. } catch (_) {
  28. return []
  29. }
  30. }
  31. return []
  32. }
  33. export function normalizeArticleRow(item) {
  34. if (!item || item.id == null) return null
  35. const images = normalizeImages(item.images)
  36. return {
  37. ...item,
  38. images,
  39. image: item.image || images[0] || ''
  40. }
  41. }
  42. export function isExternalArticleUrl(value) {
  43. return /^https?:\/\//i.test(String(value || '').trim())
  44. }
  45. function unwrapArticleBody(data, raw) {
  46. if (!data || typeof data !== 'object') data = {}
  47. if (data.id != null && (data.content != null || data.url != null || data.title != null)) {
  48. return data
  49. }
  50. if (data.data && typeof data.data === 'object') {
  51. const inner = data.data
  52. if (inner.id != null || inner.content != null || inner.url != null) return inner
  53. }
  54. const env = raw?.res?.data ?? raw?.data
  55. if (env && typeof env === 'object') {
  56. if (env.id != null || env.content != null || env.url != null) return env
  57. if (env.data && typeof env.data === 'object') {
  58. const nested = env.data
  59. if (nested.id != null || nested.content != null || nested.url != null) return nested
  60. }
  61. }
  62. return data
  63. }
  64. function pickArticleContent(article) {
  65. if (!article || typeof article !== 'object') return ''
  66. return (
  67. article.content ??
  68. article.content_html ??
  69. article.detail ??
  70. article.body ??
  71. ''
  72. )
  73. }
  74. /**
  75. * @param {string} html
  76. * @param {(src: string, w?: number, h?: number) => string} [ossFn]
  77. * @param {{ hideImages?: boolean }} [opts]
  78. * @returns {{ html: string, nodes: unknown[] }}
  79. */
  80. export function formatArticleRichContent(html, ossFn, opts = {}) {
  81. const raw = pickArticleContent(typeof html === 'object' ? html : { content: html })
  82. if (!raw) return { html: '', nodes: [] }
  83. let content = String(raw).replace(/\\/g, '')
  84. if (opts.hideImages) {
  85. content = content.replace(/<img/gi, '<img style="display:none;"')
  86. } else if (ossFn) {
  87. content = content.replace(/<img[^>]*src=['"]([^'"]+)['"][^>]*>/gi, (match, capture) => {
  88. const src = ossFn(capture, 500, 0)
  89. return `<img style="display:block;max-width:100%;height:auto;" src="${src}">`
  90. })
  91. }
  92. let nodes = []
  93. try {
  94. nodes = parseHtml(content)
  95. } catch (_) {
  96. nodes = [{ type: 'text', text: content.replace(/<[^>]+>/g, '') }]
  97. }
  98. return { html: content, nodes }
  99. }
  100. /** @deprecated 使用 formatArticleRichContent */
  101. export function formatArticleHtmlNodes(html, ossFn, opts = {}) {
  102. return formatArticleRichContent(html, ossFn, opts).nodes
  103. }
  104. export async function fetchArticleDetails(id) {
  105. const { data, raw } = await get(ROUTE_DETAILS, { id })
  106. const article = unwrapArticleBody(data, raw)
  107. if (!article.content) {
  108. const merged = pickArticleContent(article)
  109. if (merged) article.content = merged
  110. }
  111. return article
  112. }
  113. export async function fetchAdvertDetails(id) {
  114. const { data, raw } = await get(ROUTE_AD_DETAILS, { id })
  115. return unwrapArticleBody(data, raw)
  116. }
  117. export async function fetchArticleListPage(page = 1, type = 'new') {
  118. const { data, raw } = await post(ROUTE_LIST, { page, type })
  119. const body = unwrapListPageBody(data, raw)
  120. const rows = (Array.isArray(body.data) ? body.data : [])
  121. .map(normalizeArticleRow)
  122. .filter(Boolean)
  123. return {
  124. rows,
  125. total: Number(body.total || rows.length),
  126. currentPage: Number(body.current_page || page || 1),
  127. lastPage: Number(body.last_page || body.current_page || 1)
  128. }
  129. }