practiceRecord.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617
  1. <template>
  2. <!-- 页面主体内容 -->
  3. <view class="common_page adffc" :style="{'min-height':h+'px', 'padding-top':mt+'px'}">
  4. <cus-header title="申领社会实践记录" bgColor="#FFFFFF"></cus-header>
  5. <!-- 证书部分 -->
  6. <view class="prove adffcac">
  7. <image class="prove-logo" mode="widthFix" :src="certificateData.logoUrl"></image>
  8. <image class="prove-title" mode="widthFix" :src="certificateData.titleUrl"></image>
  9. <image class="prove-line" mode="widthFix" :src="certificateData.lineUrl"></image>
  10. <view class="prove-no">证书编号:{{ certificateData.certificateNumber }}</view>
  11. <view class="prove-info">
  12. <view class="prove-info-pre adf">
  13. <view class="prove-info-pre-left">
  14. <view class="prove-info-pre-left-text">义工服务姓名:</view>
  15. <view class="prove-info-pre-left-tip">Volunteer service Name</view>
  16. </view>
  17. <view class="prove-info-pre-right">{{ certificateData.memberName }}</view>
  18. </view>
  19. <view class="prove-info-pre adf">
  20. <view class="prove-info-pre-left">
  21. <view class="prove-info-pre-left-text space">所属学校:</view>
  22. <view class="prove-info-pre-left-tip">Affiliated school</view>
  23. </view>
  24. <view class="prove-info-pre-right">{{ certificateData.currentSchool }}</view>
  25. </view>
  26. <view class="prove-info-pre adf">
  27. <view class="prove-info-pre-left">
  28. <view class="prove-info-pre-left-text space">证件类型:</view>
  29. <view class="prove-info-pre-left-tip">Type of ID</view>
  30. </view>
  31. <view class="prove-info-pre-right">{{ certificateData.idType }}</view>
  32. </view>
  33. <view class="prove-info-pre adf">
  34. <view class="prove-info-pre-left">
  35. <view class="prove-info-pre-left-text space">证件号码:</view>
  36. <view class="prove-info-pre-left-tip">IdCard No</view>
  37. </view>
  38. <view class="prove-info-pre-right">{{ certificateData.idCard }}</view>
  39. </view>
  40. <view class="prove-info-pre adf">
  41. <view class="prove-info-pre-left">
  42. <view class="prove-info-pre-left-text">义工服务时长:</view>
  43. <view class="prove-info-pre-left-tip">Volunteer service Time</view>
  44. </view>
  45. <view class="prove-info-pre-right">{{ certificateData.volunteerHours }}小时</view>
  46. </view>
  47. </view>
  48. <view class="prove-memo">
  49. 您累计参与了 <span>{{ certificateData.welfareCount }}</span> 场活动,捐赠了 <span>{{ certificateData.loveValue }}</span> 爱心值。向您践行志愿精神,为社会进步奉献力量致以最崇高的敬意。
  50. </view>
  51. <view class="prove-memo" style="margin-top: 30rpx;">特发此证!</view>
  52. <view class="prove-bottom">
  53. <view class="prove-bottom-pre">证明单位:{{ certificateData.issuer }}</view>
  54. <view class="prove-bottom-pre">发证日期:{{ certificateData.createDate }}</view>
  55. </view>
  56. <image class="prove-seal" mode="widthFix" :src="certificateData.sealUrl"></image>
  57. </view>
  58. <!-- 列表部分 -->
  59. <view class="list">
  60. <view class="list-box" v-for="(item,index) in activityList" :key="index">
  61. <view class="title">{{ item.activityName||'' }}</view>
  62. <view class="content adf">
  63. <view class="right">
  64. <view class="right-pre adf">
  65. <view class="tip">活动时间:</view>
  66. <view class="text">{{ item.activityStartTime||'' }}</view>
  67. </view>
  68. <view class="right-pre adf">
  69. <template v-if="item.activityLimit==2">
  70. <view class="tip">专享券贡献:</view>
  71. <view class="text">{{ item.valueLimit||0 }}张</view>
  72. </template>
  73. <template v-else>
  74. <view class="tip">爱心值贡献:</view>
  75. <view class="text">{{ item.valueLimit||0 }}</view>
  76. </template>
  77. </view>
  78. <view class="right-pre adf">
  79. <view class="tip">义工时长:</view>
  80. <view class="text">{{ item.serviceHours||0 }}小时</view>
  81. </view>
  82. <view class="right-pre adf">
  83. <view class="tip">公益合作:</view>
  84. <view class="text">{{ item.channelName||'' }}</view>
  85. </view>
  86. </view>
  87. </view>
  88. </view>
  89. </view>
  90. <!-- 下载按钮 -->
  91. <view class="btn" @click="handleDownload">
  92. {{ isLoading ? '生成中...' : '下载' }}
  93. </view>
  94. </view>
  95. <canvas canvas-id="pdf-canvas" :style="'width:' + canvasWidth + 'px; height:' + canvasHeight + 'px; position: fixed; left: -9999px; top: -9999px;'"></canvas>
  96. </template>
  97. <script setup>
  98. import CusHeader from '@/components/CusHeader/index.vue'
  99. import { onLoad } from '@dcloudio/uni-app'
  100. import { ref, computed, getCurrentInstance } from 'vue'
  101. const { proxy } = getCurrentInstance()
  102. // 页面数据,实际项目中这些数据应该是动态获取的
  103. const certificateData = ref({
  104. logoUrl: 'https://oss.familydaf.cn/sxsnfile/20251218/b198be43fefc4d9aa11176a79dd82b89.png',
  105. titleUrl: 'https://oss.familydaf.cn/sxsnfile/20251218/92165c10788943b3a63e922dc4646ad9.png',
  106. lineUrl: 'https://oss.familydaf.cn/sxsnfile/20251218/d1a2627aed1c477883f6b4a35d26434f.png',
  107. backgroundUrl: 'https://oss.familydaf.cn/sxsnfile/20251218/f66a2b0f890542048731701396420697.png',
  108. sealUrl: 'https://oss.familydaf.cn/sxsnfile/20251218/6eb067ae1d19451aaffe0118d214f053.png',
  109. certificateNumber: '',
  110. memberName: '',
  111. currentSchool: '',
  112. idType: '居民身份证',
  113. idCard: '',
  114. volunteerHours: 0,
  115. welfareCount: 0,
  116. loveValue: 0,
  117. issuer: '善行少年服务基金会',
  118. createDate: ''
  119. });
  120. const activityList = ref([]);
  121. const isLoading = ref(false);
  122. const canvasWidth = ref(0);
  123. const canvasHeight = ref(0);
  124. // rpx转px的工具函数
  125. const rpxToPx = (rpx) => {
  126. const screenWidth = uni.getSystemInfoSync().windowWidth;
  127. return (screenWidth / 750) * rpx;
  128. };
  129. // 文本换行绘制函数
  130. const drawWrappedText = (ctx, text, x, y, maxWidth, lineHeight) => {
  131. const words = text.split('');
  132. let line = '';
  133. let currentY = y;
  134. for (let n = 0; n < words.length; n++) {
  135. const testLine = line + words[n];
  136. const metrics = ctx.measureText(testLine);
  137. const testWidth = metrics.width;
  138. if (testWidth > maxWidth && n > 0) {
  139. ctx.fillText(line, x, currentY);
  140. line = words[n];
  141. currentY += lineHeight;
  142. } else {
  143. line = testLine;
  144. }
  145. }
  146. ctx.fillText(line, x, currentY);
  147. return currentY - y + lineHeight; // 返回这段文本所占的总高度
  148. };
  149. // 获取网络图片信息,返回一个Promise
  150. const getImageInfo = (url) => {
  151. return new Promise((resolve, reject) => {
  152. uni.getImageInfo({
  153. src: url,
  154. success: (res) => resolve(res),
  155. fail: (err) => reject(err),
  156. });
  157. });
  158. };
  159. // 主下载处理函数
  160. const handleDownload = async () => {
  161. if (isLoading.value) return;
  162. isLoading.value = true;
  163. uni.showLoading({ title: '正在生成图片...', mask: true });
  164. try {
  165. // 1. 动态计算Canvas尺寸
  166. const proveHeight = rpxToPx(945);
  167. const pagePaddingTop = rpxToPx(20); // prove-margin-top
  168. const pagePaddingBottom = rpxToPx(184); // 页面底部到按钮的距离
  169. // 估算一个列表项的高度: 上下padding(36*2) + 标题(32) + 标题margin(30) + 4行内容(4*50) + 列表项间距(20)
  170. // 这里的 4*50 是一个估算值,基于你的CSS
  171. const singleListHeight = rpxToPx(36 * 2 + 32 + 30 + (20 + 24) * 4 + 20); // 估算每行高度(margin-top + font-size)
  172. canvasWidth.value = uni.getSystemInfoSync().windowWidth;
  173. // 总高度 = 证书高度 + 列表总高度 + 页面顶部边距 + 底部空白区域
  174. canvasHeight.value = proveHeight + (singleListHeight * activityList.value.length) + pagePaddingTop + pagePaddingBottom;
  175. // 2. 预加载所有图片
  176. const imagesToLoad = [
  177. certificateData.value.backgroundUrl,
  178. certificateData.value.logoUrl,
  179. certificateData.value.titleUrl,
  180. certificateData.value.lineUrl,
  181. certificateData.value.sealUrl
  182. ];
  183. const imageInfos = await Promise.all(imagesToLoad.map(url => getImageInfo(url)));
  184. const [
  185. bgImg, logoImg, titleImg, lineImg, sealImg
  186. ] = imageInfos.map(info => info.path);
  187. // 3. 开始绘制
  188. const ctx = uni.createCanvasContext('pdf-canvas', proxy); // 传入 proxy
  189. // 绘制白色背景
  190. ctx.setFillStyle('#F5F5F5');
  191. ctx.fillRect(0, 0, canvasWidth.value, canvasHeight.value);
  192. let currentY = rpxToPx(20); // 从顶部20rpx的margin开始
  193. // ---- 3.1 绘制证书 (这部分逻辑基本正确,无需大改) ----
  194. const proveX = 0;
  195. const proveWidth = canvasWidth.value;
  196. // 绘制证书背景
  197. ctx.drawImage(bgImg, proveX, currentY, proveWidth, proveHeight);
  198. // 绘制Logo
  199. ctx.drawImage(logoImg, rpxToPx(72), currentY + rpxToPx(58), rpxToPx(133), rpxToPx(40));
  200. // 绘制标题
  201. ctx.drawImage(titleImg, (proveWidth - rpxToPx(363)) / 2, currentY + rpxToPx(101), rpxToPx(363), rpxToPx(40));
  202. // 绘制线条
  203. ctx.drawImage(lineImg, (proveWidth - rpxToPx(400)) / 2, currentY + rpxToPx(101 + 40 + 7), rpxToPx(400), rpxToPx(5));
  204. // 绘制证书编号
  205. ctx.setFontSize(rpxToPx(20));
  206. ctx.setFillStyle('#9F793F');
  207. ctx.setTextAlign('center');
  208. ctx.fillText(`证书编号:${certificateData.value.certificateNumber}`, proveWidth / 2, currentY + rpxToPx(190));
  209. // 绘制个人信息
  210. ctx.setTextAlign('left');
  211. const infoStartX = (proveWidth - rpxToPx(135 + 188)) / 2;
  212. const infoStartY = currentY + rpxToPx(244);
  213. const infoItems = [
  214. { label: '义工服务姓名:', value: certificateData.value?.memberName||'', tip: 'Volunteer service Name' },
  215. { label: '所属学校:', value: certificateData.value?.currentSchool||'', tip: 'Affiliated school' },
  216. { label: '证件类型:', value: certificateData.value?.idType, tip: 'Type of ID' },
  217. { label: '证件号码:', value: certificateData.value?.idCard||'', tip: 'IdCard No' },
  218. { label: '义工服务时长:', value: `${certificateData.value?.volunteerHours||0}小时`, tip: 'Volunteer service Time' }
  219. ];
  220. infoItems.forEach((item, index) => {
  221. const itemY = infoStartY + index * rpxToPx(40);
  222. ctx.setFillStyle('#252525');
  223. ctx.setFontSize(rpxToPx(17));
  224. ctx.fillText(item.label, infoStartX, itemY);
  225. ctx.setFontSize(rpxToPx(10));
  226. ctx.fillText(item.tip, infoStartX, itemY + rpxToPx(13));
  227. const valueX = infoStartX + rpxToPx(135);
  228. ctx.setFontSize(rpxToPx(17));
  229. ctx.fillText(item.value, valueX, itemY);
  230. ctx.setStrokeStyle('#DDCEAF');
  231. ctx.setLineWidth(rpxToPx(1)); // 1px的线在高清屏下更清晰
  232. ctx.beginPath();
  233. ctx.moveTo(valueX, itemY + rpxToPx(8));
  234. ctx.lineTo(valueX + rpxToPx(188), itemY + rpxToPx(8));
  235. ctx.stroke();
  236. });
  237. // 绘制memo
  238. ctx.setFontSize(rpxToPx(20));
  239. const memoY = currentY + rpxToPx(500);
  240. const memoLineHeight = rpxToPx(31);
  241. const memoStartX = rpxToPx(80);
  242. const memoMaxWidth = proveWidth - rpxToPx(160); // 左右边距各80rpx
  243. const memoSegments = [
  244. { text: `您累计参与了 `, color: '#252525' },
  245. { text: `${certificateData.value.welfareCount}`, color: '#C9A771' },
  246. { text: ` 场活动,捐赠了 `, color: '#252525' },
  247. { text: `${certificateData.value.loveValue}`, color: '#C9A771' },
  248. { text: ` 爱心值。向您践行志愿精神,为社会进步奉献力量致以最崇高的敬意。`, color: '#252525' }
  249. ];
  250. let currentMemoX = memoStartX;
  251. let currentMemoY = memoY;
  252. ctx.setTextAlign('left'); // 确保对齐方式正确
  253. for (const segment of memoSegments) {
  254. ctx.setFillStyle(segment.color);
  255. const chars = segment.text.split('');
  256. for (const char of chars) {
  257. const charWidth = ctx.measureText(char).width;
  258. // 如果当前字符加上后会超出最大宽度,则换行
  259. if (currentMemoX + charWidth > memoStartX + memoMaxWidth) {
  260. currentMemoX = memoStartX; // X坐标重置
  261. currentMemoY += memoLineHeight; // Y坐标下移一行
  262. }
  263. ctx.fillText(char, currentMemoX, currentMemoY);
  264. currentMemoX += charWidth; // 更新X坐标
  265. }
  266. }
  267. ctx.fillText('特发此证!', rpxToPx(80), currentY + rpxToPx(600));
  268. // 绘制证明单位和日期
  269. ctx.setFontSize(rpxToPx(17));
  270. ctx.fillText(`证明单位:${certificateData.value.issuer}`, rpxToPx(80), currentY + rpxToPx(700));
  271. ctx.fillText(`发证日期:${certificateData.value.createDate}`, rpxToPx(80), currentY + rpxToPx(730));
  272. // 绘制印章
  273. ctx.drawImage(sealImg, proveWidth - rpxToPx(73 + 131), currentY + proveHeight - rpxToPx(69 + 131), rpxToPx(131), rpxToPx(131));
  274. currentY += proveHeight; // 更新Y坐标到证书底部
  275. // ---- 3.2 绘制活动列表 ----
  276. for (const item of activityList.value) {
  277. currentY += rpxToPx(20); // 列表项间距
  278. const boxX = rpxToPx(24);
  279. const boxWidth = canvasWidth.value - rpxToPx(48);
  280. let itemContentHeight = 0; // 动态计算每个item的高度
  281. // 预计算高度
  282. const titleHeight = rpxToPx(32);
  283. const contentPaddingTop = rpxToPx(36);
  284. const titleMarginBottom = rpxToPx(30);
  285. const contentPaddingBottom = rpxToPx(36);
  286. const rightItemLineHeight = rpxToPx(50); // 估算行高(包含margin)
  287. itemContentHeight = contentPaddingTop + titleHeight + titleMarginBottom + (rightItemLineHeight * 4) + contentPaddingBottom;
  288. // 绘制列表项背景
  289. ctx.setFillStyle('#FFFFFF');
  290. ctx.setShadow(0, rpxToPx(5), rpxToPx(10), 'rgba(0,0,0,0.05)'); // 可选:添加阴影使其更逼真
  291. ctx.fillRect(boxX, currentY, boxWidth, itemContentHeight);
  292. ctx.setShadow(0, 0, 0, 'rgba(0,0,0,0)'); // 清除阴影
  293. const contentPaddingX = rpxToPx(20);
  294. let itemInnerY = currentY + contentPaddingTop;
  295. // 绘制标题
  296. ctx.setFontSize(rpxToPx(32));
  297. ctx.setFillStyle('#252525');
  298. ctx.font = `bold ${rpxToPx(32)}px sans-serif`;
  299. ctx.fillText(item.activityName || '', boxX + contentPaddingX, itemInnerY + rpxToPx(16)); // Y微调
  300. itemInnerY += titleHeight + titleMarginBottom;
  301. // 绘制右侧文字
  302. const textStartX = boxX + contentPaddingX;
  303. ctx.setFontSize(rpxToPx(24));
  304. ctx.font = `normal ${rpxToPx(24)}px sans-serif`;
  305. const rightItems = [
  306. { tip: '活动时间:', text: item.activityStartTime || '' },
  307. { tip: `${item.activityLimit == 2 ? '专享券贡献:' : '爱心值贡献:'}`, text: `${item.valueLimit || 0}${item.activityLimit == 2 ? '张' : ''}` },
  308. { tip: '义工时长:', text: `${item.serviceHours || 0}小时` },
  309. { tip: '公益合作:', text: item.channelName || '' },
  310. ];
  311. rightItems.forEach((rightItem, idx) => {
  312. const textY = itemInnerY + (idx * rightItemLineHeight);
  313. ctx.setFillStyle('#676775');
  314. ctx.fillText(rightItem.tip, textStartX, textY);
  315. const tipWidth = ctx.measureText(rightItem.tip).width;
  316. ctx.setFillStyle('#252525');
  317. ctx.font = `bold ${rpxToPx(24)}px sans-serif`;
  318. // 绘制文本,这里不使用换行函数,因为内容一般不长
  319. ctx.fillText(rightItem.text, textStartX + tipWidth, textY);
  320. ctx.font = `normal ${rpxToPx(24)}px sans-serif`; // 重置字体
  321. });
  322. currentY += itemContentHeight;
  323. }
  324. // 4. 执行绘制并生成图片
  325. ctx.draw(false, () => {
  326. uni.canvasToTempFilePath({
  327. canvasId: 'pdf-canvas',
  328. destWidth: canvasWidth.value * 2, // 提高图片清晰度
  329. destHeight: canvasHeight.value * 2,
  330. fileType: 'png',
  331. quality: 1,
  332. success: (res) => {
  333. // 5. 保存图片到相册
  334. uni.saveImageToPhotosAlbum({
  335. filePath: res.tempFilePath,
  336. success: () => {
  337. uni.showToast({ title: '已保存到相册', icon: 'success' });
  338. },
  339. fail: (err) => {
  340. console.log(err);
  341. if (err.errMsg && err.errMsg.includes('auth deny')) {
  342. uni.showModal({
  343. title: '提示',
  344. content: '需要您授权保存相册',
  345. showCancel: false,
  346. success: () => uni.openSetting()
  347. });
  348. } else {
  349. uni.showToast({ title: '保存失败,请稍后重试', icon: 'none' });
  350. }
  351. }
  352. });
  353. },
  354. fail: (err) => {
  355. console.error('canvasToTempFilePath failed:', err);
  356. uni.showToast({ title: '图片生成失败', icon: 'none' });
  357. },
  358. complete: () => {
  359. uni.hideLoading();
  360. isLoading.value = false;
  361. }
  362. }, proxy); // 传入 proxy
  363. });
  364. } catch (error) {
  365. console.error('handleDownload error:', error);
  366. uni.hideLoading();
  367. isLoading.value = false;
  368. uni.showToast({
  369. title: '生成失败,请检查网络或资源链接', // 提示更具体
  370. icon: 'none'
  371. });
  372. }
  373. };
  374. onLoad((options)=>{
  375. proxy.$api.get(`/core/social/practice/record/${options?.id||''}`).then(({data:res})=>{
  376. if(res.code!==0) return proxy.$showToast(res.msg)
  377. certificateData.value = {...certificateData.value,...res.data};
  378. certificateData.value.idCard = certificateData.value.idCard&&certificateData.value.idCard.replace(/^(\d{6})(\d{8})(\d{3}[\dX])$/i,'$1********$3');
  379. activityList.value = res.data?.activityVos||[];
  380. })
  381. })
  382. </script>
  383. <style scoped lang="scss">
  384. .common_page {
  385. background-color: #F5F5F5;
  386. padding: 0 0 184rpx;
  387. .prove{
  388. margin: 20rpx 24rpx 0; // 改为外边距,避免影响全屏截图
  389. width: calc(100% - 40rpx);
  390. background: url('https://oss.familydaf.cn/sxsnfile/20251218/f66a2b0f890542048731701396420697.png') no-repeat;
  391. background-size: 100% 100%;
  392. height: 945rpx;
  393. position: relative;
  394. &-logo{
  395. width: 133rpx;
  396. position: absolute;
  397. top: 58rpx;
  398. left: 72rpx;
  399. }
  400. &-title{
  401. width: 363rpx;
  402. margin-top: 101rpx;
  403. }
  404. &-line{
  405. width: 400rpx;
  406. margin-top: 7rpx;
  407. }
  408. &-seal{
  409. width: 131rpx;
  410. border-radius: 50%;
  411. position: absolute;
  412. bottom: 69rpx;
  413. right: 73rpx;
  414. }
  415. &-no{
  416. font-family: SourceHanSerifSC, SourceHanSerifSC;
  417. font-weight: bold;
  418. font-size: 20rpx;
  419. color: #9F793F;
  420. line-height: 20rpx;
  421. letter-spacing: 1px;
  422. margin-top: 9rpx;
  423. }
  424. &-info{
  425. margin-top: 27rpx;
  426. overflow: auto;
  427. &-pre{
  428. margin-top: 22rpx;
  429. &-left{
  430. width: 135rpx;
  431. &-text{
  432. font-family: PingFangSC, PingFang SC;
  433. font-weight: 400;
  434. font-size: 17rpx;
  435. color: #252525;
  436. line-height: 17rpx;
  437. letter-spacing: 1px;
  438. &.space{
  439. letter-spacing: 10rpx;
  440. }
  441. }
  442. &-tip{
  443. font-family: PingFangSC, PingFang SC;
  444. font-weight: 400;
  445. font-size: 10rpx;
  446. color: #252525;
  447. line-height: 10rpx;
  448. margin-top: 3rpx;
  449. }
  450. }
  451. &-right{
  452. width: 188rpx;
  453. font-family: PingFangSC, PingFang SC;
  454. font-weight: 400;
  455. font-size: 17rpx;
  456. color: #252525;
  457. line-height: 17rpx;
  458. padding-bottom: 8rpx;
  459. border-bottom: 2rpx solid #DDCEAF;
  460. }
  461. }
  462. }
  463. &-memo{
  464. width: 100%;
  465. padding: 0 80rpx;
  466. box-sizing: border-box;
  467. margin-top: 79rpx;
  468. font-family: PingFangSC, PingFang SC;
  469. font-weight: 400;
  470. font-size: 20rpx;
  471. color: #252525;
  472. line-height: 31rpx;
  473. label{ // canvas无法直接渲染span,颜色在js中处理
  474. color: #C9A771;
  475. margin: 0 5rpx;
  476. }
  477. }
  478. &-bottom{
  479. width: 100%;
  480. padding: 0 80rpx;
  481. box-sizing: border-box;
  482. margin-top: 89rpx;
  483. overflow: hidden;
  484. &-pre{
  485. margin-top: 16rpx;
  486. font-family: PingFangSC, PingFang SC;
  487. font-weight: 400;
  488. font-size: 17rpx;
  489. color: #252525;
  490. line-height: 20rpx;
  491. }
  492. }
  493. }
  494. .list{
  495. margin: 0 24rpx;
  496. &-box{
  497. margin-top: 20rpx;
  498. padding: 36rpx 20rpx;
  499. background: #FFFFFF;
  500. .title{
  501. font-family: PingFang-SC, PingFang-SC;
  502. font-weight: bold;
  503. font-size: 32rpx;
  504. color: #252525;
  505. // line-height: 32rpx;
  506. }
  507. .content{
  508. margin-top: 30rpx;
  509. // .left{
  510. // width: 182rpx;
  511. // height: 240rpx;
  512. // image{
  513. // width: 100%;
  514. // height: 100%;
  515. // }
  516. // }
  517. .right{
  518. width: 100%;
  519. // width: calc(100% - 182rpx);
  520. // padding-left: 20rpx;
  521. // box-sizing: border-box;
  522. &-pre{
  523. margin-top: 20rpx;
  524. &:first-child{
  525. margin-top: 12rpx;
  526. }
  527. .tip{
  528. width: 150rpx;
  529. font-family: PingFangSC, PingFang SC;
  530. font-weight: 400;
  531. font-size: 24rpx;
  532. color: #676775;
  533. }
  534. .text{
  535. width: calc(100% - 150rpx);
  536. font-family: PingFang-SC, PingFang-SC;
  537. font-weight: bold;
  538. font-size: 24rpx;
  539. color: #252525;
  540. }
  541. }
  542. }
  543. }
  544. }
  545. }
  546. .btn{
  547. width: calc(100% - 210rpx);
  548. height: 90rpx;
  549. background: #B7F358;
  550. border-radius: 45rpx;
  551. font-family: PingFang-SC, PingFang-SC;
  552. font-weight: bold;
  553. font-size: 32rpx;
  554. color: #151B29;
  555. line-height: 90rpx;
  556. text-align: center;
  557. letter-spacing: 2rpx;
  558. position: fixed;
  559. left: 105rpx;
  560. bottom: 64rpx;
  561. z-index: 10;
  562. }
  563. }
  564. </style>