Compare commits

...

481 Commits

Author SHA1 Message Date
YunaiV 48811b88dc (〃'▽'〃) v2025.09 发布:新增 AI 支持联网搜索、推理、文件/图片、MCP 等功能,完善 IoT 场景联动 2025-08-31 11:36:50 +08:00
YunaiV 1ab9d97fce fix:uploadFilesFromPath 上传时,directory 存在 “[object Undefined]” 的情况 2025-08-24 11:15:05 +08:00
YunaiV 7b76e969af feat:【system 系统管理】租户支持匹配多域名、微信小程序 appid 等 2025-08-19 23:01:57 +08:00
YunaiV eca7bb81d3 fix:uploadFilesFromPath 上传时,directory 存在 “[object Undefined]” 的情况 2025-08-17 10:33:08 +08:00
芋道源码 ef56dd4a2a
!150 fix(chat): 修复uni-easyinput在手机键盘弹出时,导致页面整体上移的问题
Merge pull request !150 from wslyx/master
2025-08-09 02:32:26 +00:00
芋道源码 685905991d
!159 fix(支付): 兼容微信支付V3参数大小写
Merge pull request !159 from wuKong/master
2025-08-09 02:30:52 +00:00
wuKong fc1e6ac22b fix(支付): 兼容微信支付V3参数大小写
- 在解析支付参数时,增加对 appId、nonceStr 和 timeStamp 字段的兼容性处理
- 如果大写版本不存在,使用小写版本赋值
- 确保支付功能在不同环境下能够正常工作
2025-08-08 23:59:37 +08:00
YunaiV 5eabbdc365 Merge branch 'master' of https://github.com/yudaocode/yudao-mall-uniapp 2025-07-31 23:46:26 +08:00
YunaiV a475776039 (〃'▽'〃) v2025.08 发布:完善 vben5(antd) 工作流、迁移 vben5(ep) 商城、支持 IoT TCP 协议 2025-07-31 13:08:11 +08:00
芋道源码 7741e4b153
!158 fix: 修正顶部导航栏扫一扫图标
Merge pull request !158 from 卢越/master
2025-07-28 14:15:01 +00:00
卢越 e64a9d2e65 fix: 完善扫一扫功能 2025-07-28 16:49:01 +08:00
卢越 030f7525f8 fix: 修正顶部导航栏扫一扫图标 2025-07-28 14:07:51 +08:00
芋道源码 dda213fc9b
!157 商城装修功能优化
Merge pull request !157 from 卢越/master
2025-07-25 12:37:58 +00:00
卢越 8226d4274f add: 添加一个字体图标:扫一扫 2025-07-25 16:58:59 +08:00
卢越 6313fc5c91 fix: 【商城装修】完善导航栏搜索框的装修,同时添加扫一扫功能 2025-07-25 13:57:34 +08:00
卢越 ee0d5d9c1e fix: 【商城装修】组件背景图片无法正常显示的问题 2025-07-22 16:54:33 +08:00
YunaiV ccc7b7ff3c fix:分销提现到银行卡时,缺少 userAccount 的问题 2025-07-21 19:56:03 +08:00
YunaiV 5d5a4ba39c fix:分销提现到支付宝余额时,缺少 userAccount 的问题 2025-07-21 19:54:38 +08:00
芋道源码 4a26132b8f
Merge pull request #24 from MaizaLin/fix_bug
修复微信环境下获取完用户信息跳转的用户登录&绑定地址
2025-07-20 15:37:01 +08:00
芋道源码 7ce2f12a95
!154 feat(order): 物流信息中增加电话号码高亮和点击功能
Merge pull request !154 from binny1024/dev-express-callphone
2025-07-20 07:29:24 +00:00
芋道源码 a2f3c598fc
!156 fix: 修复directory参数丢失导致无法创建目标文件目录的问题。
Merge pull request !156 from macro/fix/20250719-uploadFiles
2025-07-20 07:26:10 +00:00
YunaiV 8e31d553e3 (〃'▽'〃) v2.6.1 发布:Vben5 + antd 管理后台支持 BPM 工作流、CRM 客户管理 2025-07-19 19:40:07 +08:00
macro 367810086a
update sheep/components/s-uploader/choose-and-upload-file.js.
修复上传文件时,directory丢失导致创建不了目标文件目录的问题。

Signed-off-by: macro <16534421@qq.com>
2025-07-18 16:29:35 +00:00
YunaiV 1b936a3988 fix:修复微信公众号分享时,缺少 updateAppMessageShareData 导致 url 无法设置 2025-07-16 23:48:52 +08:00
YunaiV 08fa88129e Merge remote-tracking branch 'origin/master' 2025-07-15 09:09:04 +08:00
YunaiV d768df328a fix:增加支付宝 App 沙箱支付时,需要设置 EnvUtils.setEnv 的提示 2025-07-15 09:08:56 +08:00
芋道源码 9696c309b7
!151 数据库雪花 id,导致的商品规格无法选择
Merge pull request !151 from 陶政阳/金开科技
2025-07-06 09:12:17 +00:00
xubinbin0303 da36d5a0fe feat(order): 物流信息中增加电话号码高亮和点击功能
- 新增 HighlightNumberText组件用于高亮和处理电话号码
- 在物流信息页面引入该组件并实现电话拨打功能
- 优化物流信息展示界面,调整样式和布局
2025-07-02 09:08:14 +08:00
linyunqi 654b85eba0 修复微信环境下获取完用户信息跳转的用户登录&绑定地址 2025-06-22 15:55:14 +08:00
puhui999 324949dbc2 feat: 商城轮播图装修组件增加高度配置 2025-06-18 18:11:00 +08:00
YunaiV 80d500ed00 Merge remote-tracking branch 'origin/master' 2025-06-15 18:28:14 +08:00
YunaiV 963b3801f5 fix:分类页打开的效果 https://t.zsxq.com/pRbKn 2025-06-15 18:28:04 +08:00
binny1024 6c6f8c84aa !153 feat(utils): 添加文本宽度测量工具函数
* feat(utils): 添加文本宽度测量工具函数
2025-06-15 07:45:51 +00:00
芋道源码 032c32b337
!152 fix:优化用户avatar显示逻辑(用户未设置avatar则使用系统default_avatar显示)
Merge pull request !152 from SuchJack/master
2025-06-15 07:43:52 +00:00
SuchJack e9bf76d506 fix:优化用户avatar显示逻辑(用户未设置avatar则使用系统default_avatar显示) 2025-06-01 18:39:35 +08:00
taozhengyang a871ea7a5f 数据库雪花 id,导致的商品规格无法选择 2025-05-25 18:22:55 +08:00
wslyx 79e9540da5 fix(chat): 修复uni-easyinput在手机键盘弹出时,导致页面整体上移的问题
- 新增 OptimizeInput组件以替换 uni-easyinput
2025-05-24 20:32:22 +08:00
YunaiV 1f169a1ed1 (〃'▽'〃) v2.6.0 发布:优化项目结构,希望你会喜欢 2025-05-23 13:01:26 +08:00
芋道源码 658ef561f8
!149 fix:响应拦截器"showError为false"时弹报错信息,条件不符逻辑。
Merge pull request !149 from SuchJack/master
2025-05-19 12:47:06 +00:00
SuchJack 6b7487de46 fix:"showError为false"时弹报错信息,条件不符逻辑。 2025-05-15 17:58:15 +08:00
YunaiV 071074b96c (〃'▽'〃) v2.5.0 发布:又熬过 30 个夜,头发还在 2025-05-13 20:23:19 +08:00
芋道源码 533f7df7ad
!147 fix:下拉刷新"我的拼团订单列表"页时重置分页对象,否则会不断叠加重复的拼团订单。
Merge pull request !147 from SuchJack/master
2025-05-12 14:46:04 +00:00
YunaiV dd71ba7d9e 客服聊天列表使用scroll-view原生组件,移除 z-paging 组件 2025-05-12 22:18:50 +08:00
YunaiV f6e74b12cb Merge branch 'develop' of https://gitee.com/yudaocode/yudao-mall-uniapp
# Conflicts:
#	pages/chat/components/messageList.vue
#	uni_modules/z-paging/changelog.md
#	uni_modules/z-paging/components/z-paging-cell/z-paging-cell.vue
#	uni_modules/z-paging/components/z-paging-empty-view/z-paging-empty-view.vue
#	uni_modules/z-paging/components/z-paging-swiper-item/z-paging-swiper-item.vue
#	uni_modules/z-paging/components/z-paging-swiper/z-paging-swiper.vue
#	uni_modules/z-paging/components/z-paging/components/z-paging-load-more.vue
#	uni_modules/z-paging/components/z-paging/components/z-paging-refresh.vue
#	uni_modules/z-paging/components/z-paging/css/z-paging-main.css
#	uni_modules/z-paging/components/z-paging/js/modules/back-to-top.js
#	uni_modules/z-paging/components/z-paging/js/modules/common-layout.js
#	uni_modules/z-paging/components/z-paging/js/modules/data-handle.js
#	uni_modules/z-paging/components/z-paging/js/modules/i18n.js
#	uni_modules/z-paging/components/z-paging/js/modules/load-more.js
#	uni_modules/z-paging/components/z-paging/js/modules/nvue.js
#	uni_modules/z-paging/components/z-paging/js/modules/refresher.js
#	uni_modules/z-paging/components/z-paging/js/modules/scroller.js
#	uni_modules/z-paging/components/z-paging/js/modules/virtual-list.js
#	uni_modules/z-paging/components/z-paging/js/z-paging-constant.js
#	uni_modules/z-paging/components/z-paging/js/z-paging-enum.js
#	uni_modules/z-paging/components/z-paging/js/z-paging-main.js
#	uni_modules/z-paging/components/z-paging/js/z-paging-utils.js
#	uni_modules/z-paging/components/z-paging/z-paging.vue
#	uni_modules/z-paging/package.json
#	uni_modules/z-paging/readme.md
2025-05-12 21:26:31 +08:00
芋道源码 c17228b0d7
!146 客服聊天列表使用scroll-view原生组件,移除 z-paging 组件
Merge pull request !146 from puhui999/dev
2025-05-12 13:25:49 +00:00
YunaiV 5071816ea0 feat:【MALL 商城】商城分佣提现,完成对微信转账(小程序)的对接 2025-05-11 12:39:23 +08:00
YunaiV 62d1f4f273 feat:【MALL 商城】商城分佣提现,初步对接成功微信支付 2025-05-11 09:08:18 +08:00
YunaiV 098057b2de feat:【MALL 商城】佣金提现,优化字段,以及支持更多 API 自动打款 2025-05-10 10:07:27 +08:00
SuchJack 12ff9bb4d1 fix:下拉刷新"我的拼团订单列表"页时重置分页对象,否则会不断叠加重复的拼团订单。 2025-05-08 22:28:24 +08:00
puhui999 af596bcc70 【代码优化】客服消息日期位置调整 2025-05-08 18:16:18 +08:00
puhui999 e0f7ec3629 【功能完善】客服消息输入键盘弹起适配 2025-05-08 17:49:12 +08:00
puhui999 9f1cd4c3a7 【功能完善】客服底部安全区适配 2025-05-08 17:31:14 +08:00
puhui999 8456d0a8b3 【功能完善】客服聊天查看最新消息和回到底部 2025-05-08 17:01:57 +08:00
puhui999 13f674ab4b 【功能完善】客服聊天历史数据加载和回到顶部 2025-05-08 16:35:01 +08:00
puhui999 c149a54e83 【代码优化】客服聊天 2025-05-08 16:18:27 +08:00
puhui999 7e6a3bbad3 【代码优化】移除 z-paging 组件使用 uniapp 原生 scroll-view 实现区域滚动 2025-05-07 18:05:48 +08:00
YunaiV 1948aae067 fix:支付结果页,state.orderId => state.id 2025-05-05 22:06:50 +08:00
YunaiV 4d308078fa reactor:【INFRA】文件上传 api,增加 directory 参数,去除 path 参数,并支持按照日期分目录、文件名不再使用 sha256 而是时间戳 2025-05-02 20:59:30 +08:00
YunaiV db60915de6 fix:【商城】店铺装修-标题栏:之前 bgImgUrl 不对的问题(彻底修复) 2025-05-01 12:07:01 +08:00
YunaiV 8ef7c89735 feat:【商城】店铺装修-标题栏:增加 skew 标题栏。同时,代码回到 titleStyles、descStyles 风格 2025-05-01 10:24:52 +08:00
YunaiV 500b377b4f feat:【商城】店铺装修-优惠劵:展示优惠、剩余信息 2025-05-01 08:53:12 +08:00
YunaiV ad7de47a47 feat:【商城】店铺装修-优惠劵:展示优惠、剩余信息 2025-05-01 08:51:50 +08:00
YunaiV c95f621144 reactor:getSystemInfoSync =》 getWindowInfo,解决 device 2025-04-29 20:38:50 +08:00
YunaiV 9582d5944e update:lime-painter 从 1.9.5 升级到1.9.6.6 2025-04-29 20:38:14 +08:00
YunaiV 82aada5ecd reactor:osName 的获取,从 getSystemInfoSync 迁移到 getDeviceInfo
reactor:pixelRatio/windowHeight/windowWidth 的获取,从 getSystemInfoSync 迁移到 getWindowInfo
2025-04-29 20:13:18 +08:00
YunaiV 04bb5e65d3 reactor:platform 的获取,从 getSystemInfoSync 迁移到 getAppBaseInfo 2025-04-29 19:58:27 +08:00
YunaiV 90e22374ee fix:refreshToken 接口的 showLoading 拼成了 loading 问题 2025-04-29 19:27:48 +08:00
YunaiV 3e7fd5277c reactor:优化前端直传,支持 H5 2025-04-29 19:03:06 +08:00
YunaiV 8c821bf8ad fix:解决微信小程序上传失败的问题(开启 es5 转换、postcss) 2025-04-29 14:00:28 +08:00
YunaiV 5da0382a25 reactor:解决微信小程序的「代码质量」在「JS 文件」提示:主包内,不应该存在主包未使用的 JS 文件 2025-04-29 12:07:11 +08:00
YunaiV f646458b15 reactor:useWebSocket.js 挪到 chat 包下,有利于分包 2025-04-29 11:57:38 +08:00
YunaiV 68ed6e1810 update:z-paging from 2.7.11 => 2.8.6 2025-04-29 11:53:42 +08:00
YunaiV c7d101cd05 fix:商品不存在时,不展示 empty 的情况 2025-04-29 11:47:50 +08:00
YunaiV 49dbce22d1 reactor:将 getWeekTimes、getMonthTimes 抽到 utils 统一复用 2025-04-28 23:30:24 +08:00
YunaiV 349a4bb0a9 feat:【MALL】App 售后筛选时,增加 status 过滤 2025-04-28 23:17:16 +08:00
YunaiV 600a29b50b feat:处理 detail-activity-tip.vue 的 type 判断 2025-04-28 23:05:16 +08:00
YunaiV 791e99057c feat:H5 访问域名,改为 .env 配置 2025-04-28 23:01:34 +08:00
芋道源码 20e7593e14
!144 增加拒绝用户协议与隐私协议,补充相关逻辑 满足部分应用分发市场的审核需求
Merge pull request !144 from huppygo/master
2025-04-28 14:57:49 +00:00
YunaiV e2fb99ae60 reactor:移除 countDown 组件,使用 s-count-down 组件 2025-04-28 22:54:42 +08:00
YunaiV 01adbff523 reactor:utils 挪到 helper 目录下 2025-04-28 22:47:00 +08:00
YunaiV dfe4a33e2d reactor:consts 挪到 helper 目录下 2025-04-28 22:43:30 +08:00
YunaiV 997404dfb4 feat:处理部分 todo 逻辑 2025-04-28 21:17:18 +08:00
YunaiV 08e7b32fc3 reactor:清理部分 TODO 2025-04-28 09:50:50 +08:00
YunaiV 93ce26f6be bugfix: 修复购物车页面刷新时底部导航消失的问题 2025-04-28 09:25:13 +08:00
YunaiV 158b0aa435 Merge branch 'master' of https://github.com/yudaocode/yudao-mall-uniapp 2025-04-28 09:24:34 +08:00
芋道源码 70a69bdfe6
Merge pull request #20 from ForeverNewbee/bugfix-cart-refresh-no-tabbar
bugfix: 修复购物车页面刷新时底部导航消失的问题
2025-04-28 09:24:26 +08:00
YunaiV 4b3c699270 sync:微信公众号网页授权登录,自动获取登录入口的准确路径 7a8298aeed 2025-04-28 00:23:33 +08:00
YunaiV da9588c263 sync:单词修改,授权弹框修复 decd3a5108
sync:小程序、h5登录弹框样式修改 4aaf21084b
2025-04-28 00:22:41 +08:00
YunaiV 00f57159b4 sync:修复上传删除文件问题 00ac44d1df 2025-04-28 00:17:43 +08:00
YunaiV ff9211e557 sync:富文本默认行高问题修复 97d31ae859
sync: 修复富文本显示问题 6976caf8e8
2025-04-28 00:10:57 +08:00
YunaiV e9ebe236e6 sync:订单结果页添加支付询问对话框,版本获取方法修改,更新README 024f311235 2025-04-28 00:01:33 +08:00
YunaiV 9e96df4651 sync:修复app端video层级过高问题 839a01f8b5 2025-04-27 23:53:20 +08:00
YunaiV c10a1c9adc sync:每次进入购物车页面请求列表 b858147579 2025-04-27 23:51:08 +08:00
YunaiV 39e04ae78d sync:购物车针对已存在商品被下架 样式 逻辑修改 c752b19acd 2025-04-27 23:48:43 +08:00
YunaiV 5dfa496998 sync:获取商品图片实时高度判断条件修改 4ac6f6c0d3 2025-04-27 21:52:27 +08:00
YunaiV b62f2ccd21 sync:问题修改 42c75bb61a 2025-04-27 21:47:39 +08:00
YunaiV b532985e84 sync:修改空置页提示 f470af9d4d 2025-04-27 21:42:39 +08:00
YunaiV 796f3e9eba sync:商品列表瀑布流问题修改 c2cd0156f3 2025-04-27 13:03:50 +08:00
YunaiV 352a61c8f5 sync:fix:分类页不同机型定位高度适配 fc8524c948 2025-04-27 12:45:15 +08:00
YunaiV 4fc6943391 sync:分类页样式重构 6cf9a35704 2025-04-27 12:42:14 +08:00
YunaiV 70f9c004ea fix:拼团分享海报时,拼团人数不正确的问题 2025-04-26 19:18:15 +08:00
YunaiV 50973a9a30 Merge branch 'develop' of https://gitee.com/yudaocode/yudao-mall-uniapp 2025-04-26 18:57:49 +08:00
芋道源码 42f10e1b8f
!145 修复了一些问题
Merge pull request !145 from puhui999/dev
2025-04-26 10:57:26 +00:00
YunaiV 0c2adc80f6 V2.4.2 发布~ 2025-04-12 12:54:50 +08:00
puhui999 336e0efb8d 【缺陷修复】修复拼团邀请小程序码位置 2025-04-01 17:56:17 +08:00
puhui999 36b4880b26 【缺陷修复】修复客服对话框中,不管先点表情还是+号~弹起后,表情和+切换,样式偏移了找不到内容的问题。 2025-04-01 17:29:47 +08:00
huppygo 8914d65fbb 增加拒绝用户协议与隐私协议,补充相关逻辑 满足部分应用分发市场的审核需求
拒绝不能点击发送短信,点击登录提示拒绝协议
2025-03-22 12:02:00 +08:00
芋道源码 f9e30d1fbc
!143 修复支付宝app支付功能正常,补充微信app支付逻辑实现功能待测试
Merge pull request !143 from huppygo/master
2025-03-15 14:22:31 +00:00
huppygo e02db4339e
update sheep/platform/pay.js.
Signed-off-by: huppygo <huppygo@qq.com>
2025-02-12 01:23:31 +00:00
huppygo 0c88f6c2af
update sheep/platform/pay.js.
Signed-off-by: huppygo <huppygo@qq.com>
2025-02-11 16:00:36 +00:00
YunaiV d91d8eb2db V2.4.1 发布~ 2025-02-09 12:41:51 +08:00
ForeverNewbee 8a0a39b3db
bugfix: 修复购物车页面刷新时底部导航消失的问题 2025-02-04 12:06:23 +00:00
YunaiV 3c403c424a Merge branch 'develop' of https://gitee.com/yudaocode/yudao-mall-uniapp 2025-01-24 20:19:33 +08:00
芋道源码 730025128a
!135 fix 了一些问题
Merge pull request !135 from puhui999/dev
2025-01-23 04:30:59 +00:00
puhui999 5275b64e60 【缺陷修复】商城主题对齐后端。修复主题切换不生效的问题。 2025-01-22 17:32:45 +08:00
puhui999 ca85a34e49 【功能完善】分销团队佣金分转元 2025-01-22 11:48:01 +08:00
puhui999 d46cfd3943 【功能完善】TabBar 角标完善 2025-01-22 10:56:33 +08:00
puhui999 a382a1a876 【缺陷修复】decryptSpm 移除 activity_id 解析,统一为 id 2025-01-22 10:19:42 +08:00
puhui999 7fc548e161 【升级】mp-html from 2.4.1 to 2.5.0 2025-01-20 17:58:34 +08:00
YunaiV ea28ca6875 【功能修复】App.vue 延迟 200 毫秒,避免 {errMsg: 'hideTabBar:fail not TabBar page'}(来自 cursor 修复建议) 2025-01-19 08:25:15 +08:00
芋道源码 11e83df02f
!134 缺陷修复
Merge pull request !134 from puhui999/dev
2025-01-17 12:19:26 +00:00
puhui999 039f05ce23 【代码优化】特殊:处理分销用户绑定失败的提示 2025-01-10 16:20:58 +08:00
puhui999 c1f2117e48 【代码优化】分销邀请 2025-01-09 15:37:18 +08:00
puhui999 5cc6d99891 【代码优化】分享优化 2025-01-09 15:14:46 +08:00
puhui999 a2ba10f796 【缺陷修复】su-tabbar-item 选中或未选中标签属性名称和父组件 su-tabbar 保持一致 2025-01-07 17:21:34 +08:00
puhui999 9ce6e63ee9 【缺陷修复】绑定推广员 2025-01-07 16:43:15 +08:00
puhui999 94f8883bd8 【缺陷修复】分享信息覆盖了海报信息导致小程序码获取失败的问题 2025-01-07 16:30:07 +08:00
puhui999 53899380c3 【缺陷修复】s-layout 组件中使用 onMounted 监听页面加载,解决 H5 中分享信息获取失败的问题 2025-01-07 16:08:57 +08:00
YunaiV bc0e731fc3 【功能修复】商品分类界面,有 categoryId 匹配不对的问题 2025-01-04 09:10:58 +08:00
YunaiV 515b8bb00f 【功能修复】申请售后的图片从 images 修复为 applyPicUrls 2025-01-04 09:00:52 +08:00
YunaiV 9f7015090d 【功能修复】s-layout 部分属性缺失时,首页无法展示的问题 2025-01-04 08:53:55 +08:00
芋道源码 139ec62e9a
!133 fix bug category里右侧列表到底刷新功能
Merge pull request !133 from 壁虎在家/master
2025-01-04 00:42:19 +00:00
18602180658 4b9ef94741 fix bug: 子组件内不应该用页面的onReachBottom。导致小程序下无效 2025-01-01 02:25:48 +08:00
YunaiV 46ba5e6aef V2.4.0 发布~ 2024-12-31 12:45:10 +08:00
芋道源码 75d0b004dc
!131 【缺陷修复】购物车提交的校验、bgStyle 样式
Merge pull request !131 from puhui999/dev
2024-12-19 12:39:47 +00:00
puhui999 52cfca1ca1 【缺陷修复】s-layout bgStyle 样式 2024-12-19 16:23:35 +08:00
puhui999 34be99fccb 【缺陷修复】购物车提交时的配送方式校验 2024-12-19 15:45:32 +08:00
YunaiV 164589f634 【代码评审】购物车提交的校验、bgStyle 样式 2024-12-11 13:08:55 +08:00
芋道源码 39869cd92d
!129 【缺陷修复】购物车提交前校验
Merge pull request !129 from puhui999/dev
2024-12-11 05:01:19 +00:00
puhui999 a863300ae3 【缺陷修复】s-layout bgStyle 样式对齐 2024-12-09 15:05:44 +08:00
puhui999 8ac9f2714f 【缺陷修复】购物车同时结算两个商品,一个仅支持门店自提,一个仅支持快递发货,就会无法结算 2024-12-09 11:57:00 +08:00
芋道源码 dcd63dd54e
!128 【缺陷修复】
Merge pull request !128 from puhui999/dev
2024-12-07 02:16:23 +00:00
puhui999 25fc972833 【缺陷修复】自己查看自己的分享时移除 shareId 2024-12-06 15:10:14 +08:00
puhui999 cde0de43ff 【缺陷修复】分销订单查询状态携带 2024-12-06 15:05:14 +08:00
puhui999 bfde5df0cd 【缺陷修复】异步绑定推广员失败的问题 2024-12-06 14:37:41 +08:00
puhui999 b92bf484df 【缺陷修复】商城装修: 顶部导航栏-文字链接支持 2024-12-06 11:26:53 +08:00
puhui999 1b02d0cefa Merge remote-tracking branch 'yudao/develop' into dev 2024-12-05 17:50:53 +08:00
puhui999 b24af1f71f 【缺陷修复】商城装修: TitleBar 样式同步 2024-12-05 15:38:40 +08:00
YunaiV 951a52d37b 【代码评审】积分界面 2024-12-01 18:19:12 +08:00
芋道源码 9616e660a6
!126 分享相关完善
Merge pull request !126 from puhui999/master
2024-12-01 08:58:39 +00:00
YunaiV da81f6df7d 【功能完善】商城:优化分销的界面 2024-12-01 16:57:54 +08:00
puhui999 c48951df8c 【功能修复】收款码上传问题 2024-11-29 15:52:41 +08:00
puhui999 1407350bf7 【功能完善】商品评论上传图片 2024-11-29 15:43:05 +08:00
puhui999 4c4134389e 【功能完善】小程序支持转发到朋友圈 2024-11-29 12:44:42 +08:00
puhui999 f98b1a2bea 【功能完善】小程序 forward 转发 2024-11-29 12:21:31 +08:00
puhui999 1ff8890d58 【功能完善】分享赚分享 2024-11-29 11:10:37 +08:00
puhui999 7be216ea4b 【功能完善】秒杀活动分享 2024-11-29 10:45:58 +08:00
puhui999 530410fcf0 【功能完善】积分商城分享 2024-11-29 10:29:31 +08:00
YunaiV 29a5024468 Merge branch 'develop' of https://gitee.com/yudaocode/yudao-mall-uniapp
# Conflicts:
#	pages/activity/point/list.vue
2024-11-25 20:37:32 +08:00
YunaiV cb8431fbd4 Merge remote-tracking branch 'origin/develop' into develop 2024-11-20 09:18:57 +08:00
YunaiV 6900eb1909 【功能完善】商城:售后退货,已经完成 2024-11-20 09:18:50 +08:00
芋道源码 7793a69b9d
!125 【功能完善】商城客服
Merge pull request !125 from puhui999/master
2024-11-19 01:12:35 +00:00
puhui999 7017561242 Merge remote-tracking branch 'yudao/develop' 2024-11-18 17:32:38 +08:00
puhui999 0cdaaac0ed 【功能完善】商城:客服消息查询 pageSize 改 limit 2024-11-18 17:19:24 +08:00
puhui999 18aa26dcae 【功能完善】商城:客服消息样式调整 2024-11-18 16:54:55 +08:00
YunaiV 87c6e967f2 【功能完善】商城:客服消息改为游标查询,消息 JSON 化 2024-11-10 18:53:26 +08:00
YunaiV 01c2dfa30d Merge branch 'develop' of https://gitee.com/yudaocode/yudao-mall-uniapp into develop 2024-11-10 18:50:46 +08:00
YunaiV 1978484071 Merge branch 'master' of https://gitee.com/yudaocode/yudao-mall-uniapp 2024-11-10 18:50:11 +08:00
芋道源码 68680d0d0e
!124 【功能完善】商城:客服消息改为游标查询,消息 JSON 化
Merge pull request !124 from puhui999/master
2024-11-10 10:49:46 +00:00
puhui999 92e89bae34 Merge remote-tracking branch 'refs/remotes/yudao/develop' 2024-11-10 18:11:58 +08:00
puhui999 94e0dd4f5d 【功能完善】商城:客服消息改为游标查询,消息 JSON 化 2024-11-10 17:56:58 +08:00
芋道源码 9a225c6ba5
!123 【功能修复】修复分佣商品界面预计佣金一直显示“计算中”的问题;
Merge pull request !123 from 卢越/master
2024-11-09 12:55:34 +00:00
卢越 6c80e36508 【功能修复】修复分佣商品界面预计佣金一直显示“计算中”的问题; 2024-11-07 17:00:56 +08:00
芋道源码 cf88789eef
!119 修复在first_two模式的分类页面里商品名字无法显示的问题
Merge pull request !119 from 杨宇庆/N/A
2024-11-01 12:47:56 +00:00
芋道源码 98b3f1fc67
!118 pageNo: state.pagination.pageSize,改为 pageNo: state.pagination.pageNo,
Merge pull request !118 from yuannuoxin/N/A
2024-11-01 12:46:26 +00:00
芋道源码 a09e171bf7
!117 【轻量级 PR】:【修复】 积分商城:商品列表加载更多异常
Merge pull request !117 from Super Junior/fix_loadmore
2024-11-01 12:44:57 +00:00
杨宇庆 5ee9c18f4c
fix: 修复在first_two模式的分类页面里商品名字无法显示的问题
Signed-off-by: 杨宇庆 <hiyyq@qq.com>
2024-11-01 07:11:58 +00:00
yuannuoxin b8132062ea
pageNo: state.pagination.pageSize,改为 pageNo: state.pagination.pageNo,
Signed-off-by: yuannuoxin <3339694043@qq.com>
2024-10-30 05:06:09 +00:00
= 35c143039c Fix: 积分商城:商品列表加载更多异常 2024-10-28 20:25:50 +08:00
芋道源码 f94df74591
!116 【功能修复】修复真机环境下的图片上传问题
Merge pull request !116 from 卢越/master
2024-10-28 11:14:29 +00:00
卢越 e9b9ac1f7a 【功能修复】修复真机环境下的图片上传问题 2024-10-28 17:04:57 +08:00
YunaiV d916fe5c80 README 更新:单体、微服务的地址 2024-10-28 09:59:47 +08:00
YunaiV 315b5b852d README 更新:单体、微服务的地址 2024-10-28 09:58:22 +08:00
YunaiV 0213a4b88a 【功能修复】领取优惠劵在 validityType = 1 时(固定日期),展示不正确的问题 2024-10-27 20:23:29 +08:00
YunaiV 4bdc82786e 【功能修复】s-title-block 的 fontWeight 多加了 px 像素 2024-10-27 18:22:47 +08:00
YunaiV f36b183407 【功能优化】签到的样式 2024-10-27 18:17:36 +08:00
芋道源码 113aead37b
!113 【优化】当原价高于售价时,才显示划线价格
Merge pull request !113 from 杨宇庆/N/A
2024-10-27 08:34:33 +00:00
芋道源码 90b6ae7d40
!114 【修复】当无优惠价格时,商品无法显示的问题
Merge pull request !114 from 杨宇庆/N/A
2024-10-27 08:34:04 +00:00
杨宇庆 959fe83d72
【修复】当无优惠价格时,商品无法显示的问题
Signed-off-by: 杨宇庆 <hiyyq@qq.com>
2024-10-16 16:51:52 +00:00
杨宇庆 f784d411b6
【优化】当原价高于售价时,才显示划线价格
Signed-off-by: 杨宇庆 <hiyyq@qq.com>
2024-10-16 16:39:30 +00:00
YunaiV c8f9043869 【功能修复】积分商城,商品不支持下拉展示更多 2024-10-13 21:41:25 +08:00
YunaiV 789ec69933 【功能优化】订单确认页,增加按照顺序自动选择物流方式,替代原有只选择“快递配送”的方式 2024-10-13 21:30:14 +08:00
YunaiV 4dd6e82309 Merge branch 'master' of https://gitee.com/yudaocode/yudao-mall-uniapp into develop 2024-10-13 21:08:13 +08:00
芋道源码 3051aed6b2
!112 【优化】1.分销中心用户卡片样式没有居中问题 2.推广排行榜周排行、月排行切换样式留白问题
Merge pull request !112 from FN/FIX-FN-241013
2024-10-13 13:07:55 +00:00
FN 8e50c5da49 【优化】1.分销中心用户卡片样式没有居中问题 2.推广排行榜周排行、月排行切换样式留白问题 2024-10-13 14:55:58 +08:00
YunaiV fd10210972 【新增功能】添加微信转账到零钱提现功能 2024-10-13 12:42:08 +08:00
芋道源码 c399bb2373
!109 【新增功能】添加微信转账到零钱提现功能
Merge pull request !109 from 痴货/jh-wxzz
2024-10-13 04:40:20 +00:00
YunaiV 9451092335 【修复】部分界面的 pageSize = 1,导致无法上啦刷新 2024-10-12 20:23:26 +08:00
YunaiV fd33176222 Merge branch 'develop' of https://gitee.com/yudaocode/yudao-mall-uniapp 2024-10-12 20:22:46 +08:00
芋道源码 290ee5db04
!110 【修改BUG】修改分销记录只显示1条记录的问题
Merge pull request !110 from 痴货/jh-bug
2024-10-12 12:16:13 +00:00
痴货 07b630601a 【功能完善】积分商城:修改每页显示商品数量 2024-10-08 21:37:08 +08:00
痴货 b59c41c5f8 【功能优化】增加转账回调接口 2024-10-08 21:30:46 +08:00
痴货 a4ec421412 微信支付未完成 2024-10-08 21:30:37 +08:00
YunaiV 0961aa610b 【版本发布】2.3.0 发布~ 2024-10-07 15:28:12 +08:00
YunaiV 545d4259c4 Merge branch 'develop' of https://gitee.com/yudaocode/yudao-mall-uniapp 2024-10-07 15:23:47 +08:00
YunaiV 1ffa754dbe 【功能完善】纯积分兑换时,直接跳转到订单详情 2024-10-04 19:17:40 +08:00
芋道源码 96d86f237d
!108 【功能完善】商城: 积分商城
Merge pull request !108 from puhui999/master
2024-10-04 10:38:03 +00:00
puhui999 8e64ecebd1 Merge remote-tracking branch 'yudao/develop' 2024-10-04 14:00:11 +08:00
puhui999 98f912f113 【功能完善】积分商城:完善订单相关信息展示 2024-10-04 13:58:41 +08:00
puhui999 732aa7686c 【功能完善】积分商城:完善订单相关信息展示 2024-10-04 13:30:08 +08:00
puhui999 b2e40e7011 【功能完善】积分商城:完善订单相关信息展示 2024-10-04 12:36:18 +08:00
puhui999 d7a18f9c01 【功能完善】积分商城:完善积分+价格显示样式 2024-10-02 15:40:09 +08:00
puhui999 c5307513cc 【功能完善】积分商城:完善积分+价格显示样式 2024-10-02 15:02:22 +08:00
芋道源码 a5d073e798
!107 【功能完善】商城: 完善积分商城
Merge pull request !107 from puhui999/master
2024-10-02 07:00:43 +00:00
puhui999 9639c2f270 【功能完善】积分商城:完善积分+价格显示样式 2024-10-02 14:56:21 +08:00
puhui999 cf6b26e565 Merge remote-tracking branch 'yudao/develop'
# Conflicts:
#	pages.json
#	pages/activity/point/list.vue
2024-10-02 14:55:53 +08:00
puhui999 9171275d3d 【功能完善】积分商城:完善积分+价格显示样式 2024-10-02 14:53:46 +08:00
puhui999 2a2ce2eff6 【功能完善】积分商城:完善活动详情页样式 2024-10-02 14:30:18 +08:00
YunaiV b6635a5d5c 【功能优化】WebSocket 使用 refreshToken 认证,解决无法刷新访问令牌的问题 2024-10-02 14:19:23 +08:00
puhui999 da4cd2238e 【功能完善】积分商城:完善活动列表布局切换 2024-10-02 12:15:53 +08:00
puhui999 fb3120d818 【功能完善】积分商城:完善活动列表 2024-10-02 11:46:50 +08:00
YunaiV 5f496038c5 【代码评审】商城首页的 activity name 的展示 2024-10-01 19:00:19 +08:00
芋道源码 146f9ddc88
!104 【功能优化】秒杀详情页过期活动处理
Merge pull request !104 from 卢越/master
2024-10-01 10:57:24 +00:00
芋道源码 6abad0df4d
!106 【功能优化】确认收货添加提示确认,提交售后信息后直接返回到订单详情
Merge pull request !106 from Lcp/pr
2024-10-01 09:33:30 +00:00
YunaiV 47ac7d1fbc 【功能】下单页的优惠明细弹窗,不展示积分、优惠劵、会员折扣,因为它们已经单独展示了 2024-10-01 10:15:08 +08:00
YunaiV 25e4686e30 Merge branch 'develop' of https://gitee.com/yudaocode/yudao-mall-uniapp 2024-09-30 09:05:31 +08:00
Lcp b476305e30 【功能优化】确认收货添加提示确认 2024-09-29 16:44:01 +08:00
Lcp 518c898882 【功能优化】提交售后信息后直接返回到订单详情 2024-09-29 16:43:55 +08:00
YunaiV 09dc98db16 【代码评审】积分商城部分 2024-09-29 09:43:29 +08:00
芋道源码 ea98ba45ca
!105 【功能完善】积分商城
Merge pull request !105 from puhui999/master
2024-09-29 01:23:47 +00:00
YunaiV 2e572639b0 【功能优化】修改 h5 的 base 是 /,解决 https://gitee.com/yudaocode/yudao-mall-uniapp/issues/IATB5V 静态资源访问的问题 2024-09-29 09:22:29 +08:00
YunaiV 4a0ea9b43c 【功能优化】文件上传,默认使用 server 模式,兼容更多存储器的类型 2024-09-28 20:25:32 +08:00
puhui999 6d762c5367 【功能完善】积分商城:完善活动详情页面 2024-09-28 16:33:26 +08:00
puhui999 2349faa1bc 【功能完善】积分商城:完善活动详情页面 2024-09-28 16:24:57 +08:00
puhui999 4cdb595a2a 【代码优化】商城装修:修复布局为单列小图时价格显示方式不正确的问题 2024-09-28 15:39:03 +08:00
puhui999 5552236952 【功能完善】积分商城: 首页活动装修显示 2024-09-28 14:43:04 +08:00
puhui999 d2f516e1ae 【功能完善】积分商城 2024-09-28 11:37:22 +08:00
卢越 cb39246850 【功能优化】拼团、秒杀组件的SPU名称使用活动的 2024-09-27 19:58:00 +08:00
卢越 18484f7ee8 【功能优化】秒杀详情页过期活动处理 2024-09-27 16:46:09 +08:00
芋道源码 2ca7f48131
!103 【功能新增】现已支持前端文件直传到OSS服务
Merge pull request !103 from 卢越/master
2024-09-26 01:24:22 +00:00
卢越 f491e87d6a 【功能优化】简化代码 2024-09-26 09:08:57 +08:00
卢越 6f8e82e6cf 【功能优化】简化代码 2024-09-25 23:16:50 +08:00
卢越 6c95891476 【功能优化】简化代码 2024-09-25 08:25:20 +08:00
卢越 76e7ed8f25 【功能优化】文件直传优化 2024-09-24 19:14:52 +08:00
卢越 f6b2eb6925 默认使用服务端上传文件 2024-09-24 17:17:18 +08:00
卢越 c23e7ac3d0 【功能新增】现已支持前端文件直传到OSS服务器 2024-09-24 17:16:38 +08:00
YunaiV ca1926a732 【功能优化】支付:查询支付订单时,增加 sync 主动轮询,解决支付宝、微信存在延迟的问题 2024-09-24 13:00:46 +08:00
YunaiV a1995c88e8 【功能优化】支付:查询支付订单时,增加 sync 主动轮询,解决支付宝、微信存在延迟的问题 2024-09-24 09:04:57 +08:00
YunaiV 27260f4a75 【功能完善】商城:支付宝 wap 的接入 2024-09-23 09:55:07 +08:00
YunaiV 0c5ef4a085 Merge branch 'master' of https://gitee.com/yudaocode/yudao-mall-uniapp into develop 2024-09-23 09:27:29 +08:00
YunaiV 3dad154ca0 【功能优化】商城:调整满减送的数据返回 2024-09-21 14:30:27 +08:00
芋道源码 e727974806
!102 【优化】优化满减提示显示
Merge pull request !102 from 痴货/develop-mall
2024-09-21 03:19:24 +00:00
痴货 ab709267be 【优化】优化满减显示 2024-09-18 18:11:54 +08:00
岳琳红 51cd8908ec 修改部分页面 2024-09-18 17:04:23 +08:00
芋道源码 56182dfbe9
!99 删除微信支付的临时测试代码
Merge pull request !99 from 杨宇庆/N/A
2024-09-17 04:11:30 +00:00
YunaiV 6b6fa0c257 【功能完善】订单确认价格时,展示具体的优惠信息 2024-09-16 19:38:48 +08:00
YunaiV ff6769001d 【功能完善】商品列表,增加满减送提示(优化) 2024-09-16 19:30:56 +08:00
YunaiV 9df0194aeb 【功能完善】商品详情,增加满减送提示 2024-09-16 18:15:13 +08:00
YunaiV b741a1d148 【功能完善】商品列表,展示满减送、限时折扣的信息 2024-09-16 09:16:16 +08:00
YunaiV f03fcf1373 Merge branch 'master' of https://gitee.com/yudaocode/yudao-mall-uniapp into develop
# Conflicts:
#	pages/goods/index.vue
#	pages/order/addressSelection.vue
#	pages/order/confirm.vue
#	sheep/components/s-goods-column/s-goods-column.vue
2024-09-16 08:24:54 +08:00
YunaiV 5a66e71809 【代码优化】差满减活动时间外,from 岳琳红 26a18e7e9a 2024-09-16 08:18:32 +08:00
YunaiV e93322959e 【代码优化】差满减活动时间外,from 岳琳红 26a18e7e9a 2024-09-16 08:15:54 +08:00
芋道源码 4a8a92f323
!98 【功能修复】修复拼团的单买价格
Merge pull request !98 from 卢越/master
2024-09-15 13:15:07 +00:00
芋道源码 ba56d9843f
!101 批量优化或修改BUG
Merge pull request !101 from 痴货/develop-mall
2024-09-15 13:13:26 +00:00
YunaiV eb3d0ff6b0 【代码优化】商城:优化 /promotion/activity/list-by-spu-id 的逻辑 2024-09-15 17:59:42 +08:00
痴货 aa571f25f9 【功能】批量修改bug和功能 2024-09-15 17:32:29 +08:00
YunaiV 49e8b9f5c3 【功能完善】下单界面,接入门店自提的开关 2024-09-13 19:21:22 +08:00
杨宇庆 6af76a20f3
删除微信支付的临时测试代码
Signed-off-by: 杨宇庆 <hiyyq@qq.com>
2024-09-11 07:32:21 +00:00
芋道源码 55131bf0c3
!97 【功能优化】支付方式默认选中首个可用方式
Merge pull request !97 from Lcp/pr
2024-09-09 14:58:32 +00:00
卢越 f3c7387650 Merge remote-tracking branch 'origin/master' 2024-09-09 22:46:34 +08:00
卢越 b2d030f96b 【功能修复】修复拼团的单买价格 2024-09-09 22:46:24 +08:00
Lcp 97711bad3e 【功能优化】支付方式默认选中首个可用方式 2024-09-09 15:16:29 +08:00
YunaiV 29584a108b 【功能修复】秒杀装修重构 2024-09-09 09:22:06 +08:00
芋道源码 1432ec5996
!96 【功能修复】优惠券相关问题修正
Merge pull request !96 from 卢越/master
2024-09-09 01:21:15 +00:00
卢越 9521d4b557 【功能修复】秒杀装修重构 2024-09-08 11:39:28 +08:00
卢越 27af994667 【功能修复】修正订单优惠券选择列表中,可用和不可用优惠券的颜色 2024-09-08 10:27:46 +08:00
卢越 dbc192faa7 【功能修复】修正“暂无可用优惠券”字体颜色 2024-09-08 10:06:15 +08:00
卢越 5f8ed35521 【功能修复】订单确认页的可用优惠券数量统计 2024-09-08 09:56:21 +08:00
卢越 b42c151585 【功能修复】订单确认页的可用优惠券数量统计 2024-09-08 09:55:34 +08:00
YunaiV 3e2749672e CDN 使用 yudao 提供,解决不定期的 https 问题! 2024-09-07 16:15:43 +08:00
芋道源码 07098527ff
!90 修复在线客服页面的一些问题
Merge pull request !90 from 英狐/master
2024-09-07 06:29:26 +00:00
YunaiV cb29c2c0aa 【BUG修复】授权登录成功后,每次都要填昵称和重新上传头像。
如果用户已经有头像和昵称,则不要弹出上传头像和填写昵称的信息弹框。
2024-09-07 14:28:02 +08:00
芋道源码 4be2b441f5
!93 【BUG修复】授权登录成功后,每次都要填昵称和重新上传头像。
Merge pull request !93 from heyho/N/A
2024-09-07 06:26:11 +00:00
芋道源码 2cd4095035
!95 【优化】分销中心 => 我的团队 页面样式,布局优化、美化
Merge pull request !95 from heyho/master
2024-09-07 06:23:40 +00:00
YunaiV 80b2bb6f5e 【功能修复】商城:选择拼团 SKU 时,存在遮罩的问题 https://gitee.com/yudaocode/yudao-mall-uniapp/issues/IAMK4A 2024-09-07 14:22:44 +08:00
YunaiV 9b6d1a9a97 【功能优化】商城:价格计算时,返回可用 + 不可用的优惠劵 2024-09-07 12:06:10 +08:00
YunaiV 735dc8c373 【代码优化】商城:拼团装修重构 2024-09-06 21:02:59 +08:00
芋道源码 fdca229a77
!91 【综合修复】拼团装修重构、处理无法发布到微信小程序的问题
Merge pull request !91 from 卢越/master
2024-09-06 12:56:49 +00:00
heyho fd8f9a6857
分销中心 => 我的团队 页面样式,布局优化、美化
原版本头部结构错乱,显示有误。

Signed-off-by: heyho <heywsk@qq.com>
2024-09-06 08:32:42 +00:00
heyho 9f5f4816d3
【BUG修复】授权登录成功后,每次都要填昵称和重新上传头像。
如果用户已经有头像和昵称,则不要弹出上传头像和填写昵称的信息弹框。

Signed-off-by: heyho <heywsk@qq.com>
2024-09-06 01:25:25 +00:00
卢越 089e0197cd 【代码优化】拼团重构 2024-09-05 22:34:59 +08:00
YunaiV 81cd02bbee 【修复】小程序端银行卡提现,银行名称改为下拉选择 2024-09-05 20:08:48 +08:00
芋道源码 1cfd974fed
!92 【修复】小程序端银行卡提现,银行名称改为下拉选择。【新增接口】新增系统字典查询接口
Merge pull request !92 from heyho/master
2024-09-05 11:23:14 +00:00
卢越 82a82a4bed 【代码优化】接口地址修正 2024-09-05 17:11:39 +08:00
卢越 75e7e58b8d 【代码优化】代码格式化 2024-09-05 12:40:53 +08:00
卢越 b6dd2ae1eb 【代码优化】拼团活动逻辑优化 2024-09-05 11:32:30 +08:00
卢越 85441704cb 【代码优化】接口地址修正 2024-09-04 23:01:16 +08:00
heyho 089191e95d
【修复】小程序端银行卡提现,银行名称改为下拉选择。
由于后端接收的是银行名称参数会由配置字典进行解析,所以小程序端银行名称是不能让用户输入框手动输入的,需要查询后端字典配置进行显示和选择。提交的值是银行字典对应的value。

Signed-off-by: heyho <heywsk@qq.com>
2024-09-04 09:31:55 +00:00
heyho 9f33e44db0
【修复】小程序端银行卡提现,银行名称改为下拉选择。
由于后端接收的是银行名称参数会由配置字典进行解析,所以小程序端银行名称是不能让用户输入框手动输入的,需要查询后端字典配置进行显示和选择。提交的值是银行字典对应的value。

Signed-off-by: heyho <heywsk@qq.com>
2024-09-04 09:22:18 +00:00
heyho ed3d370de2
【新增接口】新增系统字典查询接口
通过字典类型查询字典数据

Signed-off-by: heyho <heywsk@qq.com>
2024-09-04 09:14:33 +00:00
卢越 36289d526c 【综合修复】改造拼团装修(仍需优化);处理无法发布到微信小程序的问题; 2024-09-03 15:36:18 +08:00
YunaiV 7268659193 【功能修复】拼团的单买价格展示不正确 2024-09-03 10:18:30 +08:00
PonyHu b1d6e1d687 【功能修复】在线客服消息列表窗体顶部导航的高度计算不准确,导致其被页面顶部标题栏遮住了一小部分;
【功能修复】在线客服不能收到服务端发来的最新消息(被判断为重置消息列表了);
【功能优化】在线客服不需要在滚动到底部时重置消息列表,因为没有必要;
2024-09-01 17:37:26 +08:00
YunaiV 89e86b84fe 增加优惠劵描述 2024-08-31 09:06:47 +08:00
芋道源码 77574e099a
!89 【功能修复】修复商城部分BUG
Merge pull request !89 from 卢越/master
2024-08-26 10:40:33 +00:00
卢越 5e6adced12 【功能修复】修复商品秒杀不选择规格提示库存不足的问题 2024-08-26 17:44:37 +08:00
卢越 4fef5c5f0e 清理TODO 2024-08-26 17:00:29 +08:00
卢越 1bff364d38 【功能修复】修复商城商品评论无法上传图片的问题 2024-08-26 16:56:48 +08:00
芋道源码 abf1235916
!88 【功能修复】修复客服聊天列表页点击商品卡片无法显示商品详情的问题
Merge pull request !88 from 卢越/master
2024-08-25 13:55:10 +00:00
卢越 2c4ad9f78a 【功能修复】修复客服聊天列表页点击商品卡片无法显示商品详情的问题 2024-08-25 19:42:04 +08:00
YunaiV 75c5203c90 【代码评审】商城的 todo 进一步说明 2024-08-25 14:33:27 +08:00
芋道源码 a514de68b3
!87 【功能修复】修复顶部导航栏装修无效的问题
Merge pull request !87 from 卢越/master
2024-08-25 06:29:58 +00:00
卢越 522591c486 【功能修复】修复顶部导航栏装修无效的问题 2024-08-25 14:06:57 +08:00
芋道源码 263eb74695
!86 【功能修复】修复订单结算页没有优惠券但是提示优惠券不存在的问题#IAK656:issue
Merge pull request !86 from 卢越/master
2024-08-23 12:04:02 +00:00
卢越 53920d2efa 【功能修复】优惠券匹配问题 2024-08-22 22:01:14 +08:00
卢越 a0f85bf157 【功能修复】修复没有优惠券时点击确定提示优惠券找不到的问题;现在订单结算页面切换地址或者配送方式会重新计算价格 2024-08-22 21:59:07 +08:00
芋道源码 adc521022f
!85 【代码优化】格式化配置文件
Merge pull request !85 from 卢越/master
2024-08-21 13:58:33 +00:00
卢越 356193aab5 【代码优化】格式化配置文件 2024-08-21 09:10:21 +08:00
芋道源码 07d167c624
!84 【功能优化】商品结算页现在可展示优惠券是否可用的原因
Merge pull request !84 from 卢越/master
2024-08-20 12:57:31 +00:00
卢越 01ee048a22 Merge remote-tracking branch 'origin/master' 2024-08-20 16:39:32 +08:00
卢越 512d87c6f2 【功能优化】商品结算页现在可展示优惠券是否可用以及是否可用的原因 2024-08-20 16:38:50 +08:00
芋道源码 af0643ce93
!83 【逻辑优化】现在活动库存可以真实显示
Merge pull request !83 from 卢越/master
2024-08-19 13:55:24 +00:00
卢越 9d333085cb 【逻辑优化】拼团库存使用总库存 2024-08-19 17:40:17 +08:00
卢越 d37b848307 【逻辑优化】更新活动的库存(拼团库存可能有逻辑问题) 2024-08-19 11:52:05 +08:00
芋道源码 28faf73c62
!82 【功能修复】修复拼团详情页的价格
Merge pull request !82 from 卢越/master
2024-08-18 05:32:29 +00:00
卢越 f10c9ec16c 【界面优化】优化装修秒杀列表的样式,补全关键信息 2024-08-18 13:03:52 +08:00
卢越 ef3a82ee14 【界面优化】优化装修拼团单列商品列表样式 2024-08-18 12:30:04 +08:00
卢越 47f4295316 【功能修复】修复拼团详情页的价格展示 2024-08-18 09:10:40 +08:00
卢越 8a6ec1cb02 【功能修复】修复拼团详情页的价格展示 2024-08-18 08:50:54 +08:00
芋道源码 edf158ec2c
!81 【功能修复】修复商品拼团和秒杀价格显示
Merge pull request !81 from 卢越/master
2024-08-16 12:22:26 +00:00
卢越 f4adfde90b 【功能修复】修复商品拼团和秒杀价格的显示 2024-08-16 17:29:09 +08:00
芋道源码 283449fc3d
!80 【功能修复】修复装修优惠券背景图片问题
Merge pull request !80 from 卢越/master
2024-08-15 13:43:47 +00:00
卢越 e66e1c6eac 【功能修复】修复优惠券排版问题和页面交互问题 2024-08-15 15:46:22 +08:00
卢越 ea8ea9e744 【功能优化】装修页面优惠券,如果每行两个优惠券,则让优惠券居中,而不是分散在两边 2024-08-15 14:51:15 +08:00
卢越 c8a95ef3ec 【功能修复】装修页面修复多个优惠券,第一个显示不全的问题 2024-08-15 14:27:27 +08:00
卢越 bf75dc2768 【功能修复】修复装修优惠券整体的背景图片问题;如果2列或者3排优惠券现在居中显示并且有间隔 2024-08-15 14:18:44 +08:00
YunaiV 0575bd56b0 Merge remote-tracking branch 'origin/master' 2024-08-15 09:24:49 +08:00
YunaiV 8154108fe9 【功能修复】拼团:未开启拼团的 SKU 需要禁用选择 2024-08-15 09:24:39 +08:00
芋道源码 7f1504214c
!77 删除无用代码
Merge pull request !77 from 卢越/master
2024-08-14 16:29:51 +00:00
芋道源码 c6b55dc90b
!78 购物车商品数量减为0时,删除错误
Merge pull request !78 from 邓飞鹏/master
2024-08-14 16:27:04 +00:00
芋道源码 ed7258b577
!79 完善拼团的剩余 TODO
Merge pull request !79 from puhui999/master
2024-08-14 16:12:22 +00:00
puhui999 6c0ba7d585 Merge remote-tracking branch 'refs/remotes/yudao/master' 2024-08-14 11:49:22 +08:00
puhui999 154a706af4 【代码优化】拼团:其它用户,打开团长的界面的相关操作实现 2024-08-14 11:46:59 +08:00
puhui999 fc396021c6 【代码优化】拼团:完善邀请拼团海报 2024-08-14 11:09:25 +08:00
puhui999 532503b65b 【代码优化】h5-router-mode: hash 改为 history 解决分享链接跳转失败 2024-08-14 11:08:55 +08:00
D b22cdae4fe 购物车商品数量减为0时,删除错误 2024-08-13 21:57:04 +08:00
卢越 42bcab7f28 删除无用输出 2024-08-13 12:29:02 +08:00
芋道源码 4db992d3fa
!76 【功能修复】修复部分装修背景图片问题
Merge pull request !76 from 卢越/master
2024-08-12 16:34:44 +00:00
卢越 e9a91b84fc 【代码优化】优化参数命名 2024-08-12 14:13:11 +08:00
卢越 e13eedc2c2 【功能修复】修复装修菜单导航背景图片问题 2024-08-12 13:57:25 +08:00
卢越 be5db900a0 【功能修复】修复标题栏装修背景图片问题 2024-08-12 13:06:35 +08:00
卢越 34274cbaa8 【功能修复】修复装修宫格导航背景图片问题 2024-08-12 11:03:39 +08:00
卢越 438897b9d3 【功能修复】修复用户卡券装修背景图片问题 2024-08-12 10:15:51 +08:00
卢越 58af434a90 【功能修复】修复用户资产装修背景图片问题 2024-08-12 10:05:39 +08:00
芋道源码 83daf7f7c2
!75 【功能修复】商城模板装修
Merge pull request !75 from 卢越/master
2024-08-12 01:28:07 +00:00
卢越 35c75338ee 【功能修复】修复用户订单卡片装修的背景图片问题 2024-08-11 14:59:08 +08:00
卢越 19d3cb33e2 【功能修复】修复装修用户卡片使用图片无效的问题 2024-08-11 14:03:20 +08:00
YunaiV 5feab41b3d 【功能修复】拼团:未开启拼团的 SKU 需要禁用选择 2024-08-10 11:36:02 +08:00
YunaiV 51ba185e81 【代码优化】进入分类页的时候,自动勾选左侧一级分类(比如从首页进入) 2024-08-10 10:25:51 +08:00
芋道源码 92b9820f20
!74 进入分类页的时候自动勾选左侧一级分类(比如从首页进入)
Merge pull request !74 from 卢越/master
2024-08-10 02:23:32 +00:00
卢越 cc9f31f8c3 进入分类页的时候自动勾选左侧一级分类 2024-08-09 22:06:35 +08:00
芋道源码 374ccf43fd
!71 修复一些 issues 提到的问题
Merge pull request !71 from puhui999/master
2024-08-09 13:38:49 +00:00
卢越 fd83bcf02a 进入分类页的时候自动勾选左侧一级分类 2024-08-09 21:26:29 +08:00
卢越 b093f99ab5 如果从主页点击某个分类进入分类页面,则自动选中左侧的一级分类 2024-08-09 20:16:56 +08:00
puhui999 e53cb63ce4 Merge remote-tracking branch 'refs/remotes/yudao/master' 2024-08-09 10:48:51 +08:00
puhui999 a9a522880d 【代码优化】修复秒杀活动详情页报错 2024-08-09 10:46:12 +08:00
puhui999 e7ba7a82e3 【代码优化】修复我的页面地址列表,点击地址卡片会返回上一页的 bug 2024-08-09 10:10:01 +08:00
芋道源码 7a3f98e5fc
!69 修复优惠券(类别型)功能
Merge pull request !69 from 卢越/master
2024-08-08 05:06:40 +00:00
YunaiV a9739208f8 Merge remote-tracking branch 'origin/master' 2024-08-08 13:05:00 +08:00
YunaiV bb1da04177 【代码优化】实现动态 terminal 终端类型的计算 2024-08-08 13:04:34 +08:00
YunaiV 15ce250c1b Merge branch 'develop' of https://gitee.com/yudaocode/yudao-mall-uniapp 2024-08-08 12:58:51 +08:00
芋道源码 f7ec1badf7
!66 feat(request): 实现动态Terminal功能。
Merge pull request !66 from Ordinary/master
2024-08-08 04:58:39 +00:00
芋道源码 2b5a06174f
!70 【代码优化】门店自提:h5 位置信息相关逻辑
Merge pull request !70 from puhui999/master
2024-08-08 04:56:46 +00:00
puhui999 1fc3537bf4 【代码优化】门店自提:h5 位置信息相关逻辑 2024-08-08 12:26:09 +08:00
卢越 e126981628 修复切换登录方式的时候,偶现登录面板无法弹出的问题 2024-08-08 09:56:21 +08:00
卢越 2eb1a1bf41 商品详情和购物车跳转到结算页面时传入商品分类编号,以便查询可用优惠券 2024-08-08 09:45:12 +08:00
YunaiV 3099c8c491 实现抖音小程序的兼容 2024-08-07 23:51:29 +08:00
YunaiV 1aa944e18c Merge branch 'develop' of https://gitee.com/yudaocode/yudao-mall-uniapp
# Conflicts:
#	pages/goods/index.vue
#	pages/order/confirm.vue
#	pages/order/detail.vue
2024-08-07 23:37:38 +08:00
芋道源码 e635b91bb5
!67 修复抖音小程序无法启动问题
Merge pull request !67 from undefined/master
2024-08-07 15:07:19 +00:00
YunaiV c7ea2f97ac 【代码评审】门店自提:相关代码的优化、以及 todo 评审 2024-08-07 22:09:15 +08:00
芋道源码 965b97dae7
!68 【新增】自提门店选择、积分选择
Merge pull request !68 from puhui999/master
2024-08-07 13:36:47 +00:00
puhui999 c78a849c96 【新增】订单详情:积分抵扣展示 2024-08-07 17:37:03 +08:00
puhui999 a2dec947a2 【新增】订单:积分选择 2024-08-07 17:36:29 +08:00
puhui999 aaad12c50e Merge remote-tracking branch 'refs/remotes/yudao/master'
# Conflicts:
#	pages/order/confirm.vue
2024-08-06 18:16:10 +08:00
puhui999 44052b22c3 【新增】订单详情:核销相关逻辑及组件 2024-08-06 18:11:49 +08:00
puhui999 196a1b2235 【优化】订单发货方式切换逻辑 2024-08-06 16:09:53 +08:00
puhui999 1bc75b2ff5 【新增】自提门店选择 2024-08-05 18:01:47 +08:00
puhui999 b98f848be1 【新增】自提门店选择 2024-08-05 18:00:11 +08:00
undefined ce81110bb3 实现抖音小程序显示(仅限显示) 2024-08-04 17:11:12 +08:00
Ordinary c9434b9ec9 feat(request): 实现动态Terminal功能。 2024-08-03 10:33:09 +08:00
YunaiV d1681ec2d5 V2.2.0 版本发布 2024-08-02 23:07:09 +08:00
YunaiV eae8f3c174 【代码优化】SYSTEM:微信小程序的订阅 2024-08-01 13:08:01 +08:00
YunaiV 7f5970d741 【代码优化】SYSTEM:微信小程序的订阅 2024-07-31 23:46:08 +08:00
YunaiV ececc6f67c 【代码优化】SYSTEM:微信小程序的订阅 2024-07-31 19:48:13 +08:00
芋道源码 77306a54e7
!65 【新增】订阅消息:拼团结果通知消息订阅
Merge pull request !65 from puhui999/master
2024-07-31 11:31:08 +00:00
puhui999 bd07762a10 【新增】订阅消息:拼团结果通知消息订阅 2024-07-31 16:22:51 +08:00
芋道源码 1b385d728a
!64 【新增】订阅消息:订单发货消息订阅
Merge pull request !64 from puhui999/master
2024-07-31 05:51:17 +00:00
puhui999 8b73d488ca 【新增】订阅消息:订单发货消息订阅 2024-07-31 12:51:49 +08:00
芋道源码 59c6613102
!63 【新增】订阅消息:订单发货、充值退款消息订阅
Merge pull request !63 from puhui999/master
2024-07-30 15:14:12 +00:00
puhui999 9add242540 【新增】订阅消息:订单发货、充值退款消息订阅 2024-07-30 17:44:55 +08:00
YunaiV d97cc41f42 Merge branch 'downey' of https://gitee.com/downeyin/yudao-mall-uniapp into develop 2024-07-28 20:06:40 +08:00
YunaiV dfe19ca710 【代码修复】cloneDeep 深拷贝对列表的处理错误 2024-07-28 19:41:07 +08:00
YunaiV d716bc4619 【代码修复】修复在微信小程序下,切换手机 + 密码登录失败的问题 2024-07-26 13:14:25 +08:00
YunaiV 0b9e8a39aa 【代码修复】下单时,备注未传递给后端的问题 2024-07-26 09:38:29 +08:00
downeyin 20f937a4ed feat: 增加租户ID自定义配置支持 2024-07-26 00:11:07 +08:00
puhui999 a2b0942d88 【优化】根据代码评审优化小程序的订阅消息 2024-07-25 17:40:29 +08:00
YunaiV f67e6a7f54 【代码修复】login.vue 微信登录绑定过慢,导致登录失败 2024-07-25 08:48:45 +08:00
芋道源码 3d7f63868f
!61 【新增】接入微信订阅消息
Merge pull request !61 from puhui999/master
2024-07-24 11:36:54 +00:00
puhui999 8977504147 【新增】接入微信订阅消息 2024-07-24 17:17:44 +08:00
YunaiV 0b9d7a8808 【代码修复】分销的提现的类型不正确的问题 2024-07-22 21:58:05 +08:00
芋道源码 1879a0b464
!60 新增在线客服,基于 WebSocket 实现实时通信
Merge pull request !60 from 芋道源码/develop
2024-07-22 10:00:11 +00:00
YunaiV f4cc8def91 【代码优化】分销相关的注释 2024-07-22 17:56:09 +08:00
芋道源码 2e423e9748
!58 复制订单信息失败,字段错误
Merge pull request !58 from WCH/N/A
2024-07-17 15:40:41 +00:00
芋道源码 9fc054a301
!59 update pages/order/aftersale/return-delivery.vue.
Merge pull request !59 from zhaowg/master
2024-07-17 15:38:18 +00:00
YunaiV 2ea027a6e0 【修复】商品详情页,未登录状态下,需要登录的问题 2024-07-17 23:35:39 +08:00
芋道源码 508bd9cbd7
!56 完善 mall 客服,实现 WebSocket 封装
Merge pull request !56 from puhui999/master
2024-07-17 14:37:56 +00:00
puhui999 d221f277a1 客服:重构消息列表(引入 z-paging) 2024-07-17 16:45:03 +08:00
zhaowg e289c10270
update pages/order/aftersale/return-delivery.vue.
修改1:进入该页面报: Cannot read properties of undefined (reading 'name'),原因是在{{ state.expresses[state.expressIndex].name }}这行代码,onLoad异步加载后端快递公司列表,在state.expresses为空数组时,直接读取name字段导致报错。
修改方式:增加v-if判断。

修改2:物流公司右侧增加箭头图标。icon-jiantou替换为_icon-forward。



Signed-off-by: zhaowg <suolong.hzw@foxmail.com>
2024-07-17 03:35:24 +00:00
puhui999 4c6a8077aa 客服:重构消息列表(引入 z-paging) 2024-07-16 16:23:49 +08:00
puhui999 c8feb58a46 客服:重构消息列表渲染逻辑和样式 2024-07-15 18:05:59 +08:00
puhui999 a95a04085b 客服:重构消息列表渲染逻辑和样式 2024-07-15 11:53:35 +08:00
puhui999 7baec78c02 完善 websocket 封装 2024-07-12 17:43:08 +08:00
puhui999 9399a3e223 websocket 初步封装 2024-07-11 17:19:21 +08:00
puhui999 39440c1e67 客服:完善订单消息发送 2024-07-09 17:34:18 +08:00
puhui999 b630d26d4b 客服:完善商品消息发送 2024-07-09 16:17:56 +08:00
WCH 72628f7e1c
复制订单信息失败,字段错误
Signed-off-by: WCH <wchhm8050@163.com>
2024-07-08 09:13:27 +00:00
puhui999 d9efd4e2e6 客服:完善图片消息发送 2024-07-02 17:32:36 +08:00
puhui999 166030e3b8 客服:使用轮训模拟 websocket 2024-07-02 17:10:17 +08:00
puhui999 a4a8548b5a 客服:根据代码评审进行 fix 2024-06-24 16:07:04 +08:00
puhui999 9c03966e12 Merge remote-tracking branch 'refs/remotes/yudao/master' 2024-06-17 17:34:14 +08:00
puhui999 793252e4aa 客服:完善消息发送(支持文本和表情)、聊天消息获取 2024-06-17 17:33:30 +08:00
puhui999 760dad0436 客服:聊天消息区域抽离封装 2024-06-12 17:05:14 +08:00
puhui999 3355f5e853 客服:组件初步抽离 2024-06-07 11:29:01 +08:00
芋道源码 6550441bc9
!55 解析分享参数,绑定推广员
Merge pull request !55 from puhui999/master
2024-05-27 13:57:30 +00:00
puhui999 561529b01e Merge remote-tracking branch 'refs/remotes/yudao/master' 2024-05-27 16:13:57 +08:00
puhui999 4d92eaf5f9 fix: 修复二次生成海报时 image 组件会闪一下的 bug 2024-05-27 16:13:33 +08:00
puhui999 950acf5275 解析分享参数,绑定推广员 2024-05-27 15:56:50 +08:00
puhui999 f00a8052ea 海报:修复海报保存报错 2024-05-27 15:26:05 +08:00
芋道源码 c3b1ff4b92
!54 海报:完善小程序码(添加前缀 data:image/png;base64,)
Merge pull request !54 from puhui999/master
2024-05-26 01:52:49 +00:00
puhui999 6b8a4fec29 Merge remote-tracking branch 'refs/remotes/yudao/master' 2024-05-25 22:20:45 +08:00
puhui999 2dfe61503f 海报:完善小程序码(添加前缀 data:image/png;base64,) 2024-05-25 22:20:19 +08:00
芋道源码 521553e2f5
!53 完善用户、商品、拼团海报生成
Merge pull request !53 from puhui999/master
2024-05-18 13:10:17 +00:00
puhui999 f46bfc6ee1 海报:完善拼团分享海报 2024-05-16 16:53:03 +08:00
puhui999 03512800b2 海报:完善用户分享海报 2024-05-16 16:29:14 +08:00
puhui999 ccd033b15a 海报:完善海报生成展示组件 2024-05-16 16:15:56 +08:00
puhui999 dabefbe21c 海报:完善商品海报分享 2024-05-14 23:23:31 +08:00
puhui999 8963ebc6af 海报:移除 qs-canvas 库,采用社区活跃的 lime-painter 来实现海报生成 2024-05-13 18:12:42 +08:00
puhui999 004294ae7c 海报:完善小程序码获取和海报生成 2024-05-13 16:22:55 +08:00
puhui999 8815972188 fix: 移除多余权限配置 2024-05-11 09:27:53 +08:00
YunaiV ae3cef3e19 【发版】全局:2.1.0 发版 2024-05-04 11:17:53 +08:00
YunaiV 68d2fc4c24 【修复】拼团装修组件,跳转拼团商品界面的 id 不正确的问题 2024-05-02 10:16:17 +08:00
YunaiV c3f3c44e63 【完善】兼容 HBuilder 最新版本 vite5 2024-05-02 09:39:00 +08:00
芋道源码 97802232a2
!51 update pages/order/detail.vue. @tap="onGoodsDetail(item.skuId)" =>@tap="onGoodsDetail(item.spuId)"
Merge pull request !51 from 孙正涛/N/A
2024-05-02 01:29:15 +00:00
孙正涛 73dbe70305
update pages/order/detail.vue. @tap="onGoodsDetail(item.skuId)" =>@tap="onGoodsDetail(item.spuId)"
点击查看商品详情时应该跳转的是spuId

Signed-off-by: 孙正涛 <alchemy7746@163.com>
2024-04-15 03:47:47 +00:00
YunaiV a816582206 bugfix:修复拼团详情错误传递 groupon_id 的问题 2024-03-30 15:28:29 +08:00
芋道源码 d2c4b9efc9
!50 适配顶部导航
Merge pull request !50 from 疯狂的世界/navbar
2024-03-17 01:26:39 +00:00
owen 13986f3943 适配顶部导航 2024-03-13 22:46:46 +08:00
芋道源码 b7c38be5b3
!49 update pages/index/login.vue.
Merge pull request !49 from 天痕/N/A
2024-03-09 10:35:24 +00:00
芋道源码 d7eb74968a
!48 配置文件描述修改
Merge pull request !48 from 天痕/N/A
2024-03-09 10:34:03 +00:00
芋道源码 ddc54679fb
!47 完善微信导入收货地址
Merge pull request !47 from DH/N/A
2024-03-09 10:33:32 +00:00
芋道源码 7226eb8eb0
!46 fix : data.combinationActivityId
Merge pull request !46 from cnyballk/fix/api/trade/order
2024-03-09 10:32:02 +00:00
芋道源码 a0deb30075
!45 fix: gravityFormat to gravityFormatMap
Merge pull request !45 from cnyballk/master
2024-03-09 10:31:30 +00:00
芋道源码 e8cc7cb6c0
!44 fix: add export checkUpdate
Merge pull request !44 from cnyballk/fix/wechat/checkUpdate
2024-03-09 10:28:49 +00:00
天痕 f6fefe3aef
update pages/index/login.vue.
移除回调,用法错误

Signed-off-by: 天痕 <614891294@qq.com>
2024-03-05 05:55:36 +00:00
天痕 bfdb7bd00e
配置文件描述修改
描述有问题
2024-03-05 05:47:01 +00:00
DH d1162d8552
完善微信导入收货地址
Signed-off-by: DH <ji534@outlook.com>
2024-03-04 08:48:18 +00:00
cnyballk 19ebbecb0c
fix: data.combinationActivityId
Signed-off-by: cnyballk <584518260@qq.com>
2024-03-04 07:14:45 +00:00
cnyballk e4ca6b4e68
fix: gravityFormat to gravityFormatMap
Signed-off-by: cnyballk <584518260@qq.com>
2024-03-04 03:46:48 +00:00
cnyballk f478606ae4
fix: mini export checkUpdate
Signed-off-by: cnyballk <584518260@qq.com>
2024-03-01 15:24:50 +00:00
YunaiV b10f8d3181 🎉 2.0.1 版本发布! 2024-03-01 20:53:46 +08:00
YunaiV 71b9a502f3 Merge remote-tracking branch 'origin/master' 2024-03-01 18:56:07 +08:00
YunaiV d51ebca23b bugfix:购物车界面,金额展示不正确 2024-03-01 18:54:36 +08:00
芋道源码 fb4220b46e
!43 fix: formatOrderStatusDescription variable error
Merge pull request !43 from cnyballk/fix/hooks/formatOrderStatusDescription
2024-03-01 10:49:25 +00:00
YunaiV d815fd5e3f bugfix:确认订单页面,不展示会员优惠信息 2024-03-01 18:48:32 +08:00
cnyballk c0105cc0fe
fix: formatOrderStatusDescription variable error
Signed-off-by: cnyballk <584518260@qq.com>
2024-03-01 09:31:59 +00:00
YunaiV a0e9ceb945 Merge remote-tracking branch 'origin/master' 2024-03-01 13:20:49 +08:00
YunaiV 12332f0f30 修复订单列表 tab 切换叠加问题 2024-03-01 13:20:39 +08:00
芋道源码 eea2ad446f
!39 修复了商品卡片没有正确格式化数字的问题
Merge pull request !39 from DH/master
2024-03-01 05:09:04 +00:00
YunaiV fe76f2fc90 砍价:暂时移除,砍价在小程序中容易被封号 2024-03-01 13:06:56 +08:00
DH 87a6b962e7
修复格式化数字问题
sheep/hooks/useGoods.js格式化数字 
情况三时没有对数字格式

Signed-off-by: DH <ji534@outlook.com>
2024-01-29 01:34:51 +00:00
DH 829bea3861
修复商品卡片销量可能会出现余数的问题
Signed-off-by: DH <ji534@outlook.com>
2024-01-29 01:24:27 +00:00
240 changed files with 20679 additions and 9976 deletions

34
.env
View File

@ -1,21 +1,35 @@
# 版本号
SHOPRO_VERSION = v1.8.3
SHOPRO_VERSION=v2.4.1
# 后端接口 - 正式环境(通过 process.env.NODE_ENV = development
SHOPRO_BASE_URL = http://api-dashboard.yudao.iocoder.cn
# 后端接口 - 正式环境(通过 process.env.NODE_ENV development
SHOPRO_BASE_URL=http://api-dashboard.yudao.iocoder.cn
# 后端接口 - 测试环境(通过 process.env.NODE_ENV 非 development
SHOPRO_DEV_BASE_URL = http://127.0.0.1:48080
# 后端接口 - 测试环境(通过 process.env.NODE_ENV = development
SHOPRO_DEV_BASE_URL=http://127.0.0.1:48080
### SHOPRO_DEV_BASE_URL=http://10.171.1.188:48080
### SHOPRO_DEV_BASE_URL = http://yunai.natapp1.cc
# 文件上传类型server - 后端上传, client - 前端直连上传,仅支持 S3 服务
SHOPRO_UPLOAD_TYPE=server
# 后端接口前缀(一般不建议调整)
SHOPRO_API_PATH = /app-api
SHOPRO_API_PATH=/app-api
# 后端 websocket 接口前缀
SHOPRO_WEBSOCKET_PATH=/infra/ws
# 开发环境运行端口
SHOPRO_DEV_PORT = 3000
SHOPRO_DEV_PORT=3000
# 客户端静态资源地址 空=默认使用服务端指定的CDN资源地址前缀 | local=本地 | http(s)://xxx.xxx=自定义静态资源地址前缀
SHOPRO_STATIC_URL = https://file.sheepjs.com
SHOPRO_STATIC_URL=http://test.yudao.iocoder.cn
### SHOPRO_STATIC_URL = https://file.sheepjs.com
# 是否开启直播 1 开启直播 | 0 关闭直播 (小程序官方后台未审核开通直播权限时请勿开启)
SHOPRO_MPLIVE_ON = 0
# 前端 H5 访问域名
SHOPRO_H5_URL=http://127.0.0.1:3000
# 是否开启直播 1 开启直播 | 0 关闭直播
SHOPRO_MPLIVE_ON=0
# 租户ID 默认 1
SHOPRO_TENANT_ID=1

1
.gitignore vendored
View File

@ -5,7 +5,6 @@ deploy.sh
.hbuilderx/
.vscode/
**/.DS_Store
.env
yarn.lock
package-lock.json
*.keystore

19
App.vue
View File

@ -4,33 +4,26 @@
onLaunch(() => {
// 使
uni.hideTabBar();
uni.hideTabBar({
fail: () => {},
});
// Shopro
ShoproInit();
});
onError((err) => {
console.log('AppOnError:', err);
});
onShow((options) => {
onShow(() => {
// #ifdef APP-PLUS
// urlSchemes
const args = plus.runtime.arguments;
if (args) {
}
}
//
uni.getClipboardData({
success: (res) => { },
success: (res) => {},
});
// #endif
// #ifdef MP-WEIXIN
//
console.log(options,'options');
// #endif
});
</script>

View File

@ -30,11 +30,11 @@
支持 Spring Boot、Spring Cloud 两种架构:
① Spring Boot 单体架构:<https://github.com/YunaiV/ruoyi-vue-pro>
① Spring Boot 单体架构:<https://doc.iocoder.cn>
![架构图](/.image/common/ruoyi-vue-pro-architecture.png)
② Spring Cloud 微服务架构:<https://github.com/YunaiV/yudao-cloud>
② Spring Cloud 微服务架构:<https://cloud.iocoder.cn>
![架构图](/.image/common/yudao-cloud-architecture.png)

View File

@ -1,9 +1,9 @@
{
"name": "芋道商城",
"appid": "__UNI__082C0BA",
"appid": "__UNI__460BC4C",
"description": "基于 uni-app + Vue3 技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。",
"versionName": "1.8.3",
"versionCode": 183,
"versionName": "2025.09",
"versionCode": "183",
"transformPx": false,
"app-plus": {
"usingComponents": true,
@ -57,7 +57,6 @@
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
"<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
@ -91,9 +90,7 @@
"idfa": true
},
"sdkConfigs": {
"speech": {
"ifly": {}
},
"speech": {},
"ad": {},
"oauth": {
"apple": {},
@ -185,7 +182,7 @@
"versionCode": 100
},
"mp-weixin": {
"appid": "wx63c280fe3248a3e7",
"appid": "wx66186af0759f47c9",
"setting": {
"urlCheck": false,
"minified": true,
@ -217,8 +214,8 @@
"h5": {
"template": "index.html",
"router": {
"mode": "hash",
"base": "./"
"mode": "history",
"base": "/"
},
"sdkConfigs": {
"maps": {}

View File

@ -2,7 +2,7 @@
"id": "shopro",
"name": "shopro",
"displayName": "芋道商城",
"version": "1.0.1",
"version": "2025.09",
"description": "芋道商城一套代码同时发行到iOS、Android、H5、微信小程序多个平台请使用手机扫码快速体验强大功能",
"scripts": {
"prettier": "prettier --write \"{pages,sheep}/**/*.{js,json,tsx,css,less,scss,vue,html,md}\""
@ -88,13 +88,12 @@
}
},
"dependencies": {
"@hyoga/uni-socket.io": "^1.0.1",
"dayjs": "^1.11.7",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"luch-request": "^3.0.8",
"pinia": "^2.0.33",
"pinia-plugin-persist-uni": "^1.2.0",
"qs-canvas": "^1.0.11",
"weixin-js-sdk": "^1.6.0"
},
"devDependencies": {

View File

@ -120,6 +120,17 @@
"group": "商品"
}
},
{
"path": "point",
"style": {
"navigationBarTitleText": "积分商品"
},
"meta": {
"sync": true,
"title": "积分商品",
"group": "商品"
}
},
{
"path": "list",
"style": {
@ -307,6 +318,18 @@
"title": "编辑地址"
}
},
{
"path": "goods_details_store/index",
"style": {
"navigationBarTitleText": "自提门店"
},
"meta": {
"auth": true,
"sync": true,
"title": "地址管理",
"group": "用户中心"
}
},
{
"path": "wallet/money",
"style": {
@ -526,7 +549,10 @@
"pages": [{
"path": "index",
"style": {
"navigationBarTitleText": "客服"
"navigationBarTitleText": "客服",
"app-plus": {
"softinputMode": "adjustResize"
}
},
"meta": {
"auth": true,
@ -630,28 +656,17 @@
"group": "营销活动"
}
},
{
"path": "bargain/list",
"style": {
"navigationBarTitleText": "砍价列表"
},
"meta": {
"sync": true,
"title": "砍价列表",
"group": "营销活动"
}
},
{
"path": "bargain/detail",
"style": {
"navigationBarTitleText": "砍价详情"
},
"meta": {
"sync": true,
"title": "砍价详情",
"group": "营销活动"
}
}
{
"path": "point/list",
"style": {
"navigationBarTitleText": "积分商城"
},
"meta": {
"sync": true,
"title": "积分商城",
"group": "营销活动"
}
}
]
}
],
@ -666,6 +681,9 @@
"list": [{
"pagePath": "pages/index/index"
},
{
"pagePath": "pages/index/category"
},
{
"pagePath": "pages/index/cart"
},
@ -674,4 +692,4 @@
}
]
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,370 +0,0 @@
<template>
<!-- TODO @科举参考 groupon/list.vue seckill/list.vue 界面调整下头部就是从 5 11 行的 -->
<s-layout navbar="inner" title='砍价列表'>
<view style='background-color: red;height:100vh;'>
<view class='bargain-list'>
<!-- #ifdef H5 -->
<view class='iconfont icon-xiangzuo' @tap='goBack' :style="'top:'+ (state.navH/2) +'rpx'"
v-if="state.returnShow">
</view>
<!-- #endif -->
<!-- 砍价记录的概要 -->
<view class='header'>
<view class="pic">
<view class='swipers'>
<swiper indicator-dots="true" autoplay="true" interval="2500" duration="500" vertical="true"
circular="true">
<block v-for="(item,index) in state.bargainSuccessList" :key='index'>
<swiper-item>
<view class="acea-row row-middle" style='display:flex'>
<image :src="item.avatar" class="mr9"></image>
<view class='mr9 nickName'>{{ item.nickname }}</view>
<text class='mr9'>拿了</text>
<view class='line1'>{{ item.activityName }}</view>
</view>
</swiper-item>
</block>
</swiper>
</view>
</view>
<view class="tit">已有{{ state.bargainTotal }}人砍成功</view>
</view>
<!-- 砍价活动列表 -->
<view class='list'>
<block v-for="(item,index) in state.bargainList" :key="index">
<view style='display:flex' class='item acea-row row-between-wrapper'
@tap="openSubscribe('/pages/activity/bargain/detail?id='+ item.id)">
<view class='pictrue'>
<image :src='item.picUrl'></image>
</view>
<view class='text acea-row row-column-around'>
<view class='name line2'>{{ item.name }}</view>
<view class="acea-row" style="margin-bottom: 14rpx;display:flex">
<s-count-down :tipText="' '" :bgColor="state.bgColor" :dayText="':'" :hourText="':'"
:minuteText="':'" :secondText="' '" :datatime="item.endTime / 1000"
:isDay="true" />
<text class="txt">后结束</text>
</view>
<view v-if="item.stock === 0">
<view style="font-size: 22rpx;"
@tap="openSubscribe('/pages/activity/goods_bargain_details/index?id='+ item.id +'&startBargainUid='+ uid)">
已售罄</view>
</view>
<view class='money font-color'>最低: <text
class='price'>{{ fen2yuan(item.bargainMinPrice) }}</text></view>
</view>
<view v-if="item.stock > 0" class='cutBnt bg-color'>参与砍价</view>
<view v-if="item.stock === 0" class='cutBnt bg-color-hui'>已售罄</view>
</view>
</block>
<view class='loadingicon acea-row row-center-wrapper' v-if='state.bargainList.length > 0'
style='text-align: center;'>
<text class='loading iconfont icon-jiazai' :hidden='!loading'></text>{{state.loadTitle}}
</view>
</view>
</view>
</view>
</s-layout>
</template>
<script setup>
import {
reactive
} from 'vue';
import sheep from '@/sheep';
import _ from 'lodash';
import {
onLoad,
onReachBottom
} from '@dcloudio/uni-app';
import {
fen2yuan
} from '@/sheep/hooks/useGoods';
import BargainApi from '@/sheep/api/promotion/bargain'
const state = reactive({
navH: '',
returnShow: true,
// ========== ==========
bargainTotal: 0,
bargainSuccessList: [],
// ========== ==========
bargainList: [],
page: 1,
limit: 10,
loading: false,
loadend: false,
bgColor: {
'bgColor': '#E93323',
'Color': '#fff',
'width': '44rpx',
'timeTxtwidth': '16rpx',
'isDay': true
},
loadTitle: '加载更多',
});
async function getBargainHeader() {
let {
code,
data
} = await BargainApi.getBargainRecordSummary()
if (code == 0) {
state.bargainTotal = data.successUserCount;
state.bargainSuccessList = data.successList;
} else {
state.$util.Tips({
title: data
});
}
}
async function getBargainList() {
// TODO @loading loadTitle 使 uni-load-more
if (state.loadend || state.loading) {
return;
}
state.loading = true;
state.loadTitle = '';
let {
data,
code
} = await BargainApi.getBargainActivityPage({
pageNo: state.page,
pageSize: state.limit
})
if (code == 0) {
const list = data.list;
const bargainList = _.concat(state.bargainList, list);
const loadend = list.length < state.limit;
state.loadend = loadend;
state.loading = false;
state.loadTitle = loadend ? '已全部加载' : '加载更多';
// this.$set(this, 'bargainList', bargainList);
state.bargainList = data.list
// this.$set(this, 'page', this.page + 1);
state.page = state.page + 1;
} else {
state.loading = false;
state.loadTitle = '加载更多';
}
}
function openSubscribe(e) {
console.log('跳转')
console.log(e)
// TODO @ pages/pay/result.vue subscribeMessage
sheep.$router.go(e)
return;
let page = e;
// #ifndef MP
uni.navigateTo({
url: page
});
// #endif
// #ifdef MP
uni.showLoading({
title: '正在加载',
})
openBargainSubscribe().then(res => {
uni.hideLoading();
}).catch((err) => {
uni.hideLoading();
});
// #endif
}
onLoad(function() {
getBargainHeader();
getBargainList();
})
onReachBottom(() => {
getBargainList();
});
</script>
<style lang='scss' scoped>
page,
.page-app {
background-color: #e93323 !important;
}
.font-color {
color: red;
}
.mr9 {
margin-right: 9rpx;
}
.swipers {
height: 100%;
width: 76%;
margin: auto;
overflow: hidden;
font-size: 22rpx;
color: #fff;
image {
width: 24rpx;
height: 24rpx;
border-radius: 50%;
overflow: hidden;
}
swiper {
height: 100%;
width: 100%;
overflow: hidden;
}
.line1 {
width: 195rpx;
}
}
.bargain-list .icon-xiangzuo {
font-size: 40rpx;
color: #fff;
position: fixed;
left: 30rpx;
z-index: 99;
transform: translateY(-20%);
height: 100%
}
.bargain-list .header {
/* TODO 芋艿此处原来采用base64 但是过长编辑到小程序卡死 目前采用网络地址 需解决 */
background-image: url('https://huizhizao-1314830814.cos.ap-shanghai.myqcloud.com/huizhizao-1314830814/bdc8a9210710b83bcd88a14703f440fc7091792706b5cb71b54361488a547298.png');
babackground-repeat: no-repeat;
background-size: 100% 100%;
width: 750rpx;
height: 420rpx;
.acea-row {
height: 50rpx;
line-height: 50rpx;
left: 50rpx;
.nickName {
width: 65rpx;
overflow: hidden;
white-space: nowrap;
}
}
.pic {
width: 478rpx;
height: 50rpx;
margin: 0 auto;
/* TODO 芋艿:此处原来是本地地址小程序不支持,需改为线上 */
background-image: url('https://huizhizao-1314830814.cos.ap-shanghai.myqcloud.com/huizhizao-1314830814/d111ac53e1390618f22fcc03e415bcd584b3f409ae52421aef95c2ab9b02aa30.png');
babackground-repeat: no-repeat;
background-size: 100% 100%;
}
.tit {
color: #FFFFFF;
font-size: 24rpx;
font-weight: 400;
text-align: center;
margin-top: 304rpx;
}
}
.bargain-list .list {
padding: 0 30rpx;
}
.bargain-list .list .item {
position: relative;
height: 250rpx;
background-color: #fff;
border-radius: 14rpx;
margin-bottom: 20rpx;
padding: 30rpx 25rpx;
}
.bargain-list .list .item .pictrue {
width: 190rpx;
height: 190rpx;
}
.bargain-list .list .item .pictrue image {
width: 100%;
height: 100%;
border-radius: 14rpx;
}
.bargain-list .list .item .text {
width: 432rpx;
font-size: 28rpx;
color: #333333;
.txt {
font-size: 22rpx;
margin-left: 4rpx;
color: #666666;
line-height: 36rpx;
}
}
.bargain-list .list .item .text .name {
width: 100%;
height: 68rpx;
line-height: 36rpx;
font-size: 28rpx;
margin-bottom: 26rpx;
}
.bargain-list .list .item .text .num {
font-size: 26rpx;
color: #999;
}
.bargain-list .list .item .text .num .iconfont {
font-size: 35rpx;
margin-right: 7rpx;
}
.bargain-list .list .item .text .money {
font-size: 24rpx;
font-weight: bold;
}
.bargain-list .list .item .text .money .price {
font-size: 38rpx;
}
.bargain-list .list .item .cutBnt {
position: absolute;
width: 162rpx;
height: 52rpx;
border-radius: 50rpx;
font-size: 24rpx;
color: #fff;
text-align: center;
line-height: 52rpx;
right: 24rpx;
bottom: 30rpx;
background: linear-gradient(90deg, #FF7931 0%, #E93323 100%);
}
.bargain-list .list .item .cutBnt .iconfont {
margin-right: 8rpx;
font-size: 30rpx;
}
.bargain-list .list .load {
font-size: 24rpx;
height: 85rpx;
text-align: center;
line-height: 85rpx;
}
</style>

View File

@ -1,6 +1,11 @@
<!-- 拼团订单的详情 -->
<template>
<s-layout title="拼团详情" class="detail-wrap" :navbar="state.data && !state.loading ? 'inner': 'normal'" :onShareAppMessage="shareInfo">
<s-layout
title="拼团详情"
class="detail-wrap"
:navbar="state.data && !state.loading ? 'inner' : 'normal'"
:onShareAppMessage="shareInfo"
>
<view v-if="state.loading"></view>
<view v-if="state.data && !state.loading">
<!-- 团长信息 + 活动信息 -->
@ -22,7 +27,7 @@
priceColor="#E1212B"
@tap="
sheep.$router.go('/pages/goods/groupon', {
id: state.data.headRecord.activityId
id: state.data.headRecord.activityId,
})
"
:style="[{ top: Number(statusBarHeight + 108) + 'rpx' }]"
@ -71,7 +76,9 @@
</view>
<view class="countdown-title ss-flex" v-else>
还差
<view class="num">{{ state.data.headRecord.userSize - state.data.headRecord.userCount }}</view>
<view class="num"
>{{ state.data.headRecord.userSize - state.data.headRecord.userCount }}</view
>
拼团成功
<view class="ss-flex countdown-time">
<view class="countdown-h ss-flex ss-row-center">{{ endTime.h }}</view>
@ -91,7 +98,7 @@
<view class="ss-m-t-60 ss-flex ss-flex-wrap ss-row-center">
<!-- 团长 -->
<view class="header-avatar ss-m-r-24 ss-m-b-20">
<image :src="sheep.$url.cdn(state.data.headRecord.avatar)" class="avatar-img"></image>
<image :src="sheep.$url.cdn(state.data.headRecord.avatar) || sheep.$url.static('/static/img/shop/default_avatar.png')" class="avatar-img"></image>
<view class="header-tag ss-flex ss-col-center ss-row-center">团长</view>
</view>
<!-- 团员 -->
@ -100,7 +107,7 @@
v-for="item in state.data.memberRecords"
:key="item.id"
>
<image :src="sheep.$url.cdn(item.avatar)" class="avatar-img"></image>
<image :src="sheep.$url.cdn(item.avatar) || sheep.$url.static('/static/img/shop/default_avatar.png')" class="avatar-img"></image>
<view
class="header-tag ss-flex ss-col-center ss-row-center"
v-if="item.is_leader == '1'"
@ -109,7 +116,11 @@
</view>
</view>
<!-- 还有几个坑位 -->
<view class="default-avatar ss-m-r-24 ss-m-b-20" v-for="item in state.remainNumber" :key="item">
<view
class="default-avatar ss-m-r-24 ss-m-b-20"
v-for="item in state.remainNumber"
:key="item"
>
<image
:src="sheep.$url.static('/static/img/shop/avatar/unknown.png')"
class="avatar-img"
@ -154,11 +165,7 @@
</view>
<view v-else class="ss-flex ss-row-center">
<view v-if="state.data.orderId">
<button
class="ss-reset-button join-btn"
:disabled="endTime.ms <= 0"
@tap="onShare"
>
<button class="ss-reset-button join-btn" :disabled="endTime.ms <= 0" @tap="onShare">
邀请好友来拼团
</button>
</view>
@ -174,11 +181,11 @@
</view>
</view>
<!-- TODO 芋艿这里暂时没接入 -->
<view v-if="state.data.goods">
<view v-if="!isEmpty(state.goodsInfo)">
<!-- 规格与数量弹框 -->
<s-select-groupon-sku
:show="state.showSelectSku"
:goodsInfo="state.data.goods"
:goodsInfo="state.goodsInfo"
:grouponAction="state.grouponAction"
:grouponNum="state.grouponNum"
@buy="onBuy"
@ -196,25 +203,29 @@
import { computed, reactive } from 'vue';
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
import { useDurationTime } from '@/sheep/hooks/useGoods';
import { fen2yuan, useDurationTime } from '@/sheep/hooks/useGoods';
import { showShareModal } from '@/sheep/hooks/useModal';
import { isEmpty } from 'lodash';
import CombinationApi from "@/sheep/api/promotion/combination";
import { isEmpty } from 'lodash-es';
import CombinationApi from '@/sheep/api/promotion/combination';
import SpuApi from '@/sheep/api/product/spu';
import { SharePageEnum } from '@/sheep/helper/const';
const headerBg = sheep.$url.css('/static/img/shop/user/withdraw_bg.png');
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const state = reactive({
data: {}, //
loading: true,
grouponAction: 'create',
showSelectSku: false,
grouponNum: 0,
number: 0,
activity: {},
goodsId: 0, // ID
goodsInfo: {}, //
showSelectSku: false, //
selectedSkuPrice: {}, //
activity: {}, //
grouponId: 0, // ID
grouponNum: 0, //
grouponAction: 'create', //
combinationHeadId: null, //
loading: true,
});
// todo
const shareInfo = computed(() => {
if (isEmpty(state.data)) return {};
return sheep.$platform.share.getShareInfo(
@ -223,16 +234,16 @@
image: sheep.$url.cdn(state.data.headRecord.picUrl),
desc: state.data.goods?.subtitle,
params: {
page: '5',
query: state.data.id,
page: SharePageEnum.GROUPON_DETAIL.value,
query: state.data.headRecord.id,
},
},
{
type: 'groupon', //
title: state.data.headRecord.spuName, //
image: sheep.$url.cdn(state.data.headRecord.picUrl), //
price: state.data.goods?.price, //
original_price: state.data.goods?.original_price, //
price: fen2yuan(state.data.headRecord.combinationPrice), //
grouponNum: state.data.headRecord.userSize, //
},
);
});
@ -244,33 +255,33 @@
});
}
// TODO
//
function onCreateGroupon() {
state.grouponAction = 'create';
state.grouponId = 0;
state.showSelectSku = true;
}
// TODO
//
function onSkuChange(e) {
state.selectedSkuPrice = e;
}
// TODO
//
function onJoinGroupon() {
state.grouponAction = 'join';
state.grouponId = state.data.activityId;
state.combinationHeadId = state.data.id;
state.grouponNum = state.data.num;
state.grouponId = state.data.headRecord.activityId;
state.combinationHeadId = state.data.headRecord.id;
state.grouponNum = state.data.headRecord.userSize;
state.showSelectSku = true;
}
// TODO
//
function onBuy(sku) {
sheep.$router.go('/pages/order/confirm', {
data: JSON.stringify({
order_type: 'goods',
combinationActivityId: state.data.activity.id,
combinationActivityId: state.activity.id,
combinationHeadId: state.combinationHeadId,
items: [
{
@ -295,8 +306,29 @@
state.remainNumber = remainNumber > 0 ? remainNumber : 0;
//
const { data: activity } = await CombinationApi.getCombinationActivity(data.headRecord.activityId);
const { data: activity } = await CombinationApi.getCombinationActivity(
data.headRecord.activityId,
);
state.activity = activity;
state.grouponNum = activity.userSize;
//
const { data: spu } = await SpuApi.getSpuDetail(activity.spuId);
state.goodsId = spu.id;
//
activity.products.forEach((product) => {
spu.price = Math.min(spu.price, product.combinationPrice); // SPU
});
state.goodsInfo = spu;
// 使
spu.skus.forEach((sku) => {
const product = activity.products.find((product) => product.skuId === sku.id);
if (product) {
sku.price = product.combinationPrice;
} else {
//
sku.stock = 0;
}
});
} else {
state.data = null;
}
@ -316,8 +348,7 @@
.recharge-box {
position: relative;
margin-bottom: 120rpx;
background: v-bind(headerBg) center/750rpx 100%
no-repeat,
background: v-bind(headerBg) center/750rpx 100% no-repeat,
linear-gradient(115deg, #f44739 0%, #ff6600 100%);
border-radius: 0 0 5% 5%;
height: 100rpx;

View File

@ -1,6 +1,6 @@
<!-- 拼团活动列表 -->
<template>
<s-layout navbar="inner" :bgStyle="{ color: '#FE832A' }">
<s-layout :bgStyle="{ color: '#FE832A' }" navbar="inner">
<view class="page-bg" :style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]" />
<view class="list-content">
<!-- 参团会员统计 -->

View File

@ -24,9 +24,9 @@
</view>
<view class="border-bottom">
<s-goods-item
:img="record.picUrl"
:title="record.spuName"
:price="record.combinationPrice"
:img="record.picUrl"
:title="record.spuName"
:price="record.combinationPrice"
>
<template #groupon>
<view class="ss-flex">
@ -67,9 +67,9 @@
import { reactive } from 'vue';
import { onLoad, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import _ from 'lodash';
import {formatOrderColor} from "@/sheep/hooks/useGoods";
import { resetPagination } from '@/sheep/util';
import _ from 'lodash-es';
import { formatOrderColor } from '@/sheep/hooks/useGoods';
import { resetPagination } from '@/sheep/helper/utils';
import CombinationApi from '@/sheep/api/promotion/combination';
//
@ -121,7 +121,7 @@
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list)
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
@ -149,6 +149,7 @@
//
onPullDownRefresh(() => {
resetPagination(state.pagination);
getGrouponList();
setTimeout(function () {
uni.stopPullDownRefresh();

View File

@ -7,7 +7,7 @@
<view class="type-text ss-flex ss-row-center">满减</view>
<view class="ss-flex-1">
<view class="tip-content" v-for="item in state.activityInfo.rules" :key="item">
{{ formatRewardActivityRule(state.activityInfo, item) }}
{{ item.description }}
</view>
</view>
<image class="activity-left-image" src="/static/activity-left.png" />
@ -63,10 +63,11 @@
import { reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import _ from 'lodash';
import _ from 'lodash-es';
import RewardActivityApi from '@/sheep/api/promotion/rewardActivity';
import { formatRewardActivityRule } from '@/sheep/hooks/useGoods';
import SpuApi from '@/sheep/api/product/spu';
import { appendSettlementProduct } from '@/sheep/hooks/useGoods';
import OrderApi from '@/sheep/api/trade/order';
const state = reactive({
activityId: 0, //
@ -118,11 +119,18 @@
const { code, data } = await SpuApi.getSpuPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
...params
...params,
});
if (code !== 0) {
return;
}
//
await OrderApi.getSettlementProduct(data.list.map((item) => item.id).join(',')).then((res) => {
if (res.code !== 0) {
return;
}
appendSettlementProduct(data.list, res.data);
});
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';

View File

@ -0,0 +1,76 @@
<!-- 积分商城商品列表 -->
<template>
<s-layout title="积分商城" navbar="normal" :leftWidth="0" :rightWidth="0">
<scroll-view
class="scroll-box"
:style="{ height: pageHeight + 'rpx' }"
scroll-y="true"
:scroll-with-animation="false"
:enable-back-to-top="true"
>
<s-point-card ref="sPointCardRef" class="ss-p-x-20 ss-m-t-20" />
<s-empty
v-if="activityTotal === 0"
icon="/static/goods-empty.png"
text="暂无积分商品"
></s-empty>
<uni-load-more
v-if="activityTotal > 0"
:status="loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}"
@tap="loadMore"
/>
</scroll-view>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { reactive, ref } from 'vue';
import PointApi from '@/sheep/api/promotion/point';
import SLayout from '@/sheep/components/s-layout/s-layout.vue';
//
const { safeAreaInsets, safeArea } = sheep.$platform.device;
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const pageHeight =
(safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sheep.$platform.navbar - 350;
const sPointCardRef = ref();
//
const activityPageParams = reactive({
pageNo: 1, //
pageSize: 5, //
});
const activityTotal = ref(0); //
const activityCount = ref(0); //
const loadStatus = ref(''); //
async function getActivityList() {
loadStatus.value = 'loading';
const { data } = await PointApi.getPointActivityPage(activityPageParams);
await sPointCardRef.value.concatActivity(data.list);
activityCount.value = sPointCardRef.value.getActivityCount();
activityTotal.value = data.total;
loadStatus.value = activityCount.value < activityTotal.value ? 'more' : 'noMore';
}
//
function loadMore() {
if (loadStatus.value !== 'noMore') {
activityPageParams.pageNo += 1;
getActivityList();
}
}
//
onReachBottom(() => {
loadMore();
});
onLoad(() => {
getActivityList();
});
</script>

View File

@ -1,15 +1,22 @@
<!-- 秒杀活动列表 -->
<template>
<s-layout navbar="inner" :bgStyle="{ color: 'rgb(245,28,19)' }">
<s-layout :bgStyle="{ color: 'rgb(245,28,19)' }" navbar="inner">
<!--顶部背景图-->
<view
class="page-bg"
:style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]"
class="page-bg"
:style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]"
></view>
<!-- 时间段轮播图 -->
<view class="header" v-if="activeTimeConfig?.sliderPicUrls?.length > 0">
<swiper indicator-dots="true" autoplay="true" :circular="true" interval="3000" duration="1500"
indicator-color="rgba(255,255,255,0.6)" indicator-active-color="#fff">
<swiper
indicator-dots="true"
autoplay="true"
:circular="true"
interval="3000"
duration="1500"
indicator-color="rgba(255,255,255,0.6)"
indicator-active-color="#fff"
>
<block v-for="(picUrl, index) in activeTimeConfig.sliderPicUrls" :key="index">
<swiper-item class="borRadius14">
<image :src="picUrl" class="slide-image borRadius14" lazy-load />
@ -21,18 +28,28 @@
<view class="flex align-center justify-between ss-p-25">
<!-- 左侧图标 -->
<view class="time-icon">
<!-- TODO 芋艿图片统一维护 -->
<image class="ss-w-100 ss-h-100" src="http://mall.yudao.iocoder.cn/static/images/priceTag.png" />
<image
class="ss-w-100 ss-h-100"
:src="sheep.$url.static('/static/img/shop/priceTag.png')"
/>
</view>
<scroll-view class="time-list" :scroll-into-view="activeTimeElId" scroll-x scroll-with-animation>
<view v-for="(config, index) in timeConfigList" :key="index"
:class="['item', { active: activeTimeIndex === index}]"
:id="`timeItem${index}`"
@tap="handleChangeTimeConfig(index)">
<scroll-view
class="time-list"
:scroll-into-view="activeTimeElId"
scroll-x
scroll-with-animation
>
<view
v-for="(config, index) in timeConfigList"
:key="index"
:class="['item', { active: activeTimeIndex === index }]"
:id="`timeItem${index}`"
@tap="handleChangeTimeConfig(index, config.id)"
>
<!-- 活动起始时间 -->
<view class="time">{{ config.startTime }}</view>
<!-- 活动状态 -->
<view class="status">{{ config.status }}</view>
<view class="status">{{ config?.status }}</view>
</view>
</scroll-view>
</view>
@ -42,7 +59,10 @@
<!-- 活动倒计时 -->
<view class="content-header ss-flex-col ss-col-center ss-row-center">
<view class="content-header-box ss-flex ss-row-center">
<view class="countdown-box ss-flex" v-if="activeTimeConfig?.status === TimeStatusEnum.STARTED">
<view
class="countdown-box ss-flex"
v-if="activeTimeConfig?.status === TimeStatusEnum.STARTED"
>
<view class="countdown-title ss-m-r-12">距结束</view>
<view class="ss-flex countdown-time">
<view class="ss-flex countdown-h">{{ countDown.h }}</view>
@ -70,19 +90,44 @@
:data="{ ...activity, price: activity.seckillPrice }"
:goodsFields="goodsFields"
:seckillTag="true"
@click="sheep.$router.go('/pages/goods/seckill', { id: activity.id })"
>
<!-- 抢购进度 -->
<template #activity>
<view class="limit">限量 <text class="ss-m-l-5">{{ activity.stock}} {{activity.unitName}}</text></view>
<view class="limit">
限量
<text class="ss-m-l-5">{{ activity.stock }} {{ activity.unitName }}</text>
</view>
<su-progress :percentage="activity.percent" strokeWidth="10" textInside isAnimate />
</template>
<!-- 抢购按钮 -->
<template #cart>
<button :class="['ss-reset-button cart-btn', { disabled: activeTimeConfig.status === TimeStatusEnum.END }]">
<span v-if="activeTimeConfig?.status === TimeStatusEnum.WAIT_START"></span>
<span v-else-if="activeTimeConfig?.status === TimeStatusEnum.STARTED">马上抢</span>
<span v-else></span>
<button
:class="[
'ss-reset-button cart-btn',
{ disabled: activeTimeConfig?.status === TimeStatusEnum.END },
]"
v-if="activeTimeConfig?.status === TimeStatusEnum.WAIT_START"
>
<span>未开始</span>
</button>
<button
:class="[
'ss-reset-button cart-btn',
{ disabled: activeTimeConfig?.status === TimeStatusEnum.END },
]"
@click="sheep.$router.go('/pages/goods/seckill', { id: activity.id })"
v-else-if="activeTimeConfig?.status === TimeStatusEnum.STARTED"
>
<span>马上抢</span>
</button>
<button
:class="[
'ss-reset-button cart-btn',
{ disabled: activeTimeConfig?.status === TimeStatusEnum.END },
]"
v-else
>
<span>已结束</span>
</button>
</template>
</s-goods-column>
@ -100,40 +145,51 @@
</s-layout>
</template>
<script setup>
import {reactive, computed, ref, nextTick} from 'vue';
import { computed, nextTick, reactive, ref } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import { useDurationTime } from '@/sheep/hooks/useGoods';
import SeckillApi from "@/sheep/api/promotion/seckill";
import dayjs from "dayjs";
import {TimeStatusEnum} from "@/sheep/util/const";
import SeckillApi from '@/sheep/api/promotion/seckill';
import dayjs from 'dayjs';
import { TimeStatusEnum } from '@/sheep/helper/const';
//
const { safeAreaInsets, safeArea } = sheep.$platform.device;
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const pageHeight = (safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sheep.$platform.navbar - 350;
const pageHeight =
(safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sheep.$platform.navbar - 350;
const headerBg = sheep.$url.css('/static/img/shop/goods/seckill-header.png');
//
const goodsFields = {
name: { show: true },
introduction: { show: true },
price: { show: true },
marketPrice: { show: true },
name: {
show: true,
},
introduction: {
show: true,
},
price: {
show: true,
},
marketPrice: {
show: true,
},
};
//#region
//
const timeConfigList = ref([])
const timeConfigList = ref([]);
//
const getSeckillConfigList = async () => {
const { data } = await SeckillApi.getSeckillConfigList()
const { data } = await SeckillApi.getSeckillConfigList();
const now = dayjs();
const today = now.format('YYYY-MM-DD')
const today = now.format('YYYY-MM-DD');
const select = ref([]);
//
data.forEach((config, index) => {
const startTime = dayjs(`${today} ${config.startTime}`)
const endTime = dayjs(`${today} ${config.endTime}`)
const startTime = dayjs(`${today} ${config.startTime}`);
const endTime = dayjs(`${today} ${config.endTime}`);
select.value[index] = config.id;
if (now.isBefore(startTime)) {
config.status = TimeStatusEnum.WAIT_START;
} else if (now.isAfter(endTime)) {
@ -142,35 +198,36 @@
config.status = TimeStatusEnum.STARTED;
activeTimeIndex.value = index;
}
})
timeConfigList.value = data
});
timeConfigList.value = data;
//
handleChangeTimeConfig(activeTimeIndex.value);
handleChangeTimeConfig(activeTimeIndex.value, select.value[activeTimeIndex.value]);
//
scrollToTimeConfig(activeTimeIndex.value)
}
scrollToTimeConfig(activeTimeIndex.value);
};
//
const activeTimeElId = ref('') // ID
const activeTimeElId = ref(''); // ID
const scrollToTimeConfig = (index) => {
nextTick(() => activeTimeElId.value = `timeItem${index}`)
}
nextTick(() => (activeTimeElId.value = `timeItem${index}`));
};
//
const activeTimeIndex = ref(0) //
const activeTimeConfig = computed(() => timeConfigList.value[activeTimeIndex.value]) //
const handleChangeTimeConfig = (index) => {
activeTimeIndex.value = index
const activeTimeIndex = ref(0); //
const activeTimeConfig = computed(() => timeConfigList.value[activeTimeIndex.value]); //
const handleChangeTimeConfig = (index, id) => {
activeTimeIndex.value = index;
//
activityPageParams.pageNo = 1
activityList.value = []
activityPageParams.pageNo = 1;
activityPageParams.configId = id;
activityList.value = [];
getActivityList();
}
};
//
const countDown = computed(() => {
const endTime = activeTimeConfig.value?.endTime
const endTime = activeTimeConfig.value?.endTime;
if (endTime) {
return useDurationTime(`${dayjs().format('YYYY-MM-DD')} ${endTime}`);
}
@ -182,20 +239,22 @@
//
const activityPageParams = reactive({
id: 0, // ID
configId: 0, // ID
pageNo: 1, //
pageSize: 5, //
})
const activityTotal = ref(0) //
const activityList = ref([]) //
const loadStatus = ref('') //
});
const activityTotal = ref(0); //
const activityList = ref([]); //
const loadStatus = ref(''); //
async function getActivityList() {
loadStatus.value = 'loading';
const { data } = await SeckillApi.getSeckillActivityPage(activityPageParams)
data.list.forEach(activity => {
const { data } = await SeckillApi.getSeckillActivityPage(activityPageParams);
data.list.forEach((activity) => {
//
activity.percent = parseInt(100 * (activity.totalStock - activity.stock) / activity.totalStock);
})
activity.percent = parseInt(
(100 * (activity.totalStock - activity.stock)) / activity.totalStock,
);
});
activityList.value = activityList.value.concat(...data.list);
activityTotal.value = data.total;
@ -205,7 +264,7 @@
//
function loadMore() {
if (loadStatus.value !== 'noMore') {
activityPageParams.pageNo += 1
activityPageParams.pageNo += 1;
getActivityList();
}
}
@ -216,7 +275,7 @@
//
onLoad(async () => {
await getSeckillConfigList()
await getSeckillConfigList();
});
</script>
<style lang="scss" scoped>
@ -235,7 +294,8 @@
margin: -276rpx auto 0 auto;
border-radius: 14rpx;
overflow: hidden;
swiper{
swiper {
height: 330rpx !important;
border-radius: 14rpx;
overflow: hidden;
@ -246,7 +306,8 @@
height: 100%;
border-radius: 14rpx;
overflow: hidden;
img{
img {
border-radius: 14rpx;
}
}
@ -257,10 +318,12 @@
width: 75rpx;
height: 70rpx;
}
//
.time-list {
width: 596rpx;
white-space: nowrap;
//
.item {
display: inline-block;
@ -270,17 +333,20 @@
box-sizing: border-box;
margin-right: 30rpx;
width: 130rpx;
//
.time {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
//
&.active {
.time {
color: var(--ui-BG-Main);
}
//
.status {
height: 30rpx;
@ -301,6 +367,7 @@
margin: 0 20rpx 0 20rpx;
background: #fff;
border-radius: 20rpx 20rpx 0 0;
.content-header {
width: 100%;
border-radius: 20rpx 20rpx 0 0;
@ -312,6 +379,7 @@
height: 64rpx;
background: rgba($color: #fff, $alpha: 0.66);
border-radius: 32px;
//
.countdown-title {
font-size: 28rpx;
@ -319,10 +387,12 @@
color: #333333;
line-height: 28rpx;
}
//
.countdown-time {
font-size: 28rpx;
color: rgba(#ed3c30, 0.23);
//
.countdown-h {
font-size: 24rpx;
@ -334,6 +404,7 @@
background: rgba(#ed3c30, 0.23);
border-radius: 6rpx;
}
//
.countdown-num {
font-size: 24rpx;
@ -348,12 +419,15 @@
}
}
}
//
.scroll-box {
height: 900rpx;
//
.goods-box {
position: relative;
//
.cart-btn {
position: absolute;
@ -373,6 +447,7 @@
color: #fff;
}
}
//
.limit {
font-size: 22rpx;

View File

@ -25,7 +25,7 @@
<view class="item" v-for="(item, index) in state.signConfigList" :key="index">
<view
:class="
(index === state.signConfigList.length ? 'reward' : '') +
(index === state.signConfigList.length ? 'reward' : '') +
' ' +
(state.signInfo.continuousDay >= item.day ? 'rewardTxt' : '')
"
@ -62,13 +62,14 @@
</view>
</view>
<!-- 签到说明 TODO @科举这里改成已累计签到 -->
<!-- 签到说明 -->
<view class="bg-white ss-m-t-16 ss-p-t-30 ss-p-b-60 ss-p-x-40">
<view class="activity-title ss-m-b-30">签到说明</view>
<view class="activity-des">1已累计签到{{state.signInfo.totalDay}}</view>
<view class="activity-des">1.已累计签到{{ state.signInfo.totalDay }}</view>
<view class="activity-des">
2据说连续签到第 {{ state.maxDay }} 天可获得超额积分一定要坚持签到哦~~~
2.据说连续签到第 {{ state.maxDay }} 天可获得超额积分要坚持签到哦~~
</view>
<view class="activity-des"> 3.积分可以在购物时抵现金结算的哦 ~~</view>
</view>
</view>
@ -110,7 +111,7 @@
signInfo: {}, //
signConfigList: [], //
maxDay: 0, //
maxDay: 0, //
showModel: false, //
signResult: {}, //
@ -159,7 +160,6 @@
getSignInfo();
getSignConfigList();
});
// TODO 1css 2
</script>
<style lang="scss" scoped>
@ -347,8 +347,7 @@
.title {
font-size: 34rpx;
font-weight: bold;
// color: var(--ui-BG-Main);
color: #ff6000;
color: var(--ui-BG-Main-TC);
}
.subtitle {
@ -410,12 +409,10 @@
background-color: #f4b409;
border-radius: 16rpx;
font-size: 20rpx;
color: #a57d3f;
color: var(--ui-BG-Main-TC);
line-height: 32rpx;
}
.venusSelect {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAYAAACohjseAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowYWFmYjU3Mi03MGJhLTRiNDctOTI2Yi0zOThlZDkzZDkxMDkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzkwRkI4NEFEMDFCMTFFODhDNDdBMDVGOTBBN0U2NTQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzkwRkI4NDlEMDFCMTFFODhDNDdBMDVGOTBBN0U2NTQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKFdpbmRvd3MpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ZmRjNTM0MmUtNmFkOC1iMDRhLThjZTEtMjk2YWYzM2FkMmUxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjBhYWZiNTcyLTcwYmEtNGI0Ny05MjZiLTM5OGVkOTNkOTEwOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkX00M0AAAfVSURBVHjaxFpdcBNVFD67STbppn9poU0LtSBYykBlgKrjqKOtihbq8ICKILyJwxM+6fjCk2/6pC+O4hs+6fgAbWFw1OID+ICMAwwKlRmKLW3Tpvlrskk3m13Pvdm0NNn//HA73+ymd3Pv+e4595xzT5bJ3DgJNWyMelUqNaD36a8N+9kaEeMR3yAERES952sxsbtya2lIbgQxqH72IT5EbEW8pZKuHkGlugyLyT3aBtW+qpJkKb/qgEeMIAYNnhlUn+Edz2NuokqtNVdTTbqVyhO0Q67qJMt2MnXbTq/cp+9+ZkyOYYFxN4EiLaF5SbokccyKkWRS1z4we4ZDfIE4hmhxvJLNu8HTtg85ekGRRcjOXwIp9pfT4aKIs4iP+f4zYrl78HPEqbLMJNAPXHBI/SQjSTd+PoD3LpCi15wMGVBlSiM+NfSihJ8Jjlt4Rheu5n7wtL+B93IJPO37aH8Z45+ohBdtKUtz7a+jJLK+/dN+BTX5p5MpWh6HF6XNE9iLwr9mSG6VZP65bPR6NVI1BwQZF3BtA+Bq2oG3Pt3HFCVnfUHaX6XQHCeXgVz8Nojz4+SDzTgo2yfIBV9G89utzi5DtRvDcnQ+JSciyd+rH+hdjb22tFOp5mreCUrocg0CPet5LATJvHbldWSiGllIzZpdeR2ZqFPtZTNJSIQeQGv3DucEbcrLmkRSXvP/cs4RZm6Ow/T1McffpyiSJTZ+jDfOZFDlOuARo5p9aKJ2IYkpCN+9AmLsIcSm/3Y0BkWpPCPRX9/nDVI1BTTAI0YRA9r99gWbv3MF90MGvG4WQrfHnRMslWcQMRL55SivbaJk064FjxhFDGj0gad5u22zkrMZiExcBQ7JcW4GlhfuQ3L+gSMTzc9fItcgYiTy8xHeTIOGmnMHtoEn2G971cP3MAUTk+B2MeBBkh4kGbp92ZEGyfxEDj1NLl56j9fbg4U9N6C179zNPZhK7cV7yRZkSYTI3atIjgUXywCLINf03L8gRGZtj0dA5CDyaMg5SPZk+OLhFZLMwtg7hTLDKGJAM09s6QGuY6+upxLTKcgkFmE5FUdFIYQEiHgv4VURl4BjgZon0SA9EeKKi1kZliU8Nnn84OabwIPw+huB8zdR+OqbwdcYMAwB4ux1yEYmtLp+I5WBdft/EApx8Cs9cu6mblyxXXTl9FpOFGDyjzGQMwlwIQlUEDAMagqvhBTVHl4ZplDezpsq+UdOTkMuIcByfBYElCWHmsgpLHQ/NwTe+gaTBH0XWkgGpPgDrfLHl4gTTOj8IVLdTqkF2aIslwF+2zCeGDjzIJ5OwtSVc6Cko9QMCUmWyZMiZJn8cI8E7Lwm11wJOTypBPuHoCG4yVrgz2VBmFBj69pGTvv1hVSN0XSxHE8LRUbaW9G01wdPvHgQQtcugJwMU5LFpIrWDjXMUGIsMNRR5DwcBPcMQV1L0NKchYHIkU2WkiVrTvgT6XEjyN/TY08R5KyAaWc6n3tagMvjhuCzQ+jlOmylVPRZjw/an9kPdYF1lucjIPLJmERoyP9j+8GflIIXPYW4XOKVJAmWZ27Y8nAMaq5t9yB4WjeumJ4hOaIErx/W978JXH2TbY9K5ZNKMhzC5dSjyTapQ5IyFxozvLJGz9Hp/Am+Y7utH8kan+yDhYVp8lVgdDWX33f+ji3gruNpnLPTxNl/8vKtbeTAeKDj0DmhuGShT3Jxkp4guGCv5cnT0QX0hgq4dOnBSshIxxehUbZJbu4OSJEpbXJvnxf0zoP6JMP/keVGTfZYEiCDQlttmXjE1hlTnJ3A+Ketuc53RwSz86AuSXFxitYzueBWUyGWE9E1pqiozoRhXdQJMLAaEyUMMdJyGp2Ux4Lm7iG5h5rkNhweFUpLFtonZANNTmOA3WzmFiGLGY3XlSel0DpOCzRs6gWXj4fkgwnIhKZIcKDhgsTLTCICfMs683gb1tbchiNjgt0TfYFkym7JQkyl8nkoixkMesiGzT1Q19q20t+0dTvwnU/A0iQSDYeoNinBQLOTE/2BjUcvCE5rMoJ2XcSYYBr3FOPmoHHLU+APdtJMpvg7bp8XAr19mKJ1weK9u5CJxUDpMt+HxfJ2HbsoVLzwa1aT4fw8dD3/Au43lv7YYjQF19gAHXv6UYMJa7UepQZFJzMT9fp9lJidorCvgbfkSRXbBB2UDRX5MdREnZYNwZECRcxQXLUnl8s5KPw6MNFsdBFzzdaaE8xGwrUx0eXZORrw3c1NNdEk0ZwUi2OQn7fvZBz9fEZKDjNzFLqn7dYApnVtNtKvecx5oxVfHCsmSt4ts/0rrxiO5NO6jvUWyC0guZgT+SPllu4Jzjr9AT0bjqKWQ1qH0RWQfvKcwzm+s6BB01X6RC1pHIf82w02NRmnsng7WjX28iJqLu5Ec4XXSE5XYg+S91A+UlHS/H2riXfq1n3N8mM2HKOOgutsodkNqZKIMxGQokvFw40jhnFMoZZ70CzyrpLd2S0kb00Oa5KMJDC8LAHL4QEmK4HGKYaSq+/bJFTyZ/GyX+VK3pzUStA1SRJrkTNZrWHG1e8IGuMZt5eqrUH9U8iwUbVci1w1BKnW65RWSVaVnBomAKoI3E9IQEEipX3jap9Q1hxmBElBocp/AmIYcQaRQSQQ36r/E8odvepOxoa5khfRT1pf+8q0/wUYAFU/P0XyeZQPAAAAAElFTkSuQmCC');
text-align: center;
padding: 2rpx;
}
.venus {
@ -424,7 +421,11 @@
background-size: 100% 100%;
width: 56rpx;
height: 56rpx;
margin: 10rpx 0;
margin: 10rpx auto;
}
.venusSelect {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADgAAAA4CAYAAACohjseAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA3ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQyIDc5LjE2MDkyNCwgMjAxNy8wNy8xMy0wMTowNjozOSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDowYWFmYjU3Mi03MGJhLTRiNDctOTI2Yi0zOThlZDkzZDkxMDkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzkwRkI4NEFEMDFCMTFFODhDNDdBMDVGOTBBN0U2NTQiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzkwRkI4NDlEMDFCMTFFODhDNDdBMDVGOTBBN0U2NTQiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTggKFdpbmRvd3MpIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ZmRjNTM0MmUtNmFkOC1iMDRhLThjZTEtMjk2YWYzM2FkMmUxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjBhYWZiNTcyLTcwYmEtNGI0Ny05MjZiLTM5OGVkOTNkOTEwOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkX00M0AAAfVSURBVHjaxFpdcBNVFD67STbppn9poU0LtSBYykBlgKrjqKOtihbq8ICKILyJwxM+6fjCk2/6pC+O4hs+6fgAbWFw1OID+ICMAwwKlRmKLW3Tpvlrskk3m13Pvdm0NNn//HA73+ymd3Pv+e4595xzT5bJ3DgJNWyMelUqNaD36a8N+9kaEeMR3yAERES952sxsbtya2lIbgQxqH72IT5EbEW8pZKuHkGlugyLyT3aBtW+qpJkKb/qgEeMIAYNnhlUn+Edz2NuokqtNVdTTbqVyhO0Q67qJMt2MnXbTq/cp+9+ZkyOYYFxN4EiLaF5SbokccyKkWRS1z4we4ZDfIE4hmhxvJLNu8HTtg85ekGRRcjOXwIp9pfT4aKIs4iP+f4zYrl78HPEqbLMJNAPXHBI/SQjSTd+PoD3LpCi15wMGVBlSiM+NfSihJ8Jjlt4Rheu5n7wtL+B93IJPO37aH8Z45+ohBdtKUtz7a+jJLK+/dN+BTX5p5MpWh6HF6XNE9iLwr9mSG6VZP65bPR6NVI1BwQZF3BtA+Bq2oG3Pt3HFCVnfUHaX6XQHCeXgVz8Nojz4+SDzTgo2yfIBV9G89utzi5DtRvDcnQ+JSciyd+rH+hdjb22tFOp5mreCUrocg0CPet5LATJvHbldWSiGllIzZpdeR2ZqFPtZTNJSIQeQGv3DucEbcrLmkRSXvP/cs4RZm6Ow/T1McffpyiSJTZ+jDfOZFDlOuARo5p9aKJ2IYkpCN+9AmLsIcSm/3Y0BkWpPCPRX9/nDVI1BTTAI0YRA9r99gWbv3MF90MGvG4WQrfHnRMslWcQMRL55SivbaJk064FjxhFDGj0gad5u22zkrMZiExcBQ7JcW4GlhfuQ3L+gSMTzc9fItcgYiTy8xHeTIOGmnMHtoEn2G971cP3MAUTk+B2MeBBkh4kGbp92ZEGyfxEDj1NLl56j9fbg4U9N6C179zNPZhK7cV7yRZkSYTI3atIjgUXywCLINf03L8gRGZtj0dA5CDyaMg5SPZk+OLhFZLMwtg7hTLDKGJAM09s6QGuY6+upxLTKcgkFmE5FUdFIYQEiHgv4VURl4BjgZon0SA9EeKKi1kZliU8Nnn84OabwIPw+huB8zdR+OqbwdcYMAwB4ux1yEYmtLp+I5WBdft/EApx8Cs9cu6mblyxXXTl9FpOFGDyjzGQMwlwIQlUEDAMagqvhBTVHl4ZplDezpsq+UdOTkMuIcByfBYElCWHmsgpLHQ/NwTe+gaTBH0XWkgGpPgDrfLHl4gTTOj8IVLdTqkF2aIslwF+2zCeGDjzIJ5OwtSVc6Cko9QMCUmWyZMiZJn8cI8E7Lwm11wJOTypBPuHoCG4yVrgz2VBmFBj69pGTvv1hVSN0XSxHE8LRUbaW9G01wdPvHgQQtcugJwMU5LFpIrWDjXMUGIsMNRR5DwcBPcMQV1L0NKchYHIkU2WkiVrTvgT6XEjyN/TY08R5KyAaWc6n3tagMvjhuCzQ+jlOmylVPRZjw/an9kPdYF1lucjIPLJmERoyP9j+8GflIIXPYW4XOKVJAmWZ27Y8nAMaq5t9yB4WjeumJ4hOaIErx/W978JXH2TbY9K5ZNKMhzC5dSjyTapQ5IyFxozvLJGz9Hp/Am+Y7utH8kan+yDhYVp8lVgdDWX33f+ji3gruNpnLPTxNl/8vKtbeTAeKDj0DmhuGShT3Jxkp4guGCv5cnT0QX0hgq4dOnBSshIxxehUbZJbu4OSJEpbXJvnxf0zoP6JMP/keVGTfZYEiCDQlttmXjE1hlTnJ3A+Ketuc53RwSz86AuSXFxitYzueBWUyGWE9E1pqiozoRhXdQJMLAaEyUMMdJyGp2Ux4Lm7iG5h5rkNhweFUpLFtonZANNTmOA3WzmFiGLGY3XlSel0DpOCzRs6gWXj4fkgwnIhKZIcKDhgsTLTCICfMs683gb1tbchiNjgt0TfYFkym7JQkyl8nkoixkMesiGzT1Q19q20t+0dTvwnU/A0iQSDYeoNinBQLOTE/2BjUcvCE5rMoJ2XcSYYBr3FOPmoHHLU+APdtJMpvg7bp8XAr19mKJ1weK9u5CJxUDpMt+HxfJ2HbsoVLzwa1aT4fw8dD3/Au43lv7YYjQF19gAHXv6UYMJa7UepQZFJzMT9fp9lJidorCvgbfkSRXbBB2UDRX5MdREnZYNwZECRcxQXLUnl8s5KPw6MNFsdBFzzdaaE8xGwrUx0eXZORrw3c1NNdEk0ZwUi2OQn7fvZBz9fEZKDjNzFLqn7dYApnVtNtKvecx5oxVfHCsmSt4ts/0rrxiO5NO6jvUWyC0guZgT+SPllu4Jzjr9AT0bjqKWQ1qH0RWQfvKcwzm+s6BB01X6RC1pHIf82w02NRmnsng7WjX28iJqLu5Ec4XXSE5XYg+S91A+UlHS/H2riXfq1n3N8mM2HKOOgutsodkNqZKIMxGQokvFw40jhnFMoZZ70CzyrpLd2S0kb00Oa5KMJDC8LAHL4QEmK4HGKYaSq+/bJFTyZ/GyX+VK3pzUStA1SRJrkTNZrWHG1e8IGuMZt5eqrUH9U8iwUbVci1w1BKnW65RWSVaVnBomAKoI3E9IQEEipX3jap9Q1hxmBElBocp/AmIYcQaRQSQQ36r/E8odvepOxoa5khfRT1pf+8q0/wUYAFU/P0XyeZQPAAAAAElFTkSuQmCC');
}
.num {
@ -435,7 +436,7 @@
.item {
align-items: center;
justify-content: space-between;
border-bottom: 1px solid #eee;
flex-direction: column;
height: 130rpx;
}
@ -446,6 +447,6 @@
}
.on {
background-color: #999 !important;
color: #f4b409;
}
</style>

View File

@ -1,23 +1,15 @@
<template>
<view class="goods ss-flex">
<image class="image" :src="sheep.$url.cdn(goodsData.image)" mode="aspectFill"> </image>
<view class="ss-flex-1">
<view class="title ss-line-2">
{{ goodsData.title }}
</view>
<view v-if="goodsData.subtitle" class="subtitle ss-line-1">
{{ goodsData.subtitle }}
</view>
<view class="price ss-m-t-8">
{{ isArray(goodsData.price) ? goodsData.price[0] : goodsData.price }}
</view>
</view>
</view>
<s-goods-item
:title="goodsData.spuName"
:img="goodsData.picUrl"
:price="goodsData.price"
:skuText="goodsData.introduction"
priceColor="#FF3000"
:titleWidth="400"
/>
</template>
<script setup>
import sheep from '@/sheep';
import { isArray } from 'lodash';
const props = defineProps({
goodsData: {
@ -27,37 +19,3 @@
});
</script>
<style lang="scss" scoped>
.goods {
background: #fff;
padding: 20rpx;
border-radius: 12rpx;
.image {
width: 116rpx;
height: 116rpx;
flex-shrink: 0;
margin-right: 20rpx;
}
.title {
height: 64rpx;
line-height: 32rpx;
font-size: 26rpx;
font-weight: 500;
color: #333;
}
.subtitle {
font-size: 24rpx;
font-weight: 400;
color: #999;
}
.price {
font-size: 26rpx;
font-weight: 500;
color: #ff3000;
}
}
</style>

View File

@ -0,0 +1,110 @@
<template>
<view class="send-wrap ss-flex">
<view class="left ss-flex ss-flex-1">
<optimize-input
class="ss-flex-1 ss-p-l-22"
:inputBorder="false"
:clearable="false"
v-model="message"
placeholder="请输入你要咨询的问题"
></optimize-input>
</view>
<text class="sicon-basic bq" @tap.stop="onTools('emoji')"></text>
<text
v-if="!message"
class="sicon-edit"
:class="{ 'is-active': toolsMode === 'tools' }"
@tap.stop="onTools('tools')"
/>
<button
v-if="message"
class="ss-reset-button send-btn"
@tap="sendMessage"
:disabled="isDisabled || sending"
:class="{ disabled: isDisabled || sending }"
>
<text v-if="sending"></text>
<text v-else></text>
</button>
</view>
</template>
<script setup>
import { computed, ref, onUnmounted } from 'vue';
import OptimizeInput from '@/pages/chat/components/optimize-input.vue';
/**
* 消息发送组件
*/
const props = defineProps({
//
modelValue: {
type: String,
default: '',
},
//
toolsMode: {
type: String,
default: '',
},
});
const emits = defineEmits(['update:modelValue', 'onTools', 'sendMessage']);
const message = computed({
get() {
return props.modelValue;
},
set(newValue) {
emits(`update:modelValue`, newValue);
}
});
//
function onTools(mode) {
emits('onTools', mode);
}
//
function sendMessage() {
emits('sendMessage');
}
</script>
<style scoped lang="scss">
.send-wrap {
padding: 18rpx 20rpx;
background: #fff;
.left {
height: 64rpx;
border-radius: 32rpx;
background: var(--ui-BG-1);
}
.bq {
font-size: 50rpx;
margin-left: 10rpx;
}
.sicon-edit {
font-size: 50rpx;
margin-left: 10rpx;
transform: rotate(0deg);
transition: all linear 0.2s;
&.is-active {
transform: rotate(45deg);
}
}
.send-btn {
width: 100rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 30rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
font-size: 26rpx;
color: #fff;
margin-left: 11rpx;
}
}
</style>

View File

@ -0,0 +1,293 @@
<template>
<!-- 聊天列表使用scroll-view原生组件整体倒置 -->
<scroll-view
:scroll-top="scroll.top"
class="chat-scroll-view"
scroll-y
:refresher-enabled="false"
@scroll="onScroll"
@scrolltolower="loadMoreHistory"
style="transform: scaleY(-1)"
>
<!-- 消息列表容器 -->
<view class="message-container">
<!-- 加载更多提示 -->
<view v-if="isLoading" class="loading-more" style="transform: scaleY(-1)">
<text>加载中...</text>
</view>
<!-- 消息列表 -->
<view class="message-list">
<view
v-for="(item, index) in messageList"
:key="item.id"
class="message-item"
style="transform: scaleY(-1)"
>
<!-- 消息渲染 -->
<MessageListItem
:message="item"
:message-index="index"
:message-list="messageList"
></MessageListItem>
</view>
</view>
</view>
</scroll-view>
<!-- 底部聊天输入框 -->
<su-fixed bottom>
<view v-if="showTip" class="back-top ss-flex ss-row-center ss-m-b-10" @tap="scrollToTop">
<text class="back-top-item ss-flex ss-row-center">
{{ showNewMessageTip ? '有新消息' : '回到底部' }}
</text>
</view>
<slot name="bottom"></slot>
</su-fixed>
</template>
<script setup>
import MessageListItem from '@/pages/chat/components/messageListItem.vue';
import { onMounted, reactive, ref, computed } from 'vue';
import KeFuApi from '@/sheep/api/promotion/kefu';
import { isEmpty } from '@/sheep/helper/utils';
import { formatDate } from '@/sheep/helper/utils';
import sheep from '@/sheep';
const { safeAreaInsets } = sheep.$platform.device;
const safeAreaInsetsBottom = safeAreaInsets.bottom + 'px'; //
const messageList = ref([]); //
const showTip = ref(false); //
const showNewMessageTip = ref(false); //
const refreshMessage = ref(false); //
const isLoading = ref(false); //
const hasMore = ref(true); //
const keyboardHeight = ref(0); //
const scroll = ref({
top: 0,
oldTop: 0,
}); //
const queryParams = reactive({
no: 1,
limit: 20,
createTime: undefined,
}); //
//
const chatScrollHeight = computed(() => {
const baseHeight = 'calc(100vh - 150px - ' + safeAreaInsetsBottom + ')';
if (keyboardHeight.value > 0) {
//
return `calc(${baseHeight} - ${keyboardHeight.value}px)`;
}
return baseHeight;
});
//
const getMessageList = async () => {
isLoading.value = true;
try {
const { data } = await KeFuApi.getKefuMessageList(queryParams);
if (isEmpty(data)) {
hasMore.value = false;
return;
}
if (queryParams.no > 1 && refreshMessage.value) {
const newMessageList = [];
for (const message of data) {
if (messageList.value.some((val) => val.id === message.id)) {
continue;
}
newMessageList.push(message);
}
//
messageList.value = [...newMessageList, ...messageList.value];
refreshMessage.value = false; //
return;
}
if (queryParams.no > 1) {
// /
if (data.length < queryParams.limit) {
hasMore.value = false; //
}
//
const historyMessages = data.filter(
(msg) => !messageList.value.some((existing) => existing.id === msg.id),
);
if (historyMessages.length > 0) {
messageList.value = [...messageList.value, ...historyMessages];
}
} else {
//
messageList.value = data;
if (data.length < queryParams.limit) {
hasMore.value = false;
}
}
if (data.slice(-1).length > 0) {
// createTime
queryParams.createTime = formatDate(data.slice(-1)[0].createTime);
}
} finally {
isLoading.value = false;
}
};
/** 加载更多历史数据 */
const loadMoreHistory = async () => {
if (isLoading.value || !hasMore.value) return;
//
queryParams.no += 1;
await getMessageList();
};
/** 刷新消息列表 */
const refreshMessageList = async (message = undefined) => {
if (typeof message !== 'undefined') {
// /
messageList.value.unshift(message);
showNewMessageTip.value = true;
} else {
queryParams.createTime = undefined;
refreshMessage.value = true;
await getMessageList();
}
//
if (queryParams.no > 1) {
showTip.value = true;
} else {
scrollToTop();
}
};
/** 滚动到顶部(倒置后相当于滚动到最新消息) */
const scrollToTop = () => {
scroll.value.top = scroll.value.oldTop;
setTimeout(() => {
scroll.value.top = 0;
}, 200); // view
showTip.value = false;
};
/** 设置键盘高度 */
const setKeyboardHeight = (height) => {
keyboardHeight.value = height;
//
if (height > 0) {
scrollToTop();
}
};
defineExpose({ getMessageList, refreshMessageList });
/** 监听消息列表滚动 */
const onScroll = (e) => {
const { scrollTop } = e.detail;
scroll.value.oldTop = scrollTop;
// ""
if (scrollTop > 100) {
showTip.value = true;
} else {
showTip.value = false;
}
};
//
const setupKeyboardListeners = () => {
// #ifdef H5
// H5
window.addEventListener('resize', () => {
//
if (
document.activeElement &&
(document.activeElement.tagName === 'INPUT' ||
document.activeElement.tagName === 'TEXTAREA')
) {
//
const currentHeight = window.innerHeight;
const viewportHeight = window.visualViewport
? window.visualViewport.height
: window.innerHeight;
const keyboardHeight = currentHeight - viewportHeight;
setKeyboardHeight(keyboardHeight > 0 ? keyboardHeight : 0);
} else {
setKeyboardHeight(0);
}
});
// #endif
// #ifdef MP-WEIXIN
// TODO puhui999:
//
uni.onKeyboardHeightChange((res) => {
setKeyboardHeight(res.height);
});
// #endif
};
onMounted(() => {
queryParams.no = 1; //
scroll.value = {
top: 0,
oldTop: 0,
};
getMessageList();
setupKeyboardListeners();
});
</script>
<style lang="scss" scoped>
.chat-scroll-view {
height: v-bind(chatScrollHeight);
width: 100%;
position: relative;
background-color: #f8f8f8;
z-index: 1;
}
.message-container {
width: 100%;
/* 确保容器至少有一屏高度 */
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.message-list {
width: 100%;
display: flex;
flex-direction: column;
padding-bottom: 20px;
}
.message-item {
margin-bottom: 10px;
}
.loading-more {
width: 100%;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
color: #999;
font-size: 14px;
}
.back-top {
.back-top-item {
height: 30px;
width: 100px;
background-color: #fff;
border-radius: 30px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
}
</style>

View File

@ -0,0 +1,297 @@
<template>
<view class="chat-box">
<!-- 消息渲染 -->
<view class="message-item ss-flex-col scroll-item">
<view class="ss-flex ss-row-center ss-col-center">
<!-- 系统消息 -->
<view
v-if="message.contentType === KeFuMessageContentTypeEnum.SYSTEM"
class="system-message"
>
{{ message.content }}
</view>
<!-- 日期 - 移到消息内容上方显示 -->
<view
v-if="
message.contentType !== KeFuMessageContentTypeEnum.SYSTEM &&
showTime(message, messageIndex)
"
class="date-message"
>
{{ formatDate(message.createTime) }}
</view>
</view>
<!-- 消息体渲染管理员消息和用户消息并左右展示 -->
<view
v-if="message.contentType !== KeFuMessageContentTypeEnum.SYSTEM"
class="ss-flex ss-col-top"
:class="[
message.senderType === UserTypeEnum.ADMIN
? `ss-row-left`
: message.senderType === UserTypeEnum.MEMBER
? `ss-row-right`
: '',
]"
>
<!-- 客服头像 -->
<image
v-show="message.senderType === UserTypeEnum.ADMIN"
class="chat-avatar ss-m-r-24"
:src="sheep.$url.cdn(message.senderAvatar) || sheep.$url.static('')"
mode="aspectFill"
lazy-load
/>
<!-- 内容 -->
<template v-if="message.contentType === KeFuMessageContentTypeEnum.TEXT">
<view class="message-box" :class="{ admin: message.senderType === UserTypeEnum.ADMIN }">
<mp-html :content="processedContent" :domain="sheep.$url.cdn('')" lazy-load />
</view>
</template>
<template v-if="message.contentType === KeFuMessageContentTypeEnum.IMAGE">
<view
class="message-box"
:class="{ admin: message.senderType === UserTypeEnum.ADMIN }"
:style="{ width: '200rpx' }"
>
<su-image
class="message-img"
isPreview
:previewList="[sheep.$url.cdn(getMessageContent(message).picUrl || message.content)]"
:current="0"
:src="sheep.$url.cdn(getMessageContent(message).picUrl || message.content)"
:height="200"
:width="200"
mode="aspectFill"
/>
</view>
</template>
<template v-if="message.contentType === KeFuMessageContentTypeEnum.PRODUCT">
<div class="ss-m-b-10">
<GoodsItem
:goodsData="getMessageContent(message)"
@tap="
sheep.$router.go('/pages/goods/index', { id: getMessageContent(message).spuId })
"
/>
</div>
</template>
<template v-if="message.contentType === KeFuMessageContentTypeEnum.ORDER">
<OrderItem
:orderData="getMessageContent(message)"
@tap="sheep.$router.go('/pages/order/detail', { id: getMessageContent(message).id })"
/>
</template>
<!-- user头像 -->
<image
v-if="message.senderType === UserTypeEnum.MEMBER"
class="chat-avatar ss-m-l-24"
:src="
sheep.$url.cdn(userInfo.avatar) ||
sheep.$url.static('/static/img/shop/chat/default.png')
"
mode="aspectFill"
>
</image>
</view>
</view>
</view>
</template>
<script setup>
import { computed, unref } from 'vue';
import dayjs from 'dayjs';
import { KeFuMessageContentTypeEnum, UserTypeEnum } from '@/pages/chat/util/constants';
import { emojiList } from '@/pages/chat/util/emoji';
import sheep from '@/sheep';
import { formatDate, jsonParse } from '@/sheep/helper/utils';
import GoodsItem from '@/pages/chat/components/goods.vue';
import OrderItem from '@/pages/chat/components/order.vue';
const props = defineProps({
//
message: {
type: Object,
default: () => ({}),
},
//
messageIndex: {
type: Number,
default: 0,
},
//
messageList: {
type: Array,
default: () => [],
},
});
const getMessageContent = computed(() => (item) => jsonParse(item.content)); //
const userInfo = computed(() => sheep.$store('user').userInfo);
//======================= =======================
const showTime = computed(() => (item, index) => {
if (unref(props.messageList)[index + 1]) {
let dateString = dayjs(unref(props.messageList)[index + 1].createTime).fromNow();
return dateString !== dayjs(unref(item).createTime).fromNow();
}
return false;
});
//
const emojiMap = computed(() => {
const map = new Map();
emojiList.forEach((emoji) => {
map.set(emoji.name, emoji.file);
});
return map;
});
// -
function replaceEmoji(data) {
let newData = data;
if (typeof newData !== 'object') {
let reg = /\[(.+?)]/g; // []
let zhEmojiName = newData.match(reg);
if (zhEmojiName) {
zhEmojiName.forEach((item) => {
const emojiFile = emojiMap.value.get(item) || '';
if (emojiFile) {
newData = newData.replace(
item,
`<img class="chat-img" style="width: 24px;height: 24px;margin: 0 3px;vertical-align: middle;" src="${sheep.$url.cdn(
'/static/img/chat/emoji/' + emojiFile,
)}"/>`,
);
}
});
}
}
return newData;
}
//
const processedContent = computed(() => {
if (props.message.contentType === KeFuMessageContentTypeEnum.TEXT) {
return replaceEmoji(getMessageContent.value(props.message).text || props.message.content);
}
return props.message.content;
});
</script>
<style scoped lang="scss">
.message-item {
margin-bottom: 10rpx;
}
.date-message,
.system-message {
width: fit-content;
border-radius: 12rpx;
padding: 8rpx 16rpx;
margin-bottom: 16rpx;
background-color: var(--ui-BG-3);
color: #999;
font-size: 24rpx;
}
.chat-avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
}
.send-status {
color: #333;
height: 80rpx;
margin-right: 8rpx;
display: flex;
align-items: center;
.loading {
width: 32rpx;
height: 32rpx;
-webkit-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite;
@-webkit-keyframes rotating {
0% {
transform: rotateZ(0);
}
100% {
transform: rotateZ(360deg);
}
}
@keyframes rotating {
0% {
transform: rotateZ(0);
}
100% {
transform: rotateZ(360deg);
}
}
}
.warning {
width: 32rpx;
height: 32rpx;
color: #ff3000;
}
}
.message-box {
max-width: 50%;
font-size: 16px;
white-space: normal;
word-break: break-all;
word-wrap: break-word;
padding: 20rpx;
color: #fff;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
margin-top: 3px;
margin-bottom: 9px;
border-top-left-radius: 10px;
border-bottom-right-radius: 10px;
border-bottom-left-radius: 10px;
&.admin {
background: #fff;
color: #333;
margin-top: 3px;
margin-bottom: 9px;
border-radius: 0 10px 10px 10px;
}
:deep() {
.imgred {
width: 100%;
}
.imgred,
img {
width: 100%;
}
}
}
:deep() {
.goods,
.order {
max-width: 500rpx;
}
}
.message-img {
width: 100px;
height: 100px;
border-radius: 6rpx;
}
.error-img {
width: 400rpx;
height: 400rpx;
}
</style>

View File

@ -0,0 +1,540 @@
<template>
<view
class="uni-easyinput"
:class="{ 'uni-easyinput-error': msg }"
:style="{ color: inputBorder && msg ? '#e43d33' : styles.color }"
>
<view
class="uni-easyinput__content"
:class="{
'is-input-border': inputBorder,
'is-input-error-border': inputBorder && msg,
'is-textarea': type === 'textarea',
'is-disabled': disabled
}"
:style="{
'border-color': inputBorder && msg ? '#dd524d' : styles.borderColor,
'background-color': disabled ? styles.disableColor : ''
}"
>
<uni-icons
v-if="prefixIcon"
class="content-clear-icon"
:type="prefixIcon"
color="#c0c4cc"
@click="onClickIcon('prefix')"
></uni-icons>
<textarea
v-if="type === 'textarea'"
class="uni-easyinput__content-textarea"
:class="{ 'input-padding': inputBorder }"
:name="name"
:value="val"
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
:disabled="disabled"
placeholder-class="uni-easyinput__placeholder-class"
:maxlength="inputMaxlength"
:focus="focused"
:autoHeight="autoHeight"
:adjust-position="false"
@input="onInput"
@blur="onBlur"
@focus="onFocus"
@confirm="onConfirm"
></textarea>
<input
v-else
:type="type === 'password' ? 'text' : type"
class="uni-easyinput__content-input"
:style="{
'padding-right': type === 'password' || clearable || prefixIcon ? '' : '10px',
'padding-left': paddingLeft + 'px'
}"
:name="name"
:value="val"
:password="!showPassword && type === 'password'"
:placeholder="placeholder"
:placeholderStyle="placeholderStyle"
placeholder-class="uni-easyinput__placeholder-class"
:disabled="disabled"
:maxlength="inputMaxlength"
:focus="focused"
:confirmType="confirmType"
:adjust-position="false"
@focus="onFocus"
@blur="onBlur"
@input="onInput"
@change="onInput"
@confirm="onConfirm"
:cursor-spacing="30"
always-embed
/>
<template v-if="type === 'password' && passwordIcon">
<uni-icons
v-if="val"
class="content-clear-icon"
:class="{ 'is-textarea-icon': type === 'textarea' }"
:type="showPassword ? 'eye-slash-filled' : 'eye-filled'"
:size="18"
color="#c0c4cc"
@click="onEyes"
></uni-icons>
</template>
<template v-else-if="suffixIcon">
<uni-icons
v-if="suffixIcon"
class="content-clear-icon"
:type="suffixIcon"
color="#c0c4cc"
@click="onClickIcon('suffix')"
></uni-icons>
</template>
<template v-else>
<uni-icons
class="content-clear-icon"
:class="{ 'is-textarea-icon': type === 'textarea' }"
type="clear"
:size="clearSize"
v-if="clearable && val && !disabled"
color="#c0c4cc"
@click="onClear"
></uni-icons>
</template>
<slot name="right"></slot>
</view>
</view>
</template>
<script>
// import {
// debounce,
// throttle
// } from './common.js'
/**
* Easyinput 输入框
* @description 此组件可以实现表单的输入与校验包括 "text" "textarea" 类型
* @tutorial https://ext.dcloud.net.cn/plugin?id=3455
* @property {String} value 输入内容
* @property {String } type 输入框的类型默认text password/text/textarea/..
* @value text 文本输入键盘
* @value textarea 多行文本输入键盘
* @value password 密码输入键盘
* @value number 数字输入键盘注意iOS上app-vue弹出的数字键盘并非9宫格方式
* @value idcard 身份证输入键盘支付宝百度QQ小程序
* @value digit 带小数点的数字键盘 App的nvue页面微信支付宝百度头条QQ小程序支持
* @property {Boolean} clearable 是否显示右侧清空内容的图标控件点击可清空输入框内容默认true
* @property {Boolean} autoHeight 是否自动增高输入区域type为textarea时有效默认false
* @property {String } placeholder 输入框的提示文字
* @property {String } placeholderStyle placeholder的样式(内联样式字符串)"color: #ddd"
* @property {Boolean} focus 是否自动获得焦点默认false
* @property {Boolean} disabled 是否禁用默认false
* @property {Number } maxlength 最大输入长度设置为 -1 的时候不限制最大长度默认140
* @property {String } confirmType 设置键盘右下角按钮的文字仅在type="text"时生效默认done
* @property {Number } clearSize 清除图标的大小单位px默认15
* @property {String} prefixIcon 输入框头部图标
* @property {String} suffixIcon 输入框尾部图标
* @property {Boolean} trim 是否自动去除两端的空格
* @value both 去除两端空格
* @value left 去除左侧空格
* @value right 去除右侧空格
* @value start 去除左侧空格
* @value end 去除右侧空格
* @value all 去除全部空格
* @value none 不去除空格
* @property {Boolean} inputBorder 是否显示input输入框的边框默认true
* @property {Boolean} passwordIcon type=password时是否显示小眼睛图标
* @property {Object} styles 自定义颜色
* @event {Function} input 输入框内容发生变化时触发
* @event {Function} focus 输入框获得焦点时触发
* @event {Function} blur 输入框失去焦点时触发
* @event {Function} confirm 点击完成按钮时触发
* @event {Function} iconClick 点击图标时触发
* @example <uni-easyinput v-model="mobile"></uni-easyinput>
*/
export default {
name: 'optimize-input',
emits: ['click', 'iconClick', 'update:modelValue', 'input', 'focus', 'blur', 'confirm'],
model: {
prop: 'modelValue',
event: 'update:modelValue'
},
props: {
name: String,
value: [Number, String],
modelValue: [Number, String],
type: {
type: String,
default: 'text'
},
clearable: {
type: Boolean,
default: true
},
autoHeight: {
type: Boolean,
default: false
},
placeholder: String,
placeholderStyle: String,
focus: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
maxlength: {
type: [Number, String],
default: 140
},
confirmType: {
type: String,
default: 'done'
},
clearSize: {
type: [Number, String],
default: 15
},
inputBorder: {
type: Boolean,
default: true
},
prefixIcon: {
type: String,
default: ''
},
suffixIcon: {
type: String,
default: ''
},
trim: {
type: [Boolean, String],
default: true
},
passwordIcon: {
type: Boolean,
default: true
},
styles: {
type: Object,
default() {
return {
color: '#333',
disableColor: '#F7F6F6',
borderColor: '#e5e5e5'
};
}
},
errorMessage: {
type: [String, Boolean],
default: ''
},
paddingLeft:{
type: [Number, String],
default: 0
}
},
data() {
return {
focused: false,
errMsg: '',
val: '',
showMsg: '',
border: false,
isFirstBorder: false,
showClearIcon: false,
showPassword: false
};
},
computed: {
msg() {
return this.errorMessage || this.errMsg;
},
// uniappinputmaxlength
inputMaxlength() {
return Number(this.maxlength);
}
},
watch: {
value(newVal) {
if (this.errMsg) this.errMsg = '';
this.val = newVal;
// fix by mehaotian is_reset uni-forms
if (this.form && this.formItem && !this.is_reset) {
this.is_reset = false;
this.formItem.setValue(newVal);
}
},
modelValue(newVal) {
if (this.errMsg) this.errMsg = '';
this.val = newVal;
if (this.form && this.formItem && !this.is_reset) {
this.is_reset = false;
this.formItem.setValue(newVal);
}
},
focus(newVal) {
this.$nextTick(() => {
this.focused = this.focus;
});
}
},
created() {
if (!this.value && this.value !== 0) {
this.val = this.modelValue;
}
if (!this.modelValue && this.modelValue !== 0) {
this.val = this.value;
}
this.form = this.getForm('uniForms');
this.formItem = this.getForm('uniFormsItem');
if (this.form && this.formItem) {
if (this.formItem.name) {
if (!this.is_reset) {
this.is_reset = false;
this.formItem.setValue(this.val);
}
this.rename = this.formItem.name;
this.form.inputChildrens.push(this);
}
}
},
mounted() {
this.$nextTick(() => {
this.focused = this.focus;
});
},
methods: {
/**
* 初始化变量值
*/
init() {},
onClickIcon(type) {
this.$emit('iconClick', type);
},
/**
* 获取父元素实例
*/
getForm(name = 'uniForms') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
},
onEyes() {
this.showPassword = !this.showPassword;
},
onInput(event) {
let value = event.detail.value;
//
if (this.trim) {
if (typeof this.trim === 'boolean' && this.trim) {
value = this.trimStr(value);
}
if (typeof this.trim === 'string') {
value = this.trimStr(value, this.trim);
}
}
if (this.errMsg) this.errMsg = '';
this.val = value;
// TODO vue2
this.$emit('input', value);
// TODO  vue3
this.$emit('update:modelValue', value);
},
onFocus(event) {
this.$emit('focus', event);
},
onBlur(event) {
let value = event.detail.value;
this.$emit('blur', event);
},
onConfirm(e) {
this.$emit('confirm', e.detail.value);
},
onClear(event) {
this.val = '';
// TODO vue2
this.$emit('input', '');
// TODO vue2
// TODO  vue3
this.$emit('update:modelValue', '');
},
fieldClick() {
this.$emit('click');
},
trimStr(str, pos = 'both') {
if (pos === 'both') {
return str.trim();
} else if (pos === 'left') {
return str.trimLeft();
} else if (pos === 'right') {
return str.trimRight();
} else if (pos === 'start') {
return str.trimStart();
} else if (pos === 'end') {
return str.trimEnd();
} else if (pos === 'all') {
return str.replace(/\s+/g, '');
} else if (pos === 'none') {
return str;
}
return str;
}
}
};
</script>
<style lang="scss">
$uni-error: #e43d33;
$uni-border-1: #dcdfe6 !default;
.uni-easyinput {
/* #ifndef APP-NVUE */
width: 100%;
/* #endif */
flex: 1;
position: relative;
text-align: left;
color: #333;
font-size: 14px;
}
.uni-easyinput__content {
flex: 1;
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
box-sizing: border-box;
min-height: 72rpx;
/* #endif */
flex-direction: row;
align-items: center;
}
.uni-easyinput__content-input {
/* #ifndef APP-NVUE */
width: auto;
/* #endif */
position: relative;
overflow: hidden;
flex: 1;
line-height: 56rpx;
font-size: 28rpx;
height: 56rpx;
}
.uni-easyinput__placeholder-class {
color: #bbbbbb;
font-size: 28rpx;
font-weight: 400;
line-height: normal;
}
.is-textarea {
align-items: flex-start;
}
.is-textarea-icon {
margin-top: 5px;
}
.uni-easyinput__content-textarea {
position: relative;
overflow: hidden;
flex: 1;
line-height: 1.5;
font-size: 14px;
padding-top: 6px;
padding-bottom: 10px;
height: 80px;
/* #ifndef APP-NVUE */
min-height: 80px;
width: auto;
/* #endif */
}
.input-padding {
padding-left: 10px;
}
.content-clear-icon {
padding: 0 5px;
}
.label-icon {
margin-right: 5px;
margin-top: -1px;
}
//
.is-input-border {
/* #ifndef APP-NVUE */
display: flex;
box-sizing: border-box;
/* #endif */
flex-direction: row;
align-items: center;
border: 1px solid $uni-border-1;
border-radius: 4px;
}
.uni-error-message {
position: absolute;
bottom: -17px;
left: 0;
line-height: 12px;
color: $uni-error;
font-size: 12px;
text-align: left;
}
.uni-error-msg--boeder {
position: relative;
bottom: 0;
line-height: 22px;
}
.is-input-error-border {
border-color: $uni-error;
.uni-easyinput__placeholder-class {
// color: mix(#fff, $uni-error, 50%);
}
}
.uni-easyinput--border {
margin-bottom: 0;
padding: 10px 15px;
// padding-bottom: 0;
border-top: 1px #eee solid;
}
.uni-easyinput-error {
padding-bottom: 0;
}
.is-first-border {
/* #ifndef APP-NVUE */
border: none;
/* #endif */
/* #ifdef APP-NVUE */
border-width: 0;
/* #endif */
}
.is-disabled {
border-color: red;
background-color: #f7f6f6;
color: #d5d5d5;
.uni-easyinput__placeholder-class {
color: #d5d5d5;
font-size: 12px;
}
}
</style>

View File

@ -1,49 +1,36 @@
<template>
<view class="order">
<view class="top ss-flex ss-row-between">
<span>{{ orderData.order_sn }}</span>
<span>{{ orderData.create_time.split(' ')[1] }}</span>
<view class="bg-white order-list-card-box ss-r-10 ss-m-t-14 ss-m-20"
:key="orderData.id">
<view class="order-card-header ss-flex ss-col-center ss-row-between ss-p-x-20">
<view class="order-no">订单号{{ orderData.no }}</view>
<view class="order-state ss-font-26" :class="formatOrderColor(orderData)">
{{ formatOrderStatus(orderData) }}
</view>
</view>
<template v-if="from != 'msg'">
<view class="bottom ss-flex" v-for="item in orderData.items" :key="item">
<image class="image" :src="sheep.$url.cdn(item.goods_image)" mode="aspectFill"> </image>
<view class="ss-flex-1">
<view class="title ss-line-2">
{{ item.goods_title }}
</view>
<view v-if="item.goods_num" class="num ss-m-b-10"> {{ item.goods_num }} </view>
<view class="ss-flex ss-row-between ss-m-t-8">
<span class="price">{{ item.goods_price }}</span>
<span class="status">{{ orderData.status_text }}</span>
</view>
<view class="border-bottom" v-for="item in orderData.items" :key="item.id">
<s-goods-item
:img="item.picUrl"
:title="item.spuName"
:skuText="item.properties.map((property) => property.valueName).join(' ')"
:price="item.price"
:num="item.count"
/>
</view>
<view class="pay-box ss-m-t-30 ss-flex ss-row-right ss-p-r-20">
<view class="ss-flex ss-col-center">
<view class="discounts-title pay-color"> {{ orderData.productCount }} 件商品,总金额:</view>
<view class="discounts-money pay-color">
{{ fen2yuan(orderData.payPrice) }}
</view>
</view>
</template>
<template v-else>
<view class="bottom ss-flex" v-for="item in [orderData.items[0]]" :key="item">
<image class="image" :src="sheep.$url.cdn(item.goods_image)" mode="aspectFill"> </image>
<view class="ss-flex-1">
<view class="title title-1 ss-line-1">
{{ item.goods_title }}
</view>
<view class="order-total ss-flex ss-row-between ss-m-t-8">
<span>{{ orderData.items.length }}件商品</span>
<span>合计 ¥{{ orderData.pay_fee }}</span>
</view>
<view class="ss-flex ss-row-right ss-m-t-8">
<span class="status">{{ orderData.status_text }}</span>
</view>
</view>
</view>
</template>
</view>
</view>
</template>
<script setup>
import sheep from '@/sheep';
import { fen2yuan, formatOrderColor, formatOrderStatus } from '@/sheep/hooks/useGoods';
const props = defineProps({
from: String,
orderData: {
type: Object,
default: {},
@ -52,71 +39,76 @@
</script>
<style lang="scss" scoped>
.order {
background: #fff;
padding: 20rpx;
border-radius: 12rpx;
.order-list-card-box {
.order-card-header {
height: 80rpx;
.top {
line-height: 40rpx;
font-size: 24rpx;
font-weight: 400;
color: #999;
border-bottom: 1px solid rgba(223, 223, 223, 0.5);
margin-bottom: 20rpx;
}
.bottom {
margin-bottom: 20rpx;
&:last-of-type {
margin-bottom: 0;
}
.image {
flex-shrink: 0;
width: 116rpx;
height: 116rpx;
margin-right: 20rpx;
}
.title {
height: 64rpx;
line-height: 32rpx;
.order-no {
font-size: 26rpx;
font-weight: 500;
color: #333;
}
&.title-1 {
height: 32rpx;
width: 300rpx;
.order-state {}
}
.pay-box {
.discounts-title {
font-size: 24rpx;
line-height: normal;
color: #999999;
}
.discounts-money {
font-size: 24rpx;
line-height: normal;
color: #999;
font-family: OPPOSANS;
}
.pay-color {
color: #333;
}
}
.order-card-footer {
height: 100rpx;
.more-item-box {
padding: 20rpx;
.more-item {
height: 60rpx;
.title {
font-size: 26rpx;
}
}
}
.num {
.more-btn {
color: $dark-9;
font-size: 24rpx;
font-weight: 400;
color: #999;
}
.price {
.content {
width: 154rpx;
color: #333333;
font-size: 26rpx;
font-weight: 500;
color: #ff3000;
}
.status {
font-size: 24rpx;
font-weight: 500;
color: var(--ui-BG-Main);
}
.order-total {
line-height: 28rpx;
font-size: 24rpx;
font-weight: 400;
color: #999;
}
}
}
.warning-color {
color: #faad14;
}
.danger-color {
color: #ff3000;
}
.success-color {
color: #52c41a;
}
.info-color {
color: #999999;
}
</style>

View File

@ -14,11 +14,11 @@
<view
class="item"
v-for="item in state.pagination.data"
:key="item"
:key="item.id"
@tap="emits('select', { type: mode, data: item })"
>
<template v-if="mode == 'goods'">
<GoodsItem :goodsData="item.goods" />
<GoodsItem :goodsData="item" />
</template>
<template v-if="mode == 'order'">
<OrderItem :orderData="item" />
@ -32,8 +32,7 @@
<script setup>
import { reactive, watch } from 'vue';
import sheep from '@/sheep';
import _ from 'lodash';
import _ from 'lodash-es';
import GoodsItem from './goods.vue';
import OrderItem from './order.vue';
import OrderApi from '@/sheep/api/trade/order';
@ -83,7 +82,7 @@
page,
list_rows,
});
let orderList = _.concat(state.pagination.data, res.data.data);
let orderList = _.concat(state.pagination.data, res.data.list);
state.pagination = {
...res.data,
data: orderList,

View File

@ -0,0 +1,166 @@
<template>
<su-popup
:show="showTools"
@close="handleClose"
>
<view class="ss-modal-box ss-flex-col">
<slot></slot>
<view class="content ss-flex ss-flex-1">
<template v-if="toolsMode === 'emoji'">
<swiper
class="emoji-swiper"
:indicator-dots="true"
circular
indicator-active-color="#7063D2"
indicator-color="rgba(235, 231, 255, 1)"
:autoplay="false"
:interval="3000"
:duration="1000"
>
<swiper-item v-for="emoji in emojiPage" :key="emoji">
<view class="ss-flex ss-flex-wrap">
<image
v-for="item in emoji" :key="item"
class="emoji-img"
:src="sheep.$url.cdn(`/static/img/chat/emoji/${item.file}`)"
@tap="onEmoji(item)"
>
</image>
</view>
</swiper-item>
</swiper>
</template>
<template v-else>
<view class="image">
<s-uploader
file-mediatype="image"
:imageStyles="{ width: 50, height: 50, border: false }"
@select="imageSelect({ type: 'image', data: $event })"
>
<image
class="icon"
:src="sheep.$url.static('/static/img/shop/chat/image.png')"
mode="aspectFill"
></image>
</s-uploader>
<view>图片</view>
</view>
<view class="goods" @tap="onShowSelect('goods')">
<image
class="icon"
:src="sheep.$url.static('/static/img/shop/chat/goods.png')"
mode="aspectFill"
></image>
<view>商品</view>
</view>
<view class="order" @tap="onShowSelect('order')">
<image
class="icon"
:src="sheep.$url.static('/static/img/shop/chat/order.png')"
mode="aspectFill"
></image>
<view>订单</view>
</view>
</template>
</view>
</view>
</su-popup>
</template>
<script setup>
/**
* 聊天工具
*/
import { emojiPage } from '@/pages/chat/util/emoji';
import sheep from '@/sheep';
const props = defineProps({
//
toolsMode: {
type: String,
default: '',
},
//
showTools: {
type: Boolean,
default: () => false,
},
});
const emits = defineEmits(['onEmoji', 'imageSelect', 'onShowSelect', 'close']);
//
function handleClose() {
emits('close');
}
//
function onEmoji(emoji) {
emits('onEmoji', emoji);
}
//
function imageSelect(val) {
emits('imageSelect', val);
}
//
function onShowSelect(mode) {
emits('onShowSelect', mode);
}
</script>
<style scoped lang="scss">
.content {
width: 100%;
align-content: space-around;
border-top: 1px solid #dfdfdf;
padding: 20rpx 0 0;
.emoji-swiper {
width: 100%;
height: 280rpx;
padding: 0 20rpx;
.emoji-img {
width: 50rpx;
height: 50rpx;
display: inline-block;
margin: 10rpx;
}
}
.image,
.goods,
.order {
width: 33.3%;
height: 280rpx;
text-align: center;
font-size: 24rpx;
color: #333;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.icon {
width: 50rpx;
height: 50rpx;
margin-bottom: 21rpx;
}
}
:deep() {
.uni-file-picker__container {
justify-content: center;
}
.file-picker__box {
display: none;
&:last-of-type {
display: flex;
}
}
}
}
</style>

View File

@ -1,278 +1,43 @@
<template>
<s-layout class="chat-wrap" title="客服" navbar="inner">
<div class="status">
{{ socketState.isConnect ? customerServiceInfo.title : '网络已断开,请检查网络后刷新重试' }}
</div>
<div class="page-bg" :style="{ height: sys_navBar + 'px' }"></div>
<view class="chat-box" :style="{ height: pageHeight + 'px' }">
<scroll-view
:style="{ height: pageHeight + 'px' }"
scroll-y="true"
:scroll-with-animation="false"
:enable-back-to-top="true"
:scroll-into-view="chat.scrollInto"
>
<button
class="loadmore-btn ss-reset-button"
v-if="
chatList.length &&
chatHistoryPagination.lastPage > 1 &&
loadingMap[chatHistoryPagination.loadStatus].title
"
@click="onLoadMore"
>
{{ loadingMap[chatHistoryPagination.loadStatus].title }}
<i
class="loadmore-icon sa-m-l-6"
:class="loadingMap[chatHistoryPagination.loadStatus].icon"
></i>
</button>
<view class="message-item ss-flex-col" v-for="(item, index) in chatList" :key="index">
<view class="ss-flex ss-row-center ss-col-center">
<!-- 日期 -->
<view v-if="item.from !== 'system' && showTime(item, index)" class="date-message">
{{ formatTime(item.date) }}
</view>
<!-- 系统消息 -->
<view v-if="item.from === 'system'" class="system-message">
{{ item.content.text }}
</view>
</view>
<!-- 常见问题 -->
<view v-if="item.mode === 'template' && item.content.list.length" class="template-wrap">
<view class="title">猜你想问</view>
<view
class="item"
v-for="(item, index) in item.content.list"
:key="index"
@click="onTemplateList(item)"
>
* {{ item.title }}
</view>
</view>
<view
v-if="
(item.from === 'customer_service' && item.mode !== 'template') ||
item.from === 'customer'
"
class="ss-flex ss-col-top"
:class="[
item.from === 'customer_service'
? `ss-row-left`
: item.from === 'customer'
? `ss-row-right`
: '',
]"
>
<!-- 客服头像 -->
<image
v-show="item.from === 'customer_service'"
class="chat-avatar ss-m-r-24"
:src="
sheep.$url.cdn(item?.sender?.avatar) ||
sheep.$url.static('/static/img/shop/chat/default.png')
"
mode="aspectFill"
></image>
<!-- 发送状态 -->
<span
v-if="
item.from === 'customer' &&
index == chatData.chatList.length - 1 &&
chatData.isSendSucces !== 0
"
class="send-status"
>
<image
v-if="chatData.isSendSucces == -1"
class="loading"
:src="sheep.$url.static('/static/img/shop/chat/loading.png')"
mode="aspectFill"
></image>
<!-- <image
v-if="chatData.isSendSucces == 1"
class="warning"
:src="sheep.$url.static('/static/img/shop/chat/warning.png')"
mode="aspectFill"
@click="onAgainSendMessage(item)"
></image> -->
</span>
<!-- 内容 -->
<template v-if="item.mode === 'text'">
<view class="message-box" :class="[item.from]">
<div
class="message-text ss-flex ss-flex-wrap"
@click="onRichtext"
v-html="replaceEmoji(item.content.text)"
></div>
</view>
</template>
<template v-if="item.mode === 'image'">
<view class="message-box" :class="[item.from]" :style="{ width: '200rpx' }">
<su-image
class="message-img"
isPreview
:previewList="[sheep.$url.cdn(item.content.url)]"
:current="0"
:src="sheep.$url.cdn(item.content.url)"
:height="200"
:width="200"
mode="aspectFill"
></su-image>
</view>
</template>
<template v-if="item.mode === 'goods'">
<GoodsItem
:goodsData="item.content.item"
@tap="
sheep.$router.go('/pages/goods/index', {
id: item.content.item.id,
})
"
/>
</template>
<template v-if="item.mode === 'order'">
<OrderItem
from="msg"
:orderData="item.content.item"
@tap="
sheep.$router.go('/pages/order/detail', {
id: item.content.item.id,
})
"
/>
</template>
<!-- user头像 -->
<image
v-show="item.from === 'customer'"
class="chat-avatar ss-m-l-24"
:src="sheep.$url.cdn(customerUserInfo.avatar)"
mode="aspectFill"
>
</image>
</view>
</view>
<view id="scrollBottom"></view>
</scroll-view>
</view>
<su-fixed bottom>
<view class="send-wrap ss-flex">
<view class="left ss-flex ss-flex-1">
<uni-easyinput
class="ss-flex-1 ss-p-l-22"
:inputBorder="false"
:clearable="false"
v-model="chat.msg"
placeholder="请输入你要咨询的问题"
></uni-easyinput>
</view>
<text class="sicon-basic bq" @tap.stop="onTools('emoji')"></text>
<text
v-if="!chat.msg"
class="sicon-edit"
:class="{ 'is-active': chat.toolsMode == 'tools' }"
@tap.stop="onTools('tools')"
></text>
<button v-if="chat.msg" class="ss-reset-button send-btn" @tap="onSendMessage">
发送
</button>
</view>
</su-fixed>
<su-popup
:show="chat.showTools"
@close="
chat.showTools = false;
chat.toolsMode = '';
"
<s-layout
class="chat-wrap"
:title="!isReconnecting ? '连接客服成功' : '会话重连中'"
navbar="inner"
>
<!-- 覆盖头部导航栏背景颜色 -->
<view class="page-bg" :style="{ height: sys_navBar + 'px' }"></view>
<!-- 聊天区域 -->
<MessageList ref="messageListRef">
<template #bottom>
<message-input
v-model="chat.msg"
@on-tools="onTools"
@send-message="onSendMessage"
:auto-focus="false"
:show-char-count="true"
:max-length="500"
></message-input>
</template>
</MessageList>
<!-- 聊天工具 -->
<tools-popup
:show-tools="chat.showTools"
:tools-mode="chat.toolsMode"
@close="handleToolsClose"
@on-emoji="onEmoji"
@image-select="onSelect"
@on-show-select="onShowSelect"
>
<view class="ss-modal-box ss-flex-col">
<view class="send-wrap ss-flex">
<view class="left ss-flex ss-flex-1">
<uni-easyinput
class="ss-flex-1 ss-p-l-22"
:inputBorder="false"
:clearable="false"
v-model="chat.msg"
placeholder="请输入你要咨询的问题"
></uni-easyinput>
</view>
<text class="sicon-basic bq" @tap.stop="onTools('emoji')"></text>
<text></text>
<text
v-if="!chat.msg"
class="sicon-edit"
:class="{ 'is-active': chat.toolsMode == 'tools' }"
@tap.stop="onTools('tools')"
></text>
<button v-if="chat.msg" class="ss-reset-button send-btn" @tap="onSendMessage">
发送
</button>
</view>
<view class="content ss-flex ss-flex-1">
<template v-if="chat.toolsMode == 'emoji'">
<swiper
class="emoji-swiper"
:indicator-dots="true"
circular
indicator-active-color="#7063D2"
indicator-color="rgba(235, 231, 255, 1)"
:autoplay="false"
:interval="3000"
:duration="1000"
>
<swiper-item v-for="emoji in emojiPage" :key="emoji">
<view class="ss-flex ss-flex-wrap">
<template v-for="item in emoji" :key="item">
<image
class="emoji-img"
:src="sheep.$url.cdn(`/static/img/chat/emoji/${item.file}`)"
@tap="onEmoji(item)"
>
</image>
</template>
</view>
</swiper-item>
</swiper>
</template>
<template v-else>
<view class="image">
<s-uploader
file-mediatype="image"
:imageStyles="{ width: 50, height: 50, border: false }"
@select="onSelect({ type: 'image', data: $event })"
>
<image
class="icon"
:src="sheep.$url.static('/static/img/shop/chat/image.png')"
mode="aspectFill"
></image>
</s-uploader>
<view>图片</view>
</view>
<view class="goods" @tap="onShowSelect('goods')">
<image
class="icon"
:src="sheep.$url.static('/static/img/shop/chat/goods.png')"
mode="aspectFill"
></image>
<view>商品</view>
</view>
<view class="order" @tap="onShowSelect('order')">
<image
class="icon"
:src="sheep.$url.static('/static/img/shop/chat/order.png')"
mode="aspectFill"
></image>
<view>订单</view>
</view>
</template>
</view>
</view>
</su-popup>
<message-input
v-model="chat.msg"
@on-tools="onTools"
@send-message="onSendMessage"
:auto-focus="false"
:show-char-count="true"
:max-length="500"
></message-input>
</tools-popup>
<!-- 商品订单选择 -->
<SelectPopup
:mode="chat.selectMode"
:show="chat.showSelect"
@ -283,109 +48,82 @@
</template>
<script setup>
import MessageList from '@/pages/chat/components/messageList.vue';
import { reactive, ref, toRefs } from 'vue';
import sheep from '@/sheep';
import { computed, reactive, toRefs } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import { emojiList, emojiPage } from './emoji.js';
import SelectPopup from './components/select-popup.vue';
import GoodsItem from './components/goods.vue';
import OrderItem from './components/order.vue';
import { useChatWebSocket } from './socket';
const {
socketInit,
state: chatData,
socketSendMsg,
formatChatInput,
socketHistoryList,
onDrop,
onPaste,
getFocus,
// upload,
getUserToken,
// socketTest,
showTime,
formatTime,
} = useChatWebSocket();
const chatList = toRefs(chatData).chatList;
const customerServiceInfo = toRefs(chatData).customerServerInfo;
const chatHistoryPagination = toRefs(chatData).chatHistoryPagination;
const customerUserInfo = toRefs(chatData).customerUserInfo;
const socketState = toRefs(chatData).socketState;
import ToolsPopup from '@/pages/chat/components/toolsPopup.vue';
import MessageInput from '@/pages/chat/components/messageInput.vue';
import SelectPopup from '@/pages/chat/components/select-popup.vue';
import {
KeFuMessageContentTypeEnum,
WebSocketMessageTypeConstants,
} from '@/pages/chat/util/constants';
import FileApi from '@/sheep/api/infra/file';
import KeFuApi from '@/sheep/api/promotion/kefu';
import { useWebSocket } from './util/useWebSocket';
import { jsonParse } from '@/sheep/helper/utils';
const sys_navBar = sheep.$platform.navbar;
const chatConfig = computed(() => sheep.$store('app').chat);
const { screenHeight, safeAreaInsets, safeArea, screenWidth } = sheep.$platform.device;
const pageHeight = safeArea.height - 44 - 35 - 50;
const chatStatus = {
online: {
text: '在线',
colorVariate: '#46c55f',
},
offline: {
text: '离线',
colorVariate: '#b5b5b5',
},
busy: {
text: '忙碌',
colorVariate: '#ff0e1b',
},
};
//
const loadingMap = {
loadmore: {
title: '查看更多',
icon: 'el-icon-d-arrow-left',
},
nomore: {
title: '没有更多了',
icon: '',
},
loading: {
title: '加载中... ',
icon: 'el-icon-loading',
},
};
const onLoadMore = () => {
chatHistoryPagination.value.page < chatHistoryPagination.value.lastPage && socketHistoryList();
};
const chat = reactive({
msg: '',
scrollInto: '',
showTools: false,
toolsMode: '',
showSelect: false,
selectMode: '',
chatStyle: {
mode: 'inner',
color: '#F8270F',
type: 'color',
alwaysShow: 1,
src: '',
list: {},
},
});
//
async function onSendMessage() {
if (!chat.msg) return;
try {
const data = {
contentType: KeFuMessageContentTypeEnum.TEXT,
content: JSON.stringify({ text: chat.msg }),
};
await KeFuApi.sendKefuMessage(data);
chat.msg = '';
} finally {
chat.showTools = false;
}
}
const messageListRef = ref();
//======================= start =======================
function handleToolsClose() {
chat.showTools = false;
chat.toolsMode = '';
}
function onEmoji(item) {
chat.msg += item.name;
}
//
function onTools(mode) {
if (!socketState.value.isConnect) {
sheep.$helper.toast(socketState.value.tip || '您已掉线!请返回重试');
if (isReconnecting.value) {
sheep.$helper.toast('您已掉线!请返回重试');
return;
}
if (!chat.toolsMode || chat.toolsMode === mode) {
chat.showTools = !chat.showTools;
//
if (chat.showTools && chat.toolsMode === mode) {
handleToolsClose();
return;
}
chat.toolsMode = mode;
if (!chat.showTools) {
//
if (chat.showTools && chat.toolsMode !== mode) {
chat.showTools = false;
chat.toolsMode = '';
}
//
setTimeout(() => {
chat.toolsMode = mode;
chat.showTools = true;
}, 200);
}
function onShowSelect(mode) {
@ -395,205 +133,77 @@
}
async function onSelect({ type, data }) {
let msg = '';
let msg;
switch (type) {
case 'image':
const { path, fullurl } = await sheep.$api.app.upload(data.tempFiles[0].path, 'default');
const res = await FileApi.uploadFile(data.tempFiles[0].path);
msg = {
from: 'customer',
mode: 'image',
date: new Date().getTime(),
content: {
url: fullurl,
path: path,
},
contentType: KeFuMessageContentTypeEnum.IMAGE,
content: JSON.stringify({ picUrl: res.data }),
};
break;
case 'goods':
msg = {
from: 'customer',
mode: 'goods',
date: new Date().getTime(),
content: {
item: {
id: data.goods.id,
title: data.goods.title,
image: data.goods.image,
price: data.goods.price,
stock: data.goods.stock,
},
},
contentType: KeFuMessageContentTypeEnum.PRODUCT,
content: JSON.stringify(data),
};
break;
case 'order':
msg = {
from: 'customer',
mode: 'order',
date: new Date().getTime(),
content: {
item: {
id: data.id,
order_sn: data.order_sn,
create_time: data.create_time,
pay_fee: data.pay_fee,
items: data.items.filter((item) => ({
goods_id: item.goods_id,
goods_title: item.goods_title,
goods_image: item.goods_image,
goods_price: item.goods_price,
})),
status_text: data.status_text,
},
},
contentType: KeFuMessageContentTypeEnum.ORDER,
content: JSON.stringify(data),
};
break;
}
if (msg) {
socketSendMsg(msg, () => {
scrollBottom();
});
//
// scrollBottom();
await KeFuApi.sendKefuMessage(msg);
await messageListRef.value.refreshMessageList();
chat.showTools = false;
chat.showSelect = false;
chat.selectMode = '';
}
}
function onAgainSendMessage(item) {
if (!socketState.value.isConnect) {
sheep.$helper.toast(socketState.value.tip || '您已掉线!请返回重试');
return;
}
if (!item) return;
const data = {
from: 'customer',
mode: 'text',
date: new Date().getTime(),
content: item.content,
};
socketSendMsg(data, () => {
scrollBottom();
});
}
function onSendMessage() {
if (!socketState.value.isConnect) {
sheep.$helper.toast(socketState.value.tip || '您已掉线!请返回重试');
return;
}
if (!chat.msg) return;
const data = {
from: 'customer',
mode: 'text',
date: new Date().getTime(),
content: {
text: chat.msg,
},
};
socketSendMsg(data, () => {
scrollBottom();
});
chat.showTools = false;
// scrollBottom();
setTimeout(() => {
chat.msg = '';
}, 100);
}
//
function onTemplateList(e) {
if (!socketState.value.isConnect) {
sheep.$helper.toast(socketState.value.tip || '您已掉线!请返回重试');
return;
}
const data = {
from: 'customer',
mode: 'text',
date: new Date().getTime(),
content: {
text: e.title,
},
customData: {
question_id: e.id,
},
};
socketSendMsg(data, () => {
scrollBottom();
});
// scrollBottom();
}
function onEmoji(item) {
chat.msg += item.name;
}
function selEmojiFile(name) {
for (let index in emojiList) {
if (emojiList[index].name === name) {
return emojiList[index].file;
//======================= end =======================
const { options } = useWebSocket({
//
onConnected: async () => {},
//
onMessage: async (data) => {
const type = data.type;
if (!type) {
console.error('未知的消息类型:' + data);
return;
}
}
return false;
}
function replaceEmoji(data) {
let newData = data;
if (typeof newData !== 'object') {
let reg = /\[(.+?)\]/g; // []
let zhEmojiName = newData.match(reg);
if (zhEmojiName) {
zhEmojiName.forEach((item) => {
let emojiFile = selEmojiFile(item);
newData = newData.replace(
item,
`<img class="chat-img" style="width: 24px;height: 24px;margin: 0 3px;" src="${sheep.$url.cdn(
'/static/img/chat/emoji/' + emojiFile,
)}"/>`,
);
});
// 2.2 KEFU_MESSAGE_TYPE
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_TYPE) {
//
await messageListRef.value.refreshMessageList(jsonParse(data.content));
return;
}
}
return newData;
}
function scrollBottom() {
let timeout = null;
chat.scrollInto = '';
clearTimeout(timeout);
timeout = setTimeout(() => {
chat.scrollInto = 'scrollBottom';
}, 100);
}
onLoad(async () => {
const { error } = await getUserToken();
if (error === 0) {
socketInit(chatConfig.value, () => {
scrollBottom();
});
} else {
socketState.value.isConnect = false;
}
// 2.3 KEFU_MESSAGE_ADMIN_READ
if (type === WebSocketMessageTypeConstants.KEFU_MESSAGE_ADMIN_READ) {
console.log('管理员已读消息');
//
sheep.$helper.toast('客服已读您的消息');
}
},
});
const isReconnecting = toRefs(options).isReconnecting; //
</script>
<style lang="scss" scoped>
.page-bg {
width: 100%;
position: absolute;
top: 0;
left: 0;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
background-size: 750rpx 100%;
z-index: 1;
}
<style scoped lang="scss">
.chat-wrap {
// :deep() {
// .ui-navbar-box {
// background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
// }
// }
.page-bg {
width: 100%;
position: absolute;
top: 0;
left: 0;
background-color: var(--ui-BG-Main);
z-index: 1;
}
.status {
position: relative;
@ -608,263 +218,5 @@
font-weight: 400;
color: var(--ui-BG-Main);
}
.chat-box {
padding: 0 20rpx 0;
.loadmore-btn {
width: 98%;
height: 40px;
font-size: 12px;
color: #8c8c8c;
.loadmore-icon {
transform: rotate(90deg);
}
}
.message-item {
margin-bottom: 33rpx;
}
.date-message,
.system-message {
width: fit-content;
border-radius: 12rpx;
padding: 8rpx 16rpx;
margin-bottom: 16rpx;
background-color: var(--ui-BG-3);
color: #999;
font-size: 24rpx;
}
.chat-avatar {
width: 70rpx;
height: 70rpx;
border-radius: 50%;
}
.send-status {
color: #333;
height: 80rpx;
margin-right: 8rpx;
display: flex;
align-items: center;
.loading {
width: 32rpx;
height: 32rpx;
-webkit-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite;
@-webkit-keyframes rotating {
0% {
transform: rotateZ(0);
}
100% {
transform: rotateZ(360deg);
}
}
@keyframes rotating {
0% {
transform: rotateZ(0);
}
100% {
transform: rotateZ(360deg);
}
}
}
.warning {
width: 32rpx;
height: 32rpx;
color: #ff3000;
}
}
.message-box {
max-width: 50%;
font-size: 16px;
line-height: 20px;
// max-width: 500rpx;
white-space: normal;
word-break: break-all;
word-wrap: break-word;
padding: 20rpx;
border-radius: 10rpx;
color: #fff;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
&.customer_service {
background: #fff;
color: #333;
}
:deep() {
.imgred {
width: 100%;
}
.imgred,
img {
width: 100%;
}
}
}
:deep() {
.goods,
.order {
max-width: 500rpx;
}
}
.message-img {
width: 100px;
height: 100px;
border-radius: 6rpx;
}
.template-wrap {
// width: 100%;
padding: 20rpx 24rpx;
background: #fff;
border-radius: 10rpx;
.title {
font-size: 26rpx;
font-weight: 500;
color: #333;
margin-bottom: 29rpx;
}
.item {
font-size: 24rpx;
color: var(--ui-BG-Main);
margin-bottom: 16rpx;
&:last-of-type {
margin-bottom: 0;
}
}
}
.error-img {
width: 400rpx;
height: 400rpx;
}
#scrollBottom {
height: 120rpx;
}
}
.send-wrap {
padding: 18rpx 20rpx;
background: #fff;
.left {
height: 64rpx;
border-radius: 32rpx;
background: var(--ui-BG-1);
}
.bq {
font-size: 50rpx;
margin-left: 10rpx;
}
.sicon-edit {
font-size: 50rpx;
margin-left: 10rpx;
transform: rotate(0deg);
transition: all linear 0.2s;
&.is-active {
transform: rotate(45deg);
}
}
.send-btn {
width: 100rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 30rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
font-size: 26rpx;
color: #fff;
margin-left: 11rpx;
}
}
}
.content {
width: 100%;
align-content: space-around;
border-top: 1px solid #dfdfdf;
padding: 20rpx 0 0;
.emoji-swiper {
width: 100%;
height: 280rpx;
padding: 0 20rpx;
.emoji-img {
width: 50rpx;
height: 50rpx;
display: inline-block;
margin: 10rpx;
}
}
.image,
.goods,
.order {
width: 33.3%;
height: 280rpx;
text-align: center;
font-size: 24rpx;
color: #333;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.icon {
width: 50rpx;
height: 50rpx;
margin-bottom: 21rpx;
}
}
:deep() {
.uni-file-picker__container {
justify-content: center;
}
.file-picker__box {
display: none;
&:last-of-type {
display: flex;
}
}
}
}
</style>
<style>
.chat-img {
width: 24px;
height: 24px;
margin: 0 3px;
}
.full-img {
object-fit: cover;
width: 100px;
height: 100px;
border-radius: 6px;
}
</style>

View File

@ -1,821 +0,0 @@
import { reactive, ref, unref } from 'vue';
import sheep from '@/sheep';
// import chat from '@/sheep/api/chat';
import dayjs from 'dayjs';
import io from '@hyoga/uni-socket.io';
export function useChatWebSocket(socketConfig) {
let SocketIo = null;
// chat状态数据
const state = reactive({
chatDotNum: 0, //总状态红点
chatList: [], //会话信息
customerUserInfo: {}, //用户信息
customerServerInfo: {
//客服信息
title: '连接中...',
state: 'connecting',
avatar: null,
nickname: '',
},
socketState: {
isConnect: true, //是否连接成功
isConnecting: false, //重连中不允许新的socket开启。
tip: '',
},
chatHistoryPagination: {
page: 0, //当前页
list_rows: 10, //每页条数
last_id: 0, //最后条ID
lastPage: 0, //总共多少页
loadStatus: 'loadmore', //loadmore-加载前的状态loading-加载中的状态nomore-没有更多的状态
},
templateChatList: [], //猜你想问
chatConfig: {}, // 配置信息
isSendSucces: -1, // 是否发送成功 -1=发送中|0=发送成功|1发送失败
});
/**
* 连接初始化
* @param {Object} config - 配置信息
* @param {Function} callBack -回调函数,有新消息接入保持底部
*/
const socketInit = (config, callBack) => {
state.chatConfig = config;
if (SocketIo && SocketIo.connected) return; // 如果socket已经连接返回false
if (state.socketState.isConnecting) return; // 重连中返回false
// 启动初始化
SocketIo = io(config.chat_domain, {
reconnection: true, // 默认 true 是否断线重连
reconnectionAttempts: 5, // 默认无限次 断线尝试次数
reconnectionDelay: 1000, // 默认 1000进行下一次重连的间隔。
reconnectionDelayMax: 5000, // 默认 5000 重新连接等待的最长时间 默认 5000
randomizationFactor: 0.5, // 默认 0.5 [0-1],随机重连延迟时间
timeout: 20000, // 默认 20s
transports: ['websocket', 'polling'], // websocket | polling,
...config,
});
// 监听连接
SocketIo.on('connect', async (res) => {
socketReset(callBack);
// socket连接
// 用户登录
// 顾客登录
console.log('socket:connect');
});
// 监听消息
SocketIo.on('message', (res) => {
if (res.error === 0) {
const { message, sender } = res.data;
state.chatList.push(formatMessage(res.data.message));
// 告诉父级页面
// window.parent.postMessage({
// chatDotNum: ++state.chatDotNum
// })
callBack && callBack();
}
});
// 监听客服接入成功
SocketIo.on('customer_service_access', (res) => {
if (res.error === 0) {
editCustomerServerInfo({
title: res.data.customer_service.name,
state: 'online',
avatar: res.data.customer_service.avatar,
});
state.chatList.push(formatMessage(res.data.message));
// callBack && callBack()
}
});
// 监听排队等待
SocketIo.on('waiting_queue', (res) => {
if (res.error === 0) {
editCustomerServerInfo({
title: res.data.title,
state: 'waiting',
avatar: '',
});
// callBack && callBack()
}
});
// 监听没有客服在线
SocketIo.on('no_customer_service', (res) => {
if (res.error === 0) {
editCustomerServerInfo({
title: '暂无客服在线...',
state: 'waiting',
avatar: '',
});
}
state.chatList.push(formatMessage(res.data.message));
// callBack && callBack()
});
// 监听客服上线
SocketIo.on('customer_service_online', (res) => {
if (res.error === 0) {
editCustomerServerInfo({
title: res.data.customer_service.name,
state: 'online',
avatar: res.data.customer_service.avatar,
});
}
});
// 监听客服下线
SocketIo.on('customer_service_offline', (res) => {
if (res.error === 0) {
editCustomerServerInfo({
title: res.data.customer_service.name,
state: 'offline',
avatar: res.data.customer_service.avatar,
});
}
});
// 监听客服忙碌
SocketIo.on('customer_service_busy', (res) => {
if (res.error === 0) {
editCustomerServerInfo({
title: res.data.customer_service.name,
state: 'busy',
avatar: res.data.customer_service.avatar,
});
}
});
// 监听客服断开链接
SocketIo.on('customer_service_break', (res) => {
if (res.error === 0) {
editCustomerServerInfo({
title: '客服服务结束',
state: 'offline',
avatar: '',
});
state.socketState.isConnect = false;
state.socketState.tip = '当前服务已结束';
}
state.chatList.push(formatMessage(res.data.message));
// callBack && callBack()
});
// 监听自定义错误 custom_error
SocketIo.on('custom_error', (error) => {
editCustomerServerInfo({
title: error.msg,
state: 'offline',
avatar: '',
});
console.log('custom_error:', error);
});
// 监听错误 error
SocketIo.on('error', (error) => {
console.log('error:', error);
});
// 重连失败 connect_error
SocketIo.on('connect_error', (error) => {
console.log('connect_error');
});
// 连接上,但无反应 connect_timeout
SocketIo.on('connect_timeout', (error) => {
console.log(error, 'connect_timeout');
});
// 服务进程销毁 disconnect
SocketIo.on('disconnect', (error) => {
console.log(error, 'disconnect');
});
// 服务重启重连上reconnect
SocketIo.on('reconnect', (error) => {
console.log(error, 'reconnect');
});
// 开始重连reconnect_attempt
SocketIo.on('reconnect_attempt', (error) => {
state.socketState.isConnect = false;
state.socketState.isConnecting = true;
editCustomerServerInfo({
title: `重连中,第${error}次尝试...`,
state: 'waiting',
avatar: '',
});
console.log(error, 'reconnect_attempt');
});
// 重新连接中reconnecting
SocketIo.on('reconnecting', (error) => {
console.log(error, 'reconnecting');
});
// 重新连接错误reconnect_error
SocketIo.on('reconnect_error', (error) => {
console.log('reconnect_error');
});
// 重新连接失败reconnect_failed
SocketIo.on('reconnect_failed', (error) => {
state.socketState.isConnecting = false;
editCustomerServerInfo({
title: `重连失败,请刷新重试~`,
state: 'waiting',
avatar: '',
});
console.log(error, 'reconnect_failed');
// setTimeout(() => {
state.isSendSucces = 1;
// }, 500)
});
};
// 重置socket
const socketReset = (callBack) => {
state.chatList = [];
state.chatHistoryList = [];
state.chatHistoryPagination = {
page: 0,
per_page: 10,
last_id: 0,
totalPage: 0,
};
socketConnection(callBack); // 连接
};
// 退出连接
const socketClose = () => {
SocketIo.emit('customer_logout', {}, (res) => {
console.log('socket:退出', res);
});
};
// 测试事件
const socketTest = () => {
SocketIo.emit('test', {}, (res) => {
console.log('test:test', res);
});
};
// 发送消息
const socketSendMsg = (data, sendMsgCallBack) => {
state.isSendSucces = -1;
state.chatList.push(data);
sendMsgCallBack && sendMsgCallBack();
SocketIo.emit(
'message',
{
message: formatInput(data),
...data.customData,
},
(res) => {
// setTimeout(() => {
state.isSendSucces = res.error;
// }, 500)
// console.log(res, 'socket:send');
// sendMsgCallBack && sendMsgCallBack()
},
);
};
// 连接socket,存入sessionId
const socketConnection = (callBack) => {
SocketIo.emit(
'connection',
{
auth: 'user',
token: uni.getStorageSync('socketUserToken') || '',
session_id: uni.getStorageSync('socketSessionId') || '',
},
(res) => {
if (res.error === 0) {
socketCustomerLogin(callBack);
uni.setStorageSync('socketSessionId', res.data.session_id);
// uni.getStorageSync('socketUserToken') && socketLogin(uni.getStorageSync(
// 'socketUserToken')) // 如果有用户token,绑定
state.customerUserInfo = res.data.chat_user;
state.socketState.isConnect = true;
} else {
editCustomerServerInfo({
title: `服务器异常!`,
state: 'waiting',
avatar: '',
});
state.socketState.isConnect = false;
}
},
);
};
// 用户id,获取token
const getUserToken = async (id) => {
const res = await chat.unifiedToken();
if (res.error === 0) {
uni.setStorageSync('socketUserToken', res.data.token);
// SocketIo && SocketIo.connected && socketLogin(res.data.token)
}
return res;
};
// 用户登录
const socketLogin = (token) => {
SocketIo.emit(
'login',
{
token: token,
},
(res) => {
console.log(res, 'socket:login');
state.customerUserInfo = res.data.chat_user;
},
);
};
// 顾客登录
const socketCustomerLogin = (callBack) => {
SocketIo.emit(
'customer_login',
{
room_id: state.chatConfig.room_id,
},
(res) => {
state.templateChatList = res.data.questions.length ? res.data.questions : [];
state.chatList.push({
from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间
mode: 'template', // goods,order,image,text,system
date: new Date().getTime(), //时间
content: {
//内容
list: state.templateChatList,
},
});
res.error === 0 && socketHistoryList(callBack);
},
);
};
// 获取历史消息
const socketHistoryList = (historyCallBack) => {
state.chatHistoryPagination.loadStatus = 'loading';
state.chatHistoryPagination.page += 1;
SocketIo.emit('messages', state.chatHistoryPagination, (res) => {
if (res.error === 0) {
state.chatHistoryPagination.total = res.data.messages.total;
state.chatHistoryPagination.lastPage = res.data.messages.last_page;
state.chatHistoryPagination.page = res.data.messages.current_page;
res.data.messages.data.forEach((item) => {
item.message_type && state.chatList.unshift(formatMessage(item));
});
state.chatHistoryPagination.loadStatus =
state.chatHistoryPagination.page < state.chatHistoryPagination.lastPage
? 'loadmore'
: 'nomore';
if (state.chatHistoryPagination.last_id == 0) {
state.chatHistoryPagination.last_id = res.data.messages.data.length
? res.data.messages.data[0].id
: 0;
}
state.chatHistoryPagination.page === 1 && historyCallBack && historyCallBack();
}
// 历史记录之后,猜你想问
// state.chatList.push({
// from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间
// mode: 'template', // goods,order,image,text,system
// date: new Date().getTime(), //时间
// content: { //内容
// list: state.templateChatList
// }
// })
});
};
// 修改客服信息
const editCustomerServerInfo = (data) => {
state.customerServerInfo = {
...state.customerServerInfo,
...data,
};
};
/**
* ================
* 工具函数
* ===============
*/
/**
* 是否显示时间
* @param {*} item - 数据
* @param {*} index - 索引
*/
const showTime = (item, index) => {
if (unref(state.chatList)[index + 1]) {
let dateString = dayjs(unref(state.chatList)[index + 1].date).fromNow();
if (dateString === dayjs(unref(item).date).fromNow()) {
return false;
} else {
dateString = dayjs(unref(item).date).fromNow();
return true;
}
}
return false;
};
/**
* 格式化时间
* @param {*} time - 时间戳
*/
const formatTime = (time) => {
let diffTime = new Date().getTime() - time;
if (diffTime > 28 * 24 * 60 * 1000) {
return dayjs(time).format('MM/DD HH:mm');
}
if (diffTime > 360 * 28 * 24 * 60 * 1000) {
return dayjs(time).format('YYYY/MM/DD HH:mm');
}
return dayjs(time).fromNow();
};
/**
* 获取焦点
* @param {*} virtualNode - 节点信息 ref
*/
const getFocus = (virtualNode) => {
if (window.getSelection) {
let chatInput = unref(virtualNode);
chatInput.focus();
let range = window.getSelection();
range.selectAllChildren(chatInput);
range.collapseToEnd();
} else if (document.selection) {
let range = document.selection.createRange();
range.moveToElementText(chatInput);
range.collapse(false);
range.select();
}
};
/**
* 文件上传
* @param {Blob} file -文件数据流
* @return {path,fullPath}
*/
const upload = (name, file) => {
return new Promise((resolve, reject) => {
let data = new FormData();
data.append('file', file, name);
data.append('group', 'chat');
ajax({
url: '/upload',
method: 'post',
headers: {
'Content-Type': 'multipart/form-data',
},
data,
success: function (res) {
resolve(res);
},
error: function (err) {
reject(err);
},
});
});
};
/**
* 粘贴到输入框
* @param {*} e - 粘贴内容
* @param {*} uploadHttp - 上传图片地址
*/
const onPaste = async (e) => {
let paste = e.clipboardData || window.clipboardData;
let filesArr = Array.from(paste.files);
filesArr.forEach(async (child) => {
if (child && child.type.includes('image')) {
e.preventDefault(); //阻止默认
let file = child;
const img = await readImg(file);
const blob = await compressImg(img, file.type);
const { data } = await upload(file.name, blob);
let image = `<img class="full-url" src='${data.fullurl}'>`;
document.execCommand('insertHTML', false, image);
} else {
document.execCommand('insertHTML', false, paste.getData('text'));
}
});
};
/**
* 拖拽到输入框
* @param {*} e - 粘贴内容
* @param {*} uploadHttp - 上传图片地址
*/
const onDrop = async (e) => {
e.preventDefault(); //阻止默认
let filesArr = Array.from(e.dataTransfer.files);
filesArr.forEach(async (child) => {
if (child && child.type.includes('image')) {
let file = child;
const img = await readImg(file);
const blob = await compressImg(img, file.type);
const { data } = await upload(file.name, blob);
let image = `<img class="full-url" src='${data.fullurl}' >`;
document.execCommand('insertHTML', false, image);
} else {
ElMessage({
message: '禁止拖拽非图片资源',
type: 'warning',
});
}
});
};
/**
* 解析富文本输入框内容
* @param {*} virtualNode -节点信息
* @param {Function} formatInputCallBack - cb 回调
*/
const formatChatInput = (virtualNode, formatInputCallBack) => {
let res = '';
let elemArr = Array.from(virtualNode.childNodes);
elemArr.forEach((child, index) => {
if (child.nodeName === '#text') {
//如果为文本节点
res += child.nodeValue;
if (
//文本节点的后面是图片并且不是emoji,分开发送。输入框中的图片和文本表情分开。
elemArr[index + 1] &&
elemArr[index + 1].nodeName === 'IMG' &&
elemArr[index + 1] &&
elemArr[index + 1].name !== 'emoji'
) {
const data = {
from: 'customer',
mode: 'text',
date: new Date().getTime(),
content: {
text: filterXSS(res),
},
};
formatInputCallBack && formatInputCallBack(data);
res = '';
}
} else if (child.nodeName === 'BR') {
res += '<br/>';
} else if (child.nodeName === 'IMG') {
// 有emjio 和 一般图片
// 图片解析后直接发送,不跟文字表情一组
if (child.name !== 'emoji') {
let srcReg = /src=[\'\']?([^\'\']*)[\'\']?/i;
let src = child.outerHTML.match(srcReg);
const data = {
from: 'customer',
mode: 'image',
date: new Date().getTime(),
content: {
url: src[1],
path: src[1].replace(/http:\/\/[^\/]*/, ''),
},
};
formatInputCallBack && formatInputCallBack(data);
} else {
// 非表情图片跟文字一起发送
res += child.outerHTML;
}
} else if (child.nodeName === 'DIV') {
res += `<div style='width:200px; white-space: nowrap;'>${child.outerHTML}</div>`;
}
});
if (res) {
const data = {
from: 'customer',
mode: 'text',
date: new Date().getTime(),
content: {
text: filterXSS(res),
},
};
formatInputCallBack && formatInputCallBack(data);
}
unref(virtualNode).innerHTML = '';
};
/**
* 状态回调
* @param {*} res -接口返回数据
*/
const callBackNotice = (res) => {
ElNotification({
title: 'socket',
message: res.msg,
showClose: true,
type: res.error === 0 ? 'success' : 'warning',
duration: 1200,
});
};
/**
* 格式化发送信息
* @param {Object} message
* @returns obj - 消息对象
*/
const formatInput = (message) => {
let obj = {};
switch (message.mode) {
case 'text':
obj = {
message_type: 'text',
message: message.content.text,
};
break;
case 'image':
obj = {
message_type: 'image',
message: message.content.path,
};
break;
case 'goods':
obj = {
message_type: 'goods',
message: message.content.item,
};
break;
case 'order':
obj = {
message_type: 'order',
message: message.content.item,
};
break;
default:
break;
}
return obj;
};
/**
* 格式化接收信息
* @param {*} message
* @returns obj - 消息对象
*/
const formatMessage = (message) => {
let obj = {};
switch (message.message_type) {
case 'system':
obj = {
from: 'system', // 用户customer左 | 顾客customer_service右 | 系统system中间
mode: 'system', // goods,order,image,text,system
date: message.create_time * 1000, //时间
content: {
//内容
text: message.message,
},
};
break;
case 'text':
obj = {
from: message.sender_identify,
mode: message.message_type,
date: message.create_time * 1000, //时间
sender: message.sender,
content: {
text: message.message,
messageId: message.id,
},
};
break;
case 'image':
obj = {
from: message.sender_identify,
mode: message.message_type,
date: message.create_time * 1000, //时间
sender: message.sender,
content: {
url: sheep.$url.cdn(message.message),
messageId: message.id,
},
};
break;
case 'goods':
obj = {
from: message.sender_identify,
mode: message.message_type,
date: message.create_time * 1000, //时间
sender: message.sender,
content: {
item: message.message,
messageId: message.id,
},
};
break;
case 'order':
obj = {
from: message.sender_identify,
mode: message.message_type,
date: message.create_time * 1000, //时间
sender: message.sender,
content: {
item: message.message,
messageId: message.id,
},
};
break;
default:
break;
}
return obj;
};
/**
* file 转换为 img
* @param {*} file - file 文件
* @returns img - img标签
*/
const readImg = (file) => {
return new Promise((resolve, reject) => {
const img = new Image();
const reader = new FileReader();
reader.onload = function (e) {
img.src = e.target.result;
};
reader.onerror = function (e) {
reject(e);
};
reader.readAsDataURL(file);
img.onload = function () {
resolve(img);
};
img.onerror = function (e) {
reject(e);
};
});
};
/**
* 压缩图片
*@param img -被压缩的img对象
* @param type -压缩后转换的文件类型
* @param mx -触发压缩的图片最大宽度限制
* @param mh -触发压缩的图片最大高度限制
* @returns blob - 文件流
*/
const compressImg = (img, type = 'image/jpeg', mx = 1000, mh = 1000, quality = 1) => {
return new Promise((resolve, reject) => {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const { width: originWidth, height: originHeight } = img;
// 最大尺寸限制
const maxWidth = mx;
const maxHeight = mh;
// 目标尺寸
let targetWidth = originWidth;
let targetHeight = originHeight;
if (originWidth > maxWidth || originHeight > maxHeight) {
if (originWidth / originHeight > 1) {
// 宽图片
targetWidth = maxWidth;
targetHeight = Math.round(maxWidth * (originHeight / originWidth));
} else {
// 高图片
targetHeight = maxHeight;
targetWidth = Math.round(maxHeight * (originWidth / originHeight));
}
}
canvas.width = targetWidth;
canvas.height = targetHeight;
context.clearRect(0, 0, targetWidth, targetHeight);
// 图片绘制
context.drawImage(img, 0, 0, targetWidth, targetHeight);
canvas.toBlob(
function (blob) {
resolve(blob);
},
type,
quality,
);
});
};
return {
compressImg,
readImg,
formatMessage,
formatInput,
callBackNotice,
socketInit,
socketSendMsg,
socketClose,
socketHistoryList,
getFocus,
formatChatInput,
onDrop,
onPaste,
upload,
getUserToken,
state,
socketTest,
showTime,
formatTime,
};
}

View File

@ -0,0 +1,19 @@
export const KeFuMessageContentTypeEnum = {
TEXT: 1, // 文本消息
IMAGE: 2, // 图片消息
VOICE: 3, // 语音消息
VIDEO: 4, // 视频消息
SYSTEM: 5, // 系统消息
// ========== 商城特殊消息 ==========
PRODUCT: 10,// 商品消息
ORDER: 11,// 订单消息"
};
export const UserTypeEnum = {
MEMBER: 1, // 会员 面向 c 端,普通用户
ADMIN: 2, // 管理员 面向 b 端,管理后台
};
// Promotion 的 WebSocket 消息类型枚举类
export const WebSocketMessageTypeConstants = {
KEFU_MESSAGE_TYPE: 'kefu_message_type', // 客服消息类型
KEFU_MESSAGE_ADMIN_READ: 'kefu_message_read_status_change' // 客服消息管理员已读
}

View File

@ -0,0 +1,150 @@
import { onBeforeUnmount, reactive, ref } from 'vue';
import { baseUrl, websocketPath } from '@/sheep/config';
import { copyValueToTarget } from '@/sheep/helper/utils';
import { getRefreshToken } from '@/sheep/request';
/**
* WebSocket 创建 hook
* @param opt 连接配置
* @return {{options: *}}
*/
export function useWebSocket(opt) {
const options = reactive({
url: (baseUrl + websocketPath).replace('http', 'ws') + '?token=' + getRefreshToken(), // ws 地址
isReconnecting: false, // 正在重新连接
reconnectInterval: 3000, // 重连间隔,单位毫秒
heartBeatInterval: 5000, // 心跳间隔,单位毫秒
pingTimeoutDuration: 1000, // 超过这个时间后端没有返回pong则判定后端断线了。
heartBeatTimer: null, // 心跳计时器
destroy: false, // 是否销毁
pingTimeout: null, // 心跳检测定时器
reconnectTimeout: null, // 重连定时器ID的属性
onConnected: () => {}, // 连接成功时触发
onClosed: () => {}, // 连接关闭时触发
onMessage: (data) => {}, // 收到消息
});
const SocketTask = ref(null); // SocketTask 由 uni.connectSocket() 接口创建
const initEventListeners = () => {
// 监听 WebSocket 连接打开事件
SocketTask.value.onOpen(() => {
console.log('WebSocket 连接成功');
// 连接成功时触发
options.onConnected();
// 开启心跳检查
startHeartBeat();
});
// 监听 WebSocket 接受到服务器的消息事件
SocketTask.value.onMessage((res) => {
try {
if (res.data === 'pong') {
// 收到心跳重置心跳超时检查
resetPingTimeout();
} else {
options.onMessage(JSON.parse(res.data));
}
} catch (error) {
console.error(error);
}
});
// 监听 WebSocket 连接关闭事件
SocketTask.value.onClose((event) => {
// 情况一:实例销毁
if (options.destroy) {
options.onClosed();
} else {
// 情况二:连接失败重连
// 停止心跳检查
stopHeartBeat();
// 重连
reconnect();
}
});
};
// 发送消息
const sendMessage = (message) => {
if (SocketTask.value && !options.destroy) {
SocketTask.value.send({ data: message });
}
};
// 开始心跳检查
const startHeartBeat = () => {
options.heartBeatTimer = setInterval(() => {
sendMessage('ping');
options.pingTimeout = setTimeout(() => {
// 如果在超时时间内没有收到 pong则认为连接断开
reconnect();
}, options.pingTimeoutDuration);
}, options.heartBeatInterval);
};
// 停止心跳检查
const stopHeartBeat = () => {
clearInterval(options.heartBeatTimer);
resetPingTimeout();
};
// WebSocket 重连
const reconnect = () => {
if (options.destroy || !SocketTask.value) {
// 如果WebSocket已被销毁或尚未完全关闭不进行重连
return;
}
// 重连中
options.isReconnecting = true;
// 清除现有的重连标志,以避免多次重连
if (options.reconnectTimeout) {
clearTimeout(options.reconnectTimeout);
}
// 设置重连延迟
options.reconnectTimeout = setTimeout(() => {
// 检查组件是否仍在运行和WebSocket是否关闭
if (!options.destroy) {
// 重置重连标志
options.isReconnecting = false;
// 初始化新的WebSocket连接
initSocket();
}
}, options.reconnectInterval);
};
const resetPingTimeout = () => {
if (options.pingTimeout) {
clearTimeout(options.pingTimeout);
options.pingTimeout = null; // 清除超时ID
}
};
const close = () => {
options.destroy = true;
stopHeartBeat();
if (options.reconnectTimeout) {
clearTimeout(options.reconnectTimeout);
}
if (SocketTask.value) {
SocketTask.value.close();
SocketTask.value = null;
}
};
const initSocket = () => {
options.destroy = false;
copyValueToTarget(options, opt);
SocketTask.value = uni.connectSocket({
url: options.url,
complete: () => {},
success: () => {},
});
initEventListeners();
};
initSocket();
onBeforeUnmount(() => {
close();
});
return { options };
}

File diff suppressed because one or more lines are too long

View File

@ -16,7 +16,7 @@
/>
</button>
</view>
<view class="ss-flex" @tap="sheep.$router.go('/pages/user/wallet/commission')">
<view class="ss-flex" @tap="sheep.$router.go('/pages/commission/wallet')">
<view class="header-title ss-m-r-4">查看明细</view>
<text class="cicon-play-arrow" />
</view>

View File

@ -45,7 +45,8 @@
type: Boolean,
default: false,
},
methods: { //
methods: {
//
type: Array,
default: [],
},
@ -57,25 +58,35 @@
const typeList = [
{
// icon: '/static/img/shop/pay/wechat.png', // TODO icon
icon: '/static/img/shop/pay/wallet.png',
title: '钱包余额',
value: '1',
},
{
icon: '/static/img/shop/pay/wechat.png',
title: '微信零钱',
icon: '/static/img/shop/pay/bank.png',
title: '银行卡转账',
value: '2',
},
{
icon: '/static/img/shop/pay/alipay.png',
title: '支付宝账户',
icon: '/static/img/shop/pay/wechat.png',
title: '微信收款码', //
value: '3',
},
{
icon: '/static/img/shop/pay/bank.png',
title: '银行卡转账',
icon: '/static/img/shop/pay/alipay.png',
title: '支付宝收款码', //
value: '4',
},
{
icon: '/static/img/shop/pay/wechat_api.png',
title: '微信零钱', // API
value: '5',
},
{
icon: '/static/img/shop/pay/alipay_api.png',
title: '支付宝余额', // API
value: '6',
},
];
function onChange(e) {
@ -89,7 +100,7 @@
}
//
emits('update:modelValue', {
type: state.currentValue
type: state.currentValue,
});
//
emits('close');

View File

@ -31,9 +31,10 @@
<style lang="scss" scoped>
//
.user-card {
width: 690rpx;
width: 700rpx;
height: 192rpx;
margin: -88rpx 20rpx 0 20rpx;
margin: 0 auto;
margin-top: -88rpx;
padding-top: 88rpx;
background: v-bind(headerBg) no-repeat;
background-size: 100% 100%;
@ -110,4 +111,4 @@
}
}
}
</style>
</style>

View File

@ -1,74 +1,90 @@
<!-- 分销首页明细列表 -->
<template>
<view class="distribution-log-wrap">
<view class="header-box">
<image class="header-bg" :src="sheep.$url.static('/static/img/shop/commission/title2.png')" />
<view class="ss-flex header-title">
<view class="title">实时动态</view>
<text class="cicon-forward" />
</view>
</view>
<scroll-view scroll-y="true" @scrolltolower="loadmore" class="scroll-box log-scroll"
scroll-with-animation="true">
<view v-if="state.pagination.list">
<view class="log-item-box ss-flex ss-row-between" v-for="item in state.pagination.list" :key="item.id">
<view class="log-item-wrap">
<view class="log-item ss-flex ss-ellipsis-1 ss-col-center">
<view class="ss-flex ss-col-center">
<image class="log-img" :src="sheep.$url.static('/static/img/shop/avatar/notice.png')" mode="aspectFill" />
</view>
<view class="log-text ss-ellipsis-1">
<view class="distribution-log-wrap">
<view class="header-box">
<image class="header-bg" :src="sheep.$url.static('/static/img/shop/commission/title2.png')" />
<view class="ss-flex header-title">
<view class="title">实时动态</view>
<text class="cicon-forward" />
</view>
</view>
<scroll-view
scroll-y="true"
@scrolltolower="loadmore"
class="scroll-box log-scroll"
scroll-with-animation="true"
>
<view v-if="state.pagination.list">
<view
class="log-item-box ss-flex ss-row-between"
v-for="item in state.pagination.list"
:key="item.id"
>
<view class="log-item-wrap">
<view class="log-item ss-flex ss-ellipsis-1 ss-col-center">
<view class="ss-flex ss-col-center">
<image
class="log-img"
:src="sheep.$url.static('/static/img/shop/avatar/notice.png')"
mode="aspectFill"
/>
</view>
<view class="log-text ss-ellipsis-1">
{{ item.title }} {{ fen2yuan(item.price) }}
</view>
</view>
</view>
<text class="log-time">{{ dayjs(item.createTime).fromNow() }}</text>
</view>
</view>
</view>
</view>
<text class="log-time">{{ dayjs(item.createTime).fromNow() }}</text>
</view>
</view>
<!-- 加载更多 -->
<uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" color="#333333"
@tap="loadmore" />
</scroll-view>
</view>
<!-- 加载更多 -->
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
color="#333333"
@tap="loadmore"
/>
</scroll-view>
</view>
</template>
<script setup>
import sheep from '@/sheep';
import { reactive } from 'vue';
import _ from 'lodash';
import dayjs from 'dayjs';
import sheep from '@/sheep';
import { reactive } from 'vue';
import _ from 'lodash-es';
import dayjs from 'dayjs';
import BrokerageApi from '@/sheep/api/trade/brokerage';
import { fen2yuan } from '../../../sheep/hooks/useGoods';
const state = reactive({
loadStatus: '',
pagination: {
const state = reactive({
loadStatus: '',
pagination: {
list: [],
total: 0,
pageNo: 1,
pageSize: 1,
},
});
pageSize: 8,
},
});
async function getLog() {
async function getLog() {
state.loadStatus = 'loading';
const { code, data } = await BrokerageApi.getBrokerageRecordPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize
});
pageSize: state.pagination.pageSize,
});
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
}
getLog();
getLog();
//
function loadmore() {
//
function loadmore() {
if (state.loadStatus === 'noMore') {
return;
}
@ -78,88 +94,88 @@
</script>
<style lang="scss" scoped>
.distribution-log-wrap {
width: 690rpx;
margin: 0 auto;
margin-bottom: 20rpx;
border-radius: 12rpx;
z-index: 3;
position: relative;
.distribution-log-wrap {
width: 690rpx;
margin: 0 auto;
margin-bottom: 20rpx;
border-radius: 12rpx;
z-index: 3;
position: relative;
.header-box {
width: 690rpx;
height: 76rpx;
position: relative;
.header-box {
width: 690rpx;
height: 76rpx;
position: relative;
.header-bg {
width: 690rpx;
height: 76rpx;
}
.header-bg {
width: 690rpx;
height: 76rpx;
}
.header-title {
position: absolute;
left: 20rpx;
top: 24rpx;
}
.header-title {
position: absolute;
left: 20rpx;
top: 24rpx;
}
.title {
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
line-height: 30rpx;
}
.title {
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
line-height: 30rpx;
}
.cicon-forward {
font-size: 30rpx;
font-weight: 400;
color: #ffffff;
line-height: 30rpx;
}
}
.cicon-forward {
font-size: 30rpx;
font-weight: 400;
color: #ffffff;
line-height: 30rpx;
}
}
.log-scroll {
height: 600rpx;
background: #fdfae9;
padding: 10rpx 20rpx 0;
box-sizing: border-box;
border-radius: 0 0 12rpx 12rpx;
.log-scroll {
height: 600rpx;
background: #fdfae9;
padding: 10rpx 20rpx 0;
box-sizing: border-box;
border-radius: 0 0 12rpx 12rpx;
.log-item-box {
margin-bottom: 20rpx;
.log-item-box {
margin-bottom: 20rpx;
.log-time {
// margin-left: 30rpx;
text-align: right;
font-size: 24rpx;
font-family: OPPOSANS;
font-weight: 400;
color: #c4c4c4;
}
}
.log-time {
// margin-left: 30rpx;
text-align: right;
font-size: 24rpx;
font-family: OPPOSANS;
font-weight: 400;
color: #c4c4c4;
}
}
.loadmore-wrap {
// line-height: 80rpx;
}
.loadmore-wrap {
// line-height: 80rpx;
}
.log-item {
// background: rgba(#ffffff, 0.2);
border-radius: 24rpx;
padding: 6rpx 20rpx 6rpx 12rpx;
.log-item {
// background: rgba(#ffffff, 0.2);
border-radius: 24rpx;
padding: 6rpx 20rpx 6rpx 12rpx;
.log-img {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
margin-right: 10rpx;
}
.log-img {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
margin-right: 10rpx;
}
.log-text {
max-width: 480rpx;
font-size: 24rpx;
font-weight: 500;
color: #333333;
}
}
}
}
</style>
.log-text {
max-width: 480rpx;
font-size: 24rpx;
font-weight: 500;
color: #333333;
}
}
}
}
</style>

View File

@ -1,138 +1,145 @@
<!-- 分销商菜单栏 -->
<template>
<view class="menu-box ss-flex-col">
<view class="header-box">
<image class="header-bg" :src="sheep.$url.static('/static/img/shop/commission/title1.png')" />
<view class="ss-flex header-title">
<view class="title">功能专区</view>
<text class="cicon-forward"></text>
</view>
</view>
<view class="menu-list ss-flex ss-flex-wrap">
<view v-for="(item, index) in state.menuList" :key="index" class="item-box ss-flex-col ss-col-center"
@tap="sheep.$router.go(item.path)">
<image class="menu-icon ss-m-b-10" :src="sheep.$url.static(item.img)" mode="aspectFill"></image>
<view>{{ item.title }}</view>
</view>
</view>
</view>
<view class="menu-box ss-flex-col">
<view class="header-box">
<image class="header-bg" :src="sheep.$url.static('/static/img/shop/commission/title1.png')" />
<view class="ss-flex header-title">
<view class="title">功能专区</view>
<text class="cicon-forward"></text>
</view>
</view>
<view class="menu-list ss-flex ss-flex-wrap">
<view
v-for="(item, index) in state.menuList"
:key="index"
class="item-box ss-flex-col ss-col-center"
@tap="sheep.$router.go(item.path)"
>
<image
class="menu-icon ss-m-b-10"
:src="sheep.$url.static(item.img)"
mode="aspectFill"
></image>
<view>{{ item.title }}</view>
</view>
</view>
</view>
</template>
<script setup>
import sheep from '@/sheep';
import { reactive } from 'vue';
import sheep from '@/sheep';
import { reactive } from 'vue';
const state = reactive({
menuList: [{
img: '/static/img/shop/commission/commission_icon1.png',
title: '我的团队',
path: '/pages/commission/team',
},
{
img: '/static/img/shop/commission/commission_icon2.png',
title: '佣金明细',
path: '/pages/commission/wallet',
},
{
img: '/static/img/shop/commission/commission_icon3.png',
title: '分销订单',
path: '/pages/commission/order',
},
{
img: '/static/img/shop/commission/commission_icon4.png',
title: '推广商品',
path: '/pages/commission/goods',
},
// {
// img: '/static/img/shop/commission/commission_icon5.png',
// title: '',
// path: '/pages/commission/apply',
// isAgentFrom: true,
// },
// todo @
{
img: '/static/img/shop/commission/commission_icon7.png',
title: '邀请海报',
path: 'action:showShareModal',
},
// TODO @ icon
const state = reactive({
menuList: [
{
// img: '/static/img/shop/commission/commission_icon7.png',
title: '推广排行',
path: '/pages/commission/promoter',
},
img: '/static/img/shop/commission/commission_icon1.png',
title: '我的团队',
path: '/pages/commission/team',
},
{
// img: '/static/img/shop/commission/commission_icon7.png',
title: '佣金排行',
path: '/pages/commission/commission-ranking',
}
],
});
img: '/static/img/shop/commission/commission_icon2.png',
title: '佣金明细',
path: '/pages/commission/wallet',
},
{
img: '/static/img/shop/commission/commission_icon3.png',
title: '分销订单',
path: '/pages/commission/order',
},
{
img: '/static/img/shop/commission/commission_icon4.png',
title: '推广商品',
path: '/pages/commission/goods',
},
// {
// img: '/static/img/shop/commission/commission_icon5.png',
// title: '',
// path: '/pages/commission/apply',
// isAgentFrom: true,
// },
{
img: '/static/img/shop/commission/commission_icon7.png',
title: '邀请海报',
path: 'action:showShareModal',
},
{
img: '/static/img/shop/commission/commission_icon8.png',
title: '推广排行',
path: '/pages/commission/promoter',
},
{
img: '/static/img/shop/commission/commission_icon9.png',
title: '佣金排行',
path: '/pages/commission/commission-ranking',
},
],
});
</script>
<style lang="scss" scoped>
.menu-box {
margin: 0 auto;
width: 690rpx;
margin-bottom: 20rpx;
margin-top: 20rpx;
border-radius: 12rpx;
z-index: 3;
position: relative;
}
.menu-box {
margin: 0 auto;
width: 690rpx;
margin-bottom: 20rpx;
margin-top: 20rpx;
border-radius: 12rpx;
z-index: 3;
position: relative;
}
.header-box {
width: 690rpx;
height: 76rpx;
position: relative;
.header-box {
width: 690rpx;
height: 76rpx;
position: relative;
.header-bg {
width: 690rpx;
height: 76rpx;
}
.header-bg {
width: 690rpx;
height: 76rpx;
}
.header-title {
position: absolute;
left: 20rpx;
top: 24rpx;
}
.header-title {
position: absolute;
left: 20rpx;
top: 24rpx;
}
.title {
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
line-height: 30rpx;
}
.title {
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
line-height: 30rpx;
}
.cicon-forward {
font-size: 30rpx;
font-weight: 400;
color: #ffffff;
line-height: 30rpx;
}
}
.cicon-forward {
font-size: 30rpx;
font-weight: 400;
color: #ffffff;
line-height: 30rpx;
}
}
.menu-list {
padding: 50rpx 0 10rpx 0;
background: #fdfae9;
border-radius: 0 0 12rpx 12rpx;
}
.menu-list {
padding: 50rpx 0 10rpx 0;
background: #fdfae9;
border-radius: 0 0 12rpx 12rpx;
}
.item-box {
width: 25%;
margin-bottom: 40rpx;
}
.item-box {
width: 25%;
margin-bottom: 40rpx;
}
.menu-icon {
width: 68rpx;
height: 68rpx;
background: #ffffff;
border-radius: 50%;
}
.menu-icon {
width: 68rpx;
height: 68rpx;
background: #ffffff;
border-radius: 50%;
}
.menu-title {
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
}
</style>
.menu-title {
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
}
</style>

View File

@ -14,12 +14,18 @@
>
<template #rightBottom>
<view class="ss-flex ss-row-between">
<view class="commission-num" v-if="item.brokerageMinPrice === undefined"></view>
<view class="commission-num" v-else-if="item.brokerageMinPrice === item.brokerageMaxPrice">
<view class="commission-num" v-if="item.brokerageMinPrice === undefined">
预计佣金计算中
</view>
<view
class="commission-num"
v-else-if="item.brokerageMinPrice === item.brokerageMaxPrice"
>
预计佣金{{ fen2yuan(item.brokerageMinPrice) }}
</view>
<view class="commission-num" v-else>
预计佣金{{ fen2yuan(item.brokerageMinPrice) }} ~ {{ fen2yuan(item.brokerageMaxPrice) }}
预计佣金{{ fen2yuan(item.brokerageMinPrice) }} ~
{{ fen2yuan(item.brokerageMaxPrice) }}
</view>
<button
class="ss-reset-button share-btn ui-BG-Main-Gradient"
@ -53,30 +59,29 @@
import $share from '@/sheep/platform/share';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import _ from 'lodash';
import _ from 'lodash-es';
import { showShareModal } from '@/sheep/hooks/useModal';
import SpuApi from '@/sheep/api/product/spu';
import BrokerageApi from '@/sheep/api/trade/brokerage';
import { fen2yuan } from '../../sheep/hooks/useGoods';
import { fen2yuan } from '@/sheep/hooks/useGoods';
const state = reactive({
pagination: {
list: [],
total: 0,
pageNo: 1,
pageSize: 1,
pageSize: 8,
},
loadStatus: '',
shareInfo: {},
});
// TODO
function onShareGoods(goodsInfo) {
state.shareInfo = $share.getShareInfo(
{
title: goodsInfo.title,
image: sheep.$url.cdn(goodsInfo.image),
desc: goodsInfo.subtitle,
title: goodsInfo.name,
image: sheep.$url.cdn(goodsInfo.picUrl),
desc: goodsInfo.introduction,
params: {
page: '2',
query: goodsInfo.id,
@ -84,10 +89,10 @@
},
{
type: 'goods', //
title: goodsInfo.title, //
image: sheep.$url.cdn(goodsInfo.image), //
price: goodsInfo.price[0], //
original_price: goodsInfo.original_price, //
title: goodsInfo.name, //
image: sheep.$url.cdn(goodsInfo.picUrl), //
price: fen2yuan(goodsInfo.price), //
original_price: fen2yuan(goodsInfo.marketPrice), //
},
);
showShareModal();
@ -99,19 +104,29 @@
pageSize: state.pagination.pageSize,
pageNo: state.pagination.pageNo,
});
if (code !== 0) {
state.loadStatus = 'error'; //
return;
}
// 使 Promise.all
await Promise.all(
data.list.map(async (item) => {
try {
const res = await BrokerageApi.getProductBrokeragePrice(item.id);
item.brokerageMinPrice = res.data.brokerageMinPrice;
item.brokerageMaxPrice = res.data.brokerageMaxPrice;
} catch (error) {
console.error(`获取商品【${item.name}】的佣金时出错:`, error);
}
}),
);
//
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
//
data.list.forEach((item) => {
BrokerageApi.getProductBrokeragePrice(item.id).then((res) => {
item.brokerageMinPrice = res.data.brokerageMinPrice;
item.brokerageMaxPrice = res.data.brokerageMaxPrice;
});
});
}
onLoad(() => {

View File

@ -1,37 +1,57 @@
<!-- 分销中心 -->
<template>
<s-layout navbar="inner" class="index-wrap" title="分销中心" :bgStyle="bgStyle" onShareAppMessage>
<!-- 分销商信息 -->
<commission-info />
<!-- 账户信息 -->
<account-info />
<!-- 菜单栏 -->
<commission-menu />
<!-- 分销记录 -->
<commission-log />
<s-layout
navbar="inner"
class="index-wrap"
title="分销中心"
:bgStyle="bgStyle"
:onShareAppMessage="shareInfo"
>
<!-- 分销商信息 -->
<commission-info />
<!-- 账户信息 -->
<account-info />
<!-- 菜单栏 -->
<commission-menu />
<!-- 分销记录 -->
<commission-log />
<!-- 权限弹窗 -->
<commission-auth />
</s-layout>
<!-- 权限弹窗 -->
<commission-auth />
</s-layout>
</template>
<script setup>
import { reactive } from 'vue';
import commissionInfo from './components/commission-info.vue';
import accountInfo from './components/account-info.vue';
import commissionLog from './components/commission-log.vue';
import commissionMenu from './components/commission-menu.vue';
import commissionAuth from './components/commission-auth.vue';
import { computed } from 'vue';
import commissionInfo from './components/commission-info.vue';
import accountInfo from './components/account-info.vue';
import commissionLog from './components/commission-log.vue';
import commissionMenu from './components/commission-menu.vue';
import commissionAuth from './components/commission-auth.vue';
import sheep from '@/sheep';
import { SharePageEnum } from '@/sheep/helper/const';
const state = reactive({});
/** 分销邀请 */
const shareInfo = computed(() => {
return sheep.$platform.share.getShareInfo(
{
params: {
page: SharePageEnum.HOME.value, //
},
},
{
type: 'user',
},
);
});
const bgStyle = {
color: '#F7D598',
};
const bgStyle = {
color: '#F7D598',
};
</script>
<style lang="scss" scoped>
:deep(.page-main) {
background-size: 100% 100% !important;
}
</style>
:deep(.page-main) {
background-size: 100% 100% !important;
}
</style>

View File

@ -41,10 +41,7 @@
<view class="no-box ss-flex ss-col-center ss-row-between">
<text class="order-code">订单编号{{ item.bizId }}</text>
<text class="order-state">
{{
item.status === 0 ? '待结算'
: item.status === 1 ? '已结算' : '已取消'
}}
{{ item.status === 0 ? '待结算' : item.status === 1 ? '已结算' : '已取消' }}
( 佣金 {{ fen2yuan(item.price) }} )
</text>
</view>
@ -75,11 +72,10 @@
<script setup>
import sheep from '@/sheep';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { onLoad, onPageScroll, onReachBottom } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import _ from 'lodash';
import { onPageScroll } from '@dcloudio/uni-app';
import { resetPagination } from '@/sheep/util';
import _ from 'lodash-es';
import { resetPagination } from '@/sheep/helper/utils';
import BrokerageApi from '@/sheep/api/trade/brokerage';
import { fen2yuan } from '../../sheep/hooks/useGoods';
@ -100,22 +96,22 @@
list: [],
total: 0,
pageNo: 1,
pageSize: 1,
pageSize: 8,
},
});
const tabMaps = [
{
name: '全部',
value: 'all',
value: -1,
},
{
name: '待结算',
value: '0', //
value: 0, //
},
{
name: '已结算',
value: '1', //
value: 1, //
},
];
@ -129,12 +125,17 @@
//
async function getOrderList() {
state.loadStatus = 'loading';
let { code, data } = await BrokerageApi.getBrokerageRecordPage({
const tab = tabMaps[state.currentTab];
const queryParams = {
pageSize: state.pagination.pageSize,
pageNo: state.pagination.pageSize,
pageNo: state.pagination.pageNo,
bizType: 1, // 广
status: state.currentTab > 0 ? state.currentTab : undefined,
});
status: tab.value,
};
if (tab.value < 0) {
delete queryParams.status;
}
const { code, data } = await BrokerageApi.getBrokerageRecordPage(queryParams);
if (code !== 0) {
return;
}

File diff suppressed because one or more lines are too long

View File

@ -1,8 +1,31 @@
<!-- 页面 TODO 芋艿该页面的实现代码需要优化包括 js css以及相关的样式设计 -->
<!-- 页面 -->
<template>
<s-layout title="我的团队" :class="state.scrollTop ? 'team-wrap' : ''" navbar="inner">
<view
class="header-box"
:style="[
{
marginTop: '-' + Number(statusBarHeight + 88) + 'rpx',
paddingTop: Number(statusBarHeight + 108) + 'rpx',
},
]"
>
<!-- 推广数据总览 -->
<view class="team-data-box ss-flex ss-col-center ss-row-between" style="width: 100%">
<view class="data-card" style="width: 100%">
<view class="total-item" style="width: 100%">
<view class="item-title" style="text-align: center">推广人数</view>
<view class="total-num" style="text-align: center">
{{
state.summary.firstBrokerageUserCount + state.summary.secondBrokerageUserCount || 0
}}
</view>
</view>
</view>
</view>
</view>
<view class="promoter-list">
<view
<!--<view
class="promoterHeader bg-color"
style="backgroundcolor: #e93323 !important; height: 218rpx; color: #fff"
>
@ -21,9 +44,9 @@
</view>
<view class="iconfont icon-tuandui" />
</view>
</view>
<view style="padding: 0 30rpx">
<view class="nav acea-row row-around l1">
</view>-->
<view style="padding: 0 20rpx">
<view class="nav acea-row row-around l1" style="margin-top: 20rpx">
<view :class="state.level == 1 ? 'item on' : 'item'" @click="setType(1)">
一级({{ state.summary.firstBrokerageUserCount || 0 }})
</view>
@ -45,7 +68,7 @@
/>
</view>
<image
src="/static/images/search.png"
:src="sheep.$url.static('/static/img/shop/search.png')"
mode=""
style="width: 60rpx; height: 64rpx"
@click="submitForm"
@ -59,8 +82,7 @@
v-if="sort === 'userCountDESC'"
>
团队排序
<!-- TODO 芋艿看看怎么从项目里拿出去 -->
<image src="/static/images/sort1.png" />
<image :src="sheep.$url.static('/static/img/shop/sort1.png')" />
</view>
<view
class="sortItem"
@ -68,15 +90,15 @@
v-else-if="sort === 'userCountASC'"
>
团队排序
<image src="/static/images/sort3.png" />
<image :src="sheep.$url.static('/static/img/shop/sort3.png')" />
</view>
<view class="sortItem" @click="setSort('userCount', 'desc')" v-else>
团队排序
<image src="/static/images/sort2.png" />
<image :src="sheep.$url.static('/static/img/shop/sort2.png')" />
</view>
<view class="sortItem" @click="setSort('price', 'asc')" v-if="sort === 'priceDESC'">
金额排序
<image src="/static/images/sort1.png" />
<image :src="sheep.$url.static('/static/img/shop/sort1.png')" />
</view>
<view
class="sortItem"
@ -84,11 +106,11 @@
v-else-if="sort === 'priceASC'"
>
金额排序
<image src="/static/images/sort3.png" />
<image :src="sheep.$url.static('/static/img/shop/sort3.png')" />
</view>
<view class="sortItem" @click="setSort('price', 'desc')" v-else>
金额排序
<image src="/static/images/sort2.png" />
<image :src="sheep.$url.static('/static/img/shop/sort2.png')" />
</view>
<view
class="sortItem"
@ -96,7 +118,7 @@
v-if="sort === 'orderCountDESC'"
>
订单排序
<image src="/static/images/sort1.png" />
<image :src="sheep.$url.static('/static/img/shop/sort1.png')" />
</view>
<view
class="sortItem"
@ -104,11 +126,11 @@
v-else-if="sort === 'orderCountASC'"
>
订单排序
<image src="/static/images/sort3.png" />
<image :src="sheep.$url.static('/static/img/shop/sort3.png')" />
</view>
<view class="sortItem" @click="setSort('orderCount', 'desc')" v-else>
订单排序
<image src="/static/images/sort2.png" />
<image :src="sheep.$url.static('/static/img/shop/sort2.png')" />
</view>
</view>
<block v-for="(item, index) in state.pagination.list" :key="index">
@ -145,14 +167,14 @@
></view
>
<view>
<text class="num">{{ item.brokeragePrice || 0 }}</text
<text class="num">{{ fen2yuan(item.brokeragePrice) || 0 }}</text
>
</view>
</view>
</view>
</block>
<block v-if="state.pagination.list.length === 0">
<view style="text-align: center">暂无推广人数</view>
<view style="text-align: center; margin-top: 30rpx">暂无推广人数</view>
</block>
</view>
</view>
@ -235,9 +257,10 @@
import sheep from '@/sheep';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { computed, reactive, ref } from 'vue';
import _ from 'lodash';
import _ from 'lodash-es';
import { onPageScroll } from '@dcloudio/uni-app';
import BrokerageApi from '@/sheep/api/trade/brokerage';
import { fen2yuan } from '../../sheep/hooks/useGoods';
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
// const agentInfo = computed(() => sheep.$store('user').agentInfo);
@ -456,7 +479,7 @@
.promoter-list .nav .item.on {
border-bottom: 5rpx solid;
// $theme-color
color: red;
color: var(--ui-BG-Main);
// $theme-color
}

File diff suppressed because it is too large Load Diff

View File

@ -15,7 +15,10 @@
<view class="num-title">可提现金额</view>
<view class="wallet-num">{{ fen2yuan(state.brokerageInfo.brokeragePrice) }}</view>
</view>
<button class="ss-reset-button log-btn" @tap="sheep.$router.go('/pages/commission/wallet', { type: 2 })">
<button
class="ss-reset-button log-btn"
@tap="sheep.$router.go('/pages/commission/wallet', { type: 2 })"
>
提现记录
</button>
</view>
@ -26,9 +29,10 @@
<view class="bank-list ss-flex ss-col-center" @tap="onAccountSelect(true)">
<view v-if="!state.accountInfo.type" class="empty-text"></view>
<view v-if="state.accountInfo.type === '1'" class="empty-text"></view>
<view v-if="state.accountInfo.type === '2'" class="empty-text"></view>
<view v-if="state.accountInfo.type === '3'" class="empty-text"></view>
<view v-if="state.accountInfo.type === '4'" class="empty-text"></view>
<view v-if="state.accountInfo.type === '2'" class="empty-text"></view>
<view v-if="state.accountInfo.type === '3'" class="empty-text"></view>
<view v-if="state.accountInfo.type === '4'" class="empty-text"></view>
<view v-if="state.accountInfo.type === '5'" class="empty-text"></view>
<text class="cicon-forward" />
</view>
</view>
@ -45,71 +49,85 @@
/>
</view>
<!-- 提现账号 -->
<view class="card-title" v-show="['2', '3', '4'].includes(state.accountInfo.type)">
<view class="card-title" v-show="['2', '6'].includes(state.accountInfo.type)">
提现账号
</view>
<view
class="input-box ss-flex ss-col-center border-bottom"
v-show="['2', '3', '4'].includes(state.accountInfo.type)"
v-show="['2', '6'].includes(state.accountInfo.type)"
>
<view class="unit" />
<uni-easyinput
:inputBorder="false"
class="ss-flex-1 ss-p-l-10"
v-model="state.accountInfo.accountNo"
v-model="state.accountInfo.userAccount"
placeholder="请输入提现账号"
/>
</view>
<!-- 收款码 -->
<view class="card-title" v-show="['2', '3'].includes(state.accountInfo.type)"></view>
<view class="card-title" v-show="['3', '4'].includes(state.accountInfo.type)"></view>
<view
class="input-box ss-flex ss-col-center"
v-show="['2', '3'].includes(state.accountInfo.type)"
v-show="['3', '4'].includes(state.accountInfo.type)"
>
<view class="unit" />
<view class="upload-img">
<s-uploader
v-model:url="state.accountInfo.accountQrCodeUrl"
v-model:url="state.accountInfo.qrCodeUrl"
fileMediatype="image"
limit="1"
mode="grid"
:imageStyles="{ width: '168rpx', height: '168rpx' }"
@success="(payload) => (state.accountInfo.qrCodeUrl = payload.tempFilePaths[0])"
/>
</view>
</view>
<!-- 持卡人姓名 -->
<view class="card-title" v-show="state.accountInfo.type === '4'"></view>
<view class="card-title" v-show="['2', '5', '6'].includes(state.accountInfo.type)">
收款真名
</view>
<view
class="input-box ss-flex ss-col-center border-bottom"
v-show="state.accountInfo.type === '4'"
v-show="['2', '5', '6'].includes(state.accountInfo.type)"
>
<view class="unit" />
<uni-easyinput
:inputBorder="false"
class="ss-flex-1 ss-p-l-10"
v-model="state.accountInfo.name"
placeholder="请输入持卡人姓名"
v-model="state.accountInfo.userName"
placeholder="请输入收款真名"
/>
</view>
<!-- 提现银行 -->
<view class="card-title" v-show="state.accountInfo.type === '4'"></view>
<view class="card-title" v-show="state.accountInfo.type === '2'"></view>
<view
class="input-box ss-flex ss-col-center border-bottom"
v-show="state.accountInfo.type === '4'"
v-show="state.accountInfo.type === '2'"
>
<view class="unit" />
<uni-easyinput
:inputBorder="false"
class="ss-flex-1 ss-p-l-10"
v-model="state.accountInfo.bankName"
placeholder="请输入提现银行"
/>
<!--银行改为下拉选择-->
<picker
@change="bankChange"
:value="state.bankListSelectedIndex"
:range="state.bankList"
range-key="label"
style="width: 100%"
>
<uni-easyinput
:inputBorder="false"
:value="state.accountInfo.bankName"
placeholder="请选择银行"
suffixIcon="right"
disabled
:styles="{ disableColor: '#fff', borderColor: '#fff', color: '#333!important' }"
/>
</picker>
</view>
<!-- 开户地址 -->
<view class="card-title" v-show="state.accountInfo.type === '4'"></view>
<view class="card-title" v-show="state.accountInfo.type === '2'"></view>
<view
class="input-box ss-flex ss-col-center border-bottom"
v-show="state.accountInfo.type === '4'"
v-show="state.accountInfo.type === '2'"
>
<view class="unit" />
<uni-easyinput
@ -146,25 +164,26 @@
</template>
<script setup>
import { computed, reactive, onBeforeMount } from 'vue';
import { onBeforeMount, reactive } from 'vue';
import sheep from '@/sheep';
import accountTypeSelect from './components/account-type-select.vue';
import { fen2yuan } from '@/sheep/hooks/useGoods';
import TradeConfigApi from '@/sheep/api/trade/config';
import BrokerageApi from '@/sheep/api/trade/brokerage';
import DictApi from '@/sheep/api/system/dict';
import SLayout from '@/sheep/components/s-layout/s-layout.vue';
import { getWeixinPayChannelCode, goBindWeixin } from '@/sheep/platform/pay';
const headerBg = sheep.$url.css('/static/img/shop/user/withdraw_bg.png');
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const userStore = sheep.$store('user');
const userInfo = computed(() => userStore.userInfo);
const state = reactive({
accountInfo: {
//
type: undefined,
accountNo: undefined,
accountQrCodeUrl: undefined,
name: undefined,
userAccount: undefined,
userName: undefined,
qrCodeUrl: undefined,
bankName: undefined,
bankAddress: undefined,
},
@ -176,6 +195,8 @@
frozenDays: 0, //
minPrice: 0, //
withdrawTypes: [], //
bankList: [], //
bankListSelectedIndex: '', // bankList index
});
//
@ -186,7 +207,6 @@
//
const onConfirm = async () => {
//
debugger;
if (
!state.accountInfo.price ||
state.accountInfo.price > state.brokerageInfo.price ||
@ -199,11 +219,31 @@
sheep.$helper.toast('请选择提现方式');
return;
}
let openid;
if (state.accountInfo.type === '5') {
openid = await sheep.$platform.useProvider('wechat').getOpenid();
// openid
if (!openid) {
goBindWeixin();
return;
}
}
//
let { code } = await BrokerageApi.createBrokerageWithdraw({
const data = {
...state.accountInfo,
price: state.accountInfo.price * 100,
});
};
if (state.accountInfo.type === '5') {
data.userAccount = openid;
data.transferChannelCode = getWeixinPayChannelCode();
} else if (state.accountInfo.type === '6' || state.accountInfo.type === '2') {
delete data.transferChannelCode;
} else {
delete data.userAccount;
delete data.transferChannelCode;
}
let { code } = await BrokerageApi.createBrokerageWithdraw(data);
if (code !== 0) {
return;
}
@ -215,12 +255,12 @@
confirmText: '查看记录',
success: (res) => {
if (res.confirm) {
sheep.$router.go('/pages/commission/wallet', { type: 2 })
sheep.$router.go('/pages/commission/wallet', { type: 2 });
return;
}
getBrokerageUser();
state.accountInfo = {};
}
},
});
};
@ -245,10 +285,29 @@
}
}
//
async function getDictDataListByType() {
let { code, data } = await DictApi.getDictDataListByType('brokerage_bank_name');
if (code !== 0) {
return;
}
if (data && data.length > 0) {
state.bankList = data;
}
}
//
function bankChange(e) {
const value = e.detail.value;
state.bankListSelectedIndex = value;
state.accountInfo.bankName = state.bankList[value].label;
}
onBeforeMount(() => {
getWithdrawRules();
getBrokerageUser()
})
getBrokerageUser();
getDictDataListByType(); //
});
</script>
<style lang="scss" scoped>

View File

@ -0,0 +1,81 @@
<template>
<view class="content-container">
<span v-for="(part, index) in formattedContent" :key="index"
@click="handleClick(part)"
:class="{'highlight-number': part.isNumber, 'phone-number': part.isPhone}">
{{ part.text }}
</span>
</view>
</template>
<script>
export default {
name: 'HighlightNumber',
props: {
content: {
type: String,
required: true
}
},
computed: {
formattedContent() {
const phoneRegex = /(1[3-9]\d{9})/g;
const numberRegex = /(\d+)/g;
let text = this.content;
let result = [];
let match;
// Step 1:
while ((match = phoneRegex.exec(text)) !== null) {
if (match.index > 0) {
const before = text.slice(0, match.index);
result.push(...this.splitAndPush(before, false, false));
}
result.push({ text: match[0], isNumber: true, isPhone: true });
text = text.slice(match.index + match[0].length);
}
// Step 2:
while ((match = numberRegex.exec(text)) !== null) {
if (match.index > 0) {
const before = text.slice(0, match.index);
result.push(...this.splitAndPush(before, false, false));
}
result.push({ text: match[0], isNumber: true });
text = text.slice(match.index + match[0].length);
}
// Step 3:
if (text.length > 0) {
result.push(...this.splitAndPush(text, false, false));
}
return result;
}
},
methods: {
splitAndPush(str, isNumber = false, isPhone = false) {
return str.split('').map(char => ({ text: char, isNumber, isPhone }));
},
handleClick(part) {
if (part.isPhone) {
this.$emit('phone-click', { phoneNumber: part.text });
} else if (part.isNumber) {
this.$emit('number-click', { number: part.text });
}
}
}
};
</script>
<style scoped>
.highlight-number {
color: #ff5722;
font-weight: bold;
}
.phone-number {
color: #007AFF;
text-decoration: underline;
}
</style>

View File

@ -16,13 +16,16 @@
<view class="title ss-m-t-50 ss-m-b-20 ss-m-x-20">{{ state.coupon.name }}</view>
<view class="subtitle ss-m-b-50">
{{ fen2yuan(state.coupon.usePrice) }}
{{ state.coupon.discountType === 1
? '减 ' + fen2yuan(state.coupon.discountPrice) + ' 元'
: '打 ' + state.coupon.discountPercent / 10.0 + ' 折' }}
{{
state.coupon.discountType === 1
? '减 ' + fen2yuan(state.coupon.discountPrice) + ' 元'
: '打 ' + state.coupon.discountPercent / 10.0 + ' 折'
}}
</view>
<button
class="ss-reset-button ss-m-b-30"
:class="state.coupon.canTake || state.coupon.status === 1
:class="
state.coupon.canTake || state.coupon.status === 1
? 'use-btn' // 使
: 'disable-btn'
"
@ -31,7 +34,13 @@
>
<text v-if="state.id > 0">{{ state.coupon.canTake ? '' : '' }}</text>
<text v-else>
{{ state.coupon.status === 1 ? '立即使用' : state.coupon.status === 2 ? '已使用' : '已过期' }}
{{
state.coupon.status === 1
? '可使用'
: state.coupon.status === 2
? '已使用'
: '已过期'
}}
</text>
</button>
<view class="time ss-m-y-30" v-if="state.coupon.validityType === 2">
@ -48,7 +57,6 @@
<view>优惠券类型</view>
<view>{{ state.coupon.discountType === 1 ? '满减券' : '折扣券' }}</view>
</view>
<!-- TODO 芋艿可优化增加优惠劵的描述 -->
<uni-collapse>
<uni-collapse-item title="优惠券说明" v-if="state.coupon.description">
<view class="content ss-p-b-20">
@ -140,12 +148,12 @@
import sheep from '@/sheep';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import _ from 'lodash';
import _ from 'lodash-es';
import CouponApi from '@/sheep/api/promotion/coupon';
import { fen2yuan } from '@/sheep/hooks/useGoods';
import SpuApi from '@/sheep/api/product/spu';
import CategoryApi from '@/sheep/api/product/category';
import { resetPagination } from '@/sheep/util';
import { resetPagination } from '@/sheep/helper/utils';
const state = reactive({
id: 0, // templateId
@ -156,7 +164,7 @@
list: [],
total: 0,
pageNo: 1,
pageSize: 1,
pageSize: 8,
},
categoryId: 0, //
tabMaps: [], // tab
@ -176,7 +184,7 @@
const { code, data } = await SpuApi.getSpuPage({
categoryId: state.categoryId,
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize
pageSize: state.pagination.pageSize,
});
if (code !== 0) {
return;
@ -197,7 +205,9 @@
//
async function getCategoryList() {
const { data, code } = await CategoryApi.getCategoryListByIds(state.coupon.productScopeValues.join(','));
const { data, code } = await CategoryApi.getCategoryListByIds(
state.coupon.productScopeValues.join(','),
);
if (code !== 0) {
return;
}
@ -225,8 +235,10 @@
//
async function getCouponContent() {
const { code, data } = state.id > 0 ? await CouponApi.getCouponTemplate(state.id)
: await CouponApi.getCoupon(state.couponId);
const { code, data } =
state.id > 0
? await CouponApi.getCouponTemplate(state.id)
: await CouponApi.getCoupon(state.couponId);
if (code !== 0) {
return;
}

View File

@ -1,6 +1,6 @@
<!-- 优惠券中心 -->
<template>
<s-layout title="优惠券" :bgStyle="{ color: '#f2f2f2' }">
<s-layout :bgStyle="{ color: '#f2f2f2' }" title="优惠券">
<su-sticky bgColor="#fff">
<su-tabs
:list="tabMaps"
@ -45,7 +45,7 @@
<template #default>
<button
class="ss-reset-button card-btn ss-flex ss-row-center ss-col-center"
:class=" item.status !== 1 ? 'disabled-btn': ''"
:class="item.status !== 1 ? 'disabled-btn' : ''"
:disabled="item.status !== 1"
@click.stop="sheep.$router.go('/pages/coupon/detail', { couponId: item.id })"
>
@ -56,9 +56,14 @@
</view>
</template>
<uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}" @tap="loadMore" />
}"
@tap="loadMore"
/>
</s-layout>
</template>
@ -66,8 +71,8 @@
import sheep from '@/sheep';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import _ from 'lodash';
import { resetPagination } from '@/sheep/util';
import _ from 'lodash-es';
import { resetPagination } from '@/sheep/helper/utils';
import CouponApi from '@/sheep/api/promotion/coupon';
//
@ -78,7 +83,7 @@
list: [],
total: 0,
pageNo: 1,
pageSize: 5
pageSize: 5,
},
loadStatus: '',
});
@ -102,13 +107,12 @@
},
];
// TODO yunai:
function onTabsChange(e) {
state.currentTab = e.index;
state.type = e.value;
resetPagination(state.pagination)
resetPagination(state.pagination);
if (state.currentTab === 0) {
getData();
getData();
} else {
getCoupon();
}
@ -135,7 +139,7 @@
const { data, code } = await CouponApi.getCouponPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
status: state.type
status: state.type,
});
if (code !== 0) {
return;
@ -177,13 +181,13 @@
//
if (Option.type === 'all' || !Option.type) {
getData();
//
//
} else {
Option.type === 'geted'
? (state.currentTab = 1)
: Option.type === 'used'
? (state.currentTab = 2)
: (state.currentTab = 3);
? (state.currentTab = 2)
: (state.currentTab = 3);
state.type = state.currentTab;
getCoupon();
}

View File

@ -1,89 +1,130 @@
<!-- 评价 -->
<template>
<s-layout title="评价">
<view>
<view v-for="(item, index) in state.orderInfo.items" :key="item.id">
<view>
<view class="commont-from-wrap">
<!-- 评价商品 -->
<s-goods-item
<s-layout title="评价">
<view>
<view v-for="(item, index) in state.orderInfo.items" :key="item.id">
<view>
<view class="commont-from-wrap">
<!-- 评价商品 -->
<s-goods-item
:img="item.picUrl"
:title="item.spuName"
:skuText="item.properties.map((property) => property.valueName).join(' ')"
:price="item.payPrice"
:price="item.payPrice"
:num="item.count"
/>
</view>
</view>
<view class="form-item">
<!-- 评分 -->
<view class="star-box ss-flex ss-col-center">
<view class="star-title ss-m-r-40">商品质量</view>
<uni-rate v-model="state.commentList[index].descriptionScores" />
</view>
<view class="star-box ss-flex ss-col-center">
<view class="star-title ss-m-r-40">服务态度</view>
<uni-rate v-model="state.commentList[index].benefitScores" />
</view>
<!-- 评价 -->
<view class="area-box">
<uni-easyinput :inputBorder="false" type="textarea" maxlength="120" autoHeight
v-model="state.commentList[index].content"
placeholder="宝贝满足你的期待吗?说说你的使用心得,分享给想买的他们吧~" />
<!-- TODO 芋艿文件上传 -->
<view class="img-box">
<s-uploader v-model:url="state.commentList[index].images" fileMediatype="image"
limit="9" mode="grid" :imageStyles="{ width: '168rpx', height: '168rpx' }" />
</view>
</view>
</view>
</view>
</view>
</view>
<!-- TODO 芋艿是否匿名 -->
<su-fixed bottom placeholder>
<view class="foot_box ss-flex ss-row-center ss-col-center">
<button class="ss-reset-button post-btn ui-BG-Main-Gradient ui-Shadow-Main" @tap="onSubmit">
发布
</button>
</view>
</su-fixed>
</s-layout>
<view class="form-item">
<!-- 评分 -->
<view class="star-box ss-flex ss-col-center">
<view class="star-title ss-m-r-40">商品质量</view>
<uni-rate v-model="state.commentList[index].descriptionScores" />
</view>
<view class="star-box ss-flex ss-col-center">
<view class="star-title ss-m-r-40">服务态度</view>
<uni-rate v-model="state.commentList[index].benefitScores" />
</view>
<!-- 评价 -->
<view class="area-box">
<uni-easyinput
:inputBorder="false"
type="textarea"
maxlength="120"
autoHeight
v-model="state.commentList[index].content"
placeholder="宝贝满足你的期待吗?说说你的使用心得,分享给想买的他们吧~"
/>
<view class="img-box">
<s-uploader
v-model:url="state.commentList[index].images"
fileMediatype="image"
limit="9"
mode="grid"
:imageStyles="{ width: '168rpx', height: '168rpx' }"
@success="(payload) => uploadSuccess(payload, index)"
/>
</view>
</view>
<view class="checkbox-container">
<checkbox-group @change="(event) => toggleAnonymous(index, event)">
<label>
<checkbox value="anonymousChecked" />
匿名评论
</label>
</checkbox-group>
</view>
</view>
</view>
</view>
</view>
<su-fixed bottom placeholder>
<view class="foot_box ss-flex ss-row-center ss-col-center">
<button class="ss-reset-button post-btn ui-BG-Main-Gradient ui-Shadow-Main" @tap="onSubmit">
发布
</button>
</view>
</su-fixed>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import OrderApi from '@/sheep/api/trade/order';
const state = reactive({
orderInfo: {},
commentList: [],
id: null
});
const state = reactive({
orderInfo: {},
commentList: [],
id: null,
});
async function onSubmit() {
/**
* 切换是否匿名
*
* @param commentIndex 当前评论下标
* @param event 复选框事件
*/
function toggleAnonymous(commentIndex, event) {
state.commentList[commentIndex].anonymous = event.detail.value[0] === 'anonymousChecked';
}
/**
* 发布评论
*
* @returns {Promise<void>}
*/
async function onSubmit() {
//
for (const comment of state.commentList) {
await OrderApi.createOrderItemComment(comment);
}
//
sheep.$router.back();
}
}
onLoad(async (options) => {
/**
* 图片添加到表单
*
* @param payload 上传成功后的回调数据
* @param commentIndex 当前评论的下标
*/
function uploadSuccess(payload, commentIndex) {
state.commentList[commentIndex].picUrls = payload.tempFilePaths;
}
onLoad(async (options) => {
if (!options.id) {
sheep.$helper.toast(`缺少订单信息,请检查`);
return
return;
}
state.id = options.id;
state.id = options.id;
const { code, data } = await OrderApi.getOrder(state.id);
const { code, data } = await OrderApi.getOrderDetail(state.id);
if (code !== 0) {
sheep.$helper.toast('无待评价订单');
return
return;
}
//
data.items.forEach((item) => {
@ -93,53 +134,57 @@
descriptionScores: 5,
benefitScores: 5,
content: '',
picUrls: []
picUrls: [],
});
});
state.orderInfo = data;
});
});
</script>
<style lang="scss" scoped>
//
.goods-card {
margin: 10rpx 0;
padding: 20rpx;
background: #fff;
}
//
.goods-card {
margin: 10rpx 0;
padding: 20rpx;
background: #fff;
}
//
.form-item {
background: #fff;
//
.form-item {
background: #fff;
.star-box {
height: 100rpx;
padding: 0 25rpx;
}
.star-box {
height: 100rpx;
padding: 0 25rpx;
}
.star-title {
font-weight: 600;
}
}
.star-title {
font-weight: 600;
}
}
.area-box {
width: 690rpx;
min-height: 306rpx;
background: rgba(249, 250, 251, 1);
border-radius: 20rpx;
padding: 28rpx;
margin: auto;
.area-box {
width: 690rpx;
min-height: 306rpx;
background: rgba(249, 250, 251, 1);
border-radius: 20rpx;
padding: 28rpx;
margin: auto;
.img-box {
margin-top: 20rpx;
}
}
.img-box {
margin-top: 20rpx;
}
}
.post-btn {
width: 690rpx;
line-height: 80rpx;
border-radius: 40rpx;
color: rgba(#fff, 0.9);
margin-bottom: 20rpx;
}
</style>
.checkbox-container {
padding: 10rpx;
}
.post-btn {
width: 690rpx;
line-height: 80rpx;
border-radius: 40rpx;
color: rgba(#fff, 0.9);
margin-bottom: 20rpx;
}
</style>

View File

@ -1,6 +1,6 @@
<!-- 商品评论的分页 -->
<template>
<s-layout title="全部评">
<s-layout title="全部评">
<su-tabs
:list="state.type"
:scrollable="false"
@ -16,6 +16,7 @@
<s-empty v-if="state.pagination.total === 0" text="暂无数据" icon="/static/data-empty.png" />
<!-- 下拉 -->
<uni-load-more
icon-type="auto"
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
@ -30,7 +31,7 @@
import CommentApi from '@/sheep/api/product/comment';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import _ from 'lodash';
import _ from 'lodash-es';
import commentItem from '../components/detail/comment-item.vue';
const state = reactive({
@ -47,7 +48,7 @@
list: [],
total: 0,
pageNo: 1,
pageSize: 1,
pageSize: 8,
},
});

View File

@ -2,7 +2,6 @@
<su-fixed bottom placeholder :val="44">
<view>
<view v-for="activity in props.activityList" :key="activity.id">
<!-- TODO 芋艿拼团 -->
<view
class="activity-box ss-p-x-38 ss-flex ss-row-between ss-col-center"
:class="activity.type === 1 ? 'seckill-box' : 'groupon-box'"
@ -14,7 +13,6 @@
:src="sheep.$url.static('/static/img/shop/goods/seckill-icon.png')"
class="activity-icon"
/>
<!-- TODO 芋艿拼团 -->
<image
v-else-if="activity.type === 3"
:src="sheep.$url.static('/static/img/shop/goods/groupon-icon.png')"
@ -33,7 +31,6 @@
<script setup>
import sheep from '@/sheep';
// TODO
const seckillBg = sheep.$url.css('/static/img/shop/goods/seckill-tip-bg.png');
const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png');
@ -42,14 +39,16 @@
type: Array,
default() {
return [];
}
}
},
},
});
function onActivity(activity) {
const type = activity.type;
const typePath = type === 1 ? 'seckill' :
type === 2 ? 'TODO 拼团' : 'groupon';
const typePath = type === 1 ? 'seckill' : type === 3 ? 'groupon' : undefined;
if (!typePath) {
return;
}
sheep.$router.go(`/pages/goods/${typePath}`, {
id: activity.id,
});

View File

@ -1,4 +1,3 @@
<!-- 商品详情描述卡片 -->
<template>
<view class="detail-content-card bg-white ss-m-x-20 ss-p-t-20">
<view class="card-header ss-flex ss-col-center ss-m-b-30 ss-m-l-20">
@ -6,15 +5,12 @@
<view class="title ss-m-l-20 ss-m-r-20">详情</view>
</view>
<view class="card-content">
<mp-html :content="content" />
<mp-html :content="content"></mp-html>
</view>
</view>
</template>
<script setup>
import sheep from '@/sheep';
const { safeAreaInsets } = sheep.$platform.device;
const props = defineProps({
content: {
type: String,
@ -49,4 +45,10 @@
}
}
}
:deep() {
image {
display: block;
}
}
</style>

View File

@ -45,7 +45,7 @@
import { onPageScroll } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import throttle from '@/sheep/helper/throttle.js';
import { showMenuTools, closeMenuTools } from '@/sheep/hooks/useModal';
import { showMenuTools } from '@/sheep/hooks/useModal';
const sys_statusBar = sheep.$platform.device.statusBarHeight;
const sys_navBar = sheep.$platform.navbar;
@ -98,7 +98,8 @@
function getCommentCardNode() {
return new Promise((res, rej) => {
uni.createSelectorQuery()
uni
.createSelectorQuery()
.select('.detail-comment-selector')
.boundingClientRect((data) => {
if (data) {

View File

@ -2,7 +2,6 @@
<template>
<view v-if="state.list.length > 0" class="groupon-list detail-card ss-p-x-20">
<view class="join-activity ss-flex ss-row-between ss-m-t-30">
<!-- todo: 接口缺少总数 -->
<view class="">已有{{ state.list.length }}人参与活动</view>
<text class="cicon-forward"></text>
</view>
@ -37,7 +36,7 @@
import { onMounted, reactive } from 'vue';
import sheep from '@/sheep';
import { useDurationTime } from '@/sheep/hooks/useGoods';
import CombinationApi from "@/sheep/api/promotion/combination";
import CombinationApi from '@/sheep/api/promotion/combination';
const props = defineProps({
modelValue: {
@ -74,7 +73,7 @@
onMounted(async () => {
//
// status = 0
const { data } = await CombinationApi.getHeadCombinationRecordList(props.modelValue.id, 0 , 10);
const { data } = await CombinationApi.getHeadCombinationRecordList(props.modelValue.id, 0, 10);
state.list = data;
});
</script>

View File

@ -7,12 +7,16 @@
<detailSkeleton v-if="state.skeletonLoading" />
<!-- 下架/售罄提醒 -->
<s-empty
v-else-if="state.goodsInfo === null || state.activity.status !== 0 || state.activity.endTime < new Date().getTime()"
text="活动不存在或已结束"
icon="/static/soldout-empty.png"
showAction
actionText="返回上一页"
@clickAction="sheep.$router.back()"
v-else-if="
state.goodsInfo === null ||
state.activity.status !== 0 ||
state.activity.endTime < new Date().getTime()
"
text="活动不存在或已结束"
icon="/static/soldout-empty.png"
showAction
actionText="返回上一页"
@clickAction="sheep.$router.back()"
/>
<block v-else>
<view class="detail-swiper-selector">
@ -47,13 +51,10 @@
</view>
</view>
<view class="ss-flex ss-row-between">
<view
class="origin-price ss-flex ss-col-center"
v-if="state.goodsInfo.price"
>
<view class="origin-price ss-flex ss-col-center" v-if="state.goodsInfo.price">
单买价
<view class="origin-price-text">
{{ fen2yuan(state.goodsInfo.price) }}
{{ fen2yuan(state.goodsInfo.marketPrice) }}
</view>
</view>
</view>
@ -79,7 +80,7 @@
<!-- 功能卡片 -->
<view class="detail-cell-card detail-card ss-flex-col">
<!-- 规格 -->
<detail-cell-sku :sku="state.selectedSkuPrice" @tap="state.showSelectSku = true" />
<detail-cell-sku :sku="state.selectedSku" @tap="state.showSelectSku = true" />
</view>
<!-- 参团列表 -->
@ -103,7 +104,6 @@
<detail-content-card class="detail-content-selector" :content="state.goodsInfo.description" />
<!-- 商品tabbar -->
<!-- TODO: 已售罄预热 判断 设计-->
<detail-tabbar v-model="state.goodsInfo">
<view class="buy-box ss-flex ss-col-center ss-p-r-20">
<button
@ -123,7 +123,14 @@
"
:disabled="state.goodsInfo.stock === 0 || state.activity.status !== 0"
>
<view class="btn-price">{{ fen2yuan(state.activity.price || state.goodsInfo.price) }}</view>
<view class="btn-price">{{
fen2yuan(
state.selectedSku.price * state.selectedSku.count ||
state.activity.price * state.selectedSku.count ||
state.goodsInfo.price * state.selectedSku.count ||
state.goodsInfo.price,
)
}}</view>
<view v-if="state.activity.startTime > new Date().getTime()"></view>
<view v-else-if="state.activity.endTime <= new Date().getTime()">已结束</view>
<view v-else>
@ -141,7 +148,7 @@
import { reactive, computed } from 'vue';
import { onLoad, onPageScroll } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import { isEmpty } from 'lodash';
import { isEmpty } from 'lodash-es';
import detailNavbar from './components/detail/detail-navbar.vue';
import detailCellSku from './components/detail/detail-cell-sku.vue';
import detailTabbar from './components/detail/detail-tabbar.vue';
@ -149,29 +156,28 @@
import detailCommentCard from './components/detail/detail-comment-card.vue';
import detailContentCard from './components/detail/detail-content-card.vue';
import grouponCardList from './components/groupon/groupon-card-list.vue';
import {useDurationTime, formatGoodsSwiper, fen2yuan} from '@/sheep/hooks/useGoods';
import CombinationApi from "@/sheep/api/promotion/combination";
import SpuApi from "@/sheep/api/product/spu";
import { useDurationTime, formatGoodsSwiper, fen2yuan } from '@/sheep/hooks/useGoods';
import CombinationApi from '@/sheep/api/promotion/combination';
import SpuApi from '@/sheep/api/product/spu';
import { SharePageEnum } from '@/sheep/helper/const';
const headerBg = sheep.$url.css('/static/img/shop/goods/groupon-bg.png');
const btnBg = sheep.$url.css('/static/img/shop/goods/groupon-btn.png');
const disabledBtnBg = sheep.$url.css(
'/static/img/shop/goods/activity-btn-disabled.png',
);
const disabledBtnBg = sheep.$url.css('/static/img/shop/goods/activity-btn-disabled.png');
const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png');
onPageScroll(() => {});
const state = reactive({
skeletonLoading: true, //
goodsId: 0, // ID
goodsInfo: {}, //
goodsSwiper: [], //
showSelectSku: false, //
selectedSkuPrice: {}, //
activity: {}, //
grouponId: 0, // ID
grouponNum: 0, //
grouponAction: 'create', //
skeletonLoading: true, //
goodsId: 0, // ID
goodsInfo: {}, //
goodsSwiper: [], //
showSelectSku: false, //
selectedSku: {}, //
activity: {}, //
grouponId: 0, // ID
grouponNum: 0, //
grouponAction: 'create', //
combinationHeadId: null, //
});
@ -182,7 +188,7 @@
//
function onSkuChange(e) {
state.selectedSkuPrice = e;
state.selectedSku = e;
}
function onSkuClose() {
@ -198,6 +204,7 @@
/**
* 去参团
*
* @param record 团长的团购记录
*/
function onJoinGroupon(record) {
@ -226,7 +233,6 @@
}
//
// TODO @
const shareInfo = computed(() => {
if (isEmpty(state.activity)) return {};
return sheep.$platform.share.getShareInfo(
@ -234,7 +240,7 @@
title: state.activity.name,
image: sheep.$url.cdn(state.goodsInfo.picUrl),
params: {
page: '3',
page: SharePageEnum.GROUPON.value,
query: state.activity.id,
},
},
@ -252,18 +258,43 @@
//
if (!options.id) {
state.goodsInfo = null;
state.skeletonLoading = false;
return;
}
state.grouponId = options.id;
//
const { code, data: activity } = await CombinationApi.getCombinationActivity(state.grouponId);
if (code !== 0) {
state.goodsInfo = null;
state.skeletonLoading = false;
return;
}
state.activity = activity;
//
const { data: spu } = await SpuApi.getSpuDetail(activity.spuId);
if (code !== 0) {
state.goodsInfo = null;
state.skeletonLoading = false;
return;
}
state.goodsId = spu.id;
activity.products.forEach(product => {
spu.price = Math.min(spu.price, product.combinationPrice); // SPU
//
spu.price = activity.products.reduce((min, product) => {
return Math.min(min, product.combinationPrice || Infinity);
}, Infinity);
// 使
spu.skus.forEach((sku) => {
const product = activity.products.find((product) => product.skuId === sku.id);
if (product) {
sku.price = product.combinationPrice;
} else {
//
sku.stock = 0;
}
});
//
state.skeletonLoading = false;
if (code === 0) {
@ -475,8 +506,7 @@
}
.groupon-box {
background: v-bind(grouponBg)
no-repeat;
background: v-bind(grouponBg) no-repeat;
background-size: 100% 100%;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,362 +1,407 @@
<template>
<s-layout navbar="normal" :leftWidth="0" :rightWidth="0" tools="search" :defaultSearch="state.keyword"
@search="onSearch">
<!-- 筛选 -->
<su-sticky bgColor="#fff">
<view class="ss-flex">
<view class="ss-flex-1">
<su-tabs :list="state.tabList" :scrollable="false" @change="onTabsChange"
:current="state.currentTab" />
</view>
<view class="list-icon" @tap="state.iconStatus = !state.iconStatus">
<text v-if="state.iconStatus" class="sicon-goods-list" />
<text v-else class="sicon-goods-card" />
</view>
</view>
</su-sticky>
<s-layout
navbar="normal"
:leftWidth="0"
:rightWidth="0"
tools="search"
:defaultSearch="state.keyword"
@search="onSearch"
>
<!-- 筛选 -->
<su-sticky bgColor="#fff">
<view class="ss-flex">
<view class="ss-flex-1">
<su-tabs
:list="state.tabList"
:scrollable="false"
@change="onTabsChange"
:current="state.currentTab"
/>
</view>
<view class="list-icon" @tap="state.iconStatus = !state.iconStatus">
<text v-if="state.iconStatus" class="sicon-goods-list" />
<text v-else class="sicon-goods-card" />
</view>
</view>
</su-sticky>
<!-- 弹窗 -->
<su-popup :show="state.showFilter" type="top" round="10" :space="sys_navBar + 38" backgroundColor="#F6F6F6"
:zIndex="10" @close="state.showFilter = false">
<view class="filter-list-box">
<view class="filter-item" v-for="(item, index) in state.tabList[state.currentTab].list"
:key="item.value" :class="[{ 'filter-item-active': index === state.curFilter }]"
@tap="onFilterItem(index)">
{{ item.label }}
</view>
</view>
</su-popup>
<!-- 弹窗 -->
<su-popup
:show="state.showFilter"
type="top"
round="10"
:space="sys_navBar + 38"
backgroundColor="#F6F6F6"
:zIndex="10"
@close="state.showFilter = false"
>
<view class="filter-list-box">
<view
class="filter-item"
v-for="(item, index) in state.tabList[state.currentTab].list"
:key="item.value"
:class="[{ 'filter-item-active': index === state.curFilter }]"
@tap="onFilterItem(index)"
>
{{ item.label }}
</view>
</view>
</su-popup>
<!-- 情况一单列布局 -->
<view v-if="state.iconStatus && state.pagination.total > 0" class="goods-list ss-m-t-20">
<view class="ss-p-l-20 ss-p-r-20 ss-m-b-20" v-for="item in state.pagination.list" :key="item.id">
<s-goods-column
<view v-if="state.iconStatus && state.pagination.total > 0" class="goods-list ss-m-t-20">
<view
class="ss-p-l-20 ss-p-r-20 ss-m-b-20"
v-for="item in state.pagination.list"
:key="item.id"
>
<s-goods-column
class=""
size="lg"
:data="item"
:topRadius="10"
:bottomRadius="10"
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
/>
</view>
</view>
</view>
</view>
<!-- 情况二双列布局 -->
<view v-if="!state.iconStatus && state.pagination.total > 0"
class="ss-flex ss-flex-wrap ss-p-x-20 ss-m-t-20 ss-col-top">
<view class="goods-list-box">
<view class="left-list" v-for="item in state.leftGoodsList" :key="item.id">
<s-goods-column
<view
v-if="!state.iconStatus && state.pagination.total > 0"
class="ss-flex ss-flex-wrap ss-p-x-20 ss-m-t-20 ss-col-top"
>
<view class="goods-list-box">
<view class="left-list" v-for="item in state.leftGoodsList" :key="item.id">
<s-goods-column
class="goods-md-box"
size="md"
:data="item"
:topRadius="10"
:bottomRadius="10"
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
@getHeight="mountMasonry($event, 'left')"
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
@getHeight="mountMasonry($event, 'left')"
>
<template v-slot:cart>
<button class="ss-reset-button cart-btn" />
</template>
</s-goods-column>
</view>
</view>
<view class="goods-list-box">
<view class="right-list" v-for="item in state.rightGoodsList" :key="item.id">
<s-goods-column
<template v-slot:cart>
<button class="ss-reset-button cart-btn" />
</template>
</s-goods-column>
</view>
</view>
<view class="goods-list-box">
<view class="right-list" v-for="item in state.rightGoodsList" :key="item.id">
<s-goods-column
class="goods-md-box"
size="md"
:topRadius="10"
:bottomRadius="10"
:data="item"
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
@getHeight="mountMasonry($event, 'right')"
@click="sheep.$router.go('/pages/goods/index', { id: item.id })"
@getHeight="mountMasonry($event, 'right')"
>
<template v-slot:cart>
<button class="ss-reset-button cart-btn" />
</template>
</s-goods-column>
</view>
</view>
</view>
<uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{
<template v-slot:cart>
<button class="ss-reset-button cart-btn" />
</template>
</s-goods-column>
</view>
</view>
</view>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}" @tap="loadMore" />
<s-empty v-if="state.pagination.total === 0" icon="/static/soldout-empty.png" text="暂无商品" />
</s-layout>
}"
@tap="loadMore"
/>
<s-empty v-if="state.pagination.total === 0" icon="/static/soldout-empty.png" text="暂无商品" />
</s-layout>
</template>
<script setup>
import { reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import _ from 'lodash';
import { resetPagination } from '@/sheep/util';
import { reactive, ref } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import _ from 'lodash-es';
import { resetPagination } from '@/sheep/helper/utils';
import SpuApi from '@/sheep/api/product/spu';
import OrderApi from '@/sheep/api/trade/order';
import { appendSettlementProduct } from '@/sheep/hooks/useGoods';
const sys_navBar = sheep.$platform.navbar;
const emits = defineEmits(['close', 'change']);
const sys_navBar = sheep.$platform.navbar;
const emits = defineEmits(['close', 'change']);
const state = reactive({
pagination: {
list: [],
const state = reactive({
pagination: {
list: [],
total: 0,
pageNo: 1,
pageSize: 6,
},
currentSort: undefined,
currentOrder: undefined,
currentTab: 0, // tab
curFilter: 0, // list
showFilter: false,
iconStatus: false, // true - false -
pageSize: 6,
},
currentSort: undefined,
currentOrder: undefined,
currentTab: 0, // tab
curFilter: 0, // list
showFilter: false,
iconStatus: false, // true - false -
keyword: '',
categoryId: 0,
tabList: [{
name: '综合推荐',
list: [{
label: '综合推荐'
},
{
label: '价格升序',
sort: 'price',
order: true,
},
{
label: '价格降序',
sort: 'price',
order: false,
},
],
},
{
name: '销量',
sort: 'salesCount',
order: false
},
{
name: '新品优先',
value: 'createTime',
order: false
},
],
loadStatus: '',
leftGoodsList: [], // -
rightGoodsList: [], // -
});
tabList: [
{
name: '综合推荐',
list: [
{
label: '综合推荐',
},
{
label: '价格升序',
sort: 'price',
order: true,
},
{
label: '价格降序',
sort: 'price',
order: false,
},
],
},
{
name: '销量',
sort: 'salesCount',
order: false,
},
{
name: '新品优先',
value: 'createTime',
order: false,
},
],
loadStatus: '',
leftGoodsList: [], // -
rightGoodsList: [], // -
});
//
let count = 0;
let leftHeight = 0;
let rightHeight = 0;
//
let count = 0;
let leftHeight = 0;
let rightHeight = 0;
// leftGoodsList + rightGoodsList
function mountMasonry(height = 0, where = 'left') {
if (!state.pagination.list[count]) {
function mountMasonry(height = 0, where = 'left') {
if (where === 'left') {
leftHeight += height;
} else {
rightHeight += height;
}
if (!state.pagination.list[count]) {
return;
}
if (where === 'left') {
leftHeight += height;
} else {
rightHeight += height;
}
if (leftHeight <= rightHeight) {
state.leftGoodsList.push(state.pagination.list[count]);
} else {
state.rightGoodsList.push(state.pagination.list[count]);
}
count++;
}
if (leftHeight <= rightHeight) {
state.leftGoodsList.push(state.pagination.list[count]);
} else {
state.rightGoodsList.push(state.pagination.list[count]);
}
count++;
}
//
function emptyList() {
function emptyList() {
resetPagination(state.pagination);
state.leftGoodsList = [];
state.rightGoodsList = [];
count = 0;
leftHeight = 0;
rightHeight = 0;
}
state.rightGoodsList = [];
count = 0;
leftHeight = 0;
rightHeight = 0;
}
//
function onSearch(e) {
state.keyword = e;
emptyList();
getList(state.currentSort, state.currentOrder);
}
//
function onSearch(e) {
state.keyword = e;
emptyList();
getList(state.currentSort, state.currentOrder);
}
//
function onTabsChange(e) {
//
function onTabsChange(e) {
//
if (state.tabList[e.index].list) {
state.currentTab = e.index;
state.showFilter = !state.showFilter;
return;
}
if (state.tabList[e.index].list) {
state.currentTab = e.index;
state.showFilter = !state.showFilter;
return;
}
state.showFilter = false;
// tab
if (e.index === state.currentTab) {
return;
}
if (e.index === state.currentTab) {
return;
}
state.currentTab = e.index;
state.currentSort = e.sort;
state.currentOrder = e.order;
emptyList();
getList(e.sort, e.order);
}
emptyList();
getList(e.sort, e.order);
}
// tab list
const onFilterItem = (val) => {
// tab list
const onFilterItem = (val) => {
//
// tabList[0] list
if (state.currentSort === state.tabList[0].list[val].sort
&& state.currentOrder === state.tabList[0].list[val].order) {
state.showFilter = false;
return;
}
if (
state.currentSort === state.tabList[0].list[val].sort &&
state.currentOrder === state.tabList[0].list[val].order
) {
state.showFilter = false;
return;
}
state.showFilter = false;
//
state.curFilter = val;
state.tabList[0].name = state.tabList[0].list[val].label;
state.currentSort = state.tabList[0].list[val].sort;
state.currentOrder = state.tabList[0].list[val].order;
// +
state.curFilter = val;
state.tabList[0].name = state.tabList[0].list[val].label;
state.currentSort = state.tabList[0].list[val].sort;
state.currentOrder = state.tabList[0].list[val].order;
// +
emptyList();
getList();
}
getList();
};
async function getList() {
state.loadStatus = 'loading';
const { code, data } = await SpuApi.getSpuPage({
async function getList() {
state.loadStatus = 'loading';
const { code, data } = await SpuApi.getSpuPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
sortField: state.currentSort,
sortAsc: state.currentOrder,
categoryId: state.categoryId,
keyword: state.keyword,
});
sortField: state.currentSort,
sortAsc: state.currentOrder,
categoryId: state.categoryId,
keyword: state.keyword,
});
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list)
//
await OrderApi.getSettlementProduct(data.list.map((item) => item.id).join(',')).then((res) => {
if (res.code !== 0) {
return;
}
appendSettlementProduct(data.list, res.data);
});
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
mountMasonry();
}
}
//
function loadMore() {
//
function loadMore() {
if (state.loadStatus === 'noMore') {
return;
}
state.pagination.pageNo++;
getList(state.currentSort, state.currentOrder);
}
}
onLoad((options) => {
state.categoryId = options.categoryId;
state.keyword = options.keyword;
getList(state.currentSort, state.currentOrder);
});
onLoad((options) => {
state.categoryId = options.categoryId;
state.keyword = options.keyword;
getList(state.currentSort, state.currentOrder);
});
//
onReachBottom(() => {
loadMore();
});
//
onReachBottom(() => {
loadMore();
});
</script>
<style lang="scss" scoped>
.goods-list-box {
width: 50%;
box-sizing: border-box;
.goods-list-box {
width: 50%;
box-sizing: border-box;
.left-list {
margin-right: 10rpx;
margin-bottom: 20rpx;
}
.left-list {
margin-right: 10rpx;
margin-bottom: 20rpx;
}
.right-list {
margin-left: 10rpx;
margin-bottom: 20rpx;
}
}
.right-list {
margin-left: 10rpx;
margin-bottom: 20rpx;
}
}
.goods-box {
&:nth-last-of-type(1) {
margin-bottom: 0 !important;
}
.goods-box {
&:nth-last-of-type(1) {
margin-bottom: 0 !important;
}
&:nth-child(2n) {
margin-right: 0;
}
}
&:nth-child(2n) {
margin-right: 0;
}
}
.list-icon {
width: 80rpx;
.list-icon {
width: 80rpx;
.sicon-goods-card {
font-size: 40rpx;
}
.sicon-goods-card {
font-size: 40rpx;
}
.sicon-goods-list {
font-size: 40rpx;
}
}
.sicon-goods-list {
font-size: 40rpx;
}
}
.goods-card {
margin-left: 20rpx;
}
.goods-card {
margin-left: 20rpx;
}
.list-filter-tabs {
background-color: #fff;
}
.list-filter-tabs {
background-color: #fff;
}
.filter-list-box {
padding: 28rpx 52rpx;
.filter-list-box {
padding: 28rpx 52rpx;
.filter-item {
font-size: 28rpx;
font-weight: 500;
color: #333333;
line-height: normal;
margin-bottom: 24rpx;
.filter-item {
font-size: 28rpx;
font-weight: 500;
color: #333333;
line-height: normal;
margin-bottom: 24rpx;
&:nth-last-child(1) {
margin-bottom: 0;
}
}
&:nth-last-child(1) {
margin-bottom: 0;
}
}
.filter-item-active {
color: var(--ui-BG-Main);
}
}
.filter-item-active {
color: var(--ui-BG-Main);
}
}
.tab-item {
height: 50px;
position: relative;
z-index: 11;
.tab-item {
height: 50px;
position: relative;
z-index: 11;
.tab-title {
font-size: 30rpx;
}
.tab-title {
font-size: 30rpx;
}
.cur-tab-title {
font-weight: $font-weight-bold;
}
.cur-tab-title {
font-weight: $font-weight-bold;
}
.tab-line {
width: 60rpx;
height: 6rpx;
border-radius: 6rpx;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 10rpx;
background-color: var(--ui-BG-Main);
z-index: 12;
}
}
</style>
.tab-line {
width: 60rpx;
height: 6rpx;
border-radius: 6rpx;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 10rpx;
background-color: var(--ui-BG-Main);
z-index: 12;
}
}
</style>

483
pages/goods/point.vue Normal file
View File

@ -0,0 +1,483 @@
<!-- 秒杀商品详情 -->
<template>
<s-layout :onShareAppMessage="shareInfo" navbar="goods">
<!-- 标题栏 -->
<detailNavbar />
<!-- 骨架屏 -->
<detailSkeleton v-if="state.skeletonLoading" />
<!-- 下架/售罄提醒 -->
<s-empty
v-else-if="
state.goodsInfo === null ||
state.goodsInfo.activity_type !== PromotionActivityTypeEnum.POINT.type
"
text="活动不存在或已结束"
icon="/static/soldout-empty.png"
showAction
actionText="再逛逛"
actionUrl="/pages/goods/list"
/>
<block v-else>
<view class="detail-swiper-selector">
<!-- 商品图轮播 -->
<su-swiper
class="ss-m-b-14"
isPreview
:list="state.goodsSwiper"
dotStyle="tag"
imageMode="widthFix"
dotCur="bg-mask-40"
:seizeHeight="750"
/>
<!-- 价格+标题 -->
<view class="title-card detail-card ss-p-y-40 ss-p-x-20">
<view class="ss-flex ss-row-between ss-col-center ss-m-b-18">
<view class="price-box ss-flex ss-col-bottom">
<image
:src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
class="point-img"
></image>
<text class="point-text ss-m-r-16">
{{ getShowPrice.point }}
{{
!getShowPrice.price || getShowPrice.price === 0 ? '' : `+¥${getShowPrice.price}`
}}
</text>
</view>
<view class="sales-text">
{{ formatExchange(state.goodsInfo.sales_show_type, state.goodsInfo.sales) }}
</view>
</view>
<view class="origin-price-text ss-m-b-60" v-if="state.goodsInfo.marketPrice">
原价{{ fen2yuan(state.selectedSku.marketPrice || state.goodsInfo.marketPrice) }}
</view>
<view class="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo.name || '' }}</view>
<view class="subtitle-text ss-line-1">{{ state.goodsInfo.introduction }}</view>
</view>
<!-- 功能卡片 -->
<view class="detail-cell-card detail-card ss-flex-col">
<detail-cell-sku :sku="state.selectedSku" @tap="state.showSelectSku = true" />
</view>
<!-- 规格与数量弹框 -->
<s-select-seckill-sku
v-model="state.goodsInfo"
:show="state.showSelectSku"
:single-limit-count="activity.singleLimitCount"
@buy="onBuy"
@change="onSkuChange"
@close="state.showSelectSku = false"
/>
</view>
<!-- 评价 -->
<detail-comment-card class="detail-comment-selector" :goodsId="state.goodsInfo.id" />
<!-- 详情 -->
<detail-content-card class="detail-content-selector" :content="state.goodsInfo.description" />
<!-- 详情tabbar -->
<detail-tabbar v-model="state.goodsInfo">
<view class="buy-box ss-flex ss-col-center ss-p-r-20">
<button
class="ss-reset-button origin-price-btn ss-flex-col"
v-if="state.goodsInfo.marketPrice"
@tap="sheep.$router.go('/pages/goods/index', { id: state.goodsInfo.id })"
>
<view>
<view class="btn-price">{{ fen2yuan(state.goodsInfo.marketPrice) }}</view>
<view>原价购买</view>
</view>
</button>
<button
class="ss-reset-button btn-box ss-flex-col"
@tap="state.showSelectSku = true"
:class="state.goodsInfo.stock != 0 ? 'check-btn-box' : 'disabled-btn-box'"
:disabled="state.goodsInfo.stock === 0"
>
<view class="price-box ss-flex">
<image
:src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
style="width: 36rpx; height: 36rpx; margin: 0 4rpx"
></image>
<text class="point-text ss-m-r-16">
{{ getShowPrice.point }}
{{
!getShowPrice.price || getShowPrice.price === 0 ? '' : `+¥${getShowPrice.price}`
}}
</text>
</view>
<view v-if="state.goodsInfo.stock === 0"></view>
<view v-else></view>
</button>
</view>
</detail-tabbar>
</block>
</s-layout>
</template>
<script setup>
import { computed, reactive, ref, unref } from 'vue';
import { onLoad, onPageScroll } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import { isEmpty } from 'lodash-es';
import { fen2yuan, formatExchange, formatGoodsSwiper } from '@/sheep/hooks/useGoods';
import detailNavbar from './components/detail/detail-navbar.vue';
import detailCellSku from './components/detail/detail-cell-sku.vue';
import detailTabbar from './components/detail/detail-tabbar.vue';
import detailSkeleton from './components/detail/detail-skeleton.vue';
import detailCommentCard from './components/detail/detail-comment-card.vue';
import detailContentCard from './components/detail/detail-content-card.vue';
import SpuApi from '@/sheep/api/product/spu';
import { PromotionActivityTypeEnum, SharePageEnum } from '@/sheep/helper/const';
import PointApi from '@/sheep/api/promotion/point';
const headerBg = sheep.$url.css('/static/img/shop/goods/score-bg.png');
const btnBg = sheep.$url.css('/static/img/shop/goods/seckill-btn.png');
const disabledBtnBg = sheep.$url.css('/static/img/shop/goods/activity-btn-disabled.png');
const seckillBg = sheep.$url.css('/static/img/shop/goods/seckill-tip-bg.png');
const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png');
onPageScroll(() => {});
const state = reactive({
skeletonLoading: true,
goodsInfo: {},
showSelectSku: false,
goodsSwiper: [],
selectedSku: {},
showModel: false,
total: 0,
price: '',
});
//
function onSkuChange(e) {
state.selectedSku = e;
}
//
function onBuy(sku) {
sheep.$router.go('/pages/order/confirm', {
data: JSON.stringify({
order_type: 'goods',
buy_type: 'point',
pointActivityId: activity.value.id,
items: [
{
skuId: sku.id,
count: sku.count,
},
],
}),
});
}
//
const shareInfo = computed(() => {
if (isEmpty(unref(activity))) return {};
return sheep.$platform.share.getShareInfo(
{
title: activity.value.name,
image: sheep.$url.cdn(state.goodsInfo.picUrl),
params: {
page: SharePageEnum.POINT.value,
query: activity.value.id,
},
},
{
type: 'goods', //
title: activity.value.name, //
image: sheep.$url.cdn(state.goodsInfo.picUrl), //
price: (getShowPrice.value.price || 0) + ` + ${getShowPrice.value.point} 积分`, //
marketPrice: fen2yuan(state.goodsInfo.marketPrice), //
},
);
});
const activity = ref();
const getShowPrice = computed(() => {
if (!isEmpty(state.selectedSku)) {
const sku = state.selectedSku;
return {
point: sku.point,
price: !sku.pointPrice ? '' : fen2yuan(sku.pointPrice),
};
}
return {
point: activity.value.point,
price: !activity.value.price ? '' : fen2yuan(activity.value.price),
};
});
//
const getActivity = async (id) => {
const { data } = await PointApi.getPointActivity(id);
if (!data) {
state.goodsInfo = null;
state.skeletonLoading = false;
return;
}
activity.value = data;
//
await getSpu(data.spuId);
};
//
const getSpu = async (id) => {
const { data } = await SpuApi.getSpuDetail(id);
if (!data) {
state.goodsInfo = null;
state.skeletonLoading = false;
return;
}
data.activity_type = PromotionActivityTypeEnum.POINT.type;
state.goodsInfo = data;
state.goodsInfo.stock = Math.min(data.stock, activity.value.stock);
//
state.goodsSwiper = formatGoodsSwiper(state.goodsInfo.sliderPicUrls);
// 使
data.skus.forEach((sku) => {
const product = activity.value.products.find((product) => product.skuId === sku.id);
if (product) {
sku.point = product.point;
sku.pointPrice = product.price;
sku.stock = Math.min(sku.stock, product.stock);
//
sku.limitCount = product.count;
} else {
//
sku.stock = 0;
}
});
state.skeletonLoading = false;
};
onLoad((options) => {
//
if (!options.id) {
state.goodsInfo = null;
state.skeletonLoading = false;
return;
}
//
getActivity(options.id);
});
</script>
<style lang="scss" scoped>
.disabled-btn-box[disabled] {
background-color: transparent;
}
.detail-card {
background-color: $white;
margin: 14rpx 20rpx;
border-radius: 10rpx;
overflow: hidden;
}
//
.title-card {
width: 710rpx;
box-sizing: border-box;
background-size: 100% 100%;
border-radius: 10rpx;
background-image: v-bind(headerBg);
background-repeat: no-repeat;
.price-box {
.point-img {
width: 36rpx;
height: 36rpx;
margin: 0 4rpx;
}
.point-text {
font-size: 42rpx;
font-weight: 500;
color: #ff3000;
line-height: 36rpx;
font-family: OPPOSANS;
}
.price-text {
font-size: 42rpx;
font-weight: 500;
color: #ff3000;
line-height: 36rpx;
font-family: OPPOSANS;
}
}
.origin-price-text {
font-size: 26rpx;
font-weight: 400;
text-decoration: line-through;
color: $gray-c;
font-family: OPPOSANS;
}
.sales-text {
font-size: 26rpx;
font-weight: 500;
color: $gray-c;
}
.discounts-box {
.discounts-tag {
padding: 4rpx 10rpx;
font-size: 24rpx;
font-weight: 500;
border-radius: 4rpx;
color: var(--ui-BG-Main);
// background: rgba(#2aae67, 0.05);
background: var(--ui-BG-Main-tag);
}
.discounts-title {
font-size: 24rpx;
font-weight: 500;
color: var(--ui-BG-Main);
line-height: normal;
}
.cicon-forward {
color: var(--ui-BG-Main);
font-size: 24rpx;
line-height: normal;
margin-top: 4rpx;
}
}
.title-text {
font-size: 30rpx;
font-weight: bold;
line-height: 42rpx;
}
.subtitle-text {
font-size: 26rpx;
font-weight: 400;
color: $dark-9;
line-height: 42rpx;
}
}
//
.buy-box {
.check-btn-box {
width: 248rpx;
height: 80rpx;
font-size: 24rpx;
font-weight: 600;
margin-left: -36rpx;
background-image: v-bind(btnBg);
background-repeat: no-repeat;
background-size: 100% 100%;
color: #ffffff;
line-height: normal;
border-radius: 0px 40rpx 40rpx 0px;
}
.disabled-btn-box {
width: 248rpx;
height: 80rpx;
font-size: 24rpx;
font-weight: 600;
margin-left: -36rpx;
background-image: v-bind(disabledBtnBg);
background-repeat: no-repeat;
background-size: 100% 100%;
color: #999999;
line-height: normal;
border-radius: 0px 40rpx 40rpx 0px;
}
.btn-price {
font-family: OPPOSANS;
&::before {
content: '¥';
}
}
.origin-price-btn {
width: 236rpx;
height: 80rpx;
background: rgba(#ff5651, 0.1);
color: #ff6000;
border-radius: 40rpx 0px 0px 40rpx;
line-height: normal;
font-size: 24rpx;
font-weight: 500;
.no-original {
font-size: 28rpx;
}
.btn-title {
font-size: 28rpx;
}
}
}
//
.seckill-box {
background: v-bind(seckillBg) no-repeat;
background-size: 100% 100%;
}
.groupon-box {
background: v-bind(grouponBg) no-repeat;
background-size: 100% 100%;
}
//
.activity-box {
width: 100%;
height: 80rpx;
box-sizing: border-box;
margin-bottom: 10rpx;
.activity-title {
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
line-height: 42rpx;
.activity-icon {
width: 38rpx;
height: 38rpx;
}
}
.activity-go {
width: 70rpx;
height: 32rpx;
background: #ffffff;
border-radius: 16rpx;
font-weight: 500;
color: #ff6000;
font-size: 24rpx;
line-height: normal;
}
}
.model-box {
.title {
font-size: 36rpx;
font-weight: bold;
color: #333333;
}
.subtitle {
font-size: 26rpx;
font-weight: 500;
color: #333333;
}
}
</style>

View File

@ -7,7 +7,9 @@
<detailSkeleton v-if="state.skeletonLoading" />
<!-- 下架/售罄提醒 -->
<s-empty
v-else-if="state.goodsInfo === null || state.goodsInfo.activity_type !== 'seckill'"
v-else-if="
state.goodsInfo === null || state.goodsInfo.activity_type !== 'seckill' || endTime.ms <= 0
"
text="活动不存在或已结束"
icon="/static/soldout-empty.png"
showAction
@ -63,16 +65,13 @@
<detail-progress :percent="state.percent" />
</view>
<view class="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo?.name }}</view>
<view class="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo.name || '' }}</view>
<view class="subtitle-text ss-line-1">{{ state.goodsInfo.introduction }}</view>
</view>
<!-- 功能卡片 -->
<view class="detail-cell-card detail-card ss-flex-col">
<detail-cell-sku
:sku="state.selectedSku"
@tap="state.showSelectSku = true"
/>
<detail-cell-sku :sku="state.selectedSku" @tap="state.showSelectSku = true" />
</view>
<!-- 规格与数量弹框 -->
<s-select-seckill-sku
@ -107,7 +106,9 @@
<button v-else class="ss-reset-button origin-price-btn ss-flex-col">
<view
class="no-original"
:class="state.goodsInfo.stock === 0 || timeStatusEnum !== TimeStatusEnum.STARTED ? '' : ''"
:class="
state.goodsInfo.stock === 0 || timeStatusEnum !== TimeStatusEnum.STARTED ? '' : ''
"
>
秒杀价
</view>
@ -136,11 +137,11 @@
</template>
<script setup>
import {reactive, computed, ref} from 'vue';
import { computed, reactive, ref, unref } from 'vue';
import { onLoad, onPageScroll } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import {isEmpty, min} from 'lodash';
import {useDurationTime, formatGoodsSwiper, fen2yuan} from '@/sheep/hooks/useGoods';
import { isEmpty, min } from 'lodash-es';
import { fen2yuan, formatGoodsSwiper, useDurationTime } from '@/sheep/hooks/useGoods';
import detailNavbar from './components/detail/detail-navbar.vue';
import detailCellSku from './components/detail/detail-cell-sku.vue';
import detailTabbar from './components/detail/detail-tabbar.vue';
@ -148,15 +149,13 @@
import detailCommentCard from './components/detail/detail-comment-card.vue';
import detailContentCard from './components/detail/detail-content-card.vue';
import detailProgress from './components/detail/detail-progress.vue';
import SeckillApi from "@/sheep/api/promotion/seckill";
import SpuApi from "@/sheep/api/product/spu";
import {getTimeStatusEnum, TimeStatusEnum} from "@/sheep/util/const";
import SeckillApi from '@/sheep/api/promotion/seckill';
import SpuApi from '@/sheep/api/product/spu';
import { getTimeStatusEnum, SharePageEnum, TimeStatusEnum } from '@/sheep/helper/const';
const headerBg = sheep.$url.css('/static/img/shop/goods/seckill-bg.png');
const btnBg = sheep.$url.css('/static/img/shop/goods/seckill-btn.png');
const disabledBtnBg = sheep.$url.css(
'/static/img/shop/goods/activity-btn-disabled.png',
);
const disabledBtnBg = sheep.$url.css('/static/img/shop/goods/activity-btn-disabled.png');
const seckillBg = sheep.$url.css('/static/img/shop/goods/seckill-tip-bg.png');
const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png');
@ -199,15 +198,15 @@
});
}
// TODO
//
const shareInfo = computed(() => {
if (isEmpty(activity)) return {};
if (isEmpty(unref(activity))) return {};
return sheep.$platform.share.getShareInfo(
{
title: activity.value.name,
image: sheep.$url.cdn(state.goodsInfo.picUrl),
params: {
page: '4',
page: SharePageEnum.SECKILL.value,
query: activity.value.id,
},
},
@ -215,42 +214,57 @@
type: 'goods', //
title: activity.value.name, //
image: sheep.$url.cdn(state.goodsInfo.picUrl), //
price: state.goodsInfo.price, //
marketPrice: state.goodsInfo.marketPrice, //
price: fen2yuan(state.goodsInfo.price), //
marketPrice: fen2yuan(state.goodsInfo.marketPrice), //
},
);
});
const activity = ref()
const timeStatusEnum = ref('')
const activity = ref();
const timeStatusEnum = ref('');
//
const getActivity = async (id) => {
const { data } = await SeckillApi.getSeckillActivity(id)
activity.value = data
timeStatusEnum.value = getTimeStatusEnum(activity.startTime, activity.endTime)
const { data } = await SeckillApi.getSeckillActivity(id);
if (!data) {
state.goodsInfo = null;
state.skeletonLoading = false;
return;
}
activity.value = data;
timeStatusEnum.value = getTimeStatusEnum(activity.value.startTime, activity.value.endTime);
state.percent = 100 - (data.stock / data.totalStock) * 100;
//
await getSpu(data.spuId)
}
await getSpu(data.spuId);
};
//
const getSpu = async (id) => {
const { data } = await SpuApi.getSpuDetail(id)
//
data.activity_type = 'seckill'
state.goodsInfo = data
const { data } = await SpuApi.getSpuDetail(id);
if (!data) {
state.goodsInfo = null;
state.skeletonLoading = false;
return;
}
data.activity_type = 'seckill';
state.goodsInfo = data;
//
state.goodsSwiper = formatGoodsSwiper(state.goodsInfo.sliderPicUrls);
//
state.goodsInfo.price = min([state.goodsInfo.price, ...activity.value.products.map(spu => spu.seckillPrice)])
state.goodsInfo.price = min([
state.goodsInfo.price,
...activity.value.products.map((spu) => spu.seckillPrice),
]);
// 使
data.skus.forEach(sku => {
const product = activity.value.products.find(product => product.skuId === sku.id);
data.skus.forEach((sku) => {
const product = activity.value.products.find((product) => product.skuId === sku.id);
if (product) {
sku.price = product.seckillPrice;
sku.stock = Math.min(sku.stock, product.stock);
} else { //
} else {
//
sku.stock = 0;
}
//
@ -264,17 +278,18 @@
});
state.skeletonLoading = false;
}
};
onLoad((options) => {
//
if (!options.id) {
state.goodsInfo = null;
state.skeletonLoading = false;
return;
}
//
getActivity(options.id)
getActivity(options.id);
});
</script>
@ -282,6 +297,7 @@
.disabled-btn-box[disabled] {
background-color: transparent;
}
.detail-card {
background-color: $white;
margin: 14rpx 20rpx;
@ -372,6 +388,7 @@
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
.countdown-h {
font-size: 24rpx;
font-family: OPPOSANS;
@ -382,6 +399,7 @@
background: rgba(#000000, 0.1);
border-radius: 6rpx;
}
.countdown-num {
font-size: 24rpx;
font-family: OPPOSANS;
@ -465,6 +483,7 @@
line-height: normal;
border-radius: 0px 40rpx 40rpx 0px;
}
.btn-price {
font-family: OPPOSANS;
@ -482,6 +501,7 @@
line-height: normal;
font-size: 24rpx;
font-weight: 500;
.no-original {
font-size: 28rpx;
}

View File

@ -1,200 +1,315 @@
<template>
<s-layout title="购物车" tabbar="/pages/index/cart" :bgStyle="{ color: '#fff' }">
<s-empty v-if="state.list.length === 0" text="购物车空空如也,快去逛逛吧~" icon="/static/cart-empty.png" />
<s-layout :bgStyle="{ color: '#fff' }" tabbar="/pages/index/cart" title="购物车">
<s-empty
v-if="state.list.length === 0"
icon="/static/cart-empty.png"
text="购物车空空如也,快去逛逛吧~"
/>
<!-- 头部 -->
<view class="cart-box ss-flex ss-flex-col ss-row-between" v-if="state.list.length">
<view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30">
<view class="header-left ss-flex ss-col-center ss-font-26">
<text class="goods-number ui-TC-Main ss-flex">{{ state.list.length }}</text>
件商品
</view>
<view class="header-right">
<button v-if="state.editMode" class="ss-reset-button" @tap="state.editMode = false">
取消
</button>
<button v-else class="ss-reset-button ui-TC-Main" @tap="state.editMode = true">
编辑
</button>
</view>
</view>
<!-- 内容 -->
<view class="cart-content ss-flex-1 ss-p-x-30 ss-m-b-40">
<view class="goods-box ss-r-10 ss-m-b-14" v-for="item in state.list" :key="item.id">
<view class="ss-flex ss-col-center">
<label class="check-box ss-flex ss-col-center ss-p-l-10" @tap="onSelectSingle(item.id)">
<radio :checked="state.selectedIds.includes(item.id)" color="var(--ui-BG-Main)"
style="transform: scale(0.8)" @tap.stop="onSelectSingle(item.id)" />
</label>
<s-goods-item :title="item.spu.name" :img="item.spu.picUrl || item.goods.image"
:price="item.sku.price/100"
:skuText="item.sku.properties.length>1? item.sku.properties.reduce((items2,items)=>items2.valueName+' '+items.valueName):item.sku.properties[0].valueName"
priceColor="#FF3000" :titleWidth="400">
<template v-if="!state.editMode" v-slot:tool>
<su-number-box :min="0" :max="item.sku.stock" :step="1" v-model="item.count"
@change="onNumberChange($event, item)"></su-number-box>
</template>
</s-goods-item>
</view>
</view>
</view>
<!-- 底部 -->
<su-fixed bottom :val="48" placeholder v-if="state.list.length > 0" :isInset="false">
<view class="cart-footer ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom">
<view class="footer-left ss-flex ss-col-center">
<label class="check-box ss-flex ss-col-center ss-p-r-30" @tap="onSelectAll">
<radio :checked="state.isAllSelected" color="var(--ui-BG-Main)"
style="transform: scale(0.8)" @tap.stop="onSelectAll" />
<view class="ss-m-l-8"> 全选 </view>
</label>
<text>合计</text>
<view class="text-price price-text">
{{ state.totalPriceSelected }}
</view>
</view>
<view class="footer-right">
<button v-if="state.editMode" class="ss-reset-button ui-BG-Main-Gradient pay-btn ui-Shadow-Main"
@tap="onDelete">
删除
</button>
<button v-else class="ss-reset-button ui-BG-Main-Gradient pay-btn ui-Shadow-Main"
@tap="onConfirm">
去结算
{{ state.selectedIds?.length ? `(${state.selectedIds.length})` : '' }}
</button>
</view>
</view>
</su-fixed>
</view>
</s-layout>
<!-- 头部 -->
<view v-if="state.list.length" class="cart-box ss-flex ss-flex-col ss-row-between">
<view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30">
<view class="header-left ss-flex ss-col-center ss-font-26">
<text class="goods-number ui-TC-Main ss-flex">{{ state.list.length }}</text>
件商品
</view>
<view class="header-right">
<button v-if="state.editMode" class="ss-reset-button" @tap="onChangeEditMode(false)">
取消
</button>
<button v-else class="ss-reset-button ui-TC-Main" @tap="onChangeEditMode(true)">
编辑
</button>
</view>
</view>
<!-- 内容 -->
<view class="cart-content ss-flex-1 ss-p-x-30 ss-m-b-40">
<view v-for="item in state.list" :key="item.id" class="goods-box ss-r-10 ss-m-b-14">
<view class="ss-flex ss-col-center">
<label class="check-box ss-flex ss-col-center ss-p-l-10" @tap="onSelectSingle(item.id)">
<radio
:checked="state.selectedIds.includes(item.id)"
color="var(--ui-BG-Main)"
style="transform: scale(0.8)"
@tap.stop="onSelectSingle(item.id)"
/>
</label>
<view v-if="item.spu?.status !== 1 && !state.editMode" class="down-box">
该商品已下架
</view>
<view v-else-if="item.spu?.stock <= 0 && !state.editMode" class="down-box">
该商品无库存
</view>
<s-goods-item
:img="item.spu.picUrl || item.goods.image"
:price="item.sku.price"
:skuText="
item.sku.properties.length > 1
? item.sku.properties.reduce(
(items2, items) => items2.valueName + ' ' + items.valueName,
)
: item.sku.properties[0].valueName
"
:title="item.spu.name"
:titleWidth="400"
priceColor="#FF3000"
>
<template v-if="!state.editMode" v-slot:tool>
<su-number-box
v-model="item.count"
:max="item.sku.stock"
:min="0"
:step="1"
@change="onNumberChange($event, item)"
/>
</template>
</s-goods-item>
</view>
</view>
</view>
<!-- 底部 -->
<su-fixed v-if="state.list.length > 0" :isInset="false" :val="48" bottom placeholder>
<view class="cart-footer ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom">
<view class="footer-left ss-flex ss-col-center">
<label class="check-box ss-flex ss-col-center ss-p-r-30" @tap="onSelectAll">
<radio
:checked="state.isAllSelected"
color="var(--ui-BG-Main)"
style="transform: scale(0.8)"
@tap.stop="onSelectAll"
/>
<view class="ss-m-l-8"> 全选</view>
</label>
<text>合计</text>
<view class="text-price price-text">
{{ fen2yuan(state.totalPriceSelected) }}
</view>
</view>
<view class="footer-right">
<button
v-if="state.editMode"
class="ss-reset-button ui-BG-Main-Gradient pay-btn ui-Shadow-Main"
@tap="onDelete"
>
删除
</button>
<button
v-else
class="ss-reset-button ui-BG-Main-Gradient pay-btn ui-Shadow-Main"
@tap="onConfirm"
>
去结算
{{ state.selectedIds?.length ? `(${state.selectedIds.length})` : '' }}
</button>
</view>
</view>
</su-fixed>
</view>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import {
computed,
reactive,
unref
} from 'vue';
import sheep from '@/sheep';
import { onShow } from '@dcloudio/uni-app';
import SpuApi from '@/sheep/api/product/spu';
import { computed, reactive } from 'vue';
import { fen2yuan } from '@/sheep/hooks/useGoods';
import { isEmpty } from '@/sheep/helper/utils';
const sys_navBar = sheep.$platform.navbar;
const cart = sheep.$store('cart');
// tabBar
uni.hideTabBar({
fail: () => {},
});
const state = reactive({
editMode: false,
list: computed(() => cart.list),
selectedList: [],
selectedIds: computed(() => cart.selectedIds),
isAllSelected: computed(() => cart.isAllSelected),
totalPriceSelected: computed(() => cart.totalPriceSelected),
});
//
function onSelectSingle(id) {
console.log('单选')
cart.selectSingle(id);
}
//
function onSelectAll() {
cart.selectAll(!state.isAllSelected);
}
const sys_navBar = sheep.$platform.navbar;
const cart = sheep.$store('cart');
//
function onConfirm() {
let items = []
let goods_list = [];
state.selectedList = state.list.filter((item) => state.selectedIds.includes(item.id));
state.selectedList.map((item) => {
console.log(item, '便利');
//
items.push({
skuId: item.sku.id,
count: item.count,
cartId: item.id,
})
goods_list.push({
// goods_id: item.goods_id,
goods_id: item.spu.id,
// goods_num: item.goods_num,
goods_num: item.count,
// id
// goods_sku_price_id: item.goods_sku_price_id,
});
});
// return;
if (goods_list.length === 0) {
sheep.$helper.toast('请选择商品');
return;
}
sheep.$router.go('/pages/order/confirm', {
data: JSON.stringify({
// order_type: 'goods',
// goods_list,
items,
// from: 'cart',
deliveryType: 1,
pointStatus: false,
}),
});
}
const state = reactive({
editMode: computed(() => cart.editMode),
list: computed(() => cart.list),
selectedList: [],
selectedIds: computed(() => cart.selectedIds),
isAllSelected: computed(() => cart.isAllSelected),
totalPriceSelected: computed(() => cart.totalPriceSelected),
});
function onNumberChange(e, cartItem) {
if (e === 0) {
cart.delete(cartItem.id);
return;
}
if (cartItem.goods_num === e) return;
cartItem.goods_num = e;
cart.update({
goods_id: cartItem.id,
goods_num: e,
goods_sku_price_id: cartItem.goods_sku_price_id,
});
}
async function onDelete() {
cart.delete(state.selectedIds);
}
//
function onSelectSingle(id) {
cart.selectSingle(id);
}
//
function onChangeEditMode(flag) {
cart.onChangeEditMode(flag);
}
//
function onSelectAll() {
cart.selectAll(!state.isAllSelected);
}
//
async function onConfirm() {
const items = [];
state.selectedList = state.list.filter((item) => state.selectedIds.includes(item.id));
state.selectedList.map((item) => {
//
items.push({
skuId: item.sku.id,
count: item.count,
cartId: item.id,
categoryId: item.spu.categoryId,
});
});
if (isEmpty(items)) {
sheep.$helper.toast('请先选择商品');
return;
}
await validateDeliveryType(state.selectedList.map((item) => item.spu).map((spu) => spu.id));
sheep.$router.go('/pages/order/confirm', {
data: JSON.stringify({
items,
}),
});
}
/**
* 校验配送方式冲突
*
* @param {string[]} spuIds - 商品ID数组
* @returns {Promise<void>}
* @throws {Error} 当配送方式冲突或获取商品信息失败时抛出错误
*/
async function validateDeliveryType(spuIds) {
//
const { data: spuList } = await SpuApi.getSpuListByIds(spuIds.join(','));
if (isEmpty(spuList)) {
sheep.$helper.toast('未找到商品信息');
throw new Error('未找到商品信息');
}
//
const deliveryTypesList = spuList.map((item) => item.deliveryTypes);
//
const hasConflict = checkDeliveryConflicts(deliveryTypesList);
if (hasConflict) {
sheep.$helper.toast('选中商品支持的配送方式冲突,不允许提交');
throw new Error('选中商品支持的配送方式冲突,不允许提交');
}
}
/**
* 检查配送方式列表中是否存在冲突
* @description
* 示例场景:
* A 商品支持[快递, 自提]
* B 商品支持[快递]
* C 商品支持[自提]
*
* 对比结果:
* A B不冲突 (有交集快递)
* A C不冲突 (有交集自提)
* B C冲突 (无交集)
* @param {Array<Array<number>>} deliveryTypesList - 配送方式列表的数组
* @returns {boolean} 是否存在冲突
*/
function checkDeliveryConflicts(deliveryTypesList) {
for (let i = 0; i < deliveryTypesList.length - 1; i++) {
const currentTypes = deliveryTypesList[i];
for (let j = i + 1; j < deliveryTypesList.length; j++) {
const nextTypes = deliveryTypesList[j];
//
const hasNoIntersection = !currentTypes.some((type) => nextTypes.includes(type));
if (hasNoIntersection) {
return true;
}
}
}
return false;
}
function onNumberChange(e, cartItem) {
if (e === 0) {
cart.delete(cartItem.id);
return;
}
if (cartItem.goods_num === e) return;
cartItem.goods_num = e;
cart.update({
goods_id: cartItem.id,
goods_num: e,
goods_sku_price_id: cartItem.goods_sku_price_id,
});
}
async function onDelete() {
cart.delete(state.selectedIds);
}
function getCartList() {
cart.getList();
}
onShow(() => {
getCartList();
});
</script>
<style lang="scss" scoped>
:deep(.ui-fixed) {
height: 72rpx;
}
:deep(.ui-fixed) {
height: 72rpx;
}
.cart-box {
width: 100%;
.cart-box {
width: 100%;
.cart-header {
height: 70rpx;
background-color: #f6f6f6;
width: 100%;
position: fixed;
left: 0;
top: v-bind('sys_navBar') rpx;
z-index: 1000;
box-sizing: border-box;
}
.cart-header {
height: 70rpx;
background-color: #f6f6f6;
width: 100%;
position: fixed;
left: 0;
top: v-bind('sys_navBar') rpx;
z-index: 1000;
box-sizing: border-box;
}
.cart-footer {
height: 100rpx;
background-color: #fff;
.cart-footer {
height: 100rpx;
background-color: #fff;
.pay-btn {
width: 180rpx;
height: 70rpx;
font-size: 28rpx;
line-height: 28rpx;
font-weight: 500;
border-radius: 40rpx;
}
}
.pay-btn {
width: 180rpx;
height: 70rpx;
font-size: 28rpx;
line-height: 28rpx;
font-weight: 500;
border-radius: 40rpx;
}
}
.cart-content {
margin-top: 70rpx;
.cart-content {
margin-top: 70rpx;
.goods-box {
background-color: #fff;
}
}
}
</style>
.goods-box {
background-color: #fff;
position: relative;
}
//
.down-box {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(#fff, 0.8);
z-index: 2;
display: flex;
justify-content: center;
align-items: center;
color: #999;
font-size: 32rpx;
}
}
}
</style>

View File

@ -1,54 +1,53 @@
<!-- 商品分类列表 -->
<template>
<s-layout title="分类" tabbar="/pages/index/category" :bgStyle="{ color: '#fff' }">
<s-layout :bgStyle="{ color: '#fff' }" tabbar="/pages/index/category" title="分类">
<view class="s-category">
<view class="three-level-wrap ss-flex ss-col-top" :style="[{ height: pageHeight + 'px' }]">
<view class="three-level-wrap ss-flex ss-col-top">
<!-- 商品分类 -->
<scroll-view class="side-menu-wrap" scroll-y :style="[{ height: pageHeight + 'px' }]">
<view
class="menu-item ss-flex"
v-for="(item, index) in state.categoryList"
:key="item.id"
:class="[{ 'menu-item-active': index === state.activeMenu }]"
@tap="onMenu(index)"
>
<view class="menu-title ss-line-1">
{{ item.name }}
<view class="side-menu-wrap" :style="[{ top: Number(statusBarHeight + 88) + 'rpx' }]">
<scroll-view scroll-y :style="[{ height: pageHeight + 'px' }]">
<view
class="menu-item ss-flex"
v-for="(item, index) in state.categoryList"
:key="item.id"
:class="[{ 'menu-item-active': index === state.activeMenu }]"
@tap="onMenu(index)"
>
<view class="menu-title ss-line-1">
{{ item.name }}
</view>
</view>
</view>
</scroll-view>
</scroll-view>
</view>
<!-- 商品分类 -->
<scroll-view
class="goods-list-box"
scroll-y
:style="[{ height: pageHeight + 'px' }]"
v-if="state.categoryList?.length"
>
<image
v-if="state.categoryList[state.activeMenu].picUrl"
class="banner-img"
:src="sheep.$url.cdn(state.categoryList[state.activeMenu].picUrl)"
mode="widthFix"
/>
<first-one v-if="state.style === 'first_one'" :pagination="state.pagination" />
<first-two v-if="state.style === 'first_two'" :pagination="state.pagination" />
<second-one
v-if="state.style === 'second_one'"
:data="state.categoryList"
:activeMenu="state.activeMenu"
/>
<uni-load-more
v-if="
(state.style === 'first_one' || state.style === 'first_two') &&
state.pagination.total > 0
"
:status="state.loadStatus"
:content-text="{
contentdown: '点击查看更多',
}"
@tap="loadMore"
/>
</scroll-view>
<view class="goods-list-box" v-if="state.categoryList?.length">
<scroll-view scroll-y :style="[{ height: pageHeight + 'px' }]">
<image
v-if="state.categoryList[state.activeMenu].picUrl"
class="banner-img"
:src="sheep.$url.cdn(state.categoryList[state.activeMenu].picUrl)"
mode="widthFix"
/>
<first-one v-if="state.style === 'first_one'" :pagination="state.pagination" />
<first-two v-if="state.style === 'first_two'" :pagination="state.pagination" />
<second-one
v-if="state.style === 'second_one'"
:data="state.categoryList"
:activeMenu="state.activeMenu"
/>
<uni-load-more
v-if="
(state.style === 'first_one' || state.style === 'first_two') &&
state.pagination.total > 0
"
:status="state.loadStatus"
:content-text="{
contentdown: '点击查看更多',
}"
@tap="loadMore"
/>
</scroll-view>
</view>
</view>
</view>
</s-layout>
@ -61,10 +60,10 @@
import sheep from '@/sheep';
import CategoryApi from '@/sheep/api/product/category';
import SpuApi from '@/sheep/api/product/spu';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { onLoad } from '@dcloudio/uni-app';
import { computed, reactive } from 'vue';
import _ from 'lodash';
import { handleTree } from '@/sheep/util';
import _ from 'lodash-es';
import { handleTree } from '@/sheep/helper/utils';
const state = reactive({
style: 'second_one', // first_one - , first_two - , second_one
@ -83,6 +82,7 @@
const { safeArea } = sheep.$platform.device;
const pageHeight = computed(() => safeArea.height - 44 - 50);
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
//
async function getList() {
@ -131,17 +131,18 @@
getGoodsList();
}
onLoad(async () => {
onLoad(async (params) => {
await getList();
// first
if (state.style === 'first_one' || state.style === 'first_two') {
onMenu(0);
}
//
const foundCategory = state.categoryList.find((category) => category.id === Number(params.id));
// onMenu onMenu(0)
onMenu(foundCategory ? state.categoryList.indexOf(foundCategory) : 0);
});
onReachBottom(() => {
function handleScrollToLower() {
loadMore();
});
}
</script>
<style lang="scss" scoped>
@ -152,6 +153,8 @@
height: 100%;
padding-left: 12rpx;
background-color: #f6f6f6;
position: fixed;
left: 0;
.menu-item {
width: 100%;
@ -222,8 +225,9 @@
.goods-list-box {
background-color: #fff;
width: calc(100vw - 100px);
width: calc(100vw - 200rpx);
padding: 10px;
margin-left: 200rpx;
}
.banner-img {

View File

@ -8,7 +8,7 @@
<image class="goods-img" :src="item.picUrl" mode="aspectFit" />
</view>
<view class="goods-content">
<view class="goods-title ss-line-1 ss-m-b-28">{{ item.title }}</view>
<view class="goods-title ss-line-1 ss-m-b-28">{{ item.name }}</view>
<view class="goods-price">{{ fen2yuan(item.price) }}</view>
</view>
</view>

View File

@ -1,87 +1,93 @@
<!-- 首页支持店铺装修 -->
<template>
<view v-if="template">
<s-layout title="首页" navbar="custom" tabbar="/pages/index/index" :bgStyle="template.page"
:navbarStyle="template.style?.navbar" onShareAppMessage>
<s-block v-for="(item, index) in template.components" :key="index" :styles="item.property.style">
<s-block-item :type="item.id" :data="item.property" :styles="item.property.style" />
</s-block>
</s-layout>
</view>
<view v-if="template">
<s-layout
title="首页"
navbar="custom"
tabbar="/pages/index/index"
:bgStyle="template.page"
:navbarStyle="template.navigationBar"
onShareAppMessage
>
<s-block
v-for="(item, index) in template.components"
:key="index"
:styles="item.property.style"
>
<s-block-item :type="item.id" :data="item.property" :styles="item.property.style" />
</s-block>
</s-layout>
</view>
</template>
<script setup>
import {
computed
} from 'vue';
import {
onLoad,
onPageScroll,
onPullDownRefresh
} from '@dcloudio/uni-app';
import sheep from '@/sheep';
import $share from '@/sheep/platform/share';
// tabBar
uni.hideTabBar();
import { computed } from 'vue';
import { onLoad, onPageScroll, onPullDownRefresh } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import $share from '@/sheep/platform/share';
// tabBar
uni.hideTabBar({
fail: () => {},
});
const template = computed(() => sheep.$store('app').template?.home);
//
// (async function() {
// console.log('',template)
// let {
// data
// } = await index2Api.decorate();
// console.log('',JSON.parse(data[1].value))
// id
// let {
// data: datas
// } = await index2Api.spids();
// template.value.data[9].data.goodsIds = datas.list.map(item => item.id);
// template.value.data[0].data.list = JSON.parse(data[0].value).map(item => {
// return {
// src: item.picUrl,
// url: item.url,
// title: item.name,
// type: "image"
// }
// })
// }())
const template = computed(() => sheep.$store('app').template?.home);
//
// (async function() {
// console.log('',template)
// let {
// data
// } = await index2Api.decorate();
// console.log('',JSON.parse(data[1].value))
// id
// let {
// data: datas
// } = await index2Api.spids();
// template.value.data[9].data.goodsIds = datas.list.map(item => item.id);
// template.value.data[0].data.list = JSON.parse(data[0].value).map(item => {
// return {
// src: item.picUrl,
// url: item.url,
// title: item.name,
// type: "image"
// }
// })
// }())
onLoad((options) => {
// #ifdef MP
//
if (options.scene) {
const sceneParams = decodeURIComponent(options.scene).split('=');
console.log('sceneParams=>', sceneParams);
options[sceneParams[0]] = sceneParams[1];
}
// #endif
onLoad((options) => {
// #ifdef MP
//
if (options.scene) {
const sceneParams = decodeURIComponent(options.scene).split('=');
options[sceneParams[0]] = sceneParams[1];
}
// #endif
//
if (options.templateId) {
sheep.$store('app').init(options.templateId);
}
//
if (options.templateId) {
sheep.$store('app').init(options.templateId);
}
//
if (options.spm) {
$share.decryptSpm(options.spm);
}
//
if (options.spm) {
$share.decryptSpm(options.spm);
}
// ()
if (options.page) {
sheep.$router.go(decodeURIComponent(options.page));
}
});
// ()
if (options.page) {
sheep.$router.go(decodeURIComponent(options.page));
}
});
//
onPullDownRefresh(() => {
sheep.$store('app').init();
setTimeout(function () {
uni.stopPullDownRefresh();
}, 800);
});
//
onPullDownRefresh(() => {
sheep.$store('app').init();
setTimeout(function() {
uni.stopPullDownRefresh();
}, 800);
});
onPageScroll(() => {});
onPageScroll(() => {});
</script>
<style></style>

View File

@ -14,19 +14,20 @@
new URLSearchParams(location.search).forEach((value, key) => {
options[key] = value;
});
// or await
const event = options.event;
const code = options.code;
const state = options.state;
if (event === 'login') { //
const res = await sheep.$platform.useProvider().login(code, state);
await sheep.$platform.useProvider().login(code, state);
} else if (event === 'bind') { //
sheep.$platform.useProvider().bind(code, state);
await sheep.$platform.useProvider().bind(code, state);
}
// H5
let returnUrl = uni.getStorageSync('returnUrl');
if (returnUrl) {
uni.removeStorage('returnUrl');
uni.removeStorage({key:'returnUrl'});
location.replace(returnUrl);
} else {
uni.switchTab({

View File

@ -1,6 +1,6 @@
<!-- 搜索界面 -->
<template>
<s-layout class="set-wrap" title="搜索" :bgStyle="{ color: '#FFF' }">
<s-layout :bgStyle="{ color: '#FFF' }" class="set-wrap" title="搜索">
<view class="ss-p-x-24">
<view class="ss-flex ss-col-center">
<uni-search-bar

View File

@ -5,10 +5,14 @@
tabbar="/pages/index/user"
navbar="custom"
:bgStyle="template.page"
:navbarStyle="template.style?.navbar"
:navbarStyle="template.navigationBar"
onShareAppMessage
>
<s-block v-for="(item, index) in template.components" :key="index" :styles="item.property.style">
<s-block
v-for="(item, index) in template.components"
:key="index"
:styles="item.property.style"
>
<s-block-item :type="item.id" :data="item.property" :styles="item.property.style" />
</s-block>
</s-layout>
@ -20,10 +24,11 @@
import sheep from '@/sheep';
// tabBar
uni.hideTabBar();
uni.hideTabBar({
fail: () => {},
});
const template = computed(() => sheep.$store('app').template.user);
const isLogin = computed(() => sheep.$store('user').isLogin);
onShow(() => {
sheep.$store('user').updateUserData();

View File

@ -0,0 +1,282 @@
<!-- 下单界面收货地址 or 自提门店的选择组件 -->
<template>
<view class="allAddress" :style="state.isPickUp ? '' : 'padding-top:10rpx;'">
<view class="nav flex flex-wrap">
<view
class="item font-color"
:class="state.deliveryType === 1 ? 'on' : 'on2'"
@tap="switchDeliveryType(1)"
v-if="state.isPickUp"
/>
<view
class="item font-color"
:class="state.deliveryType === 2 ? 'on' : 'on2'"
@tap="switchDeliveryType(2)"
v-if="state.isPickUp"
/>
</view>
<!-- 情况一收货地址的选择 -->
<view
class="address flex flex-wrap flex-center ss-row-between"
@tap="onSelectAddress"
v-if="state.deliveryType === 1"
:style="state.isPickUp ? '' : 'border-top-left-radius: 14rpx;border-top-right-radius: 14rpx;'"
>
<view class="addressCon" v-if="state.addressInfo.name">
<view class="name"
>{{ state.addressInfo.name }}
<text class="phone">{{ state.addressInfo.mobile }}</text>
</view>
<view class="flex flex-wrap">
<text class="default font-color" v-if="state.addressInfo.defaultStatus">[]</text>
<text class="line2">
{{ state.addressInfo.areaName }} {{ state.addressInfo.detailAddress }}
</text>
</view>
</view>
<view class="addressCon" v-else>
<view class="setaddress">设置收货地址</view>
</view>
<view class="iconfont">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</view>
</view>
<!-- 情况二门店的选择 -->
<view
class="address flex flex-wrap flex-center ss-row-between"
v-if="state.deliveryType === 2"
@tap="onSelectAddress"
>
<view class="addressCon" v-if="state.pickUpInfo.name">
<view class="name"
>{{ state.pickUpInfo.name }}
<text class="phone">{{ state.pickUpInfo.phone }}</text>
</view>
<view class="line1">
{{ state.pickUpInfo.areaName }}{{ ', ' + state.pickUpInfo.detailAddress }}
</view>
</view>
<view class="addressCon" v-else>
<view class="setaddress">选择自提门店</view>
</view>
<view class="iconfont">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</view>
</view>
<view class="line">
<image :src="sheep.$url.static('/static/img/shop/line.png')" />
</view>
</view>
</template>
<script setup>
import { computed } from 'vue';
import sheep from '@/sheep';
import { isEmpty } from 'lodash-es';
const props = defineProps({
modelValue: {
type: Object,
default() {},
},
});
const emits = defineEmits(['update:modelValue']);
// computed
const state = computed({
get() {
return new Proxy(props.modelValue, {
set(obj, name, val) {
emits('update:modelValue', {
...obj,
[name]: val,
});
return true;
},
});
},
set(val) {
emits('update:modelValue', val);
},
});
//
function onSelectAddress() {
let emitName = 'SELECT_ADDRESS';
let addressPage = '/pages/user/address/list?type=select';
if (state.value.deliveryType === 2) {
emitName = 'SELECT_PICK_UP_INFO';
addressPage = '/pages/user/goods_details_store/index';
}
uni.$once(emitName, (e) => {
changeConsignee(e.addressInfo);
});
sheep.$router.go(addressPage);
}
// &
async function changeConsignee(addressInfo = {}) {
if (!isEmpty(addressInfo)) {
if (state.value.deliveryType === 1) {
state.value.addressInfo = addressInfo;
}
if (state.value.deliveryType === 2) {
state.value.pickUpInfo = addressInfo;
}
}
}
//
const switchDeliveryType = (type) => {
state.value.deliveryType = type;
};
</script>
<style scoped lang="scss">
.allAddress .font-color {
color: #e93323 !important;
}
.line2 {
width: 504rpx;
}
.textR {
text-align: right;
}
.line {
width: 100%;
height: 3rpx;
}
.line image {
width: 100%;
height: 100%;
display: block;
}
.address {
padding: 28rpx;
background-color: #fff;
box-sizing: border-box;
}
.address .addressCon {
width: 596rpx;
font-size: 26rpx;
color: #666;
}
.address .addressCon .name {
font-size: 30rpx;
color: #282828;
font-weight: bold;
margin-bottom: 10rpx;
}
.address .addressCon .name .phone {
margin-left: 50rpx;
}
.address .addressCon .default {
margin-right: 12rpx;
}
.address .addressCon .setaddress {
color: #333;
font-size: 28rpx;
}
.address .iconfont {
font-size: 35rpx;
color: #707070;
}
.allAddress {
width: 100%;
background: linear-gradient(to bottom, #e93323 0%, #f5f5f5 100%);
// background-image: linear-gradient(to bottom, #e93323 0%, #f5f5f5 100%);
// background-image: -webkit-linear-gradient(to bottom, #e93323 0%, #f5f5f5 100%);
// background-image: -moz-linear-gradient(to bottom, #e93323 0%, #f5f5f5 100%);
//padding: 100rpx 30rpx 0 30rpx;
padding-top: 100rpx;
padding-bottom: 10rpx;
}
.allAddress .nav {
width: 690rpx;
margin: 0 auto;
}
.allAddress .nav .item {
width: 334rpx;
}
.allAddress .nav .item.on {
position: relative;
width: 230rpx;
}
.allAddress .nav .item.on::before {
position: absolute;
bottom: 0;
content: '快递配送';
font-size: 28rpx;
display: block;
height: 0;
width: 336rpx;
border-width: 0 20rpx 80rpx 0;
border-style: none solid solid;
border-color: transparent transparent #fff;
z-index: 2;
border-radius: 14rpx 36rpx 0 0;
text-align: center;
line-height: 80rpx;
}
.allAddress .nav .item:nth-of-type(2).on::before {
content: '到店自提';
border-width: 0 0 80rpx 20rpx;
border-radius: 36rpx 14rpx 0 0;
}
.allAddress .nav .item.on2 {
position: relative;
}
.allAddress .nav .item.on2::before {
position: absolute;
bottom: 0;
content: '到店自提';
font-size: 28rpx;
display: block;
height: 0;
width: 401rpx;
border-width: 0 0 60rpx 60rpx;
border-style: none solid solid;
border-color: transparent transparent #f7c1bd;
border-radius: 36rpx 14rpx 0 0;
text-align: center;
line-height: 60rpx;
}
.allAddress .nav .item:nth-of-type(1).on2::before {
content: '快递配送';
border-width: 0 60rpx 60rpx 0;
border-radius: 14rpx 36rpx 0 0;
}
.allAddress .address {
width: 690rpx;
max-height: 180rpx;
margin: 0 auto;
}
.allAddress .line {
width: 100%;
margin: 0 auto;
}
</style>

View File

@ -64,10 +64,9 @@
v-model="formData.applyDescription"
placeholder="客官~请描述您遇到的问题,建议上传照片"
/>
<!-- TODO 芋艿上传的测试 -->
<view class="upload-img">
<s-uploader
v-model:url="formData.images"
v-model:url="formData.applyPicUrls"
fileMediatype="image"
limit="9"
mode="grid"
@ -98,11 +97,7 @@
</view>
<view class="modal-content content_box">
<radio-group @change="onChange">
<label
class="radio ss-flex ss-col-center"
v-for="item in state.reasonList"
:key="item"
>
<label class="radio ss-flex ss-col-center" v-for="item in state.reasonList" :key="item">
<view class="ss-flex-1 ss-p-20">{{ item }}</view>
<radio
:value="item"
@ -152,21 +147,18 @@
],
reasonList: [], //
showModal: false, //
currentValue: '' //
currentValue: '', //
});
const formData = reactive({
let formData = reactive({
way: '',
applyReason: '',
applyDescription: '',
images: [],
applyPicUrls: [],
});
const rules = reactive({});
//
async function submit() {
// #ifdef MP
sheep.$platform.useProvider('wechat').subscribeMessage('order_aftersale_change');
// #endif
let data = {
orderItemId: state.itemId,
refundPrice: state.item.payPrice,
@ -177,7 +169,7 @@
uni.showToast({
title: '申请成功',
});
sheep.$router.go('/pages/order/aftersale/list');
sheep.$router.redirect('/pages/order/aftersale/list');
}
}
@ -214,7 +206,7 @@
state.itemId = parseInt(options.itemId);
//
const { code, data } = await OrderApi.getOrder(state.orderId);
const { code, data } = await OrderApi.getOrderDetail(state.orderId);
if (code !== 0) {
return;
}

View File

@ -1,156 +1,193 @@
<!-- 售后详情 -->
<template>
<s-layout title="售后详情" :navbar="!isEmpty(state.info) && state.loading ? 'inner' : 'normal'">
<view class="content_box" v-if="!isEmpty(state.info) && state.loading">
<!-- 步骤条 -->
<view class="steps-box ss-flex" :style="[
<s-layout title="售后详情" :navbar="!isEmpty(state.info) && state.loading ? 'inner' : 'normal'">
<view class="content_box" v-if="!isEmpty(state.info) && state.loading">
<!-- 步骤条 -->
<view
class="steps-box ss-flex"
:style="[
{
marginTop: '-' + Number(statusBarHeight + 88) + 'rpx',
paddingTop: Number(statusBarHeight + 88) + 'rpx',
},
]">
<view class="ss-flex">
<view class="steps-item" v-for="(item, index) in state.list" :key="index">
<view class="ss-flex">
<text class="sicon-circleclose"
v-if="state.list.length - 1 === index && [61, 62, 63].includes(state.info.status)" />
<text class="sicon-circlecheck" v-else
:class="state.active >= index ? 'activity-color' : 'info-color'" />
]"
>
<view class="ss-flex">
<view class="steps-item" v-for="(item, index) in state.list" :key="index">
<view class="ss-flex">
<text
class="sicon-circleclose"
v-if="state.list.length - 1 === index && [61, 62, 63].includes(state.info.status)"
/>
<text
class="sicon-circlecheck"
v-else
:class="state.active >= index ? 'activity-color' : 'info-color'"
/>
<view v-if="state.list.length - 1 !== index" class="line"
:class="state.active >= index ? 'activity-bg' : 'info-bg'" />
</view>
<view class="steps-item-title" :class="state.active >= index ? 'activity-color' : 'info-color'">
{{ item.title }}
</view>
</view>
</view>
</view>
<view
v-if="state.list.length - 1 !== index"
class="line"
:class="state.active >= index ? 'activity-bg' : 'info-bg'"
/>
</view>
<view
class="steps-item-title"
:class="state.active >= index ? 'activity-color' : 'info-color'"
>
{{ item.title }}
</view>
</view>
</view>
</view>
<!-- 服务状态 -->
<view class="status-box ss-flex ss-col-center ss-row-between ss-m-x-20"
@tap="sheep.$router.go('/pages/order/aftersale/log', { id: state.id })">
<view class="">
<view class="status-text">
<!-- 服务状态 -->
<view
class="status-box ss-flex ss-col-center ss-row-between ss-m-x-20"
@tap="sheep.$router.go('/pages/order/aftersale/log', { id: state.id })"
>
<view class="">
<view class="status-text">
{{ formatAfterSaleStatusDescription(state.info) }}
</view>
<view class="status-time">
<view class="status-time">
{{ sheep.$helper.timeFormat(state.info.updateTime, 'yyyy-mm-dd hh:MM:ss') }}
</view>
</view>
<text class="ss-iconfont _icon-forward" style="color: #666" />
</view>
</view>
<text class="ss-iconfont _icon-forward" style="color: #666" />
</view>
<!-- 退款金额 -->
<view class="aftersale-money ss-flex ss-col-center ss-row-between">
<view class="aftersale-money--title">退款总额</view>
<view class="aftersale-money--num">{{ fen2yuan(state.info.refundPrice) }}</view>
</view>
<!-- 服务商品 -->
<view class="order-shop">
<s-goods-item
:img=" state.info.picUrl"
:title=" state.info.spuName"
:titleWidth="480"
<!-- 退款金额 -->
<view class="aftersale-money ss-flex ss-col-center ss-row-between">
<view class="aftersale-money--title">退款总额</view>
<view class="aftersale-money--num">{{ fen2yuan(state.info.refundPrice) }}</view>
</view>
<!-- 服务商品 -->
<view class="order-shop">
<s-goods-item
:img="state.info.picUrl"
:title="state.info.spuName"
:titleWidth="480"
:skuText="state.info.properties.map((property) => property.valueName).join(' ')"
:num=" state.info.count"
:num="state.info.count"
/>
</view>
</view>
<!-- 服务内容 -->
<view class="aftersale-content">
<view class="aftersale-item ss-flex ss-col-center">
<view class="item-title">服务单号</view>
<view class="item-content ss-m-r-16">{{ state.info.no }}</view>
<button class="ss-reset-button copy-btn" @tap="onCopy"></button>
</view>
<view class="aftersale-item ss-flex ss-col-center">
<view class="item-title">申请时间</view>
<view class="item-content">
{{ sheep.$helper.timeFormat(state.info.createTime, 'yyyy-mm-dd hh:MM:ss') }}
</view>
</view>
<view class="aftersale-item ss-flex ss-col-center">
<view class="item-title">售后类型</view>
<view class="item-content">{{ state.info.way === 10 ? '仅退款' : '退款退货' }}</view>
</view>
<view class="aftersale-item ss-flex ss-col-center">
<view class="item-title">申请原因</view>
<view class="item-content">{{ state.info.applyReason }}</view>
</view>
<view class="aftersale-item ss-flex ss-col-center">
<view class="item-title">相关描述</view>
<view class="item-content">{{ state.info.applyDescription }}</view>
</view>
</view>
</view>
<!-- 服务内容 -->
<view class="aftersale-content">
<view class="aftersale-item ss-flex ss-col-center">
<view class="item-title">服务单号</view>
<view class="item-content ss-m-r-16">{{ state.info.no }}</view>
<button class="ss-reset-button copy-btn" @tap="onCopy"></button>
</view>
<view class="aftersale-item ss-flex ss-col-center">
<view class="item-title">申请时间</view>
<view class="item-content">
{{ sheep.$helper.timeFormat(state.info.createTime, 'yyyy-mm-dd hh:MM:ss') }}
</view>
</view>
<view class="aftersale-item ss-flex ss-col-center">
<view class="item-title">售后类型</view>
<view class="item-content">{{ state.info.way === 10 ? '仅退款' : '退款退货' }}</view>
</view>
<view class="aftersale-item ss-flex ss-col-center">
<view class="item-title">申请原因</view>
<view class="item-content">{{ state.info.applyReason }}</view>
</view>
<view class="aftersale-item ss-flex ss-col-center">
<view class="item-title">相关描述</view>
<view class="item-content">{{ state.info.applyDescription }}</view>
</view>
</view>
</view>
<!-- 操作区 -->
<s-empty v-if="isEmpty(state.info) && state.loading" icon="/static/order-empty.png" text="暂无该订单售后详情" />
<su-fixed bottom placeholder bg="bg-white" v-if="!isEmpty(state.info)">
<view class="foot_box">
<button class="ss-reset-button btn" v-if="state.info.buttons?.includes('cancel')"
@tap="onApply(state.info.id)">
<s-empty
v-if="isEmpty(state.info) && state.loading"
icon="/static/order-empty.png"
text="暂无该订单售后详情"
/>
<su-fixed bottom placeholder bg="bg-white" v-if="!isEmpty(state.info)">
<view class="foot_box">
<button
class="ss-reset-button btn"
v-if="state.info.buttons?.includes('cancel')"
@tap="onApply(state.info.id)"
>
取消申请
</button>
<button class="ss-reset-button btn" v-if="state.info.buttons?.includes('delivery')"
@tap="sheep.$router.go('/pages/order/aftersale/return-delivery', { id: state.info.id })">
<button
class="ss-reset-button btn"
v-if="state.info.buttons?.includes('delivery')"
@tap="sheep.$router.go('/pages/order/aftersale/return-delivery', { id: state.info.id })"
>
填写退货
</button>
<button class="ss-reset-button contcat-btn btn" @tap="sheep.$router.go('/pages/chat/index')">
<button
class="ss-reset-button contcat-btn btn"
@tap="sheep.$router.go('/pages/chat/index')"
>
联系客服
</button>
</view>
</su-fixed>
</s-layout>
</view>
</su-fixed>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import { isEmpty } from 'lodash';
import { fen2yuan, formatAfterSaleStatusDescription, handleAfterSaleButtons } from '@/sheep/hooks/useGoods';
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import { isEmpty } from 'lodash-es';
import {
fen2yuan,
formatAfterSaleStatusDescription,
handleAfterSaleButtons,
} from '@/sheep/hooks/useGoods';
import AfterSaleApi from '@/sheep/api/trade/afterSale';
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const headerBg = sheep.$url.css('/static/img/shop/order/order_bg.png');
const state = reactive({
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const headerBg = sheep.$url.css('/static/img/shop/order/order_bg.png');
const state = reactive({
id: 0, //
info: {}, //
loading: false,
loading: false,
active: 0, // list
list: [{
title: '提交申请',
}, {
title: '处理中',
}, {
title: '完成'
}], //
});
list: [
{
title: '提交申请',
},
{
title: '处理中',
},
{
title: '完成',
},
], //
});
function onApply(id) {
uni.showModal({
title: '提示',
content: '确定要取消此申请吗?',
success: async function(res) {
if (!res.confirm) {
return;
}
function onApply(id) {
uni.showModal({
title: '提示',
content: '确定要取消此申请吗?',
success: async function (res) {
if (!res.confirm) {
return;
}
const { code } = await AfterSaleApi.cancelAfterSale(id);
if (code === 0) {
await getDetail(id);
}
},
});
}
},
});
}
//
const onCopy = () => {
sheep.$helper.copyText(state.info.no);
};
sheep.$helper.copyText(state.info.no);
};
async function getDetail(id) {
async function getDetail(id) {
state.loading = true;
const { code, data } = await AfterSaleApi.getAfterSale(id);
if (code !== 0) {
@ -170,173 +207,173 @@
} else if ([61, 62, 63].includes(state.info.status)) {
state.active = 2;
}
}
}
onLoad((options) => {
onLoad((options) => {
if (!options.id) {
sheep.$helper.toast(`缺少订单信息,请检查`);
return
return;
}
state.id = options.id;
getDetail(options.id);
});
state.id = options.id;
getDetail(options.id);
});
</script>
<style lang="scss" scoped>
//
.steps-box {
width: 100%;
height: 190rpx;
background: v-bind(headerBg) no-repeat,
linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
background-size: 750rpx 100%;
padding-left: 72rpx;
//
.steps-box {
width: 100%;
height: 190rpx;
background: v-bind(headerBg) no-repeat,
linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
background-size: 750rpx 100%;
padding-left: 72rpx;
.steps-item {
.sicon-circleclose {
font-size: 24rpx;
color: #fff;
}
.steps-item {
.sicon-circleclose {
font-size: 24rpx;
color: #fff;
}
.sicon-circlecheck {
font-size: 24rpx;
}
.sicon-circlecheck {
font-size: 24rpx;
}
.steps-item-title {
font-size: 24rpx;
font-weight: 400;
margin-top: 16rpx;
margin-left: -36rpx;
width: 100rpx;
text-align: center;
}
}
}
.steps-item-title {
font-size: 24rpx;
font-weight: 400;
margin-top: 16rpx;
margin-left: -36rpx;
width: 100rpx;
text-align: center;
}
}
}
.activity-color {
color: #fff;
}
.activity-color {
color: #fff;
}
.info-color {
color: rgba(#fff, 0.4);
}
.info-color {
color: rgba(#fff, 0.4);
}
.activity-bg {
background: #fff;
}
.activity-bg {
background: #fff;
}
.info-bg {
background: rgba(#fff, 0.4);
}
.info-bg {
background: rgba(#fff, 0.4);
}
.line {
width: 270rpx;
height: 4rpx;
}
.line {
width: 270rpx;
height: 4rpx;
}
//
.status-box {
position: relative;
z-index: 3;
background-color: #fff;
border-radius: 20rpx 20rpx 0px 0px;
padding: 20rpx;
margin-top: -20rpx;
//
.status-box {
position: relative;
z-index: 3;
background-color: #fff;
border-radius: 20rpx 20rpx 0px 0px;
padding: 20rpx;
margin-top: -20rpx;
.status-text {
font-size: 28rpx;
.status-text {
font-size: 28rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
margin-bottom: 20rpx;
}
font-weight: 500;
color: rgba(51, 51, 51, 1);
margin-bottom: 20rpx;
}
.status-time {
font-size: 24rpx;
.status-time {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
}
}
font-weight: 400;
color: rgba(153, 153, 153, 1);
}
}
// 退
.aftersale-money {
background-color: #fff;
height: 98rpx;
padding: 0 20rpx;
margin: 20rpx;
// 退
.aftersale-money {
background-color: #fff;
height: 98rpx;
padding: 0 20rpx;
margin: 20rpx;
.aftersale-money--title {
font-size: 28rpx;
.aftersale-money--title {
font-size: 28rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
}
font-weight: 500;
color: rgba(51, 51, 51, 1);
}
.aftersale-money--num {
font-size: 28rpx;
font-family: OPPOSANS;
font-weight: 500;
color: #ff3000;
}
}
.aftersale-money--num {
font-size: 28rpx;
font-family: OPPOSANS;
font-weight: 500;
color: #ff3000;
}
}
// order-shop
.order-shop {
padding: 20rpx;
background-color: #fff;
margin: 0 20rpx 20rpx 20rpx;
}
// order-shop
.order-shop {
padding: 20rpx;
background-color: #fff;
margin: 0 20rpx 20rpx 20rpx;
}
//
.aftersale-content {
background-color: #fff;
padding: 20rpx;
margin: 0 20rpx;
//
.aftersale-content {
background-color: #fff;
padding: 20rpx;
margin: 0 20rpx;
.aftersale-item {
height: 60rpx;
.aftersale-item {
height: 60rpx;
.copy-btn {
background: #eeeeee;
color: #333;
border-radius: 20rpx;
width: 75rpx;
height: 40rpx;
font-size: 22rpx;
}
.copy-btn {
background: #eeeeee;
color: #333;
border-radius: 20rpx;
width: 75rpx;
height: 40rpx;
font-size: 22rpx;
}
.item-title {
color: #999;
font-size: 28rpx;
}
.item-title {
color: #999;
font-size: 28rpx;
}
.item-content {
color: #333;
font-size: 28rpx;
}
}
}
.item-content {
color: #333;
font-size: 28rpx;
}
}
}
//
.foot_box {
height: 100rpx;
background-color: #fff;
display: flex;
align-items: center;
justify-content: flex-end;
//
.foot_box {
height: 100rpx;
background-color: #fff;
display: flex;
align-items: center;
justify-content: flex-end;
.btn {
width: 160rpx;
line-height: 60rpx;
background: rgba(238, 238, 238, 1);
border-radius: 30rpx;
padding: 0;
margin-right: 20rpx;
font-size: 26rpx;
.btn {
width: 160rpx;
line-height: 60rpx;
background: rgba(238, 238, 238, 1);
border-radius: 30rpx;
padding: 0;
margin-right: 20rpx;
font-size: 26rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
}
}
</style>
font-weight: 400;
color: rgba(51, 51, 51, 1);
}
}
</style>

View File

@ -1,193 +1,209 @@
<!-- 售后列表 -->
<template>
<s-layout title="售后列表">
<!-- tab -->
<su-sticky bgColor="#fff">
<su-tabs :list="tabMaps" :scrollable="false" @change="onTabsChange" :current="state.currentTab" />
</su-sticky>
<s-empty v-if="state.pagination.total === 0" icon="/static/data-empty.png" text="暂无数据" />
<!-- 列表 -->
<view v-if="state.pagination.total > 0">
<view class="list-box ss-m-y-20" v-for="order in state.pagination.list" :key="order.id"
@tap="sheep.$router.go('/pages/order/aftersale/detail', { id: order.id })">
<view class="order-head ss-flex ss-col-center ss-row-between">
<text class="no">服务单号{{ order.no }}</text>
<text class="state">{{ formatAfterSaleStatus(order) }}</text>
</view>
<s-goods-item
<s-layout title="售后列表">
<!-- tab -->
<su-sticky bgColor="#fff">
<su-tabs
:list="tabMaps"
:scrollable="false"
@change="onTabsChange"
:current="state.currentTab"
/>
</su-sticky>
<s-empty v-if="state.pagination.total === 0" icon="/static/data-empty.png" text="暂无数据" />
<!-- 列表 -->
<view v-if="state.pagination.total > 0">
<view
class="list-box ss-m-y-20"
v-for="order in state.pagination.list"
:key="order.id"
@tap="sheep.$router.go('/pages/order/aftersale/detail', { id: order.id })"
>
<view class="order-head ss-flex ss-col-center ss-row-between">
<text class="no">服务单号{{ order.no }}</text>
<text class="state">{{ formatAfterSaleStatus(order) }}</text>
</view>
<s-goods-item
:img="order.picUrl"
:title="order.spuName"
:skuText="order.properties.map((property) => property.valueName).join(' ')"
:skuText="order.properties.map((property) => property.valueName).join(' ')"
:price="order.refundPrice"
/>
<view class="apply-box ss-flex ss-col-center ss-row-between border-bottom ss-p-x-20">
<view class="ss-flex ss-col-center">
<view class="title ss-m-r-20">{{ order.way === 10 ? '仅退款' : '退款退货' }}</view>
<view class="value">{{ formatAfterSaleStatusDescription(order) }}</view>
</view>
<text class="_icon-forward"></text>
</view>
<view class="tool-btn-box ss-flex ss-col-center ss-row-right ss-p-r-20">
<!-- TODO 功能缺失填写退货信息 -->
<view>
<button class="ss-reset-button tool-btn" @tap.stop="onApply(order.id)"
v-if="order?.buttons.includes('cancel')">取消申请</button>
</view>
</view>
</view>
</view>
<uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{
<view class="apply-box ss-flex ss-col-center ss-row-between border-bottom ss-p-x-20">
<view class="ss-flex ss-col-center">
<view class="title ss-m-r-20">{{ order.way === 10 ? '仅退款' : '退款退货' }}</view>
<view class="value">{{ formatAfterSaleStatusDescription(order) }}</view>
</view>
<text class="_icon-forward"></text>
</view>
<view class="tool-btn-box ss-flex ss-col-center ss-row-right ss-p-r-20">
<view>
<button
class="ss-reset-button tool-btn"
@tap.stop="onApply(order.id)"
v-if="order?.buttons.includes('cancel')"
>
取消申请
</button>
</view>
</view>
</view>
</view>
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}" @tap="loadMore" />
</s-layout>
}"
@tap="loadMore"
/>
</s-layout>
</template>
<script setup>
import sheep from '@/sheep';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import _ from 'lodash';
import { formatAfterSaleStatus, formatAfterSaleStatusDescription, handleAfterSaleButtons } from '@/sheep/hooks/useGoods';
import sheep from '@/sheep';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import _ from 'lodash-es';
import {
formatAfterSaleStatus,
formatAfterSaleStatusDescription,
handleAfterSaleButtons,
} from '@/sheep/hooks/useGoods';
import AfterSaleApi from '@/sheep/api/trade/afterSale';
import { resetPagination } from '@/sheep/helper/utils';
const paginationNull = {
list: [],
total: 0,
pageNo: 1,
pageSize: 10
};
const state = reactive({
currentTab: 0,
showApply: false,
pagination: {
const state = reactive({
currentTab: 0,
showApply: false,
pagination: {
list: [],
total: 0,
pageNo: 1,
pageSize: 10
},
loadStatus: '',
});
pageSize: 10,
},
loadStatus: '',
});
// TODO
const tabMaps = [{
name: '全部',
value: 'all',
},
// {
// name: '',
// value: 'nooper',
// },
// {
// name: '',
// value: 'ing',
// },
// {
// name: '',
// value: 'completed',
// },
// {
// name: '',
// value: 'refuse',
// },
];
const tabMaps = [
{
name: '全部',
value: [],
},
{
name: '申请中',
value: [10],
},
{
name: '处理中',
value: [20, 30, 40],
},
{
name: '已完成',
value: [50],
},
{
name: '已拒绝',
value: [61, 62, 63],
},
];
//
function onTabsChange(e) {
state.pagination = paginationNull
state.currentTab = e.index;
getOrderList();
}
//
function onTabsChange(e) {
resetPagination(state.pagination);
state.currentTab = e.index;
getOrderList();
}
//
async function getOrderList() {
state.loadStatus = 'loading';
let { data, code } = await AfterSaleApi.getAfterSalePage({
// type: tabMaps[state.currentTab].value,
//
async function getOrderList() {
state.loadStatus = 'loading';
let { data, code } = await AfterSaleApi.getAfterSalePage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
});
if (code !== 0) {
statuses: tabMaps[state.currentTab].value.join(','),
});
if (code !== 0) {
return;
}
data.list.forEach(order => handleAfterSaleButtons(order));
}
data.list.forEach((order) => handleAfterSaleButtons(order));
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
}
function onApply(orderId) {
uni.showModal({
title: '提示',
content: '确定要取消此申请吗?',
success: async function(res) {
if (!res.confirm) {
function onApply(orderId) {
uni.showModal({
title: '提示',
content: '确定要取消此申请吗?',
success: async function (res) {
if (!res.confirm) {
return;
}
}
const { code } = await AfterSaleApi.cancelAfterSale(orderId);
if (code === 0) {
state.pagination = paginationNull
resetPagination(state.pagination);
await getOrderList();
}
},
});
}
},
});
}
onLoad(async (options) => {
if (options.type) {
state.currentTab = options.type;
}
await getOrderList();
});
onLoad(async (options) => {
if (options.type) {
state.currentTab = options.type;
}
await getOrderList();
});
//
function loadMore() {
//
function loadMore() {
if (state.loadStatus === 'noMore') {
return
return;
}
state.pagination.pageNo++;
getOrderList();
}
}
//
onReachBottom(() => {
loadMore();
});
//
onReachBottom(() => {
loadMore();
});
</script>
<style lang="scss" scoped>
.list-box {
background-color: #fff;
.list-box {
background-color: #fff;
.order-head {
padding: 0 25rpx;
height: 77rpx;
}
.order-head {
padding: 0 25rpx;
height: 77rpx;
}
.apply-box {
height: 82rpx;
.apply-box {
height: 82rpx;
.title {
font-size: 24rpx;
}
.title {
font-size: 24rpx;
}
.value {
font-size: 22rpx;
color: $dark-6;
}
}
.value {
font-size: 22rpx;
color: $dark-6;
}
}
.tool-btn-box {
height: 100rpx;
.tool-btn-box {
height: 100rpx;
.tool-btn {
width: 160rpx;
height: 60rpx;
background: #f6f6f6;
border-radius: 30rpx;
font-size: 26rpx;
font-weight: 400;
}
}
}
</style>
.tool-btn {
width: 160rpx;
height: 60rpx;
background: #f6f6f6;
border-radius: 30rpx;
font-size: 26rpx;
font-weight: 400;
}
}
}
</style>

View File

@ -74,4 +74,10 @@
color: #999999;
margin-bottom: 40rpx;
}
:deep() {
image {
display: block;
}
}
</style>

View File

@ -6,14 +6,15 @@
<view class='list borRadius14'>
<view class='item acea-row row-between-wrapper' style="display: flex;align-items: center;">
<view>物流公司</view>
<picker mode='selector' class='num' @change="bindPickerChange" :value="state.expressIndex"
:range="state.expresses" range-key="name">
<view class="picker acea-row row-between-wrapper">
<view class='reason'>{{ state.expresses[state.expressIndex].name }}</view>
<!-- TODO 芋艿这里样式有问题少了 > 按钮 -->
<text class='iconfont icon-jiantou' />
</view>
</picker>
<view v-if="state.expresses.length>0" style="flex:1">
<picker mode='selector' class='num' @change="bindPickerChange" :value="state.expressIndex"
:range="state.expresses" range-key="name">
<view class="picker acea-row row-between-wrapper" style="display: flex;justify-content: space-between;">
<view class='reason'>{{ state.expresses[state.expressIndex].name }}</view>
<text class='iconfont _icon-forward' />
</view>
</picker>
</view>
</view>
<view class='item textarea acea-row row-between' style="display: flex;align-items: center;">
<view>物流单号</view>

View File

@ -1,13 +1,7 @@
<template>
<s-layout title="确认订单">
<!-- TODO这个判断先删除 v-if="state.orderInfo.need_address === 1" -->
<view class="bg-white address-box ss-m-b-14 ss-r-b-10" @tap="onSelectAddress">
<s-address-item :item="state.addressInfo" :hasBorderBottom="false">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</s-address-item>
</view>
<!-- 头部地址选择配送地址自提地址 -->
<AddressSelection v-model="addressState" />
<!-- 商品信息 -->
<view class="order-card-box ss-m-b-14">
@ -46,26 +40,89 @@
</text>
</view>
</view>
<!-- TODO 芋艿接入积分 -->
<view
v-if="state.orderPayload.pointActivityId"
class="order-item ss-flex ss-col-center ss-row-between"
v-if="state.orderPayload.order_type === 'score'"
>
<view class="item-title">扣除积分</view>
<view class="item-title">兑换积分</view>
<view class="ss-flex ss-col-center">
<image
:src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
class="score-img"
/>
<text class="item-value ss-m-r-24">{{ state.orderInfo.score_amount }}</text>
<text class="item-value ss-m-r-24">
{{ state.orderInfo.usePoint }}
</text>
</view>
</view>
<view class="order-item ss-flex ss-col-center ss-row-between">
<view
class="order-item ss-flex ss-col-center ss-row-between"
v-if="state.orderInfo.type === 0 || state.orderPayload.pointActivityId"
>
<view class="item-title">积分抵扣</view>
<view class="ss-flex ss-col-center">
{{ state.pointStatus || state.orderPayload.pointActivityId ? '剩余积分' : '当前积分' }}
<image
:src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
class="score-img"
/>
<text class="item-value ss-m-r-24">
{{
state.pointStatus || state.orderPayload.pointActivityId
? state.orderInfo.totalPoint - state.orderInfo.usePoint
: state.orderInfo.totalPoint || 0
}}
</text>
<checkbox-group @change="changeIntegral" v-if="!state.orderPayload.pointActivityId">
<checkbox
:checked="state.pointStatus"
:disabled="!state.orderInfo.totalPoint || state.orderInfo.totalPoint <= 0"
/>
</checkbox-group>
</view>
</view>
<!-- 快递配置时信息的展示 -->
<view
class="order-item ss-flex ss-col-center ss-row-between"
v-if="addressState.deliveryType === 1"
>
<view class="item-title">运费</view>
<view class="ss-flex ss-col-center">
<text class="item-value ss-m-r-24">
<text class="item-value ss-m-r-24" v-if="state.orderInfo.price.deliveryPrice > 0">
+{{ fen2yuan(state.orderInfo.price.deliveryPrice) }}
</text>
<view class="item-value ss-m-r-24" v-else></view>
</view>
</view>
<!-- 门店自提时需要填写姓名和手机号 -->
<view
class="order-item ss-flex ss-col-center ss-row-between"
v-if="addressState.deliveryType === 2"
>
<view class="item-title">联系人</view>
<view class="ss-flex ss-col-center">
<uni-easyinput
maxlength="20"
placeholder="请填写您的联系姓名"
v-model="addressState.receiverName"
:inputBorder="false"
:clearable="false"
/>
</view>
</view>
<view
class="order-item ss-flex ss-col-center ss-row-between"
v-if="addressState.deliveryType === 2"
>
<view class="item-title">联系电话</view>
<view class="ss-flex ss-col-center">
<uni-easyinput
maxlength="20"
placeholder="请填写您的联系电话"
v-model="addressState.receiverMobile"
:inputBorder="false"
:clearable="false"
/>
</view>
</view>
<!-- 优惠劵只有 type = 0 普通订单非拼团秒杀砍价才可以使用优惠劵 -->
@ -80,11 +137,17 @@
</text>
<text
class="item-value"
:class="state.couponInfo.length > 0 ? 'text-red' : 'text-disabled'"
:class="
state.couponInfo.filter((coupon) => coupon.match).length > 0
? 'text-red'
: 'text-disabled'
"
v-else
>
{{
state.couponInfo.length > 0 ? state.couponInfo.length + ' 张可用' : '暂无可用优惠券'
state.couponInfo.filter((coupon) => coupon.match).length > 0
? state.couponInfo.filter((coupon) => coupon.match).length + ' 张可用'
: '暂无可用优惠券'
}}
</text>
<text class="_icon-forward item-icon" />
@ -95,21 +158,31 @@
v-if="state.orderInfo.price.discountPrice > 0"
>
<view class="item-title">活动优惠</view>
<view class="ss-flex ss-col-center">
<!-- @tap="state.showDiscount = true" TODO 芋艿后续要把优惠信息打进去 -->
<view class="ss-flex ss-col-center" @tap="state.showDiscount = true">
<text class="item-value text-red">
-{{ fen2yuan(state.orderInfo.price.discountPrice) }}
</text>
<text class="_icon-forward item-icon" />
</view>
</view>
<view
class="order-item ss-flex ss-col-center ss-row-between"
v-if="state.orderInfo.price.vipPrice > 0"
>
<view class="item-title">会员优惠</view>
<view class="ss-flex ss-col-center">
<text class="item-value text-red">
-{{ fen2yuan(state.orderInfo.price.vipPrice) }}
</text>
</view>
</view>
</view>
<view class="total-box-footer ss-font-28 ss-flex ss-row-right ss-col-center ss-m-r-28">
<view class="total-num ss-m-r-20">
{{ state.orderInfo.items.reduce((acc, item) => acc + item.count, 0) }}
</view>
<view>合计</view>
<view class="total-num text-red"> {{ fen2yuan(state.orderInfo.price.payPrice) }} </view>
<view class="total-num text-red"> {{ fen2yuan(state.orderInfo.price.payPrice) }}</view>
</view>
</view>
@ -121,7 +194,7 @@
@close="state.showCoupon = false"
/>
<!-- 满额折扣弹框 TODO 芋艿后续要把优惠信息打进去 -->
<!-- 满额折扣弹框 -->
<s-discount-list
v-model="state.orderInfo"
:show="state.showDiscount"
@ -148,13 +221,14 @@
</template>
<script setup>
import { reactive } from 'vue';
import { reactive, ref, watch } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import AddressSelection from '@/pages/order/addressSelection.vue';
import sheep from '@/sheep';
import { isEmpty } from 'lodash';
import OrderApi from '@/sheep/api/trade/order';
import CouponApi from '@/sheep/api/promotion/coupon';
import TradeConfigApi from '@/sheep/api/trade/config';
import { fen2yuan } from '@/sheep/hooks/useGoods';
import { DeliveryTypeEnum } from '@/sheep/helper/const';
const state = reactive({
orderPayload: {},
@ -162,41 +236,62 @@
items: [], //
price: {}, //
},
addressInfo: {}, //
showCoupon: false, //
couponInfo: [], //
showDiscount: false, //
// ========== ==========
pointStatus: false, //使
});
//
function onSelectAddress() {
uni.$once('SELECT_ADDRESS', (e) => {
changeConsignee(e.addressInfo);
});
sheep.$router.go('/pages/user/address/list');
}
const addressState = ref({
addressInfo: {}, //
deliveryType: undefined, // 1-2-
isPickUp: true, //
pickUpInfo: {}, //
receiverName: '', //
receiverMobile: '', //
});
// &
async function changeConsignee(addressInfo = {}) {
if (!isEmpty(addressInfo)) {
state.addressInfo = addressInfo;
}
// ========== ==========
/**
* 使用积分抵扣
*/
const changeIntegral = async () => {
state.pointStatus = !state.pointStatus;
await getOrderInfo();
}
};
//
async function onSelectCoupon(couponId) {
state.orderPayload.couponId = couponId || 0;
state.orderPayload.couponId = couponId;
await getOrderInfo();
state.showCoupon = false;
}
//
function onConfirm() {
if (!state.addressInfo.id) {
if (addressState.value.deliveryType === 1 && !addressState.value.addressInfo.id) {
sheep.$helper.toast('请选择收货地址');
return;
}
if (addressState.value.deliveryType === 2) {
if (!addressState.value.pickUpInfo.id) {
sheep.$helper.toast('请选择自提门店地址');
return;
}
if (addressState.value.receiverName === '' || addressState.value.receiverMobile === '') {
sheep.$helper.toast('请填写联系人或联系人电话');
return;
}
if (!/^[\u4e00-\u9fa5\w]{2,16}$/.test(addressState.value.receiverName)) {
sheep.$helper.toast('请填写您的真实姓名');
return;
}
if (!/^1(3|4|5|7|8|9|6)\d{9}$/.test(addressState.value.receiverMobile)) {
sheep.$helper.toast('请填写正确的手机号');
return;
}
}
submitOrder();
}
@ -205,12 +300,17 @@
const { code, data } = await OrderApi.createOrder({
items: state.orderPayload.items,
couponId: state.orderPayload.couponId,
addressId: state.addressInfo.id,
deliveryType: 1, // TODO
pointStatus: false, // TODO
remark: state.orderPayload.remark,
deliveryType: addressState.value.deliveryType,
addressId: addressState.value.addressInfo.id, //
pickUpStoreId: addressState.value.pickUpInfo.id, //
receiverName: addressState.value.receiverName, //
receiverMobile: addressState.value.receiverMobile, //
pointStatus: state.pointStatus,
combinationActivityId: state.orderPayload.combinationActivityId,
combinationHeadId: state.orderPayload.combinationHeadId,
seckillActivityId: state.orderPayload.seckillActivityId
seckillActivityId: state.orderPayload.seckillActivityId,
pointActivityId: state.orderPayload.pointActivityId,
});
if (code !== 0) {
return;
@ -219,10 +319,17 @@
if (state.orderPayload.items[0].cartId > 0) {
sheep.$store('cart').getList();
}
//
sheep.$router.redirect('/pages/pay/index', {
id: data.payOrderId,
});
if (data.payOrderId && data.payOrderId > 0) {
sheep.$router.redirect('/pages/pay/index', {
id: data.payOrderId,
});
} else {
sheep.$router.redirect('/pages/order/detail', {
id: data.id,
});
}
}
// &
@ -231,44 +338,72 @@
const { data, code } = await OrderApi.settlementOrder({
items: state.orderPayload.items,
couponId: state.orderPayload.couponId,
addressId: state.addressInfo.id,
deliveryType: 1, // TODO
pointStatus: false, // TODO
deliveryType: addressState.value.deliveryType,
addressId: addressState.value.addressInfo.id, //
pickUpStoreId: addressState.value.pickUpInfo.id, //
receiverName: addressState.value.receiverName, //
receiverMobile: addressState.value.receiverMobile, //
pointStatus: state.pointStatus,
combinationActivityId: state.orderPayload.combinationActivityId,
combinationHeadId: state.orderPayload.combinationHeadId,
seckillActivityId: state.orderPayload.seckillActivityId
seckillActivityId: state.orderPayload.seckillActivityId,
pointActivityId: state.orderPayload.pointActivityId,
});
if (code !== 0) {
return;
return code;
}
state.orderInfo = data;
state.couponInfo = data.coupons || [];
//
if (state.orderInfo.address) {
state.addressInfo = state.orderInfo.address;
}
}
//
async function getCoupons() {
const { code, data } = await CouponApi.getMatchCouponList(
state.orderInfo.price.payPrice,
state.orderInfo.items.map((item) => item.spuId),
state.orderPayload.items.map((item) => item.skuId),
state.orderPayload.items.map((item) => item.categoryId),
);
if (code === 0) {
state.couponInfo = data;
addressState.value.addressInfo = state.orderInfo.address;
}
return code;
}
onLoad(async (options) => {
//
if (!options.data) {
sheep.$helper.toast('参数不正确,请检查!');
return;
}
state.orderPayload = JSON.parse(options.data);
//
const { data, code } = await TradeConfigApi.getTradeConfig();
if (code === 0) {
addressState.value.isPickUp = data.deliveryPickUpEnabled;
}
//
//
addressState.value.deliveryType = DeliveryTypeEnum.EXPRESS.type;
let orderCode = await getOrderInfo();
if (orderCode === 0) {
return;
}
//
if (addressState.value.isPickUp) {
addressState.value.deliveryType = DeliveryTypeEnum.PICK_UP.type;
let orderCode = await getOrderInfo();
if (orderCode === 0) {
return;
}
}
//
addressState.value.deliveryType = undefined;
await getOrderInfo();
await getCoupons();
});
// 使 watch
watch(addressState, async (newAddress, oldAddress) => {
//
if (
newAddress.addressInfo.id !== oldAddress.addressInfo.id ||
newAddress.deliveryType !== oldAddress.deliveryType
) {
await getOrderInfo();
}
});
</script>

View File

@ -1,7 +1,6 @@
<!-- 订单详情 -->
<template>
<s-layout title="订单详情" class="index-wrap" navbar="inner">
<!-- 订单状态 TODO -->
<view
class="state-box ss-flex-col ss-col-center ss-row-right"
:style="[
@ -12,42 +11,41 @@
]"
>
<view class="ss-flex ss-m-t-32 ss-m-b-20">
<!-- 待支付 -->
<image
v-if="
state.orderInfo.status_code == 'unpaid' ||
state.orderInfo.status === 10 || //
state.orderInfo.status_code == 'nocomment'
"
v-if="state.orderInfo.status === 0"
class="state-img"
:src="sheep.$url.static('/static/img/shop/order/no_pay.png')"
/>
<!-- 待发货 -->
<image
v-if="state.orderInfo.status === 10"
class="state-img"
:src="sheep.$url.static('/static/img/shop/order/order_loading.png')"
>
</image>
/>
<!-- 已完成 -->
<image
v-if="
state.orderInfo.status_code == 'completed' ||
state.orderInfo.status_code == 'refund_agree'
"
v-else-if="state.orderInfo.status === 30"
class="state-img"
:src="sheep.$url.static('/static/img/shop/order/order_success.png')"
>
</image>
/>
<!-- 已关闭 -->
<image
v-if="state.orderInfo.status_code == 'cancel' || state.orderInfo.status_code == 'closed'"
v-else-if="state.orderInfo.status === 40"
class="state-img"
:src="sheep.$url.static('/static/img/shop/order/order_close.png')"
>
</image>
/>
<!-- 已发货 -->
<image
v-if="state.orderInfo.status_code == 'noget'"
v-else-if="state.orderInfo.status === 20"
class="state-img"
:src="sheep.$url.static('/static/img/shop/order/order_express.png')"
>
</image>
/>
<view class="ss-font-30">{{ formatOrderStatus(state.orderInfo) }}</view>
</view>
<view class="ss-font-26 ss-m-x-20 ss-m-b-70">{{
formatOrderStatusDescription(state.orderInfo)
}}</view>
<view class="ss-font-26 ss-m-x-20 ss-m-b-70">
{{ formatOrderStatusDescription(state.orderInfo) }}
</view>
</view>
<!-- 收货地址 -->
@ -67,11 +65,11 @@
class="detail-goods"
:style="[{ marginTop: state.orderInfo.receiverAreaId > 0 ? '0' : '-40rpx' }]"
>
<!-- 订单信TODO 芋艿 -->
<!-- 订单信 -->
<view class="order-list" v-for="item in state.orderInfo.items" :key="item.goods_id">
<view class="order-card">
<s-goods-item
@tap="onGoodsDetail(item.skuId)"
@tap="onGoodsDetail(item.spuId)"
:img="item.picUrl"
:title="item.spuName"
:skuText="item.properties.map((property) => property.valueName).join(' ')"
@ -126,6 +124,13 @@
</view>
</view>
<!-- 自提核销 -->
<PickUpVerify
:order-info="state.orderInfo"
:systemStore="systemStore"
ref="pickUpVerifyRef"
></PickUpVerify>
<!-- 订单信息 -->
<view class="notice-box">
<view class="notice-box__content">
@ -167,11 +172,22 @@
<text class="title">运费</text>
<text class="detail">{{ fen2yuan(state.orderInfo.deliveryPrice) }}</text>
</view>
<!-- TODO 芋艿优惠劵抵扣积分抵扣 -->
<view class="notice-item ss-flex ss-row-between" v-if="state.orderInfo.couponPrice > 0">
<text class="title">优惠劵金额</text>
<text class="detail">-¥{{ fen2yuan(state.orderInfo.couponPrice) }}</text>
</view>
<view class="notice-item ss-flex ss-row-between" v-if="state.orderInfo.pointPrice > 0">
<text class="title">积分抵扣</text>
<text class="detail">-¥{{ fen2yuan(state.orderInfo.pointPrice) }}</text>
</view>
<view class="notice-item ss-flex ss-row-between" v-if="state.orderInfo.discountPrice > 0">
<text class="title">优惠金额</text>
<text class="title">活动优惠</text>
<text class="detail">¥{{ fen2yuan(state.orderInfo.discountPrice) }}</text>
</view>
<view class="notice-item ss-flex ss-row-between" v-if="state.orderInfo.vipPrice > 0">
<text class="title">会员优惠</text>
<text class="detail">-¥{{ fen2yuan(state.orderInfo.vipPrice) }}</text>
</view>
<view class="notice-item all-rpice-item ss-flex ss-m-t-20">
<text class="title">{{ state.orderInfo.payStatus ? '已付款' : '需付款' }}</text>
<text class="detail all-price">{{ fen2yuan(state.orderInfo.payPrice) }}</text>
@ -203,13 +219,12 @@
>
继续支付
</button>
<!-- TODO 芋艿拼团接入 -->
<button
class="ss-reset-button cancel-btn"
v-if="state.orderInfo.buttons?.includes('combination')"
@tap="
sheep.$router.go('/pages/activity/groupon/detail', {
id: state.orderInfo.ext.groupon_id,
id: state.orderInfo.combinationRecordId,
})
"
>
@ -243,9 +258,9 @@
<script setup>
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import { isEmpty } from 'lodash';
import { onLoad, onShow } from '@dcloudio/uni-app';
import { reactive, ref } from 'vue';
import { isEmpty } from 'lodash-es';
import {
fen2yuan,
formatOrderStatus,
@ -253,6 +268,8 @@
handleOrderButtons,
} from '@/sheep/hooks/useGoods';
import OrderApi from '@/sheep/api/trade/order';
import DeliveryApi from '@/sheep/api/trade/delivery';
import PickUpVerify from '@/pages/order/pickUpVerify.vue';
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const headerBg = sheep.$url.css('/static/img/shop/order/order_bg.png');
@ -263,9 +280,12 @@
comeinType: '', //
});
// ========== ==========
const systemStore = ref({}); //
//
const onCopy = () => {
sheep.$helper.copyText(state.orderInfo.sn);
sheep.$helper.copyText(state.orderInfo.no);
};
//
@ -306,10 +326,10 @@
});
}
// TODO
//
async function onConfirm(orderId, ignore = false) {
//
// todo:
// todo: https://gitee.com/sheepjs/shopro-uniapp/commit/a6bbba49b84dd418b84c5fefc8b7463df8f4901f
// 1.return
// 2.mpConfirm,App.vueshow
let isOpenBusinessView = true;
@ -323,11 +343,20 @@
return;
}
//
const { code } = await OrderApi.receiveOrder(orderId);
if (code === 0) {
await getOrderDetail(orderId);
}
uni.showModal({
title: '提示',
content: '确认收货吗?',
success: async function (res) {
if (!res.confirm) {
return;
}
//
const { code } = await OrderApi.receiveOrder(orderId);
if (code === 0) {
await getOrderDetail(orderId);
}
},
});
}
// #ifdef MP-WEIXIN
@ -359,45 +388,61 @@
},
});
}
// #endif
//
function onComment(id) {
sheep.$router.go('/pages/goods/comment/add', {
id
id,
});
}
const pickUpVerifyRef = ref();
async function getOrderDetail(id) {
//
let res;
if (state.comeinType === 'wechat') {
// TODO
res = await OrderApi.getOrder(id, {
// TODO
res = await OrderApi.getOrderDetail(id, {
merchant_trade_no: state.merchantTradeNo,
});
} else {
res = await OrderApi.getOrder(id);
res = await OrderApi.getOrderDetail(id);
}
if (res.code === 0) {
state.orderInfo = res.data;
handleOrderButtons(state.orderInfo);
//
if (res.data.pickUpStoreId) {
const { data } = await DeliveryApi.getDeliveryPickUpStore(res.data.pickUpStoreId);
systemStore.value = data || {};
}
if (state.orderInfo.deliveryType === 2 && state.orderInfo.payStatus) {
pickUpVerifyRef.value && pickUpVerifyRef.value.markCode(res.data.pickUpVerifyCode);
}
} else {
sheep.$router.back();
}
}
onShow(async () => {
//onShow,
await getOrderDetail(state.orderInfo.id);
});
onLoad(async (options) => {
let id = 0;
if (options.id) {
id = options.id;
}
// TODO
// TODO
state.comeinType = options.comein_type;
if (state.comeinType === 'wechat') {
state.merchantTradeNo = options.merchant_trade_no;
}
await getOrderDetail(id);
state.orderInfo.id = id;
});
</script>

View File

@ -12,11 +12,11 @@
</swiper>
</uni-swiper-dot>
<view class="log-card-msg">
<!-- TODO 芋艿优化点展示状态 -->
<!-- <view class="ss-flex ss-m-b-8">-->
<!-- <view>物流状态</view>-->
<!-- <view class="warning-color">{{ state.info.status_text }}</view>-->
<!-- </view>-->
<!-- TODO 芋艿物流优化点展示状态 -->
<!-- <view class="ss-flex ss-m-b-8">-->
<!-- <view>物流状态</view>-->
<!-- <view class="warning-color">{{ state.info.status_text }}</view>-->
<!-- </view>-->
<view class="ss-m-b-8">快递单号{{ state.info.logisticsNo }}</view>
<view>快递公司{{ state.info.logisticsName }}</view>
</view>
@ -34,11 +34,15 @@
<view v-if="state.tracks.length - 1 !== index" class="line" />
</view>
<view class="log-content-msg">
<!-- TODO 芋艿优化点展示状态 -->
<!-- <view class="log-msg-title ss-m-b-20">-->
<!-- {{ item.status_text }}-->
<!-- </view>-->
<view class="log-msg-desc ss-m-b-16">{{ item.content }}</view>
<!-- TODO 芋艿物流优化点展示状态 -->
<!-- <view class="log-msg-title ss-m-b-20">-->
<!-- {{ item.status_text }}-->
<!-- </view>-->
<!-- <view class="log-msg-desc ss-m-b-16">{{ item.content }}</view>-->
<view class="log-msg-desc ss-m-b-16">
<highlight-number :content="item.content" @phone-click="handlePhoneClick" />
<!-- <rich-text :nodes="item.content"></rich-text>-->
</view>
<view class="log-msg-date ss-m-b-40">
{{ sheep.$helper.timeFormat(item.time, 'yyyy-mm-dd hh:MM:ss') }}
</view>
@ -54,6 +58,7 @@
import { onLoad } from '@dcloudio/uni-app';
import { computed, reactive } from 'vue';
import OrderApi from '@/sheep/api/trade/order';
import HighlightNumber from '@/pages/components/HighlightNumberText.vue';
const state = reactive({
info: [],
@ -74,11 +79,11 @@
async function getExpressDetail(id) {
const { data } = await OrderApi.getOrderExpressTrackList(id);
state.tracks = data.reverse();
state.tracks = data;
}
async function getOrderDetail(id) {
const { data } = await OrderApi.getOrder(id)
const { data } = await OrderApi.getOrderDetail(id);
state.info = data;
}
@ -86,53 +91,117 @@
getExpressDetail(options.id);
getOrderDetail(options.id);
});
function handlePhoneClick(data) {
handleClick(data);
}
function handleClick(data) {
const phoneNumber = data.phoneNumber;
if (!phoneNumber) return;
//
const platform = uni.getSystemInfoSync().platform.toLowerCase();
if (platform === 'devtools') {
uni.showToast({ title: '真机才可拨打电话', icon: 'none' });
handleCopy(phoneNumber);
return;
}
if (platform === 'wechat') {
uni.showToast({ title: '请手动拨打', icon: 'none' });
handleCopy(phoneNumber);
return;
}
uni.makePhoneCall({
phoneNumber: phoneNumber,
success: () => {
console.log('拨打电话成功');
},
fail: (err) => {
console.error('拨打电话失败', err);
uni.showToast({ title: '拨号失败,请手动拨打', icon: 'none' });
handleCopy(phoneNumber);
},
});
}
function handleCopy(text) {
uni.setClipboardData({
data: text,
success: () => {
uni.showToast({ title: '已复制到剪贴板', icon: 'success' });
},
fail: () => {
uni.showToast({ title: '复制失败', icon: 'none' });
},
});
}
</script>
<style lang="scss" scoped>
.highlight {
color: red; /* 高亮颜色 */
font-weight: bold;
}
.swiper-box {
width: 200rpx;
height: 200rpx;
}
.log-card {
border-top: 2rpx solid rgba(#dfdfdf, 0.5);
padding: 20rpx;
background: #fff;
margin-bottom: 20rpx;
.log-card-img {
width: 200rpx;
height: 200rpx;
margin-right: 20rpx;
}
.log-card-msg {
font-size: 28rpx;
font-weight: 500;
width: 490rpx;
color: #333333;
.warning-color {
color: #999;
}
}
}
.log-content {
padding: 34rpx 20rpx 0rpx 20rpx;
background: #fff;
.log-content-box {
align-items: stretch;
}
.log-icon {
height: inherit;
.cicon-title {
color: #ccc;
font-size: 40rpx;
}
.activity-color {
color: #f0c785;
font-size: 40rpx;
}
.info-color {
color: #ccc;
font-size: 40rpx;
}
.line {
width: 1px;
height: 100%;
@ -146,16 +215,18 @@
font-weight: bold;
color: #333333;
}
.log-msg-desc {
font-size: 24rpx;
font-weight: 400;
color: #333333;
line-height: 36rpx;
}
.log-msg-date {
font-size: 24rpx;
font-weight: 500;
color: #999999;
color: #000000;
}
}
}

View File

@ -1,249 +1,293 @@
<!-- 订单列表 -->
<template>
<s-layout title="我的订单">
<su-sticky bgColor="#fff">
<su-tabs :list="tabMaps" :scrollable="false" @change="onTabsChange" :current="state.currentTab" />
</su-sticky>
<s-empty v-if="state.pagination.total === 0" icon="/static/order-empty.png" text="暂无订单" />
<view v-if="state.pagination.total > 0">
<view class="bg-white order-list-card-box ss-r-10 ss-m-t-14 ss-m-20" v-for="order in state.pagination.list"
:key="order.id" @tap="onOrderDetail(order.id)">
<view class="order-card-header ss-flex ss-col-center ss-row-between ss-p-x-20">
<view class="order-no">订单号{{ order.no }}</view>
<view class="order-state ss-font-26" :class="formatOrderColor(order)">
<s-layout title="我的订单">
<su-sticky bgColor="#fff">
<su-tabs
:list="tabMaps"
:scrollable="false"
@change="onTabsChange"
:current="state.currentTab"
/>
</su-sticky>
<s-empty v-if="state.pagination.total === 0" icon="/static/order-empty.png" text="暂无订单" />
<view v-if="state.pagination.total > 0">
<view
class="bg-white order-list-card-box ss-r-10 ss-m-t-14 ss-m-20"
v-for="order in state.pagination.list"
:key="order.id"
@tap="onOrderDetail(order.id)"
>
<view class="order-card-header ss-flex ss-col-center ss-row-between ss-p-x-20">
<view class="order-no">订单号{{ order.no }}</view>
<view class="order-state ss-font-26" :class="formatOrderColor(order)">
{{ formatOrderStatus(order) }}
</view>
</view>
<view class="border-bottom" v-for="item in order.items" :key="item.id">
<s-goods-item
</view>
<view class="border-bottom" v-for="item in order.items" :key="item.id">
<s-goods-item
:img="item.picUrl"
:title="item.spuName"
:skuText="item.properties.map((property) => property.valueName).join(' ')"
:price="item.price"
:skuText="item.properties.map((property) => property.valueName).join(' ')"
:price="item.price"
:num="item.count"
/>
</view>
<view class="pay-box ss-m-t-30 ss-flex ss-row-right ss-p-r-20">
<view class="ss-flex ss-col-center">
<view class="discounts-title pay-color"> {{ order.productCount }} 件商品,总金额:</view>
<view class="discounts-money pay-color">
{{ fen2yuan(order.payPrice) }}
</view>
</view>
</view>
<view class="order-card-footer ss-flex ss-col-center ss-p-x-20"
:class="order.buttons.length > 3 ? 'ss-row-between' : 'ss-row-right'">
<view class="ss-flex ss-col-center">
<button v-if="order.buttons.includes('combination')" class="tool-btn ss-reset-button"
@tap.stop="onOrderGroupon(order)">
</view>
<view class="pay-box ss-m-t-30 ss-flex ss-row-right ss-p-r-20">
<view class="ss-flex ss-col-center">
<view class="discounts-title pay-color"
> {{ order.productCount }} 件商品,总金额:</view
>
<view class="discounts-money pay-color"> {{ fen2yuan(order.payPrice) }} </view>
</view>
</view>
<view
class="order-card-footer ss-flex ss-col-center ss-p-x-20"
:class="order.buttons.length > 3 ? 'ss-row-between' : 'ss-row-right'"
>
<view class="ss-flex ss-col-center">
<button
v-if="order.buttons.includes('combination')"
class="tool-btn ss-reset-button"
@tap.stop="onOrderGroupon(order)"
>
拼团详情
</button>
<button v-if="order.buttons.length === 0" class="tool-btn ss-reset-button"
@tap.stop="onOrderDetail(order.id)">
</button>
<button
v-if="order.buttons.length === 0"
class="tool-btn ss-reset-button"
@tap.stop="onOrderDetail(order.id)"
>
查看详情
</button>
<button v-if="order.buttons.includes('confirm')" class="tool-btn ss-reset-button"
@tap.stop="onConfirm(order)">
</button>
<button
v-if="order.buttons.includes('confirm')"
class="tool-btn ss-reset-button"
@tap.stop="onConfirm(order)"
>
确认收货
</button>
<button v-if="order.buttons.includes('express')" class="tool-btn ss-reset-button"
@tap.stop="onExpress(order.id)">
查看物流
</button>
<button v-if="order.buttons.includes('cancel')" class="tool-btn ss-reset-button"
@tap.stop="onCancel(order.id)">
取消订单
</button>
<button v-if="order.buttons.includes('comment')" class="tool-btn ss-reset-button"
@tap.stop="onComment(order.id)">
评价
</button>
<button v-if="order.buttons.includes('delete')" class="delete-btn ss-reset-button"
@tap.stop="onDelete(order.id)">
删除订单
</button>
<button v-if="order.buttons.includes('pay')" class="tool-btn ss-reset-button ui-BG-Main-Gradient"
@tap.stop="onPay(order.payOrderId)">
继续支付
</button>
</view>
</view>
</view>
</view>
</button>
<button
v-if="order.buttons.includes('express')"
class="tool-btn ss-reset-button"
@tap.stop="onExpress(order.id)"
>
查看物流
</button>
<button
v-if="order.buttons.includes('cancel')"
class="tool-btn ss-reset-button"
@tap.stop="onCancel(order.id)"
>
取消订单
</button>
<button
v-if="order.buttons.includes('comment')"
class="tool-btn ss-reset-button"
@tap.stop="onComment(order.id)"
>
评价
</button>
<button
v-if="order.buttons.includes('delete')"
class="delete-btn ss-reset-button"
@tap.stop="onDelete(order.id)"
>
删除订单
</button>
<button
v-if="order.buttons.includes('pay')"
class="tool-btn ss-reset-button ui-BG-Main-Gradient"
@tap.stop="onPay(order.payOrderId)"
>
继续支付
</button>
</view>
</view>
</view>
</view>
<!-- 加载更多 -->
<uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{
<!-- 加载更多 -->
<uni-load-more
v-if="state.pagination.total > 0"
:status="state.loadStatus"
:content-text="{
contentdown: '上拉加载更多',
}" @tap="loadMore" />
</s-layout>
}"
@tap="loadMore"
/>
</s-layout>
</template>
<script setup>
import { reactive } from 'vue';
import { onLoad, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app';
import { reactive } from 'vue';
import { onLoad, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app';
import {
fen2yuan,
formatOrderColor, formatOrderStatus, handleOrderButtons,
formatOrderColor,
formatOrderStatus,
handleOrderButtons,
} from '@/sheep/hooks/useGoods';
import sheep from '@/sheep';
import _ from 'lodash';
import {
isEmpty
} from 'lodash';
import sheep from '@/sheep';
import _ from 'lodash-es';
import { isEmpty } from 'lodash-es';
import OrderApi from '@/sheep/api/trade/order';
import { resetPagination } from '@/sheep/helper/utils';
const paginationNull = {
list: [],
total: 0,
pageNo: 1,
pageSize: 5,
};
//
const state = reactive({
currentTab: 0, // tabMaps
pagination: {
//
const state = reactive({
currentTab: 0, // tabMaps
pagination: {
list: [],
total: 0,
pageNo: 1,
pageSize: 5,
},
loadStatus: ''
});
},
loadStatus: '',
});
const tabMaps = [{
name: '全部'
},
{
name: '待付款',
value: 0,
},
{
name: '待发货',
value: 10,
},
{
name: '待收货',
value: 20,
},
{
name: '待评价',
value: 30,
},
];
const tabMaps = [
{
name: '全部',
},
{
name: '待付款',
value: 0,
},
{
name: '待发货',
value: 10,
},
{
name: '待收货',
value: 20,
},
{
name: '待评价',
value: 30,
},
];
//
function onTabsChange(e) {
if (state.currentTab === e.index) {
//
function onTabsChange(e) {
if (state.currentTab === e.index) {
return;
}
//
state.pagination = paginationNull;
state.currentTab = e.index;
getOrderList();
}
resetPagination(state.pagination);
state.currentTab = e.index;
getOrderList();
}
//
function onOrderDetail(id) {
sheep.$router.go('/pages/order/detail', {
id,
});
}
// TODO
function onOrderGroupon(order) {
sheep.$router.go('/pages/activity/groupon/detail', {
id: order.ext.groupon_id,
});
}
//
function onPay(payOrderId) {
sheep.$router.go('/pages/pay/index', {
id: payOrderId,
});
}
//
function onComment(id) {
sheep.$router.go('/pages/goods/comment/add', {
id,
});
}
// TODO
async function onConfirm(order, ignore = false) {
//
// todo:
// 1.return
// 2.mpConfirm,App.vueshow
let isOpenBusinessView = true;
if (
sheep.$platform.name === 'WechatMiniProgram' &&
!isEmpty(order.wechat_extra_data) &&
isOpenBusinessView &&
!ignore
) {
mpConfirm(order);
return;
}
//
const { code } = await OrderApi.receiveOrder(order.id);
if (code === 0) {
state.pagination = paginationNull;
await getOrderList();
}
}
// #ifdef MP-WEIXIN
// TODO
function mpConfirm(order) {
if (!wx.openBusinessView) {
sheep.$helper.toast(`请升级微信版本`);
return;
}
wx.openBusinessView({
businessType: 'weappOrderConfirm',
extraData: {
merchant_id: '1481069012',
merchant_trade_no: order.wechat_extra_data.merchant_trade_no,
transaction_id: order.wechat_extra_data.transaction_id,
},
success(response) {
console.log('success:', response);
if (response.errMsg === 'openBusinessView:ok') {
if (response.extraData.status === 'success') {
onConfirm(order, true);
}
}
},
fail(error) {
console.log('error:', error);
},
complete(result) {
console.log('result:', result);
},
});
}
// #endif
//
async function onExpress(id) {
sheep.$router.go('/pages/order/express/log', {
//
function onOrderDetail(id) {
sheep.$router.go('/pages/order/detail', {
id,
});
}
});
}
//
async function onCancel(orderId) {
uni.showModal({
title: '提示',
content: '确定要取消订单吗?',
success: async function(res) {
if (!res.confirm) {
//
function onOrderGroupon(order) {
sheep.$router.go('/pages/activity/groupon/detail', {
id: order.combinationRecordId,
});
}
//
function onPay(payOrderId) {
sheep.$router.go('/pages/pay/index', {
id: payOrderId,
});
}
//
function onComment(id) {
sheep.$router.go('/pages/goods/comment/add', {
id,
});
}
//
async function onConfirm(order, ignore = false) {
//
// todo: https://gitee.com/sheepjs/shopro-uniapp/commit/a6bbba49b84dd418b84c5fefc8b7463df8f4901f
// 1.return
// 2.mpConfirm,App.vueshow
let isOpenBusinessView = true;
if (
sheep.$platform.name === 'WechatMiniProgram' &&
!isEmpty(order.wechat_extra_data) &&
isOpenBusinessView &&
!ignore
) {
mpConfirm(order);
return;
}
uni.showModal({
title: '提示',
content: '确认收货吗?',
success: async function (res) {
if (!res.confirm) {
return;
}
}
//
const { code } = await OrderApi.receiveOrder(order.id);
if (code === 0) {
resetPagination(state.pagination);
await getOrderList();
}
},
});
}
// #ifdef MP-WEIXIN
// TODO
function mpConfirm(order) {
if (!wx.openBusinessView) {
sheep.$helper.toast(`请升级微信版本`);
return;
}
wx.openBusinessView({
businessType: 'weappOrderConfirm',
extraData: {
merchant_id: '1481069012',
merchant_trade_no: order.wechat_extra_data.merchant_trade_no,
transaction_id: order.wechat_extra_data.transaction_id,
},
success(response) {
console.log('success:', response);
if (response.errMsg === 'openBusinessView:ok') {
if (response.extraData.status === 'success') {
onConfirm(order, true);
}
}
},
fail(error) {
console.log('error:', error);
},
complete(result) {
console.log('result:', result);
},
});
}
// #endif
//
async function onExpress(id) {
sheep.$router.go('/pages/order/express/log', {
id,
});
}
//
async function onCancel(orderId) {
uni.showModal({
title: '提示',
content: '确定要取消订单吗?',
success: async function (res) {
if (!res.confirm) {
return;
}
const { code } = await OrderApi.cancelOrder(orderId);
if (code === 0) {
//
@ -252,208 +296,209 @@
orderInfo.status = 40;
handleOrderButtons(orderInfo);
}
},
});
}
},
});
}
//
function onDelete(orderId) {
uni.showModal({
title: '提示',
content: '确定要删除订单吗?',
success: async function(res) {
if (res.confirm) {
const { code } = await OrderApi.deleteOrder(orderId);
if (code === 0) {
//
function onDelete(orderId) {
uni.showModal({
title: '提示',
content: '确定要删除订单吗?',
success: async function (res) {
if (res.confirm) {
const { code } = await OrderApi.deleteOrder(orderId);
if (code === 0) {
//
let index = state.pagination.list.findIndex((order) => order.id === orderId);
state.pagination.list.splice(index, 1);
}
}
},
});
}
let index = state.pagination.list.findIndex((order) => order.id === orderId);
state.pagination.list.splice(index, 1);
}
}
},
});
}
//
async function getOrderList() {
state.loadStatus = 'loading';
let { code, data } = await OrderApi.getOrderPage({
//
async function getOrderList() {
state.loadStatus = 'loading';
let { code, data } = await OrderApi.getOrderPage({
pageNo: state.pagination.pageNo,
pageSize: state.pagination.pageSize,
status: tabMaps[state.currentTab].value,
commentStatus: tabMaps[state.currentTab].value === 30 ? false : null
});
commentStatus: tabMaps[state.currentTab].value === 30 ? false : null,
});
if (code !== 0) {
return;
}
data.list.forEach(order => handleOrderButtons(order));
state.pagination.list = _.concat(state.pagination.list, data.list)
data.list.forEach((order) => handleOrderButtons(order));
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
onLoad(async (options) => {
if (options.type) {
state.currentTab = options.type;
}
await getOrderList();
});
onLoad(async (options) => {
if (options.type) {
state.currentTab = options.type;
}
await getOrderList();
});
//
function loadMore() {
if (state.loadStatus === 'noMore') {
return
}
//
function loadMore() {
if (state.loadStatus === 'noMore') {
return;
}
state.pagination.pageNo++;
getOrderList();
}
//
onReachBottom(() => {
loadMore();
});
//
onReachBottom(() => {
loadMore();
});
//
onPullDownRefresh(() => {
state.pagination = paginationNull;
getOrderList();
setTimeout(function() {
uni.stopPullDownRefresh();
}, 800);
});
//
onPullDownRefresh(() => {
resetPagination(state.pagination);
getOrderList();
setTimeout(function () {
uni.stopPullDownRefresh();
}, 800);
});
</script>
<style lang="scss" scoped>
.score-img {
width: 36rpx;
height: 36rpx;
margin: 0 4rpx;
}
.score-img {
width: 36rpx;
height: 36rpx;
margin: 0 4rpx;
}
.tool-btn {
width: 160rpx;
height: 60rpx;
background: #f6f6f6;
font-size: 26rpx;
border-radius: 30rpx;
margin-right: 10rpx;
.tool-btn {
width: 160rpx;
height: 60rpx;
background: #f6f6f6;
font-size: 26rpx;
border-radius: 30rpx;
margin-right: 10rpx;
&:last-of-type {
margin-right: 0;
}
}
&:last-of-type {
margin-right: 0;
}
}
.delete-btn {
width: 160rpx;
height: 56rpx;
color: #ff3000;
background: #fee;
border-radius: 28rpx;
font-size: 26rpx;
margin-right: 10rpx;
line-height: normal;
.delete-btn {
width: 160rpx;
height: 56rpx;
color: #ff3000;
background: #fee;
border-radius: 28rpx;
font-size: 26rpx;
margin-right: 10rpx;
line-height: normal;
&:last-of-type {
margin-right: 0;
}
}
&:last-of-type {
margin-right: 0;
}
}
.apply-btn {
width: 140rpx;
height: 50rpx;
border-radius: 25rpx;
font-size: 24rpx;
border: 2rpx solid #dcdcdc;
line-height: normal;
margin-left: 16rpx;
}
.apply-btn {
width: 140rpx;
height: 50rpx;
border-radius: 25rpx;
font-size: 24rpx;
border: 2rpx solid #dcdcdc;
line-height: normal;
margin-left: 16rpx;
}
.swiper-box {
flex: 1;
.swiper-box {
flex: 1;
.swiper-item {
height: 100%;
width: 100%;
}
}
.swiper-item {
height: 100%;
width: 100%;
}
}
.order-list-card-box {
.order-card-header {
height: 80rpx;
.order-list-card-box {
.order-card-header {
height: 80rpx;
.order-no {
font-size: 26rpx;
font-weight: 500;
}
.order-no {
font-size: 26rpx;
font-weight: 500;
}
.order-state {}
}
.order-state {
}
}
.pay-box {
.discounts-title {
font-size: 24rpx;
line-height: normal;
color: #999999;
}
.pay-box {
.discounts-title {
font-size: 24rpx;
line-height: normal;
color: #999999;
}
.discounts-money {
font-size: 24rpx;
line-height: normal;
color: #999;
font-family: OPPOSANS;
}
.discounts-money {
font-size: 24rpx;
line-height: normal;
color: #999;
font-family: OPPOSANS;
}
.pay-color {
color: #333;
}
}
.pay-color {
color: #333;
}
}
.order-card-footer {
height: 100rpx;
.order-card-footer {
height: 100rpx;
.more-item-box {
padding: 20rpx;
.more-item-box {
padding: 20rpx;
.more-item {
height: 60rpx;
.more-item {
height: 60rpx;
.title {
font-size: 26rpx;
}
}
}
.title {
font-size: 26rpx;
}
}
}
.more-btn {
color: $dark-9;
font-size: 24rpx;
}
.more-btn {
color: $dark-9;
font-size: 24rpx;
}
.content {
width: 154rpx;
color: #333333;
font-size: 26rpx;
font-weight: 500;
}
}
}
.content {
width: 154rpx;
color: #333333;
font-size: 26rpx;
font-weight: 500;
}
}
}
:deep(.uni-tooltip-popup) {
background: var(--ui-BG);
}
:deep(.uni-tooltip-popup) {
background: var(--ui-BG);
}
.warning-color {
color: #faad14;
}
.warning-color {
color: #faad14;
}
.danger-color {
color: #ff3000;
}
.danger-color {
color: #ff3000;
}
.success-color {
color: #52c41a;
}
.success-color {
color: #52c41a;
}
.info-color {
color: #999999;
}
</style>
.info-color {
color: #999999;
}
</style>

View File

@ -0,0 +1,260 @@
<template>
<view class="order-details">
<!-- 自提商品核销 -->
<view v-if="orderInfo.deliveryType === 2 && orderInfo.payStatus" class="writeOff borRadius14">
<view class="title">核销信息</view>
<view class="grayBg flex-center">
<view class="pictrue">
<image
v-if="!!painterImageUrl"
:src="painterImageUrl"
:style="{ width: `${state.qrcodeSize}px`, height: `${state.qrcodeSize}px` }"
:show-menu-by-longpress="true"
/>
</view>
</view>
<view class="gear">
<image :src="sheep.$url.static('/static/img/shop/writeOff.png')"></image>
</view>
<view class="num">{{ orderInfo.pickUpVerifyCode }}</view>
<view class="rules">
<view class="item">
<view class="rulesTitle flex flex-wrap align-center"> 核销时间 </view>
<view class="info">
每日
<text class="time">{{ systemStore.openingTime }} - {{ systemStore.closingTime }}</text>
</view>
</view>
<view class="item">
<view class="rulesTitle flex flex-wrap align-center">
<text class="iconfont icon-shuoming1"></text>
使用说明
</view>
<view class="info">可将二维码出示给店员扫描或提供数字核销码</view>
</view>
</view>
</view>
<view
v-if="orderInfo.deliveryType === 2"
class="map flex flex-wrap align-center ss-row-between borRadius14"
>
<view>自提地址信息</view>
<view class="place cart-color flex flex-wrap flex-center" @tap="showMaoLocation">
查看位置
</view>
</view>
<!-- 海报画板默认隐藏只用来生成海报生成方式为主动调用 -->
<l-painter
v-if="showPainter"
isCanvasToTempFilePath
pathType="url"
@success="setPainterImageUrl"
hidden
ref="painterRef"
/>
</view>
</template>
<script setup>
import sheep from '@/sheep';
import { reactive, ref } from 'vue';
const props = defineProps({
orderInfo: {
type: Object,
default() {},
},
systemStore: {
type: Object,
default() {},
},
});
const state = reactive({
qrcodeSize: 145,
});
/**
* 打开地图
*/
const showMaoLocation = () => {
if (!props.systemStore.latitude || !props.systemStore.longitude) {
sheep.$helper.toast('缺少经纬度信息无法查看地图!');
return;
}
uni.openLocation({
latitude: props.systemStore.latitude,
longitude: props.systemStore.longitude,
scale: 8,
name: props.systemStore.name,
address: props.systemStore.areaName + props.systemStore.detailAddress,
});
};
/**
* 拨打电话
*/
const makePhone = () => {
uni.makePhoneCall({
phoneNumber: props.systemStore.phone,
});
};
const painterRef = ref(); //
const painterImageUrl = ref(); // url
const showPainter = ref(true);
//
const renderPoster = async (poster) => {
await painterRef.value.render(poster);
};
//
const setPainterImageUrl = (path) => {
painterImageUrl.value = path;
showPainter.value = false;
};
/**
* 生成核销二维码
*/
const markCode = (text) => {
renderPoster({
css: {
width: `${state.qrcodeSize}px`,
height: `${state.qrcodeSize}px`,
},
views: [
{
type: 'qrcode',
text: text,
css: {
width: `${state.qrcodeSize}px`,
height: `${state.qrcodeSize}px`,
},
},
],
});
};
defineExpose({
markCode,
});
</script>
<style scoped lang="scss">
.borRadius14 {
border-radius: 14rpx !important;
}
.cart-color {
color: #e93323 !important;
border: 1px solid #e93323 !important;
}
.order-details {
border-radius: 10rpx;
margin: 0 20rpx 20rpx 20rpx;
}
.order-details .writeOff {
background-color: #fff;
margin-top: 15rpx;
padding-bottom: 50rpx;
}
.order-details .writeOff .title {
font-size: 30rpx;
color: #282828;
height: 87rpx;
border-bottom: 1px solid #f0f0f0;
padding: 0 24rpx;
line-height: 87rpx;
}
.order-details .writeOff .grayBg {
background-color: #f2f5f7;
width: 590rpx;
height: 384rpx;
border-radius: 20rpx 20rpx 0 0;
margin: 50rpx auto 0 auto;
padding-top: 55rpx;
}
.order-details .writeOff .grayBg .pictrue {
width: 290rpx;
height: 290rpx;
}
.order-details .writeOff .grayBg .pictrue image {
width: 100%;
height: 100%;
}
.order-details .writeOff .gear {
width: 590rpx;
height: 30rpx;
margin: 0 auto;
}
.order-details .writeOff .gear image {
width: 100%;
height: 100%;
}
.order-details .writeOff .num {
background-color: #f0c34c;
width: 590rpx;
height: 84rpx;
color: #282828;
font-size: 48rpx;
margin: 0 auto;
border-radius: 0 0 20rpx 20rpx;
text-align: center;
padding-top: 4rpx;
}
.order-details .writeOff .rules {
margin: 46rpx 30rpx 0 30rpx;
border-top: 1px solid #f0f0f0;
padding-top: 10rpx;
}
.order-details .writeOff .rules .item {
margin-top: 20rpx;
}
.order-details .writeOff .rules .item .rulesTitle {
font-size: 28rpx;
color: #282828;
}
.order-details .writeOff .rules .item .rulesTitle .iconfont {
font-size: 30rpx;
color: #333;
margin-right: 8rpx;
margin-top: 5rpx;
}
.order-details .writeOff .rules .item .info {
font-size: 28rpx;
color: #999;
margin-top: 7rpx;
}
.order-details .writeOff .rules .item .info .time {
margin-left: 20rpx;
}
.order-details .map {
height: 86rpx;
font-size: 30rpx;
color: #282828;
line-height: 86rpx;
border-bottom: 1px solid #f0f0f0;
margin-top: 15rpx;
background-color: #fff;
padding: 0 24rpx;
}
.order-details .map .place {
font-size: 26rpx;
width: 176rpx;
height: 50rpx;
border-radius: 25rpx;
line-height: 50rpx;
text-align: center;
}
</style>

View File

@ -81,7 +81,7 @@
import { fen2yuan, useDurationTime } from '@/sheep/hooks/useGoods';
import PayOrderApi from '@/sheep/api/pay/order';
import PayChannelApi from '@/sheep/api/pay/channel';
import { getPayMethods } from '@/sheep/platform/pay';
import { getPayMethods, goPayResult } from '@/sheep/platform/pay';
const userWallet = computed(() => sheep.$store('user').userWallet);
@ -135,12 +135,22 @@
// payOrder.status => payStatus
function checkPayStatus() {
if (state.orderInfo.status === 10
|| state.orderInfo.status === 20 ) { //
if (state.orderInfo.status === 10 || state.orderInfo.status === 20) {
//
state.payStatus = 2;
//
uni.showModal({
title: '提示',
content: '订单已支付',
showCancel: false,
success: function () {
goPayResult(state.orderInfo.id, state.orderType);
},
});
return;
}
if (state.orderInfo.status === 30) { //
if (state.orderInfo.status === 30) {
//
state.payStatus = -1;
return;
}
@ -155,31 +165,39 @@
//
async function setOrder(id) {
//
const { data, code } = await PayOrderApi.getOrder(id);
const { data, code } = await PayOrderApi.getOrder(id, true);
if (code !== 0 || !data) {
state.payStatus = -2;
return;
}
state.orderInfo = data;
//
await setPayMethods();
//
checkPayStatus();
//
await setPayMethods();
}
//
async function setPayMethods() {
const { data, code } = await PayChannelApi.getEnableChannelCodeList(state.orderInfo.appId)
const { data, code } = await PayChannelApi.getEnableChannelCodeList(state.orderInfo.appId);
if (code !== 0) {
return
return;
}
state.payMethods = getPayMethods(data)
state.payMethods = getPayMethods(data);
state.payMethods.find((item) => {
if (item.value && !item.disabled) {
state.payment = item.value;
return true;
}
});
}
onLoad((options) => {
if (sheep.$platform.name === 'WechatOfficialAccount'
&& sheep.$platform.os === 'ios'
&& !sheep.$platform.landingPage.includes('pages/pay/index')) {
if (
sheep.$platform.name === 'WechatOfficialAccount' &&
sheep.$platform.os === 'ios' &&
!sheep.$platform.landingPage.includes('pages/pay/index')
) {
location.reload();
return;
}
@ -208,7 +226,6 @@
position: relative;
padding: 60rpx 20rpx 40rpx;
.money-text {
color: $red;
font-size: 46rpx;

View File

@ -8,7 +8,7 @@
<view class="title">充值金额</view>
<view class="num" :class="item.refundStatus === 10 ? 'danger-color' : 'success-color'">
{{ fen2yuan(item.payPrice) }}
<text v-if="item.bonusPrice > 0"> {{ fen2yuan(item.bonusPrice)}} </text>
<text v-if="item.bonusPrice > 0"> {{ fen2yuan(item.bonusPrice) }} </text>
</view>
</view>
<view class="status-box item ss-flex ss-col-center ss-row-between">
@ -30,7 +30,9 @@
</view>
<view class="time-box item ss-flex ss-col-center ss-row-between">
<text class="item-title">充值时间</text>
<view class="time"> {{ sheep.$helper.timeFormat(item.payTime, 'yyyy-mm-dd hh:MM:ss') }}</view>
<view class="time">
{{ sheep.$helper.timeFormat(item.payTime, 'yyyy-mm-dd hh:MM:ss') }}</view
>
</view>
</view>
</view>
@ -53,7 +55,7 @@
<script setup>
import { reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import _ from 'lodash';
import _ from 'lodash-es';
import PayWalletApi from '@/sheep/api/pay/wallet';
import sheep from '@/sheep';
import { fen2yuan } from '../../sheep/hooks/useGoods';

View File

@ -1,259 +1,277 @@
<!-- 充值界面 -->
<template>
<s-layout title="充值" class="withdraw-wrap" navbar="inner">
<view class="wallet-num-box ss-flex ss-col-center ss-row-between" :style="[
{
marginTop: '-' + Number(statusBarHeight + 88) + 'rpx',
paddingTop: Number(statusBarHeight + 108) + 'rpx',
},
]">
<view class="">
<view class="num-title">当前余额</view>
<view class="wallet-num">{{ fen2yuan(userWallet.balance) }}</view>
</view>
<button class="ss-reset-button log-btn" @tap="sheep.$router.go('/pages/pay/recharge-log')">
<s-layout title="充值" class="withdraw-wrap" navbar="inner">
<view
class="wallet-num-box ss-flex ss-col-center ss-row-between"
:style="[
{
marginTop: '-' + Number(statusBarHeight + 88) + 'rpx',
paddingTop: Number(statusBarHeight + 108) + 'rpx',
},
]"
>
<view class="">
<view class="num-title">当前余额</view>
<view class="wallet-num">{{ fen2yuan(userWallet.balance) }}</view>
</view>
<button class="ss-reset-button log-btn" @tap="sheep.$router.go('/pages/pay/recharge-log')">
充值记录
</button>
</view>
<view class="recharge-box">
<view class="recharge-card-box">
<view class="input-label ss-m-b-50">充值金额</view>
<view class="input-box ss-flex border-bottom ss-p-b-20">
<view class="unit"></view>
<uni-easyinput v-model="state.recharge_money" type="digit" placeholder="请输入充值金额"
:inputBorder="false" />
</view>
<view class="face-value-box ss-flex ss-flex-wrap ss-m-y-40">
<button class="ss-reset-button face-value-btn" v-for="item in state.packageList" :key="item.money"
:class="[{ 'btn-active': state.recharge_money === fen2yuan(item.payPrice) }]"
@tap="onCard(item.payPrice)">
<text class="face-value-title">{{ fen2yuan(item.payPrice) }}</text>
<view v-if="item.bonusPrice" class="face-value-tag">
{{ fen2yuan(item.bonusPrice) }}
</view>
<view class="recharge-box">
<view class="recharge-card-box">
<view class="input-label ss-m-b-50">充值金额</view>
<view class="input-box ss-flex border-bottom ss-p-b-20">
<view class="unit"></view>
<uni-easyinput
v-model="state.recharge_money"
type="digit"
placeholder="请输入充值金额"
:inputBorder="false"
/>
</view>
<view class="face-value-box ss-flex ss-flex-wrap ss-m-y-40">
<button
class="ss-reset-button face-value-btn"
v-for="item in state.packageList"
:key="item.money"
:class="[{ 'btn-active': state.recharge_money === fen2yuan(item.payPrice) }]"
@tap="onCard(item.payPrice)"
>
<text class="face-value-title">{{ fen2yuan(item.payPrice) }}</text>
<view v-if="item.bonusPrice" class="face-value-tag">
{{ fen2yuan(item.bonusPrice) }}
</view>
</button>
</view>
<button class="ss-reset-button save-btn ui-BG-Main-Gradient ss-m-t-60 ui-Shadow-Main" @tap="onConfirm">
确认充值
</button>
</view>
</view>
</s-layout>
</button>
</view>
<button
class="ss-reset-button save-btn ui-BG-Main-Gradient ss-m-t-60 ui-Shadow-Main"
@tap="onConfirm"
>
确认充值
</button>
</view>
</view>
</s-layout>
</template>
<script setup>
import { computed, reactive } from 'vue';
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
import { computed, reactive } from 'vue';
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
import { fen2yuan } from '@/sheep/hooks/useGoods';
import PayWalletApi from '@/sheep/api/pay/wallet';
import { WxaSubscribeTemplate } from '@/sheep/helper/const';
const userWallet = computed(() => sheep.$store('user').userWallet);
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const headerBg = sheep.$url.css('/static/img/shop/user/withdraw_bg.png');
const userWallet = computed(() => sheep.$store('user').userWallet);
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const headerBg = sheep.$url.css('/static/img/shop/user/withdraw_bg.png');
const state = reactive({
recharge_money: '', //
const state = reactive({
recharge_money: '', //
packageList: [],
});
});
//
function onCard(e) {
state.recharge_money = fen2yuan(e);
}
//
function onCard(e) {
state.recharge_money = fen2yuan(e);
}
//
async function getRechargeTabs() {
async function getRechargeTabs() {
const { code, data } = await PayWalletApi.getWalletRechargePackageList();
if (code !== 0) {
return;
}
state.packageList = data;
}
}
//
async function onConfirm() {
const { code, data } = await PayWalletApi.createWalletRecharge({
packageId: state.packageList.find((item) => fen2yuan(item.payPrice) === state.recharge_money)?.id,
payPrice: state.recharge_money * 100
});
if (code !== 0) {
return;
}
async function onConfirm() {
const { code, data } = await PayWalletApi.createWalletRecharge({
packageId: state.packageList.find((item) => fen2yuan(item.payPrice) === state.recharge_money)
?.id,
payPrice: state.recharge_money * 100,
});
if (code !== 0) {
return;
}
// #ifdef MP
sheep.$platform.useProvider('wechat').subscribeMessage('money_change');
sheep.$platform
.useProvider('wechat')
.subscribeMessage(WxaSubscribeTemplate.PAY_WALLET_RECHARGER_SUCCESS);
// #endif
sheep.$router.go('/pages/pay/index', {
id: data.payOrderId,
orderType: 'recharge'
orderType: 'recharge',
});
}
}
onLoad(() => {
getRechargeTabs();
});
onLoad(() => {
getRechargeTabs();
});
</script>
<style lang="scss" scoped>
:deep() {
.uni-input-input {
font-family: OPPOSANS !important;
}
}
:deep() {
.uni-input-input {
font-family: OPPOSANS !important;
}
}
.wallet-num-box {
padding: 0 40rpx 80rpx;
background: var(--ui-BG-Main) v-bind(headerBg) center/750rpx 100% no-repeat;
border-radius: 0 0 5% 5%;
.wallet-num-box {
padding: 0 40rpx 80rpx;
background: var(--ui-BG-Main) v-bind(headerBg) center/750rpx 100% no-repeat;
border-radius: 0 0 5% 5%;
.num-title {
font-size: 26rpx;
font-weight: 500;
color: $white;
margin-bottom: 20rpx;
}
.num-title {
font-size: 26rpx;
font-weight: 500;
color: $white;
margin-bottom: 20rpx;
}
.wallet-num {
font-size: 60rpx;
font-weight: 500;
color: $white;
font-family: OPPOSANS;
}
.wallet-num {
font-size: 60rpx;
font-weight: 500;
color: $white;
font-family: OPPOSANS;
}
.log-btn {
width: 170rpx;
height: 60rpx;
line-height: 60rpx;
border: 1rpx solid $white;
border-radius: 30rpx;
padding: 0;
font-size: 26rpx;
font-weight: 500;
color: $white;
}
}
.log-btn {
width: 170rpx;
height: 60rpx;
line-height: 60rpx;
border: 1rpx solid $white;
border-radius: 30rpx;
padding: 0;
font-size: 26rpx;
font-weight: 500;
color: $white;
}
}
.recharge-box {
position: relative;
padding: 0 30rpx;
margin-top: -60rpx;
}
.recharge-box {
position: relative;
padding: 0 30rpx;
margin-top: -60rpx;
}
.save-btn {
width: 620rpx;
height: 86rpx;
border-radius: 44rpx;
font-size: 30rpx;
}
.save-btn {
width: 620rpx;
height: 86rpx;
border-radius: 44rpx;
font-size: 30rpx;
}
.recharge-card-box {
width: 690rpx;
background: var(--ui-BG);
border-radius: 20rpx;
padding: 30rpx;
box-sizing: border-box;
.recharge-card-box {
width: 690rpx;
background: var(--ui-BG);
border-radius: 20rpx;
padding: 30rpx;
box-sizing: border-box;
.input-label {
font-size: 30rpx;
font-weight: 500;
color: #333;
}
.input-label {
font-size: 30rpx;
font-weight: 500;
color: #333;
}
.unit {
display: flex;
align-items: center;
font-size: 48rpx;
font-weight: 500;
}
.unit {
display: flex;
align-items: center;
font-size: 48rpx;
font-weight: 500;
}
.uni-easyinput__placeholder-class {
font-size: 30rpx;
height: 60rpx;
display: flex;
align-items: center;
}
.uni-easyinput__placeholder-class {
font-size: 30rpx;
height: 60rpx;
display: flex;
align-items: center;
}
:deep(.uni-easyinput__content-input) {
font-size: 48rpx;
}
:deep(.uni-easyinput__content-input) {
font-size: 48rpx;
}
.face-value-btn {
width: 200rpx;
height: 144rpx;
border: 1px solid var(--ui-BG-Main);
border-radius: 10rpx;
position: relative;
z-index: 1;
margin-bottom: 15rpx;
margin-right: 15rpx;
.face-value-btn {
width: 200rpx;
height: 144rpx;
border: 1px solid var(--ui-BG-Main);
border-radius: 10rpx;
position: relative;
z-index: 1;
margin-bottom: 15rpx;
margin-right: 15rpx;
&:nth-of-type(3n) {
margin-right: 0;
}
&:nth-of-type(3n) {
margin-right: 0;
}
.face-value-title {
font-size: 36rpx;
font-weight: 500;
color: var(--ui-BG-Main);
font-family: OPPOSANS;
.face-value-title {
font-size: 36rpx;
font-weight: 500;
color: var(--ui-BG-Main);
font-family: OPPOSANS;
&::after {
content: '元';
font-size: 24rpx;
margin-left: 6rpx;
}
}
&::after {
content: '元';
font-size: 24rpx;
margin-left: 6rpx;
}
}
.face-value-tag {
position: absolute;
z-index: 2;
height: 40rpx;
line-height: 40rpx;
background: var(--ui-BG-Main);
opacity: 0.8;
border-radius: 10rpx 0 20rpx 0;
top: 0;
left: -2rpx;
padding: 0 16rpx;
font-size: 22rpx;
color: $white;
font-family: OPPOSANS;
}
.face-value-tag {
position: absolute;
z-index: 2;
height: 40rpx;
line-height: 40rpx;
background: var(--ui-BG-Main);
opacity: 0.8;
border-radius: 10rpx 0 20rpx 0;
top: 0;
left: -2rpx;
padding: 0 16rpx;
font-size: 22rpx;
color: $white;
font-family: OPPOSANS;
}
&::before {
position: absolute;
content: ' ';
width: 100%;
height: 100%;
background: var(--ui-BG-Main);
opacity: 0.1;
z-index: 0;
left: 0;
top: 0;
}
}
&::before {
position: absolute;
content: ' ';
width: 100%;
height: 100%;
background: var(--ui-BG-Main);
opacity: 0.1;
z-index: 0;
left: 0;
top: 0;
}
}
.btn-active {
z-index: 1;
.btn-active {
z-index: 1;
&::before {
content: '';
background: var(--ui-BG-Main);
opacity: 1;
}
&::before {
content: '';
background: var(--ui-BG-Main);
opacity: 1;
}
.face-value-title {
color: $white;
position: relative;
z-index: 1;
font-family: OPPOSANS;
}
.face-value-title {
color: $white;
position: relative;
z-index: 1;
font-family: OPPOSANS;
}
.face-value-tag {
background: $white;
color: var(--ui-BG-Main);
font-family: OPPOSANS;
}
}
}
</style>
.face-value-tag {
background: $white;
color: var(--ui-BG-Main);
font-family: OPPOSANS;
}
}
}
</style>

View File

@ -1,6 +1,6 @@
<!-- 支付结果页面 -->
<template>
<s-layout title="支付结果" :bgStyle="{ color: '#FFF' }">
<s-layout :bgStyle="{ color: '#FFF' }" title="支付结果">
<view class="pay-result-box ss-flex-col ss-row-center ss-col-center">
<!-- 信息展示 -->
<view class="pay-waiting ss-m-b-30" v-if="payResult === 'waiting'" />
@ -39,7 +39,6 @@
<button class="check-btn ss-reset-button" v-if="payResult === 'success'" @tap="onOrder">
查看订单
</button>
<!-- TODO 芋艿拼团接入 -->
<button
class="check-btn ss-reset-button"
v-if="payResult === 'success' && state.tradeOrder.type === 3"
@ -49,9 +48,11 @@
</button>
</view>
<!-- TODO 芋艿订阅 -->
<!-- #ifdef MP -->
<view class="subscribe-box ss-flex ss-m-t-44">
<view
class="subscribe-box ss-flex ss-m-t-44"
v-if="showSubscribeBtn && state.orderType === 'goods'"
>
<image class="subscribe-img" :src="sheep.$url.static('/static/img/shop/order/cargo.png')" />
<view class="subscribe-title ss-m-r-48 ss-m-l-16">获取实时发货信息与订单状态</view>
<view class="subscribe-start" @tap="subscribeMessage"></view>
@ -62,13 +63,14 @@
</template>
<script setup>
import { onLoad, onHide, onShow } from '@dcloudio/uni-app';
import { reactive, computed } from 'vue';
import { isEmpty } from 'lodash';
import { onHide, onLoad, onShow } from '@dcloudio/uni-app';
import { computed, reactive, ref } from 'vue';
import { isEmpty } from 'lodash-es';
import sheep from '@/sheep';
import PayOrderApi from '@/sheep/api/pay/order';
import { fen2yuan } from '../../sheep/hooks/useGoods';
import { fen2yuan } from '@/sheep/hooks/useGoods';
import OrderApi from '@/sheep/api/trade/order';
import { WxaSubscribeTemplate } from '@/sheep/helper/const';
const state = reactive({
id: 0, //
@ -95,6 +97,24 @@
}
});
function showRepayModal() {
if (state.result !== 'failed') return;
uni.showModal({
title: '确认支付',
content: '未检测到您的支付结果,请确认您是否已经支付完成?',
cancelText: '取消',
confirmText: '我已支付',
success: function (res) {
if (res.confirm) {
state.counter = 0;
setTimeout(() => {
getOrderInfo(state.id);
}, 100);
}
},
});
}
//
async function getOrderInfo(id) {
state.counter++;
@ -111,11 +131,23 @@
// 退
state.result = 'paid';
// #ifdef MP
subscribeMessage();
uni.showModal({
title: '支付结果',
showCancel: false, //
content: '支付成功',
success: () => {
// showModal
autoSubscribeMessage();
},
});
// #endif
//
if (state.orderType === 'goods') {
const { data, code } = await OrderApi.getOrder(state.orderInfo.merchantOrderId);
const { data, code } = await OrderApi.getOrderDetail(
state.orderInfo.merchantOrderId,
true,
);
if (code === 0) {
state.tradeOrder = data;
}
@ -123,20 +155,20 @@
return;
}
}
// 2.1
if (state.counter < 3 && state.result === 'unpaid') {
// 2.1
if (state.counter < 5 && state.result === 'unpaid') {
setTimeout(() => {
getOrderInfo(id);
}, 1500);
}, 2000);
}
// 2.2
if (state.counter >= 3) {
// 2.2
if (state.counter >= 5) {
state.result = 'failed';
showRepayModal();
}
}
function onOrder() {
// TODO
if (state.orderType === 'recharge') {
sheep.$router.redirect('/pages/pay/recharge-log');
} else {
@ -144,15 +176,35 @@
}
}
// TODO
// #ifdef MP
const showSubscribeBtn = ref(false); //
const SUBSCRIBE_BTN_STATUS_STORAGE_KEY = 'subscribe_btn_status';
function subscribeMessage() {
let event = ['order_dispatched'];
if (state.tradeOrder.type === 3) {
event.push('groupon_finish');
event.push('groupon_fail');
if (state.orderType !== 'goods') {
return;
}
sheep.$platform.useProvider('wechat').subscribeMessage(event);
const event = [WxaSubscribeTemplate.TRADE_ORDER_DELIVERY];
if (state.tradeOrder.type === 3) {
event.push(WxaSubscribeTemplate.PROMOTION_COMBINATION_SUCCESS);
}
sheep.$platform.useProvider('wechat').subscribeMessage(event, () => {
//
uni.removeStorageSync(SUBSCRIBE_BTN_STATUS_STORAGE_KEY);
uni.setStorageSync(SUBSCRIBE_BTN_STATUS_STORAGE_KEY, '已订阅');
//
showSubscribeBtn.value = false;
});
}
async function autoSubscribeMessage() {
// 1.
const subscribeBtnStatus = uni.getStorageSync(SUBSCRIBE_BTN_STATUS_STORAGE_KEY);
if (!subscribeBtnStatus) {
showSubscribeBtn.value = true;
return;
}
// 2.
subscribeMessage();
}
// #endif
@ -170,7 +222,7 @@
if (options.payState === 'fail') {
state.result = 'failed';
} else {
//
//
await getOrderInfo(state.id);
}
});

View File

@ -13,7 +13,7 @@
<s-empty
v-else-if="errCode === 'TemplateError'"
icon="/static/internet-empty.png"
text="未找到模板"
text="未找到模板,请前往后台启用对应模板"
showAction
actionText="重新加载"
@clickAction="onReconnect"

View File

@ -1,6 +1,6 @@
<!-- FAQ 常见问题 -->
<template>
<s-layout class="set-wrap" title="常见问题" :bgStyle="{ color: '#FFF' }">
<s-layout :bgStyle="{ color: '#FFF' }" class="set-wrap" title="常见问题">
<uni-collapse>
<uni-collapse-item v-for="(item, index) in state.list" :key="item">
<template v-slot:title>
@ -49,7 +49,7 @@
}
}
onLoad(() => {
// TODO 使 faq
// TODO 使 faq
if (true) {
sheep.$router.go('/pages/public/richtext', {
title: '常见问题',

View File

@ -1,9 +1,7 @@
<!-- 文章展示 -->
<template>
<s-layout class="set-wrap" :title="state.title" :bgStyle="{ color: '#FFF' }">
<view class="ss-p-30">
<mp-html class="richtext" :content="state.content" />
</view>
<s-layout :bgStyle="{ color: '#FFF' }" :title="state.title" class="set-wrap">
<view class="ss-p-30 richtext"><mp-html :content="state.content"></mp-html></view>
</s-layout>
</template>
@ -41,7 +39,6 @@
}
getRichTextContent(options.id, options.title);
});
</script>
<style lang="scss" scoped>
@ -49,6 +46,9 @@
margin: 0 30rpx;
}
.richtext {
:deep() {
image {
display: block;
}
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<s-layout class="set-wrap" title="系统设置" :bgStyle="{ color: '#fff' }">
<s-layout :bgStyle="{ color: '#fff' }" class="set-wrap" title="系统设置">
<view class="header-box ss-flex-col ss-row-center ss-col-center">
<image
class="logo-img ss-m-b-46"

View File

@ -1,78 +1,118 @@
<!-- 收货地址的新增/编辑 -->
<template>
<s-layout :title="state.model.id ? '编辑地址' : '新增地址'">
<uni-forms ref="addressFormRef" v-model="state.model" :rules="rules" validateTrigger="bind"
labelWidth="160" labelAlign="left" border :labelStyle="{ fontWeight: 'bold' }">
<view class="bg-white form-box ss-p-x-30">
<uni-forms-item name="name" label="收货人" class="form-item">
<uni-easyinput v-model="state.model.name" placeholder="请填写收货人姓名" :inputBorder="false"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal" />
</uni-forms-item>
<s-layout :title="state.model.id ? '编辑地址' : '新增地址'">
<uni-forms
ref="addressFormRef"
v-model="state.model"
:rules="rules"
validateTrigger="bind"
labelWidth="160"
labelAlign="left"
border
:labelStyle="{ fontWeight: 'bold' }"
>
<view class="bg-white form-box ss-p-x-30">
<uni-forms-item name="name" label="收货人" class="form-item">
<uni-easyinput
v-model="state.model.name"
placeholder="请填写收货人姓名"
:inputBorder="false"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
/>
</uni-forms-item>
<uni-forms-item name="mobile" label="手机号" class="form-item">
<uni-easyinput v-model="state.model.mobile" type="number" placeholder="请输入手机号" :inputBorder="false"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal">
</uni-easyinput>
</uni-forms-item>
<uni-forms-item name="areaName" label="省市区" @tap="state.showRegion = true" class="form-item">
<uni-easyinput v-model="state.model.areaName" disabled :inputBorder="false"
:styles="{ disableColor: '#fff', color: '#333' }"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
placeholder="请选择省市区">
<template v-slot:right>
<uni-icons type="right" />
</template>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item name="detailAddress" label="详细地址" :formItemStyle="{ alignItems: 'flex-start' }"
:labelStyle="{ lineHeight: '5em' }" class="textarea-item">
<uni-easyinput :inputBorder="false" type="textarea" v-model="state.model.detailAddress"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
placeholder="请输入详细地址" clearable />
</uni-forms-item>
</view>
<view class="ss-m-y-20 bg-white ss-p-x-30 ss-flex ss-row-between ss-col-center default-box">
<view class="default-box-title"> 设为默认地址 </view>
<su-switch style="transform: scale(0.8)" v-model="state.model.defaultStatus" />
</view>
</uni-forms>
<su-fixed bottom :opacity="false" bg="" placeholder :noFixed="false" :index="10">
<view class="footer-box ss-flex-col ss-row-between ss-p-20">
<view class="ss-m-b-20">
<uni-forms-item name="mobile" label="手机号" class="form-item">
<uni-easyinput
v-model="state.model.mobile"
type="number"
placeholder="请输入手机号"
:inputBorder="false"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item
name="areaName"
label="省市区"
@tap="state.showRegion = true"
class="form-item"
>
<uni-easyinput
v-model="state.model.areaName"
disabled
:inputBorder="false"
:styles="{ disableColor: '#fff', color: '#333' }"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
placeholder="请选择省市区"
>
<template v-slot:right>
<uni-icons type="right" />
</template>
</uni-easyinput>
</uni-forms-item>
<uni-forms-item
name="detailAddress"
label="详细地址"
:formItemStyle="{ alignItems: 'flex-start' }"
:labelStyle="{ lineHeight: '5em' }"
class="textarea-item"
>
<uni-easyinput
:inputBorder="false"
type="textarea"
v-model="state.model.detailAddress"
placeholderStyle="color:#BBBBBB;font-size:30rpx;font-weight:400;line-height:normal"
placeholder="请输入详细地址"
clearable
/>
</uni-forms-item>
</view>
<view class="ss-m-y-20 bg-white ss-p-x-30 ss-flex ss-row-between ss-col-center default-box">
<view class="default-box-title"> 设为默认地址 </view>
<su-switch style="transform: scale(0.8)" v-model="state.model.defaultStatus" />
</view>
</uni-forms>
<su-fixed bottom :opacity="false" bg="" placeholder :noFixed="false" :index="10">
<view class="footer-box ss-flex-col ss-row-between ss-p-20">
<view class="ss-m-b-20">
<button class="ss-reset-button save-btn ui-Shadow-Main" @tap="onSave"></button>
</view>
<button v-if="state.model.id" class="ss-reset-button cancel-btn" @tap="onDelete">
删除
</button>
</view>
</su-fixed>
<button v-if="state.model.id" class="ss-reset-button cancel-btn" @tap="onDelete">
删除
</button>
</view>
</su-fixed>
<!-- 省市区弹窗 -->
<su-region-picker :show="state.showRegion" @cancel="state.showRegion = false" @confirm="onRegionConfirm" />
</s-layout>
<!-- 省市区弹窗 -->
<su-region-picker
:show="state.showRegion"
@cancel="state.showRegion = false"
@confirm="onRegionConfirm"
/>
</s-layout>
</template>
<script setup>
import { ref, reactive, unref } from 'vue';
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
import _ from 'lodash';
import { mobile } from '@/sheep/validate/form';
import { ref, reactive, unref } from 'vue';
import sheep from '@/sheep';
import { onLoad } from '@dcloudio/uni-app';
import _ from 'lodash-es';
import { mobile } from '@/sheep/validate/form';
import AreaApi from '@/sheep/api/system/area';
import AddressApi from '@/sheep/api/member/address';
const addressFormRef = ref(null);
const state = reactive({
showRegion: false,
model: {
name: '',
mobile: '',
const addressFormRef = ref(null);
const state = reactive({
showRegion: false,
model: {
name: '',
mobile: '',
detailAddress: '',
defaultStatus: false,
defaultStatus: false,
areaName: '',
},
},
rules: {},
});
});
const rules = {
name: {
@ -85,167 +125,181 @@
},
mobile,
detailAddress: {
rules: [{
required: true,
errorMessage: '请输入详细地址',
}]
rules: [
{
required: true,
errorMessage: '请输入详细地址',
},
],
},
areaName: {
rules: [{
required: true,
errorMessage: '请选择您的位置'
}]
rules: [
{
required: true,
errorMessage: '请选择您的位置',
},
],
},
};
//
const onRegionConfirm = (e) => {
state.model.areaName = `${e.province_name} ${e.city_name} ${e.district_name}`
const onRegionConfirm = (e) => {
state.model.areaName = `${e.province_name} ${e.city_name} ${e.district_name}`;
state.model.areaId = e.district_id;
state.showRegion = false;
};
state.showRegion = false;
};
//
const getAreaData = () => {
if (_.isEmpty(uni.getStorageSync('areaData'))) {
const getAreaData = () => {
if (_.isEmpty(uni.getStorageSync('areaData'))) {
AreaApi.getAreaTree().then((res) => {
if (res.code === 0) {
uni.setStorageSync('areaData', res.data);
}
});
}
};
}
};
//
const onSave = async () => {
const onSave = async () => {
//
const validate = await unref(addressFormRef)
.validate()
.catch((error) => {
console.log('error: ', error);
});
if (!validate) {
const validate = await unref(addressFormRef)
.validate()
.catch((error) => {
console.log('error: ', error);
});
if (!validate) {
return;
}
//
const formData = {
...state.model
...state.model,
};
const { code } =
state.model.id > 0
? await AddressApi.updateAddress(formData)
: await AddressApi.createAddress(formData);
if (code === 0) {
sheep.$router.back();
}
const {code } = state.model.id > 0 ? await AddressApi.updateAddress(formData)
: await AddressApi.createAddress(formData);
if (code === 0) {
sheep.$router.back();
}
};
};
//
const onDelete = () => {
uni.showModal({
title: '提示',
content: '确认删除此收货地址吗?',
success: async function(res) {
if (!res.confirm) {
return;
}
const onDelete = () => {
uni.showModal({
title: '提示',
content: '确认删除此收货地址吗?',
success: async function (res) {
if (!res.confirm) {
return;
}
const { code } = await AddressApi.deleteAddress(state.model.id);
if (code === 0) {
sheep.$router.back();
}
},
});
};
},
});
};
onLoad(async (options) => {
onLoad(async (options) => {
//
getAreaData();
getAreaData();
// id
if (options.id) {
let { code, data} = await AddressApi.getAddress(options.id);
if (options.id) {
let { code, data } = await AddressApi.getAddress(options.id);
if (code !== 0) {
return;
}
state.model = data;
}
// TODO
if (options.data) {
let data = JSON.parse(options.data);
const areaData = uni.getStorageSync('areaData');
let provinceArr = areaData.filter(item => item.name == data.province_name);
data.province_id = provinceArr[0].id;
let provinceArr2 = provinceArr[0].children.filter(item => item.name == data.city_name);
data.city_id = provinceArr2[0].id;
let provinceArr3 = provinceArr2[0].children.filter(item => item.name == data.district_name);
data.district_id = provinceArr3[0].id;
state.model = {
...state.model,
...data,
};
}
});
}
//
if (options.data) {
let data = JSON.parse(options.data);
const areaData = uni.getStorageSync('areaData');
const findAreaByName = (areas, name) => areas.find((item) => item.name === name);
let provinceObj = findAreaByName(areaData, data.province_name);
let cityObj = provinceObj ? findAreaByName(provinceObj.children, data.city_name) : undefined;
let districtObj = cityObj ? findAreaByName(cityObj.children, data.district_name) : undefined;
let areaId = (districtObj || cityObj || provinceObj).id;
state.model = {
...state.model,
areaId,
areaName: [data.province_name, data.city_name, data.district_name]
.filter(Boolean)
.join(' '),
defaultStatus: false,
detailAddress: data.address,
mobile: data.mobile,
name: data.consignee,
};
}
});
</script>
<style lang="scss" scoped>
:deep() {
.uni-forms-item__label .label-text {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
}
:deep() {
.uni-forms-item__label .label-text {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
}
.uni-easyinput__content-input {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
padding-left: 0 !important;
}
.uni-easyinput__content-input {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
padding-left: 0 !important;
}
.uni-easyinput__content-textarea {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
margin-top: 8rpx !important;
}
.uni-easyinput__content-textarea {
font-size: 28rpx !important;
color: #333333 !important;
line-height: normal !important;
margin-top: 8rpx !important;
}
.uni-icons {
font-size: 40rpx !important;
}
.uni-icons {
font-size: 40rpx !important;
}
.is-textarea-icon {
margin-top: 22rpx;
}
.is-textarea-icon {
margin-top: 22rpx;
}
.is-disabled {
color: #333333;
}
}
.is-disabled {
color: #333333;
}
}
.default-box {
width: 100%;
box-sizing: border-box;
height: 100rpx;
.default-box {
width: 100%;
box-sizing: border-box;
height: 100rpx;
.default-box-title {
font-size: 28rpx;
color: #333333;
line-height: normal;
}
}
.default-box-title {
font-size: 28rpx;
color: #333333;
line-height: normal;
}
}
.footer-box {
.save-btn {
width: 710rpx;
height: 80rpx;
border-radius: 40rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
color: $white;
}
.footer-box {
.save-btn {
width: 710rpx;
height: 80rpx;
border-radius: 40rpx;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
color: $white;
}
.cancel-btn {
width: 710rpx;
height: 80rpx;
border-radius: 40rpx;
background: var(--ui-BG);
}
}
</style>
.cancel-btn {
width: 710rpx;
height: 80rpx;
border-radius: 40rpx;
background: var(--ui-BG);
}
}
</style>

View File

@ -1,143 +1,165 @@
<!-- 收件地址列表 -->
<template>
<s-layout title="收货地址" :bgStyle="{ color: '#FFF' }">
<view v-if="state.list.length">
<s-address-item hasBorderBottom v-for="item in state.list" :key="item.id" :item="item"
@tap="onSelect(item)" />
</view>
<s-layout :bgStyle="{ color: '#FFF' }" title="收货地址">
<view v-if="state.list.length">
<s-address-item
hasBorderBottom
v-for="item in state.list"
:key="item.id"
:item="item"
@tap="onSelect(item)"
/>
</view>
<su-fixed bottom placeholder>
<view class="footer-box ss-flex ss-row-between ss-p-20">
<!-- 微信小程序和微信H5 -->
<button v-if="['WechatMiniProgram', 'WechatOfficialAccount'].includes(sheep.$platform.name)"
@tap="importWechatAddress"
class="border ss-reset-button sync-wxaddress ss-m-20 ss-flex ss-row-center ss-col-center">
<text class="cicon-weixin ss-p-r-10" style="color: #09bb07; font-size: 40rpx"></text>
导入微信地址
</button>
<button class="add-btn ss-reset-button ui-Shadow-Main"
@tap="sheep.$router.go('/pages/user/address/edit')">
新增收货地址
</button>
</view>
</su-fixed>
<s-empty v-if="state.list.length === 0 && !state.loading" text="暂无收货地址" icon="/static/data-empty.png" />
</s-layout>
<su-fixed bottom placeholder>
<view class="footer-box ss-flex ss-row-between ss-p-20">
<!-- 微信小程序和微信H5 -->
<button
v-if="['WechatMiniProgram', 'WechatOfficialAccount'].includes(sheep.$platform.name)"
@tap="importWechatAddress"
class="border ss-reset-button sync-wxaddress ss-m-20 ss-flex ss-row-center ss-col-center"
>
<text class="cicon-weixin ss-p-r-10" style="color: #09bb07; font-size: 40rpx"></text>
导入微信地址
</button>
<button
class="add-btn ss-reset-button ui-Shadow-Main"
@tap="sheep.$router.go('/pages/user/address/edit')"
>
新增收货地址
</button>
</view>
</su-fixed>
<s-empty
v-if="state.list.length === 0 && !state.loading"
text="暂无收货地址"
icon="/static/data-empty.png"
/>
</s-layout>
</template>
<script setup>
import { reactive, onBeforeMount } from 'vue';
import { onShow } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import { isEmpty } from 'lodash';
import { onBeforeMount, reactive } from 'vue';
import { onLoad, onShow } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import { isEmpty } from 'lodash-es';
import AreaApi from '@/sheep/api/system/area';
import AddressApi from '@/sheep/api/member/address';
const state = reactive({
list: [], //
loading: true,
});
const state = reactive({
list: [], //
loading: true,
openType: '', //
});
//
const onSelect = (addressInfo) => {
uni.$emit('SELECT_ADDRESS', {
addressInfo,
});
sheep.$router.back();
};
//
const onSelect = (addressInfo) => {
if (state.openType !== 'select'){ //
return
}
uni.$emit('SELECT_ADDRESS', {
addressInfo,
});
sheep.$router.back();
};
//
// TODO
function importWechatAddress() {
let wechatAddress = {};
// #ifdef MP
uni.chooseAddress({
success: (res) => {
wechatAddress = {
consignee: res.userName,
mobile: res.telNumber,
province_name: res.provinceName,
city_name: res.cityName,
district_name: res.countyName,
address: res.detailInfo,
region: '',
is_default: false,
};
if (!isEmpty(wechatAddress)) {
sheep.$router.go('/pages/user/address/edit', {
data: JSON.stringify(wechatAddress),
});
}
},
fail: (err) => {
console.log('%cuni.chooseAddress,调用失败', 'color:green;background:yellow');
},
});
// #endif
// #ifdef H5
sheep.$platform.useProvider('wechat').jssdk.openAddress({
success: (res) => {
wechatAddress = {
consignee: res.userName,
mobile: res.telNumber,
province_name: res.provinceName,
city_name: res.cityName,
district_name: res.countryName,
address: res.detailInfo,
region: '',
is_default: false,
};
if (!isEmpty(wechatAddress)) {
sheep.$router.go('/pages/user/address/edit', {
data: JSON.stringify(wechatAddress),
});
}
},
});
// #endif
}
//
function importWechatAddress() {
let wechatAddress = {};
// #ifdef MP
uni.chooseAddress({
success: (res) => {
wechatAddress = {
consignee: res.userName,
mobile: res.telNumber,
province_name: res.provinceName,
city_name: res.cityName,
district_name: res.countyName,
address: res.detailInfo,
region: '',
is_default: false,
};
if (!isEmpty(wechatAddress)) {
sheep.$router.go('/pages/user/address/edit', {
data: JSON.stringify(wechatAddress),
});
}
},
fail: (err) => {
console.log('%cuni.chooseAddress,调用失败', 'color:green;background:yellow');
},
});
// #endif
// #ifdef H5
sheep.$platform.useProvider('wechat').jssdk.openAddress({
success: (res) => {
wechatAddress = {
consignee: res.userName,
mobile: res.telNumber,
province_name: res.provinceName,
city_name: res.cityName,
district_name: res.countryName,
address: res.detailInfo,
region: '',
is_default: false,
};
if (!isEmpty(wechatAddress)) {
sheep.$router.go('/pages/user/address/edit', {
data: JSON.stringify(wechatAddress),
});
}
},
});
// #endif
}
onShow(async () => {
state.list = (await AddressApi.getAddressList()).data;
state.loading = false;
});
onLoad((option) => {
if (option.type) {
state.openType = option.type;
}
});
onBeforeMount(() => {
if (!!uni.getStorageSync('areaData')) {
return;
}
//
onShow(async () => {
state.list = (await AddressApi.getAddressList()).data;
state.loading = false;
});
onBeforeMount(() => {
if (!!uni.getStorageSync('areaData')) {
return;
}
//
AreaApi.getAreaTree().then((res) => {
if (res.code === 0) {
uni.setStorageSync('areaData', res.data);
}
});
});
if (res.code === 0) {
uni.setStorageSync('areaData', res.data);
}
});
});
</script>
<style lang="scss" scoped>
.footer-box {
.add-btn {
flex: 1;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 80rpx;
font-size: 30rpx;
font-weight: 500;
line-height: 80rpx;
color: $white;
position: relative;
z-index: 1;
}
.footer-box {
.add-btn {
flex: 1;
background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
border-radius: 80rpx;
font-size: 30rpx;
font-weight: 500;
line-height: 80rpx;
color: $white;
position: relative;
z-index: 1;
}
.sync-wxaddress {
flex: 1;
line-height: 80rpx;
background: $white;
border-radius: 80rpx;
font-size: 30rpx;
font-weight: 500;
color: $dark-6;
margin-right: 18rpx;
}
}
</style>
.sync-wxaddress {
flex: 1;
line-height: 80rpx;
background: $white;
border-radius: 80rpx;
font-size: 30rpx;
font-weight: 500;
color: $dark-6;
margin-right: 18rpx;
}
}
</style>

View File

@ -5,7 +5,8 @@
<!-- 头部 -->
<view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30">
<view class="header-left ss-flex ss-col-center ss-font-26">
<text class="goods-number ui-TC-Main ss-flex">{{ state.pagination.total }}</text> 件商品
<text class="goods-number ui-TC-Main ss-flex">{{ state.pagination.total }}</text> 件商品
</view>
<view class="header-right">
<button
@ -77,7 +78,8 @@
<view class="footer-right">
<button
class="ss-reset-button ui-BG-Main-Gradient pay-btn ss-font-28 ui-Shadow-Main"
@tap="onCancel">
@tap="onCancel"
>
取消收藏
</button>
</view>
@ -100,9 +102,9 @@
import sheep from '@/sheep';
import { reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import _ from 'lodash';
import _ from 'lodash-es';
import FavoriteApi from '@/sheep/api/product/favorite';
import { resetPagination } from '@/sheep/util';
import { resetPagination } from '@/sheep/helper/utils';
const sys_navBar = sheep.$platform.navbar;
@ -129,7 +131,7 @@
if (code !== 0) {
return;
}
state.pagination.list = _.concat(state.pagination.list, data.list)
state.pagination.list = _.concat(state.pagination.list, data.list);
state.pagination.total = data.total;
state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
}
@ -174,7 +176,7 @@
//
function loadMore() {
if (state.loadStatus === 'noMore') {
return
return;
}
state.pagination.pageNo++;
getData();

View File

@ -1,6 +1,6 @@
<!-- 商品浏览记录 -->
<template>
<s-layout title="我的足迹" :bgStyle="{ color: '#f2f2f2' }">
<s-layout :bgStyle="{ color: '#f2f2f2' }" title="我的足迹">
<view class="cart-box ss-flex ss-flex-col ss-row-between">
<!-- 头部 -->
<view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30">
@ -81,11 +81,13 @@
</view>
<view class="footer-right ss-flex">
<button
:class="['ss-reset-button pay-btn ss-font-28 ',
{
'ui-BG-Main-Gradient': state.selectedSpuIdList.length > 0,
'ui-Shadow-Main': state.selectedSpuIdList.length > 0
}]"
:class="[
'ss-reset-button pay-btn ss-font-28 ',
{
'ui-BG-Main-Gradient': state.selectedSpuIdList.length > 0,
'ui-Shadow-Main': state.selectedSpuIdList.length > 0,
},
]"
@tap="onDelete"
>
删除足迹
@ -120,9 +122,9 @@
import sheep from '@/sheep';
import { reactive } from 'vue';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import _ from 'lodash';
import SpuHistoryApi from "@/sheep/api/product/history";
import {cloneDeep} from "@/sheep/helper/utils";
import _ from 'lodash-es';
import SpuHistoryApi from '@/sheep/api/product/history';
import { cloneDeep } from '@/sheep/helper/utils';
const sys_navBar = sheep.$platform.navbar;
const pagination = {

View File

@ -0,0 +1,282 @@
<template>
<s-layout :bgStyle="{ color: '#FFF' }" title="选择自提门店">
<view class="storeBox" ref="container">
<view
class="storeBox-box"
v-for="(item, index) in state.storeList"
:key="index"
@tap="checked(item)"
>
<view class="store-img">
<image :src="item.logo" class="img" />
</view>
<view class="store-cent-left">
<view class="store-name">{{ item.name }}</view>
<view class="store-address line1">
{{ item.areaName }}{{ ', ' + item.detailAddress }}
</view>
</view>
<view class="row-right ss-flex-col ss-col-center">
<view>
<!-- #ifdef H5 -->
<a class="store-phone" :href="'tel:' + item.phone">
<view class="iconfont">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</view>
</a>
<!-- #endif -->
<!-- #ifdef MP -->
<view class="store-phone" @click="call(item.phone)">
<view class="iconfont">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</view>
</view>
<!-- #endif -->
</view>
<view class="store-distance ss-flex ss-row-center" @tap.stop="showMaoLocation(item)">
<text class="addressTxt" v-if="item.distance">
距离{{ item.distance.toFixed(2) }}千米
</text>
<text class="addressTxt" v-else></text>
<view class="iconfont">
<view class="ss-rest-button">
<text class="_icon-forward" />
</view>
</view>
</view>
</view>
</view>
</view>
</s-layout>
</template>
<script setup>
import DeliveryApi from '@/sheep/api/trade/delivery';
import { onMounted, reactive } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
import sheep from '@/sheep';
const LONGITUDE = 'user_longitude';
const LATITUDE = 'user_latitude';
const state = reactive({
loaded: false,
loading: false,
storeList: [],
system_store: {},
locationShow: false,
user_latitude: 0,
user_longitude: 0,
});
const call = (phone) => {
uni.makePhoneCall({
phoneNumber: phone,
});
};
const selfLocation = () => {
// #ifdef H5
const jsWxSdk = sheep.$platform.useProvider('wechat').jsWxSdk;
if (jsWxSdk.isWechat()) {
jsWxSdk.getLocation((res) => {
state.user_latitude = res.latitude;
state.user_longitude = res.longitude;
uni.setStorageSync(LATITUDE, res.latitude);
uni.setStorageSync(LONGITUDE, res.longitude);
getList();
});
} else {
// #endif
uni.getLocation({
type: 'gcj02',
success: (res) => {
try {
state.user_latitude = res.latitude;
state.user_longitude = res.longitude;
uni.setStorageSync(LATITUDE, res.latitude);
uni.setStorageSync(LONGITUDE, res.longitude);
} catch (e) {
console.error(e);
}
getList();
},
complete: () => {
getList();
},
});
// #ifdef H5
}
// #endif
};
const showMaoLocation = (e) => {
// #ifdef H5
const jsWxSdk = sheep.$platform.useProvider('wechat').jsWxSdk;
if (jsWxSdk.isWechat()) {
jsWxSdk.openLocation({
latitude: Number(e.latitude),
longitude: Number(e.longitude),
name: e.name,
address: `${e.areaName}-${e.detailAddress}`,
});
} else {
// #endif
uni.openLocation({
latitude: Number(e.latitude),
longitude: Number(e.longitude),
name: e.name,
address: `${e.areaName}-${e.detailAddress}`,
success: function () {
console.log('success');
},
});
// #ifdef H5
}
// #endif
};
/**
* 选中门店
*/
const checked = (addressInfo) => {
uni.$emit('SELECT_PICK_UP_INFO', {
addressInfo,
});
sheep.$router.back();
};
/**
* 获取门店列表数据
*/
const getList = async () => {
if (state.loading || state.loaded) {
return;
}
state.loading = true;
const { data, code } = await DeliveryApi.getDeliveryPickUpStoreList({
latitude: state.user_latitude,
longitude: state.user_longitude,
});
if (code !== 0) {
return;
}
state.loading = false;
state.storeList = data;
};
onMounted(() => {
if (state.user_latitude && state.user_longitude) {
getList();
} else {
selfLocation();
getList();
}
});
onLoad(() => {
try {
state.user_latitude = uni.getStorageSync(LATITUDE);
state.user_longitude = uni.getStorageSync(LONGITUDE);
} catch (e) {
console.error(e);
}
});
</script>
<style lang="scss" scoped>
.line1 {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.geoPage {
position: fixed;
width: 100%;
height: 100%;
top: 0;
z-index: 10000;
}
.storeBox {
width: 100%;
background-color: #fff;
padding: 0 30rpx;
}
.storeBox-box {
width: 100%;
height: auto;
display: flex;
align-items: center;
padding: 23rpx 0;
justify-content: space-between;
border-bottom: 1px solid #eee;
}
.store-cent {
display: flex;
align-items: center;
width: 80%;
}
.store-cent-left {
//width: 45%;
flex: 2;
}
.store-img {
flex: 1;
width: 120rpx;
height: 120rpx;
border-radius: 6rpx;
margin-right: 22rpx;
}
.store-img .img {
width: 100%;
height: 100%;
}
.store-name {
color: #282828;
font-size: 30rpx;
margin-bottom: 22rpx;
font-weight: 800;
}
.store-address {
color: #666666;
font-size: 24rpx;
}
.store-phone {
width: 50rpx;
height: 50rpx;
color: #fff;
border-radius: 50%;
display: block;
text-align: center;
line-height: 48rpx;
background-color: #e83323;
margin-bottom: 22rpx;
text-decoration: none;
}
.store-distance {
font-size: 22rpx;
color: #e83323;
}
.iconfont {
font-size: 20rpx;
}
.row-right {
flex: 2;
//display: flex;
//flex-direction: column;
//align-items: flex-end;
//width: 33.5%;
}
</style>

View File

@ -15,7 +15,7 @@
class="content-img"
isPreview
:current="0"
:src="state.model?.avatar"
:src="state.model?.avatar || sheep.$url.static('/static/img/shop/default_avatar.png')"
:height="160"
:width="160"
:radius="80"
@ -26,7 +26,8 @@
<button
class="ss-reset-button avatar-action-btn"
open-type="chooseAvatar"
@chooseavatar="onChooseAvatar">
@chooseavatar="onChooseAvatar"
>
修改
</button>
<!-- #endif -->
@ -154,10 +155,7 @@
</view>
<view class="ss-flex ss-col-center">
<view class="info ss-flex ss-col-center" v-if="state.thirdInfo">
<image
class="avatar ss-m-r-20"
:src="sheep.$url.cdn(state.thirdInfo.avatar)"
/>
<image class="avatar ss-m-r-20" :src="sheep.$url.cdn(state.thirdInfo.avatar)" />
<text class="name">{{ state.thirdInfo.nickname }}</text>
</view>
<view class="bind-box ss-m-l-20">
@ -185,10 +183,13 @@
<script setup>
import { computed, reactive, onBeforeMount } from 'vue';
import sheep from '@/sheep';
import { clone } from 'lodash';
import { clone } from 'lodash-es';
import { showAuthModal } from '@/sheep/hooks/useModal';
import FileApi from '@/sheep/api/infra/file';
import UserApi from '@/sheep/api/member/user';
import {
chooseAndUploadFile,
uploadFilesFromPath,
} from '@/sheep/components/s-uploader/choose-and-upload-file';
const state = reactive({
model: {}, //
@ -198,14 +199,15 @@
const placeholderStyle = 'color:#BBBBBB;font-size:28rpx;line-height:normal';
const sexRadioMap = [{
const sexRadioMap = [
{
name: '男',
value: '1',
},
{
name: '女',
value: '2',
}
},
];
const userInfo = computed(() => sheep.$store('user').userInfo);
@ -221,28 +223,22 @@
};
//
function onChooseAvatar(e) {
async function onChooseAvatar(e) {
debugger;
const tempUrl = e.detail.avatarUrl || '';
uploadAvatar(tempUrl);
if (!tempUrl) return;
const files = await uploadFilesFromPath(tempUrl);
if (files.length > 0) {
state.model.avatar = files[0].url;
}
}
//
function onChangeAvatar() {
uni.chooseImage({
success: async (chooseImageRes) => {
const tempUrl = chooseImageRes.tempFilePaths[0];
await uploadAvatar(tempUrl);
},
});
}
//
async function uploadAvatar(tempUrl) {
if (!tempUrl) {
return;
async function onChangeAvatar() {
const files = await chooseAndUploadFile({ type: 'image' });
if (files.length > 0) {
state.model.avatar = files[0].url;
}
let { data } = await FileApi.uploadFile(tempUrl);
state.model.avatar = data;
}
//
@ -279,7 +275,7 @@
//
async function onSubmit() {
const { code } = await UserApi.updateUser({
const { code } = await UserApi.updateUser({
avatar: state.model.avatar,
nickname: state.model.nickname,
sex: state.model.sex,

View File

@ -13,7 +13,9 @@
/>
</view>
<view class="ss-flex ss-row-between ss-col-center ss-m-t-64">
<view class="money-num">{{ state.showMoney ? fen2yuan(userWallet.balance) : '*****' }}</view>
<view class="money-num">{{
state.showMoney ? fen2yuan(userWallet.balance) : '*****'
}}</view>
<button class="ss-reset-button topup-btn" @tap="sheep.$router.go('/pages/pay/recharge')">
充值
</button>
@ -24,7 +26,12 @@
<su-sticky>
<!-- 统计 -->
<view class="filter-box ss-p-x-30 ss-flex ss-col-center ss-row-between">
<uni-datetime-picker v-model="state.data" type="daterange" @change="onChangeTime" :end="state.today">
<uni-datetime-picker
v-model="state.data"
type="daterange"
@change="onChangeTime"
:end="state.today"
>
<button class="ss-reset-button date-btn">
<text>{{ dateFilterText }}</text>
<text class="cicon-drop-down ss-seldate-icon"></text>
@ -82,10 +89,10 @@
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import sheep from '@/sheep';
import dayjs from 'dayjs';
import _ from 'lodash';
import _ from 'lodash-es';
import PayWalletApi from '@/sheep/api/pay/wallet';
import { fen2yuan } from '@/sheep/hooks/useGoods';
import { resetPagination } from '@/sheep/util';
import { resetPagination } from '@/sheep/helper/utils';
const headerBg = sheep.$url.css('/static/img/shop/user/wallet_card_bg.png');
@ -98,7 +105,7 @@
list: [],
total: 0,
pageNo: 1,
pageSize: 8
pageSize: 8,
},
summary: {
totalIncome: 0,
@ -155,7 +162,7 @@
//
async function getSummary() {
const { data, code } = await PayWalletApi.getWalletTransactionSummary({
'createTime': [state.date[0] + ' 00:00:00', state.date[1] + ' 23:59:59'],
createTime: [state.date[0] + ' 00:00:00', state.date[1] + ' 23:59:59'],
});
if (code !== 0) {
return;
@ -226,8 +233,7 @@
position: absolute;
top: 0;
left: 0;
background: v-bind(headerBg)
no-repeat;
background: v-bind(headerBg) no-repeat;
pointer-events: none;
}

View File

@ -24,19 +24,24 @@
<su-sticky :customNavHeight="sys_navBar">
<!-- 统计 -->
<view class="filter-box ss-p-x-30 ss-flex ss-col-center ss-row-between">
<uni-datetime-picker v-model="state.date" type="daterange" @change="onChangeTime" :end="state.today">
<button class="ss-reset-button date-btn">
<text>{{ dateFilterText }}</text>
<text class="cicon-drop-down ss-seldate-icon"></text>
</button>
</uni-datetime-picker>
<uni-datetime-picker
v-model="state.date"
type="daterange"
@change="onChangeTime"
:end="state.today"
>
<button class="ss-reset-button date-btn">
<text>{{ dateFilterText }}</text>
<text class="cicon-drop-down ss-seldate-icon"></text>
</button>
</uni-datetime-picker>
<!-- TODO 芋艿优化 -->
<!-- <view class="total-box">-->
<!-- <view class="ss-m-b-10">总收入{{ state.pagination.income }}</view>-->
<!-- <view>总支出{{ -state.pagination.expense }}</view>-->
<!-- </view>-->
</view>
<!-- TODO 芋艿钱包-优化展示一下 -->
<!-- <view class="total-box">-->
<!-- <view class="ss-m-b-10">总收入{{ state.pagination.income }}</view>-->
<!-- <view>总支出{{ -state.pagination.expense }}</view>-->
<!-- </view>-->
</view>
<su-tabs
:list="tabMaps"
@change="onChange"
@ -83,10 +88,10 @@
import sheep from '@/sheep';
import { onLoad, onReachBottom } from '@dcloudio/uni-app';
import { computed, reactive } from 'vue';
import _ from 'lodash';
import _ from 'lodash-es';
import dayjs from 'dayjs';
import PointApi from '@/sheep/api/member/point';
import { resetPagination } from '@/sheep/util';
import { resetPagination } from '@/sheep/helper/utils';
const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
const userInfo = computed(() => sheep.$store('user').userInfo);
@ -274,4 +279,4 @@
}
}
}
</style>
</style>

11
sheep/api/index.js Normal file
View File

@ -0,0 +1,11 @@
// 目的解决微信小程序的「代码质量」在「JS 文件」提示:主包内,不应该存在主包未使用的 JS 文件
const files = import.meta.glob('./*/*.js', { eager: true });
let api = {};
Object.keys(files).forEach((key) => {
api = {
...api,
[key.replace(/(.*\/)*([^.]+).*/gi, '$2')]: files[key].default,
};
});
export default api;

View File

@ -1,10 +1,9 @@
import { baseUrl, apiPath } from '@/sheep/config';
import { baseUrl, apiPath, tenantId } from '@/sheep/config';
import request, { getAccessToken } from '@/sheep/request';
const FileApi = {
// 上传文件
uploadFile: (file) => {
// TODO 芋艿:访问令牌的接入;
const token = uni.getStorageSync('token');
uploadFile: (file, directory = '') => {
uni.showLoading({
title: '上传中',
});
@ -14,10 +13,12 @@ const FileApi = {
filePath: file,
name: 'file',
header: {
// Accept: 'text/json',
Accept : '*/*',
'tenant-id' :'1',
// Authorization: 'Bearer test247',
Accept: '*/*',
'tenant-id': tenantId,
Authorization: 'Bearer ' + getAccessToken(),
},
formData: {
directory,
},
success: (uploadFileRes) => {
let result = JSON.parse(uploadFileRes.data);
@ -40,6 +41,27 @@ const FileApi = {
});
});
},
// 获取文件预签名地址
getFilePresignedUrl: (name, directory) => {
return request({
url: '/infra/file/presigned-url',
method: 'GET',
params: {
name,
directory,
},
});
},
// 创建文件
createFile: (data) => {
return request({
url: '/infra/file/create', // 请求的 URL
method: 'POST', // 请求方法
data: data, // 要发送的数据
});
},
};
export default FileApi;

17
sheep/api/infra/tenant.js Normal file
View File

@ -0,0 +1,17 @@
import request from '@/sheep/request';
/**
* 通过网站域名获取租户信息
* @param {string} website - 网站域名
* @returns {Promise<Object>} 租户信息
*/
export function getTenantByWebsite(website) {
return request({
url: '/system/tenant/get-by-website',
method: 'GET',
params: { website },
custom: {
isToken: false, // 避免登录情况下,跨租户访问被拦截
},
});
}

View File

@ -56,10 +56,10 @@ const AuthUtil = {
url: '/member/auth/refresh-token',
method: 'POST',
params: {
refreshToken
refreshToken,
},
custom: {
loading: false, // 不用加载中
showLoading: false, // 不用加载中
showError: false, // 不展示错误提示
},
});
@ -103,7 +103,7 @@ const AuthUtil = {
data: {
phoneCode,
loginCode,
state
state,
},
custom: {
showSuccess: true,
@ -118,13 +118,13 @@ const AuthUtil = {
url: '/member/auth/create-weixin-jsapi-signature',
method: 'POST',
params: {
url
url,
},
custom: {
showError: false,
showLoading: false,
},
})
});
},
//
};

View File

@ -49,6 +49,28 @@ const SocialApi = {
},
});
},
// 获取订阅消息模板列表
getSubscribeTemplateList: () =>
request({
url: '/member/social-user/get-subscribe-template-list',
method: 'GET',
custom: {
showError: false,
showLoading: false,
},
}),
// 获取微信小程序码
getWxaQrcode: async (path, query) => {
return await request({
url: '/member/social-user/wxa-qrcode',
method: 'POST',
data: {
scene: query,
path,
checkPath: false, // TODO 开发环境暂不检查 path 是否存在
},
});
},
};
export default SocialApi;

View File

@ -1,6 +1,6 @@
import request from '@/sheep/request';
// TODO 芋艿:小程序直播还不支持
// TODO 芋艿:【直播】小程序直播还不支持
export default {
//小程序直播
mplive: {
@ -10,12 +10,12 @@ export default {
method: 'GET',
params: {
ids: ids.join(','),
}
},
}),
getMpLink: () =>
request({
url: 'app/mplive/getMpLink',
method: 'GET'
method: 'GET',
}),
},
};

View File

@ -1,14 +0,0 @@
import request from '@/sheep/request';
// TODO 芋艿:暂不支持 socket 聊天
export default {
// 获取聊天token
unifiedToken: () =>
request({
url: 'unifiedToken',
custom: {
showError: false,
showLoading: false,
},
}),
};

View File

@ -1,10 +0,0 @@
const files = import.meta.globEager('./*.js');
let api = {};
Object.keys(files).forEach((key) => {
api = {
...api,
[key.replace(/(.*\/)*([^.]+).*/gi, '$2')]: files[key].default,
};
});
export default api;

View File

@ -1,32 +1,6 @@
import request from '@/sheep/request';
import { baseUrl, apiPath } from '@/sheep/config';
export default {
// 微信相关
wechat: {
// 小程序订阅消息
subscribeTemplate: (params) =>
request({
url: 'third/wechat/subscribeTemplate',
method: 'GET',
params: {
platform: 'miniProgram',
},
custom: {
showError: false,
showLoading: false,
},
}),
// 获取微信小程序码
getWxacode: (path) =>
`${baseUrl}${apiPath}third/wechat/wxacode?platform=miniProgram&payload=${encodeURIComponent(
JSON.stringify({
path,
}),
)}`,
},
// 苹果相关
apple: {
// 第三方登录

View File

@ -2,11 +2,11 @@ import request from '@/sheep/request';
const PayOrderApi = {
// 获得支付订单
getOrder: (id) => {
getOrder: (id, sync) => {
return request({
url: '/pay/order/get',
method: 'GET',
params: { id }
params: { id, sync },
});
},
// 提交支付订单
@ -14,9 +14,9 @@ const PayOrderApi = {
return request({
url: '/pay/order/submit',
method: 'POST',
data
data,
});
}
},
};
export default PayOrderApi;

Some files were not shown because too many files have changed in this diff Show More