article-detail.vue 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500
  1. <template>
  2. <view class="app-page">
  3. <view v-if="loading !== 'success'" class="loading-wrap">
  4. <tm-skeleton model="card"></tm-skeleton>
  5. <tm-skeleton model="card"></tm-skeleton>
  6. <tm-skeleton model="card"></tm-skeleton>
  7. </view>
  8. <block v-else>
  9. <!-- 顶部信息 -->
  10. <view class="head ma-24">
  11. <view class="title">{{ result.spec.title }}</view>
  12. <view class="detail">
  13. <view class="author">
  14. <text class="author-name">作者:{{ result.owner.displayName }}</text>
  15. <text class="author-time">
  16. 时间:{{ {d: result.spec.publishTime, f: 'yyyy年MM月dd日 星期w'} | formatTime }}
  17. </text>
  18. </view>
  19. <view class="cover" v-if="result.spec.cover">
  20. <image class="cover-img" mode="aspectFill" :src="calcUrl(result.spec.cover)"></image>
  21. </view>
  22. <view class="count" :class="{ 'no-thumbnail': !result.spec.cover }">
  23. <view class="count-item">
  24. <text class="value">{{ result.stats.visit }}</text>
  25. <text class="label">阅读</text>
  26. </view>
  27. <view class="count-item">
  28. <text class="value">{{ result.stats.upvote }}</text>
  29. <text class="label">喜欢</text>
  30. </view>
  31. <view v-if="postDetailConfig && postDetailConfig.showComment" class="count-item">
  32. <text class="value">{{ result.stats.comment }}</text>
  33. <text class="label">评论</text>
  34. </view>
  35. <view class="count-item">
  36. <text class="value">{{ result.content.raw.length }}</text>
  37. <text class="label">字数</text>
  38. </view>
  39. </view>
  40. </view>
  41. </view>
  42. <!-- 分类 -->
  43. <view class="category">
  44. <view class="category-type">
  45. <text class="text-weight-b">分类:</text>
  46. <text v-if="result.categories.length === 0" class="category-tag is-empty">未选择分类</text>
  47. <block v-else>
  48. <text class="category-tag" v-for="(item, index) in result.categories" :key="index"
  49. @click="fnToCate(item)">
  50. {{ item.spec.displayName }}
  51. </text>
  52. </block>
  53. </view>
  54. <view class="mt-18 category-type">
  55. <text class="text-weight-b">标签:</text>
  56. <text v-if="result.tags.length === 0" class="category-tag is-empty">未选择标签</text>
  57. <block v-else>
  58. <text class="category-tag" :style="{ backgroundColor: item.color }"
  59. v-for="(item, index) in result.tags" :key="index" @click="fnToTag(item)">
  60. {{ item.spec.displayName }}
  61. </text>
  62. </block>
  63. </view>
  64. <view v-if="originalURL" class="mt-18 category-type original-url">
  65. <view class="original-url_left text-weight-b">原文:</view>
  66. <view class="original-url_right text-grey">
  67. <text class="original-url_right__link" @click.stop="fnToOriginal(originalURL)">{{ originalURL }}
  68. </text>
  69. <text class="original-url_right__btn" @click.stop="fnToOriginal(originalURL)">阅读原文
  70. <text class="iconfont icon-angle-right ml-5 text-size-s"></text>
  71. </text>
  72. </view>
  73. </view>
  74. </view>
  75. <!-- 内容区域 -->
  76. <view class="content ml-24 mr-24">
  77. <!-- markdown渲染 -->
  78. <view class="markdown-wrap">
  79. <view v-if="checkPostRestrictRead(result)">
  80. <view v-if="showContentArr.length == 0">
  81. <restrict-read-skeleton
  82. :loading="true"
  83. :lines="3"
  84. :tip-text="`此处内容已隐藏,「${getRestrictReadTypeName(result)}可见」`"
  85. button-text="查看更多"
  86. button-color="#1890ff"
  87. skeleton-color="#f0f0f0"
  88. skeleton-highlight="#e0e0e0"
  89. animation-duration="2"
  90. @refresh="readMore"
  91. />
  92. </view>
  93. <view v-else v-for="showContent in showContentArr">
  94. <mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
  95. :loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
  96. :tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
  97. :content="showContent" :markdown="true" :showLineNumber="true" :showLanguageName="true"
  98. :copyByLongPress="true"/>
  99. <restrict-read-skeleton
  100. :loading="true"
  101. :lines="3"
  102. :tip-text="`此处内容已隐藏,「${getRestrictReadTypeName(result)}可见」`"
  103. button-text="查看更多"
  104. button-color="#1890ff"
  105. skeleton-color="#f0f0f0"
  106. skeleton-highlight="#e0e0e0"
  107. animation-duration="2"
  108. @refresh="readMore"
  109. />
  110. </view>
  111. </view>
  112. <view v-else>
  113. <mp-html class="evan-markdown" lazy-load :domain="markdownConfig.domain"
  114. :loading-img="markdownConfig.loadingGif" :scroll-table="true" :selectable="true"
  115. :tag-style="markdownConfig.tagStyle" :container-style="markdownConfig.containStyle"
  116. :content="result.content.raw" :markdown="true" :showLineNumber="true" :showLanguageName="true"
  117. :copyByLongPress="true"/>
  118. </view>
  119. </view>
  120. <!-- 版权声明 -->
  121. <view v-if="postDetailConfig && postDetailConfig.copyrightEnabled"
  122. class="copyright-wrap bg-white mt-24 pa-24 round-4">
  123. <view class="copyright-title text-weight-b">版权声明</view>
  124. <view
  125. class="copyright-content mt-12 grey-lighten-5 text-grey-darken-2 round-4 pt-12 pb-12 pl-24 pr-24 ">
  126. <view v-if="postDetailConfig.copyrightAuthor" class="copyright-text text-size-s ">
  127. 版权归属:{{ postDetailConfig.copyrightAuthor }}
  128. </view>
  129. <view v-if="postDetailConfig.copyrightDesc" class="copyright-text text-size-s mt-12">
  130. 版权说明:{{ postDetailConfig.copyrightDesc }}
  131. </view>
  132. <view v-if="postDetailConfig.copyrightViolation"
  133. class="copyright-text text-size-s mt-12 text-red">
  134. 侵权处理:{{ postDetailConfig.copyrightViolation }}
  135. </view>
  136. </view>
  137. </view>
  138. <!-- 评论展示区域 -->
  139. <block v-if="postDetailConfig && postDetailConfig.showComment">
  140. <view v-if="result" id="CommentList" class="comment-wrap bg-white mt-24 pa-24 round-4">
  141. <commentList ref="commentListRef" :disallowComment="!result.spec.allowComment"
  142. :postName="result.metadata.name" :post="result" @on-comment="fnOnComment"
  143. @on-comment-detail="fnOnShowCommentDetail" @on-loaded="fnOnCommentLoaded">
  144. </commentList>
  145. </view>
  146. </block>
  147. </view>
  148. <!-- 弹幕效果 -->
  149. <barrage ref="barrage" :maxTop="240" :type="globalAppSettings.barrage.type"></barrage>
  150. <!-- 返回顶部 -->
  151. <tm-flotbutton :offset="[16, 80]" icon="icon-angle-up" color="bg-gradient-light-blue-accent"
  152. @click="fnToTopPage()"></tm-flotbutton>
  153. <tm-flotbutton :actions="flotButtonActions" :click-actions-hiden="false" actions-pos="left"
  154. :show-text="true" color="bg-gradient-orange-accent" @change="fnOnFlotButtonChange"></tm-flotbutton>
  155. </block>
  156. <!-- 评论详情 -->
  157. <tm-poup v-model="commentDetail.show" height="auto" :round="6" :over-close="true" position="bottom">
  158. <view v-if="commentDetail.show && result" class="pa-24">
  159. <view class="poup-head pb-24">
  160. <view class="poup-title text-align-center text-size-g text-weight-b mb-32">评论详情</view>
  161. <comment-item :useContentBg="false" :useActions="false" :isChild="false"
  162. :comment="commentDetail.comment" :postName="result.metadata.name"></comment-item>
  163. </view>
  164. <scroll-view :scroll-y="true" class="poup-body">
  165. <view v-if="commentDetail.loading !== 'success'" class="poup-loading-wrap flex flex-center">
  166. <view v-if="commentDetail.loading === 'loading'" class="loading flex flex-center flex-col">
  167. <text class="e-loading-icon iconfont icon-loading text-blue"></text>
  168. <view class="text-size-n text-grey-lighten-1 py-12 mt-12">加载中,请稍等...</view>
  169. </view>
  170. <view v-else-if="commentDetail.loading === 'error'" class="error">
  171. <tm-empty icon="icon-wind-cry" label="加载失败">
  172. <tm-button theme="bg-gradient-light-blue-accent" size="m" @click="fnGetChildComments()">
  173. 刷新试试
  174. </tm-button>
  175. </tm-empty>
  176. </view>
  177. </view>
  178. <block v-else>
  179. <view v-if="commentDetail.list.length === 0" class="poup-empty flex flex-center">
  180. <tm-empty icon="icon-shiliangzhinengduixiang-" label="没有更多评论啦~"></tm-empty>
  181. </view>
  182. <block v-else>
  183. <comment-item v-for="(comment, index) in commentDetail.list" :useContentBg="false"
  184. :useSolid="false" :useActions="false" :key="index" :isChild="false" :comment="comment"
  185. :postName="result.metadata.name"></comment-item>
  186. </block>
  187. </block>
  188. </scroll-view>
  189. </view>
  190. </tm-poup>
  191. <!-- 海报 -->
  192. <!-- <tm-poup v-model="poster.show" width="90vw" height="auto" :round="6" :over-close="true" position="center">
  193. <view class="poster-content pt-12 bg-white">
  194. <view v-if="poster.loading" class="poster-loading flex flex-center text-grey-darken-1">
  195. <text class="e-loading-icon iconfont icon-loading"></text>
  196. <text class="ml-6">海报正在生成...</text>
  197. </view>
  198. <block v-if="poster.showCanvas">
  199. <r-canvas ref="rCanvas"></r-canvas>
  200. <view class="poster-save ma-24 mt-0 pt-20 flex flex-center">
  201. <tm-button theme="bg-gradient-light-blue-accent" size="m" @click="fnSavePoster()">
  202. 保存到相册
  203. </tm-button>
  204. <tm-button v-if="false" theme="bg-gradient-orange-accent" size="m" @click="fnShareTo()">
  205. 分享给好友
  206. </tm-button>
  207. <tm-button theme="bg-gradient-blue-grey-accent" size="m" @click="fnOnPosterClose()">
  208. 关 闭
  209. </tm-button>
  210. </view>
  211. </block>
  212. </view>
  213. </tm-poup> -->
  214. <tm-poup v-model="poster.show" width="90vw" height="auto" :round="6" :over-close="true" position="center">
  215. <view class="poster-content pt-12 bg-white">
  216. <liu-poster ref="liuPoster" :width="674" :height="940" @change="handleOnPosterChange"></liu-poster>
  217. <view v-if="poster.loading" class="poster-loading flex flex-center text-grey-darken-1">
  218. <text class="e-loading-icon iconfont icon-loading"></text>
  219. <text class="ml-6">海报正在生成...</text>
  220. </view>
  221. <block v-if="!poster.loading">
  222. <image :style="{
  223. width:'100%',
  224. height:'940rpx'
  225. }" :src="poster.url"></image>
  226. <view class="poster-save ma-24 mt-0 pt-20 flex flex-center">
  227. <tm-button theme="bg-gradient-light-blue-accent" size="m" @click="fnSavePoster()">
  228. 保存到相册
  229. </tm-button>
  230. <tm-button v-if="false" theme="bg-gradient-orange-accent" size="m" @click="fnShareTo()">
  231. 分享给好友
  232. </tm-button>
  233. <tm-button theme="bg-gradient-blue-grey-accent" size="m" @click="fnOnPosterClose()">
  234. 关 闭
  235. </tm-button>
  236. </view>
  237. </block>
  238. </view>
  239. </tm-poup>
  240. <!-- 验证码弹窗 -->
  241. <tm-dialog v-model="verificationCodeModal.show" :disabled="true" title="验证提示"
  242. :confirmText="verificationCodeModal.confirmText"
  243. :showCancel="true" model="verificationCodeModal.model" theme="split" confirmColor="blue shadow-blue-0"
  244. @cancel="verificationCodeModal.show = false"
  245. @confirm="restrictReadCheckOrViewVideo">
  246. <template #default>
  247. <view class="pa-20">
  248. <!-- 扫码验证模式 -->
  249. <view v-if="verificationCodeModal.type === 'scan'" class="flex flex-col flex-center">
  250. <text class="mb-20">请扫描下方二维码获取验证码</text>
  251. <tm-images
  252. :width="180"
  253. :height="180"
  254. :src="verificationCodeModal.imgUrl"
  255. preview
  256. round="5"
  257. ></tm-images>
  258. <tm-input bg-color="grey-lighten-5" required v-model="restrictReadInputCode" placeholder="请输入验证码"
  259. :border-bottom="false" :flat="true"></tm-input>
  260. </view>
  261. <!-- 观看视频模式 -->
  262. <view v-else class="flex flex-col flex-center">
  263. <text class="mb-20">点击观看视频之后,可访问</text>
  264. </view>
  265. </view>
  266. </template>
  267. </tm-dialog>
  268. <!-- 密码弹窗 -->
  269. <tm-dialog v-model="passwordModal.show" :disabled="true" title="验证提示" confirmText="确定" content="请输入密码"
  270. :showCancel="true" model="confirm" theme="split" confirmColor="blue shadow-blue-0"
  271. :input-val.sync="restrictReadInputCode"
  272. @cancel="passwordModal.show = false"
  273. @confirm="restrictReadCheck"></tm-dialog>
  274. <!-- 评论弹窗 -->
  275. <block v-if="calcIsShowComment">
  276. <comment-modal :show="commentModal.show" :title="commentModal.title" :postName="commentModal.postName"
  277. :isComment="commentModal.isComment" @on-close="fnOnCommentModalClose"></comment-modal>
  278. </block>
  279. </view>
  280. </template>
  281. <script>
  282. import MarkdownConfig from '@/common/markdown/markdown.config.js';
  283. import tmSkeleton from '@/tm-vuetify/components/tm-skeleton/tm-skeleton.vue';
  284. import tmPoup from '@/tm-vuetify/components/tm-poup/tm-poup.vue';
  285. import tmFlotbutton from '@/tm-vuetify/components/tm-flotbutton/tm-flotbutton.vue';
  286. import tmButton from '@/tm-vuetify/components/tm-button/tm-button.vue';
  287. import tmEmpty from '@/tm-vuetify/components/tm-empty/tm-empty.vue';
  288. import tmDialog from '@/tm-vuetify/components/tm-dialog/tm-dialog.vue';
  289. import tmMore from '@/tm-vuetify/components/tm-more/tm-more.vue';
  290. import mpHtml from '@/components/mp-html/components/mp-html/mp-html.vue';
  291. import commentList from '@/components/comment-list/comment-list.vue';
  292. import commentItem from '@/components/comment-item/comment-item.vue';
  293. import commentModal from '@/components/comment-modal/comment-modal.vue';
  294. import rCanvas from '@/components/r-canvas/r-canvas.vue';
  295. import barrage from '@/components/barrage/barrage.vue';
  296. import {getAppConfigs} from '@/config/index.js'
  297. import {upvote} from '@/utils/upvote.js'
  298. import {
  299. checkPostRestrictRead,
  300. copyToClipboard,
  301. getRestrictReadTypeName,
  302. getShowableContent
  303. } from "@/utils/restrictRead";
  304. import HaloTokenConfig from "@/config/uhalo.config";
  305. import RestrictReadSkeleton from "@/components/restrict-read-skeleton/restrict-read-skeleton.vue";
  306. import TmImages from "@/tm-vuetify/components/tm-images/tm-images.vue";
  307. import TmInput from "@/tm-vuetify/components/tm-input/tm-input.vue";
  308. let videoAd = null;
  309. export default {
  310. components: {
  311. TmInput,
  312. TmImages,
  313. RestrictReadSkeleton,
  314. tmSkeleton,
  315. tmPoup,
  316. tmFlotbutton,
  317. tmButton,
  318. tmEmpty,
  319. tmDialog,
  320. tmMore,
  321. mpHtml,
  322. commentList,
  323. commentItem,
  324. rCanvas,
  325. barrage,
  326. commentModal
  327. },
  328. data() {
  329. return {
  330. loading: 'loading',
  331. markdownConfig: MarkdownConfig,
  332. flotButtonActions: [],
  333. queryParams: {
  334. name: null
  335. },
  336. result: null,
  337. commentDetail: {
  338. loading: 'loading',
  339. show: false,
  340. comment: {},
  341. postName: undefined,
  342. list: []
  343. },
  344. poster: {
  345. show: false,
  346. showCanvas: false,
  347. loading: true,
  348. res: null,
  349. url: "",
  350. configs: []
  351. },
  352. metas: [], // 自定义元数据
  353. showContentArr: [],
  354. restrictReadInputCode: '',
  355. verificationCodeModal: {
  356. show: false,
  357. model: 'confirm',
  358. confirmText: '确定',
  359. type: '',
  360. imgUrl: '',
  361. adId: ''
  362. },
  363. passwordModal: {
  364. show: false
  365. },
  366. commentModal: {
  367. show: false,
  368. isComment: false,
  369. postName: "",
  370. title: ""
  371. },
  372. commentListScrollTop: 0
  373. };
  374. },
  375. computed: {
  376. haloConfigs() {
  377. return this.$tm.vx.getters().getConfigs;
  378. },
  379. postDetailConfig() {
  380. return this.haloConfigs.basicConfig.postDetailConfig;
  381. },
  382. calcUrl() {
  383. return url => {
  384. if (this.$utils.checkIsUrl(url)) {
  385. return url;
  386. }
  387. return getApp().globalData.baseApiUrl + url;
  388. };
  389. },
  390. // 获取博主信息
  391. bloggerInfo() {
  392. const blogger = this.$tm.vx.getters().getConfigs.authorConfig.blogger;
  393. blogger.avatar = this.$utils.checkAvatarUrl(blogger.avatar, true);
  394. return blogger;
  395. },
  396. // 原文链接:个人资质=可以打开公众号文章;非个人:任意链接地址(需在小程序后台配置)
  397. originalURL() {
  398. return this.result?.metadata?.annotations?.unihalo_originalURL || ""
  399. },
  400. calcIsShowComment() {
  401. return this.postDetailConfig.showComment
  402. },
  403. calcUpvoted() {
  404. return upvote.has("post", this.result?.metadata?.name)
  405. }
  406. },
  407. watch: {
  408. haloConfigs: {
  409. deep: true,
  410. immediate: true,
  411. handler: function (newVal) {
  412. if (!newVal) return;
  413. this.fnHandleSetFlotButtonItems(newVal);
  414. }
  415. }
  416. },
  417. onLoad(e) {
  418. this.fnSetPageTitle('文章加载中...');
  419. this.queryParams.name = e.name;
  420. this.fnGetData();
  421. },
  422. onPullDownRefresh() {
  423. this.fnGetData();
  424. },
  425. onShareAppMessage() {
  426. const cover = this.result.spec.cover ? this.calcUrl(this.result.spec.cover) : ""
  427. return {
  428. path: '/pagesA/article-detail/article-detail?name=' + this.result.metadata.name,
  429. title: this.result.spec.title,
  430. imageUrl: cover
  431. }
  432. },
  433. onShareTimeline() {
  434. const cover = this.result.spec.cover ? this.calcUrl(this.result.spec.cover) : ""
  435. return {
  436. title: this.result.spec.title,
  437. query: {
  438. name: this.result.metadata.name
  439. },
  440. imageUrl: cover
  441. }
  442. },
  443. methods: {
  444. getRestrictReadTypeName,
  445. checkPostRestrictRead,
  446. fnGetData() {
  447. this.loading = 'loading';
  448. this.$httpApi.v2
  449. .getPostByName(this.queryParams.name)
  450. .then(res => {
  451. console.log('详情', res);
  452. this.result = res;
  453. const openid = uni.getStorageSync('openid');
  454. if (openid === '' || openid === null) {
  455. this.fnGetOpenid();
  456. }
  457. const toolsPluginEnabled = getAppConfigs().pluginConfig.toolsPlugin?.enabled;
  458. const restrictRead = checkPostRestrictRead(this.result);
  459. if (restrictRead && toolsPluginEnabled) {
  460. let verifyCodeType = getAppConfigs().pluginConfig.toolsPlugin?.verifyCodeType;
  461. const postVerifyCodeType = this.result?.metadata?.annotations?.verifyCodeType;
  462. if (postVerifyCodeType) {
  463. verifyCodeType = postVerifyCodeType;
  464. }
  465. if (verifyCodeType === 'scan') {
  466. const scanCodeUrl = getAppConfigs().pluginConfig.toolsPlugin?.scanCodeUrl;
  467. this.verificationCodeModal.type = 'scan';
  468. this.verificationCodeModal.imgUrl = this.$utils.checkImageUrl(scanCodeUrl);
  469. this.verificationCodeModal.model = 'confirm';
  470. this.verificationCodeModal.confirmText = '立即验证';
  471. } else if (verifyCodeType === 'advert') {
  472. const rewardedVideoAdId = getAppConfigs().pluginConfig.toolsPlugin?.rewardedVideoAdId;
  473. this.verificationCodeModal.type = 'advert';
  474. this.verificationCodeModal.adId = rewardedVideoAdId;
  475. this.verificationCodeModal.model = 'dialog';
  476. this.verificationCodeModal.confirmText = '观看视频';
  477. // #ifdef MP-WEIXIN
  478. this.adLoad();
  479. // #endif
  480. }
  481. const showableContentArr = getShowableContent(this.result);
  482. this.showContentArr = showableContentArr;
  483. }
  484. this.fnSetPageTitle('文章详情');
  485. this.loading = 'success';
  486. this.fnHandleSetFlotButtonItems(this.haloConfigs);
  487. this.handleQueryCommentListScrollTop()
  488. })
  489. .catch(err => {
  490. console.log("错误", err)
  491. this.loading = 'error';
  492. })
  493. .finally(() => {
  494. uni.hideLoading();
  495. uni.stopPullDownRefresh();
  496. });
  497. },
  498. fnHandleSetFlotButtonItems(configs) {
  499. const actions = [{
  500. icon: 'icon-share1',
  501. color: 'bg-gradient-blue-accent',
  502. use: true,
  503. },
  504. {
  505. icon: upvote.has("post", this.result?.metadata?.name) ? 'icon-heart-fill' : 'icon-like',
  506. color: upvote.has("post", this.result?.metadata?.name) ? 'bg-gradient-red-accent' :
  507. 'bg-gradient-orange-accent',
  508. use: true,
  509. },
  510. {
  511. icon: 'icon-commentdots-fill',
  512. color: 'bg-gradient-green-accent',
  513. use: configs?.basicConfig?.postDetailConfig?.showComment
  514. }
  515. ]
  516. this.flotButtonActions = actions.filter(x => x.use === true)
  517. },
  518. // 浮动按钮点击
  519. fnOnFlotButtonChange(index) {
  520. switch (index) {
  521. case 0:
  522. this.fnShowShare();
  523. break;
  524. case 1:
  525. this.fnDoLikes();
  526. break;
  527. case 2:
  528. this.fnToComment();
  529. break;
  530. }
  531. },
  532. fnToComment() {
  533. if (!this.calcIsShowComment) {
  534. return;
  535. }
  536. if (!this.result.spec.allowComment) {
  537. return uni.$tm.toast('文章已开启禁止评论!');
  538. }
  539. this.commentModal.isComment = true;
  540. this.commentModal.postName = this.result.metadata.name;
  541. this.commentModal.title = "新增评论";
  542. this.commentModal.show = true;
  543. setTimeout(() => {
  544. uni.pageScrollTo({
  545. scrollTop: this.commentListScrollTop,
  546. duration: 100
  547. })
  548. }, 300)
  549. },
  550. fnOnComment(data) {
  551. this.commentModal.isComment = data.isComment;
  552. this.commentModal.postName = data.postName;
  553. this.commentModal.title = data.title;
  554. this.commentModal.show = true;
  555. },
  556. fnOnCommentModalClose({
  557. refresh,
  558. isSubmit
  559. }) {
  560. console.log("refresh", refresh)
  561. console.log("isSubmit", isSubmit)
  562. if (refresh && isSubmit && this.$refs.commentListRef) {
  563. this.$refs.commentListRef.fnGetData()
  564. }
  565. this.commentModal.show = false;
  566. this.commentModal.isComment = false;
  567. this.commentModal.postName = "";
  568. this.commentModal.title = "";
  569. },
  570. fnDoLikes() {
  571. if (upvote.has("post", this.result?.metadata?.name)) {
  572. uni.$tm.toast('已经点过赞啦!');
  573. return;
  574. }
  575. this.$httpApi.v2.submitUpvote({
  576. group: "content.halo.run",
  577. plural: "posts",
  578. name: this.result?.metadata?.name
  579. })
  580. .then(res => {
  581. uni.$tm.toast('点赞成功!');
  582. upvote.set("post", this.result?.metadata?.name)
  583. this.fnHandleSetFlotButtonItems(this.haloConfigs);
  584. })
  585. .catch(err => {
  586. uni.$tm.toast('点赞失败');
  587. });
  588. },
  589. async fnShowShare() {
  590. this.poster.show = true;
  591. await this.handleCreatePoster()
  592. setTimeout(() => {
  593. this.poster.showCanvas = true;
  594. // this.fnCreatePoster(res => {
  595. // this.poster.res = res;
  596. // });
  597. this.$nextTick(() => {
  598. this.$refs.liuPoster.init(this.poster.configs)
  599. })
  600. }, 500);
  601. },
  602. handleOnPosterChange(url) {
  603. this.poster.url = url;
  604. this.poster.loading = false
  605. },
  606. async handleCreatePoster() {
  607. const systemInfo = await uni.getSystemInfoSync();
  608. const _bloggerAvatar = this.$utils.checkAvatarUrl(this.bloggerInfo.avatar, true);
  609. const _articleCover = this.$utils.checkThumbnailUrl(this.result.spec.cover, true);
  610. const _qrCodeImageUrl = await this.qrCodeImageUrl();
  611. this.poster.configs = [{
  612. type: 'color',
  613. width: 674,
  614. height: 940,
  615. x: 0,
  616. y: 0,
  617. radius: 24,
  618. lineWidth: 0,
  619. lineColor: '#ffffff',
  620. colorObj: {
  621. colorList: ['#FFFFFF'],
  622. direction: 2
  623. },
  624. }, {
  625. type: 'image',
  626. width: 96,
  627. height: 96,
  628. x: 24,
  629. y: 24,
  630. radius: 48,
  631. lineWidth: 2,
  632. lineColor: '#FFFFFF',
  633. path: _bloggerAvatar
  634. }, {
  635. type: 'text',
  636. width: 400,
  637. height: 40,
  638. x: 140,
  639. y: 42,
  640. color: '#000000',
  641. fontSize: 30,
  642. lineHeight: 30,
  643. bold: true,
  644. content: this.bloggerInfo.nickname
  645. }, {
  646. type: 'text',
  647. width: 400,
  648. height: 40,
  649. x: 140,
  650. y: 90,
  651. color: '#666666',
  652. fontSize: 24,
  653. lineHeight: 24,
  654. bold: false,
  655. content: this.bloggerInfo.description,
  656. }, {
  657. type: 'image',
  658. width: 624,
  659. height: 360,
  660. x: 24,
  661. y: 152,
  662. radius: 12,
  663. lineWidth: 0,
  664. lineColor: '#FFFFFF',
  665. path: _articleCover
  666. }, {
  667. type: 'text',
  668. width: 626,
  669. height: 40,
  670. x: 24,
  671. y: 562,
  672. color: '#333333',
  673. fontSize: 28,
  674. lineHeight: 28,
  675. bold: true,
  676. content: this.result.spec.title
  677. }, {
  678. type: 'text',
  679. width: 626,
  680. height: 80,
  681. x: 24,
  682. y: 612,
  683. color: '#333333',
  684. fontSize: 24,
  685. lineHeight: 40,
  686. bold: false,
  687. content: this.result?.status?.excerpt || "文章暂无摘要信息"
  688. }, {
  689. type: 'line',
  690. width: 2,
  691. color: '#999999',
  692. startX: 24,
  693. startY: 722,
  694. endX: 646,
  695. endY: 722,
  696. lineType: 'dash',
  697. }, {
  698. type: 'image',
  699. width: 160,
  700. height: 160,
  701. x: 24,
  702. y: 752,
  703. radius: 12,
  704. lineWidth: 0,
  705. lineColor: '#FFFFFF',
  706. path: this.$utils.checkImageUrl(_qrCodeImageUrl),
  707. }, {
  708. type: 'text',
  709. width: 300,
  710. height: 44,
  711. x: 320,
  712. y: 772,
  713. color: '#333333',
  714. fontSize: 32,
  715. lineHeight: 44,
  716. bold: true,
  717. content: '长按识别小程序',
  718. }, {
  719. type: 'text',
  720. width: 442,
  721. height: 24,
  722. x: 234,
  723. y: 872,
  724. color: '#333333',
  725. fontSize: 24,
  726. lineHeight: 24,
  727. bold: false,
  728. content: '关注我,给你分享更多有趣的知识',
  729. }]
  730. },
  731. // 绘制虚线:https://blog.csdn.net/a460550542/article/details/124821248
  732. drawDashedLine(ctx, x, y, w, h, pattern, color) {
  733. ctx.lineWidth = h;
  734. ctx.strokeStyle = color;
  735. ctx.beginPath();
  736. ctx.setLineDash(pattern);
  737. ctx.moveTo(x, y);
  738. ctx.lineTo(w, y);
  739. ctx.stroke();
  740. y += 20;
  741. },
  742. fnCreatePoster(callback) {
  743. this.$nextTick(async () => {
  744. const systemInfo = await uni.getSystemInfoSync();
  745. const _bloggerAvatar = this.$utils.checkAvatarUrl(this.bloggerInfo.avatar, true);
  746. const _articleCover = this.$utils.checkThumbnailUrl(this.result.spec.cover, true);
  747. // 初始化
  748. await this.$refs.rCanvas.init({
  749. canvas_id: 'rCanvas',
  750. // canvas_width: systemInfo.windowWidth - uni.upx2px(76),
  751. canvas_width: 337,
  752. canvas_height: 460,
  753. background_color: 'rgba(255,255,255,0)'
  754. });
  755. // 画圆角背景
  756. await this.$refs.rCanvas
  757. .fillRoundRect({
  758. x: 0,
  759. y: 0,
  760. w: 337,
  761. h: 460,
  762. radius: 12,
  763. fill_color: '#fff'
  764. })
  765. .catch(err_msg => {
  766. uni.showToast({
  767. title: err_msg,
  768. icon: 'none'
  769. });
  770. });
  771. // 博主信息
  772. await this.$refs.rCanvas
  773. .drawImage({
  774. url: _bloggerAvatar,
  775. x: 12,
  776. y: 12,
  777. w: 48,
  778. h: 48,
  779. border_radius: 24
  780. })
  781. .catch(err_msg => {
  782. uni.showToast({
  783. title: err_msg,
  784. icon: 'none'
  785. });
  786. });
  787. await this.$refs.rCanvas
  788. .drawText({
  789. text: this.bloggerInfo.nickname,
  790. max_width: 0,
  791. x: 70,
  792. y: 30,
  793. font_color: '#000',
  794. font_size: 15
  795. })
  796. .catch(err_msg => {
  797. uni.showToast({
  798. title: err_msg,
  799. icon: 'none'
  800. });
  801. });
  802. await this.$refs.rCanvas
  803. .drawText({
  804. text: this.bloggerInfo.description,
  805. max_width: 0,
  806. x: 70,
  807. y: 52,
  808. font_color: '#666',
  809. font_size: 12
  810. })
  811. .catch(err_msg => {
  812. uni.showToast({
  813. title: err_msg,
  814. icon: 'none'
  815. });
  816. });
  817. // 文章封面图
  818. await this.$refs.rCanvas
  819. .drawImage({
  820. url: _articleCover,
  821. x: 12,
  822. y: 75,
  823. w: 312,
  824. h: 180,
  825. border_radius: 6
  826. })
  827. .catch(err_msg => {
  828. uni.showToast({
  829. title: err_msg,
  830. icon: 'none'
  831. });
  832. });
  833. // 文章标题
  834. await this.$refs.rCanvas
  835. .drawText({
  836. text: this.result.spec.title,
  837. max_width: 312,
  838. line_clamp: 1,
  839. x: 12,
  840. y: 285,
  841. font_weight: 'bold',
  842. font_color: '#333',
  843. font_size: 14
  844. })
  845. .catch(err_msg => {
  846. uni.showToast({
  847. title: err_msg,
  848. icon: 'none'
  849. });
  850. });
  851. await this.$refs.rCanvas
  852. .drawText({
  853. text: this.result?.status?.excerpt || "文章暂无摘要信息",
  854. max_width: 312,
  855. line_clamp: 2,
  856. x: 12,
  857. y: 310,
  858. font_color: '#333',
  859. font_size: 13,
  860. line_height: 20
  861. })
  862. .catch(err_msg => {
  863. uni.showToast({
  864. title: err_msg,
  865. icon: 'none'
  866. });
  867. });
  868. this.drawDashedLine(this.$refs.rCanvas.ctx, 14, 356, 332, 0.5, [8, 5, 5, 5], '#999');
  869. // 小程序信息
  870. const _qrCodeImageUrl = await this.qrCodeImageUrl();
  871. await this.$refs.rCanvas
  872. .drawImage({
  873. url: this.$utils.checkImageUrl(_qrCodeImageUrl),
  874. x: 20,
  875. y: 360,
  876. w: 80,
  877. h: 80
  878. })
  879. .catch(err_msg => {
  880. uni.showToast({
  881. title: err_msg,
  882. icon: 'none'
  883. });
  884. });
  885. await this.$refs.rCanvas
  886. .drawText({
  887. text: '长按识别小程序',
  888. x: 150,
  889. y: 390,
  890. font_color: '#333',
  891. font_size: 15,
  892. font_weight: 'bold',
  893. line_height: 22
  894. })
  895. .catch(err_msg => {
  896. uni.showToast({
  897. title: err_msg,
  898. icon: 'none'
  899. });
  900. });
  901. await this.$refs.rCanvas
  902. .drawText({
  903. text: '关注我,给你分享更多有趣的知识',
  904. x: 115,
  905. y: 425,
  906. font_color: '#333',
  907. font_size: 12,
  908. line_height: 22
  909. })
  910. .catch(err_msg => {
  911. uni.showToast({
  912. title: err_msg,
  913. icon: 'none'
  914. });
  915. });
  916. // 生成海报
  917. await this.$refs.rCanvas.draw(res => {
  918. //res.tempFilePath:生成成功,返回base64图片
  919. // 保存图片
  920. this.poster.loading = false;
  921. callback(res);
  922. });
  923. });
  924. },
  925. fnOnPosterClose() {
  926. this.poster.show = false;
  927. this.poster.showCanvas = false;
  928. this.poster.loading = true;
  929. },
  930. fnSavePoster() {
  931. // this.$refs.rCanvas.saveImage(this.poster.res.tempFilePath);
  932. uni.saveImageToPhotosAlbum({
  933. filePath: this.poster.url,
  934. success: () => {
  935. uni.$tm.toast('保存成功');
  936. }, fail: (e) => {
  937. uni.$tm.toast('保存失败,请重试');
  938. }
  939. })
  940. },
  941. fnShareTo() {
  942. // #ifdef MP-WEIXIN
  943. uni.$tm.toast('点击右上角分享给好友!');
  944. // #endif
  945. },
  946. fnOnShowCommentDetail(data) {
  947. const {
  948. postName,
  949. comment
  950. } = data;
  951. this.commentDetail.comment = comment;
  952. this.commentDetail.postName = postName;
  953. this.commentDetail.list = [];
  954. this.commentDetail.show = true;
  955. this.fnGetChildComments();
  956. },
  957. fnGetChildComments() {
  958. this.commentDetail.loading = 'loading';
  959. this.$httpApi.v2
  960. .getPostCommentReplyList(this.commentDetail.postName, {
  961. page: 1,
  962. size: 100
  963. })
  964. .then(res => {
  965. console.log('getPostChildrenCommentList res', res);
  966. this.commentDetail.loading = 'success';
  967. this.commentDetail.list = res.items;
  968. })
  969. .catch(err => {
  970. console.log('getPostChildrenCommentList err', err);
  971. this.commentDetail.loading = 'error';
  972. });
  973. },
  974. fnToCate(category) {
  975. uni.navigateTo({
  976. url: `/pagesA/category-detail/category-detail?name=${category.metadata.name}&title=${category.spec.displayName}`
  977. });
  978. },
  979. fnToTag(tag) {
  980. uni.navigateTo({
  981. url: `/pagesA/tag-detail/tag-detail?name=${tag.metadata.name}&title=${tag.spec.displayName}`
  982. });
  983. },
  984. async fnOnCommentLoaded(data) {
  985. const _list = [];
  986. const _handleData = list => {
  987. return new Promise(resolve => {
  988. if (list.length === 0) {
  989. resolve();
  990. } else {
  991. list.forEach(item => {
  992. _list.push(item);
  993. if (item.replies && item.replies.length !== 0) {
  994. _handleData(item.replies.items);
  995. }
  996. resolve();
  997. });
  998. }
  999. });
  1000. };
  1001. await _handleData(data);
  1002. // if (this.globalAppSettings.barrage.use) {
  1003. // this.$nextTick(() => {
  1004. // if (_list.length != 0) {
  1005. // _handleAddBarrage();
  1006. // }
  1007. // });
  1008. // }
  1009. // const _handleRemove = () => {
  1010. // this.$refs['barrage'] && this.$refs['barrage'].remove({
  1011. // duration: 5000, // 延迟关闭的时间
  1012. // speed: 600 // 弹幕消失的速度
  1013. // });
  1014. // };
  1015. // let index = 0;
  1016. // const _handleAddBarrage = () => {
  1017. // setTimeout(() => {
  1018. // this.$refs['barrage'] && this.$refs['barrage'].add(_list[index]);
  1019. // index += 1;
  1020. // if (index < _list.length - 1) {
  1021. // _handleAddBarrage();
  1022. // } else {
  1023. // _handleRemove();
  1024. // }
  1025. // }, 1000);
  1026. // };
  1027. },
  1028. fnToWebview(data) {
  1029. uni.navigateTo({
  1030. url: '/pagesC/website/website?data=' +
  1031. JSON.stringify({
  1032. title: data.title,
  1033. url: encodeURIComponent(data.url)
  1034. })
  1035. });
  1036. },
  1037. fnToOriginal(originalURL) {
  1038. this.fnToWebview({
  1039. title: this.result.title,
  1040. url: originalURL
  1041. });
  1042. },
  1043. readMore() {
  1044. const annotations = this.result?.metadata?.annotations;
  1045. const restrictReadEnable = annotations?.restrictReadEnable;
  1046. if (restrictReadEnable === 'password') {
  1047. this.passwordModal.show = true;
  1048. return;
  1049. } else if (restrictReadEnable === 'code') {
  1050. this.verificationCodeModal.show = true;
  1051. return;
  1052. } else if (restrictReadEnable === 'comment') {
  1053. uni.showToast({
  1054. title: '前往web端评论后访问',
  1055. icon: 'none'
  1056. });
  1057. } else if (restrictReadEnable === 'login') {
  1058. uni.showToast({
  1059. title: '前往web端登录后访问',
  1060. icon: 'none'
  1061. });
  1062. } else if (restrictReadEnable === 'pay') {
  1063. uni.showToast({
  1064. title: '前往web端支付后访问',
  1065. icon: 'none'
  1066. });
  1067. }
  1068. // 两秒后执行
  1069. setTimeout(() => {
  1070. copyToClipboard(`${HaloTokenConfig.BASE_API + this.result.status.permalink}`);
  1071. }, 2000);
  1072. },
  1073. // 获取openid
  1074. fnGetOpenid() {
  1075. // #ifdef MP-WEIXIN
  1076. uni.login({
  1077. provider: 'weixin',
  1078. success: function (loginRes) {
  1079. try {
  1080. // todo 因为没有获取openid,所以先使用code代替
  1081. uni.setStorageSync('openid', loginRes.code)
  1082. } catch (error) {
  1083. console.log(error)
  1084. }
  1085. }
  1086. })
  1087. // #endif
  1088. },
  1089. restrictReadCheckOrViewVideo() {
  1090. console.log('restrictReadCheckOrViewVideo', this.verificationCodeModal.type)
  1091. if (this.verificationCodeModal.type === 'advert') {
  1092. this.openVideoAd();
  1093. } else {
  1094. this.restrictReadCheck();
  1095. }
  1096. },
  1097. // 校验密码
  1098. restrictReadCheck() {
  1099. if (!this.restrictReadInputCode) {
  1100. uni.showToast({
  1101. title: '请输入内容',
  1102. icon: 'none'
  1103. });
  1104. return;
  1105. }
  1106. this.$httpApi.v2.requestRestrictReadCheck(this.result?.metadata?.annotations?.restrictReadEnable, this.restrictReadInputCode, this.result?.metadata?.name)
  1107. .then(res => {
  1108. if (res.code === 200) {
  1109. this.passwordModal.show = false;
  1110. this.verificationCodeModal.show = false;
  1111. this.fnGetData();
  1112. } else {
  1113. uni.showToast({
  1114. title: '密码错误',
  1115. icon: 'none'
  1116. });
  1117. }
  1118. })
  1119. .catch(err => {
  1120. console.error(err);
  1121. });
  1122. },
  1123. adLoad() {
  1124. if (wx.createRewardedVideoAd) {
  1125. videoAd = wx.createRewardedVideoAd({
  1126. adUnitId: this.verificationCodeModal.adId
  1127. })
  1128. videoAd.onError(err => {
  1129. })
  1130. videoAd.onClose((status) => {
  1131. if (status && status.isEnded || status === undefined) {
  1132. //这里写广告播放完成后的事件
  1133. this.getVerificationCode();
  1134. } else {
  1135. // 广告播放未完成
  1136. }
  1137. })
  1138. }
  1139. },
  1140. openVideoAd: function () {
  1141. if (videoAd && this.verificationCodeModal.adId !== '') {
  1142. videoAd.show().catch(err => {
  1143. // 失败重试
  1144. console.log("广告拉取失败")
  1145. videoAd.load().then(() => videoAd.show())
  1146. })
  1147. } else {
  1148. this.getVerificationCode();
  1149. }
  1150. },
  1151. getVerificationCode() {
  1152. uni.showLoading({
  1153. title: '正在获取...'
  1154. });
  1155. this.$httpApi.v2.createVerificationCode()
  1156. .then(res => {
  1157. if (res.code === 200) {
  1158. this.verificationCodeModal.show = false;
  1159. this.restrictReadInputCode = res.data;
  1160. this.restrictReadCheck();
  1161. } else {
  1162. uni.$tm.toast('操作失败,请重试!');
  1163. }
  1164. })
  1165. .catch(err => {
  1166. uni.$tm.toast(err.message);
  1167. });
  1168. },
  1169. async qrCodeImageUrl() {
  1170. const useDynamicQRCode = this.haloConfigs?.appConfig?.appInfo?.useDynamicQRCode;
  1171. if (useDynamicQRCode) {
  1172. const qrCodeImg = await this.$httpApi.v2.getQRCodeImg(this.result.metadata.name);
  1173. return qrCodeImg;
  1174. } else {
  1175. return this.haloConfigs?.appConfig?.appInfo?.qrCodeImageUrl;
  1176. }
  1177. },
  1178. handleQueryCommentListScrollTop() {
  1179. if (!this.postDetailConfig) return;
  1180. if (!this.postDetailConfig.showComment) return;
  1181. this.$nextTick(() => {
  1182. setTimeout(() => {
  1183. uni.createSelectorQuery().in(this).select('#CommentList').boundingClientRect(
  1184. res => {
  1185. this.commentListScrollTop = res.top - 12;
  1186. }).exec();
  1187. }, 2 * 1000)
  1188. })
  1189. }
  1190. }
  1191. };
  1192. </script>
  1193. <style lang="scss" scoped>
  1194. .app-page {
  1195. width: 100vw;
  1196. min-height: 100vh;
  1197. display: flex;
  1198. flex-direction: column;
  1199. box-sizing: border-box;
  1200. background-color: #fafafd;
  1201. padding-bottom: 24rpx;
  1202. }
  1203. .bg-image {
  1204. position: fixed;
  1205. left: 0;
  1206. top: 0;
  1207. right: 0;
  1208. bottom: 0;
  1209. width: 100vw;
  1210. height: 100vh;
  1211. z-index: 0;
  1212. }
  1213. .loading-wrap {
  1214. padding: 0 24rpx;
  1215. height: inherit;
  1216. background-color: #fff;
  1217. }
  1218. .head {
  1219. display: flex;
  1220. flex-direction: column;
  1221. align-items: center;
  1222. justify-content: center;
  1223. padding: 36rpx 24rpx;
  1224. background-color: #ffffff;
  1225. box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.03);
  1226. // box-shadow: 0rpx 6rpx 30rpx rgba(182, 223, 255, 0.3);
  1227. border-radius: 12rpx;
  1228. .title {
  1229. font-size: 36rpx;
  1230. font-weight: 600;
  1231. text-align: center;
  1232. }
  1233. .detail {
  1234. width: 100%;
  1235. margin-top: 24rpx;
  1236. font-size: 26rpx;
  1237. .author {
  1238. text-align: center;
  1239. font-size: 24rpx;
  1240. color: #666;
  1241. &-name {
  1242. }
  1243. &-time {
  1244. margin-left: 36rpx;
  1245. }
  1246. }
  1247. .cover {
  1248. margin-top: 24rpx;
  1249. width: 100%;
  1250. height: 280rpx;
  1251. &-img {
  1252. width: 100%;
  1253. height: 100%;
  1254. border-radius: 12rpx;
  1255. }
  1256. }
  1257. .count {
  1258. margin-top: 24rpx;
  1259. display: flex;
  1260. justify-content: space-between;
  1261. &.no-thumbnail {
  1262. border-top: 2rpx solid #f2f2f2;
  1263. padding-top: 12rpx;
  1264. .count-item {
  1265. position: relative;
  1266. color: #666;
  1267. &::after {
  1268. content: '';
  1269. position: absolute;
  1270. right: 0;
  1271. background-color: #eee;
  1272. width: 2rpx;
  1273. height: 32rpx;
  1274. }
  1275. &:last-child {
  1276. &::after {
  1277. display: none;
  1278. }
  1279. }
  1280. }
  1281. }
  1282. &-item {
  1283. flex: 1;
  1284. display: flex;
  1285. align-items: flex-end;
  1286. justify-content: center;
  1287. color: #666;
  1288. .label {
  1289. font-size: 24rpx;
  1290. padding-left: 8rpx;
  1291. }
  1292. .value {
  1293. font-size: 32rpx;
  1294. }
  1295. }
  1296. }
  1297. }
  1298. }
  1299. .category {
  1300. margin: 0 24rpx;
  1301. padding: 24rpx;
  1302. background-color: #ffffff;
  1303. border-radius: 12rpx;
  1304. // box-shadow: 0rpx 0rpx 24rpx rgba(182, 223, 255, 0.3);
  1305. box-shadow: 0rpx 4rpx 24rpx rgba(0, 0, 0, 0.03);
  1306. // border: 2rpx solid #f8f8f8;
  1307. font-size: 28rpx;
  1308. &-type {
  1309. line-height: 55rpx;
  1310. }
  1311. &-tag {
  1312. background-color: #5bb8fa;
  1313. color: #fff;
  1314. padding: 6rpx 12rpx;
  1315. border-radius: 6rpx;
  1316. font-size: 24rpx;
  1317. &.is-empty {
  1318. background-color: #607d8b;
  1319. }
  1320. }
  1321. }
  1322. .category-tag + .category-tag {
  1323. margin-left: 12rpx;
  1324. }
  1325. .content {
  1326. margin-top: 24rpx;
  1327. }
  1328. .markdown-wrap,
  1329. .evan-markdown,
  1330. .ad-wrap,
  1331. .copyright-wrap,
  1332. .comment-wrap {
  1333. box-shadow: 0rpx 0rpx 24rpx rgba(0, 0, 0, 0.03);
  1334. }
  1335. .markdown-wrap {
  1336. overflow: hidden;
  1337. border-radius: 12rpx;
  1338. }
  1339. .copyright-title {
  1340. position: relative;
  1341. box-sizing: border-box;
  1342. padding-left: 24rpx;
  1343. font-size: 30rpx;
  1344. &:before {
  1345. content: '';
  1346. position: absolute;
  1347. left: 0rpx;
  1348. top: 8rpx;
  1349. width: 8rpx;
  1350. height: 26rpx;
  1351. background-color: rgb(3, 174, 252);
  1352. border-radius: 6rpx;
  1353. }
  1354. }
  1355. .poup-head {
  1356. border-bottom: 2rpx solid #f5f5f5;
  1357. }
  1358. .poup-body {
  1359. height: 50vh;
  1360. }
  1361. .poup-empty {
  1362. width: 100%;
  1363. height: 40vh;
  1364. }
  1365. .poup-loading-wrap {
  1366. width: 100%;
  1367. height: 40vh;
  1368. .e-loading-icon {
  1369. font-size: 80rpx;
  1370. }
  1371. }
  1372. .poster-content {
  1373. width: 100%;
  1374. min-height: 60vh;
  1375. overflow: hidden;
  1376. }
  1377. .copyright-text {
  1378. line-height: 1.7;
  1379. }
  1380. .poster-loading {
  1381. width: 100%;
  1382. position: absolute;
  1383. left: 0;
  1384. top: 0;
  1385. right: 0;
  1386. bottom: 0;
  1387. // background-color: rgba(0, 0, 0, 0.1);
  1388. z-index: 666;
  1389. }
  1390. .poster-save {
  1391. box-sizing: border-box;
  1392. border-top: 2rpx dashed #eee;
  1393. }
  1394. .original-url {
  1395. display: flex;
  1396. &_left {
  1397. flex-shrink: 0;
  1398. width: 84rpx;
  1399. }
  1400. &_right {
  1401. flex-grow: 1;
  1402. display: inline-flex;
  1403. align-items: center;
  1404. &__link {
  1405. width: 410rpx;
  1406. display: inline-block;
  1407. overflow: hidden;
  1408. text-overflow: ellipsis;
  1409. white-space: nowrap;
  1410. }
  1411. &__btn {
  1412. flex-grow: 1;
  1413. flex-shrink: 0;
  1414. display: block;
  1415. text-align: right;
  1416. }
  1417. }
  1418. }
  1419. </style>