微信小店联盟带货小程序

index.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. // pages/goods/list/index.js
  2. const app = getApp()
  3. import { request } from '../../../utils/request'
  4. import { checkNeedLogin, doLogin, getLoginInfo, checkNeedBindInstitution } from '../../../utils/login'
  5. Page({
  6. data: {
  7. isLogin: false, // 添加登录状态
  8. goodsList: [],
  9. loading: false,
  10. currentPage: 1,
  11. pageSize: 10,
  12. totalPages: 1,
  13. showAuthModal: false, // 添加弹窗显示状态
  14. bindStatus: 0,
  15. currentGoodsId: null, // 添加当前商品ID
  16. currentShopAppid: '', // 当前商品的小店appid
  17. currentProductId: '', // 当前商品的商品ID
  18. productPromotionLink: '', // 当前商品的推广链接
  19. currentProduct: null,
  20. iconZhihu: '/static/images/icon-zhihu.png',
  21. iconUser: '/static/images/icon-user.png',
  22. posterUrl: '',
  23. showPosterModal: false
  24. },
  25. onLoad: function (options) {
  26. this.checkLoginStatus()
  27. this.loadGoodsList()
  28. },
  29. // 加载商品列表
  30. loadGoodsList() {
  31. if (this.data.loading) return
  32. this.setData({ loading: true })
  33. request({
  34. url: '/api/goods/list',
  35. method: 'GET',
  36. data: {
  37. page: this.data.currentPage,
  38. page_size: this.data.pageSize,
  39. sort: 7
  40. }
  41. }).then(res => {
  42. if (res.rst && res.rst.data) {
  43. const newGoods = res.rst.data
  44. const totalPages = res.rst.pageInfo ? parseInt(res.rst.pageInfo.pages || 1) : 1
  45. // 判断是第一页还是加载更多
  46. const updatedGoods = this.data.currentPage === 1
  47. ? newGoods
  48. : [...this.data.goodsList, ...newGoods]
  49. this.setData({
  50. goodsList: updatedGoods,
  51. currentPage: this.data.currentPage + 1,
  52. totalPages: totalPages
  53. })
  54. }
  55. }).catch(err => {
  56. console.error('获取商品列表失败:', err)
  57. wx.showToast({
  58. title: '获取商品列表失败',
  59. icon: 'none'
  60. })
  61. }).finally(() => {
  62. this.setData({ loading: false })
  63. wx.stopPullDownRefresh()
  64. })
  65. },
  66. // 检查登录状态
  67. async checkLoginStatus() {
  68. console.log('检查登录状态')
  69. if (checkNeedLogin()) {
  70. try {
  71. await doLogin()
  72. this.setData({ isLogin: true })
  73. await getUserInfo()
  74. } catch (err) {
  75. console.error('登录失败:', err)
  76. this.setData({ isLogin: false })
  77. }
  78. } else {
  79. this.setData({ isLogin: true })
  80. await getUserInfo()
  81. }
  82. // 检查是否需要绑定机构
  83. const bindStatus = wx.getStorageSync('bindStatus')
  84. console.log('当前bindStatus:', bindStatus)
  85. const needBind = checkNeedBindInstitution()
  86. console.log('是否需要绑定机构:', needBind)
  87. this.setData({ bindStatus: bindStatus || 0 })
  88. },
  89. // 获取用户信息
  90. async getUserInfo() {
  91. try {
  92. const userInfo = wx.getStorageSync('userInfo')
  93. console.log('当前userInfo:', userInfo)
  94. this.setData({
  95. userInfo,
  96. isLogin: true // 设置登录状态
  97. })
  98. } catch (err) {
  99. console.error('获取用户信息失败:', err)
  100. }
  101. },
  102. // 跳转到商品详情
  103. navigateToDetail(e) {
  104. const goodsId = e.currentTarget.dataset.goodsId;
  105. console.log('1. 获取到的商品ID:', goodsId);
  106. if (!goodsId) {
  107. console.error('商品ID不存在');
  108. return;
  109. }
  110. // 从商品列表中查找商品信息
  111. const product = this.data.goodsList.find(item => item.goods_id === goodsId);
  112. console.log('2. 找到的商品信息:', product);
  113. if (!product) {
  114. console.error('未找到商品信息');
  115. return;
  116. }
  117. // 获取bindStatus
  118. const bindStatus = wx.getStorageSync('bindStatus') || 0;
  119. console.log('3. 当前bindStatus:', bindStatus);
  120. // 获取用户信息
  121. const userInfo = wx.getStorageSync('userInfo');
  122. console.log('4. 用户信息:', userInfo);
  123. // 先设置基本信息
  124. const setData = {
  125. showAuthModal: true,
  126. bindStatus,
  127. currentProduct: {
  128. goods_id: product.goods_id,
  129. name: product.title,
  130. price: product.sale_price,
  131. image: product.head_img_list[0],
  132. estimatedReturn: product.total_commission_amount || '0'
  133. },
  134. currentGoodsId: goodsId,
  135. currentShopAppid: product.shop_appid,
  136. currentProductId: product.product_id,
  137. productPromotionLink: ''
  138. };
  139. console.log('5. 商品基本信息:', {
  140. shop_appid: product.shop_appid,
  141. product_id: product.product_id
  142. });
  143. // 获取推广链接
  144. request({
  145. url: '/api/goods/productPromotionLink',
  146. method: 'POST',
  147. data: {
  148. goods_id: goodsId,
  149. user_id: userInfo ? userInfo.id : '',
  150. sharer_appid: userInfo ? userInfo.sharer_appid : ''
  151. }
  152. }).then(res => {
  153. console.log('6. 推广链接API返回:', res);
  154. if (res.errno === '0' && res.rst) {
  155. setData.productPromotionLink = res.rst.product_promotion_link || '';
  156. console.log('7. 最终设置的数据:', setData);
  157. this.setData(setData, () => {
  158. console.log('8. 数据设置完成,当前状态:', {
  159. showAuthModal: this.data.showAuthModal,
  160. currentShopAppid: this.data.currentShopAppid,
  161. currentProductId: this.data.currentProductId,
  162. productPromotionLink: this.data.productPromotionLink
  163. });
  164. });
  165. } else {
  166. console.error('9. 获取推广链接失败:', res);
  167. wx.showToast({
  168. title: '获取商品信息失败',
  169. icon: 'none'
  170. });
  171. }
  172. }).catch(err => {
  173. console.error('10. 获取推广链接请求失败:', err);
  174. wx.showToast({
  175. title: '获取商品信息失败',
  176. icon: 'none'
  177. });
  178. });
  179. },
  180. // 下拉刷新
  181. onPullDownRefresh: function() {
  182. this.setData({
  183. currentPage: 1,
  184. goodsList: []
  185. }, () => {
  186. this.loadGoodsList()
  187. })
  188. },
  189. // 上拉加载更多
  190. onReachBottom: function() {
  191. if (!this.data.loading && this.data.currentPage <= this.data.totalPages) {
  192. this.loadGoodsList()
  193. }
  194. },
  195. // 隐藏授权弹窗
  196. hideAuthModal() {
  197. this.setData({
  198. showAuthModal: false,
  199. currentProduct: null,
  200. currentGoodsId: null,
  201. currentShopAppid: '',
  202. currentProductId: '',
  203. productPromotionLink: ''
  204. });
  205. },
  206. // 阻止事件冒泡
  207. preventTap() {
  208. // 空函数,仅用于阻止事件冒泡
  209. },
  210. // 点击授权按钮
  211. onAuthClick() {
  212. if (!this.data.isLogin) {
  213. wx.showToast({
  214. title: '请先登录',
  215. icon: 'none'
  216. })
  217. return
  218. }
  219. const userInfo = wx.getStorageSync('userInfo')
  220. if (!userInfo) {
  221. wx.showToast({
  222. title: '获取用户信息失败',
  223. icon: 'none'
  224. })
  225. return
  226. }
  227. const businessType = userInfo.bind_business_type
  228. const queryString = userInfo.bind_query_string
  229. const commissionType = userInfo.commission_type
  230. const commissionRatio = userInfo.commission_ratio
  231. const headSupplierAppid = userInfo.head_supplier_appid
  232. wx.openBusinessView({
  233. businessType: businessType,
  234. queryString: queryString,
  235. extraData: {
  236. commissionType: commissionType,
  237. commissionRatio: commissionRatio,
  238. headSupplierAppid: headSupplierAppid
  239. },
  240. success: (res) => {
  241. console.log('绑定机构成功', res)
  242. wx.setStorageSync('bindStatus', 1)
  243. this.setData({
  244. bindStatus: 1,
  245. showAuthModal: false
  246. })
  247. },
  248. fail: (err) => {
  249. console.error('绑定机构失败', err)
  250. wx.showToast({
  251. title: '绑定失败',
  252. icon: 'none'
  253. })
  254. }
  255. })
  256. },
  257. // 分享商品
  258. async onShareProduct() {
  259. console.log('[分享商品] 开始分享商品');
  260. await this.generatePoster();
  261. },
  262. // 生成海报
  263. async generatePoster() {
  264. try {
  265. wx.showLoading({
  266. title: '生成海报中...',
  267. mask: true
  268. });
  269. const { currentProduct, currentProductId, currentGoodsId } = this.data;
  270. if (!currentProduct || !currentProductId) {
  271. throw new Error('商品信息不完整');
  272. }
  273. // 获取二维码
  274. const qrcodeUrl = await this.getShareQrCode(currentGoodsId);
  275. if (!qrcodeUrl) {
  276. throw new Error('获取二维码失败');
  277. }
  278. // 检查商品图片
  279. const productImage = await new Promise((resolve, reject) => {
  280. wx.getImageInfo({
  281. src: currentProduct.image,
  282. success: res => resolve(res),
  283. fail: err => reject(err)
  284. });
  285. });
  286. // 下载二维码图片
  287. const qrcodeImage = await new Promise((resolve, reject) => {
  288. wx.getImageInfo({
  289. src: qrcodeUrl,
  290. success: res => resolve(res),
  291. fail: err => reject(err)
  292. });
  293. });
  294. // 绘制海报
  295. const ctx = wx.createCanvasContext('posterCanvas');
  296. // 绘制背景
  297. ctx.setFillStyle('#FF5B4C'); // 设置整个画布背景为橙红色
  298. ctx.fillRect(0, 0, 750, 1500);
  299. // 绘制内层白色圆角背景
  300. ctx.save();
  301. ctx.beginPath();
  302. const radius = 20;
  303. // 白色区域的位置和大小(上下边距80px,左右边距40px)
  304. ctx.moveTo(40 + radius, 80);
  305. ctx.lineTo(710 - radius, 80);
  306. ctx.arcTo(710, 80, 710, 80 + radius, radius);
  307. ctx.lineTo(710, 1420 - radius); // 1500 - 80
  308. ctx.arcTo(710, 1420, 710 - radius, 1420, radius);
  309. ctx.lineTo(40 + radius, 1420);
  310. ctx.arcTo(40, 1420, 40, 1420 - radius, radius);
  311. ctx.lineTo(40, 80 + radius);
  312. ctx.arcTo(40, 80, 40 + radius, 80, radius);
  313. ctx.closePath();
  314. ctx.setFillStyle('#ffffff');
  315. ctx.fill();
  316. ctx.restore();
  317. // 绘制顶部装饰
  318. // 左侧横线
  319. ctx.setStrokeStyle('#FF5B4C');
  320. ctx.setLineWidth(2); // 加粗线条
  321. ctx.moveTo(260, 180); // 下移装饰线
  322. ctx.lineTo(350, 180);
  323. ctx.stroke();
  324. // 右侧横线
  325. ctx.moveTo(400, 180); // 线条右移
  326. ctx.lineTo(490, 180);
  327. ctx.stroke();
  328. // 蝴蝶结(简化版)
  329. ctx.beginPath();
  330. ctx.arc(375, 180, 20, 0, 2 * Math.PI); // 增大圆圈
  331. ctx.setFillStyle('#FF5B4C');
  332. ctx.fill();
  333. // 绘制商品标题
  334. ctx.setFillStyle('#333333');
  335. ctx.setFontSize(32); // 放大标题字号
  336. const title = currentProduct.name;
  337. const titleLines = this.splitText(ctx, title, 630);
  338. titleLines.slice(0, 2).forEach((line, index) => {
  339. ctx.fillText(line, 60, 280 + index * 45); // 下移标题
  340. });
  341. // 绘制价格
  342. ctx.setFillStyle('#FF5B4C');
  343. ctx.setFontSize(36); // 放大价格符号
  344. ctx.fillText('¥', 60, 400); // 下移价格
  345. ctx.setFontSize(48); // 放大价格数字
  346. ctx.fillText(currentProduct.price, 90, 400);
  347. // 绘制商品图片
  348. ctx.drawImage(productImage.path, 60, 460, 630, 650); // 下移并适当增加高度
  349. // 绘制底部信息
  350. ctx.drawImage(qrcodeImage.path, 480, 1200, 180, 180); // 下移二维码
  351. // 绘制微信小店icon和文字
  352. // 加载小店icon
  353. await new Promise((resolve, reject) => {
  354. const iconPath = 'https://kx-wechat-shop.oss-cn-beijing.aliyuncs.com/2025/02/17/wechat_shop.png'; // 使用相对路径
  355. console.log('[海报生成] 尝试加载小店icon:', iconPath);
  356. wx.getImageInfo({
  357. src: iconPath,
  358. success: res => {
  359. console.log('[海报生成] 加载小店icon成功', res);
  360. ctx.drawImage(res.path, 60, 1260, 32, 32);
  361. ctx.setFillStyle('#666666');
  362. ctx.setFontSize(28);
  363. ctx.fillText('微信小店', 120, 1290);
  364. ctx.setFillStyle('#999999');
  365. ctx.setFontSize(26);
  366. ctx.fillText('微信扫一扫购买', 120, 1330);
  367. resolve();
  368. },
  369. fail: err => {
  370. console.error('[海报生成] 加载小店icon失败', err);
  371. // 即使加载失败也继续绘制文字
  372. ctx.setFillStyle('#666666');
  373. ctx.setFontSize(28);
  374. ctx.fillText('微信小店', 120, 1290);
  375. ctx.setFillStyle('#999999');
  376. ctx.setFontSize(26);
  377. ctx.fillText('微信扫一扫购买', 120, 1330);
  378. resolve();
  379. }
  380. });
  381. });
  382. // 导出画布为图片
  383. console.log('[海报生成] 画布绘制完成,准备导出图片');
  384. const posterRes = await new Promise((resolve, reject) => {
  385. ctx.draw(false, () => {
  386. wx.canvasToTempFilePath({
  387. canvasId: 'posterCanvas',
  388. success: res => resolve(res),
  389. fail: err => reject(err)
  390. });
  391. });
  392. });
  393. // 生成海报成功后,显示预览并打开分享菜单
  394. this.setData({
  395. posterUrl: posterRes.tempFilePath,
  396. showPosterModal: true
  397. }, () => {
  398. // 显示分享菜单
  399. wx.showShareImageMenu({
  400. path: posterRes.tempFilePath,
  401. complete: () => {
  402. // 无论用户是分享还是取消,都关闭海报预览
  403. this.setData({
  404. showPosterModal: false
  405. })
  406. }
  407. })
  408. });
  409. wx.hideLoading();
  410. } catch (error) {
  411. console.error('生成海报失败:', error);
  412. wx.hideLoading();
  413. wx.showToast({
  414. title: '生成海报失败',
  415. icon: 'none'
  416. });
  417. }
  418. },
  419. // 获取商品二维码
  420. async getShareQrCode(goodsId) {
  421. try {
  422. const userInfo = wx.getStorageSync('userInfo');
  423. if (!userInfo || !userInfo.sharer_appid) {
  424. throw new Error('缺少分享者信息');
  425. }
  426. const res = await request({
  427. url: '/api/goods/shareQrCode',
  428. method: 'POST',
  429. data: {
  430. goods_id: goodsId,
  431. user_id: userInfo ? userInfo.id : '',
  432. sharer_appid: userInfo ? userInfo.sharer_appid : ''
  433. }
  434. });
  435. if (res.errno === '0' && res.rst && res.rst.qrcode_url) {
  436. return res.rst.qrcode_url;
  437. } else {
  438. throw new Error(res.err || '获取二维码失败');
  439. }
  440. } catch (error) {
  441. console.error('获取二维码失败:', error);
  442. wx.showToast({
  443. title: error.message || '获取二维码失败',
  444. icon: 'none'
  445. });
  446. return null;
  447. }
  448. },
  449. // 文本分行处理
  450. splitText(ctx, text, maxWidth) {
  451. console.log('[文本处理] 开始分行处理:', { text, maxWidth });
  452. const chars = text.split('');
  453. const lines = [];
  454. let tempLine = '';
  455. chars.forEach((char) => {
  456. const testLine = tempLine + char;
  457. const metrics = ctx.measureText(testLine);
  458. if (metrics.width > maxWidth) {
  459. console.log('[文本处理] 当前行宽度超出,添加新行:', tempLine);
  460. lines.push(tempLine);
  461. tempLine = char;
  462. } else {
  463. tempLine = testLine;
  464. }
  465. });
  466. if (tempLine) {
  467. console.log('[文本处理] 添加最后一行:', tempLine);
  468. lines.push(tempLine);
  469. }
  470. console.log('[文本处理] 分行结果:', lines);
  471. return lines;
  472. },
  473. closePosterModal() {
  474. this.setData({
  475. showPosterModal: false
  476. });
  477. },
  478. // 处理海报图片加载完成
  479. onPosterImageLoad(e) {
  480. console.log('[海报预览] 图片加载完成:', {
  481. width: e.detail.width,
  482. height: e.detail.height
  483. });
  484. },
  485. // store-product组件事件处理
  486. onStoreProductEnterError(e) {
  487. console.error('进入商品失败,错误详情:', e.detail);
  488. wx.showToast({
  489. title: '进入商品失败',
  490. icon: 'none'
  491. });
  492. }
  493. });