search.nvue 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. <template>
  2. <view class="container">
  3. <view class="search-container">
  4. <!-- 搜索框 -->
  5. <view class="search-container-bar">
  6. <!-- #ifdef APP-PLUS -->
  7. <uni-icons class="search-icons" :color="iconColor" size="22" type="mic-filled" @click="speech" />
  8. <!-- #endif -->
  9. <!-- :cancelText="keyBoardPopup ? '取消' : '搜索'" -->
  10. <uni-search-bar ref="searchBar" style="flex:1;" radius="100" v-model="searchText" :focus="focus"
  11. :placeholder="hotWorld" clearButton="auto" cancelButton="none" @clear="cancel" @confirm="confirm"
  12. @cancel="cancel" />
  13. <uni-icons class="scan-icons" :color="iconColor" size="22" type="scan" @click="scanEvent"></uni-icons>
  14. </view>
  15. </view>
  16. <view class="search-body">
  17. <unicloud-db ref='listUdb' v-slot:default="{ pagination, hasMore, loading, error, options }"
  18. @error="onqueryerror" :collection="colList" :page-size="10" orderby="publish_date desc" @load="onDbLoad"
  19. loadtime="manual">
  20. <template v-if="!isLoadData">
  21. <!-- 搜索历史 -->
  22. <view class="word-container" v-if="localSearchList.length">
  23. <view class="word-container_header">
  24. <text class="word-container_header-text">搜索历史</text>
  25. <uni-icons v-if="!localSearchListDel" @click="localSearchListDel = true" class="search-icons"
  26. style="padding-right: 0;" :color="iconColor" size="18" type="trash"></uni-icons>
  27. <view v-else class="flex-center flex-row"
  28. style="font-weight: 500;justify-content: space-between;">
  29. <text
  30. style="font-size: 22rpx;color: #666;padding-top:4rpx;padding-bottom:4rpx;padding-right:20rpx;"
  31. @click="LocalSearchListClear">全部删除</text>
  32. <text
  33. style="font-size: 22rpx;color: #c0402b;padding-top:4rpx;padding-bottom:4rpx;padding-left:20rpx;"
  34. @click="localSearchListDel = false">完成</text>
  35. </view>
  36. </view>
  37. <view class="word-container_body">
  38. <view class="flex-center flex-row word-container_body-text"
  39. v-for="(word, index) in localSearchList" :key="index"
  40. @click="LocalSearchlistItemClick(word, index)">
  41. <text class="word-display" :key="word">{{ word }}</text>
  42. <uni-icons v-if="localSearchListDel" size="12" type="closeempty" />
  43. </view>
  44. </view>
  45. </view>
  46. <!-- 搜索发现 -->
  47. <view class="word-container">
  48. <view class="word-container_header">
  49. <view class="flex-center flex-row">
  50. <text class="word-container_header-text">搜索发现</text>
  51. <uni-icons v-if="!netHotListIsHide" class="search-icons" :color="iconColor" size="14"
  52. type="reload" @click="searchHotRefresh"></uni-icons>
  53. </view>
  54. <uni-icons class="search-icons" style="padding-right: 0;" :color="iconColor" size="18"
  55. :type="netHotListIsHide ? 'eye-slash' : 'eye'"
  56. @click="netHotListIsHide = !netHotListIsHide"></uni-icons>
  57. </view>
  58. <unicloud-db ref="udb" #default="{ data, loading, error, options }" field="content"
  59. collection="opendb-search-hot" orderby="create_date desc,count desc" page-data="replace"
  60. :page-size="10">
  61. <text v-if="loading && !netHotListIsHide" class="word-container_body-info">正在加载...</text>
  62. <view v-else class="word-container_body">
  63. <template v-if="!netHotListIsHide">
  64. <text v-if="error" class="word-container_body-info">{{ error.message }}</text>
  65. <template v-else>
  66. <text v-for="(word, index) in data" class="word-container_body-text" :key="index"
  67. @click="search(word.content)">{{ word.content }}</text>
  68. </template>
  69. </template>
  70. <view v-else style="flex:1;">
  71. <text class="word-container_body-info">当前搜索发现已隐藏</text>
  72. </view>
  73. </view>
  74. </unicloud-db>
  75. </view>
  76. </template>
  77. <uni-list v-else class="uni-list" :border="false" :style="{ height: listHeight }">
  78. <!-- 列表渲染 -->
  79. <uni-list-item :to="'/uni_modules/uni-cms-article/pages/detail/detail?id=' + item._id"
  80. v-for="(item, index) in searchList" :key="index">
  81. <!-- 通过header插槽定义列表左侧图片 -->
  82. <template v-slot:header>
  83. <image class="thumbnail" :src="item.thumbnail" mode="aspectFill"></image>
  84. </template>
  85. <!-- 通过body插槽定义布局 -->
  86. <template v-slot:body>
  87. <view class="main">
  88. <text class="title">{{ item.title }}</text>
  89. <view class="info">
  90. <text class="author">{{ item.user_id[0] ? item.user_id[0].nickname : '' }}</text>
  91. <text class="publish_date">{{ publishTime(item.publish_date) }}</text>
  92. <!-- -->
  93. <!-- <uni-dateformat class="publish_date" :date="item.publish_date"-->
  94. <!-- format="yyyy-MM-dd" :threshold="[60000, 2592000000]"/>-->
  95. </view>
  96. </view>
  97. </template>
  98. </uni-list-item>
  99. <!-- 加载状态:上拉加载更多,加载中,没有更多数据了,加载错误 -->
  100. <!-- #ifdef APP-PLUS -->
  101. <uni-list-item>
  102. <template v-slot:body>
  103. <!-- #endif -->
  104. <uni-load-state @networkResume="refresh" :state="{ data: searchList, pagination, hasMore, loading, error }"
  105. @loadMore="loadMore">
  106. </uni-load-state>
  107. <!-- #ifdef APP-PLUS -->
  108. </template>
  109. </uni-list-item>
  110. <!-- #endif -->
  111. </uni-list>
  112. </unicloud-db>
  113. </view>
  114. <!-- 搜索联想 -->
  115. <view class="search-associative" v-if="associativeShow">
  116. <uni-list>
  117. <uni-list-item v-for="(item, index) in associativeList" :key="item._id" :ellipsis="1" :title="item.title"
  118. @click="associativeClick(item)" show-extra-icon clickable
  119. :extra-icon="{ size: 18, color: iconColor, type: 'search' }">
  120. </uni-list-item>
  121. </uni-list>
  122. </view>
  123. </view>
  124. </template>
  125. <script>
  126. /**
  127. * 云端一体搜索模板
  128. * @description uniCloud云端一体搜索模板,自带下拉候选、历史搜索、热搜。无需再开发服务器代码
  129. */
  130. import translatePublishTime from "@/uni_modules/uni-cms-article/common/publish-time";
  131. import parseScanResult from "@/uni_modules/uni-cms-article/common/parse-scan-result";
  132. import {parseImageUrl} from "@/uni_modules/uni-cms-article/common/parse-image-url";
  133. const searchLogDbName = 'opendb-search-log'; // 搜索记录数据库
  134. const articleDbName = 'uni-cms-articles'; // 文章数据库
  135. const associativeSearchField = 'title'; // 联想时,搜索框值检索数据库字段名
  136. const associativeField = '_id,title'; // 联想列表每一项携带的字段
  137. const localSearchListKey = '__local_search_history'; // 本地历史存储字段名
  138. const db = uniCloud.database();
  139. const articleDBName = 'uni-cms-articles'
  140. const userDBName = 'uni-id-users'
  141. // 数组去重
  142. const arrUnique = arr => {
  143. for (let i = arr.length - 1; i >= 0; i--) {
  144. const curIndex = arr.indexOf(arr[i]);
  145. const lastIndex = arr.lastIndexOf(arr[i])
  146. curIndex != lastIndex && arr.splice(lastIndex, 1)
  147. }
  148. return arr
  149. } // 节流
  150. // 防抖
  151. function debounce(fn, interval, isFirstAutoRun) {
  152. /**
  153. *
  154. * @param {要执行的函数} fn
  155. * @param {在操作多长时间后可再执行,第一次立即执行} interval
  156. */
  157. var _self = fn;
  158. var timer = null;
  159. var first = true;
  160. if (isFirstAutoRun) {
  161. _self();
  162. }
  163. return function () {
  164. var args = arguments;
  165. var _me = this;
  166. if (first) {
  167. first = false;
  168. _self.apply(_me, args);
  169. }
  170. if (timer) {
  171. clearTimeout(timer)
  172. // return false;
  173. }
  174. timer = setTimeout(function () {
  175. clearTimeout(timer);
  176. timer = null;
  177. _self.apply(_me, args);
  178. }, interval || 200);
  179. }
  180. }
  181. export default {
  182. // 组件数据
  183. data() {
  184. return {
  185. // 文章数据库名称
  186. articleDbName,
  187. // 搜索记录数据库名称
  188. searchLogDbName,
  189. // 状态栏高度
  190. statusBarHeight: '0px',
  191. // 本地搜索列表
  192. localSearchList: uni.getStorageSync(localSearchListKey),
  193. // 是否删除本地搜索列表
  194. localSearchListDel: false,
  195. // 是否隐藏网络热搜列表
  196. netHotListIsHide: false,
  197. // 搜索文本
  198. searchText: '',
  199. // 图标颜色
  200. iconColor: '#999999',
  201. // 联想列表
  202. associativeList: [],
  203. // 是否弹出键盘
  204. keyBoardPopup: false,
  205. // 搜索热词
  206. hotWorld: 'DCloud', // 搜索热词,如果没有输入即回车,则搜索热词,但是不会加入搜索记录
  207. // 是否自动聚焦
  208. focus: true,
  209. // 语音识别引擎
  210. speechEngine: 'iFly', // 语音识别引擎 iFly 讯飞 baidu 百度
  211. // 是否正在加载数据
  212. isLoadData: false,
  213. // 数据库查询条件
  214. where: '"article_status" == 1',
  215. // 列表高度
  216. listHeight: 0,
  217. // 是否显示联想列表
  218. associativeShow: false,
  219. // 是否显示无联想列表
  220. noAssociativeShow: false,
  221. // 搜索结果列表
  222. searchList: []
  223. }
  224. },
  225. // 组件创建时执行
  226. created() {
  227. // 初始化数据库
  228. this.db = uniCloud.database();
  229. this.searchLogDb = this.db.collection(this.searchLogDbName);
  230. this.articleDbName = this.db.collection(this.articleDbName);
  231. // #ifndef H5
  232. // 监听键盘高度变化
  233. uni.onKeyboardHeightChange((res) => {
  234. this.keyBoardPopup = res.height !== 0;
  235. })
  236. // #endif
  237. },
  238. // 计算属性
  239. computed: {
  240. colList() {
  241. // 返回文章和用户列表
  242. return [
  243. db.collection(articleDBName).where(this.where).field('thumbnail,title,publish_date,user_id').getTemp(),
  244. db.collection(userDBName).field('_id,nickname').getTemp()
  245. ]
  246. }
  247. },
  248. // 页面初次渲染完成时执行
  249. onReady() {
  250. // #ifdef APP-NVUE
  251. /* 可用窗口高度 - 搜索框高 - 状态栏高 */
  252. this.listHeight = uni.getSystemInfoSync().windowHeight + 'px';
  253. // #endif
  254. // #ifndef APP-NVUE
  255. this.listHeight = 'auto'
  256. // #endif
  257. },
  258. // 页面加载时执行
  259. onLoad() {
  260. //#ifdef APP-PLUS
  261. // 获取状态栏高度
  262. this.statusBarHeight = `${uni.getSystemInfoSync().statusBarHeight}px`;
  263. //#endif
  264. },
  265. // 组件方法
  266. methods: {
  267. // 清空搜索框
  268. clear(res) {
  269. console.log("res: ", res);
  270. },
  271. // 确认搜索
  272. confirm(res) {
  273. // 键盘确认
  274. this.search(res.value);
  275. },
  276. // 取消搜索
  277. cancel(res) {
  278. uni.hideKeyboard();
  279. this.searchText = '';
  280. this.isLoadData = false
  281. this.associativeShow = false
  282. // this.loadList();
  283. },
  284. // 执行搜索
  285. search(value) {
  286. if (!value && !this.hotWorld) {
  287. return;
  288. }
  289. if (value) {
  290. if (this.searchText !== value) {
  291. this.searchText = value
  292. }
  293. this.localSearchListManage(value);
  294. this.searchLogDbAdd(value)
  295. } else if (this.hotWorld) {
  296. this.searchText = this.hotWorld
  297. }
  298. uni.hideKeyboard();
  299. this.loadList(this.searchText);
  300. },
  301. // 管理本地搜索列表
  302. localSearchListManage(word) {
  303. let list = uni.getStorageSync(localSearchListKey);
  304. if (list.length) {
  305. this.localSearchList.unshift(word);
  306. arrUnique(this.localSearchList);
  307. if (this.localSearchList.length > 10) {
  308. this.localSearchList.pop();
  309. }
  310. } else {
  311. this.localSearchList = [word];
  312. }
  313. uni.setStorageSync(localSearchListKey, this.localSearchList);
  314. },
  315. // 清空本地搜索列表
  316. LocalSearchListClear() {
  317. uni.showModal({
  318. content: "确认清空搜索历史吗",
  319. confirmText: "删除",
  320. confirmColor: 'red',
  321. cancelColor: '#808080',
  322. success: res => {
  323. if (res.confirm) {
  324. this.localSearchListDel = false;
  325. this.localSearchList = [];
  326. uni.removeStorageSync(localSearchListKey)
  327. }
  328. }
  329. });
  330. },
  331. // 点击本地搜索列表项
  332. LocalSearchlistItemClick(word, index) {
  333. if (this.localSearchListDel) {
  334. this.localSearchList.splice(index, 1);
  335. uni.setStorageSync(localSearchListKey, this.localSearchList);
  336. if (!this.localSearchList.length) {
  337. this.localSearchListDel = false;
  338. }
  339. return;
  340. }
  341. this.noAssociativeShow = true;
  342. this.search(word);
  343. },
  344. // 刷新搜索热词
  345. searchHotRefresh() {
  346. this.$refs.udb.refresh();
  347. },
  348. // 语音搜索
  349. speech() {
  350. // #ifdef APP-PLUS
  351. plus.speech.startRecognize({
  352. engine: this.speechEngine,
  353. punctuation: false, // 标点符号
  354. timeout: 10000
  355. }, word => {
  356. word = word instanceof Array ? word[0] : word;
  357. this.search(word)
  358. }, err => {
  359. console.error("语音识别错误: ", err);
  360. });
  361. // #endif
  362. },
  363. // 添加搜索记录
  364. searchLogDbAdd(value) {
  365. /*
  366. 在此处存搜索记录,如果登录则需要存 user_id,若未登录则存device_id
  367. */
  368. this.getDeviceId().then(device_id => {
  369. this.searchLogDb.add({
  370. // user_id: device_id,
  371. device_id,
  372. content: value,
  373. create_date: Date.now()
  374. })
  375. })
  376. },
  377. // 获取设备ID
  378. getDeviceId() {
  379. return new Promise((resolve, reject) => {
  380. // 从本地缓存中获取uni_id
  381. const uniId = uni.getStorageSync('uni_id');
  382. // 如果uni_id不存在,则获取设备信息
  383. if (!uniId) {
  384. // #ifdef APP-PLUS
  385. plus.device.getInfo({
  386. success: (deviceInfo) => {
  387. resolve(deviceInfo.uuid)
  388. },
  389. fail: () => {
  390. // 如果获取设备信息失败,则返回一个随机字符串
  391. resolve(uni.getSystemInfoSync().system + '_' + Math.random().toString(36).substr(2))
  392. }
  393. });
  394. // #endif
  395. // #ifndef APP-PLUS
  396. // 如果不是APP-PLUS,则返回一个随机字符串
  397. resolve(uni.getSystemInfoSync().system + '_' + Math.random().toString(36).substr(2))
  398. // #endif
  399. } else {
  400. // 如果uni_id存在,则直接返回uni_id
  401. resolve(uniId)
  402. }
  403. })
  404. },
  405. // 点击联想词
  406. associativeClick(item) {
  407. /**
  408. * 注意:这里用户根据自己的业务需要,选择跳转的页面即可
  409. */
  410. console.log("associativeClick: ", item, item.title);
  411. // 隐藏联想词
  412. this.noAssociativeShow = true;
  413. // 将搜索框的文本设置为联想词的标题
  414. this.searchText = item.title;
  415. // 加载列表
  416. this.loadList(item.title);
  417. },
  418. // 加载列表
  419. loadList(text = '') {
  420. // 设置查询条件
  421. let where = '"article_status" == 1 '
  422. if (text) {
  423. this.where = where + `&& /${text}/.test(title)`;
  424. } else {
  425. this.where = where;
  426. }
  427. // 隐藏联想词
  428. this.associativeList = [];
  429. this.associativeShow = false;
  430. this.searchList = []
  431. this.isLoadData = true
  432. // 延迟0ms后加载数据
  433. setTimeout(() => {
  434. this.$refs.listUdb.loadData({
  435. clear: true
  436. })
  437. }, 0)
  438. },
  439. // 数据库加载完成
  440. async onDbLoad(data) {
  441. console.log('onDbLoad')
  442. // 设置数据已加载标志
  443. this.isLoadData = true
  444. // 显示联想词
  445. this.noAssociativeShow = false;
  446. for (const article of data) {
  447. const parseImages = await parseImageUrl(article.thumbnail)
  448. article.thumbnail = parseImages ? parseImages.map(image => image.src): []
  449. }
  450. this.searchList = data
  451. },
  452. // 查询错误
  453. onqueryerror(e) {
  454. console.error(e);
  455. },
  456. // 刷新
  457. refresh() {
  458. // 刷新数据
  459. this.$refs.listUdb.loadData({
  460. clear: true
  461. }, () => {
  462. // 停止下拉刷新
  463. uni.stopPullDownRefresh()
  464. // #ifdef APP-NVUE
  465. // 隐藏刷新按钮
  466. this.showRefresh = false
  467. // #endif
  468. })
  469. },
  470. // 加载更多
  471. loadMore() {
  472. // 加载更多数据
  473. this.$refs.listUdb.loadMore()
  474. },
  475. // 格式化发布时间
  476. publishTime(timestamp) {
  477. return translatePublishTime(timestamp)
  478. },
  479. scanEvent () {
  480. uni.scanCode({
  481. onlyFromCamera: true,
  482. scanType: ["qrCode"],
  483. success: (e) => parseScanResult(e.result),
  484. fail: (e) => {
  485. console.error(e)
  486. }
  487. })
  488. }
  489. },
  490. onReachBottom() {
  491. // 当滚动到底部时,加载更多数据
  492. this.loadMore()
  493. },
  494. watch: {
  495. searchText: debounce(function (value, oldValue) {
  496. // 当搜索框的文本发生变化时,执行以下操作
  497. if (value === oldValue) return
  498. if (this.noAssociativeShow) return
  499. if (value) {
  500. // 根据搜索框的文本,查询联想词
  501. this.articleDbName.where({
  502. [associativeSearchField]: new RegExp(value, 'gi'),
  503. }).field(associativeField).get().then(res => {
  504. // 将查询结果赋值给联想词列表,并显示联想词
  505. this.associativeList = res.result.data;
  506. this.associativeShow = true
  507. })
  508. } else {
  509. // 如果搜索框的文本为空,则清空联想词列表
  510. this.associativeList = [];
  511. }
  512. }, 100)
  513. }
  514. }
  515. </script>
  516. <style>
  517. /* #ifndef APP-NVUE */
  518. page {
  519. height: 100%;
  520. flex: 1;
  521. }
  522. /* #endif */
  523. </style>
  524. <style lang="scss" scoped>
  525. $search-bar-height: 52px;
  526. $word-container_header-height: 72rpx;
  527. .status-bar {
  528. background-color: #fff;
  529. }
  530. .container {
  531. /* #ifndef APP-NVUE */
  532. height: 100%;
  533. /* #endif */
  534. flex: 1;
  535. background-color: #f7f7f7;
  536. }
  537. .search-body {
  538. background-color: #fff;
  539. border-bottom-right-radius: 10px;
  540. border-bottom-left-radius: 10px;
  541. }
  542. @mixin uni-flex {
  543. /* #ifndef APP-NVUE */
  544. display: flex;
  545. /* #endif */
  546. }
  547. @mixin words-display {
  548. font-size: 26rpx;
  549. color: #666;
  550. }
  551. .flex-center {
  552. @include uni-flex;
  553. justify-content: center;
  554. align-items: center;
  555. }
  556. .flex-row {
  557. @include uni-flex;
  558. flex-direction: row;
  559. }
  560. /* #ifdef APP-PLUS */
  561. /* #ifndef APP-NVUE || VUE3*/
  562. ::v-deep
  563. /* #endif */
  564. .uni-searchbar {
  565. padding-left: 0;
  566. }
  567. /* #endif */
  568. /* #ifndef APP-NVUE || VUE3*/
  569. ::v-deep
  570. /* #endif */
  571. .uni-searchbar__box {
  572. border-width: 0;
  573. }
  574. /* #ifndef APP-NVUE || VUE3 */
  575. ::v-deep
  576. /* #endif */
  577. .uni-input-placeholder {
  578. font-size: 28rpx;
  579. }
  580. .search-container {
  581. height: $search-bar-height;
  582. @include uni-flex;
  583. flex-direction: column;
  584. justify-content: center;
  585. align-items: center;
  586. position: relative;
  587. background-color: #fff;
  588. @at-root {
  589. #{&}-bar {
  590. @include uni-flex;
  591. flex-direction: row;
  592. justify-content: center;
  593. align-items: center;
  594. position: absolute;
  595. top: 0;
  596. left: 0;
  597. right: 0;
  598. }
  599. }
  600. }
  601. .search-associative {
  602. /* #ifndef APP-NVUE */
  603. overflow-y: auto;
  604. /* #endif */
  605. position: absolute;
  606. top: $search-bar-height;
  607. left: 0;
  608. right: 0;
  609. bottom: 0;
  610. background-color: #fff;
  611. margin-top: 10rpx;
  612. padding-left: 10rpx;
  613. padding-right: 10rpx;
  614. }
  615. .search-icons, .scan-icons {
  616. padding: 16rpx;
  617. }
  618. .scan-icons {
  619. padding-left: 0;
  620. }
  621. .word-display {
  622. @include words-display;
  623. }
  624. .word-container {
  625. padding: 20rpx;
  626. @at-root {
  627. #{&}_header {
  628. @include uni-flex;
  629. height: $word-container_header-height;
  630. line-height: $word-container_header-height;
  631. flex-direction: row;
  632. justify-content: space-between;
  633. align-items: center;
  634. @at-root {
  635. #{&}-text {
  636. color: #3e3e3e;
  637. font-size: 30rpx;
  638. font-weight: bold;
  639. }
  640. }
  641. }
  642. #{&}_body {
  643. @include uni-flex;
  644. flex-wrap: wrap;
  645. flex-direction: row;
  646. @at-root {
  647. #{&}-text {
  648. @include uni-flex;
  649. @include words-display;
  650. justify-content: center;
  651. align-items: center;
  652. background-color: #f6f6f6;
  653. padding: 10rpx 20rpx;
  654. margin: 20rpx 30rpx 0 0;
  655. border-radius: 30rpx;
  656. /* #ifndef APP-NVUE */
  657. box-sizing: border-box;
  658. /* #endif */
  659. text-align: center;
  660. }
  661. #{&}-info {
  662. /* #ifndef APP-NVUE */
  663. display: block;
  664. /* #endif */
  665. flex: 1;
  666. text-align: center;
  667. font-size: 26rpx;
  668. color: #808080;
  669. margin-top: 20rpx;
  670. }
  671. }
  672. }
  673. }
  674. }
  675. .thumbnail {
  676. width: 240rpx;
  677. height: 160rpx;
  678. margin-right: 20rpx;
  679. border-radius: 8rpx;
  680. }
  681. .main {
  682. justify-content: space-between;
  683. flex: 1;
  684. }
  685. .title {
  686. font-size: 32rpx;
  687. }
  688. .info {
  689. flex-direction: row;
  690. justify-content: space-between;
  691. }
  692. .author,
  693. .publish_date {
  694. font-size: 28rpx;
  695. color: #999999;
  696. }
  697. </style>