ColumnContent.tsx 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459
  1. import React, { useState } from 'react'
  2. import useRequest from '@ahooksjs/use-request/es'
  3. import { useHistory } from 'react-router-dom'
  4. import dayjs from 'dayjs'
  5. import { Modal, message, Watermark } from 'antd'
  6. import { StarFilled, StarOutlined, EyeOutlined, LikeOutlined, LikeFilled } from '@ant-design/icons'
  7. import { ReactComponent as Close } from 'assets/close.svg'
  8. import { useLogin2p } from 'Login2p/Login2pContext'
  9. import { ColumnService, IColumnDetail, IDocsList, IEditColumnDetail } from 'Column/Column.service'
  10. import Picture from 'Activity/components/Picture'
  11. import { FilesSvg } from 'Column/ColumnEditor'
  12. import { ColumnStatus } from './ColumnCenter'
  13. import AskAdd from 'Material/components/AskAdd'
  14. import ColumnContentMessage from './ColumnContentMessage'
  15. import { ITryType, INewPermissionType } from 'Material/components/NoPermission'
  16. import PayNoPermission from 'Material/components/PayNoPermission'
  17. import styles from '../css/ColumnDetail.module.scss'
  18. import 'froala-editor/css/froala_style.min.css'
  19. import 'froala-editor/css/froala_editor.pkgd.min.css'
  20. import 'froala-editor/css/plugins/image.min.css'
  21. import 'froala-editor/css/third_party/image_tui.min.css'
  22. import 'froala-editor/css/plugins/file.min.css'
  23. import 'froala-editor/css/plugins/colors.min.css'
  24. import 'froala-editor/css/plugins/emoticons.min.css'
  25. export enum ColumnType {
  26. Note = 1,
  27. Viewpoint = 2
  28. }
  29. interface IColumnContentProps {
  30. detail: IColumnDetail | IEditColumnDetail // 直接传入渲染的数据
  31. type?: 'detail' | 'list' | 'preview' | 'check' // detail-详情页,list-列表页,preview-预览页,check-审查页
  32. onCollect?: (ID: number) => void // 收藏回调(预览时没有收藏功能)
  33. onLike?: (ID: number) => void // 收藏回调(预览时没有收藏功能)
  34. onClose?: () => void
  35. handleToRefresh?: () => void
  36. }
  37. /**笔记/观点 */
  38. const ColumnContent: React.FC<IColumnContentProps> = props => {
  39. const { detail, type = 'detail', onClose, onCollect, handleToRefresh, onLike } = props
  40. const login2p = useLogin2p()
  41. const history = useHistory()
  42. const [bigImg, setBigImg] = useState<string>() // 展开的图片
  43. const [visibleAsk, setVisibleAsk] = useState(false)
  44. // 收藏/取消收藏(研选专栏)
  45. const { run: applyCollect } = useRequest(ColumnService.postColumnCollect, {
  46. manual: true,
  47. onSuccess: res => {
  48. message.info(res.data.Msg || res.data.ErrMsg)
  49. if (res.data.Success) {
  50. onCollect && onCollect(detail.Id)
  51. }
  52. }
  53. })
  54. // 点赞/取消点赞(研选专栏)
  55. const { run: postColumnLike } = useRequest(ColumnService.postColumnLike, {
  56. manual: true,
  57. onSuccess: res => {
  58. message.info(res.data.Msg || res.data.ErrMsg)
  59. if (res.data.Success) {
  60. onLike && onLike(detail.Id)
  61. }
  62. }
  63. })
  64. // 审核文章
  65. const { run: applyCheckColumnNote } = useRequest(ColumnService.postCheckColumnNote, {
  66. manual: true,
  67. onSuccess: res => {
  68. res.data.Success ? message.success(res.data.Msg) : message.error(res.data.Msg || res.data.ErrMsg)
  69. res.data.Success && history.push(`/column`)
  70. }
  71. })
  72. // 专栏添加关注
  73. const { run: postColumnFollow } = useRequest(ColumnService.postColumnAuthorFollow, {
  74. manual: true,
  75. onSuccess: res => {
  76. res.data.Success
  77. ? (detail as IColumnDetail).IsFollowAuthor
  78. ? message.success(res.data.Msg)
  79. : Modal.info({
  80. title: '已关注专栏',
  81. content: '请关注【买方研选】公众号,及时获取专栏下内容更新提醒',
  82. okText: '知道了',
  83. centered: true
  84. })
  85. : message.error(res.data.Msg || res.data.ErrMsg)
  86. res.data.Success && onCollect && onCollect(detail.Id)
  87. }
  88. })
  89. // 关注专栏
  90. const handleOnFollowColumn = () => {
  91. // 检查是否登录了,未登录进入登录页面
  92. if (!login2p.jwt) {
  93. history.push(`/login2p?next=${encodeURIComponent(window.location.pathname + window.location.search)}`)
  94. return
  95. }
  96. postColumnFollow(detail.SpecialColumnId, (detail as IColumnDetail)?.IsFollowAuthor ? 2 : 1)
  97. }
  98. const handleOnClose = () => {
  99. onClose && onClose()
  100. }
  101. // 放大图片
  102. const handleOnClickPicture = (item: string) => {
  103. setBigImg(item)
  104. }
  105. // 关闭图片查看
  106. const handleToCloseBigImg = () => {
  107. setBigImg(undefined)
  108. }
  109. // 收藏与取消收藏
  110. const handleOnCollect = (collect: number) => {
  111. if (!login2p.jwt) {
  112. message.error('请先登录')
  113. return
  114. }
  115. applyCollect(detail.Id, !!collect ? 2 : 1)
  116. }
  117. // 点赞与取消点赞
  118. const handleOnLike = (like: boolean) => {
  119. if (!login2p.jwt) {
  120. message.error('请先登录')
  121. return
  122. }
  123. postColumnLike(detail.Id, !!like ? 0 : 1)
  124. }
  125. // 查看全文
  126. const handleToDetail = () => {
  127. window.open(`/column/detail/${detail.Id}`)
  128. }
  129. // 跳转到专栏详情页
  130. const handleToColumnIndex = () => {
  131. if (type === 'preview') return
  132. history.push(
  133. `/column/view/${(detail as IColumnDetail)?.SpecialColumnId}${
  134. login2p.inviteCode ? '?invite_code=' + login2p.inviteCode : ''
  135. }`
  136. )
  137. }
  138. // 预览文件
  139. const handlePreviewFile = (file: IDocsList) => {
  140. if (file.DocSuffix === 'pdf') {
  141. window.open(file.DocUrl)
  142. return
  143. }
  144. window.open('https://view.officeapps.live.com/op/view.aspx?src=' + encodeURIComponent(file.DocUrl))
  145. }
  146. const computeFileSvgShow = (name: string, type: 'suffix' | 'svg') => {
  147. if (!name) return
  148. const suffix = name?.split('.')?.pop()
  149. if (type === 'suffix') return suffix
  150. return FilesSvg?.find(item => item.suffix === suffix)?.svg
  151. }
  152. // 如果是列表显示,隐藏img标签
  153. const handleHideImg = (html: string) => {
  154. if (
  155. type === 'list' ||
  156. ((detail as IColumnDetail)?.IsShowWxPay && (detail as IColumnDetail)?.HasPermission !== INewPermissionType.OK)
  157. ) {
  158. return html.replace(/<img[^>]*>/gi, '')
  159. }
  160. return html
  161. }
  162. const handleToClick = (type: string) => {
  163. if (!(detail as IColumnDetail).IsApprovalAdmin) return message.error('您没有权限操作')
  164. if ((detail as IColumnDetail).Status !== ColumnStatus.Auditing) return message.error('该文章未提交审批或已审批')
  165. const typeFn: {
  166. [key: string]: () => void
  167. } = {
  168. modify: () => {
  169. // 修改
  170. history.push(`/column/modify/${detail.Id}`)
  171. },
  172. reject: () => {
  173. // 打开一个弹框,输入驳回理由
  174. setVisibleAsk(true)
  175. },
  176. pass: () => {
  177. // 通过 打开二次确认框
  178. Modal.confirm({
  179. title: '提示',
  180. content: '确定通过此内容在小程序展示吗?',
  181. okText: '确定',
  182. cancelText: '取消',
  183. centered: true,
  184. closable: true,
  185. onOk: () => {
  186. applyCheckColumnNote(detail.Id, 1, '')
  187. }
  188. })
  189. }
  190. }
  191. typeFn[type]()
  192. }
  193. // 关闭驳回弹框
  194. const handleOkAsk = () => {
  195. setVisibleAsk(false)
  196. }
  197. // 提交驳回内容
  198. const handleApplyContent = (ID: number, content: string) => {
  199. applyCheckColumnNote(ID, 2, content)
  200. handleOkAsk()
  201. }
  202. // 查看更多
  203. const handleToShowMore = () => {
  204. if (!login2p.jwt) {
  205. history.push(`/login2p?next=${encodeURIComponent(window.location.pathname + window.location.search)}`)
  206. return
  207. }
  208. }
  209. return (
  210. <div className={`${styles['columndetail-show-wrapper']}`}>
  211. {type === 'preview' && (
  212. <div className="columndetail-close">
  213. <Close className="detail-close" onClick={handleOnClose} />
  214. </div>
  215. )}
  216. <div className="author-line g-flex g-flex-v-center g-flex-between g-flex-wap">
  217. <div className="g-flex g-flex-v-center g-flex-c1">
  218. <div className="artcle-tag artcle-tag-two m-r-sm m-b-xs">
  219. {detail.Type === ColumnType.Note ? '笔记' : '观点'}
  220. </div>
  221. <div className="g-flex g-flex-v-center">
  222. <img
  223. src={detail.HeadImg}
  224. alt="图片"
  225. className="author-avatar com-cursor-p g-va-middle m-b-xs"
  226. onClick={handleToColumnIndex}
  227. />
  228. <span
  229. className="com-cursor-p m-r-sm g-va-middle m-b-xs g-ellipsis1 nickname-text"
  230. onClick={handleToColumnIndex}
  231. >
  232. {detail.NickName}
  233. </span>
  234. {type === 'detail' && (
  235. <div className="columndetail-follow com-cursor-p m-b-xs" onClick={handleOnFollowColumn}>
  236. {(detail as IColumnDetail).IsFollowAuthor ? '取消关注' : '关注专栏'}
  237. </div>
  238. )}
  239. </div>
  240. </div>
  241. <div className="columndetail-time">{dayjs(detail?.PublishTime || '').format('YYYY-MM-DD HH:mm:ss')}</div>
  242. </div>
  243. <div className="com-fz18 com-fw-bold m-b-sm">{detail.Title}</div>
  244. <Watermark
  245. content={login2p.userInfo ? login2p.userInfo?.Mobile : '水印'}
  246. font={{ fontSize: 22, color: 'rgba(0,0,0,.08)' }}
  247. >
  248. <div
  249. className={`fr-element fr-view ${
  250. type === 'list' ||
  251. (type === 'detail' && !login2p.jwt) ||
  252. ((detail as IColumnDetail)?.IsShowWxPay &&
  253. (detail as IColumnDetail)?.HasPermission !== INewPermissionType.OK)
  254. ? 'note-content'
  255. : 'columndetail-content'
  256. } `}
  257. dangerouslySetInnerHTML={{
  258. __html: handleHideImg(detail.Content)
  259. }}
  260. />
  261. </Watermark>
  262. {(detail as IColumnDetail)?.IsShowWxPay &&
  263. (detail as IColumnDetail)?.HasPermission !== INewPermissionType.OK && ( // 暂时这么写
  264. <PayNoPermission
  265. dataInfo={detail as IColumnDetail}
  266. tryType={ITryType.YanxuanSpecial}
  267. border={false}
  268. onRefresh={handleToRefresh}
  269. />
  270. )}
  271. <>
  272. {type === 'list' && (
  273. <div className="g-flex g-flex-v-center g-flex-between m-b-md note-showmore-line">
  274. <div className="g-flex g-flex-v-center">
  275. <div className="note-pv">
  276. <EyeOutlined size={14} className="pv-icon" />
  277. <span>{(detail as IColumnDetail).Pv || 0}</span>
  278. </div>
  279. {(detail as IColumnDetail)?.IsCollect ? (
  280. <StarFilled
  281. className="collect-icon"
  282. onClick={handleOnCollect.bind(this, (detail as IColumnDetail)?.IsCollect)}
  283. />
  284. ) : (
  285. <StarOutlined
  286. className="collect-icon"
  287. onClick={handleOnCollect.bind(this, (detail as IColumnDetail)?.IsCollect)}
  288. />
  289. )}
  290. <span style={{ marginRight: 10 }} className="collect-count">
  291. {(detail as IColumnDetail)?.CollectNum}
  292. </span>
  293. {(detail as IColumnDetail)?.IsLikeCount ? (
  294. <LikeFilled
  295. className="collect-icon"
  296. onClick={handleOnLike.bind(this, (detail as IColumnDetail)?.IsLikeCount)}
  297. />
  298. ) : (
  299. <LikeOutlined
  300. className="collect-icon"
  301. onClick={handleOnLike.bind(this, (detail as IColumnDetail)?.IsLikeCount)}
  302. />
  303. )}
  304. <span className="collect-count">{(detail as IColumnDetail)?.LikeCount}</span>
  305. </div>
  306. <div className="note-showmore-btn" onClick={handleToDetail}>
  307. 查看全文
  308. </div>
  309. </div>
  310. )}
  311. </>
  312. {(detail as IColumnDetail)?.HasPermission === INewPermissionType.OK && (
  313. <>
  314. {!login2p.jwt && type === 'detail' ? (
  315. <div className="please-login-btn" onClick={handleToShowMore}>
  316. 请登录后查看更多内容
  317. </div>
  318. ) : (
  319. <>
  320. <div className="operate-file-wrapper">
  321. {detail?.Docs?.map((item: IDocsList, index: number) => (
  322. <div
  323. className="operate-file-item com-cursor-p"
  324. key={index}
  325. onClick={handlePreviewFile.bind(this, item)}
  326. >
  327. <div className="g-flex g-flex-v-center">
  328. {item.DocIcon ? (
  329. <img src={item.DocIcon} className="operate-file-type" alt="图标" />
  330. ) : (
  331. computeFileSvgShow(item.DocName, 'svg')
  332. )}
  333. <span className="operate-file-text">{item.DocName}</span>
  334. </div>
  335. </div>
  336. ))}
  337. </div>
  338. <div className="operate-file-wrapper">
  339. {detail?.ImgUrlList?.map((item, index) => (
  340. <React.Fragment key={index}>
  341. {item && (
  342. <div className="operate-image-item" key={item} onClick={handleOnClickPicture.bind(this, item)}>
  343. <img src={item} alt={'图片' + item} className="operate-image" />
  344. </div>
  345. )}
  346. </React.Fragment>
  347. ))}
  348. </div>
  349. <div className="columndetail-category-label g-flex g-flex-v-center g-flex-wap">
  350. {type !== 'list' ? (
  351. <>
  352. {detail?.IndustryTags?.map(item => (
  353. <div className="select-item" key={item}>
  354. <span>{item}</span>
  355. </div>
  356. ))}
  357. {detail?.CompanyTags?.map(item => (
  358. <div className="select-item" key={item}>
  359. <span>{item}</span>
  360. </div>
  361. ))}
  362. </>
  363. ) : (
  364. <>
  365. {(detail as IColumnDetail)?.TagList?.map(item => (
  366. <div className="select-item" key={item}>
  367. <span>{item}</span>
  368. </div>
  369. ))}
  370. </>
  371. )}
  372. </div>
  373. {type === 'detail' && (detail as IColumnDetail).Status === ColumnStatus.Published && (
  374. <>
  375. <div className="detail-pv-collect-like note-showmore-line">
  376. <div className="note-pv">
  377. <EyeOutlined size={14} className="pv-icon" />
  378. <span>{(detail as IColumnDetail).Pv || 0}</span>
  379. </div>
  380. <div>
  381. {(detail as IColumnDetail)?.IsCollect ? (
  382. <StarFilled
  383. className="collect-icon"
  384. onClick={handleOnCollect.bind(this, (detail as IColumnDetail)?.IsCollect)}
  385. />
  386. ) : (
  387. <StarOutlined
  388. className="collect-icon"
  389. onClick={handleOnCollect.bind(this, (detail as IColumnDetail)?.IsCollect)}
  390. />
  391. )}
  392. <span style={{ marginRight: 10 }} className="collect-count">
  393. {(detail as IColumnDetail)?.CollectNum}
  394. </span>
  395. {(detail as IColumnDetail)?.IsLikeCount ? (
  396. <LikeFilled
  397. className="collect-icon"
  398. onClick={handleOnLike.bind(this, (detail as IColumnDetail)?.IsLikeCount)}
  399. />
  400. ) : (
  401. <LikeOutlined
  402. className="collect-icon"
  403. onClick={handleOnLike.bind(this, (detail as IColumnDetail)?.IsLikeCount)}
  404. />
  405. )}
  406. <span className="collect-count">{(detail as IColumnDetail)?.LikeCount}</span>
  407. </div>
  408. </div>
  409. {/* <div>请写下您的留言</div>
  410. */}
  411. <ColumnContentMessage detailId={(detail as IColumnDetail).Id} />
  412. </>
  413. )}
  414. </>
  415. )}
  416. {type === 'check' && (
  417. <>
  418. <div className="check-operate-wrapper">
  419. <div className="operate-nomal-btn save-btn" onClick={handleToClick.bind(this, 'modify')}>
  420. 修改
  421. </div>
  422. <div className="operate-nomal-btn reject-btn" onClick={handleToClick.bind(this, 'reject')}>
  423. 驳回
  424. </div>
  425. <div className="operate-nomal-btn publish-btn" onClick={handleToClick.bind(this, 'pass')}>
  426. 通过
  427. </div>
  428. </div>
  429. {visibleAsk ? (
  430. <AskAdd
  431. title="驳回"
  432. placeholder="请输入驳回理由"
  433. visible={visibleAsk}
  434. ID={detail.Id}
  435. onApply={handleApplyContent}
  436. onCloseModel={handleOkAsk}
  437. />
  438. ) : null}
  439. </>
  440. )}
  441. <Picture visible={!!bigImg} imgSrc={bigImg} onClose={handleToCloseBigImg} />
  442. </>
  443. )}
  444. </div>
  445. )
  446. }
  447. export default ColumnContent