list.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. <template>
  2. <view class="app-container">
  3. <view class="fixed-container">
  4. <view class="top-container">
  5. <scroll-view
  6. :scroll-x="true"
  7. :scroll-left="scrollLeft"
  8. scroll-with-animation
  9. :show-scrollbar="false"
  10. class="tabs-view"
  11. ref="tabs-view"
  12. >
  13. <view class="tab">
  14. <block v-for="(item, index) in tabList" :key="index">
  15. <view
  16. class="item"
  17. :ref="`tabs-item-${index}`"
  18. :class="[
  19. `tabs-item-${index}`,
  20. item.categoryId == tabCurrent ? 'current' : '',
  21. ]"
  22. @tap="changeTab(item.categoryId)"
  23. >{{ item.name }}</view
  24. >
  25. </block>
  26. </view>
  27. </scroll-view>
  28. </view>
  29. <view class="search-container">
  30. <view class="search">
  31. <image src="/static/icon/search.png" class=""></image>
  32. <input
  33. type="text"
  34. confirm-type="search"
  35. placeholder="搜索商品名称"
  36. v-model="keyword"
  37. @confirm="searchSubmit"
  38. />
  39. </view>
  40. </view>
  41. </view>
  42. <view class="banner-container" v-if="bannerUrl">
  43. <image :src="bannerUrl" mode="widthFix"></image>
  44. </view>
  45. <view class="list-container">
  46. <block v-for="(item, index) in dataList" :key="index">
  47. <div class="item" @tap="toGoodsDetail(item.goodsId)">
  48. <image :src="item.goodsImgSrc" mode="aspectFill"></image>
  49. <view class="right">
  50. <view class="title ellipsis-2">{{ item.goodsName }}</view>
  51. <view class="des ellipsis-2">{{
  52. item.describeText ? item.describeText : ''
  53. }}</view>
  54. <view class="stock-sales">
  55. <view class="stock">
  56. <text>剩余{{ item.stock }}件</text>
  57. <view class="progress-box">
  58. <!-- 库存 / 总数 * 100 = 剩余百分比 -->
  59. <progress
  60. :percent="(item.stock / (item.stock + 100)) * 100"
  61. activeColor="#FF3F42"
  62. active
  63. stroke-width="6"
  64. />
  65. </view>
  66. </view>
  67. <view class="sales">销量:{{ item.saleNum || '0' }}</view>
  68. </view>
  69. <view class="bottom">
  70. <view class="price">
  71. <view class="price-1">¥{{ item.groupPrice | numToFixed }}</view>
  72. <view class="price-2"
  73. >¥{{ item.orgGoodsPrice | numToFixed }}</view
  74. >
  75. </view>
  76. <view class="btn">去拼团</view>
  77. </view>
  78. </view>
  79. </div>
  80. </block>
  81. </view>
  82. <no-data v-if="!dataList.length" :showText="'暂无数据'"></no-data>
  83. <loading-text
  84. v-if="dataList.length"
  85. :loading="loading"
  86. :noMore="noMore"
  87. ></loading-text>
  88. <view
  89. class="share-container"
  90. :style="{ top: top + 'px' }"
  91. v-if="dataList.length"
  92. >
  93. <button @tap="clickShare">
  94. <image src="@/static/icon/share.png"></image>
  95. <text>分享</text>
  96. </button>
  97. </view>
  98. <view
  99. class="global-mask"
  100. v-show="isShareDialog"
  101. @tap="isShareDialog = false"
  102. ></view>
  103. <view class="sharelist-container" v-show="isShareDialog">
  104. <button class="item" open-type="share" @tap="isShareDialog = false">
  105. <image src="@/static/icon/wechat.png"></image>
  106. <text>分享给微信好友</text>
  107. </button>
  108. <view class="item" @tap="markImage">
  109. <image src="@/static/icon/image.png"></image>
  110. <text>生成图片分享</text>
  111. </view>
  112. </view>
  113. <view class="global-mask" v-show="isShowCanvas" @tap="closeCanvas"></view>
  114. <view class="canvas-container" v-show="isShowCanvas">
  115. <view class="content">
  116. <canvas
  117. style="width: 340px; height: 340px"
  118. canvas-id="myCanvas"
  119. id="myCanvas"
  120. ></canvas>
  121. </view>
  122. <view class="button"><text @tap="saveImage">保存图片</text></view>
  123. </view>
  124. <!-- <drag-button :isDock="true" :customBar="true" ref="dragButton"></drag-button> -->
  125. </view>
  126. </template>
  127. <script>
  128. import { mapState } from 'vuex';
  129. // import dragButton from '@/components/drag-button.vue';
  130. export default {
  131. // components:{
  132. // dragButton
  133. // },
  134. data() {
  135. return {
  136. configInfo: uni.getStorageSync('configInfo'),
  137. dataList: [], // 优惠券列表
  138. pageNum: 1,
  139. pageSize: 8,
  140. noMore: false,
  141. loading: false,
  142. isShareDialog: false, // 是否显示分享弹窗
  143. isShowCanvas: false, // 是否显示海报弹窗
  144. isFinishCanvas: false, // 是否已完成海报
  145. codeUrl: '',
  146. bgUrl: '',
  147. headUrl: '',
  148. top: 300,
  149. tabList: [], // 分类列表
  150. tabCurrent: '', // 分类当前值
  151. keyword: '',
  152. scrollLeft: 50,
  153. scrollViewWidth: 0,
  154. tabsRect: {
  155. left: 0,
  156. },
  157. bannerUrl: '',
  158. };
  159. },
  160. watch: {
  161. tabList() {
  162. this.$nextTick(() => {
  163. this.resize();
  164. });
  165. },
  166. },
  167. computed: {
  168. ...mapState(['userInfo', 'isLogin', 'userId']),
  169. },
  170. onShow() {
  171. // this.$refs.dragButton.init();
  172. this.top =
  173. uni.getStorageSync('top') < 200
  174. ? uni.getStorageSync('top')
  175. : uni.getStorageSync('top') - 124;
  176. },
  177. onLoad() {
  178. this.getTabList();
  179. this.getBanner();
  180. },
  181. onShow() {
  182. uni.$on('hanbleShare', () => {
  183. this.clickShare();
  184. });
  185. },
  186. onHide() {
  187. uni.$off('hanbleShare');
  188. },
  189. // 下拉刷新
  190. onPullDownRefresh() {
  191. this.pageNum = 1;
  192. this.getList();
  193. },
  194. // 上拉加载
  195. onReachBottom() {
  196. this.getList(1);
  197. },
  198. onShareAppMessage(options) {
  199. if (options && options.from == 'button') {
  200. // 来自页面内的转发按钮
  201. } else {
  202. // 点击微信右上角的分享按钮
  203. }
  204. return {
  205. title: `${this.userInfo.nickName}向你推荐了${this.configInfo.minAppName}的团购活动`,
  206. // imageUrl: this.detail.imgUrl,
  207. path:
  208. '/pages/index/index?serviceId=' +
  209. this.userId +
  210. '&otherType=groupbuyList',
  211. query: {
  212. // id: this.goodsId,
  213. },
  214. success: function (res) {
  215. if (res.errMsg == 'shareAppMessage:ok') {
  216. this.$successToast('分享完成');
  217. }
  218. },
  219. };
  220. },
  221. methods: {
  222. getList(loadMore) {
  223. if (this.noMore && loadMore) return;
  224. this.noMore = false;
  225. if (!loadMore) {
  226. this.pageNum = 1;
  227. } else {
  228. this.loading = true;
  229. }
  230. this.$axios({
  231. url: '/goods/promotion/group/list',
  232. method: 'get',
  233. params: {
  234. pageNo: this.pageNum,
  235. pageSize: this.pageSize,
  236. userId: this.userId,
  237. goodsCategoryId: this.tabCurrent,
  238. keyword: this.keyword,
  239. },
  240. isLoading: !loadMore,
  241. }).then((res) => {
  242. let _list = res.data.records;
  243. let pageTotal = res.data.pages;
  244. if (this.pageNum >= pageTotal) {
  245. this.noMore = true;
  246. }
  247. if (_list.length) {
  248. this.pageNum += 1;
  249. }
  250. if (loadMore) {
  251. this.dataList = this.dataList.concat(_list);
  252. this.loading = false;
  253. } else {
  254. this.dataList = _list;
  255. }
  256. uni.stopPullDownRefresh();
  257. });
  258. },
  259. // 获取一级菜单
  260. getTabList() {
  261. this.$axios({
  262. url: '/goods/category/list',
  263. method: 'get',
  264. params: {},
  265. }).then((res) => {
  266. res.data.unshift({ name: '全部', categoryId: '' });
  267. this.tabList = res.data;
  268. this.tabCurrent = res.data.length > 0 ? res.data[0].categoryId : 0;
  269. this.getList();
  270. });
  271. },
  272. // 切换一级菜单
  273. changeTab(current) {
  274. this.pageNum = 1;
  275. this.tabCurrent = current;
  276. this.resize();
  277. this.getList();
  278. },
  279. searchSubmit() {
  280. this.pageNum = 1;
  281. this.getList();
  282. },
  283. setScrollLeft() {
  284. // 当前活动tab的布局信息,有tab菜单的width和left(为元素左边界到父元素左边界的距离)等信息
  285. const index = this.findElem(this.tabList, 'categoryId', this.tabCurrent);
  286. const tabRect = this.tabList[index];
  287. // 累加得到当前item到左边的距离
  288. const offsetLeft = this.tabList.slice(0, index).reduce((total, curr) => {
  289. return total + curr.rect.width;
  290. }, 0);
  291. // 此处为屏幕宽度
  292. const res = uni.getSystemInfoSync();
  293. const windowWidth = res.windowWidth;
  294. // 将活动的tabs-item移动到屏幕正中间,实际上是对scroll-view的移动
  295. let scrollLeft =
  296. offsetLeft -
  297. (this.tabsRect.width - tabRect.rect.width) / 2 -
  298. (windowWidth - this.tabsRect.right) / 2 +
  299. this.tabsRect.left / 2;
  300. // 这里做一个限制,限制scrollLeft的最大值为整个scroll-view宽度减去tabs组件的宽度
  301. scrollLeft = Math.min(
  302. scrollLeft,
  303. this.scrollViewWidth - this.tabsRect.width
  304. );
  305. this.scrollLeft = Math.max(0, scrollLeft);
  306. },
  307. // 获取所有标签的尺寸
  308. resize() {
  309. // 如果不存在list,则不处理
  310. if (this.tabList.length === 0) {
  311. return;
  312. }
  313. Promise.all([this.getTabsRect(), this.getAllItemRect()]).then(
  314. ([tabsRect, itemRect = []]) => {
  315. this.tabsRect = tabsRect;
  316. this.scrollViewWidth = 0;
  317. itemRect.map((item, index) => {
  318. // 计算scroll-view的宽度,这里
  319. this.scrollViewWidth += item.width;
  320. // 另外计算每一个item的中心点X轴坐标
  321. this.tabList[index].rect = item;
  322. });
  323. // 获取了tabs的尺寸之后,设置滑块的位置
  324. this.setScrollLeft();
  325. }
  326. );
  327. },
  328. // 获取导航菜单的尺寸
  329. getTabsRect() {
  330. return new Promise((resolve) => {
  331. this.queryRect('tabs-view').then((size) => resolve(size));
  332. });
  333. },
  334. // 获取所有标签的尺寸
  335. getAllItemRect() {
  336. return new Promise((resolve) => {
  337. const promiseAllArr = this.tabList.map((item, index) =>
  338. this.queryRect(`tabs-item-${index}`, true)
  339. );
  340. Promise.all(promiseAllArr).then((sizes) => resolve(sizes));
  341. });
  342. },
  343. // 获取各个标签的尺寸
  344. queryRect(el, item) {
  345. const query = uni.createSelectorQuery().in(this);
  346. return new Promise((resolve) => {
  347. query
  348. .select(`.${el}`)
  349. .boundingClientRect((data) => {
  350. resolve(data);
  351. })
  352. .exec();
  353. });
  354. },
  355. findElem(array, attr, val) {
  356. for (var i = 0; i < array.length; i++) {
  357. if (array[i][attr] == val) {
  358. return i; //返回当前索引值
  359. }
  360. }
  361. return -1;
  362. },
  363. // 进入商品详情
  364. toGoodsDetail(id) {
  365. if (!id) {
  366. return false;
  367. }
  368. uni.navigateTo({
  369. url: '/packageGoods/pages/detail?id=' + id,
  370. });
  371. },
  372. // 获取海报图
  373. getBanner() {
  374. this.$axios({
  375. url: '/goods/promotion/share/qrcode',
  376. method: 'get',
  377. params: {
  378. userId: this.userId,
  379. },
  380. }).then((res) => {
  381. this.bannerUrl = res.data.posterImgUrl;
  382. });
  383. },
  384. // 点击分享
  385. clickShare() {
  386. if (!this.isLogin) {
  387. return uni.navigateTo({
  388. url: '/pages/login/index',
  389. });
  390. }
  391. this.getCode();
  392. this.isShareDialog = true;
  393. },
  394. // 获取二维码
  395. getCode() {
  396. let that = this;
  397. this.$axios({
  398. url: '/goods/promotion/share/qrcode',
  399. method: 'get',
  400. params: {
  401. userId: this.userId,
  402. },
  403. }).then((res) => {
  404. if (res.data) {
  405. // this.codeUrl = res.data.qrcode;
  406. // this.bgUrl = res.data.promotionImgUrl;
  407. if (!this.isFinishCanvas) {
  408. uni.downloadFile({
  409. url: res.data.promotionImgUrl,
  410. success: function (fileRes) {
  411. that.bgUrl = fileRes.tempFilePath;
  412. },
  413. });
  414. uni.downloadFile({
  415. url: res.data.qrcode,
  416. success: function (fileRes) {
  417. that.codeUrl = fileRes.tempFilePath;
  418. },
  419. });
  420. uni.downloadFile({
  421. url:
  422. that.userInfo.avatar.indexOf('http') >= 0
  423. ? that.userInfo.avatar
  424. : that.$imageUrl + that.userInfo.avatar,
  425. success: function (fileRes) {
  426. that.headUrl = fileRes.tempFilePath;
  427. },
  428. });
  429. }
  430. }
  431. });
  432. },
  433. // 生成图片
  434. markImage() {
  435. if (!this.bgUrl || !this.codeUrl || !this.headUrl) {
  436. this.getCode();
  437. return this.$toast('生成失败,请重新操作');
  438. }
  439. let that = this;
  440. this.isShareDialog = false;
  441. this.isShowCanvas = true;
  442. if (this.isFinishCanvas) {
  443. return false;
  444. }
  445. uni.showLoading({
  446. title: '海报生成中',
  447. });
  448. let codeUrl = this.codeUrl;
  449. let bgUrl = this.bgUrl;
  450. let headUrl = this.headUrl;
  451. // this.userInfo.nickName = '阿里巴巴的的';
  452. let name =
  453. this.userInfo.nickName.length > 4
  454. ? this.userInfo.nickName.slice(0, 4) + '...'
  455. : this.userInfo.nickName;
  456. var ctx = uni.createCanvasContext('myCanvas');
  457. // 背景图片
  458. ctx.drawImage(bgUrl, 0, 0, 340, 340);
  459. // 二维码
  460. this.circleImgOne(ctx, codeUrl, 260, 260, uni.upx2px(70));
  461. // ctx.drawImage(codeUrl, 260, 260, 70, 70)
  462. // 矩形
  463. // ctx.setFillStyle('rgba(0,0,0,0.4)')
  464. // ctx.fillRect(185, 10, 145, 30)
  465. // 圆角矩形
  466. this.fillRoundRect(ctx, 175, 10, 155, 30, 15, 'rgba(0,0,0,0.4)');
  467. // 头像
  468. // this.circleImgOne(ctx, headUrl, 200, 10, 15);
  469. ctx.drawImage(headUrl, 187, 12, 26, 26);
  470. // 文字
  471. ctx.setFontSize(12);
  472. ctx.setFillStyle('#FFFFFF');
  473. ctx.fillText(name + '向你推荐', 217, 30);
  474. ctx.draw();
  475. uni.hideLoading();
  476. this.isFinishCanvas = true;
  477. },
  478. // ctx=Canvas实例, img=图片地址, x=x轴坐标, y=y轴坐标, r=圆形半径
  479. circleImgOne(ctx, img, x, y, r) {
  480. // 如果在绘制图片之后还有需要绘制别的元素,需启动 save() 、restore() 方法,否则 clip() 方法会导致之后元素都不可见
  481. // save():保存当前 Canvas 画布状态
  482. // restore():恢复到保存时的状态
  483. ctx.save();
  484. let d = r * 2;
  485. let cx = x + r;
  486. let cy = y + r;
  487. ctx.arc(cx, cy, r, 0, 2 * Math.PI);
  488. ctx.strokeStyle = '#FFFFFF'; // 设置绘制圆形边框的颜色
  489. ctx.stroke(); // 绘制出圆形,默认为黑色,可通过 ctx.strokeStyle = '#FFFFFF', 设置想要的颜色
  490. ctx.clip();
  491. ctx.drawImage(img, x, y, d, d);
  492. ctx.restore();
  493. },
  494. /**该方法用来绘制一个有填充色的圆角矩形
  495. *@param cxt:canvas的上下文环境
  496. *@param x:左上角x轴坐标
  497. *@param y:左上角y轴坐标
  498. *@param width:矩形的宽度
  499. *@param height:矩形的高度
  500. *@param radius:圆的半径
  501. *@param fillColor:填充颜色
  502. **/
  503. fillRoundRect(cxt, x, y, width, height, radius, fillColor) {
  504. //圆的直径必然要小于矩形的宽高
  505. if (2 * radius > width || 2 * radius > height) {
  506. return false;
  507. }
  508. cxt.save();
  509. cxt.translate(x, y);
  510. //绘制圆角矩形的各个边
  511. this.drawRoundRectPath(cxt, width, height, radius);
  512. cxt.fillStyle = fillColor;
  513. cxt.fill();
  514. cxt.restore();
  515. },
  516. drawRoundRectPath(cxt, width, height, radius) {
  517. cxt.beginPath(0);
  518. //从右下角顺时针绘制,弧度从0到1/2PI
  519. cxt.arc(width - radius, height - radius, radius, 0, Math.PI / 2);
  520. //矩形下边线
  521. cxt.lineTo(radius, height);
  522. //左下角圆弧,弧度从1/2PI到PI
  523. cxt.arc(radius, height - radius, radius, Math.PI / 2, Math.PI);
  524. //矩形左边线
  525. cxt.lineTo(0, radius);
  526. //左上角圆弧,弧度从PI到3/2PI
  527. cxt.arc(radius, radius, radius, Math.PI, (Math.PI * 3) / 2);
  528. //上边线
  529. cxt.lineTo(width - radius, 0);
  530. //右上角圆弧
  531. cxt.arc(width - radius, radius, radius, (Math.PI * 3) / 2, Math.PI * 2);
  532. //右边线
  533. cxt.lineTo(width, height - radius);
  534. cxt.closePath();
  535. },
  536. // 保存图片
  537. saveImage() {
  538. let that = this;
  539. uni.canvasToTempFilePath({
  540. x: 0,
  541. y: 0,
  542. width: 340,
  543. height: 340,
  544. canvasId: 'myCanvas',
  545. success: function (res) {
  546. uni.saveImageToPhotosAlbum({
  547. filePath: res.tempFilePath,
  548. success: function () {
  549. that.$successToast('保存成功');
  550. },
  551. });
  552. },
  553. });
  554. },
  555. // 关闭canvas
  556. closeCanvas() {
  557. this.isShowCanvas = false;
  558. uni.hideLoading();
  559. },
  560. },
  561. };
  562. </script>
  563. <style lang="scss">
  564. .app-container {
  565. background: #f4f2f2;
  566. box-sizing: border-box;
  567. padding-top: 184rpx;
  568. }
  569. .fixed-container {
  570. position: fixed;
  571. top: 0;
  572. left: 0;
  573. width: 100%;
  574. }
  575. .share-container {
  576. position: fixed;
  577. right: 20rpx;
  578. // bottom: 30vh;
  579. button {
  580. width: 108rpx;
  581. height: 108rpx;
  582. padding: 0;
  583. display: flex;
  584. flex-direction: column;
  585. justify-content: center;
  586. align-items: center;
  587. border-radius: 50%;
  588. // box-shadow: 0 1px 4px rgba(0, 21, 41, .8);
  589. background: #ffffff;
  590. border: 4rpx solid #eaeaea;
  591. image {
  592. width: 28rpx;
  593. height: 28rpx;
  594. display: block;
  595. flex-shrink: 0;
  596. margin-bottom: 6rpx;
  597. }
  598. text {
  599. font-size: 22rpx;
  600. line-height: 24rpx;
  601. color: #666666;
  602. margin-top: 8rpx;
  603. }
  604. }
  605. }
  606. .top-container {
  607. background: #ffffff;
  608. .tab {
  609. display: flex;
  610. margin-left: -10rpx;
  611. .item {
  612. display: flex;
  613. flex-direction: column;
  614. align-items: center;
  615. flex-shrink: 0;
  616. font-size: 28rpx;
  617. color: #666666;
  618. height: 80rpx;
  619. line-height: 80rpx;
  620. position: relative;
  621. padding: 0 30rpx;
  622. &.current {
  623. color: #ff3f42;
  624. font-weight: 600;
  625. &::after {
  626. content: '';
  627. display: block;
  628. width: 50rpx;
  629. height: 6rpx;
  630. background: #ff3f42;
  631. position: absolute;
  632. bottom: 0;
  633. left: 50%;
  634. margin-left: -25rpx;
  635. }
  636. }
  637. }
  638. }
  639. }
  640. .search-container {
  641. background: #ffffff;
  642. padding: 20rpx;
  643. .search {
  644. height: 64rpx;
  645. display: flex;
  646. align-items: center;
  647. border-radius: 64rpx;
  648. padding: 0 20rpx;
  649. border: 1px solid #eaeaea;
  650. image {
  651. width: 28rpx;
  652. height: 28rpx;
  653. }
  654. input {
  655. width: 100%;
  656. padding-left: 15rpx;
  657. }
  658. }
  659. }
  660. .banner-container {
  661. padding: 20rpx 20rpx 0;
  662. image {
  663. display: block;
  664. width: 100%;
  665. border-radius: 10rpx;
  666. }
  667. }
  668. .list-container {
  669. padding: 20rpx;
  670. .item {
  671. background: #ffffff;
  672. border-radius: 10rpx;
  673. display: flex;
  674. padding: 20rpx;
  675. margin-bottom: 20rpx;
  676. image {
  677. display: block;
  678. width: 180rpx;
  679. height: 180rpx;
  680. flex-shrink: 0;
  681. }
  682. .right {
  683. width: 490rpx;
  684. box-sizing: border-box;
  685. padding-left: 20rpx;
  686. .title {
  687. font-size: 30rpx;
  688. color: #333333;
  689. line-height: 36rpx;
  690. font-weight: 600;
  691. }
  692. .des {
  693. font-size: 24rpx;
  694. line-height: 30rpx;
  695. color: #999999;
  696. margin-top: 6rpx;
  697. }
  698. .stock-sales {
  699. display: flex;
  700. justify-content: space-between;
  701. align-items: center;
  702. margin-top: 10rpx;
  703. font-size: 24rpx;
  704. color: #666666;
  705. .stock {
  706. display: flex;
  707. align-items: center;
  708. text {
  709. font-size: 24rpx;
  710. color: #666666;
  711. }
  712. .progress-box {
  713. width: 140rpx;
  714. border-radius: 6px;
  715. overflow: hidden;
  716. margin-left: 10rpx;
  717. }
  718. }
  719. }
  720. .bottom {
  721. display: flex;
  722. justify-content: space-between;
  723. align-items: center;
  724. margin-top: 10rpx;
  725. .price {
  726. display: flex;
  727. flex-direction: column;
  728. }
  729. .price-1 {
  730. font-size: 32rpx;
  731. color: #ff3f42;
  732. line-height: 36rpx;
  733. }
  734. .price-2 {
  735. font-size: 26rpx;
  736. color: #666666;
  737. line-height: 30rpx;
  738. text-decoration: line-through;
  739. }
  740. .btn {
  741. width: 110rpx;
  742. height: 44rpx;
  743. background: #ff3f42;
  744. border-radius: 5rpx;
  745. font-size: 28rpx;
  746. color: #ffffff;
  747. text-align: center;
  748. line-height: 44rpx;
  749. }
  750. .btn2 {
  751. width: 110rpx;
  752. height: 44rpx;
  753. background: #aaaaaa;
  754. border-radius: 5rpx;
  755. font-size: 28rpx;
  756. color: #ffffff;
  757. text-align: center;
  758. line-height: 44rpx;
  759. }
  760. }
  761. }
  762. }
  763. }
  764. .sharelist-container {
  765. position: fixed;
  766. bottom: 0;
  767. left: 0;
  768. z-index: 999;
  769. width: 100%;
  770. box-sizing: border-box;
  771. background: #ffffff;
  772. padding: 30rpx 0;
  773. display: flex;
  774. button {
  775. background: none;
  776. border-radius: 0;
  777. &::after {
  778. border: none;
  779. }
  780. }
  781. .item {
  782. display: flex;
  783. width: 50%;
  784. flex-direction: column;
  785. align-items: center;
  786. image {
  787. width: 100rpx;
  788. height: 100rpx;
  789. display: block;
  790. margin-bottom: 20rpx;
  791. }
  792. text {
  793. font-size: 28rpx;
  794. line-height: 32rpx;
  795. color: #333333;
  796. }
  797. }
  798. }
  799. .canvas-container {
  800. position: fixed;
  801. left: 50%;
  802. top: 50%;
  803. z-index: 999;
  804. margin-left: -170px;
  805. margin-top: -170px;
  806. .button {
  807. display: flex;
  808. justify-content: center;
  809. text {
  810. display: block;
  811. width: 280rpx;
  812. height: 70rpx;
  813. border-radius: 70rpx;
  814. background: linear-gradient(-90deg, #ff3f42 0%, #fe781f 100%);
  815. font-size: 28rpx;
  816. color: #ffffff;
  817. text-align: center;
  818. line-height: 70rpx;
  819. margin-top: 20rpx;
  820. }
  821. }
  822. }
  823. </style>