ColumnContent.tsx 14 KB

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