RIceWqy 3 years ago
commit d9e2784c1b

4
.gitignore vendored

@ -0,0 +1,4 @@
/node_modules/*
/unpackage/*
!/unpackage/res/*
/.idea/*

@ -0,0 +1,16 @@
{ // launch.json 配置了启动调试时相关设置,configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
"version": "0.0",
"configurations": [{
"default" :
{
"launchtype" : "local"
},
"mp-weixin" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
}

@ -0,0 +1,15 @@
<script>
import {
init
} from "@/shopro";
export default {
onLaunch(options) {
init(options);
}
};
</script>
<style lang="scss">
@import 'static/style/index.scss';
</style>

@ -0,0 +1,236 @@
vue3版本正式发布,持续更新中![官网: https://www.shopro.top](https://www.shopro.top)
### :sparkles: 开源不易,给个star吧~ 问题反馈交流:648050824
[官方地址: https://www.shopro.top](https://www.shopro.top)
[插件地址: https://ext.dcloud.net.cn/plugin?id=10180](https://ext.dcloud.net.cn/plugin?id=10180)
[vue2码云仓库: https://gitee.com/itmonkey-cn/shopro.git](https://gitee.com/itmonkey-cn/shopro.git)
[vue3码云仓库: https://gitee.com/sheepjs/shopro-uniapp](https://gitee.com/sheepjs/shopro-uniapp)
[github仓库:https://github.com/ITmonkey-cn/shopro.git](https://github.com/ITmonkey-cn/shopro.git)
# 部分页面展示
![1](https://images.gitee.com/uploads/images/2020/0710/162852_71fb79d2_2264724.jpeg "b1.jpg")
# Shopro使用说明
1. 首次安装需要 npm i 加载相关依赖
2. 基于uni-app,需要下载hbuilderX进行编译
3. manifest.json中配置相关信息
# Shopro演示站
| ![微信小程序码](https://shopro-1253949872.cos.ap-beijing.myqcloud.com/dcloud/wx_code.jpg "微信小程序码") | ![H5](https://shopro-1253949872.cos.ap-beijing.myqcloud.com/dcloud/h5new.png "H5.png") | ![apk](https://shopro-1253949872.cos.ap-beijing.myqcloud.com/dcloud/apk_code.png "apk_code.png") |
|------------------------------------------------- | ---------------------------------------- | ---------------------------------- |
| 微信小程序 | H5二维码 | 安卓apk (普通浏览器扫描) |
# 更新日志
## 更新vue3版本
vue3版本正式上线!将持续更新+开源,欢迎下载使用,望点赞+收藏~
[官方地址: https://www.shopro.top](https://www.shopro.top)
[插件地址: https://ext.dcloud.net.cn/plugin?id=10180](https://ext.dcloud.net.cn/plugin?id=10180)
[vue2码云仓库: https://gitee.com/itmonkey-cn/shopro.git](https://gitee.com/itmonkey-cn/shopro.git)
[vue3码云仓库: https://gitee.com/sheepjs/shopro-uniapp](https://gitee.com/sheepjs/shopro-uniapp)
[github仓库:https://github.com/ITmonkey-cn/shopro.git](https://github.com/ITmonkey-cn/shopro.git)
## V1.4.0(2023-02-24)
1. 修复积分记录上拉加载更多
2. 修复小程序分类左侧数据过多,滚动问题
3. 修复门店申请,选择地区,没有区级单位
4. 修复日志取值
5. 修复登录流程、优化自提时间不能选择当前时间之前
6. 修复支付支持 v3
7. 修复隐私协议框跳转问题
8. 修复导航栏、状态栏
9. 修复小程序上传报错
10. 修复小程序消息通知偶发订阅报错问题
11. 修复服务商拉起失败问题
12. 修复扫码核销、核销总码
13. 优化app登录\微信小程序登录
## V1.3.8(2022-01-08)
1. 新增商品购买输入数量
2. 新增商品评价内容过滤
3. 新增充值到余额
4. 新增消费送积分
5. 修复首页拼团组件标题取值错误问题
6. 修复物流列表真机样式错乱问题
7. 修复某些ios机型优惠券不显示
8. 修复自定义底部导航分享后,返回首页底部导航不显示问题
9. 修复商品评论图片预览问题
## V1.3.7(2021-10-18)
1. 新增 商品订单申请开具发票功能
2. 修复 用户协议,隐私协议,门店申请协议,自提协议等富文本跳转
3. 修复 小程序端弹窗图片,非https协议无法保存的问题
4. 修复 用户信息页面刷新时数据丢失问题
5. 修复 用户信息页面修改密码点击无效问题
6. 修复 客服页面刷新,切换为默认客服问题
7. 修复 客服中,商品和订单,多次打开时列表内容重复问题
8. 修复 登录、注册、找回密码,获取验证码按钮未显示问题
9. 修复 轮播图组件参数
10. 优化 小程序端自提商品二维码不稳定问题
11. 优化 商家中心,订单金额的统计规则
12. 优化 钱包提现规则
13. 优化 商品列表搜索内容,存入搜索历史记录
14. 优化 商品详情优惠券 显示已领取状态
## V1.3.6(2021-8-30)
1. 优化 小程序性能问题,更换router底层依赖版本,精简店铺装修组件
2. 修复 登录卡片隐藏后,需要点击两次才能调起的问题
3. 修复 微信小程序,进入直播内容页面,跳转商品详情后,无法返回上一级的问题
4. 修复搜索页跳转商品列表,商品卡片偶尔重复问题
5. 修复门店中心的订单详情,下单时间NaN问题
6. 修复微信小程序真机测试时,自提商品核销码生成不稳定的问题
7. 修复微信小程序,登录错误,显示The given payload报错的问题。
8. 修复ios部分机型,首页装修优惠券背景空白问题
9. 重构 入口文件和shopro启动流程
10. 重构 vuex相关代码,拆分精简数据。
11. 优化 优惠券相关逻辑和样式
12. 修复 门店禁用后,前台还展示的问题
13. 优化 购物车逻辑
14. 优化 瀑布流排版不稳定(左右排列错误)
15. 优化 日历无法快速切换年份
16. 优化 选择商品规格组件
17. 优化 分享海报
## V1.3.2(2021-6-30)
1. 修复秒杀卡片百分比改为件数,统一百分比计算
2. 修复门店地址信息过长会被遮盖
3. 修复苹果6s小程序端,分销中心弹窗不出来
4. 修复门店订单详情,单笔商品数量过多,底部发货按钮顶起
5. 修复商品卡片,添加添加购物车库存无限制问题
6. 修复小程序端秒杀卡片,跳转详情问题
7. 优化小程序端,确认订单优惠金额样式异常问题
8. 优化海报,首页banner裁剪方式
9. 优化地址定位问题,改为选择位置
10. 优化确认订单,配送自提点的默认手机号问题
11. 优化购物车失效商品拦截,
12. 优化确认订单配送商品无经纬度等错误异常
13. 优化门店申请,位置选择
14. 优化瀑布流左右偶发不均问题
## V1.3.1(2021-6-17)
1. 优化自提点协议勾选检测。
2. 修复无自提门店,选择自提方式报错
3. 修复商品规格滑动问题
4. 修复一级分类,快速购买,持续loading问题
5. 修复分销中心分享链接,无法复制问题
6. 修复确认订单商品计数问题
7. 优化app端底部安全区域问题
8. 新增app端苹果登录
9. 更多细节体验优化,可扫码体验~
## V1.3.0(2021-6-8)
1. 重构所有页面,重新设计了样式,全面使用uview替换color-ui,感谢uview团队!
2. 优化页面加载速度,重写初始化流程,兼容H5的hash模式与history模式
3. 重写登录注册模块,自动登录注册、绑定/解绑 第三方授权登录信息,更新微信小程序最新的getUserProfile接口
4. 重构分享逻辑,分享信息使用状态管理,解决重复冗余加载,使用spm参数构造,支持添加自定义参数,精确的分享链路追踪(分享人、分享方式、页面、分享平台、受邀请人平台)解决了因小程序码参数苏的位数限制导致无法支持长页面和大用户ID被截断的问题。(之前的海报和分享卡片将作废)
5. 添加多提现方式(包含支付宝、微信企业付款、银行卡)
6. 重写海报模块
7. 更新了路由拦截和请求拦截器。
8. 重构App权限请求和加载速度
9. 默认使用原生底部导航,可修改代码实现自定义底部导航和后端对接
10. 更多重构细节体验优化,可扫码体验~
## V1.2.0(2021-1-12)
1. 新增官方客服
2. 新增分销中心,搭配后台强大的分销功能
3. 新增全局静态资源的服务地址
4. 新增uView UI 部分组件(特别鸣谢)
5. 优化IOS APP的网络权限验证流程
6. 优化海报的头像位置,适应小屏机型
7. 优化多选项卡切换的数据抖动问题
8. 优化H5微信支付的回调逻辑
7. 修复IOS App的webview内核,防止被Appstore审核被拒
8. 更多细节优化,可扫码体验~
## 更新功能部分截图
![V1.2.0功能](https://images.gitee.com/uploads/images/2021/0112/182440_a691a92b_2264724.png "some.png")
## V1.1.2(2020-10-15)
1. 新增:分享到朋友圈
2. 优化:商品详情页体验
3. 优化:分类页体验
4. 修复:商品购买计数器相关bug
5. 修复:小程序分享后空白页(适配新规则)
6. 修复:小程序自动更新版本报错
7. 其他:相关优化,问题修复。
## V1.0.7(2020-09-29)
1. 新增:多配送方式(到店自提、商家配送、自动发货)和对应详情列表页面
2. 新增:用户商家中心,门店切换,输码扫码核销,门店信息查看,发货,交易信息统计。
3. 新增:物流信息,对接快递鸟物流查询,多包裹列表。
4. 新增:商品售后,完善商品售后流程,服务单详情,售后进度查询和通知。
5. 新增:全局优惠券,优化优惠券领取使用相关。
6. 新增:部分页面下拉刷新,优化体验
7. 新增:第三方客服,跟进购物闭环。
8. 新增:微信,公众号地址导入。
9. 新增:多分类灵活配置,支持立即购买(类外卖)
10. 新增:app端,隐私协议相关。
11. 优化:海报升级体验
12. 优化: 富文本相关
14. 优化:小程序登录提示
15. 优化:首页推荐商品加载更多
16. 修复:低版本tabbar首次不渲染。
17. 修复:iphonX底部安全区域问题。
18. 修复:直播列表瀑布流排序问题
## 更新功能部分截图
![v1.0.7功能](https://images.gitee.com/uploads/images/2020/0929/161907_1de1deff_2264724.png "04.png")
## V1.0.6(2020-07-16)
1. 新增:底部导航购物车角标
2. 新增:单规格商品选择
3. 新增: 分享海报权限控制,必须得登录
4. 修复:自定义底部导航,带参报错
5. 修复:拼团商品数量选择限制
6. 修复:头部搜索为空查询。
7. 优化:我的拼团下拉刷新
8. 优化:商品下架,详情提醒
9. 以及后台相关修改优化。
## V1.0.5(2020-07-10)
1. 新增:自定义底部导航。后台返回底部导航数据,跳转任意页面,如果没有默认使用原生导航。
2. 新增:悬浮工具按钮。后台控制按钮显示页面, 工具按钮可以是弹窗,也可跳转页面。
3. 新增: 广告弹窗。后台控制弹窗显示页面,单次或者多次。
4. 新增:小程序分包。优化了不能开发模式不能预览,相应速度。
5. 新增: easycom组件引入。减少繁琐的组件引入(控制台提示的分包建议并不合理,可以忽略)
6. 修复:支付宝部分退款问题
7. 修复:小程序用户登录,无感登录支付
8. 修复:商品分类优化
9. 修复:小程序端授权登录session_key时效问题
10. 优化部分UI界面性能体验,更多后台新增功能可去官网体验~
## V1.0.4(2020-06-09)
1.首次开源发布

@ -0,0 +1,3 @@
{
"prompt" : "none"
}

@ -0,0 +1,992 @@
<template>
<view class="cu-modal bottom-modal" :class="{ show: showAuth }" @tap="closeAuthModal" style="z-index: 10080;">
<view class="login-wrap cu-dialog form-wrap safe-area-inset-bottom" @tap.stop
style="border-radius: 20rpx 20rpx 0 0;">
<!-- 1.账号密码登录 -->
<view v-if="authType === 'accountLogin'">
<!-- 标题 -->
<view class="head-box u-m-b-60 u-flex-col ">
<view class="u-flex u-m-b-20">
<view class="head-title u-m-r-40 head-title-animation">账号登录</view>
<view class="head-title-active head-title-line" @tap="showAuthModal('smsLogin')">
短信登录
</view>
</view>
<view class="head-subtitle">如果未设置过密码,请点击忘记密码</view>
</view>
<view class="form-item u-border-bottom ">
<view class="item-content u-flex u-col-center">
<view class="item-title">账号</view>
<u-input class="u-m-r-10 u-flex-1" placeholder="请输入账号" @blur="checkValue($event, 'account')"
@input="checkValue($event, 'account')" :placeholderStyle="placeholderStyle"
v-model="form['accountLogin'].data.account" type="text"></u-input>
<button class="u-reset-button forgot-btn" @tap="showAuthModal('forgotPwd')">
忘记密码
</button>
</view>
<view class="message-error">
{{ form["accountLogin"].error.account || "" }}
</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">密码</view>
<u-input class="u-m-r-10 u-flex-1" placeholder="请输入密码" :placeholderStyle="placeholderStyle"
v-model="form['accountLogin'].data.password" type="password"
@blur="checkValue($event, 'password')" @input="checkValue($event, 'password')"></u-input>
<button class="u-reset-button login-btn-start" @tap="accountLoginSubmit">
登录
</button>
</view>
<view class="message-error">
{{ form["accountLogin"].error.password || "" }}
</view>
</view>
<button class="u-reset-button type-btn" @tap="showAuthModal('register')">
立即注册
</button>
</view>
<!-- 2.短信登录 -->
<view v-if="authType === 'smsLogin'">
<view class="head-box u-m-b-60">
<view class="u-flex u-m-b-20">
<view class="head-title-active u-m-r-40" @tap="showAuthModal('accountLogin')">
账号登录
</view>
<view class="head-title head-title-line head-title-animation">
短信登录
</view>
</view>
<view class="head-subtitle">未注册手机号请先点击下方立即注册</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">手机号</view>
<u-input class="u-m-r-10 u-flex-1" placeholder="请输入手机号" @blur="checkValue($event, 'mobile')"
@input="mobileInput($event, 'mobile')" :placeholderStyle="placeholderStyle"
v-model="form['smsLogin'].data.mobile" type="number"></u-input>
<button class="u-reset-button code-btn code-btn-start"
:disabled="!form['smsLogin'].data.isMobileEnd"
:class="{ 'code-btn-end': form['smsLogin'].data.isMobileEnd }"
@tap="getSmsCode('mobilelogin')">
{{ codeText }}
</button>
</view>
<view class="message-error">{{ form["smsLogin"].error.mobile || "" }}</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">验证码</view>
<u-input class="u-m-r-10 u-flex-1" @blur="checkValue($event, 'code')"
@input="checkValue($event, 'code')" placeholder="请输入验证码"
:placeholderStyle="placeholderStyle" v-model="form['smsLogin'].data.code"
type="number"></u-input>
<button class="u-reset-button login-btn-start" @tap="smsLoginSubmit">
登录
</button>
</view>
<view class="message-error">{{ form["smsLogin"].error.code || "" }}</view>
</view>
<button class="u-reset-button type-btn" @tap="showAuthModal('register')">
立即注册
</button>
</view>
<!-- 3.注册 -->
<view v-if="authType === 'register'">
<view class="head-box u-m-b-60">
<view class="head-title u-m-b-20">注册</view>
<view class="head-subtitle">请使用本人手机号完成注册</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">手机号</view>
<u-input class="u-m-r-10 u-flex-1" placeholder="请输入手机号" @blur="checkValue($event, 'mobile')"
@input="mobileInput($event, 'mobile')" :placeholderStyle="placeholderStyle"
v-model="form['register'].data.mobile" type="number"></u-input>
<button class="u-reset-button code-btn code-btn-start"
:disabled="!form['register'].data.isMobileEnd"
:class="{ 'code-btn-end': form['register'].data.isMobileEnd }"
@tap="getSmsCode('register')">
{{ codeText }}
</button>
</view>
<view class="message-error">{{ form["register"].error.mobile || "" }}</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">密码</view>
<u-input class="u-m-r-10 u-flex-1" @blur="checkValue($event, 'password')"
@input="checkValue($event, 'password')" placeholder="请输入密码"
:placeholderStyle="placeholderStyle" v-model="form['register'].data.password"
type="password"></u-input>
</view>
<view class="message-error">{{ form["register"].error.password || "" }}</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">验证码</view>
<u-input class="u-m-r-10 u-flex-1" @blur="checkValue($event, 'code')"
@input="checkValue($event, 'code')" placeholder="请输入验证码"
:placeholderStyle="placeholderStyle" v-model="form['register'].data.code"
type="number"></u-input>
<button class="u-reset-button login-btn-start" @tap="registerSubmit">
注册
</button>
</view>
<view class="message-error">{{ form["register"].error.code || "" }}</view>
</view>
<button v-if="!isLogin" class="u-reset-button type-btn" @tap="showAuthModal('accountLogin')">
返回登录
</button>
</view>
<!-- 4.忘记密码 -->
<view v-if="authType === 'forgotPwd'">
<view class="head-box u-m-b-60">
<view class="head-title u-m-b-20">忘记密码</view>
<view class="head-subtitle">为了您的账号安全,修改密码前请先进行安全验证</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">手机号</view>
<u-input class="u-m-r-10 u-flex-1" @blur="checkValue($event, 'mobile')"
@input="mobileInput($event, 'mobile')" :placeholderStyle="placeholderStyle"
v-model="form['forgotPwd'].data.mobile" type="number"></u-input>
<button class="u-reset-button code-btn code-btn-start"
:disabled="!form['forgotPwd'].data.isMobileEnd"
:class="{ 'code-btn-end': form['forgotPwd'].data.isMobileEnd }"
@tap="getSmsCode('resetpwd')">
{{ codeText }}
</button>
</view>
<view class="message-error">{{ form["forgotPwd"].error.mobile || "" }}</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">验证码</view>
<u-input class="u-m-r-10 u-flex-1" @blur="checkValue($event, 'code')"
@input="checkValue($event, 'code')" placeholder="请输入验证码"
:placeholderStyle="placeholderStyle" v-model="form['forgotPwd'].data.code"
type="number"></u-input>
</view>
<view class="message-error">{{ form["forgotPwd"].error.code || "" }}</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">密码</view>
<u-input class="u-m-r-10 u-flex-1" @blur="checkValue($event, 'password')"
@input="checkValue($event, 'password')" placeholder="请输入密码"
:placeholderStyle="placeholderStyle" v-model="form['forgotPwd'].data.password"
type="password"></u-input>
<button class="u-reset-button login-btn-start" @tap="forgotPwdSubmit">
确认
</button>
</view>
<view class="message-error">{{ form["forgotPwd"].error.password || "" }}</view>
</view>
<button v-if="!isLogin" class="u-reset-button type-btn" @tap="showAuthModal('accountLogin')">
返回登录
</button>
</view>
<!-- 5.绑定手机号 -->
<view v-if="authType === 'bindMobile'">
<view class="head-box u-m-b-60">
<view class="head-title u-m-b-20">绑定手机号</view>
<view class="head-subtitle">为了您的账号安全,请绑定手机号</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">手机号</view>
<u-input class="u-m-r-10 u-flex-1" @blur="checkValue($event, 'mobile')"
@input="mobileInput($event, 'mobile')" placeholder="请输入手机号"
:placeholderStyle="placeholderStyle" v-model="form['bindMobile'].data.mobile"
type="number"></u-input>
<button class="u-reset-button code-btn code-btn-start"
:disabled="!form['bindMobile'].data.isMobileEnd"
:class="{ 'code-btn-end': form['bindMobile'].data.isMobileEnd }"
@tap="getSmsCode('changemobile')">
{{ codeText }}
</button>
</view>
<view class="message-error">{{ form["bindMobile"].error.mobile || "" }}</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">验证码</view>
<u-input class="u-m-r-10 u-flex-1" @blur="checkValue($event, 'code')"
@input="checkValue($event, 'code')" placeholder="请输入验证码"
:placeholderStyle="placeholderStyle" v-model="form['bindMobile'].data.code"
type="number"></u-input>
<button class="u-reset-button login-btn-start" @tap="bindMobileSubmit">
立即绑定
</button>
</view>
<view class="message-error">{{ form["bindMobile"].error.code || "" }}</view>
</view>
</view>
<!-- 6.修改密码 -->
<view v-if="authType === 'changePwd'">
<view class="head-box u-m-b-60">
<view class="head-title u-m-b-20">修改密码</view>
<view class="head-subtitle"></view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">旧密码</view>
<u-input class="u-m-r-10 u-flex-1" @blur="checkValue($event, 'oldPassword')"
@input="checkValue($event, 'oldPassword')" placeholder="请输入旧密码"
:placeholderStyle="placeholderStyle" v-model="form['changePwd'].data.oldPassword"
type="password"></u-input>
</view>
<view class="message-error">
{{ form["changePwd"].error.oldPassword || "" }}
</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">新密码</view>
<u-input class="u-m-r-10 u-flex-1" @blur="checkValue($event, 'newPassword')"
@input="checkValue($event, 'newPassword')" placeholder="请输入新密码"
:placeholderStyle="placeholderStyle" v-model="form['changePwd'].data.newPassword"
type="password"></u-input>
</view>
<view class="message-error">
{{ form["changePwd"].error.newPassword || "" }}
</view>
</view>
<view class="form-item u-border-bottom">
<view class="item-content u-flex u-col-center">
<view class="item-title">确认密码</view>
<u-input class="u-m-r-10 u-flex-1" @blur="checkValue($event, 'reNewPassword')"
@input="checkValue($event, 'reNewPassword')" placeholder="再次输入新密码"
:placeholderStyle="placeholderStyle" v-model="form['changePwd'].data.reNewPassword"
type="password"></u-input>
</view>
<view class="message-error">
{{ form["changePwd"].error.reNewPassword || "" }}
</view>
</view>
<view class="editPwd-btn-box u-m-t-80">
<button class="u-reset-button save-btn" @tap="changePwdSubmit">保存</button>
<button class="u-reset-button forgot-btn" @tap="showAuthModal('forgotPwd')">
忘记密码
</button>
</view>
</view>
<!-- 第三方登录 -->
<view v-if="authType === 'accountLogin' || authType === 'smsLogin'"
class="auto-login-box u-flex u-row-center u-col-center">
<!-- 微信 -->
<button v-if="['App', 'wxOfficialAccount', 'wxMiniProgram'].includes(platform)" class="u-reset-button"
open-type="getPhoneNumber" @getphonenumber="thirdLogin('wechat', $event)"
@tap="thirdLogin('wechat')">
<image class="auto-login-img" :src="$IMG_URL + '/imgs/auto_login_wx.png'"></image>
</button>
<!-- 支付宝 -->
<!-- <image
v-if="['App', 'alipayMiniProgram', 'H5'].includes(platform)"
class="auto-login-img"
@tap="thirdLogin('alipay')"
:src="$IMG_URL + '/imgs/auto_login_ali.png'"
mode=""
></image> -->
<!-- 苹果 -->
<!-- #ifdef APP-PLUS -->
<image v-if="device === 'ios'" class="auto-login-img" @tap="thirdLogin('apple')"
:src="$IMG_URL + '/imgs/auto_login_iphone.png'"></image>
<!-- #endif -->
</view>
<!-- 协议 -->
<view v-if="['accountLogin', 'smsLogin', 'register'].includes(authType)"
class="agreement-box u-flex u-row-center">
<u-checkbox v-model="protocol" shape="circle" active-color="#E9B461">
<view class="agreement-text tcp-text u-flex u-col-center">
我已阅读并遵守
<view class="tcp-text u-flex u-col-center">
<view @tap.stop="
$Router.push({
path: '/pages/public/richtext',
query: { id: initShop.user_protocol || 0 }
})
">
《用户协议》
</view>
与
<view @tap.stop="
$Router.push({
path: '/pages/public/richtext',
query: { id: initShop.privacy_protocol || 0 }
})
">
《隐私协议》
</view>
</view>
</view>
</u-checkbox>
</view>
</view>
</view>
</template>
<script>
/**
* 登录提示页
*/
import FormValidate from "@/shopro/validate/form";
import schema from "@/uview-ui/libs/util/async-validator";
import wechat from "@/shopro/wechat/wechat";
import { mapMutations, mapActions, mapState, mapGetters } from "vuex";
// #ifdef APP-PLUS
import apple from "@/shopro/apple";
// #endif
export default {
name: "shoproAuthModal",
data() {
return {
platform: this.$platform.get(),
device: this.$platform.device(),
form: {
// 1.账号密码登录
accountLogin: {
data: {
account: "", // 账号
password: "" // 密码
},
rules: {
account: FormValidate.account,
password: FormValidate.password
},
error: {
account: "",
password: ""
}
},
// 2.短信登录
smsLogin: {
data: {
mobile: "", // 手机号
code: "", // 验证码
isMobileEnd: false // 手机号输入完毕
},
rules: {
code: FormValidate.code,
mobile: FormValidate.mobile
},
error: {
mobile: "", // 手机号
code: "" // 验证码
}
},
// 3.注册
register: {
data: {
mobile: "", // 手机号
code: "", // 验证码
password: "", // 密码
isMobileEnd: false // 手机号输入完毕
},
rules: {
code: FormValidate.code,
mobile: FormValidate.mobile,
password: FormValidate.password
},
error: {
mobile: "", // 手机号
code: "", // 验证码
password: "" // 密码
}
},
// 4.忘记密码
forgotPwd: {
data: {
mobile: "", //手机号
code: "", //验证码
password: "", //密码
isMobileEnd: false // 手机号输入完毕
},
rules: {
mobile: FormValidate.mobile,
code: FormValidate.code,
password: FormValidate.password
},
error: {
mobile: "", //手机号
code: "", //验证码
password: "" //密码
}
},
// 5.绑定手机号
bindMobile: {
data: {
mobile: "", //手机号
code: "", //验证码
isMobileEnd: false // 手机号输入完毕
},
rules: {
code: FormValidate.code,
mobile: FormValidate.mobile
},
error: {
mobile: "", //手机号
code: "" //验证码
}
},
// 6.修改密码
changePwd: {
data: {
oldPassword: "", //旧密码
newPassword: "", //新密码
reNewPassword: "" //确认密码
},
rules: {
oldPassword: FormValidate.password,
newPassword: FormValidate.password,
reNewPassword: [
{
required: true,
message: "请重新输入密码",
trigger: ["change", "blur"]
},
{
validator: (rule, value, callback) => {
return value === this.form.changePwd.data.newPassword;
},
message: "两次输入的密码不一致",
trigger: ["change", "blur"]
}
]
},
error: {
oldPassword: "", //旧密码
newPassword: "", //新密码
reNewPassword: "" //确认密码
}
}
},
codeText: "获取验证码",
disabledCode: false,
protocol: false, //是否同意隐私协议
placeholderStyle: "font-size: 30rpx; font-weight: 500;color:#C2C7CF;"
};
},
computed: {
...mapGetters(["initShop", "isLogin", "authType"]),
showAuth: {
get() {
return !!this.authType;
},
set(value) {
value ? uni.hideTabBar() : uni.showTabBar();
}
}
},
mounted() { },
methods: {
...mapActions(["getUserInfo", "showAuthModal"]),
// 检测
checkValue(e, key) {
this.validation(key);
},
// 校验数据
validation(key, callback = () => { }) {
let that = this;
//拿到需要校验的数据
let fieldValue = this.form[this.authType].data[key];
//拿到需要检验的规则
let rules = this.form[this.authType].rules[key];
// 判空
if (!rules || rules.length === 0) {
return callback("");
}
// 设置当前的装填,标识为校验中
let validateState = "validating";
// 调用async-validator的方法
let validator = new schema({
[key]: rules
});
// 校验
validator.validate(
{
[key]: fieldValue
},
{
firstFields: true
},
(errors, fields) => {
// 记录状态和报错信息
validateState = !errors ? "success" : "error";
let validateMessage = errors ? errors[0].message : "";
that.form[that.authType].error[key] = validateMessage;
callback(validateMessage);
}
);
},
// 校验全部数据
validateAll(callback) {
let that = this;
return new Promise(resolve => {
// 对当前所有表单检验
let valid = true; // 默认通过
let count = 0; // 用于标记是否检查完毕
let errorArr = []; // 存放错误信息
let curFormData = that.form[that.authType].data;
Object.keys(curFormData).map(field => {
that.validation(field, error => {
// 如果回调有error
if (error) {
valid = false;
errorArr.push(error);
}
if (++count === Object.keys(curFormData).length) {
resolve(valid);
if (errorArr.length) {
this.$u.toast(errorArr[0]);
}
if (typeof callback == "function") callback(valid);
}
});
});
});
},
// 手机号是否输入完毕
mobileInput(e, key) {
this.form[this.authType].data.isMobileEnd = this.$u.test.mobile(
this.form[this.authType].data.mobile
);
this.validation(key);
},
closeAuthModal() {
this.$store.dispatch("showAuthModal", "");
},
// 获取短信验证码
getSmsCode(type) {
const that = this;
if (that.form[this.authType].data.isMobileEnd && !that.disabledCode) {
that.$http(
"common.smsSend",
{
mobile: that.form[this.authType].data.mobile,
event: type
},
"获取验证码中..."
).then(res => {
if (res.code === 1) {
that.codeChange();
that.$u.toast("验证码已发送,请注意查收短信");
} else {
that.$u.toast(res.msg);
}
});
} else {
that.$u.toast("请稍后再试");
}
},
// 倒计时
codeChange() {
if (this.disabledCode) return;
this.disabledCode = true;
let n = 60;
this.codeText = n + "s";
const run = setInterval(() => {
n -= 1;
if (n < 0) {
clearInterval(run);
}
this.codeText = n + "s";
if (this.codeText < 0 + "s") {
this.disabledCode = false;
this.codeText = "重新获取";
}
}, 1000);
},
// 规则校验
validateSubmit() {
if (
["accountLogin", "smsLogin", "register"].includes(this.authType) &&
!this.protocol
) {
this.$u.toast("请同意用户协议");
return false;
}
return this.validateAll();
},
// 第三方登录
async thirdLogin(provider, payload = null) {
if (this.platform === 'wxMiniProgram' && payload === null) return;
if (!this.protocol) {
this.$u.toast("请同意用户协议");
return false;
}
const that = this;
let token = "";
switch (provider) {
case "wechat":
token = await wechat.login(payload);
break;
case "alipay":
break;
case "apple":
token = await apple.appleIdOauth();
break;
default:
break;
}
if (token) {
that.closeAuthModal();
that.getUserInfo(token);
}
},
// 1.账号登录
async accountLoginSubmit() {
let that = this;
(await that.validateSubmit()) &&
that
.$http(
"user.accountLogin",
{
account: that.form["accountLogin"].data.account,
password: that.form["accountLogin"].data.password
},
"登录中..."
)
.then(res => {
if (res.code === 1) {
that.closeAuthModal();
that.getUserInfo(res.data.token);
that.$u.toast(res.msg);
}
});
},
// 2.短信登录
async smsLoginSubmit() {
let that = this;
(await that.validateSubmit()) &&
that
.$http(
"user.smsLogin",
{
mobile: that.form["smsLogin"].data.mobile,
code: that.form["smsLogin"].data.code
},
"登录中..."
)
.then(res => {
if (res.code === 1) {
that.closeAuthModal();
that.getUserInfo(res.data.token);
that.$u.toast(res.msg);
}
});
},
// 3.注册
async registerSubmit() {
let that = this;
(await that.validateSubmit()) &&
that
.$http(
"user.register",
{
mobile: that.form["register"].data.mobile,
code: that.form["register"].data.code,
password: that.form["register"].data.password
},
"注册中..."
)
.then(res => {
if (res.code === 1) {
that.$u.toast(res.msg);
that.closeAuthModal();
that.getUserInfo(res.data.token);
}
});
},
// 4.忘记密码
async forgotPwdSubmit() {
let that = this;
(await that.validateSubmit()) &&
that
.$http(
"user.forgotPwd",
{
mobile: that.form["forgotPwd"].data.mobile,
code: that.form["forgotPwd"].data.code,
password: that.form["forgotPwd"].data.password
},
"修改中..."
)
.then(res => {
if (res.code === 1) {
that.$u.toast(res.msg);
if (that.isLogin) {
that.closeAuthModal();
} else {
that.showAuthModal("accountLogin");
}
}
});
},
// 5.绑定手机
async bindMobileSubmit() {
let that = this;
(await that.validateSubmit()) &&
that
.$http(
"user.bindMobile",
{
mobile: that.form["bindMobile"].data.mobile,
code: that.form["bindMobile"].data.code,
password: that.form["bindMobile"].data.password
},
"绑定中..."
)
.then(res => {
if (res.code === 1) {
that.$u.toast(res.msg);
that.closeAuthModal();
that.getUserInfo();
}
});
},
// 6.修改密码
async changePwdSubmit() {
let that = this;
(await that.validateSubmit()) &&
that
.$http(
"user.changePwd",
{
oldpassword: that.form["changePwd"].data.oldPassword,
newpassword: that.form["changePwd"].data.newPassword
},
"修改中..."
)
.then(res => {
if (res.code === 1) {
that.closeAuthModal();
that.$u.toast(res.msg);
that.getUserInfo(res.data.userinfo.token);
}
});
}
}
};
</script>
<style lang="scss" scoped>
@keyframes title-animation {
0% {
font-size: 32rpx;
}
100% {
font-size: 36rpx;
}
}
.form-wrap {
.form-item {
display: flex;
flex-direction: column;
font-size: 28rpx;
padding: 20rpx 0;
color: $u-main-color;
box-sizing: border-box;
line-height: $u-form-item-height;
width: 100%;
.item-title {
width: 140rpx;
font-size: 30rpx;
color: #333;
font-weight: 600;
text-align: left;
}
.message-error {
text-align: left;
font-size: 24rpx;
line-height: 24rpx;
color: $u-type-error;
margin-top: 12rpx;
margin-left: 120rpx;
}
}
}
.login-wrap {
padding: 50rpx 34rpx;
min-height: 700rpx;
background-color: #fff;
.head-box {
.head-title {
min-width: 160rpx;
font-size: 36rpx;
font-weight: bold;
color: #333333;
line-height: 36rpx;
}
.head-title-active {
width: 160rpx;
font-size: 32rpx;
font-weight: 600;
color: #999;
line-height: 36rpx;
}
.head-title-animation {
animation-name: title-animation;
animation-duration: 0.1s;
animation-timing-function: ease-out;
animation-fill-mode: forwards;
}
.head-title-line {
position: relative;
&::before {
content: "";
width: 1rpx;
height: 34rpx;
background-color: #e4e7ed;
position: absolute;
left: -30rpx;
top: 50%;
transform: translateY(-50%);
}
}
.head-subtitle {
font-size: 26rpx;
font-weight: 400;
color: #c2c7cf;
text-align: left;
display: flex;
}
}
.code-btn[disabled] {
background-color: #fff;
}
.code-btn-start {
width: 160rpx;
line-height: 56rpx;
border: 1rpx solid #e9b766;
border-radius: 28rpx;
font-size: 26rpx;
font-weight: 400;
color: #e9b766;
opacity: 0.5;
}
.forgot-btn {
width: 160rpx;
line-height: 56rpx;
font-size: 30rpx;
font-weight: 500;
color: #999;
}
.code-btn-end {
opacity: 1 !important;
}
.login-btn-start {
width: 158rpx;
line-height: 56rpx;
background: linear-gradient(90deg, #e9b461, #eecc89);
border-radius: 28rpx;
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
}
.type-btn {
padding: 20rpx;
margin: 40rpx auto;
width: 200rpx;
font-size: 30rpx;
font-weight: 500;
color: #999999;
}
.auto-login-box {
width: 100%;
.auto-login-img {
width: 68rpx;
height: 68rpx;
border-radius: 50%;
margin: 0 30rpx;
}
}
.agreement-box {
margin: 80rpx auto 0;
.protocol-check {
transform: scale(0.7);
}
.agreement-text {
font-size: 26rpx;
font-weight: 500;
color: #999999;
.tcp-text {
color: #e9b562;
}
}
}
}
// 修改密码
.editPwd-btn-box {
.save-btn {
width: 690rpx;
line-height: 70rpx;
background: linear-gradient(90deg, #e9b461, #eecc89);
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
}
.forgot-btn {
width: 690rpx;
line-height: 70rpx;
font-size: 28rpx;
font-weight: 500;
color: #999999;
}
}
</style>

@ -0,0 +1,123 @@
<template>
<view class="invite-poster-content">
<!-- TODO: 小程序端使用type='2d'会有报错 -->
<canvas class="hideCanvas" canvas-id="self_canvas" :style="{ width: (poster.width || 1) + 'px', height: (poster.height || 1) + 'px' }"></canvas>
</view>
</template>
<script>
/**
* shopro-canvas - canvas
* @property {Object} canvasParams - 自定义参数
* @property {Boolean} isAutoInit = [true] - 是否自动渲染canvas
*/
import _app from '@/shopro/poster/QS-SharePoster/app.js';
import { getSharePoster } from '@/shopro/poster/QS-SharePoster/QS-SharePoster.js';
import tools from '@/shopro/poster/tools.js';
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
let ctx = null;
export default {
components: {},
data() {
return {
poster: {},
canvasId: 'self_canvas'
};
},
props: {
canvasParams: {
type: Object,
default: () => {}
},
isAutoInit: {
type: Boolean,
default: true
}
},
computed: {
...mapGetters(['initShare', 'userInfo'])
},
async mounted() {
ctx = uni.createCanvasContext(this.canvasId, this);
ctx && this.isAutoInit && this.shareFc();
},
methods: {
async shareFc() {
let that = this;
_app.showLoading('绘制中...');
let config = {};
if (that.canvasParams.backgroundImage) {
config = {
backgroundImage: tools.checkImgHttp(that.canvasParams.backgroundImage, 'bgImage')
};
}
if (that.canvasParams.background) {
config = {
background: {
width: that.canvasParams.background?.width || 100,
height: that.canvasParams.background?.height || 100,
backgroundColor: that.canvasParams.background?.color || '#000'
}
};
}
try {
_app.log('准备生成:' + new Date());
const d = await getSharePoster({
_this: that, //若在组件中使用 必传
...config,
posterCanvasId: that.canvasId, //canvasId
Context: ctx,
delayTimeScale: 10, //延时系数
draw: false, //是否执行ctx.draw方法, 推荐false,自己去draw
drawArray: ({ bgObj, type, bgScale }) => {
let arr = tools.initDrawArray(bgObj, that.canvasParams.drawArray);
return new Promise((rs, rj) => {
rs(arr);
});
},
setCanvasWH: ({ bgObj, type, bgScale }) => {
// 为动态设置画布宽高的方法,
this.poster = bgObj;
}
});
await that.drawPoster();
} catch (e) {
_app.hideLoading();
_app.showToast(JSON.stringify(e));
console.log(JSON.stringify(e));
}
},
async drawPoster() {
let that = this;
ctx.draw(false, () => {
uni.canvasToTempFilePath(
{
canvasId: that.canvasId,
success: res => {
_app.hideLoading();
that.$emit('success', res.tempFilePath);
_app.log('海报生成成功, 时间:' + new Date() + ', 临时路径: ' + res.tempFilePath);
},
fail: err => {
_app.hideLoading();
console.log('生成异常', err);
}
},
that
);
});
}
}
};
</script>
<style lang="scss">
.hideCanvas {
position: fixed;
top: -99999upx;
left: -99999upx;
z-index: -99999;
}
</style>

@ -0,0 +1,176 @@
<template>
<view class="">
<!-- 未领取,已领取 -->
<view class="coupon-wrap" :style="gradientColor" :class="{ 'gray-wrap': state === 3 || state === 2 || couponData.status_code == 'cannot_get' }">
<view class="coupon-item u-flex u-row-between">
<view class="coupon-left u-flex-col ">
<view class="sum-box">
<text class="unit " :style="{ color: priceColor }">¥</text>
<text class="sum " :style="{ color: priceColor }">{{ couponData.amount }}</text>
<text class="sub " :style="{ color: priceColor }">{{ couponData.name }}</text>
</view>
<view class="notice " :style="{ color: color }">满{{ couponData.enough }}元可用</view>
<view class="notice" :style="{ color: color }">
有效期:{{ $u.timeFormat(couponData.usetime.start, 'yyyy-mm-dd') }} 至 {{ $u.timeFormat(couponData.usetime.end, 'yyyy-mm-dd') }}
</view>
</view>
<view class="coupon-right u-flex-col">
<button class="get-btn" :style="{ color: btnTextColor }" v-if="state === 0" @tap.stop="getCoupon">{{ stateMap[couponData.status_code] || '立即领取' }}</button>
<button class="get-btn" :style="{ color: btnTextColor }" v-if="state === 1">去使用</button>
<button class="get-btn" :style="{ color: btnTextColor }" v-if="state === 2">暂不可用</button>
<button class="get-btn" v-if="state === 3">{{ stateMap[couponData.status_code] }}</button>
<view class="surplus-num" :style="{ color: color }" v-if="state === 0">仅剩{{ stock }}张</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* shoproCoupon-优惠券
* @property {Number} state - 0:立即领取,1:去使用,2:查看详情,3:已失效。
* @property {Object} couponData - 优惠劵数据。
* @property {String} gradientColor - 渐变色。
*/
export default {
components: {},
data() {
return {
stock: 0,
stateMap: {
can_use: '立即使用',
used: '已使用',
expired: '已失效',
cannot_use: '暂不可用',
can_get: '立即领取',
cannot_get: '已领取'
}
};
},
props: {
state: 0, //0:立即领取,1:去使用,2:查看详情,3:已失效。
couponData: {},
gradientColor: {
type: String,
default: ''
},
color: {
type: String,
default: ''
},
btnTextColor: {
type: String,
default: ''
},
priceColor: {
type: String,
default: ''
}
},
computed: {},
created() {
this.$nextTick(() => {
this.stock = this.couponData.stock;
});
},
methods: {
getCoupon() {
let that = this;
that.$http(
'coupons.get',
{
id: that.couponData.id
},
'领取中...'
).then(res => {
if (res.code === 1) {
that.stock -= 1;
that.$u.toast('领取成功');
}
});
}
}
};
</script>
<style lang="scss" scoped>
// 失效
.gray-wrap {
-webkit-filter: grayscale(1); /* Webkit */
filter: gray; /* IE6-9 */
filter: grayscale(1); /* W3C */
}
// 未领取,已领取
.coupon-wrap {
mask: url($IMG_URL+'/imgs/coupon_bg1.png');
-webkit-mask-box-image: url($IMG_URL+'/imgs/coupon_bg1.png');
mask-size: 100% 100%;
position: relative;
border-radius: 10rpx;
width: 710rpx;
height: 170rpx;
background: linear-gradient(to right, $u-type-warning-disabled, $u-type-warning);
.coupon-item {
width: 100%;
height: 168rpx;
border-radius: 10rpx;
.coupon-left {
height: 100%;
justify-content: center;
padding-left: 40rpx;
.unit {
font-size: 24rpx;
color: #fff;
}
.sum {
font-size: 55rpx;
font-weight: bold;
color: #fff;
line-height: 55rpx;
padding-right: 10rpx;
}
.sub {
font-size: 26rpx;
color: #fff;
}
.notice {
font-size: 22rpx;
color: rgba(#fff, 0.7);
margin-top: 6rpx;
}
}
.coupon-right {
height: 100%;
width: 220rpx;
justify-content: center;
align-items: center;
.get-btn {
width: 142rpx;
height: 54rpx;
background: #ffffff;
border-radius: 27rpx;
padding: 0;
font-size: 24rpx;
font-weight: 500;
color: $u-type-warning;
}
.surplus-num {
font-size: 22rpx;
font-weight: 500;
color: #fff;
margin-top: 14rpx;
}
}
}
}
</style>

@ -0,0 +1,77 @@
<template>
<view class="shopro-empty-wrap u-flex-col u-row-center u-col-center" :style="{ 'margin-top': marginTop }">
<image class="empty-img" :src="image" mode="aspectFill"></image>
<view class="empty-text u-tips-color u-font-26">{{ tipText }}</view>
<view class="btn-box u-m-t-100" v-if="btnText">
<button class="u-reset-button empty-btn" :style="customStyle" hover-class="none" @tap="onBtn">{{ btnText }}</button>
</view>
</view>
</template>
<script>
/**
* shoproEmpty- 数据为空页
* @property {String} image - 空白图。
* @property {String} tipText - 提示语。
* @property {String} btnText - 按钮文字。
* @property {String} marginTop - 距离父级距离。
* @property {Object} customStyle - 自定义按钮样式。
* @event {Fuction} click - 点击按钮
*/
export default {
name: 'shoproEmpty',
props: {
image: {
type: [String,null],
default: '/static/images/empty_network.png'
},
tipText: {
type: String,
default: ''
},
btnText: {
type: String,
default: ''
},
marginTop: {
type: String,
default: '300rpx'
},
customStyle: {
type: Object,
default: () => {
return {
width: '200rpx',
height: '70rpx',
background: 'linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1))',
borderRradius: '35rpx',
fontSize: '28rpx',
color: '#fff',
border: 0
};
}
}
},
methods: {
onBtn() {
this.$emit('click');
}
}
};
</script>
<style lang="scss" scoped>
.empty-img {
width: 540rpx;
height: 290rpx;
}
.empty-btn {
width: 320rpx;
height: 70rpx;
line-height: 70rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
border-radius: 35rpx;
font-size: 28rpx;
color: rgba(#fff, 0.9);
}
</style>

@ -0,0 +1,184 @@
<template>
<view class="" v-if="floatList && floatList.length">
<view class="cu-modal" :class="{ show: showMask }" @tap="hideMask"></view>
<!-- 悬浮按钮菜单 -->
<view class="shopro-float-btn">
<button class="wechat-btn u-reset-button" @tap="onBtn">
<image class="wechat_img" :src="floatList.length == 1 ? floatList[0].btnimage : floatData.image" mode="widthFix"></image>
</button>
<view :class="showBtnList ? 'float--active' : 'float-list-box'">
<view class="btn-img-box u-flex-col" @tap="onBtnItem(item)" v-for="item in floatList" :key="item.btnimage">
<view class="btn-item u-flex-col u-row-center u-col-center">
<image class="btn-img" :src="item.btnimage" mode="aspectFill"></image>
<view class="btn-name">{{ item.name }}</view>
</view>
</view>
</view>
</view>
<!-- 弹窗 -->
<view class="cu-modal" :class="{ show: showModal }" @tap="showModal = false">
<view class="cu-dialog" style="width: 610rpx;background: none;">
<view class="img-box"><image class="modal-img" :src="modalImg" mode="aspectFit" @tap="saveImage(modalImg)"></image></view>
</view>
</view>
</view>
</template>
<script>
/**
* 悬浮按钮,悬浮菜单。全局通用,数据为vuex初始化导入。
* @property {Array} floatList - 悬浮按钮菜单数据列表
*/
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
import Auth from '@/shopro/permission/index.js';
export default {
components: {},
data() {
return {
showBtnList: false, //列表弹出
modalImg: '', //子项图片弹出
showModal: false, //modal层
showMask: false,
platform: this.$platform.get()
};
},
props: {},
computed: {
...mapGetters(['floatData']),
// 悬浮按钮数据列表
floatList() {
if (this.floatData) {
return this.floatData.list;
}
}
},
created() {},
methods: {
hideMask() {
this.showBtnList = false;
this.showMask = false;
},
// 点击悬浮按钮
onBtn() {
this.showMask = !this.showMask;
if (this.floatList.length == 1) {
this.modalImg = this.floatList[0].image;
this.floatList[0].style == 2 ? this.$tools.routerTo(this.floatList[0].path) : (this.showModal = true);
this.showMask = false;
} else {
this.showBtnList = !this.showBtnList;
}
},
// 点击按钮菜单,如果悬浮按钮数据为一条,按钮图片为唯一菜单项图片和标题
onBtnItem(item) {
if (item.style == 2) {
this.showMask = false;
this.showModal = false;
this.showBtnList = false;
this.$tools.routerTo(item.path);
} else {
this.modalImg = item.image;
this.showMask = false;
this.showModal = true;
this.showBtnList = false;
!item.image && console.log(`%cerr:弹窗图片未配置`, 'color:green;background:yellow');
}
},
saveImageToPhotosAlbum(path) {
uni.saveImageToPhotosAlbum({
filePath: path,
success: res => {
this.showMask = false;
this.showModal = false;
this.$u.toast('保存成功');
},
fail: err => {
console.log(`图片保存失败:`, err);
this.$u.toast('保存失败');
}
});
},
// 保存图片
async saveImage(path) {
let that = this;
if (['wxOfficialAccount', 'H5'].includes(this.platform)) {
this.$u.toast('长按图片保存');
return false;
}
let authState = await new Auth('writePhotosAlbum').check();
if (authState) {
// #ifdef MP ||APP-VUE
const res = await uni.downloadFile({
url: this.$tools.checkMPUrl(path)
});
res[1].statusCode === 200 && this.saveImageToPhotosAlbum(res[1].tempFilePath);
// #endif
}
}
}
};
</script>
<style lang="scss">
.shopro-float-btn {
position: fixed;
bottom: calc(var(--window-bottom) + 100rpx);
right: 30rpx;
z-index: 9999;
.float--active {
position: absolute;
bottom: 80rpx;
right: 0;
left: 0;
margin: 0 auto;
z-index: 10010;
transform: scale(1);
transition: all 0.2s linear;
}
.float-list-box {
position: absolute;
z-index: 10010;
bottom: 0;
right: 0;
left: 0;
margin: 0 auto;
transform: scale(0);
transform-origin: bottom;
opacity: 0;
transition: all 0.2s linear;
}
.btn-item {
margin-bottom: 20rpx;
.btn-img {
width: 60rpx;
height: 60rpx;
}
.btn-name {
font-size: 20rpx;
color: #fff;
text-align: center;
white-space: nowrap;
}
}
.wechat-btn {
box-shadow: 0px 0px 20px 4px rgba(199, 199, 199, 0.22);
border-radius: 50%;
width: 80rpx;
height: 80rpx;
.wechat_img {
width: 80rpx;
height: 80rpx;
}
}
}
.img-box {
position: relative;
width: 610rpx;
.modal-img {
width: 100%;
will-change: transform;
height: 830rpx;
}
}
</style>

@ -0,0 +1,348 @@
<template>
<!-- m -->
<view class="goods-box" @tap="click">
<view class="img-box">
<image class="goods-img" lazy-load fade-show :src="image" mode="aspectFill"></image>
</view>
<view class="goods-bottom u-p-14"
:style="type ? 'background-image: url(' + $IMG_URL + typeMap[type].goodsBg + ')' : ''">
<view class="title u-ellipsis-2 u-m-b-10">
<view class=" sm cu-tag radius title-tag u-m-r-10"
:style="{ backgroundColor: typeMap[type].tagBg, color: '#fff' }" v-if="type">
{{ typeMap[type].text }}
</view>
{{ title }}
</view>
<view class="sub-title u-ellipsis-1 u-m-b-10" v-show="subtitle">{{ subtitle }}</view>
<view class="u-m-b-20">
<view class="cu-tag line-red sm radius" v-for="(item, index) in tagTextList" :key="index">{{ item }}
</view>
</view>
<slot name="cardBottom">
<view class="u-flex u-col-center u-row-between">
<view class="price-box">
<view class="price u-m-b-10">{{ price }}</view>
<view class="origin-price">¥{{ originPrice }}</view>
</view>
<!-- 加入购物车 -->
<view class="cart-box">
<!-- 单规格 -->
<view class="" v-if="!detail.is_sku">
<button class="u-reset-button cart-btn u-flex u-col-center u-row-center"
v-if="!isCart(detail.id)" @tap.stop="addCart(detail.sku_price[0])">
<view class="u-iconfont uicon-shopping-cart-fill" style="color: #fff;"></view>
</button>
<view class="num-step" @tap.stop v-else>
<u-number-box :value="checkCart[detail.id].num" :min="0" :step="1" :long-press="false"
:max="detail.sku_price[0].stock > 999 ? 999 : detail.sku_price[0].stock"
@min="onMin" @plus="plus($event, detail.sku_price[0])"
@change="onChangeNum($event, detail.sku_price[0])"></u-number-box>
</view>
</view>
<!-- 多规格 -->
<button class="u-reset-button item-btn cart-btn u-flex u-col-center u-row-center"
@tap.stop="selSku(detail)" v-else>
<view class="u-iconfont uicon-shopping-cart-fill" style="color: #fff;"></view>
</button>
</view>
</view>
</slot>
</view>
<!-- 规格弹窗 -->
<shopro-sku v-if="showSku && goodsInfo.id" v-model="showSku" :goodsInfo="goodsInfo" buyType="cart"></shopro-sku>
</view>
</template>
<script>
/**
*shoproGoodsCard - 商品列表卡片
* @property {Object} detail - 商品详情
* @property {String} type - 商品类型
* @property {String} image - 商品图片
* @property {String} title - 商品标题
* @property {String} subtitle - 商品副标题
* @property {String | Number} price - 商品价格
* @property {String | Number} originPrice - 商品原价
* @property {String | Number} sales - 商品销量
* @property {Array} tagTextList - 活动标签
* @event {Function} click 商品被点击
*/
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {},
data() {
return {
showSku: false,
goodsInfo: {}, //商品详情
typeMap: {
seckill: {
text: '秒杀',
tagBg: '#FF5854',
goodsBg: '/imgs/tag/seckill_y_bg.png'
},
groupon: {
text: '拼团',
tagBg: '#FE832A',
goodsBg: '/imgs/tag/groupon_y_bg.png'
}
}
};
},
computed: {
...mapGetters(['cartList', 'checkCart'])
},
props: {
detail: {
type: Object,
default: () => {
return {};
}
},
type: {
type: [String, null],
default: ''
},
image: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
subtitle: {
type: String,
default: ''
},
price: {
type: [String, Number],
default: ''
},
originPrice: {
type: [String, Number],
default: ''
},
sales: {
type: [String, Number],
default: ''
},
tagTextList: {
type: Array,
default: () => []
}
},
methods: {
...mapActions(['getCartList', 'changeCartList', 'addCartGoods']),
//点击商品
click() {
this.$emit('click');
},
// 检测是否为购物车商品
isCart(id) {
return Object.keys(this.checkCart).includes(id + '');
},
// 检测商品在购物车中的下标
checkGoodsIndex(id) {
let cIndex = 0;
this.cartList.forEach((item, index) => {
if (id == item.goods_id) {
cIndex = index;
}
});
return cIndex;
},
// 更改商品数
async onChangeNum(e, sku) {
let gIndex = this.checkGoodsIndex(sku.goods_id);
if (e.value != this.checkCart[sku.goods_id].num) {
uni.showLoading({
mask: true
});
this.$set(this.cartList[gIndex], 'goods_num', +e.value);
await this.changeCartList({
ids: [this.checkCart[sku.goods_id].cartOrderId],
goodsNum: +e.value,
art: 'change'
});
await uni.hideLoading();
}
},
// 到达最小值
onMin() {
const that = this;
let cartGoodId = 0;
cartGoodId = this.cartList.filter(item => item.goods_id === that.detail.id)[0].id;
uni.showModal({
title: '删除提示',
confirmColor: '#f0c785',
content: `是否确认从购物车中删除此商品?`,
success: res => {
res.confirm && this.changeCartList({ ids: [cartGoodId], art: 'delete' });
}
});
},
// 增加
plus(e, sku) {
if (e.value >= sku.stock) {
this.$u.toast('库存不足');
return;
}
if (this.detail.activity_type === 'seckill' || this.detail.activity_type === 'groupon') {
let rules = this.detail.activity.rules;
if (rules.limit_buy != 0 && e.value >= rules.limit_buy) {
this.$u.toast('本次活动最多购买' + rules.limit_buy + '件');
return;
}
}
},
// 添加购物车,多规格
async selSku(info) {
if (this.detail.activity_type) {
this.$Router.push({ path: '/pages/goods/detail', query: { id: this.detail.id } });
return;
}
this.goodsInfo = {};
this.getGoodsDetail(info.id);
this.showSku = true;
},
// 商品详情
getGoodsDetail(id) {
let that = this;
that.$http('goods.detail', {
id: id
}).then(res => {
if (res.code === 1) {
that.goodsInfo = res.data;
}
});
},
// 加入购物车
addCart(sku) {
if (sku.stock <= 0) {
this.$u.toast('库存不足');
return;
}
if (this.detail.activity_type) {
this.$Router.push({ path: '/pages/goods/detail', query: { id: this.detail.id } });
return;
}
let confirmGoodsList = {
list: [{
goods_id: sku.goods_id,
goods_num: 1,
sku_price_id: sku.id,
goods_price: sku.price
}],
from: 'goods'
};
this.addCartGoods(confirmGoodsList).then(res => {
if (res.code === 1) {
this.$u.toast(res.msg);
}
});
}
}
};
</script>
<style lang="scss">
.goods-box {
width: 345rpx;
background: #fff;
border-radius: 20rpx;
overflow: hidden;
.goods-bottom {
background-size: 100% 100%;
background-position: bottom center;
background-repeat: no-repeat;
}
.img-box {
width: 345rpx;
height: 345rpx;
overflow: hidden;
position: relative;
background-color: #fff;
border-radius: 6rpx;
.goods-img {
width: 345rpx;
height: 345rpx;
background-color: #ccc;
}
}
.title {
width: 330rpx;
vertical-align: center;
font-size: 28rpx;
font-weight: bold;
line-height: 40rpx;
color: #333333;
padding-top: 6rpx;
.title-tag {
position: relative;
top: -6rpx;
}
}
.sub-title {
font-size: 24rpx;
font-weight: 400;
width: 330rpx;
color: #999999;
}
.price-box {
.price {
font-size: 30rpx;
color: #ff3000;
font-weight: 600;
&::before {
content: '¥';
font-size: 24rpx;
}
}
.origin-price {
font-size: 26rpx;
font-weight: 400;
text-decoration: line-through;
color: #c4c4c4;
}
.sales-box {
font-size: 18rpx;
font-weight: 400;
color: #c4c4c4;
line-height: 20rpx;
}
}
// 购物车
.cart-box {
.cart-btn {
width: 54rpx;
height: 54rpx;
border-radius: 50%;
padding: 0;
background: linear-gradient(90deg, #e9b461, #eecc89);
}
}
}
</style>

@ -0,0 +1,361 @@
<template>
<view class="live-card-wrap">
<!-- 小卡片 -->
<view class="sp-live-card" v-if="liveType == 2" :style="{ width: wh + 'rpx' }">
<view class="live-content" @tap="goRoom" :style="{ width: wh + 'rpx' }">
<image class="item-cover" :src="detail.share_img" mode="aspectFill"></image>
<view class="item-status">
<image class="status-img" :src="liveStatus[liveState].img" mode=""></image>
<text class="status-text">{{ liveStatus[liveState].title }}</text>
</view>
<view class="item-title u-ellipsis-1" :style="{ width: wh + 'rpx' }">{{ detail.name }}</view>
</view>
<view class="live-bottom" :style="{ width: wh + 'rpx' }">
<view class="live-info">
<view class="info-box">
<view class="info-name u-ellipsis-1" :style="{ width: wh + 'rpx' }">{{ detail.anchor_name }}</view>
</view>
</view>
<slot name="liveGoods">
<view class="live-goods" v-if="liveGoodsList.length">
<view class="live-goods__item" v-for="(goods, index) in liveGoodsList" :key="goods.id" v-if="index < 3">
<image class="live-goods__img" :src="goods.cover_img" mode=""></image>
<view class="live-goods__price" v-if="index < 2">¥{{ goods.price }}</view>
<view class="live-goods__mark" v-else>
<text>{{ liveGoodsList.length }}+</text>
</view>
</view>
</view>
</slot>
</view>
</view>
<!-- 大卡片 -->
<view class="big-card-wrap" v-if="liveType == 1">
<view class="content-one__item" @tap="goRoom">
<image class="item-cover" :src="detail.share_img" mode="widthFix"></image>
<view class="item-status">
<image class="status-img" :src="liveStatus[liveState].img" mode=""></image>
<text class="status-text">{{ liveStatus[liveState].title }}</text>
</view>
<view class="item-title u-ellipsis-1">{{ detail.name }}</view>
</view>
</view>
</view>
</template>
<script>
/**
* 小程序直播显示卡片
* @property {Object} detail - 直播卡片显示数据
* @property {Number} wh = [345] - 直播卡片宽度
* @property {Number|String} style = 2 - 1:大卡片;2::小卡片
*/
// #ifdef MP-WEIXIN
import { HAS_LIVE } from '@/env';
let livePlayer = null;
// 全局配置文件控制是否开启直播组件,没有此项功能的小程序,运行会报错。
if (HAS_LIVE) {
livePlayer = requirePlugin('live-player-plugin');
}
// #endif
let timer = null;
export default {
name: 'shoproLiveCard',
components: {},
data() {
return {
liveType: this.type,
liveState: this.detail.live_status,
liveGoodsList: this.detail.goods,
liveStatus: {
'101': {
img: this.$IMG_URL + '/imgs/live/live.png',
title: '直播中'
},
'102': {
img: this.$IMG_URL + '/imgs/live/prevue.png',
title: '未开始'
},
'103': {
img: this.$IMG_URL + '/imgs/live/playback.png',
title: '已结束'
},
'104': {
img: this.$IMG_URL + '/imgs/live/104.png',
title: '禁播'
},
'105': {
img: this.$IMG_URL + '/imgs/live/105.png',
title: '暂停中'
},
'106': {
img: this.$IMG_URL + '/imgs/live/106.png',
title: '异常'
},
'107': {
img: this.$IMG_URL + '/imgs/live/past.png',
title: '已过期'
}
}
};
},
props: {
detail: {
type: Object,
default: null
},
wh: {
type: Number,
default: 345
},
type: {
type: [Number, String],
default: 2
}
},
computed: {},
created() {
this.getLiveStatus();
},
mounted() {
let that = this;
timer = setInterval(() => {
that.getLiveStatus();
}, 60000);
},
beforeDestroy() {
timer = null;
clearInterval(timer);
},
methods: {
goRoom() {
let that = this;
wx.navigateTo({
url: `plugin-private://wx2b03c6e691cd7370/pages/live-player-plugin?room_id=${that.detail.room_id}`
});
},
// 轮询liveStatus
getLiveStatus() {
if (HAS_LIVE) {
let that = this;
let date = '';
if (that.liveState == 102) {
date = that.$u.timeFormat(that.detail.starttime, 'mm-dd hh:MM');
that.liveStatus['102'].title = '预告 ' + date;
}
livePlayer
.getLiveStatus({ room_id: that.detail.room_id })
.then(res => {
// 101: 直播中, 102: 未开始, 103: 已结束, 104: 禁播, 105: 暂停中, 106: 异常,107:已过期
that.liveState = res.liveStatus;
})
.catch(err => {
console.log('get live status', err);
});
}
}
}
};
</script>
<style lang="scss">
// 小卡片
.sp-live-card {
width: 335rpx;
box-shadow: 0px 0px 10rpx 4rpx rgba(199, 199, 199, 0.22);
border-radius: 20rpx;
height: 100%;
overflow: auto;
}
.live-content {
position: relative;
width: 335rpx;
height: 335rpx;
overflow: hidden;
.item-cover {
background-color: #eee;
width: 100%;
height: 100%;
border-radius: 20rpx 20rpx 0 0;
}
.item-status {
position: absolute;
top: 20rpx;
left: 10rpx;
height: 40rpx;
background: rgba(0, 0, 0, 0.4);
border-radius: 20rpx;
display: flex;
justify-content: center;
align-items: center;
.status-img {
width: 40rpx;
height: 40rpx;
}
.status-text {
font-size: 22rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
padding: 0 10rpx;
}
}
.item-title {
width: 335rpx;
position: absolute;
bottom: 0;
line-height: 60rpx;
padding: 0 20rpx;
font-size: 26rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
background: linear-gradient(transparent, rgba(#000, 0.5));
padding-right: 60rpx;
}
.like-img {
position: absolute;
bottom: 20rpx;
right: 10rpx;
width: 60rpx;
height: 130rpx;
}
}
.live-bottom {
background-color: #fff;
padding: 20rpx;
width: 335rpx;
.live-info {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.info-box {
display: flex;
align-items: center;
}
.info-avatar {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
margin-right: 10rpx;
background: #eee;
}
.info-name {
width: 150rpx;
font-size: 24rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
}
.views {
font-size: 20rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
}
}
.live-goods {
display: flex;
align-items: center;
margin-top: 20rpx;
&__item {
position: relative;
width: 96rpx;
height: 96rpx;
border: 1rpx solid rgba(238, 238, 238, 1);
border-radius: 10rpx;
overflow: hidden;
margin-right: 8rpx;
&:nth-child(3n) {
margin-right: 0;
}
}
&__img {
background: #eee;
width: 100%;
height: 100%;
}
&__price {
position: absolute;
bottom: 0;
line-height: 40rpx;
width: 100%;
background: linear-gradient(transparent, rgba(#000, 0.5));
font-size: 20rpx;
color: #fff;
}
&__mark {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
margin: auto;
display: flex;
justify-content: center;
align-items: center;
background: rgba(#000, 0.3);
font-size: 24rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
}
}
}
// 单个大图直播
.big-card-wrap {
.content-one__item {
position: relative;
height: 280rpx;
border-radius: 20rpx;
margin-top: 16rpx;
overflow: hidden;
.item-cover {
background-color: #eee;
width: 100%;
height: 100%;
}
.item-status {
position: absolute;
top: 20rpx;
left: 10rpx;
height: 40rpx;
background: rgba(0, 0, 0, 0.4);
border-radius: 20rpx;
display: flex;
justify-content: center;
align-items: center;
.status-img {
width: 38rpx;
height: 38rpx;
}
.status-text {
font-size: 22rpx;
font-family: PingFang SC;
font-weight: 500;
color: rgba(255, 255, 255, 1);
padding: 0 10rpx;
}
}
.item-title {
width: 100%;
position: absolute;
bottom: 0;
line-height: 60rpx;
padding: 0 20rpx;
font-size: 26rpx;
font-family: PingFang SC;
font-weight: 500;
color: rgba(255, 255, 255, 1);
background: linear-gradient(transparent, rgba(#000, 0.5));
}
.like-img {
position: absolute;
bottom: 20rpx;
right: 10rpx;
width: 60rpx;
height: 130rpx;
}
}
}
</style>

@ -0,0 +1,130 @@
<template>
<view class="goods-box u-flex u-col-top" @tap="click">
<view class="goods__tag" v-show="tag"><image class="tag-img" :src="tag" mode="widthFix"></image></view>
<image class="goods_img" lazy-load fade-show :src="image" mode="aspectFill"></u-image>
<view class="u-m-l-20">
<view class="goods-title u-ellipsis-2 u-m-b-10">{{ title }}</view>
<view v-if="subtitle" class="describe-text u-m-b-10 u-ellipsis-1">{{ subtitle }}</view>
<slot name="describe"></slot>
<slot name="cardBottom">
<view class="u-flex u-col-bottom font-OPPOSANS">
<view class="price u-m-r-10">{{ price }}</view>
<view class="origin-price">{{ originPrice }}</view>
</view>
</slot>
</view>
</view>
</template>
<script>
/**
* 商品小卡片
* @property {String} title - 标题
* @property {String} subtitle - 副标题
* @property {String} image - 图片地址
* @property {String} describe - 描述信息
* @property {String | Number} price - 价格
* @property {String | Number} originPrice - 原价
* @property {String} tag - 商品标签
* @property {Number} number - 商品数量
* @event {Function} click - 点击卡片
*/
export default {
components: {},
data() {
return {};
},
props: {
image: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
subtitle: {
type: String,
default: ''
},
describe: {
type: String,
default: ''
},
price: {
type: [Number, String],
default: ''
},
originPrice: {
type: [Number, String],
default: ''
},
tag: {
type: String,
default: ''
},
number: {
type: Number,
default: 0
}
},
computed: {},
created() {},
methods: {
click() {
this.$emit('click');
}
}
};
</script>
<style lang="scss">
.goods-box {
position: relative;
.goods__tag {
position: absolute;
top: 0;
left: 0;
z-index: 5;
.tag-img {
width: 60rpx;
}
}
.goods_img{
width: 180rpx;
height: 180rpx;
border-radius: 6rpx;
}
.goods-title {
font-size: 28rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
width: 450rpx;
line-height: 40rpx;
}
.describe-text {
font-size: 24rpx;
width: 450rpx;
color: #a8700d;
}
.price {
color: $u-type-error;
font-weight: 600;
&::before {
content: '¥';
font-size: 20rpx;
}
}
.origin-price {
color: $u-type-info-disabled;
font-size: 24rpx;
text-decoration: line-through;
&::before {
content: '¥';
font-size: 20rpx;
}
}
}
</style>

@ -0,0 +1,130 @@
<template>
<view>
<view class="cu-custom" :style="[{ height: customBar + 'px' }]">
<view class="cu-bar u-flex u-row-between" :class="{ fixed: isFixed }" :style="[style]">
<view class="action u-flex" @tap="goBack" v-if="isBack">
<view class="u-iconfont" :class="'uicon-' + backIconName" :style="{ color: backIconColor, fontSize: backIconSize + 'rpx' }"></view>
<view class="u-back-text u-line-1 u-m-l-20" v-if="backText" :style="[backTextStyle]">{{ backText || '' }}</view>
</view>
<view class="content" :style="[{ top: statusBarHeight + 'px' }]"><slot name="content"></slot></view>
<slot name="right"></slot>
</view>
</view>
</view>
</template>
<script>
/**
* shopro-navbar -自定义标题栏
* @property {Boolean} isBack = [true] - 是否显示返回按钮
* @property {Booelan} isFixed = [true] - 是否开启定位
*/
// 获取系统状态栏的高度
let systemInfo = uni.getSystemInfoSync();
let menuButtonInfo = {};
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
export default {
name: 'shopro-tabbar',
data() {
return {
statusBarHeight: systemInfo.statusBarHeight
};
},
computed: {
// tabbar样式
style() {
let statusBarHeight = systemInfo.statusBarHeight;
let style = {};
style.height = `${this.customBar}px`;
style.paddingTop = `${statusBarHeight}px`;
Object.assign(style, this.background);
return style;
},
// 高度
customBar() {
let statusBarHeight = systemInfo.statusBarHeight;
// #ifndef MP
return systemInfo.platform == 'android' ? statusBarHeight + 50 : statusBarHeight + 45;
// #endif
// #ifdef MP-WEIXIN
return menuButtonInfo.bottom + menuButtonInfo.top - statusBarHeight;
// #endif
// #ifdef MP-ALIPAY
return statusBarHeight + systemInfo.titleBarHeight;
// #endif
}
},
props: {
isBack: {
type: Boolean,
default: true
},
isFixed: {
type: Boolean,
default: true
},
background: {
type: Object,
default() {
return {
background: '#ffffff'
};
}
},
// 返回箭头的颜色
backIconColor: {
type: String,
default: '#606266'
},
// 左边返回的图标
backIconName: {
type: String,
default: 'arrow-left'
},
// 左边返回图标的大小,rpx
backIconSize: {
type: [String, Number],
default: '44'
},
// 返回的文字提示
backText: {
type: String,
default: ''
},
// 返回的文字的 样式
backTextStyle: {
type: Object,
default() {
return {
color: '#606266'
};
}
}
},
methods: {
goBack() {
uni.navigateBack();
}
}
};
</script>
<style lang="scss" scoped>
.cu-custom {
width: 100%;
}
.cu-bar {
width: 100%;
.content {
width: 560rpx;
display: flex;
flex-direction: row;
flex: 1;
align-items: center;
pointer-events: auto;
}
}
</style>

@ -0,0 +1,71 @@
<template>
<view class="popup-box" v-show="newPopupList && newPopupList.length">
<view class="" v-for="(p, index) in newPopupList" :key="index">
<view class="cu-modal" :class="{ show: showModal }" @tap="hideModal(p, index)" v-if="popupCurrent === index && p.image">
<view class="cu-dialog" style="width: 610rpx;background: none;">
<view class="img-box" @tap.stop="onPopup(p.path)"><image class="modal-img" :src="p.image" mode="aspectFit"></image></view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 广告模态框。连续弹窗和只弹一次。
* @property {Object} newPopupList - vuex 初始化传过来的数据
*/
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
let timer = null;
export default {
name: 'shoproNoticeModal',
components: {},
data() {
return {
popupCurrent: 0,
showModal: true
};
},
props: {},
computed: {
...mapGetters(['popupData', 'isLogin']),
newPopupList() {
if (this.popupData) {
return this.popupData.list;
}
}
},
beforeDestroy() {
clearTimeout(timer);
timer = null;
},
methods: {
hideModal(p, index) {
clearTimeout(timer);
this.showModal = false;
if (p.style == 1) {
this.$store.commit('delPopup', index);
}
timer = setTimeout(() => {
this.popupCurrent += 1;
this.showModal = true;
}, 500);
},
onPopup(path) {
this.$tools.routerTo(path);
}
}
};
</script>
<style lang="scss">
.img-box {
position: relative;
width: 610rpx;
.modal-img {
width: 100%;
will-change: transform;
height: 830rpx;
}
}
</style>

@ -0,0 +1,571 @@
<template>
<view class="content">
<!-- 海报弹窗 -->
<view class="cu-modal" :class="{ show: showPoster }" @tap="onClosePoster">
<view class="cu-dialog" style="width: 640rpx;background: none;">
<view class="poster-img-box"><image class="poster-img" :src="posterImage" mode="widthFix"></image></view>
<view class="poster-btn-box u-m-t-20 u-flex u-row-between u-col-center" v-show="posterImage">
<button class="cancel-btn u-reset-button" @tap="showPoster = false">取消</button>
<button class="save-btn u-reset-button" @tap="saveImage">{{ ['wxOfficialAccount', 'H5'].includes(platform) ? '长按图片保存' : '保存图片' }}</button>
</view>
</view>
</view>
<!-- 分享tools -->
<view class="cu-modal bottom-modal" :class="{ show: showShare }" @tap="showShare = false">
<view class="cu-dialog safe-area-inset-bottom" style="border-radius: 20rpx 20rpx 0 0;background: none;">
<view class="share-box">
<view class="share-list-box u-flex">
<!-- #ifdef MP-WEIXIN -->
<button class="share-item share-btn u-flex-col u-col-center" open-type="share">
<image class="share-img" :src="$IMG_URL + '/imgs/share/share_wx.png'" mode=""></image>
<text class="share-title">微信好友</text>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<view v-if="platform !== 'H5'" class="share-item u-flex-col u-col-center" @tap="shareFriend">
<image class="share-img" :src="$IMG_URL + '/imgs/share/share_wx.png'" mode=""></image>
<text class="share-title">微信好友</text>
</view>
<!-- #endif -->
<view class="share-item u-flex-col u-col-center" @tap="onPoster">
<image class="share-img" :src="$IMG_URL + '/imgs/share/share_poster.png'" mode=""></image>
<text class="share-title">生成海报</text>
</view>
<view class="share-item u-flex-col u-col-center" @tap="copySharePath">
<image class="share-img" :src="$IMG_URL + '/imgs/share/share_link.png'" mode=""></image>
<text class="share-title">复制链接</text>
</view>
</view>
<view class="share-foot u-flex u-row-center u-col-center" @tap="showShare = false">取消</view>
</view>
</view>
</view>
<!-- 分享指引 -->
<view class="cu-modal bottom-modal" :class="{ show: showShareGuide }" @tap="showShareGuide = false">
<view class="cu-dialog safe-area-inset-bottom" style="border-radius: 20rpx 20rpx 0 0;background: none;vertical-align: top;">
<view class="guide-wrap u-flex u-col-top u-row-center"><image class="guide-img" :src="$IMG_URL + '/imgs/share/share_guide.png'" mode=""></image></view>
</view>
</view>
<!-- 各海报模块 -->
<shopro-canvas v-if="showPoster" ref="shoproCanvas" :canvasParams="canvasParams" @success="onSuccess"></shopro-canvas>
</view>
</template>
<script>
/**
* 分享弹窗
* @property {Boolean} value = showModal - v-model控制显隐
* @property {String} posterType - 海报类别
* @property {Object} posterInfo - 海报数据
*/
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
import Auth from '@/shopro/permission/index.js';
export default {
name: 'shoproShare',
components: {},
data() {
return {
showShareGuide: false, //H5的指引。
showPoster: false, //海报弹窗
platform: this.$platform.get(),
posterImage: '',
canvasParams: {}
};
},
props: {
posterType: {
type: String,
default: ''
},
posterInfo: {
type: Object,
default: () => {}
},
value: {}
},
computed: {
...mapGetters(['initShare', 'userInfo', 'isLogin', 'shareInfo']),
showShare: {
get() {
return this.value;
},
set(val) {
if (!this.showPoster) {
val ? uni.hideTabBar() : uni.showTabBar();
}
this.$emit('input', val);
}
}
},
created() {
uni.$on('ON_WX_SHARE', () => {
this.showShare = false;
});
},
methods: {
// 关闭弹窗
onClosePoster() {
this.showPoster = false;
uni.showTabBar();
},
// 绘制成功
onSuccess(e) {
this.posterImage = e;
},
// 开始绘制
onPoster() {
this.posterImage = '';
uni.hideTabBar();
if (this.$store.getters.isLogin) {
this.canvasParams = this.getPosterFormatter();
this.showPoster = true;
} else {
this.$store.dispatch('showAuthModal', 'accountLogin');
}
this.showShare = false;
},
// 分享好友
shareFriend() {
let that = this;
// #ifdef APP-PLUS
uni.share({
provider: 'weixin',
scene: 'WXSceneSession',
type: 0,
href: that.shareInfo.path,
title: that.shareInfo.title,
summary: that.shareInfo.title,
image: that.shareInfo.image,
success: res => {
console.log('success:' + JSON.stringify(res));
this.showShare = false;
},
fail: err => {
console.log('fail:' + JSON.stringify(err));
}
});
// #endif
// #ifdef H5
this.showShare = false;
this.showShareGuide = true;
// #endif
},
// 保存图片
async saveImage() {
let that = this;
if (['wxOfficialAccount', 'H5'].includes(this.platform)) {
this.$u.toast('长按图片保存');
return false;
}
let authState = await new Auth('writePhotosAlbum').check();
if (authState) {
uni.saveImageToPhotosAlbum({
filePath: that.posterImage,
success: res => {
this.$u.toast('保存成功');
this.showPoster = false;
},
fail: err => {
console.log(`图片保存失败:`, err);
this.$u.toast('保存失败');
}
});
}
},
// 复制链接
copySharePath() {
let that = this;
uni.setClipboardData({
data: that.shareInfo.copyLink,
success: data => {
that.$u.toast('已复制到剪切板');
that.showShare = false;
}
});
},
// 获取海报格式,规则说明在@/shopro/poster/tools.js中的initDrawArray
getPosterFormatter() {
const that = this;
let data = {};
switch (this.posterType) {
case 'user':
data = {
backgroundImage: that.initShare.user_poster_bg,
drawArray: [
{
name: '用户昵称',
type: 'text',
text: that.userInfo.nickname,
isBgCenter: true,
size: 28,
dy: 250,
color: '#333',
textAlign: 'middle',
textBaseLine: 'middle'
},
{
name: 'avatar',
type: 'image',
url: that.userInfo.avatar,
alpha: 1,
isBgCenter: true,
dy: 95,
dWidth: 120,
dHeight: 120,
circleSet: {}
},
// #ifdef MP-WEIXIN
{
name: 'wxCode',
type: 'image',
url: `${that.$API_URL}wechat/wxacode?scene=${that.shareInfo.query}`,
alpha: 1,
dy: 560,
isBgCenter: true,
dWidth: 180,
dHeight: 180
},
// #endif
// #ifndef MP-WEIXIN
{
name: '普通二维码',
type: 'qrcode',
text: that.shareInfo.path,
size: 180,
dy: 560,
isBgCenter: true
}
// #endif
]
};
break;
case 'goods':
data = {
backgroundImage: that.initShare.goods_poster_bg,
drawArray: [
{
name: 'avatar',
type: 'image',
url: that.userInfo.avatar,
alpha: 1,
dy: 40,
dx: 38,
dWidth: 80,
dHeight: 80,
circleSet: {}
},
{
type: 'text',
text: that.userInfo.nickname,
size: 28,
color: '#333',
alpha: 1,
textAlign: 'middle',
textBaseline: 'bottom',
dx: 140,
dy: 40
},
{
type: 'text',
text: '推荐一个好物给你,请查收!',
size: 26,
color: '#333',
alpha: 1,
textAlign: 'middle',
textBaseline: 'middle',
dx: 140,
dy: 80
},
{
name: 'goodsImage',
type: 'image',
url: that.posterInfo.image,
alpha: 1,
isBgCenter: true,
dy: 140,
dWidth: 620,
dHeight: 620,
roundRectSet: {
r: 20
}
},
{
type: 'text', //标题
text: that.posterInfo.title,
size: 28,
color: '#333',
alpha: 1,
textAlign: 'left',
textBaseline: 'middle',
fontWeight: 'bold',
lineFeed: {
maxWidth: 620,
lineHeight: 40,
lineNum: 2
},
dx: 36,
dy: 780
},
{
type: 'text',
text: `¥${that.posterInfo.price}`,
size: 38,
color: '#E1212B',
alpha: 1,
textAlign: 'left',
textBaseline: 'middle',
fontWeight: 'bold',
fontFamily: 'OPPOSANS',
dx: 30,
dy: 860
},
{
type: 'text',
text: `¥${that.posterInfo.original_price}`,
size: 28,
color: '#999999',
alpha: 1,
textAlign: 'left',
textBaseline: 'top',
lineThrough: {
style: '#999999'
},
dx: 400,
dy: 860
},
// #ifdef MP-WEIXIN
{
name: 'wxCode',
type: 'image', //微信小程序码
url: `${that.$API_URL}wechat/wxacode?scene=${that.shareInfo.query}`,
alpha: 1,
dx: 522,
dy: 911,
dWidth: 110,
dHeight: 110
},
// #endif
// #ifndef MP-WEIXIN
{
type: 'qrcode',
text: that.shareInfo.copyLink,
size: 110,
dx: 530,
dy: 930
}
// #endif
]
};
break;
case 'groupon':
data = {
backgroundImage: that.initShare.groupon_poster_bg,
drawArray: [
{
name: 'avatar',
type: 'image',
url: that.userInfo.avatar,
alpha: 1,
dy: 40,
dx: 38,
dWidth: 80,
dHeight: 80,
circleSet: {}
},
{
type: 'text',
text: that.userInfo.nickname,
size: 28,
color: '#333',
alpha: 1,
textAlign: 'middle',
textBaseline: 'bottom',
dx: 140,
dy: 40
},
{
type: 'text',
text: '发现一个好物,快来和我一起拼吧!',
size: 26,
color: '#333',
alpha: 1,
textAlign: 'middle',
textBaseline: 'middle',
dx: 140,
dy: 80
},
{
name: 'goodsImage',
type: 'image',
url: that.posterInfo.goods.image,
alpha: 1,
isBgCenter: true,
dy: 140,
dWidth: 620,
dHeight: 620,
roundRectSet: {
r: 20
}
},
{
type: 'text', //标题
text: that.posterInfo.goods.title,
size: 28,
color: '#333',
alpha: 1,
textAlign: 'left',
textBaseline: 'middle',
fontWeight: 'bold',
lineFeed: {
maxWidth: 620,
lineHeight: 40,
lineNum: 2
},
dx: 36,
dy: 780
},
{
type: 'text',
text: `拼团价:¥${that.posterInfo.goods.groupon_price}`,
size: 32,
color: '#E1212B',
alpha: 1,
textAlign: 'left',
textBaseline: 'middle',
fontWeight: 'bold',
fontFamily: 'OPPOSANS',
dx: 30,
dy: 860
},
{
type: 'text',
text: `${that.posterInfo.num}人团`,
size: 24,
color: '#fff',
alpha: 1,
textAlign: 'left',
textBaseline: 'top',
dx: 565,
dy: 863
},
// #ifdef MP-WEIXIN
{
name: 'wxCode',
type: 'image', //微信小程序码
url: `${that.$API_URL}wechat/wxacode?scene=${that.shareInfo.query}`,
alpha: 1,
dx: 530,
dy: 930,
dWidth: 110,
dHeight: 110
},
// #endif
// #ifndef MP-WEIXIN
{
type: 'qrcode',
text: that.shareInfo.path,
size: 110,
dx: 530,
dy: 930
}
// #endif
]
};
break;
default:
console.log('%cerr:没有此类型海报数据', 'color:green;background:yellow');
break;
}
return data;
}
}
};
</script>
<style lang="scss">
// 指引
.guide-wrap {
height: 100%;
.guide-img {
width: 580rpx;
height: 430rpx;
}
}
// 分享海报
.poster-btn-box {
.cancel-btn {
width: 300rpx;
height: 70rpx;
line-height: 70rpx;
background: #ffffff;
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: #999999;
}
.save-btn {
width: 300rpx;
height: 70rpx;
line-height: 70rpx;
background: linear-gradient(90deg, #e9b461, #eecc89);
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
}
}
.poster-img-box {
.poster-img {
width: 660rpx;
min-height: 800rpx;
border-radius: 20rpx;
}
}
// 分享tool
.share-box {
background: #fff;
width: 750rpx;
border-radius: 30rpx 30rpx 0 0;
padding-top: 30rpx;
position: relative;
.share-foot {
font-size: 24rpx;
color: #bfbfbf;
height: 80rpx;
border-top: 1rpx solid #eee;
}
.share-list-box {
.share-btn {
background: none;
border: none;
line-height: 1;
padding: 0;
&::after {
border: none;
}
}
.share-item {
flex: 1;
padding-bottom: 20rpx;
.share-img {
width: 70rpx;
height: 70rpx;
background: rgba(246, 246, 254, 1);
border-radius: 50%;
margin-bottom: 20rpx;
}
.share-title {
font-size: 24rpx;
color: #666;
}
}
}
}
</style>

@ -0,0 +1,589 @@
<template>
<!-- 规格 -->
<view class="cu-modal bottom-modal" @touchmove.stop.prevent="" :class="{ show: showModal }"
@tap.stop="showModal = false" v-if="goodsInfo.sku_price">
<view class="cu-dialog" @tap.stop style="border-radius: 30rpx 30rpx 0 0;">
<view class="shop-modal page_box" :style="goodsInfo.is_sku == 0 ? 'height:700rpx' : ''">
<text class="u-iconfont uicon-close-circle-fill close-icon" @tap.stop="showModal = false"></text>
<!-- 商品卡片-->
<view class="top u-flex modal-head__box">
<image class="shop-img" :src="currentSkuPrice.image ? currentSkuPrice.image : goodsInfo.image"
mode="aspectFill"></image>
<view class=" goods-box u-flex-col u-row-between">
<view class="goods-title u-ellipsis-2">{{ goodsInfo.title }}</view>
<view class="u-flex u-row-between goods-bottom">
<view class="price-box u-flex">
<view v-if="goodsType === 'score'">{{ currentSkuPrice.price_text || goodsInfo.price }}
</view>
<view v-else-if="grouponBuyType === 'groupon'">
¥{{ currentSkuPrice.groupon_price || (goodsInfo.activity_type === 'groupon' ? goodsInfo.groupon_price : goodsInfo.price) }}
</view>
<view v-else>¥{{ currentSkuPrice.price || goodsInfo.price }}</view>
</view>
<text class="stock">库存{{ currentSkuPrice.stock || goodsInfo.stock }}件</text>
</view>
</view>
</view>
<!-- 规格选项 -->
<scroll-view scroll-y class="content_box">
<view class="select-box u-flex-col u-row-left" v-for="(s, x) in skuList" :key="s.id">
<view class="type-title u-flex">{{ s.name }}</view>
<view class="tag-box u-flex u-flex-wrap">
<button class="tag u-reset-button" v-for="(sc, y) in s.content" :key="sc.id"
:class="{ 'tag-active': currentSkuArray[sc.pid] == sc.id, 'tag-disabled': sc.disabled == true }"
:disabled="sc.disabled == true" @tap="chooseSku(sc.pid, sc.id)">
{{ sc.name }}
</button>
</view>
</view>
<!-- 计步器 -->
<view class="buy-num-box u-flex u-row-between">
<view class="num-title">购买数量</view>
<u-number-box v-model="goodsNum" :min="1" :step="1" :max="maxStep" @plus="plus"
:long-press="false" @change="changeNum"></u-number-box>
</view>
</scroll-view>
<!-- 功能按钮 -->
<view class="btn-box foot_box u-flex u-row-between" v-if="buyType === 'cart' || buyType === 'buy'">
<button class="u-reset-button cu-btn save-btn"
v-if="(activityRules && activityRules.status === 'ing') || !goodsInfo.activity_type"
@tap="confirm">确认</button>
<button class="u-reset-button cu-btn cancel-btn"
v-if="activityRules && activityRules.status !== 'ing' && goodsInfo.activity_type"
@tap="confirm">确定</button>
</view>
<view class="btn-box foot_box u-flex u-row-between" v-else>
<button class="u-reset-button cu-btn cart-btn" @tap="confirmCart">加入购物车</button>
<button class="u-reset-button cu-btn buy-btn" @tap="confirmBuy">立即购买</button>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 多规格组件
* @property {Object} goodsInfo - 商品数据
* @property {Boolean} value = showModal - 显隐
* @property {String} buyType - 购买方式
* @property {String} goodsType - 商品类别
* @property {String} grouponBuyType -拼团商品购买方式
* @property {Number} grouponId - 拼团ID,分享进入
* @property {Object} activityRules - 活动状态。
*/
import { mapMutations, mapActions, mapState } from 'vuex';
export default {
components: {},
data() {
return {
maxStep: 999,
skuList: [],
currentSkuPrice: {},
currentSkuArray: [],
goodsNum: 1,
confirmGoodsInfo: {},
type: this.buyType
};
},
props: {
goodsInfo: {},
activityRules: {},
value: {},
buyType: {
type: String,
default: 'sku'
},
goodsType: {
type: String,
default: 'goods'
},
grouponBuyType: {
type: String,
default: 'alone'
},
grouponId: {
//参加拼团的时候,传入当前团id;
type: Number,
default: 0
}
},
created() {
this.skuList = this.goodsInfo.sku;
this.changeDisabled(false);
},
mounted() {
// 单规格选项
if (!this.goodsInfo.is_sku) {
this.currentSkuPrice = this.skuPrice[0];
this.maxStep = this.skuPrice[0].stock > 999 ? 999 : this.skuPrice[0].stock;
}
},
watch: {
type(nweVal, oldVal) {
return newVal;
}
},
computed: {
skuPrice() {
return this.goodsInfo.sku_price;
},
showModal: {
get() {
return this.value;
},
set(val) {
val ? uni.hideTabBar() : uni.showTabBar();
this.$emit('input', val);
return val;
}
},
currentSkuText() {
let that = this;
let str = '';
let currentSkuArray = this.currentSkuArray;
currentSkuArray.forEach(v => {
that.skuList.forEach(s => {
s.content.forEach(u => {
if (u.id === v) {
str += ' ' + u.name;
}
});
});
});
that.$emit('getSkuText', str);
return str;
}
},
methods: {
...mapActions(['addCartGoods', 'getCartList']),
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 选择规格
chooseSku(pid, skuId) {
let that = this;
let isChecked = true; // 选中 or 取消选中
this.goodsNum = 1; //选择规格时,数量重置为1.
this.maxStep = 999; //选择其他规格时,取消上个规格库存限制
if (that.currentSkuArray[pid] != undefined && that.currentSkuArray[pid] == skuId) {
// 点击已被选中的,删除并填充 ''
isChecked = false;
that.currentSkuArray.splice(pid, 1, '');
} else {
// 选中
that.$set(that.currentSkuArray, pid, skuId);
}
let chooseSkuId = []; // 选中的规格大类
that.currentSkuArray.forEach(sku => {
if (sku != '') {
// sku 为空是反选 填充的
chooseSkuId.push(sku);
}
});
// 当前所选规格下,所有可以选择的 skuPric
let newPrice = this.getCanUseSkuPrice();
// 判断所有规格大类是否选择完成
if (chooseSkuId.length == that.skuList.length && newPrice.length) {
that.currentSkuPrice = newPrice[0];
} else {
that.currentSkuPrice = {};
}
// 改变规格项禁用状态
this.changeDisabled(isChecked, pid, skuId);
},
// 改变禁用状态
changeDisabled(isChecked = false, pid = 0, skuId = 0) {
let newPrice = []; // 所有可以选择的 skuPrice
if (isChecked) {
// 选中规格
// 当前点击选中规格下的 所有可用 skuPrice
for (let price of this.skuPrice) {
if (price.stock <= 0) {
// this.goodsNum 不判断是否大于当前选择数量,在 uni-number-box 判断,并且 取 stock 和 goods_num 的小值
continue;
}
if (price.goods_sku_id_arr.indexOf(skuId.toString()) >= 0) {
newPrice.push(price);
}
}
} else {
// 取消选择规格
// 当前所选规格下,所有可以选择的 skuPric
newPrice = this.getCanUseSkuPrice();
}
// 所有存在并且有库存未选择的规格项 的 子项 id
let noChooseSkuIds = [];
for (let price of newPrice) {
noChooseSkuIds = noChooseSkuIds.concat(price.goods_sku_id_arr);
}
// 去重
noChooseSkuIds = Array.from(new Set(noChooseSkuIds));
if (isChecked) {
// 去除当前选中的规格项
let index = noChooseSkuIds.indexOf(skuId.toString());
noChooseSkuIds.splice(index, 1);
} else {
// 循环去除当前已选择的规格项
this.currentSkuArray.forEach(sku => {
if (sku.toString() != '') {
// sku 为空是反选 填充的
let index = noChooseSkuIds.indexOf(sku.toString());
if (index >= 0) {
// sku 存在于 noChooseSkuIds
noChooseSkuIds.splice(index, 1);
}
}
});
}
// 当前已选择的规格大类
let chooseSkuKey = [];
if (!isChecked) {
// 当前已选择的规格大类
this.currentSkuArray.forEach((sku, key) => {
if (sku != '') {
// sku 为空是反选 填充的
chooseSkuKey.push(key);
}
});
} else {
// 当前点击选择的规格大类
chooseSkuKey = [pid];
}
for (let i in this.skuList) {
// 当前点击的规格,或者取消选择时候 已选中的规格 不进行处理
if (chooseSkuKey.indexOf(this.skuList[i]['id']) >= 0) {
continue;
}
for (let j in this.skuList[i]['content']) {
// 如果当前规格项 id 不存在于有库存的规格项中,则禁用
if (noChooseSkuIds.indexOf(this.skuList[i]['content'][j]['id'].toString()) >= 0) {
this.skuList[i]['content'][j]['disabled'] = false;
} else {
this.skuList[i]['content'][j]['disabled'] = true;
}
}
}
},
// 当前所选规格下,获取所有有库存的 skuPrice
getCanUseSkuPrice() {
let newPrice = [];
for (let price of this.skuPrice) {
if (price.stock <= 0) {
// || price.stock < this.goodsNum 不判断是否大于当前选择数量,在 uni-number-box 判断,并且 取 stock 和 goods_num 的小值
continue;
}
var isOk = true;
this.currentSkuArray.forEach(sku => {
// sku 不为空,并且,这个 条 skuPrice 没有被选中,则排除
if (sku.toString() != '' && price.goods_sku_id_arr.indexOf(sku.toString()) < 0) {
isOk = false;
}
});
if (isOk) {
newPrice.push(price);
}
}
return newPrice;
},
// 数量
changeNum(e) {
this.changeDisabled(false);
},
// 增加
plus(e) {
if (e.value >= this.currentSkuPrice.stock) {
this.maxStep = this.currentSkuPrice.stock;
this.$u.toast('库存不足');
return;
}
if (this.goodsInfo.activity_type === 'seckill' || this.goodsInfo.activity_type === 'groupon') {
let rules = this.goodsInfo.activity.rules;
if (rules.limit_buy != 0 && e.value >= rules.limit_buy) {
this.maxStep = rules.limit_buy;
this.$u.toast('本次活动最多购买' + rules.limit_buy + '件');
return;
}
}
},
// 加入购物车确定
confirmCart() {
let that = this;
if (this.confirmSku()) {
let confirmGoodsList = {
list: [that.confirmGoodsInfo],
from: 'goods'
};
that.addCartGoods(confirmGoodsList).then(res => {
if (res.code === 1) {
that.showModal = false;
that.$u.toast(res.msg);
}
});
}
},
// 立即购买
confirmBuy() {
let that = this;
that.showModal = false;
if (this.confirmSku()) {
let confirmGoodsList = [];
confirmGoodsList.push(that.confirmGoodsInfo);
that.jump('/pages/order/confirm', {
goodsList: confirmGoodsList,
from: 'goods',
orderType: that.goodsType,
grouponBuyType: that.grouponBuyType,
grouponId: that.grouponId
});
}
},
// 确定
confirm() {
if (this.confirmSku()) {
switch (this.buyType) {
case 'cart':
this.confirmCart();
break;
case 'buy':
this.confirmBuy();
break;
default:
}
}
},
// 确定规格
confirmSku() {
let that = this;
if (that.currentSkuPrice.stock == 0 || that.currentSkuPrice.stock < that.goodsNum) {
that.$u.toast('库存不足');
that.showModal = false;
return false;
} else {
that.currentSkuPrice.goods_num = that.goodsNum;
that.confirmGoodsInfo = {
goods_id: that.currentSkuPrice.goods_id,
goods_num: that.currentSkuPrice.goods_num,
sku_price_id: that.currentSkuPrice.id,
goods_price: that.currentSkuPrice.price
};
if (!that.confirmGoodsInfo.sku_price_id) {
that.$u.toast('请选择规格');
return false;
} else {
that.showModal = false;
return true;
}
}
}
}
};
</script>
<style lang="scss" scoped>
.size-box {
line-height: 82rpx;
background: #fff;
padding: 0 20rpx;
margin: 20rpx 0;
font-size: 28rpx;
.title {
color: #999;
margin-right: 20rpx;
}
}
// 规格
.shop-modal {
width: 750rpx;
height: 950rpx;
background: rgba(255, 255, 255, 1);
padding: 20rpx 20rpx 30rpx;
position: relative;
.close-icon {
font-size: 34rpx;
color: #e0e0e0;
position: absolute;
top: 20rpx;
right: 20rpx;
}
// 商品卡片
.top {
margin: 30rpx 0;
border-radius: 20rpx;
padding: 20rpx;
.shop-img {
width: 160upx;
height: 160upx;
border-radius: 6upx;
margin-right: 30upx;
background: #ccc;
}
.goods-box {
height: 160upx;
width: 490rpx;
align-items: flex-start;
.goods-title {
font-size: 28rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
line-height: 42rpx;
text-align: left;
}
.goods-bottom {
width: 100%;
}
.price-box {
font-size: 36upx;
font-weight: bold;
color: #e1212b;
.unit {
font-size: 24upx;
font-weight: bold;
color: #e1212b;
}
}
.stock {
font-size: 26rpx;
color: #999;
}
}
}
// 规格选项
.select-box {
margin-bottom: 30rpx;
.type-title {
font-size: 26upx;
font-weight: 400;
margin-bottom: 20upx;
}
.tag-box {
flex-wrap: wrap;
}
.tag {
line-height: 62rpx;
background: #f4f4f4;
border-radius: 31rpx;
font-size: 28upx;
font-weight: 400;
color: #666666;
padding: 0 30upx;
margin-bottom: 20rpx;
margin-right: 10rpx;
}
.tag-active {
background: linear-gradient(90deg, #e9b461, #eecc89);
font-size: 28rpx;
font-weight: 400;
color: #ffffff;
}
.tag-disabled {
background: #f8f8f8;
color: #cacaca;
}
}
.buy-num-box {
.num-title {
font-size: 26upx;
font-weight: 400;
margin-bottom: 20upx;
}
}
}
.btn-box {
height: 100rpx;
.cu-btn {
width: 340rpx;
line-height: 70rpx;
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
padding: 0;
}
.cart-btn {
background: linear-gradient(90deg, rgba(103, 104, 105, 1), rgba(82, 82, 82, 1));
box-shadow: 0px 2rpx 5rpx 0px rgba(102, 103, 104, 0.46);
}
.buy-btn {
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
}
.save-btn {
width: 710rpx;
height: 70rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
font-size: 28rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
border-radius: 35rpx;
padding: 0;
}
.cancel-btn {
width: 710rpx;
height: 70rpx;
background: rgba(221, 221, 221, 1);
font-size: 28rpx;
font-weight: 500;
color: #999999;
border-radius: 35rpx;
padding: 0;
}
}
</style>

@ -0,0 +1,142 @@
<template>
<view class="shopro-tabbar-wrap" v-if="tabbarList && tabbarList.length && showTabbar">
<view class="tabbar-box" :style="{ background: tabbarData.bgcolor || '#fff' }">
<view class="tabbar-item" v-for="(tab, index) in tabbarList" :key="tab.name" @tap="switchTabbar(tab, index)">
<view class="img-box">
<image
class="tabbar-icon"
v-if="tabbarData.style == 1 || tabbarData.style == 2"
:src="currentPath == tab.path ? tab.activeImage : tab.image"
mode="aspectFill"
></image>
<!-- 购物车角标 -->
<view v-if="tab.path == '/pages/index/cart' && cartNum" class="badge">{{ cartNum }}</view>
</view>
<view
class="tabbar-text"
v-if="tabbarData.style == 1 || tabbarData.style == 3"
:style="{ color: currentPath == tab.path ? tabbarData.activeColor : tabbarData.color }"
>
{{ tab.name }}
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 自定义底部导航
* @property {Array} tabbarList - vuex初始化的底部导航数据
* @property {String} currentPath -computed解析当前页面路径。
*/
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
import { router } from '@/shopro/router';
export default {
name: 'shoproTabbar',
components: {},
data() {
return {
currentPath: '' //当前页面路径
};
},
mounted() {
this.currentPath = this.$Route.path === '/pages/index/index' ? this.$Route.path : this.$Route.fullPath;
},
computed: {
...mapGetters(['cartNum', 'tabbarData']),
// 底部导航栏列表
tabbarList() {
if (this.tabbarData) {
return this.tabbarData.list;
}
},
// 后台tabbarList数据中必需含有'/pages/index/index',不然逻辑混乱
showTabbar() {
if (this.tabbarData && this.tabbarData.list) {
let arr = ['/pages/index/index'];
let path = '';
for (let item of this.tabbarData.list) {
arr.push(item.path);
}
return arr.includes(this.currentPath);
}
}
},
methods: {
// 切换tabbar
switchTabbar(tab, index) {
this.$tools.routerTo(tab.path, true);
}
}
};
</script>
<style lang="scss">
.shopro-tabbar-wrap {
height: calc(100rpx + env(safe-area-inset-bottom) / 2);
padding-bottom: calc(env(safe-area-inset-bottom) / 2);
position: relative;
width: 100%;
z-index: 70;
.tabbar-box {
position: fixed;
display: flex;
align-items: center;
width: 100%;
height: calc(100rpx + env(safe-area-inset-bottom) / 2);
border-top: 1rpx solid #eeeeee;
background-color: #fff;
z-index: 998;
bottom: 0;
padding-bottom: calc(env(safe-area-inset-bottom) / 2);
.tabbar-item {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: 1;
.img-box {
position: relative;
.badge {
position: absolute;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
justify-content: center;
align-items: center;
line-height: 24rpx;
padding: 4rpx 10rpx;
border-radius: 100rpx;
color: #fff;
font-size: 24rpx;
z-index: 9;
background-color: $u-type-error;
transform: scale(0.8);
transform-origin: center center;
top: 0;
left: 46rpx;
white-space: nowrap;
}
}
.tabbar-icon {
width: 50rpx;
height: 50rpx;
display: block;
}
.tabbar-text {
font-size: 20rpx;
margin-top: 4rpx;
}
}
}
}
</style>

@ -0,0 +1,18 @@
/**
* Shopro全局配置文件 v1.4.0
*/
// 接口根域名 https://demo.shopro.top
export const BASE_URL = 'https://demo.shopro.top'
// 接口路径
export const API_URL = `${BASE_URL}/addons/shopro/`
// 全局网络图片地址变量,css背景图片地址变量在uni.scss中定义
export const IMG_URL = 'https://file.shopro.top'
//高德地图开发者Web服务key,逆坐标解析必须
export const MAP_KEY = '2658492***4a9ac5839753dc'
//后台是否开通直播权限,根据情况在manifest.json中引入直播插件,并在pages.json中打开直播页面
export const HAS_LIVE = false

@ -0,0 +1,33 @@
import Vue from "vue";
import App from "./App";
import {
router,
RouterMount
} from "@/shopro/router";
import store from "@/shopro/store";
import uView from "uview-ui";
import shopro from "@/shopro";
async function bootstrap() {
App.mpType = "app";
//引入路由
Vue.use(router);
// 引入全局uView
Vue.use(uView);
// 加载shopro
Vue.use(shopro);
const app = new Vue({
store,
...App
});
// #ifdef H5
RouterMount(app, router, "#app");
// #endif
// #ifndef H5
app.$mount();
// #endif
}
bootstrap();

@ -0,0 +1,233 @@
{
"name" : "Shopro商城",
"appid" : "__UNI_*****06",
"description" : "Shopro商城",
"versionName" : "1.4.0",
"versionCode" : 140,
"transformPx" : false,
"app-plus" : {
"optimization" : {
"subPackages" : true
},
"runmode" : "liberate",
"safearea" : {
"background" : "#fff",
"bottom" : {
"offset" : "auto"
}
},
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 200
},
"usingComponents" : true,
"nvueCompiler" : "uni-app",
"compilerVersion" : 3,
"modules" : {
"Geolocation" : {},
"Maps" : {},
"OAuth" : {},
"Payment" : {},
"Share" : {},
"VideoPlayer" : {}
},
"distribute" : {
"android" : {
"permissions" : [
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<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\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
],
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ]
},
"ios" : {
"capabilities" : {
"entitlements" : {
"com.apple.developer.associated-domains" : [ "applinks:m.*****.top" ]
}
},
"privacyDescription" : {
"NSLocationAlwaysAndWhenInUseUsageDescription" : "需要同意获取您的位置信息才能展示附近信息",
"NSLocationAlwaysUsageDescription" : "需要同意获取您的位置信息才能展示附近信息",
"NSLocationWhenInUseUsageDescription" : "需要同意获取您的位置信息才能展示附近信息",
"NSPhotoLibraryAddUsageDescription" : "需要同意访问您的相册才能保存图片",
"NSPhotoLibraryUsageDescription" : "需要同意访问您的相册才能选取图片",
"NSCameraUsageDescription" : "需要同意访问您的摄像头才能拍摄照片",
"NSUserTrackingUsageDescription" : "请放心,开启权限不会获取您在其他站点的隐私信息,该权限仅用于标识设备并保障服务安全与提升浏览体验"
}
},
"sdkConfigs" : {
"ad" : {},
"payment" : {
"alipay" : {
"__platform__" : [ "ios", "android" ]
},
"weixin" : {
"__platform__" : [ "ios", "android" ],
"appid" : "wx522b*****ae",
"UniversalLinks" : "https://m.*****.top/uni-universallinks/__U*****"
}
},
"geolocation" : {
"amap" : {
"__platform__" : [ "android" ],
"appkey_ios" : "949f86b55f*****de010206494",
"appkey_android" : "0d51d7*****ebf2bcbe264dac409"
}
},
"maps" : {
"amap" : {
"appkey_ios" : "949f86b55f*****de010206494",
"appkey_android" : "0d51d7a*****f2bcbe264dac409"
}
},
"oauth" : {
"weixin" : {
"appid" : "wx522b*****319ae",
"appsecret" : "531b39d953f9*****e83c7238fb4dc",
"UniversalLinks" : "https://m.*****.top/uni-universallinks/__UNI__EB95F06"
},
"apple" : {}
},
"share" : {
"weixin" : {
"appid" : "wx522*****319ae",
"UniversalLinks" : "https://m.*****.top/uni-universallinks/__UNI__EB95F06"
}
},
"push" : {}
},
"icons" : {
"android" : {
"hdpi" : "",
"xhdpi" : "",
"xxhdpi" : "",
"xxxhdpi" : ""
},
"ios" : {
"appstore" : "",
"ipad" : {
"app" : "",
"app@2x" : "",
"notification" : "",
"notification@2x" : "",
"proapp@2x" : "",
"settings" : "",
"settings@2x" : "",
"spotlight" : "",
"spotlight@2x" : ""
},
"iphone" : {
"app@2x" : "",
"app@3x" : "",
"notification@2x" : "",
"notification@3x" : "",
"settings@2x" : "",
"settings@3x" : "",
"spotlight@2x" : "",
"spotlight@3x" : ""
}
}
},
"splashscreen" : {
"useOriginalMsgbox" : false
}
},
"kernel" : {
"ios" : "WKWebview"
}
},
"quickapp" : {},
"mp-weixin" : {
"appid" : "wx3*****67dd0",
"setting" : {
"urlCheck" : false,
"es6" : true, //消除大多数样式警告
"minified" : true,
"postcss" : true,
"checkSiteMap" : false // 关闭页面索引检测
},
"optimization" : {
"subPackages" : true
},
"plugins" : {},
//这段代码,需要放到plugins中。
//只有后台开通小程序直播功能权限,才可以打开,不然报错,程序无法运行。
// "live-player-plugin": {
// "version": "1.3.2", // 注意填写该直播组件最新版本号,微信开发者工具调试时可获取最新版本号
// "provider": "wx2b03c6e691cd7370" // 必须填该直播组件appid,该示例值即为直播组件appid
// }
"usingComponents" : true,
"permission" : {
"scope.userLocation" : {
"desc" : "计算附近商家位置"
}
},
"requiredPrivateInfos" : [ "getLocation", "chooseLocation", "chooseAddress" ]
},
"mp-alipay" : {
"usingComponents" : true,
"component2" : true
},
"mp-qq" : {
"appid" : ""
},
"mp-baidu" : {
"usingComponents" : true,
"appid" : ""
},
"mp-toutiao" : {
"usingComponents" : true,
"appid" : ""
},
"h5" : {
"template" : "template.h5.html",
"router" : {
"mode" : "history",
"base" : ""
},
"optimization" : {
"treeShaking" : {
"enable" : true
}
},
"title" : "Shopro商城",
"devServer" : {
"https" : false,
"port" : ""
},
"sdkConfigs" : {
"maps" : {
"qqmap" : {
"key" : "4NRBZ-CM*****-ARYXH-DCFH3"
}
}
},
"domain" : "*****"
},
"_spaceID" : "f0536a*****8350-2aa7c3e11334"
}

138
package-lock.json generated

@ -0,0 +1,138 @@
{
"name": "shopro-plus",
"version": "1.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "shopro-plus",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"jweixin-module": "^1.6.0",
"uni-read-pages": "^1.0.5",
"uni-simple-router": "^2.0.6",
"vconsole": "^3.7.0"
},
"devDependencies": {}
},
"node_modules/@babel/runtime": {
"version": "7.23.6",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.6.tgz",
"integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/copy-text-to-clipboard": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz",
"integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q==",
"engines": {
"node": ">=12"
}
},
"node_modules/core-js": {
"version": "3.34.0",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.34.0.tgz",
"integrity": "sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag==",
"hasInstallScript": true
},
"node_modules/jweixin-module": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/jweixin-module/-/jweixin-module-1.6.0.tgz",
"integrity": "sha512-dGk9cf+ipipHmtzYmKZs5B2toX+p4hLyllGLF6xuC8t+B05oYxd8fYoaRz0T30U2n3RUv8a4iwvjhA+OcYz52w=="
},
"node_modules/mutation-observer": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/mutation-observer/-/mutation-observer-1.0.3.tgz",
"integrity": "sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA=="
},
"node_modules/regenerator-runtime": {
"version": "0.14.0",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
},
"node_modules/uni-read-pages": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/uni-read-pages/-/uni-read-pages-1.0.5.tgz",
"integrity": "sha512-GkrrZ0LX0vn9R5k6RKEi0Ez3Q3e2vUpjXQ8Z6/K/d28KudI9ajqgt8WEjQFlG5EPm1K6uTArN8LlqmZTEixDUA==",
"hasInstallScript": true
},
"node_modules/uni-simple-router": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/uni-simple-router/-/uni-simple-router-2.0.7.tgz",
"integrity": "sha512-8FKv5dw7Eoonm0gkO8udprrxzin0fNUI0+AvIphFkFRH5ZmP5ZWJ2pvnWzb2NiiqQSECTSU5VSB7HhvOSwD5eA=="
},
"node_modules/vconsole": {
"version": "3.15.1",
"resolved": "https://registry.npmmirror.com/vconsole/-/vconsole-3.15.1.tgz",
"integrity": "sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==",
"dependencies": {
"@babel/runtime": "^7.17.2",
"copy-text-to-clipboard": "^3.0.1",
"core-js": "^3.11.0",
"mutation-observer": "^1.0.3"
}
}
},
"dependencies": {
"@babel/runtime": {
"version": "7.23.6",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.23.6.tgz",
"integrity": "sha512-zHd0eUrf5GZoOWVCXp6koAKQTfZV07eit6bGPmJgnZdnSAvvZee6zniW2XMF7Cmc4ISOOnPy3QaSiIJGJkVEDQ==",
"requires": {
"regenerator-runtime": "^0.14.0"
}
},
"copy-text-to-clipboard": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/copy-text-to-clipboard/-/copy-text-to-clipboard-3.2.0.tgz",
"integrity": "sha512-RnJFp1XR/LOBDckxTib5Qjr/PMfkatD0MUCQgdpqS8MdKiNUzBjAQBEN6oUy+jW7LI93BBG3DtMB2KOOKpGs2Q=="
},
"core-js": {
"version": "3.34.0",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.34.0.tgz",
"integrity": "sha512-aDdvlDder8QmY91H88GzNi9EtQi2TjvQhpCX6B1v/dAZHU1AuLgHvRh54RiOerpEhEW46Tkf+vgAViB/CWC0ag=="
},
"jweixin-module": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/jweixin-module/-/jweixin-module-1.6.0.tgz",
"integrity": "sha512-dGk9cf+ipipHmtzYmKZs5B2toX+p4hLyllGLF6xuC8t+B05oYxd8fYoaRz0T30U2n3RUv8a4iwvjhA+OcYz52w=="
},
"mutation-observer": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/mutation-observer/-/mutation-observer-1.0.3.tgz",
"integrity": "sha512-M/O/4rF2h776hV7qGMZUH3utZLO/jK7p8rnNgGkjKUw8zCGjRQPxB8z6+5l8+VjRUQ3dNYu4vjqXYLr+U8ZVNA=="
},
"regenerator-runtime": {
"version": "0.14.0",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
"integrity": "sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA=="
},
"uni-read-pages": {
"version": "1.0.5",
"resolved": "https://registry.npmmirror.com/uni-read-pages/-/uni-read-pages-1.0.5.tgz",
"integrity": "sha512-GkrrZ0LX0vn9R5k6RKEi0Ez3Q3e2vUpjXQ8Z6/K/d28KudI9ajqgt8WEjQFlG5EPm1K6uTArN8LlqmZTEixDUA=="
},
"uni-simple-router": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/uni-simple-router/-/uni-simple-router-2.0.7.tgz",
"integrity": "sha512-8FKv5dw7Eoonm0gkO8udprrxzin0fNUI0+AvIphFkFRH5ZmP5ZWJ2pvnWzb2NiiqQSECTSU5VSB7HhvOSwD5eA=="
},
"vconsole": {
"version": "3.15.1",
"resolved": "https://registry.npmmirror.com/vconsole/-/vconsole-3.15.1.tgz",
"integrity": "sha512-KH8XLdrq9T5YHJO/ixrjivHfmF2PC2CdVoK6RWZB4yftMykYIaXY1mxZYAic70vADM54kpMQF+dYmvl5NRNy1g==",
"requires": {
"@babel/runtime": "^7.17.2",
"copy-text-to-clipboard": "^3.0.1",
"core-js": "^3.11.0",
"mutation-observer": "^1.0.3"
}
}
}
}

@ -0,0 +1,24 @@
{
"name": "shopro-plus",
"version": "1.0.0",
"main": "main.js",
"scripts": {
"test": ""
},
"repository": {
"type": "git",
"url": ""
},
"keywords": [],
"author": "",
"license": "ISC",
"homepage": "",
"dependencies": {
"jweixin-module": "^1.6.0",
"uni-read-pages": "^1.0.5",
"uni-simple-router": "^2.0.6",
"vconsole": "^3.7.0"
},
"description": "",
"devDependencies": {}
}

@ -0,0 +1,921 @@
{
"easycom": {
"^u-(.*)": "@/uview-ui/components/u-$1/u-$1.vue",
"^shopro-(.*)": "@/components/shopro-$1/shopro-$1.vue"
},
"pages": [{
"path": "pages/index/index",
"aliasPath": "/", //对于h5端你必须在首页加上aliasPath并设置为/
"style": {
"navigationBarTitleText": "首页",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true,
"navigationStyle": "custom",
"mp-alipay": {
"transparentTitle": "always",
"titlePenetrate": "YES",
"allowsBounceVertical": "NO"
}
},
"meta": {
"auth": false, //需要登录
"async": true, //是否同步
"title": "首页", //标题
"group": "商城" //分组
}
},
{
"path": "pages/index/category",
"style": {
"navigationBarTitleText": "分类"
},
"meta": {
"auth": false,
"async": true,
"title": "分类",
"group": "商城"
}
},
{
"path": "pages/index/cart",
"style": {
"navigationBarTitleText": "购物车"
},
"meta": {
"auth": false,
"async": true,
"title": "购物车",
"group": "商城"
}
},
{
"path": "pages/index/user",
"style": {
"navigationBarTitleText": "我的",
"navigationStyle": "custom",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true
},
"meta": {
"auth": false,
"async": true,
"title": "我的",
"group": "商城"
}
},
{
"path": "pages/index/view",
"name": "view",
"style": {
"navigationBarTitleText": "",
"enablePullDownRefresh": true
},
"meta": {
"auth": false,
"async": true,
"title": "自定义页面",
"group": "商城"
}
}
],
"subPackages": [{
"root": "pages/activity",
"pages": [{
"path": "sign/index",
"style": {
"navigationBarTitleText": "签到中心"
},
"meta": {
"auth": true,
"async": true,
"title": "签到中心",
"group": "应用"
}
},
{
"path": "seckill/list",
"style": {
"navigationBarTitleText": "限时秒杀",
"enablePullDownRefresh": true
},
"meta": {
"auth": false,
"async": true,
"title": "限时秒杀",
"group": "秒杀"
}
},
{
"path": "groupon/list",
"style": {
"navigationBarTitleText": "今日必拼",
"enablePullDownRefresh": true
},
"meta": {
"auth": false,
"async": true,
"title": "今日必拼",
"group": "拼团"
}
},
{
"path": "groupon/detail",
"style": {
"navigationBarTitleText": "拼团详情",
"enablePullDownRefresh": true
},
"meta": {
"auth": false,
"async": false,
"title": "拼团详情",
"group": "拼团"
}
},
{
"path": "groupon/my-groupon",
"style": {
"navigationBarTitleText": "我的拼团",
"enablePullDownRefresh": true
},
"meta": {
"auth": true,
"async": true,
"title": "我的拼团",
"group": "拼团"
}
},
{
"path": "discounts/list",
"style": {
"navigationBarTitleText": "优惠活动商品"
},
"meta": {
"auth": false,
"async": false,
"title": "优惠活动商品",
"group": "活动"
}
}
]
},
{
"root": "pages/app",
"pages": [
// {
// "path": "live/list",
// "style": {
// "navigationBarTitleText": "直播列表"
// },
// "meta": {
// "auth": false,
// "async": false,
// "title": "直播列表",
// "group": "直播"
// }
// },
{
"path": "score/list",
"style": {
"navigationBarTitleText": "积分商品"
},
"meta": {
"auth": false,
"async": true,
"title": "积分商品",
"group": "积分"
}
},
{
"path": "coupon/list",
"style": {
"navigationBarTitleText": "优惠券中心",
"navigationStyle": "custom"
},
"meta": {
"auth": true,
"async": true,
"title": "优惠券中心",
"group": "优惠券"
}
}, {
"path": "coupon/detail",
"style": {
"navigationBarTitleText": "优惠券详情",
"navigationStyle": "custom"
},
"meta": {
"auth": true,
"async": true,
"title": "优惠券详情",
"group": "优惠券"
}
},
{
"path": "merchant/index",
"style": {
"navigationBarTitleText": "门店中心",
"navigationStyle": "custom",
"enablePullDownRefresh": true,
"navigationBarTextStyle": "white"
},
"meta": {
"auth": true,
"async": false,
"title": "门店中心",
"group": "门店"
}
},
{
"path": "merchant/apply",
"style": {
"navigationBarTitleText": "门店入驻",
"navigationStyle": "custom"
},
"meta": {
"auth": true,
"async": true,
"title": "门店入驻",
"group": "门店"
}
},
{
"path": "merchant/detail",
"style": {
"navigationBarTitleText": "订单详情"
},
"meta": {
"auth": true,
"async": false,
"title": "订单详情",
"group": "订单"
}
},
{
"path": "merchant/info",
"style": {
"navigationBarTitleText": "门店详情"
},
"meta": {
"auth": true,
"async": false,
"title": "门店详情",
"group": "门店"
}
},
{
"path": "merchant/list",
"style": {
"navigationBarTitleText": "我的门店"
},
"meta": {
"auth": true,
"async": true,
"title": "我的门店",
"group": "门店"
}
},
{
"path": "commission/index",
"style": {
"navigationBarTitleText": "分销中心",
"enablePullDownRefresh": true,
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
},
"meta": {
"auth": true,
"async": true,
"title": "分销中心",
"group": "分销"
}
},
{
"path": "commission/team",
"style": {
"navigationBarTitleText": "我的团队",
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
},
"meta": {
"auth": true,
"async": true,
"title": "我的团队",
"group": "分销"
}
},
{
"path": "commission/commission-log",
"style": {
"navigationBarTitleText": "佣金明细",
"enablePullDownRefresh": true
},
"meta": {
"auth": true,
"async": true,
"title": "佣金明细",
"group": "分销"
}
},
{
"path": "commission/order",
"style": {
"navigationBarTitleText": "分销订单",
"navigationStyle": "custom",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true
},
"meta": {
"auth": true,
"async": true,
"title": "分销订单",
"group": "分销"
}
},
{
"path": "commission/goods",
"style": {
"navigationBarTitleText": "推广商品"
},
"meta": {
"auth": true,
"async": true,
"title": "推广商品",
"group": "分销"
}
},
{
"path": "commission/apply",
"style": {
"navigationBarTitleText": "申请分销商",
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
},
"meta": {
"auth": true,
"async": true,
"title": "申请分销商",
"group": "分销"
}
},
{
"path": "commission/rankings",
"style": {
"navigationBarTitleText": "分销排行",
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
},
"meta": {
"auth": true,
"async": true,
"title": "分销排行",
"group": "分销"
}
},
{
"path": "commission/share-log",
"style": {
"navigationBarTitleText": "分享记录",
"navigationStyle": "custom",
"navigationBarTextStyle": "white",
"enablePullDownRefresh": true
},
"meta": {
"auth": true,
"async": false,
"title": "分享记录",
"group": "分销"
}
}
]
},
{
"root": "pages/goods",
"pages": [{
"path": "list",
"style": {
"navigationBarTitleText": "商品列表",
"navigationStyle": "custom"
},
"meta": {
"auth": false,
"async": true,
"title": "商品列表",
"group": "商品"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "商品详情",
"navigationStyle": "custom"
},
"meta": {
"auth": false,
"async": true,
"title": "商品详情",
"group": "商品"
}
},
{
"path": "comment/add-comment",
"style": {
"navigationBarTitleText": "评价"
},
"meta": {
"auth": false,
"async": false,
"title": "评价",
"group": "商品"
}
},
{
"path": "comment/comment-list",
"style": {
"navigationBarTitleText": "评价列表"
},
"meta": {
"auth": false,
"async": false,
"title": "评价列表",
"group": "商品"
}
}
]
},
{
"root": "pages/order",
"pages": [{
"path": "confirm",
"style": {
"navigationBarTitleText": "确认订单"
},
"meta": {
"auth": true,
"async": false,
"title": "确认订单",
"group": "订单"
}
},
{
"path": "payment/method",
"style": {
"navigationBarTitleText": "收银台"
},
"meta": {
"auth": true,
"async": false,
"title": "收银台",
"group": "订单"
}
},
{
"path": "payment/result",
"style": {
"navigationBarTitleText": "支付结果"
},
"meta": {
"auth": false,
"async": false,
"title": "支付结果",
"group": "订单"
}
},
{
"path": "list",
"style": {
"navigationBarTitleText": "订单列表"
},
"meta": {
"auth": true,
"async": true,
"title": "订单列表",
"group": "订单"
}
},
{
"path": "detail",
"style": {
"navigationBarTitleText": "订单详情"
},
"meta": {
"auth": true,
"async": false,
"title": "订单详情",
"group": "订单"
}
},
{
"path": "after-sale/detail",
"style": {
"navigationBarTitleText": "售后详情"
},
"meta": {
"auth": true,
"async": false,
"title": "售后详情",
"group": "订单"
}
},
{
"path": "after-sale/list",
"style": {
"navigationBarTitleText": "售后列表"
},
"meta": {
"auth": true,
"async": true,
"title": "售后列表",
"group": "订单"
}
},
{
"path": "after-sale/log",
"style": {
"navigationBarTitleText": "售后记录"
},
"meta": {
"auth": true,
"async": false,
"title": "售后记录",
"group": "订单"
}
},
{
"path": "after-sale/refund",
"style": {
"navigationBarTitleText": "申请售后"
},
"meta": {
"auth": true,
"async": false,
"title": "申请售后",
"group": "订单"
}
},
{
"path": "express/distribution-detail",
"style": {
"navigationBarTitleText": "配送详情"
},
"meta": {
"auth": true,
"async": false,
"title": "配送详情",
"group": "订单"
}
},
{
"path": "express/express-detail",
"style": {
"navigationBarTitleText": "物流详情"
},
"meta": {
"auth": true,
"async": false,
"title": "物流详情",
"group": "订单"
}
},
{
"path": "express/express-list",
"style": {
"navigationBarTitleText": "包裹列表"
},
"meta": {
"auth": true,
"async": false,
"title": "包裹列表",
"group": "订单"
}
},
{
"path": "express/store-address",
"style": {
"navigationBarTitleText": "选择自提点",
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
},
"meta": {
"auth": true,
"async": false,
"title": "选择自提点",
"group": "订单"
}
}
]
},
{
"root": "pages/public",
"pages": [{
"path": "faq",
"style": {
"navigationBarTitleText": "常见问题"
},
"meta": {
"auth": false,
"async": true,
"title": "常见问题",
"group": "通用"
}
},
{
"path": "feedback",
"style": {
"navigationBarTitleText": "问题反馈"
},
"meta": {
"auth": true,
"async": true,
"title": "问题反馈",
"group": "通用"
}
},
{
"path": "chat/index",
"style": {
"navigationBarTitleText": "客服",
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
},
"meta": {
"auth": true,
"async": true,
"title": "客服",
"group": "通用"
}
},
{
"path": "search",
"style": {
"navigationBarTitleText": "搜索"
},
"meta": {
"auth": false,
"async": true,
"title": "搜索",
"group": "通用"
}
},
{
"path": "richtext",
"style": {
"navigationBarTitleText": ""
},
"meta": {
"auth": false,
"async": true,
"title": "富文本",
"group": "通用"
}
},
{
"path": "webview",
"style": {
"navigationBarTitleText": ""
},
"meta": {
"auth": false,
"async": true,
"title": "外链",
"group": "通用"
}
},
{
"path": "404",
"name": "404",
"style": {
"navigationBarTitleText": "页面不存在"
}
},
{
"path": "loading",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"enablePullDownRefresh": false
}
}
]
},
{
"root": "pages/user",
"pages": [{
"path": "info",
"style": {
"navigationBarTitleText": "个人信息"
},
"meta": {
"auth": true,
"async": true,
"title": "个人信息",
"group": "用户"
}
},
{
"path": "set",
"style": {
"navigationBarTitleText": "系统设置"
},
"meta": {
"auth": false,
"async": true,
"title": "系统设置",
"group": "通用"
}
},
{
"path": "view-log",
"style": {
"navigationBarTitleText": "浏览足迹"
},
"meta": {
"auth": true,
"async": true,
"title": "浏览足迹",
"group": "用户"
}
},
{
"path": "wallet/index",
"style": {
"navigationBarTitleText": "我的钱包"
},
"meta": {
"auth": true,
"async": true,
"title": "钱包",
"group": "用户"
}
},
{
"path": "wallet/withdraw",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom"
},
"meta": {
"auth": true,
"async": true,
"title": "提现",
"group": "用户"
}
},
{
"path": "wallet/withdraw-log",
"style": {
"navigationBarTitleText": "提现记录"
},
"meta": {
"auth": true,
"async": true,
"title": "提现记录",
"group": "用户"
}
},
{
"path": "wallet/top-up",
"style": {
"navigationBarTitleText": "充值",
"navigationStyle": "custom"
},
"meta": {
"auth": true,
"async": true,
"title": "充值",
"group": "用户"
}
},
{
"path": "wallet/top-up-log",
"style": {
"navigationBarTitleText": "充值记录"
},
"meta": {
"auth": true,
"async": true,
"title": "充值记录",
"group": "用户"
}
},
{
"path": "wallet/score-balance",
"style": {
"navigationBarTitleText": "积分余额",
"navigationStyle": "custom"
},
"meta": {
"auth": true,
"async": true,
"title": "积分余额",
"group": "积分"
}
},
{
"path": "address/list",
"style": {
"navigationBarTitleText": "收货地址"
},
"meta": {
"auth": true,
"async": true,
"title": "收货地址",
"group": "用户"
}
},
{
"path": "address/edit",
"style": {
"navigationBarTitleText": ""
},
"meta": {
"auth": true,
"async": false,
"title": "修改地址",
"group": "用户"
}
},
{
"path": "favorite",
"style": {
"navigationBarTitleText": "我的收藏"
},
"meta": {
"auth": true,
"async": true,
"title": "我的收藏",
"group": "用户"
}
}
]
}
],
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["pages/activity", "pages/user", "pages/goods", "pages/app", "pages/public",
"pages/order"
]
}
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "shopro-plus",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#FFFFFF"
},
"tabBar": {
"color": "#333",
"selectedColor": "#a8700d",
"backgroundColor": "#ffffff",
"borderStyle": "black",
"list": [{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/images/tabbar/tabbar_home.png",
"selectedIconPath": "/static/images/tabbar/tabbar_home1.png"
},
{
"pagePath": "pages/index/category",
"text": "分类",
"iconPath": "static/images/tabbar/tabbar_category.png",
"selectedIconPath": "/static/images/tabbar/tabbar_category1.png"
},
{
"pagePath": "pages/index/cart",
"text": "购物车",
"iconPath": "static/images/tabbar/tabbar_cart.png",
"selectedIconPath": "/static/images/tabbar/tabbar_cart1.png"
},
{
"pagePath": "pages/index/user",
"text": "我的",
"iconPath": "static/images/tabbar/tabbar_personal.png",
"selectedIconPath": "/static/images/tabbar/tabbar_personal1.png"
}
]
}
}

@ -0,0 +1,227 @@
<!-- 商品列表 -->
<template>
<view class="list-box">
<!-- 倒计时 -->
<u-sticky ref="uSticky" bgColor="#f6f6f6" offsetTop="0">
<view class="tip-list-box">
<view class="tip-list u-flex-col u-flex-1 u-p-30">
<view class="count-down-box u-flex u-col-center">
<view class="u-m-r-10">{{ timeRule.title }}</view>
<u-count-down
v-if="timeRule.time"
class="count-down-demo"
:timestamp="timeRule.time / 1000"
separator-color="#333 "
bg-color="#FF0000 "
ref="uCountDown"
color="#fff"
@end="getTime"
autoplay
></u-count-down>
</view>
<view class="u-flex u-flex-1 u-col-center modal-item u-flex-wrap u-m-t-10" v-if="activityInfo.tags">
<view class="title-tag cu-tag bg-red sm radius u-m-r-10 u-m-t-10" >{{ activityInfo.title }}</view>
<view class="tag-box cu-tag line-red sm radius u-m-r-10 u-m-t-10" v-for="(tag, index) in activityInfo.tags" :key="tag">{{ tag }}</view>
</view>
</view>
</view>
</u-sticky>
<!-- 商品列表 -->
<view class="u-waterfall u-p-16" v-if="!isEmpty">
<view id="u-left-column" class="u-column">
<view class="goods-item u-m-b-16 u-flex u-row-center u-col-center" v-for="leftGoods in leftList" :key="leftGoods.id">
<shopro-goods-card
:detail="leftGoods"
:type="leftGoods.activity_type"
:image="leftGoods.image"
:title="leftGoods.title"
:subtitle="leftGoods.subtitle"
:price="leftGoods.price"
:originPrice="leftGoods.original_price"
:sales="leftGoods.sales"
:tagTextList="leftGoods.activity_discounts_tags"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: leftGoods.id } })"
></shopro-goods-card>
</view>
</view>
<view id="u-right-column" class="u-column">
<view class="goods-item u-m-b-16 u-flex u-row-center u-col-center" v-for="rightGoods in rightList" :key="rightGoods.id">
<shopro-goods-card
:detail="rightGoods"
:type="rightGoods.activity_type"
:image="rightGoods.image"
:title="rightGoods.title"
:subtitle="rightGoods.subtitle"
:price="rightGoods.price"
:originPrice="rightGoods.original_price"
:sales="rightGoods.sales"
:tagTextList="rightGoods.activity_discounts_tags"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: rightGoods.id } })"
></shopro-goods-card>
</view>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty v-if="isEmpty" :image="$IMG_URL + '/imgs/empty/empty_goods.png'" tipText="暂无该商品,还有更多好货等着你噢~"></shopro-empty>
<!-- 加载更多 -->
<u-loadmore v-show="goodsList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
<!-- 登录弹窗 -->
<shopro-auth-modal></shopro-auth-modal>
</view>
</template>
<script>
import { mapMutations, mapActions, mapState } from 'vuex';
export default {
components: {},
data() {
return {
activityInfo: {},
timeRule: {
title: '',
time: 0
},
isEmpty: false,
goodsList: [],
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
lastPage: 1,
currentPage: 1,
// 瀑布流 350-330
addTime: 100, //排序间隙时间
leftHeight: 0,
rightHeight: 0,
leftList: [],
rightList: [],
tempList: []
};
},
// 触底加载更多
onReachBottom() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getGoodsList();
}
},
onLoad() {
this.activityInfo = this.$Route.query;
this.getGoodsList();
this.getCartList();
},
mounted() {
this.$nextTick(() => {
this.getTime();
});
},
methods: {
...mapActions(['getCartList']),
// 瀑布流相关
async splitData() {
if (!this.tempList.length) return;
let item = this.tempList[0];
if (!item) return;
// 分左右
if (this.leftHeight < this.rightHeight) {
this.leftHeight += item.activity_discounts_tags.length ? 350 : 330;
this.leftList.push(item);
} else if (this.leftHeight > this.rightHeight) {
this.rightHeight += item.activity_discounts_tags.length ? 350 : 330;
this.rightList.push(item);
} else {
this.leftHeight += item.activity_discounts_tags.length ? 350 : 330;
this.leftList.push(item);
}
// 移除临时列表的第一项,如果临时数组还有数据,继续循环
this.tempList.splice(0, 1);
if (this.tempList.length) {
setTimeout(() => {
this.splitData();
}, this.addTime);
}
},
clear() {
this.leftList = [];
this.rightList = [];
this.leftHeight = 0;
this.rightHeight = 0;
this.tempList = [];
},
// 计算倒计时
getTime() {
let nowTime = new Date().getTime();
let { endtime, starttime } = this.activityInfo;
endtime = endtime * 1000;
starttime = starttime * 1000;
// 当前时间小于开始时间,未开始
if (nowTime < starttime) {
this.timeRule.title = '距开始';
this.timeRule.time = starttime - nowTime;
}
// 当前时间大于开始时间,小于结束时间,进行中
if (nowTime > starttime && nowTime < endtime) {
this.timeRule.title = '距结束';
this.timeRule.time = endtime - nowTime;
}
// 当前时间大于结束时间,已结束
if (nowTime > endtime) {
this.timeRule.title = '已结束';
this.timeRule.time = 0;
}
},
// 商品列表
getGoodsList() {
let that = this;
that.loadStatus = 'loading';
that.$http('goods.lists', {
goods_ids: that.activityInfo.goods_ids,
page: that.currentPage
}).then(res => {
if (res.code === 1) {
that.goodsList = [...that.goodsList, ...res.data.data];
that.isEmpty = !that.goodsList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
that.tempList = res.data.data;
that.splitData();
}
});
}
}
};
</script>
<style lang="scss">
@mixin vue-flex($direction: row) {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: $direction;
/* #endif */
}
.u-waterfall {
@include vue-flex;
flex-direction: row;
align-items: flex-start;
}
.u-column {
@include vue-flex;
flex: 1;
flex-direction: column;
height: auto;
}
.tip-list-box {
background-color: #f6f6f6;
padding: 20rpx;
}
.tip-list {
font-size: 28rpx;
background-color: #fff;
border-radius: 20rpx;
}
</style>

@ -0,0 +1,371 @@
<!-- 拼团详情 -->
<template>
<view class="page_box">
<!-- 商品卡片 -->
<view class="head_box">
<view class="goods-card u-p-x-20 u-p-y-40" v-if="grouponDetail.id">
<shopro-mini-card
:title="grouponDetail.goods.title"
:image="grouponDetail.goods.image"
:subtitle="grouponDetail.goods.subtitle"
:price="grouponDetail.goods.groupon_price"
:originPrice="grouponDetail.goods.original_price"
>
<template #describe>
<view class="u-flex u-m-b-20">
<view class="sell-box">
<text class=" hot-icon iconfont icon-icon-test"></text>
<text class="sell-num">已拼{{ grouponDetail.goods.sales }}件</text>
</view>
<text class="group-num">{{ grouponDetail.num || 0 }}人团</text>
</view>
</template>
</shopro-mini-card>
</view>
</view>
<view class="content_box">
<view class="u-flex-col u-row-between group-box">
<!-- 拼团成功 -->
<view class="u-flex u-row-center u-col-center" v-if="grouponDetail.status === 'finish' || grouponDetail.status === 'finish-fictitious'">
<view class="tip-box u-flex" v-if="grouponDetail.my">
<view class="u-iconfont uicon-checkmark-circle-fill u-m-r-10" style="color:#42B111;font-size:34rpx ;"></view>
<text>恭喜您~拼团成功!</text>
</view>
<view class="tip-box u-flex" v-else>
<view class="u-iconfont uicon-close-circle-fill u-m-r-10" style="color:#E1212B;font-size:34rpx ;"></view>
<text>对不起~您来晚了,该团已满</text>
</view>
</view>
<!-- 拼团失败-->
<view class="u-flex u-row-center u-col-center" v-if="grouponDetail.status === 'invalid'">
<view class="tip-box u-flex">
<view class="u-iconfont uicon-close-circle-fill u-m-r-10" style="color:#E1212B;font-size:34rpx ;"></view>
<text>{{ grouponDetail.my ? '对不起~拼团已过期!已全额退款' : '对不起~拼团已过期!您来晚了~' }}</text>
</view>
</view>
<!-- 拼团中 -->
<view class="title-box u-flex u-row-center u-col-center" v-if="grouponDetail.status === 'ing'">
<!-- 活动结束 -->
<view class="" v-if="grouponDetail.activity_status === 'ended'">
<view class="tip-box u-flex u-col-center">
<view class="u-iconfont uicon-close-circle-fill u-m-r-10" style="color:#E1212B;font-size:34rpx ;"></view>
<text>对不起~拼团活动已结束!您来晚了~</text>
</view>
</view>
<view v-else class="u-flex u-col-center">
<view class="title-text">
待成团,还差
<text class="group-num">{{ grouponDetail.num - grouponDetail.current_num }}</text>
人拼团成功
</view>
<view class="count-down u-flex" v-if="time">
<text class="count-down-tip">倒计时</text>
<view class="time-box u-flex">
<view class="count-text">{{ time.h || '00' }}</view>
:
<view class="count-text">{{ time.m || '00' }}</view>
:
<view class="count-text">{{ time.s || '00' }}</view>
</view>
</view>
</view>
</view>
<!-- 拼团人 -->
<view class="group-people u-flex u-row-center">
<view class="img-box" v-for="(team, index) in grouponDetail.groupon_log" :key="team.user_avatar">
<view class="tag" v-if="index == 0">团长</view>
<image class="avatar" :class="{ leader: index == 0 }" :src="team.user_avatar" mode="aspectFill"></image>
</view>
<view class="img-box" v-for="(base, index) in surplusNum" :key="base" v-if="index < 100">
<image class="avatar" :src="$IMG_URL + '/imgs/group/base_group_avatar.png'" mode="aspectFill"></image>
</view>
</view>
<!-- 底部按钮 -->
<view class="btn-box u-flex u-row-center u-col-center">
<!-- 拼团中 -->
<view v-if="grouponDetail.status === 'ing'">
<block v-if="grouponDetail.activity_status === 'ended'">
<button class="u-reset-button btn2" v-if="grouponDetail.my" @tap.stop="jump('/pages/order/detail', { id: grouponDetail.my.order_id })">查看订单</button>
</block>
<block v-else>
<button class="u-reset-button btn1" v-if="grouponDetail.my" @tap="showShare = true">邀请好友参团</button>
<button class="u-reset-button btn1" v-else @tap="showSku = true">立即参团</button>
</block>
</view>
<!-- 拼团成功/失败-->
<view v-if="grouponDetail.status === 'finish' || grouponDetail.status === 'finish-fictitious' || grouponDetail.status === 'invalid'">
<button class="u-reset-button btn2" v-if="grouponDetail.my" @tap.stop="jump('/pages/order/detail', { id: grouponDetail.my.order_id })">查看订单</button>
<button class="u-reset-button btn1" v-else @tap="jump('/pages/goods/detail', { id: grouponDetail.goods_id })">我要开团</button>
</view>
</view>
</view>
<!-- 玩法 -->
<view v-if="activity && activity.id" class="groupon-play u-flex u-row-between u-col-center" @tap="jump('/pages/public/richtext', { id: activity.richtext_id })">
<text class="title">玩法</text>
<view class="u-flex u-col-center">
<view class="description u-ellipsis-1">{{ activity.richtext_title || '开团/参团·邀请好友·人满发货(不满退款' }}</view>
<view class="u-iconfont uicon-arrow-right u-m-r-10" style="color:#999;font-size:28rpx ;"></view>
</view>
</view>
</view>
<!-- 邀请好友 -->
<shopro-share v-model="showShare" :posterInfo="grouponDetail" :posterType="'groupon'"></shopro-share>
<!-- 规格弹窗 -->
<shopro-sku
v-if="grouponDetail.goods"
v-model="showSku"
:activityRules="{ status: grouponDetail.status }"
:goodsInfo="grouponDetail.goods"
:grouponId="grouponDetail.id"
buyType="buy"
grouponBuyType="groupon"
></shopro-sku>
<!-- 登录提示 -->
<shopro-auth-modal></shopro-auth-modal>
</view>
</template>
<script>
let timer = null;
import share from '@/shopro/share';
export default {
components: {},
data() {
return {
time: 0,
grouponDetail: {},
showShare: false,
showSku: false,
activity: {},
surplusNum: 0 //剩余拼团人数、
};
},
computed: {},
onLoad() {
this.getGrouponDetail();
},
onPullDownRefresh() {
this.getGrouponDetail();
},
onHide() {
clearInterval(timer);
timer = null;
},
onUnload() {
share.setShareInfo();
},
methods: {
// 路由跳转
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 倒计时
countDown(t) {
const that = this;
timer = setInterval(() => {
if (t > 0) {
that.time = that.$tools.format(t);
t--;
} else {
clearInterval(timer);
that.time = false;
}
}, 1000);
},
// 拼团详情
getGrouponDetail() {
let that = this;
that.$http('goods.grouponDetail', {
id: that.$Route.query.id
}).then(res => {
uni.stopPullDownRefresh();
that.grouponDetail = res.data;
that.activity = res.data.activity;
if (that.activity) {
that.activity.richtext_id = parseInt(that.activity.richtext_id);
}
that.surplusNum = res.data.num - res.data.current_num;
let newTime = new Date().getTime();
let endTime = res.data.expiretime * 1000;
let t = endTime - newTime;
that.countDown(t / 1000);
// 设置分享信息
share.setShareInfo({
title: that.grouponDetail.goods.title,
desc: that.grouponDetail.goods.subtitle,
image: that.grouponDetail.goods.image,
params: {
page: 3,
pageId: that.$Route.query.id
}
});
});
}
}
};
</script>
<style lang="scss">
// 商品卡片
.head_box {
background: url($IMG_URL+'/imgs/group/group_detail_bg.png') no-repeat;
background-size: 100% 316rpx;
padding: 60rpx 20rpx 20rpx;
.goods-card {
background-color: #fff;
border-radius: 20rpx;
.group-num {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
margin-left: 30rpx;
}
.sell-box {
line-height: 32rpx;
background: rgba(255, 224, 226, 0.3);
border-radius: 16rpx;
padding: 0 10rpx;
.hot-icon {
font-size: 26rpx;
color: #e1212b;
margin-right: 8rpx;
}
.sell-num {
font-size: 20rpx;
color: #f7979c;
}
}
}
}
// 拼团人
.group-box {
background: #fff;
padding: 40rpx 0;
min-height: 370rpx;
justify-content: center;
.tip-box {
font-size: 28rpx;
font-weight: bold;
}
.title-box {
font-size: 26rpx;
font-weight: bold;
color: #333;
.group-num {
color: #f8002c;
}
.count-down-tip {
font-size: 24rpx;
padding-left: 10rpx;
}
.time-box {
font-size: 18rpx;
.count-text {
display: inline-block;
background-color: #383a46;
color: #fff;
font-size: 18rpx;
border-radius: 2rpx;
padding: 0 5rpx;
height: 28rpx;
text-align: center;
line-height: 28rpx;
margin: 0 6rpx;
}
}
}
.group-people {
flex-wrap: wrap;
padding: 50rpx;
.img-box {
position: relative;
margin-right: 30rpx;
&:nth-child(6n) {
margin-right: 0;
}
.tag {
position: absolute;
line-height: 36rpx;
background: linear-gradient(132deg, rgba(243, 223, 177, 1), rgba(243, 223, 177, 1), rgba(236, 190, 96, 1));
border-radius: 14rpx;
padding: 0 10rpx;
white-space: nowrap;
font-size: 24rpx;
color: #784f06;
z-index: 2;
top: -20rpx;
left: 50%;
transform: translateX(-50%);
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #ccc;
}
.leader {
border: 4rpx solid rgba(237, 195, 108, 1);
}
}
}
.btn-box {
width: 750rpx;
.btn1 {
width: 690rpx;
line-height: 70rpx;
background: linear-gradient(90deg, rgba(254, 131, 42, 1), rgba(255, 102, 0, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(255, 104, 4, 0.22);
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
}
.btn2 {
width: 690rpx;
line-height: 70rpx;
border: 1rpx solid rgba(223, 223, 223, 1);
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
}
}
}
.groupon-play {
height: 94rpx;
border-top: 1rpx solid rgba(#dfdfdf, 0.5);
padding: 0 20rpx;
background: #fff;
.title {
font-size: 28rpx;
color: #999;
}
.description {
font-size: 28rpx;
text-align: right;
}
}
</style>

@ -0,0 +1,246 @@
<!-- 拼购列表 -->
<template>
<view class="groupon-wrap">
<view class="group-head u-flex u-row-between">
<text class="group-head__title">爆款推荐</text>
<text class="group-head__notice">省钱省心限时拼</text>
</view>
<view class="group-box">
<view class="goods-item u-m-b-16" v-for="(item, index) in grouponList" :key="item.id" @tap="$Router.push({ path: '/pages/goods/detail', query: { id: item.id } })">
<view class="big-goods u-p-20 u-flex u-col-top">
<image v-if="index < 3" class="top-tag" :src="tagMap[index]" mode=""></image>
<image class="goods-img" lazy-load fade-show :src="item.image" mode="aspectFill"></image>
<view class=" card-right u-m-l-20 u-flex-col u-row-between">
<view class="">
<view class="goods-title u-ellipsis-1 u-m-t-10 u-m-b-10">
<view class="title-tag cu-tag bg-orange sm radius u-m-r-10">拼团</view>
{{ item.title }}
</view>
<view v-show="item.subtitle" class="subtitle-text u-m-b-10 u-ellipsis-1">{{ item.subtitle }}</view>
</view>
<view class="u-flex u-m-y-20">
<view class="sell-box">
<text class=" hot-icon iconfont icon-icon-test"></text>
<text class="sell-num">已拼{{ item.sales }}件</text>
</view>
<text class="group-num">{{ item.activity.rules.team_num || 0 }}人团</text>
</view>
<view class=" u-flex u-row-between u-col-center">
<view class="u-flex u-col-bottom font-OPPOSANS">
<view class="price u-m-r-10">{{ item.groupon_price }}</view>
<view class="origin-price">{{ item.original_price }}</view>
</view>
<button class="u-reset-button buy-btn">马上拼</button>
</view>
</view>
</view>
</view>
<!-- 空白 -->
<shopro-empty
v-if="!grouponList.length && !isLoading"
style="padding-top: 200rpx;"
marginTop="0"
:image="$IMG_URL + '/imgs/empty/empty_goods.png'"
tipText="暂无拼团商品,敬请期待~"
></shopro-empty>
<!-- 加载更多 -->
</view>
<u-loadmore v-if="grouponList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</view>
</template>
<script>
export default {
components: {},
data() {
return {
tagMap: {
0: this.$IMG_URL + '/imgs/group/groupon_top1.png',
1: this.$IMG_URL + '/imgs/group/groupon_top2.png',
2: this.$IMG_URL + '/imgs/group/groupon_top3.png'
},
isLoading: true,
loadStatus: 'loadmore', //loadmore:加载前的状态,loading:加载中的状态,nomore:没有更多的状态
lastPage: 1,
currentPage: 1,
grouponList: []
};
},
onLoad() {
this.getGrouponList();
},
onReachBottom() {
this.loadMore();
},
onPullDownRefresh() {
this.currentPage = 1;
this.lastPage = 1;
this.grouponList = [];
this.getGrouponList();
},
computed: {},
methods: {
// 路由跳转
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getGrouponList();
}
},
// 拼团列表
getGrouponList() {
let that = this;
that.isLoading = true;
that.loadStatus = 'loading';
that.$http('goods.grouponList', {
page: that.currentPage
}).then(res => {
that.isLoading = false;
uni.stopPullDownRefresh();
if (res.code === 1) {
that.grouponList = [...that.grouponList, ...res.data.data];
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
}
}
};
</script>
<style lang="scss">
// 背景
.groupon-wrap {
background: url($IMG_URL+'/imgs/group/group_list_bg.png') no-repeat;
background-size: 100% 374rpx;
}
.group-head {
padding: 0 25rpx;
height: 100rpx;
.group-head__title {
font-size: 32rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
}
.group-head__notice {
font-size: 26rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
}
}
.group-box {
width: 710rpx;
background: linear-gradient(#fff, #f5f5f5);
border-radius: 20rpx;
margin: 0 auto;
min-height: 800rpx;
.goods-item {
border-radius: 20rpx;
background-color: #fff;
// 大商品卡片
.big-goods {
width: 710rpx;
height: 260rpx;
background: #ffffff;
box-shadow: 0px 7rpx 8rpx 1rpx rgba(254, 76, 29, 0.05);
border-radius: 20rpx;
position: relative;
.goods-img {
width: 220rpx;
height: 220rpx;
border-radius: 6rpx;
}
.top-tag {
position: absolute;
z-index: 3;
top: 20rpx;
left: 20rpx;
width: 84rpx;
height: 36rpx;
}
.card-right {
width: 430rpx;
height: 220rpx;
}
.goods-title {
font-size: 26rpx;
font-weight: 600;
width: 400rpx;
color: #000000;
vertical-align: middle;
}
.subtitle-text {
font-size: 22rpx;
width: 400rpx;
font-weight: 500;
color: #666666;
}
.buy-btn {
width: 120rpx;
line-height: 50rpx;
background: linear-gradient(90deg, #ff6600 0%, #fe832a 100%);
border-radius: 25rpx;
font-size: 24rpx;
font-weight: 500;
color: #ffffff;
}
// 拼团
.sell-box {
background: rgba(#f9efd6, 0.3);
border-radius: 16rpx;
line-height: 32rpx;
.sell-num {
font-size: 20rpx;
font-weight: 400;
color: #ff6904;
}
.hot-icon {
font-size: 26rpx;
color: #ff6904;
margin-right: 8rpx;
}
}
.group-num {
font-size: 20rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
margin-left: 20rpx;
}
// 价格
.price {
color: #ff0000;
font-weight: 600;
&::before {
content: '¥';
font-size: 20rpx;
}
}
.origin-price {
color: #c4c4c4;
font-size: 24rpx;
text-decoration: line-through;
&::before {
content: '¥';
font-size: 20rpx;
}
}
}
}
}
</style>

@ -0,0 +1,247 @@
<!-- 我的拼团 -->
<template>
<view class="page_box">
<!-- tab -->
<view class="head_box">
<view class="order-nav u-flex">
<view class="nav-item u-flex-col u-flex-1 u-row-center u-col-center" v-for="nav in groupState" :key="nav.id" @tap="onNav(nav.id)">
<view class="item-title">{{ nav.title }}</view>
<text class="nav-line" :class="{ 'line-active': stateId === nav.id }"></text>
</view>
</view>
</view>
<view class="content_box">
<scroll-view scroll-y="true" enable-back-to-top @scrolltolower="loadMore" refresher-background="#f5f5f5" class="scroll-box">
<block v-for="groupon in myGrouponList" :key="groupon.id">
<view class="group-card">
<image v-if="stateId !== 'ing'" class="group-state" :src="grouponStatus[groupon.groupon.status]" mode=""></image>
<view class="goods-content">
<shopro-mini-card
:title="groupon.goods.title"
:image="groupon.goods.image"
:subtitle="groupon.goods.subtitle"
:price="groupon.goods.price"
:originPrice="groupon.goods.original_price"
@click="jump('/pages/goods/detail', { id: groupon.goods.id })"
>
<template #describe>
<view class="u-flex u-m-b-20">
<view class="sell-box">
<text class="iconfont icon-fire"></text>
<text class="sell-num">已拼{{ groupon.goods.sales }}件</text>
</view>
<text class="group-num">{{ groupon.groupon.num || 0 }}人团</text>
</view>
</template>
</shopro-mini-card>
</view>
<view class="btn-box u-flex">
<button class="u-reset-button invite-btn" @tap="jump('/pages/activity/groupon/detail', { id: groupon.groupon_id })">拼团详情</button>
</view>
</view>
</block>
<!-- 空白页 -->
<shopro-empty
v-if="isEmpty"
:image="$IMG_URL + '/imgs/empty/empty_groupon.png'"
tipText="暂无拼团商品,更多拼团好货等着你噢~"
btnText="去首页逛逛"
@click="$Router.pushTab('/pages/index/index')"
></shopro-empty>
<!-- 加载更多 -->
<u-loadmore v-if="myGrouponList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</scroll-view>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
isEmpty: false,
loadStatus: 'loadmore', //loadmore:加载前的状态,loading:加载中的状态,nomore-没有更多的状态
lastPage: 1,
currentPage: 1,
stateId: 'all',
grouponStatus: {
finish: this.$IMG_URL + '/imgs/group/group_state_succeed.png',
'finish-fictitious': this.$IMG_URL + '/imgs/group/group_state_succeed.png',
invalid: this.$IMG_URL + '/imgs/group/group_state_failed.png'
},
myGrouponList: [], //我的拼团列表。
groupState: [
{
id: 'all', //0
title: '全部'
},
{
id: 'ing', //1
title: '进行中'
},
{
id: 'finish', //2
title: '成功'
},
{
id: 'invalid', //3
title: '失败'
}
]
};
},
computed: {},
onLoad() {
this.getMyGroupon();
},
onPullDownRefresh() {
this.lastPage = 1;
this.currentPage = 1;
this.myGrouponList = [];
this.getMyGroupon();
},
methods: {
onNav(id) {
if (this.stateId !== id) {
this.stateId = id;
this.myGrouponList = [];
this.currentPage = 1;
this.getMyGroupon();
}
},
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getMyGroupon();
}
},
// 我的拼团
getMyGroupon() {
let that = this;
that.loadStatus = 'loading';
that.$http(
'goods.myGroupon',
{
type: that.stateId,
page: that.currentPage
},
'加载中'
).then(res => {
uni.stopPullDownRefresh();
if (res.code === 1) {
that.myGrouponList = [...that.myGrouponList, ...res.data.data];
that.isEmpty = !that.myGrouponList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
}
}
};
</script>
<style lang="scss">
// tab
.order-nav {
background: #fff;
height: 80rpx;
.nav-item {
flex: 1;
.item-title {
font-size: 30rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
line-height: 76rpx;
}
.nav-line {
width: 120rpx;
height: 4rpx;
background: #fff;
}
.line-active {
background: rgba(230, 184, 115, 1);
}
}
}
// 拼团卡片
.group-card {
background: #fff;
margin-top: 20rpx;
position: relative;
overflow: hidden;
.group-state {
position: absolute;
z-index: 2;
top: -20rpx;
right: -20rpx;
width: 126rpx;
height: 126rpx;
}
.goods-content {
padding: 20rpx;
position: relative;
.group-num {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
margin-left: 30rpx;
}
.sell-box {
line-height: 32rpx;
background: rgba(255, 224, 226, 0.3);
border-radius: 16rpx;
padding: 0 10rpx;
.icon-fire {
color: #e1212b;
font-size: 26rpx;
margin-right: 10rpx;
}
.sell-num {
font-size: 20rpx;
color: #f7979c;
}
}
}
.btn-box {
height: 95rpx;
border-top: 1px solid rgba(223, 223, 223, 0.5);
justify-content: flex-end;
.invite-btn {
width: 210rpx;
line-height: 65rpx;
border: 1rpx solid #a8700d;
border-radius: 33rpx;
font-size: 26rpx;
color: #a8700d;
padding: 0;
margin-right: 20rpx;
background: #fff;
}
}
}
</style>

@ -0,0 +1,248 @@
<!-- 秒杀列表 -->
<template>
<view class="page_box seckill-list-wrap">
<!-- tab栏 -->
<view class="tab-box u-flex">
<view class="tab-item u-flex-col u-row-center u-col-center" @tap="onTab(tab.id)" v-for="tab in tabList" :key="tab.id">
<text class="tab-title u-m-b-20" :class="{ 'tab-active': tabCurrent === tab.id }">{{ tab.title }}</text>
<text v-show="tabCurrent === tab.id" class="tab-line"></text>
</view>
</view>
<!-- 商品列表 -->
<view class="content_box">
<scroll-view scroll-y="true" enable-back-to-top @scrolltolower="loadMore" class="scroll-box">
<view class="goods-item u-m-b-16" v-for="item in goodsList" :key="item.id" @tap="toSeckillDetail(item.id)">
<view class="big-goods u-flex u-p-20 u-col-top ">
<image class="goods-img" lazy-load fade-show :src="item.image" mode="aspectFill"></image>
<view class=" card-right u-m-l-20 u-flex-col u-row-between">
<view class="">
<view class="goods-title u-ellipsis-1 u-m-t-10 u-m-b-10">
<view class="title-tag cu-tag bg-red sm radius u-m-r-10">秒杀</view>
{{ item.title }}
</view>
<view v-show="item.subtitle" class="subtitle-text u-m-b-10 u-ellipsis-1">{{ item.subtitle }}</view>
</view>
<view class="u-flex u-m-y-20">
<view class="cu-progress round sm" style="width:210rpx;"><view :style="[{ width: item.percent + '%',backgroundColor:'#ffbbbb' }]"></view></view>
<view class="progress-text">已售出{{ item.sales }}件</view>
</view>
<view class=" u-flex u-row-between u-col-center">
<view class="u-flex u-col-bottom font-OPPOSANS">
<view class="price u-m-r-10">{{ item.price }}</view>
<view class="origin-price">{{ item.original_price }}</view>
</view>
<button class="u-reset-button buy-btn">去抢购</button>
</view>
</view>
</view>
</view>
<!-- 空白 -->
<shopro-empty v-if="isEmpty" tipText="暂无秒杀商品,敬请期待~" :image="$IMG_URL + '/imgs/empty/empty_goods.png'"></shopro-empty>
<!-- 加载更多 -->
<u-loadmore v-if="!isEmpty" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</scroll-view>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
isEmpty: false, //无数据
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
lastPage: 1,
currentPage: 1,
tabCurrent: 'ing',
goodsList: [],
tabList: [
{
id: 'ing',
title: '抢购进行中'
},
{
id: 'nostart',
title: '即将开始'
}
]
};
},
computed: {},
onLoad() {
this.getGoodsList();
},
onPullDownRefresh() {
this.lastPage = 1;
this.currentPage = 1;
this.goodsList = [];
this.getGoodsList();
},
methods: {
onTab(id) {
if (this.tabCurrent !== id) {
this.tabCurrent = id;
this.goodsList = [];
this.currentPage = 1;
this.lastPage = 1;
this.getGoodsList();
}
},
toSeckillDetail(id) {
this.$Router.push({ path: '/pages/goods/detail', query: { id: id } });
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getGoodsList();
}
},
// 秒杀列表
getGoodsList() {
let that = this;
that.loadStatus = 'loading';
that.$http(
'goods.seckillList',
{
type: that.tabCurrent,
page: that.currentPage
},
'加载中...'
).then(res => {
uni.stopPullDownRefresh();
if (res.code === 1) {
that.goodsList = [...that.goodsList, ...res.data.data];
that.goodsList.map(item => {
item.percent = item.stock + item.sales > 0 ? ((item.sales / (item.sales + item.stock)) * 100).toFixed(2) : 0;
});
that.isEmpty = !that.goodsList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
}
}
};
</script>
<style lang="scss">
.seckill-list-wrap {
background: url($IMG_URL+'/imgs/seckill/sekill_list_head_bg.png') no-repeat;
background-size: 100% auto;
}
// tab
.tab-box {
.tab-item {
flex: 1;
height: 140rpx;
color: rgba(#fff, 0.4);
font-size: 32rpx;
font-weight: 600;
.tab-line {
width: 50rpx;
height: 8rpx;
background: #fff;
border-radius: 4rpx;
}
}
.tab-active {
color: rgba(#fff, 1);
}
}
.scroll-box {
background: linear-gradient(#fff, #f5f5f5);
width: 710rpx;
border-radius: 20rpx;
margin: 0 auto;
}
// 列表
.goods-item {
// 大商品卡片
.big-goods {
width: 710rpx;
height: 260rpx;
background: #ffffff;
box-shadow: 0px 7rpx 8rpx 1rpx rgba(254, 76, 29, 0.05);
border-radius: 20rpx;
.goods-img {
width: 220rpx;
height: 220rpx;
border-radius: 6rpx;
}
.card-right {
width: 430rpx;
height: 220rpx;
}
.goods-title {
font-size: 26rpx;
font-weight: 600;
width: 400rpx;
color: #000000;
}
.subtitle-text {
font-size: 22rpx;
width: 400rpx;
font-weight: 500;
color: #666666;
}
.buy-btn {
width: 120rpx;
line-height: 50rpx;
background: linear-gradient(90deg, #d01325, #ed3c30);
border-radius: 25rpx;
font-size: 24rpx;
font-weight: 500;
color: #ffffff;
}
.progress-text {
color: #c4c4c4;
font-size: 20rpx;
margin-left: 25rpx;
}
// 价格
.price {
color: #ff0000;
font-weight: 600;
&::before {
content: '¥';
font-size: 20rpx;
}
}
.origin-price {
color: #c4c4c4;
font-size: 24rpx;
text-decoration: line-through;
&::before {
content: '¥';
font-size: 20rpx;
}
}
}
.buy-btn {
width: 120rpx;
line-height: 50rpx;
background: linear-gradient(90deg, #d01325, #ed3c30);
border-radius: 25rpx;
font-size: 24rpx;
font-weight: 500;
color: #ffffff;
}
.btn-end,
.btn-nostart {
background: rgba(238, 238, 238, 1);
color: #999999;
}
.btn-ing {
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(229, 138, 0, 0.22);
color: rgba(255, 255, 255, 1);
}
}
</style>

@ -0,0 +1,361 @@
<!-- 签到 -->
<template>
<view class="sign-wrap">
<view class="calendar">
<!-- 每日签到 -->
<view class="sign-everyday u-flex u-row-between u-p-30">
<text class="sign-everyday-title">每日签到</text>
<view class="sign-num-box">
已连续签到
<text class="sign-num">{{ cuntinueDays }}</text>
天
</view>
</view>
<!-- 切换年月 -->
<view class="bar u-flex u-row-center">
<view class="previous" @tap="handleCalendar(0)">
<button class="u-reset-button bar-btn u-p-r-30"><view class="u-iconfont uicon-arrow-left" style="color:#c4c4c4 ;font-size:28rpx ;"></view></button>
</view>
<view class="date">{{ cur_year || '--' }} 年 {{ cur_month || '--' }} 月</view>
<view class="next" @tap="handleCalendar(1)">
<button class="u-reset-button bar-btn u-p-l-30"><view class="u-iconfont uicon-arrow-right" style="color:#c4c4c4 ;font-size:28rpx ;"></view></button>
</view>
</view>
<!-- 显示星期 -->
<view class="week u-flex">
<view class="week-item u-flex u-row-center" v-for="(item, index) in weeks_ch" :key="index">{{ item }}</view>
</view>
<!-- 日历表 -->
<view class="myDateTable">
<view v-for="(item, j) in days" :key="j" class="dateCell u-flex u-row-center u-col-center">
<!-- 空格 -->
<view v-if="!item.date" class="cell"><text :decode="true">&nbsp;&nbsp;</text></view>
<view v-else>
<!-- 已签到日期 -->
<view v-if="item.is_sign" class="cell is-sign">
<text>{{ item.day }}</text>
</view>
<!-- 未签到日期 -->
<view class="cell" v-else>
<text>{{ item.day }}</text>
</view>
</view>
</view>
<!-- 签到按钮 -->
<view class="u-flex u-col-center u-row-center sign-box">
<button :disabled="isPresentMonth || isSign" @tap="onSign" class="u-reset-button sign-btn">{{ isSign ? '已签到' : '签到' }}</button>
</view>
</view>
</view>
<view class="cu-modal" :class="{ show: showSign }" @tap="showSign = false">
<view class="sign-modal-box cu-dialog" style="width: 610rpx;">
<view class="modal-head u-flex-col u-col-center">
<image class="modal-bg" :src="$IMG_URL + '/imgs/modal/sign_modal_bg.jpg'" mode=""></image>
<image class="sign-tag" :src="$IMG_URL + '/imgs/modal/sign_modal_succeed.png'" mode=""></image>
<view class="sign-num-box">
已连续打卡
<text class="sign-num">{{ cuntinueDays }}</text>
天
</view>
</view>
<view class="modal-content u-flex-col u-col-center">
<view class="sign-success">签到成功</view>
<view class="sign-score">恭喜您获得{{ score || '0' }}积分</view>
</view>
<view class="modal-bottom u-flex u-row-center"><button class="u-reset-button confirem-btn" @tap="showSign = false">确认</button></view>
</view>
</view>
</view>
</template>
<script>
import { mapMutations, mapActions, mapState } from 'vuex';
export default {
components: {},
data() {
return {
days: [], //日历
cuntinueDays: 0, //签到天数
score: '', //签到获得的积分
cur_year: 0, //当前选的年
cur_month: 0, //当前选的月
cur_day: 0, //当前选择的天
today: parseInt(new Date().getDate()), //本日
toMonth: parseInt(new Date().getMonth() + 1), //本月
toYear: parseInt(new Date().getFullYear()), //本年
weeks_ch: ['日', '一', '二', '三', '四', '五', '六'], //星期
isPresentMonth: false, //是否可签到
isSign: false,
showSign: false
};
},
computed: {},
onLoad() {
this.cur_year = this.toYear;
this.cur_month = this.toMonth;
this.cur_day = this.today;
this.getSignList();
},
methods: {
...mapActions(['getUserInfo']),
// 当前签到记录
getSignList() {
let that = this;
let month = that.cur_month < 10 ? '0' + that.cur_month : that.cur_month;
let query = `${that.cur_year}-${month}`;
that.$http('user.signList', {
month: query
}).then(res => {
if (res.code == 1) {
let emptyDays = that.calculateEmptyGrids();
that.cuntinueDays = that.cuntinueDays === 0 ? res.data.cuntinue_days : that.cuntinueDays;
that.days = [...emptyDays, ...res.data.days];
that.selSign();
}
});
},
// 选中日期
selSign() {
let that = this;
let selToday = `${that.toYear}-${that.toMonth.toString().padStart(2, '0')}-${that.today.toString().padStart(2, '0')}`;
let newDay = `${that.cur_year}-${that.cur_month.toString().padStart(2, '0')}-${that.cur_day.toString().padStart(2, '0')}`;
if (selToday === newDay) {
let day = that.days.find(item => {
return item.date === selToday;
});
that.isSign = day.is_sign;
}
},
// 计算当月1号前空了几个格子
calculateEmptyGrids() {
let that = this;
let emptyDays = [];
const firstDayOfWeek = new Date(Date.UTC(that.cur_year, that.cur_month - 1, 1)).getDay();
if (firstDayOfWeek > 0) {
for (let i = 0; i < firstDayOfWeek; i++) {
let obj = {
day: null,
is_sign: false
};
emptyDays.push(obj);
}
}
return emptyDays;
},
//签到
onSign() {
let that = this;
let month = that.cur_month.toString().padStart(2, '0');
let query = `${that.cur_year}-${month}`;
that.$http(
'user.sign',
{
month: query
},
'签到中...'
).then(res => {
if (res.code == 1) {
that.getSignList();
that.score = res.data.score;
that.getUserInfo();
that.showSign = true;
}
});
},
// 切换控制年月,上一个月,下一个月
handleCalendar(type) {
const cur_year = parseInt(this.cur_year);
const cur_month = parseInt(this.cur_month);
let newMonth = cur_month;
let newYear = cur_year;
switch (type) {
case 0:
//上个月
this.isPresentMonth = true;
newMonth = cur_month - 1;
if (newMonth < 1) {
newYear = cur_year - 1;
newMonth = 12;
}
if (newYear < this.toYear || (newYear === this.toYear && newMonth <= this.toMonth)) {
this.isPresentMonth = false;
}
break;
case 1:
newMonth = cur_month + 1;
if (newMonth > 12) {
newYear = cur_year + 1;
newMonth = 1;
}
if (newYear > this.toYear || (newYear === this.toYear && newMonth > this.toMonth)) {
this.isPresentMonth = true;
}
break;
default:
break;
}
this.cur_year = newYear;
this.cur_month = newMonth;
this.getSignList();
}
}
};
</script>
<style lang="scss">
// 日历
.calendar {
background: #fff;
.sign-everyday {
height: 100rpx;
background: rgba(255, 255, 255, 1);
border: 1rpx solid rgba(223, 223, 223, 0.4);
.sign-everyday-title {
font-size: 32rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
}
.sign-num-box {
font-size: 26rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
.sign-num {
font-weight: 600;
color: #e3ad5b;
padding: 0 10rpx;
}
}
}
// 年月日
.bar {
height: 100rpx;
.date {
font-weight: 600;
}
}
// 星期
.week {
.week-item {
font-size: 24rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
flex: 1;
}
}
// 日历表
.myDateTable {
display: flex;
flex-wrap: wrap;
.dateCell {
width: (750rpx/7);
height: 80rpx;
font-size: 26rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
.cell {
display: flex;
justify-content: center;
align-items: center;
}
}
}
}
.is-sign {
width: 40rpx;
height: 40rpx;
background: linear-gradient(132deg, rgba(243, 223, 177, 1), rgba(243, 223, 177, 1), rgba(236, 190, 96, 1));
border-radius: 50%;
box-shadow: 0 0 4rpx 4rpx rgba(#e3ad5b, 0.4);
color: #fff;
}
// 签到按钮
.sign-box {
height: 140rpx;
width: 100%;
.sign-btn {
width: 219rpx;
line-height: 70rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
}
}
.sign-modal-box {
width: 520rpx;
background: #fff;
border-radius: 20rpx;
position: relative;
.modal-head {
height: 320rpx;
align-items: center;
justify-content: center;
position: relative;
.modal-bg {
width: 100%;
height: 100%;
}
.sign-tag {
width: 70rpx;
height: 70rpx;
position: absolute;
top: 50rpx;
left: 50%;
transform: translateX(-50%);
}
.sign-num-box {
font-size: 30rpx;
color: #fff;
position: absolute;
top: 150rpx;
left: 50%;
transform: translateX(-50%);
.sign-num {
font-size: 36rpx;
font-weight: 600;
padding: 0 10rpx;
}
}
}
.modal-content {
padding: 30rpx 0;
.sign-success {
font-size: 34rpx;
font-weight: bold;
color: rgba(227, 173, 91, 1);
}
.sign-score {
font-size: 26rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
padding-top: 16rpx;
}
}
.modal-bottom {
padding-bottom: 47rpx;
.confirem-btn {
width: 300rpx;
line-height: 70rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
border-radius: 35rpx;
padding: 0;
font-size: 28rpx;
font-weight: 500;
color: rgba(255, 255, 255, 0.9);
}
}
}
</style>

@ -0,0 +1,269 @@
<!-- 申请分销商 -->
<template>
<view class="apply-commission-wrap">
<!-- 标题栏 -->
<view class="head-box" :style="{ backgroundImage: ' url(' + formHeadImg + ')' }">
<shopro-navbar back-icon-color="#fff" :background="{}"></shopro-navbar>
</view>
<!-- 表单 -->
<view class="apply-form">
<u-form :model="model" :rules="rules" ref="uForm" :errorType="errorType">
<block v-for="(form, index) in formList" :key="index">
<u-form-item v-if="form.type === 'input'" :labelStyle="labelStyle"   label-width="150" label-position="left" :label="form.name" :prop="`value${index}`">
<u-input
:disabled="!hasPostBtn"
:placeholder="`请输入${form.name}`"
:placeholderStyle="placeholderStyle"
v-model="model['value' + index]"
type="text"
></u-input>
</u-form-item>
<u-form-item v-if="form.type === 'number'" :labelStyle="labelStyle" label-position="left" :label="form.name" :prop="`value${index}`" label-width="150">
<u-input
:disabled="!hasPostBtn"
:placeholder="`请输入${form.name}`"
:placeholderStyle="placeholderStyle"
v-model="model['value' + index]"
type="number"
></u-input>
</u-form-item>
<u-form-item
v-if="form.type === 'image'"
:labelStyle="labelStyle"
:prop="`value${index}`"
:label="form.name"
label-position="top"
label-width="150"
:borderBottom="true"
>
<u-upload
:deletable="hasPostBtn"
:placeholderStyle="placeholderStyle"
:showProgress="false"
@on-uploaded="uploadSuccess($event, `value${index}`)"
@on-remove="uploadRemove($event, `value${index}`)"
:file-list="model[`valueList${index}`]"
:action="`${$BASE_URL}/api/common/upload`"
:header="{token: token}"
width="148"
height="148"
maxCount="1"
></u-upload>
</u-form-item>
</block>
<view class="agreement u-flex" v-if="protocol && hasPostBtn">
<u-checkbox v-model="model.agreement" activeColor="#b095ff" shape="circle" @change="onAgreement"></u-checkbox>
<view class="agreement-text" @tap="$Router.push({ path: '/pages/public/richtext', query: { id: protocol.richtext_id } })">
我已阅读并遵守
<text class="text-underline">{{ protocol.name }}</text>
</view>
</view>
<button class="u-reset-button save-btn" v-if="hasPostBtn" @tap="onSubmit" :disabled="isFormEnd">确认提交</button>
</u-form>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
formList: [], //返回的表单
formHeadImg: '', //表单头部背景
protocol: null, //协议
isFormEnd: false, //提交成功
hasPostBtn: false, //是否显示提交按钮
errorType: ['message'],
labelStyle: {
'font-size': '28rpx',
'font-weight': '500',
color: '#333'
},
placeholderStyle: 'font-size: 28rpx;color:#c4c4c4;',
model: {},
inputNumber: [
{
required: true,
message: '内容不能为空!',
trigger: ['change', 'blur']
}
],
inputImage: [
{
required: true,
message: '请上传图片',
trigger: ['change', 'blur']
}
],
rules: {},
token: uni.getStorageSync('token')
};
},
computed: {},
onLoad() {
this.getFrom();
},
methods: {
// 上传图片成功-单图
uploadSuccess(e, value) {
this.model[value] = e[0].response.data.url;
},
// 移除图片
uploadRemove(index, value) {
this.model[value] = '';
},
// 勾选同意
onAgreement(e) {
this.model.agreement = e.value;
},
// 获取申请分销商表单
getFrom() {
let that = this;
that.$http('commission.form').then(res => {
if (res.code === 1) {
that.protocol = res.data.apply_protocol; //表单协议同
that.formList = res.data.apply_info; //表单
that.hasPostBtn = res.data.apply_status; //是否显示提交按钮
that.formHeadImg = res.data.background_image ? res.data.background_image : this.$IMG_URL + '/imgs/commission/apply_bg.png'; //头部背景
that.initRules(); //规则
that.$refs.uForm.setRules(that.rules);
}
});
},
// 构建验证规则
initRules() {
let that = this;
that.formList.forEach((item, index) => {
that.model = {
...that.model,
...{
[`value${index}`]: item.value ? item.value : null
}
}; //构造model数据
that.model.agreement = false;
if (item.type === 'input' || item.type === 'number') {
//构造表单验证规则
that.rules = {
...that.rules,
...{
[`value${index}`]: that.inputNumber
}
};
}
if (item.type === 'image') {
if (item.value) {
let arr = [];
arr.push({ url: item.value });
that.model = {
...that.model,
...{
[`valueList${index}`]: arr
}
};
}
that.rules = {
...that.rules,
...{
[`value${index}`]: that.inputImage
}
};
}
});
},
//申请分销商
applyCommission(data) {
let that = this;
that.isFormEnd = true;
that.$http(
'commission.apply',
{
data: data
},
'申请中...'
).then(res => {
that.isFormEnd = false;
if (res.code === 1) {
uni.showToast({
title: res.msg,
success: () => {
that.$Router.back();
}
});
}
});
},
// 提交
onSubmit() {
let that = this;
this.$refs.uForm.validate(valid => {
if (valid) {
if (!this.model.agreement && this.protocol) return this.$u.toast('请勾选协议');
let formData = this.formList;
formData.map((item, index) => {
item.value = that.model[`value${index}`];
});
this.applyCommission(formData);
} else {
this.$u.toast('请完善表单');
}
});
}
}
};
</script>
<style lang="scss">
.apply-commission-wrap {
height: 100%;
background-color: #fff;
.head-box {
width: 100%;
height: 400rpx;
background-size: 100% auto;
background-repeat: no-repeat;
}
}
// 表单
.apply-form {
width: 750rpx;
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
.agreement {
margin-top: 20rpx;
.agreement-text {
font-size: 24rpx;
font-weight: 500;
color: #b095ff;
.text-underline {
text-decoration: underline;
}
}
}
.save-btn {
width: 690rpx;
line-height: 86rpx;
background: linear-gradient(-90deg, #a36fff, #5336ff);
box-shadow: 0px 7rpx 11rpx 2rpx rgba(124, 103, 214, 0.34);
border-radius: 43rpx;
font-weight: 500;
color: #ffffff;
margin: 30rpx auto;
}
}
</style>

@ -0,0 +1,393 @@
<!-- 佣金明细 -->
<template>
<view class="commission-log-wrap">
<!-- 钱包卡片 -->
<view class="wallet-wrap">
<view class="wallet-card">
<view class="head-box u-flex">
<view class="head-title">总收益(元)</view>
<button class="u-reset-button look-btn" @tap="showMoney = !showMoney">
<view class="u-iconfont" :class="showMoney ? 'uicon-eye-fill' : 'uicon-eye-off'" style="color: #fff;font-size: 50rpx;"></view>
</button>
</view>
<view class="card-num">{{ showMoney ? agentInfo.total_income || '0.00' : '***' }}</view>
<view class="card-bottom u-flex u-row-left">
<view class="card-item u-flex-1">
<view class="item-title">待入账佣金</view>
<view class="item-value">{{ showMoney ? agentInfo.delay_money || '0.00' : '***' }}</view>
</view>
<view class="card-item u-flex-1">
<view class="item-title">可提现佣金</view>
<view class="item-value">{{ showMoney ? userInfo.money || '0.00' : '***' }}</view>
</view>
</view>
<button class="u-reset-button draw-btn" @tap="jump('/pages/user/wallet/withdraw')">提现</button>
</view>
</view>
<!-- 筛选 -->
<u-sticky offset-top="0" :enable="true">
<view class="head_box u-flex u-row-between">
<button class="u-reset-button date-btn u-flex u-col-center" @tap="showCalendar = true">
{{ selDateText }}
<text class="u-iconfont uicon-arrow-down-fill u-m-l-20" style="color:#e5e5e5 ; font-size: 34rpx;"></text>
</button>
<view class="">
<view class="total-box">{{ stateMap[stateCurrent] }}¥{{ totalMoney || '0.00' }}</view>
</view>
</view>
<!-- 状态分类 -->
<view class="u-flex nav-box">
<view class="state-item u-flex-1 " v-for="(state, index) in statusList" :key="state.value" @tap="onTab(state.value)">
<text class="state-title" :class="{ 'title-active': stateCurrent === state.value }">{{ state.name }}</text>
<text class="underline" :class="{ 'underline-active': stateCurrent === state.value }"></text>
</view>
</view>
</u-sticky>
<view class="item-box">
<!-- 佣金明细列表 -->
<view class="log-item u-flex u-row-between" v-for="item in rewardLog" :key="item.id">
<view class="item-left u-flex">
<image class="log-img" :src="item.buyer.avatar" mode=""></image>
<view class="">
<view class="log-name">{{ item.buyer.nickname }}</view>
<view class="log-notice">{{ $u.timeFormat(item.createtime, 'yyyy.mm.dd') }}</view>
</view>
</view>
<view class="item-right">
<view class="log-num" :style="{ color: classType[item.status] }">{{ item.status < 0 ? '-' : '+' }} {{ item.commission }}</view>
<view class="log-date"></view>
</view>
</view>
<!-- 更多 -->
<u-loadmore v-if="rewardLog.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
<!-- 缺省页 -->
<shopro-empty v-if="isEmpty" marginTop="100rpx" :image="$IMG_URL + '/imgs/empty/no_data.png'" tipText="暂无数据"></shopro-empty>
</view>
<!-- 日期选择 -->
<u-calendar
v-model="showCalendar"
safeAreaInsetBottom
ref="uCalendar"
mode="range"
start-text="开始"
end-text="结束"
active-bg-color="#7063d2"
active-color="#fff"
range-bg-color="#e5e2ff"
range-color="#7063d2"
:customStyle="{ background: 'linear-gradient(90deg, #A36FFF, #5336FF)', boxShadow: '0 7rpx 11rpx 2rpx rgba(124, 103, 214, 0.34)' }"
@change="selDate"
></u-calendar>
</view>
</template>
<script>
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {},
data() {
return {
stateCurrent: 'all', //默认
stateMap: {
all: '全部收入',
waiting: '待入账',
accounted: '已入账',
back: '已退回',
cancel: '已取消'
},
classType: {
'-1': '#EB2B3D',
'0': '#05C3A1',
'1': '#7063D2',
'-2': '#EEEEEE'
},
statusList: [
{
name: '全部',
value: 'all'
},
{
name: '待入账',
value: 'waiting'
},
{
name: '已入账',
value: 'accounted'
},
{
name: '已退回',
value: 'back'
},
{
name: '已取消',
value: 'cancel'
}
],
showMoney: true, //是否显示金额
//日期选择
showCalendar: false,
selDateText: '',
rewardLog: [], //佣金记录
propsDate: '', //日期参数
totalMoney: '', //收入
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1,
isEmpty: false
};
},
computed: {
...mapGetters(['userInfo', 'agentInfo'])
},
onLoad() {
this.getToday();
this.getCommissionLog();
},
onPullDownRefresh() {
this.rewardLog = [];
this.currentPage = 1;
this.lastPage = 1;
this.getCommissionLog();
},
onReachBottom() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getCommissionLog();
}
},
methods: {
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 切换分类
onTab(state) {
if (this.stateCurrent !== state) {
this.rewardLog = [];
this.currentPage = 1;
this.lastPage = 1;
this.stateCurrent = state;
this.getCommissionLog();
}
},
// 今日
getToday() {
let now = new Date();
this.selDateText = `${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}`;
let dateText = `${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}`;
this.propsDate = `${dateText}-${dateText}`;
},
// 选择日期
selDate(e) {
this.rewardLog = [];
this.currentPage = 1;
this.lastPage = 1;
this.selDateText = `${e.startYear}.${e.startMonth}.${e.startDay}-${e.endYear}.${e.endMonth}.${e.endDay}`;
let dateText = `${e.startYear}/${e.startMonth}/${e.startDay}-${e.endYear}/${e.endMonth}/${e.endDay}`;
this.propsDate = dateText;
this.getCommissionLog();
this.$refs.uCalendar.init();
},
// 佣金明细
getCommissionLog() {
let that = this;
that.loadStatus = 'loading';
that.$http(
'commission.rewardLog',
{
date: that.propsDate,
type: that.stateCurrent,
page: that.currentPage
},
'加载中...'
).then(res => {
uni.stopPullDownRefresh();
if (res.code === 1) {
that.totalMoney = res.data.total_money;
that.rewardLog = [...that.rewardLog, ...res.data.rewards.data];
that.lastPage = res.data.rewards.last_page;
that.isEmpty = !that.rewardLog.length;
that.loadStatus = that.currentPage < res.data.rewards.last_page ? 'loadmore' : 'nomore';
}
});
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getCommissionLog();
}
}
}
};
</script>
<style lang="scss">
// 分类
.state-item {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fff;
border-bottom: 1rpx solid rgba(#999, 0.5);
.state-title {
color: #666;
font-weight: 500;
font-size: 28rpx;
line-height: 90rpx;
}
.title-active {
color: #333;
}
.underline {
display: block;
width: 68rpx;
height: 4rpx;
background: #fff;
border-radius: 2rpx;
}
.underline-active {
background: #5e49c3;
display: block;
width: 68rpx;
height: 4rpx;
border-radius: 2rpx;
}
}
// 钱包卡片
.wallet-wrap {
background-color: #fff;
padding: 30rpx;
}
.wallet-card {
width: 690rpx;
height: 301rpx;
background: url($IMG_URL+'/imgs/commission/commission_card_bg.png') no-repeat;
background-size: 100% 100%;
border-radius: 20rpx;
padding: 30rpx;
position: relative;
box-shadow: 1rpx 5rpx 16rpx 0px rgba(94, 73, 195, 0.81);
.head-box {
margin-bottom: 20rpx;
.head-title {
font-size: 24rpx;
font-weight: 400;
color: #ffffff;
}
.look-btn {
color: #fff;
font-size: 30rpx;
background: none;
padding: 0;
margin-left: 20rpx;
}
}
.card-num {
font-size: 40rpx;
font-weight: 500;
color: #fefefe;
margin-bottom: 30rpx;
}
.card-bottom {
.card-item {
.item-title {
font-size: 24rpx;
font-weight: 400;
color: #ffffff;
margin-bottom: 10rpx;
}
.item-value {
font-size: 30rpx;
font-weight: 500;
color: #fefefe;
}
}
}
.draw-btn {
position: absolute;
top: 35rpx;
right: 35rpx;
width: 121rpx;
line-height: 58rpx;
background: #ffffff;
border-radius: 29rpx;
padding: 0;
font-size: 24rpx;
font-weight: 500;
color: #5848c4;
}
}
// 表头
.head_box {
height: 120rpx;
padding: 0 30rpx;
background-color: #f6f6f6;
.date-btn {
background-color: #fff;
line-height: 54rpx;
border-radius: 27rpx;
padding: 0 20rpx;
font-size: 24rpx;
font-weight: 500;
color: #666666;
}
.total-box {
font-size: 24rpx;
font-weight: 500;
color: #666666;
}
}
// 佣金明细列表
.item-box {
margin: 20rpx 0;
}
.log-item {
height: 142rpx;
background-color: #fff;
padding: 0 30rpx;
border-bottom: 1rpx solid #eee;
.item-left {
.log-img {
width: 50rpx;
height: 50rpx;
border-radius: 50%;
margin-right: 24rpx;
}
.log-name {
font-size: 28rpx;
font-weight: 500;
color: #333333;
margin-bottom: 12rpx;
}
.log-notice {
font-size: 22rpx;
font-weight: 500;
color: #c0c0c0;
}
}
.item-right {
.log-num {
font-size: 28rpx;
font-weight: 500;
color: #05c3a1;
}
.log-date {
font-size: 24rpx;
font-weight: 400;
color: #c0c0c0;
}
}
}
</style>

@ -0,0 +1,167 @@
<!-- 推广商品 -->
<template>
<view class="promotion-goods-wrap">
<view class="goods-list u-flex" v-for="(item, index) in goodsList" :key="item.goods_id" @tap="jump('/pages/goods/detail', { id: item.id })">
<view class="img-box"><image class="goods-img" :src="item.image" mode=""></image></view>
<view class="goods-info u-flex-col u-row-between">
<view class="goods-title u-ellipsis-1">{{ item.title }}</view>
<view class="goods-des u-ellipsis-1">{{ item.subtitle }}</view>
<view class="goods-price u-flex font-OPPOSANS">
<view class="price">¥{{ item.price }}</view>
<view class="origin-price">¥{{ item.original_price }}</view>
</view>
<view class="commission-num">预计佣金:¥{{ item.commission_money }}</view>
</view>
<button class="share-btn u-reset-button" @tap.stop="toShare(index)">分享赚</button>
</view>
<!-- 分享组件 -->
<shopro-share v-model="showShare" :posterInfo="posterInfo" :posterType="'goods'"></shopro-share>
<!-- 缺省页 -->
<shopro-empty v-if="isEmpty" :image="$IMG_URL + '/imgs/empty/no_data.png'" tipText="暂无数据"></shopro-empty>
<!-- 更多 -->
<u-loadmore v-if="goodsList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</view>
</template>
<script>
import share from '@/shopro/share';
export default {
components: {},
data() {
return {
showShare: false, //是否分享
posterInfo: {}, //分享信息
goodsList: [], //分销商品
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1,
isEmpty: false
};
},
computed: {},
onLoad() {
this.getGoodList();
},
onUnload() {
share.setShareInfo();
},
onReachBottom() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getGoodList();
}
},
methods: {
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 去分享
toShare(index) {
const that = this;
let goodsInfo = this.goodsList[index];
this.posterInfo = goodsInfo;
share.setShareInfo({
title: goodsInfo.title,
desc: goodsInfo.subtitle,
image: goodsInfo.image,
params: {
page: 2,
pageId: goodsInfo.id
}
});
this.showShare = true;
},
getGoodList() {
let that = this;
that.loadStatus = 'loading';
that.$http('commission.goods', {
page: that.currentPage
}).then(res => {
if (res.code === 1) {
that.goodsList = [...that.goodsList, ...res.data.data];
that.lastPage = res.data.last_page;
that.isEmpty = !that.goodsList.length;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
}
}
};
</script>
<style lang="scss">
page {
background-color: #fff;
}
// 推广商品列表
.goods-list {
padding: 30rpx;
border-bottom: 1rpx solid rgba(#dfdfdf, 0.5);
position: relative;
.share-btn {
position: absolute;
bottom: 30rpx;
right: 30rpx;
width: 160rpx;
line-height: 60rpx;
background: linear-gradient(90deg, #a36fff, #5336ff);
box-shadow: 0px 7rpx 11rpx 2rpx rgba(124, 103, 214, 0.34);
border-radius: 30rpx;
font-size: 28rpx;
color: #fff;
font-weight: 500;
}
.img-box {
border: 1rpx solid rgba(223, 223, 223, 0.43);
margin-right: 30rpx;
.goods-img {
width: 200rpx;
height: 200rpx;
}
}
.goods-info {
align-items: flex-start;
height: 200rpx;
.goods-title {
width: 400rpx;
font-size: 28rpx;
font-weight: 500;
color: #333333;
margin-bottom: 10rpx;
}
.goods-des {
width: 400rpx;
font-size: 22rpx;
font-weight: 500;
color: #a8700d;
margin-bottom: 10rpx;
}
.goods-price {
margin-bottom: 10rpx;
.price {
font-size: 28rpx;
font-weight: 500;
color: #333333;
margin-right: 16rpx;
}
.origin-price {
font-size: 24rpx;
font-weight: 400;
text-decoration: line-through;
color: #999999;
}
}
.commission-num {
font-size: 24rpx;
font-weight: 500;
color: #5e4ddf;
}
}
}
</style>

@ -0,0 +1,855 @@
<!-- 佣金中心 -->
<template>
<view style="width:100%;height: 100%;">
<view class="commission-wrap" :class="{ blur: !hasAuth }">
<!-- 标题栏 -->
<shopro-navbar back-icon-color="#fff" :background="{}"></shopro-navbar>
<!-- 用户资料 -->
<view class="user-card">
<view class="card-top u-flex u-row-between">
<view class="u-flex">
<view class="head-img-box"><image class="head-img" :src="userInfo.avatar" mode=""></image></view>
<view class="u-flex-col">
<view class="user-name">{{ userInfo.nickname }}</view>
<view class="user-info-box u-flex">
<view class="tag-box u-flex" v-if="commissionLv">
<image v-if="commissionLv.image" class="tag-img" :src="commissionLv.image" mode=""></image>
<text class="tag-title">{{ commissionLv.name }}</text>
<view v-show="showLv" class="u-iconfont uicon-arrow-right" style="color: #fff;font-size: 28rpx;"></view>
</view>
</view>
</view>
</view>
<view class="u-flex-col">
<view class="u-flex-col u-col-center">
<button class="u-reset-button log-btn u-m-b-20" @tap="jump('/pages/app/commission/commission-log')">明细</button>
<button class="u-reset-button look-btn" @tap="showMoney = !showMoney">
<view class="u-iconfont" :class="showMoney ? 'uicon-eye-fill' : 'uicon-eye-off'" style="color: #fff;font-size: 50rpx;"></view>
</button>
</view>
</view>
</view>
<!-- 收益 -->
<view class="card-bottom u-flex" v-if="commissionWallet">
<view class="u-flex-1 ">
<view class="item-title">总收益</view>
<view class="item-detail">{{ showMoney ? commissionWallet.total_income || '0.00' : '***' }}</view>
</view>
<view class="u-flex-1 u-col-center">
<view class="item-title">待入账佣金</view>
<view class="item-detail">{{ showMoney ? commissionWallet.delay_money || '0.00' : '***' }}</view>
</view>
<view class="u-flex-1 u-col-center">
<view class="item-title">我的消费</view>
<view class="item-detail">{{ showMoney ? userInfo.total_consume || '0.00' : '***' }}</view>
</view>
</view>
</view>
<!-- 滚动明细 -->
<view class="commission-log">
<scroll-view scroll-y="true" @scrolltolower="loadMore" class="scroll-box log-scroll">
<view class="log-item-box u-flex u-row-between" v-for="item in commissionLog" :key="item.id" @tap="$u.toast(item.remark)">
<view class="log-item-wrap">
<view class="log-item u-flex u-ellipsis-1 u-col-center">
<view class="u-flex u-col-center ">
<image
class="log-img"
:src="item.oper_type !== 'user' ? logMap[item.oper_type] : item.oper ? item.oper.avatar : logMap['admin']"
mode=""
></image>
</view>
<view class="log-text u-ellipsis-1">{{ item.remark }}</view>
</view>
</view>
<text class="log-time">{{ $u.timeFrom(item.createtime) }}</text>
</view>
<!-- 更多 -->
<view class="loadmore-wrap" v-if="commissionLog.length"><u-loadmore :status="loadStatus" icon-type="flower" color="#f6f6f6" /></view>
</scroll-view>
</view>
<!-- 功能菜单 -->
<view class="menu-box u-flex">
<view class="menu-item u-flex-col u-col-center" v-for="(menu, index) in menuList" v-if="!menu.isAgentFrom" :key="index" @tap="jump(menu.path)">
<image class="item-img" :src="menu.img" mode=""></image>
<view class="item-title">{{ menu.title }}</view>
</view>
</view>
</view>
<!-- 分享组件 -->
<shopro-share v-model="showShare" posterType="user"></shopro-share>
<!-- 佣金中心权限验证 -->
<u-popup v-if="showAuthModal" class="auth-box" :mask="false" v-model="showAuthModal" mode="center" :mask-close-able="false" close-icon-pos="top-left" border-radius="20">
<view class="notice-box">
<view class="img-wrap"><image class="notice-img" :src="authNotice.img" mode=""></image></view>
<view class="notice-title">{{ authNotice.title }}</view>
<view class="notice-detail">{{ authNotice.detail }}</view>
<button class="u-reset-button notice-btn" @tap="onAuthBtn(authNotice.btnPath)">{{ authNotice.btnText }}</button>
<button class="u-reset-button back-btn" @tap="$Router.back()" v-if="!authNotice.hideBtn">返回</button>
<button class="u-reset-button back-btn" @tap="hasAuth = true" v-else>取消</button>
</view>
</u-popup>
<!-- 成为分销商条件 -->
<u-popup
v-if="showTerm"
class="into-agent-modal"
:bgStyle="{
background: 'none'
}"
v-model="showTerm"
mode="center"
:mask="false"
:mask-close-able="false"
>
<view class="condition-box u-flex u-row-center u-col-center">
<!-- 商品 -->
<view class="goods-condition u-flex-col u-col-center" v-if="showGoodsTerm">
<scroll-view class="card-wrap" scroll-y="true">
<view class="card-box u-flex u-m-x-10 u-m-y-10" v-for="item in goodsTermList" :key="item.id" @tap="jump('/pages/goods/detail', { id: item.id })">
<view class="img-wrap"><image class="goods-img" :src="item.image" mode=""></image></view>
<view class="detail u-fle u-row-between">
<view class="title u-ellipsis-2 u-m-b-10">{{ item.title }}</view>
<view class="sub u-ellipsis-1">{{ item.subtitle }}</view>
</view>
</view>
<view class="hack" style="height: 100rpx;"></view>
</scroll-view>
<view class="btn-box u-flex-col u-col-center u-p-20">
<button class="u-reset-button buy-btn u-m-b-20" @tap="$Router.back()">知道了</button>
<view class="tips">* 购买指定商品即可成为分销商</view>
</view>
</view>
<!-- 金额人数 -->
<view class="goods-condition u-flex u-row-between" v-if="showMoneyTerm">
<scroll-view class="card-wrap" scroll-y="true">
<view class="card-box u-flex u-m-x-10 u-m-y-10" :class="{ 'filter-card': userInfo.total_consume == moneyTermNum }">
<view class="img-wrap"><image class="goods-img" :src="$IMG_URL + '/imgs/user/commission_task_card.png'" mode=""></image></view>
<view class="detail">
<view class="title u-ellipsis-2 u-m-l-10">满足以下消费金额</view>
<!-- 进度条 -->
<view class="u-flex modal-progress ">
<view class="progress-box u-m-l-30 u-flex u-col-bottom">
<view class="cu-progress" style="width:150rpx ;">
<!-- 圆环 -->
<view
class="round-wrap"
v-if="userInfo.total_consume != moneyTermNum"
:style="[{ left: getProgress(userInfo.total_consume, moneyTermNum) + '%' }]"
>
<view class="round-inner"></view>
</view>
<u-line-progress
height="12"
:show-percent="false"
inactive-color="#7430EE"
:percent="getProgress(userInfo.total_consume, moneyTermNum)"
active-color="#C6A6FF"
></u-line-progress>
<!-- 已完成数 -->
<view
class="progress-num u-m-t-10"
v-if="userInfo.total_consume != moneyTermNum"
:style="[{ left: getProgress(userInfo.total_consume, moneyTermNum) + '%' }]"
>
{{ userInfo.total_consume }}
</view>
</view>
</view>
<view class="progress-tip" v-if="userInfo.total_consume != moneyTermNum">{{ moneyTermNum }}</view>
</view>
</view>
</view>
</scroll-view>
<view class="btn-box u-flex-col u-p-20 u-col-center">
<button class="u-reset-button buy-btn u-m-b-10" @tap="$Router.back()">知道了</button>
<view class="tips">* 满足指定消费金额即可成为分销商</view>
</view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
import share from '@/shopro/share';
export default {
components: {},
data() {
return {
showShare: false, //是否显示分享海报
showMoney: true, //是否显示金额
hasAuth: true, //是否有权限
commissionLv: null, //分销商等级
commissionWallet: null, //分销商钱包
agentFrom: null, //是否显示我的资料
showLv: true,
commissionLog: [], //动态
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1,
logMap: {
system: this.$IMG_URL + '/imgs/commission/commission_base_notice.png',
admin: this.$IMG_URL + '/imgs/commission/commission_base_avatar.png'
},
showTerm: false, //条件弹窗
showGoodsTerm: false, //商品条件
showMoneyTerm: false, //金额条件
goodsTermList: [],
moneyTermNum: 0,
authNotice: {},
menuList: [
//menu
{
img: this.$IMG_URL + '/imgs/commission/commission_icon1.png',
title: '我的团队',
path: '/pages/app/commission/team'
},
{
img: this.$IMG_URL + '/imgs/commission/commission_icon2.png',
title: '佣金明细',
path: '/pages/app/commission/commission-log'
},
{
img: this.$IMG_URL + '/imgs/commission/commission_icon3.png',
title: '分销订单',
path: '/pages/app/commission/order'
},
{
img: this.$IMG_URL + '/imgs/commission/commission_icon4.png',
title: '推广商品',
path: '/pages/app/commission/goods'
},
{
img: this.$IMG_URL + '/imgs/commission/commission_icon5.png',
title: '我的资料',
path: '/pages/app/commission/apply',
isAgentFrom: true
},
{
img: this.$IMG_URL + '/imgs/commission/commission_icon6.png',
title: '分销排行',
path: '/pages/app/commission/rankings'
},
{
img: this.$IMG_URL + '/imgs/commission/commission_icon7.png',
title: '邀请海报',
path: ''
},
{
img: this.$IMG_URL + '/imgs/commission/commission_icon8.png',
title: '分享记录',
path: '/pages/app/commission/share-log'
}
]
};
},
computed: {
...mapGetters(['userInfo', 'agentInfo']),
showAuthModal: {
get() {
return !!this.authNotice.title && !this.hasAuth;
},
set(val) {
return val;
}
}
},
onShow() {
this.init();
},
onHide() {
this.commissionLog = [];
this.lastPage = 1;
this.currentPage = 1;
},
onLoad() {},
onPullDownRefresh() {
this.currentPage = 1;
this.lastPage = 1;
this.commissionLog = [];
this.init();
},
methods: {
...mapActions(['getAgent', 'getUserInfo']),
init() {
return Promise.all([this.getStatus(), this.getUserInfo()]).then(() => {
uni.stopPullDownRefresh();
});
},
// 跳转
jump(path, query) {
if (!path) {
this.showShare = true;
} else {
this.$Router.push({
path: path,
query: query
});
}
},
// 百分比
getProgress(sales, stock) {
let unit = '';
if (stock + sales > 0) {
let num = (sales / stock) * 100;
unit = num.toFixed(2);
} else {
unit = '0';
}
return Number(unit);
},
// 身份认证
getStatus() {
let that = this;
this.getAgent().then(res => {
uni.stopPullDownRefresh();
if (res.code === 1) {
that.authStatus(res.data);
that.commissionWallet = res.data.data;
that.commissionLv = res.data.data?.agent_level;
that.showLv = res.data.upgrade_display;
that.menuList.map(item => {
if (item.title === '我的资料') {
item.isAgentFrom = !res.data.agent_form;
}
});
that.commissionWallet && that.getLog();
}
});
},
// 分销动态
getLog() {
let that = this;
that.loadStatus = 'loading';
this.$http('commission.log', {
page: that.currentPage,
per_page: 5
}).then(res => {
if (res.code === 1) {
that.commissionLog = [...that.commissionLog, ...res.data.data];
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getLog();
}
},
// 状态鉴权
authStatus(data) {
switch (data.status) {
case 'forbidden':
this.hasAuth = false;
this.showTerm = false;
this.authNotice = {
img: this.$IMG_URL + '/imgs/commission/auth_stop.png',
title: '抱歉!你的账户已被禁用',
detail: data.msg,
btnText: '联系客服',
btnPath: '/pages/public/chat/index'
};
break;
case 'pending':
this.hasAuth = false;
this.showTerm = false;
this.authNotice = {
img: this.$IMG_URL + '/imgs/commission/auth_stop.png',
title: '正在审核中!',
detail: data.msg,
btnText: '知道了',
btnPath: ''
};
break;
case 'needinfo':
this.hasAuth = false;
this.authNotice = {
img: this.$IMG_URL + '/imgs/commission/auth_perfect.png',
title: '待完善信息',
detail: data.msg,
btnText: '去完善',
btnPath: '/pages/app/commission/apply'
};
break;
case 'reject':
this.hasAuth = false;
this.showTerm = false;
this.authNotice = {
img: this.$IMG_URL + '/imgs/commission/auth_reject.png',
title: '申请驳回',
detail: data.msg,
btnText: '修改资料',
btnPath: '/pages/app/commission/apply'
};
break;
case 'close':
this.hasAuth = false;
this.showTerm = false;
this.authNotice = {
img: this.$IMG_URL + '/imgs/commission/auth_close.png',
title: '分销中心已关闭',
detail: data.msg,
btnText: '我知道了',
btnPath: ''
};
break;
case 'freeze':
this.hasAuth = false;
this.showTerm = false;
this.authNotice = {
img: this.$IMG_URL + '/imgs/commission/auth_freeze.png',
title: '抱歉!你的账户已被冻结',
detail: data.msg,
btnText: '联系客服',
btnPath: '/pages/public/chat/index',
hideBtn: true
};
break;
case null:
this.hasAuth = false;
this.showTerm = true;
if (data.become_agent) {
if (data.become_agent.type === 'goods') {
this.showGoodsTerm = true;
this.getGoodsTermList(data.become_agent.value);
}
if (data.become_agent.type === 'consume') {
this.showMoneyTerm = true;
this.moneyTermNum = data.become_agent.value;
}
}
break;
default:
this.hasAuth = true;
this.showTerm = false;
}
},
// 成为分销商,商品列表
getGoodsTermList(ids) {
let that = this;
that.$http('goods.lists', {
goods_ids: ids
}).then(res => {
if (res.code === 1) {
that.goodsTermList = res.data.data;
}
});
},
// 权限
onAuthBtn(path) {
path
? this.$Router.push({
path: path
})
: this.$Router.back();
}
}
};
</script>
<style lang="scss" scoped>
.commission-wrap {
background: url($IMG_URL+'/imgs/commission/commission_bg1.jpg') no-repeat;
background-size: cover;
height: 100%;
width: 100%;
background-position: center center;
transition: all 0.3s ease-in-out 0s;
overflow: hidden;
}
// 进度条
.progress-box {
.progress-tip {
font-size: 24rpx;
font-weight: 500;
color: #c7b2ff;
}
.progress-num {
font-size: 24rpx;
position: absolute;
color: #c7b2ff;
bottom: -30rpx;
white-space: nowrap;
}
.round-wrap {
width: 30rpx;
height: 30rpx;
border: 2rpx solid #5c3cff;
background: linear-gradient(0deg, #a36fff 0%, #846fff 100%);
border-radius: 50%;
position: absolute;
bottom: -6rpx;
transform: translateY(-50%);
left: 0;
margin-left: -15rpx;
.round-inner {
width: 20rpx;
height: 20rpx;
border: 2rpx solid #5c3cff;
background: linear-gradient(0deg, #a36fff 0%, #846fff 100%);
border-radius: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
// 佣金中心权限验证蒙层
.blur {
filter: blur(20rpx);
transition: all 0.3s ease-in-out 0s;
}
.auth-box {
width: 100%;
height: 100%;
background: none;
.notice-box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #fff;
width: 612rpx;
min-height: 658rpx;
background: #ffffff;
padding: 30rpx;
border-radius: 20rpx;
.img-wrap {
margin-bottom: 50rpx;
.notice-img {
width: 180rpx;
height: 170rpx;
}
}
.notice-title {
font-size: 35rpx;
font-weight: bold;
color: #46351b;
margin-bottom: 28rpx;
}
.notice-detail {
font-size: 28rpx;
font-weight: 400;
color: #999999;
line-height: 36rpx;
margin-bottom: 50rpx;
}
.notice-btn {
width: 492rpx;
line-height: 70rpx;
background: linear-gradient(-90deg, #a36fff, #5336ff);
box-shadow: 0px 7rpx 11rpx 2rpx rgba(124, 103, 214, 0.34);
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
margin-bottom: 10rpx;
}
.back-btn {
width: 492rpx;
line-height: 70rpx;
font-size: 28rpx;
font-weight: 500;
color: #c5b8fb;
background: none;
}
}
}
// 成为分销商
.into-agent-modal {
.condition-box {
width: 640rpx;
height: 786rpx;
background: url($IMG_URL+'/imgs/commission/into_commission.png') no-repeat;
background-size: 100% 100%;
}
.goods-condition {
width: 484rpx;
height: 580rpx;
padding: 80rpx 0 30rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
.card-wrap {
width: 484rpx;
height: 460rpx;
padding: 10rpx;
padding-bottom: 30rpx;
}
.modal-progress {
.progress-tip {
font-size: 24rpx;
}
.progress-box {
margin-right: 10rpx;
.cu-progress {
width: 200rpx;
position: relative;
.round-wrap {
width: 20rpx;
height: 20rpx;
}
.round-inner {
width: 10rpx;
height: 10rpx;
}
}
}
}
.card-box {
background: rgba(#c3b6ff, 0.3);
padding: 10rpx;
border-radius: 10rpx;
.img-wrap {
background: #fff;
width: 110rpx;
height: 110rpx;
border-radius: 10rpx;
margin-right: 20rpx;
.goods-img {
width: 110rpx;
height: 110rpx;
border-radius: 10rpx;
}
}
.detail {
height: 110rpx;
width: 280rpx;
align-items: flex-start;
.title {
font-size: 20rpx;
font-weight: 500;
color: #333333;
line-height: 35rpx;
text-align: left;
}
.sub {
font-size: 16rpx;
font-weight: 500;
color: #9281e2;
text-align: left;
width: 280rpx;
}
}
}
.filter-card {
filter: grayscale(100%);
filter: gray;
}
.btn-box {
background-color: #fff;
width: 100%;
position: absolute;
bottom: 0;
.buy-btn {
width: 390rpx;
line-height: 60rpx;
background: linear-gradient(-90deg, #a36fff, #5336ff);
box-shadow: 0px 7rpx 11rpx 2rpx rgba(124, 103, 214, 0.34);
border-radius: 35rpx;
font-size: 24rpx;
font-weight: 500;
color: #ffffff;
}
.tips {
font-size: 20rpx;
font-weight: 500;
color: #9281e2;
}
}
}
}
// 用户资料卡片
.user-card {
width: 690rpx;
min-height: 350rpx;
border-radius: 14rpx;
margin: 30rpx;
background: url($IMG_URL+'/imgs/commission/commission_card_bg.png') no-repeat;
background-size: 100% 100%;
padding-top: 10rpx;
.card-top {
padding: 40rpx 40rpx 30rpx;
margin-bottom: 30rpx;
border-bottom: 1px solid rgba(#fff, 0.12);
.user-name {
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
line-height: 30rpx;
margin-bottom: 20rpx;
}
.log-btn {
width: 83rpx;
line-height: 41rpx;
border: 1rpx solid rgba(#ffffff, 0.33);
border-radius: 21rpx;
font-size: 22rpx;
font-weight: 400;
color: #ffffff;
}
.look-btn {
color: #fff;
font-size: 40rpx;
}
}
.head-img-box {
margin-right: 26rpx;
width: 76rpx;
height: 76rpx;
border-radius: 50px;
position: relative;
background: #fff;
padding: 10rpx;
background-clip: padding-box;
.head-img {
width: 66rpx;
height: 66rpx;
border-radius: 50%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
.user-info-box {
.tag-box {
background: rgba(0, 0, 0, 0.2);
border-radius: 21rpx;
line-height: 38rpx;
.tag-img {
width: 36rpx;
height: 36rpx;
border-radius: 50%;
}
.tag-title {
font-size: 20rpx;
padding: 0 10rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
line-height: 36rpx;
}
}
}
.card-bottom {
margin: 0 40rpx 40rpx;
.item-title {
font-size: 24rpx;
font-weight: 400;
color: #ffffff;
line-height: 30rpx;
}
.item-detail {
font-size: 40rpx;
font-family: DIN;
font-weight: 500;
color: #fefefe;
line-height: 30rpx;
margin-top: 30rpx;
}
}
}
// 滚动明细
.commission-log {
min-height: 450rpx;
padding: 0 30rpx;
margin-top: 60rpx;
.log-scroll {
height: 380rpx;
.log-item-box {
height: 78rpx;
.log-time {
margin-left: 30rpx;
font-size: 22rpx;
font-weight: 500;
color: #fefefe;
text-align: right;
}
}
.loadmore-wrap {
line-height: 80rpx;
}
.log-item {
height: 48rpx;
background: rgba(#5e49c3, 0.4);
border-radius: 24rpx;
padding-left: 6rpx;
padding-right: 20rpx;
.log-img {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
margin-right: 10rpx;
}
.log-text {
font-size: 22rpx;
font-weight: 500;
color: #fefefe;
max-width: 480rpx;
}
}
}
}
// 功能菜单
.commission-bottom-bg {
position: fixed;
width: 100%;
bottom: 0;
z-index: 6;
}
.menu-box {
flex-wrap: wrap;
margin: 30rpx;
position: fixed;
width: 750rpx;
bottom: 0;
z-index: 10;
.menu-item {
width: (690rpx/4);
margin-bottom: 50rpx;
.item-img {
width: 68rpx;
height: 68rpx;
}
.item-title {
font-size: 24rpx;
font-weight: 600;
color: #fff;
line-height: 30rpx;
margin-top: 16rpx;
}
}
}
</style>

@ -0,0 +1,497 @@
<!-- 分销订单 -->
<template>
<view class="page_box commission-order-wrap">
<view class="head_box">
<!-- 标题栏 -->
<shopro-navbar back-icon-color="#fff" :background="{}" :backTextStyle="{ color: '#fff', fontSize: '40rpx', fontWeight: '500' }" backText="分销订单"></shopro-navbar>
<!-- 团队数据总览 -->
<view class="team-data-box u-flex u-row-between">
<view class="data-card">
<view class="total-item">
<view class="item-title">团队订单数量(单)</view>
<view class="total-num">{{ agentInfo.child_order_count || 0 }}</view>
</view>
<view class="category-item u-flex">
<view class="u-flex-1">
<view class="item-title">一级订单</view>
<view class="category-num">{{ agentInfo.child_order_count_1 || 0 }}</view>
</view>
<view class="u-flex-1">
<view class="item-title">二级订单</view>
<view class="category-num">{{ agentInfo.child_order_count_2 || 0 }}</view>
</view>
</view>
</view>
<view class="data-card">
<view class="total-item">
<view class="item-title">团队订单金额(元)</view>
<view class="total-num">{{ agentInfo.child_order_money || '0.00' }}</view>
</view>
<view class="category-item u-flex">
<view class="u-flex-1">
<view class="item-title">一级订单</view>
<view class="category-num">{{ agentInfo.child_order_money_1 || '0.00' }}</view>
</view>
<view class="u-flex-1">
<view class="item-title">二级订单</view>
<view class="category-num">{{ agentInfo.child_order_money_2 || '0.00' }}</view>
</view>
</view>
</view>
</view>
<!-- 直推 -->
<view class="direct-box u-flex u-row-between">
<view class="direct-item">
<view class="item-title">直推分销订单数量(单)</view>
<view class="item-value">{{ agentInfo.order_count }}</view>
</view>
<view class="direct-item">
<view class="item-title">直推分销订单金额(元)</view>
<view class="item-value">{{ agentInfo.order_money }}</view>
</view>
</view>
<!-- 筛选 -->
<view class="">
<!-- 日期筛选 -->
<view class="filter-box u-flex u-row-between">
<button class="u-reset-button date-btn u-flex u-col-center" @tap="showCalendar = true">
{{ selDateText }}
<text class="u-iconfont uicon-arrow-down-fill u-m-l-20" style="color:#e5e5e5 ; font-size: 34rpx;"></text>
</button>
<view class="">
<view class="total-box">{{ stateMap[stateCurrent] }}¥{{ amount || '0.00' }}</view>
</view>
</view>
<!-- 状态分类 -->
<view class="u-flex nav-box">
<view class="state-item u-flex-1" v-for="(state, index) in statusList" :key="state.value" @tap="onTab(state.value)">
<text class="state-title" :class="{ 'title-active': stateCurrent === state.value }">{{ state.name }}</text>
<text class="underline" :class="{ 'underline-active': stateCurrent === state.value }"></text>
</view>
</view>
</view>
</view>
<view class="content_box">
<scroll-view scroll-y="true" @scrolltolower="loadMore" class="scroll-box">
<!-- 订单列表 -->
<view class="order-list" v-for="item in orderList" :key="item.id">
<view class="order-head u-flex u-row-between">
<text class="order-code">订单编号:{{ item.order.order_sn }}</text>
<text class="order-state">{{ item.order.status_name }}</text>
</view>
<view class="order-from u-flex u-row-between">
<view class="from-user u-flex">
<text>下单人:</text>
<image class="user-avatar" :src="item.buyer.avatar" mode=""></image>
<text class="user-name">{{ item.buyer.nickname }}</text>
</view>
<view class="order-time">{{ $u.timeFormat(item.order.createtime, ' yyyy.mm.dd hh:MM ') }}</view>
</view>
<view class="goods-card u-flex" v-for="corder in item.commission_order" :key="corder.id">
<view class="goods-img-box"><image class="goods-img" :src="corder.order_item.goods_image" mode=""></image></view>
<view class="goods-info u-flex-col u-row-between">
<view class="goods-title more-t">{{ corder.order_item.goods_title }}</view>
<view class="goods-sku">数量: {{ corder.order_item.goods_num }};{{ corder.order_item.goods_sku_text || '' }}</view>
<view class="total-box u-flex u-flex-1 u-row-between">
<view class="goods-price">
{{ corder.amount }}
<text class="goods-state">{{ corder.commission_order_status_text }}</text>
</view>
<view class="u-flex" v-if="corder.reward">
<text class="name">佣金</text>
<text class="commission-num">{{ corder.reward.commission }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty marginTop="100rpx" v-if="isEmpty" :image="$IMG_URL + '/imgs/empty/no_order.png'" tipText="暂无订单"></shopro-empty>
<!-- 更多 -->
<u-loadmore v-if="orderList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</scroll-view>
</view>
<!-- 日期选择 -->
<u-calendar
v-model="showCalendar"
safeAreaInsetBottom
ref="uCalendar"
mode="range"
start-text="开始"
end-text="结束"
active-bg-color="#7063d2"
active-color="#fff"
range-bg-color="#e5e2ff"
range-color="#7063d2"
:customStyle="{ background: 'linear-gradient(90deg, #A36FFF, #5336FF)', boxShadow: '0 7rpx 11rpx 2rpx rgba(124, 103, 214, 0.34)' }"
@change="selDate"
></u-calendar>
</view>
</template>
<script>
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {},
data() {
return {
backTextStyle: {
color: '#fff',
fontSize: '40rpx',
fontWeight: '500'
},
stateCurrent: 'all', //默认
stateMap: {
all: '全部',
// no: '不计入',
yes: '已计入',
back: '已退回',
cancel: '已取消'
},
statusList: [
{
name: '全部',
value: 'all'
},
// {
// name: '不计入',
// value: 'no'
// },
{
name: '已计入',
value: 'yes'
},
{
name: '已退回',
value: 'back'
},
{
name: '已取消',
value: 'cancel'
}
],
orderList: [], //分销订单
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1,
isEmpty: false,
//日期选择
showCalendar: false,
selDateText: '',
propsDate: '', //日期参数
amount: '' //收入
};
},
computed: {
...mapGetters(['userInfo', 'agentInfo'])
},
onLoad() {
this.getToday();
this.getOrderList();
},
onPullDownRefresh() {
this.orderList = [];
this.currentPage = 1;
this.lastPage = 1;
this.getOrderList();
},
methods: {
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 今日
getToday() {
let now = new Date();
this.selDateText = `${now.getFullYear()}.${now.getMonth() + 1}.${now.getDate()}`;
let dateText = `${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}`;
this.propsDate = `${dateText}-${dateText}`;
},
// 选择日期
selDate(e) {
this.orderList = [];
this.currentPage = 1;
this.lastPage = 1;
this.selDateText = `${e.startYear}.${e.startMonth}.${e.startDay}-${e.endYear}.${e.endMonth}.${e.endDay}`;
let dateText = `${e.startYear}/${e.startMonth}/${e.startDay}-${e.endYear}/${e.endMonth}/${e.endDay}`;
this.propsDate = dateText;
this.getOrderList();
this.$refs.uCalendar.init();
},
// 切换分类
onTab(state) {
if (this.stateCurrent !== state) {
this.orderList = [];
this.currentPage = 1;
this.lastPage = 1;
this.stateCurrent = state;
this.getOrderList();
}
},
// 分销订单
getOrderList() {
let that = this;
that.loadStatus = 'loading';
that.$http('commission.orderLog', {
date: that.propsDate,
type: that.stateCurrent,
page: that.currentPage
}).then(res => {
uni.stopPullDownRefresh();
if (res.code === 1) {
that.orderList = [...that.orderList, ...res.data.orders.data];
that.amount = res.data.amount;
that.lastPage = res.data.orders.last_page;
that.isEmpty = !that.orderList.length;
that.loadStatus = that.currentPage < res.data.orders.last_page ? 'loadmore' : 'nomore';
}
});
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getOrderList();
}
}
}
};
</script>
<style lang="scss">
// 直推
.direct-box {
margin: 20rpx;
.direct-item {
width: 341rpx;
height: 117rpx;
background: #ffffff;
border-radius: 20rpx;
padding: 20rpx;
.item-title {
font-size: 22rpx;
font-weight: 500;
color: #999999;
margin-bottom: 6rpx;
}
.item-value {
font-size: 38rpx;
font-weight: 600;
color: #333333;
}
}
}
// 团队信息总览
.team-data-box {
margin: 20rpx;
.data-card {
width: 340rpx;
background: #ffffff;
border-radius: 20rpx;
padding: 20rpx;
.item-title {
font-size: 22rpx;
font-weight: 500;
color: #999999;
line-height: 30rpx;
margin-bottom: 10rpx;
}
.total-item {
margin-bottom: 20rpx;
}
.total-num {
font-size: 38rpx;
font-weight: 600;
color: #333333;
}
.category-num {
font-size: 26rpx;
font-weight: 600;
color: #333333;
}
}
}
// 状态分类
.head_box {
width: 750rpx;
background: url($IMG_URL+'/imgs/commission/card_bg.png') no-repeat;
}
// 表头
.filter-box {
height: 120rpx;
padding: 0 30rpx;
background-color: #f6f6f6;
.date-btn {
background-color: #fff;
line-height: 54rpx;
border-radius: 27rpx;
padding: 0 20rpx;
font-size: 24rpx;
font-weight: 500;
color: #666666;
}
.total-box {
font-size: 24rpx;
font-weight: 500;
color: #666666;
}
}
.nav-box {
background-color: #fff;
}
.state-item {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.state-title {
color: #666;
font-weight: 500;
font-size: 28rpx;
line-height: 90rpx;
}
.title-active {
color: #333;
}
.underline {
display: block;
width: 68rpx;
height: 4rpx;
background: #fff;
border-radius: 2rpx;
}
.underline-active {
background: #5e49c3;
display: block;
width: 68rpx;
height: 4rpx;
border-radius: 2rpx;
}
}
// 订单列表
.order-list {
background-color: #fff;
margin-top: 20rpx;
.order-head {
padding: 20rpx;
.order-code {
font-size: 26rpx;
font-weight: 400;
color: #999999;
}
.order-state {
font-size: 26rpx;
font-weight: 500;
color: #05c3a1;
}
}
.order-from {
background-color: #f9f9f9;
padding: 20rpx;
.from-user {
font-size: 24rpx;
font-weight: 400;
color: #666666;
.user-avatar {
width: 26rpx;
height: 26rpx;
border-radius: 50%;
margin-right: 8rpx;
}
.user-name {
font-size: 24rpx;
font-weight: 400;
color: #999999;
}
}
.order-time {
font-size: 24rpx;
font-weight: 400;
color: #999999;
}
}
.goods-card {
padding: 30rpx 20rpx;
// border-bottom: 1rpx solid #dfdfdf;
.goods-img-box {
margin-right: 30rpx;
.goods-img {
width: 160rpx;
height: 160rpx;
background-color: #ccc;
}
}
.goods-info {
height: 160rpx;
width: 600rpx;
align-items: flex-start;
.goods-title {
font-size: 28rpx;
font-weight: 500;
color: #333333;
}
.goods-sku {
font-size: 24rpx;
font-weight: 400;
color: #666666;
}
.goods-price {
font-size: 30rpx;
font-weight: 500;
color: #333333;
.goods-state {
line-height: 30rpx;
padding: 0 10rpx;
background: #f1eeff;
border: 1rpx solid #603fff;
border-radius: 30rpx;
margin-left: 20rpx;
font-size: 20rpx;
color: #5e49c3;
}
&::before {
content: '¥';
font-size: 20rpx;
}
}
.total-box {
height: 80rpx;
width: 100%;
.num-price {
font-size: 24rpx;
font-weight: 400;
color: #999999;
}
.name {
font-size: 24rpx;
font-weight: 400;
color: #999999;
}
.commission-num {
font-size: 30rpx;
font-weight: 400;
color: #eb2b3d;
&::before {
content: '¥';
font-size: 22rpx;
}
}
}
}
}
}
</style>

@ -0,0 +1,148 @@
<!-- 分销排行 -->
<template>
<view class="rankings-wrap">
<!-- 标题栏 -->
<shopro-navbar back-icon-color="#fff" :background="{}" :backTextStyle="{ color: '#fff', fontSize: '40rpx', fontWeight: '500' }" backText="分销排行榜"></shopro-navbar>
<!-- 排行榜 -->
<view class="rankings-list-box">
<scroll-view scroll-y="true" @scrolltolower="loadMore" class="scroll-box">
<view class="ranking-list u-flex u-row-between" v-for="(item, index) in rankingsList" :key="index">
<view class="list-left u-flex">
<view class="tag-box u-flex u-row-center u-col-center">
<text class="tag-text" v-if="index >= 3">{{ index + 1 }}</text>
<image v-else class="tag-icon" :src="rankingsIcon[index]" mode=""></image>
</view>
<image class="user-avatar" :src="item.user ? item.user.avatar : $IMG_URL + '/imgs/base_avatar.png'" mode=""></image>
<view class="user-info">
<view class="name u-m-b-10">{{ item.user ? item.user.nickname : '当前用户已注销' }}</view>
<view class="date">{{ $u.timeFormat(item.createtime, 'yyyy年mm月dd日') }}</view>
</view>
</view>
<view class="list-right y-end">
<view class="num u-m-b-10">{{ item.total_income }}</view>
<view class="des">累计收益</view>
</view>
</view>
<!-- 更多 -->
<u-loadmore v-if="rankingsList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
<view class="hack-box" style="height: 200rpx;width: 100%;"></view>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
rankingsIcon: {
0: this.$IMG_URL + '/imgs/commission/01.png',
1: this.$IMG_URL + '/imgs/commission/02.png',
2: this.$IMG_URL + '/imgs/commission/03.png'
},
rankingsList: [], //排行榜
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1
};
},
computed: {},
onLoad() {
this.getRankings();
},
methods: {
getRankings() {
let that = this;
that.loadStatus = 'loading';
that.$http('commission.ranking', {
page: that.currentPage
}).then(res => {
if (res.code === 1) {
that.rankingsList = [...that.rankingsList, ...res.data.data];
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getRankings();
}
}
}
};
</script>
<style lang="scss">
.rankings-wrap {
background: url($IMG_URL+'/imgs/commission/rankings_bg.png') no-repeat;
background-size: 100% auto;
height: 100%;
overflow: hidden;
}
// 排行榜列表
.rankings-list-box {
background-color: #fff;
border-radius: 20rpx 20rpx 0px 0px;
width: 690rpx;
height: 100%;
margin: 60rpx auto 0;
.scroll-box {
height: 100%;
}
.ranking-list {
height: 140rpx;
padding: 0 30rpx;
border-bottom: 1rpx solid #e5e5e5;
.list-left {
.tag-box {
width: 50rpx;
font-size: 36rpx;
font-weight: 500;
color: #beb4b3;
margin-right: 20rpx;
.tag-icon {
width: 40rpx;
height: 60rpx;
}
}
.user-avatar {
width: 66rpx;
height: 66rpx;
border-radius: 50%;
margin-right: 30rpx;
}
.user-info {
.name {
font-size: 28rpx;
font-weight: bold;
color: #333333;
}
.date {
font-size: 24rpx;
font-weight: 400;
color: #999999;
}
}
}
.list-right {
.num {
font-size: 30rpx;
font-weight: 500;
color: #5e4ddf;
}
.des {
font-size: 24rpx;
font-weight: 500;
color: #a09a98;
}
}
}
}
</style>

@ -0,0 +1,255 @@
<!-- 分享记录 -->
<template>
<view class="page_box">
<!-- 标题栏 -->
<view class="head_box">
<view class="nav-box">
<!-- 标题栏 -->
<shopro-navbar back-icon-color="#fff" :background="{}" :backTextStyle="{ color: '#fff', fontSize: '40rpx', fontWeight: '500' }" backText="分享记录"></shopro-navbar>
</view>
<!-- 分类tab -->
<view class="tab-box u-flex">
<view class="tab-item u-flex-1 " v-for="(tab, index) in tabsList" :key="tab.value" @tap="onTab(tab.value)">
<text class="tab-title" :class="{ 'title-active': tabCurrent === tab.value }">{{ tab.name }}</text>
<text class="underline" :class="{ 'underline-active': tabCurrent === tab.value }"></text>
</view>
</view>
</view>
<view class="content_box">
<scroll-view scroll-y="true" @scrolltolower="loadMore" class="scroll-box">
<!-- 分享记录列表 -->
<view class="log-list u-flex" v-for="item in shareLogList" :key="item.id">
<view class="log-avatar-wrap"><image class="log-avatar" :src="item.user.avatar" mode=""></image></view>
<view class="item-right">
<view class="name">{{ item.user.nickname }}</view>
<view class="content u-flex">
<view class="content-img-wrap" v-if="item.type_data"><image class="content-img" :src="item.type_data.image" mode=""></image></view>
<view class="content-text">{{ item.msg }}</view>
</view>
<view class="item-bottom u-flex u-row-between">
<view class="time">{{ $u.timeFormat(item.createtime, 'yyyy年mm月dd日 hh:MM') }}</view>
<view class="from-text">来自{{ typeObj[item.type] }}分享</view>
</view>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty v-if="isEmpty" :image="$IMG_URL + '/imgs/empty/no_data.png'" tipText="暂无数据"></shopro-empty>
<!-- 更多 -->
<u-loadmore v-if="shareLogList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</scroll-view>
</view>
<view class="foot_box"></view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
shareLogList: [], //分享记录
tabCurrent: 'all', //默认
tabsList: [
{
name: '全部',
value: 'all'
},
{
name: '名片',
value: 'index'
},
{
name: '商品',
value: 'goods'
},
{
name: '拼团',
value: 'groupon'
}
],
typeObj: {
index: '名片',
goods: '商品',
groupon: '拼团'
},
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1,
isEmpty: false
};
},
computed: {},
onLoad() {
this.getShareLog();
},
onPullDownRefresh() {
this.currentPage = 1;
this.lastPage = 1;
this.shareLogList = [];
this.getShareLog();
},
methods: {
// 切换分类
onTab(type) {
if (this.tabCurrent !== type) {
this.tabCurrent = type;
this.currentPage = 1;
this.lastPage = 1;
this.shareLogList = [];
this.getShareLog();
}
},
// 分享记录数据
getShareLog() {
let that = this;
that.loadStatus = 'loading';
that.$http('commission.share', {
type: that.tabCurrent,
page: that.currentPage
}).then(res => {
uni.stopPullDownRefresh();
if (res.code === 1) {
that.shareLogList = [...that.shareLogList, ...res.data.data];
that.lastPage = res.data.last_page;
that.isEmpty = !that.shareLogList.length;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getShareLog();
}
}
}
};
</script>
<style lang="scss">
// nav
.head_box {
height: 280rpx;
background: url($IMG_URL+'/imgs/commission/share_head_bg.png') no-repeat;
background-size: 100% auto;
position: relative;
}
// 分类
.tab-box {
background-color: #fff;
border-radius: 20rpx 20rpx 0px 0px;
position: absolute;
width: 100%;
bottom: 0;
.tab-item {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.tab-title {
color: #666;
font-weight: 500;
font-size: 28rpx;
line-height: 90rpx;
}
.title-active {
color: #333;
}
.underline {
display: block;
width: 68rpx;
height: 4rpx;
background: #fff;
border-radius: 2rpx;
}
.underline-active {
background: #5e49c3;
display: block;
width: 68rpx;
height: 4rpx;
border-radius: 2rpx;
}
}
}
// 分享记录列表
.log-list {
background-color: #fff;
padding: 30rpx;
margin: 10rpx 0;
align-items: flex-start;
.log-avatar-wrap {
margin-right: 14rpx;
.log-avatar {
width: 40rpx;
height: 40rpx;
border-radius: 50%;
}
}
.item-right {
flex: 1;
.name {
font-size: 26rpx;
font-weight: 500;
color: #7f7aa5;
margin-bottom: 30rpx;
}
.content {
background: rgba(241, 241, 241, 0.46);
border-radius: 2rpx;
padding: 10rpx;
margin-bottom: 20rpx;
.content-img-wrap {
margin-right: 16rpx;
width: 80rpx;
height: 80rpx;
.content-img {
width: 80rpx;
height: 80rpx;
border-radius: 6rpx;
}
}
.content-text {
font-size: 24rpx;
font-weight: 500;
color: #333333;
}
}
.item-bottom {
width: 100%;
.time {
font-size: 22rpx;
font-weight: 500;
color: #c8c8c8;
}
.from-text {
font-size: 22rpx;
font-weight: 500;
color: #c8c8c8;
}
}
}
}
</style>

@ -0,0 +1,344 @@
<!-- 我的团队 -->
<template>
<view class="page_box team-wrap">
<view class="head_box">
<!-- 标题栏 -->
<shopro-navbar back-icon-color="#fff" :background="{}" :backTextStyle="{ color: '#fff', fontSize: '40rpx', fontWeight: '500' }" backText="我的团队"></shopro-navbar>
<!-- 推荐人 -->
<view class="referrer-box u-flex u-p-x-20" v-if="referrerInfo && referrerInfo.avatar">
推荐人:
<image class="referrer-avatar u-m-r-10" :src="referrerInfo.avatar" mode=""></image>
{{ referrerInfo.nickname }}
</view>
<!-- 团队数据总览 -->
<view class="team-data-box u-flex u-row-between">
<view class="data-card">
<view class="total-item">
<view class="item-title">团队总人数(人)</view>
<view class="total-num">{{ userInfo.child_user_count || 0 }}</view>
</view>
<view class="category-item u-flex">
<view class=" u-flex-1">
<view class="item-title">一级成员</view>
<view class="category-num">{{ userInfo.child_user_count_1 || 0 }}</view>
</view>
<view class=" u-flex-1">
<view class="item-title">二级成员</view>
<view class="category-num">{{ userInfo.child_user_count_2 || 0 }}</view>
</view>
</view>
</view>
<view class="data-card">
<view class="total-item">
<view class="item-title">团队分销商人数(人)</view>
<view class="total-num">{{ agentInfo.child_agent_count || 0 }}</view>
</view>
<view class="category-item u-flex">
<view class=" u-flex-1">
<view class="item-title">一级分销商</view>
<view class="category-num">{{ agentInfo.child_agent_count_1 || 0 }}</view>
</view>
<view class="u-flex-1 ">
<view class="item-title">二级分销商</view>
<view class="category-num">{{ agentInfo.child_agent_count_2 || 0 }}</view>
</view>
</view>
</view>
</view>
</view>
<view class="content_box">
<scroll-view scroll-y="true" @scrolltolower="loadMore" class="scroll-box">
<!-- 团队列表 -->
<view class="team-box">
<view class="team-list" v-for="(item, index) in teamList" :key="item.id">
<sh-collapse-item
:avatar="item.avatar"
:dateTime="item.createtime"
:level="item.agent ? item.agent.level : null"
:name="item.nickname"
:isUnfold="item.isUnfold"
:childNum="item.child_user_count_1"
@click="onTeamList(item.id, index)"
>
<view slot="collapse-children" v-if="childrenTeamList.length">
<view class="team-children u-flex" v-for="children in childrenTeamList" :key="children.id">
<image class="head-img" :src="children.avatar" mode=""></image>
<view class="head-info">
<view class="name-box u-flex">
<view class="name-text">{{ children.nickname }}</view>
<view class="grade-tag tag-box u-flex" v-if="children.agent">
<image class="tag-img" :src="children.agent ? children.agent.level.image : ''" mode=""></image>
<text class="tag-title">{{ children.agent ? children.agent.level.name : '' }}</text>
</view>
</view>
<view class="head-time">{{ $u.timeFormat(children.createtime, 'yyyy年mm月dd日') }}</view>
</view>
</view>
<button class="cu-btn refresh-btn u-flex u-row-center u-col-center" @tap.stop="childrenLoadMore(item.id)">
<view
v-if="childrenCurrentPage < childrenLastPage"
class="u-iconfont uicon-reload u-m-r-10"
:class="{ 'refresh-active': isRefresh }"
style="color: #999;font-size: 26rpx;"
></view>
{{ childrenLoad ? '点击加载更多' : '没有更多~' }}
</button>
</view>
</sh-collapse-item>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty v-if="isEmpty" marginTop="50rpx" :image="$IMG_URL + '/imgs/empty/no_team.png'" tipText="暂无团队人员"></shopro-empty>
<!-- 更多 -->
<u-loadmore v-if="teamList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</scroll-view>
</view>
</view>
</template>
<script>
import shCollapseItem from '../components/sh-collapse-item.vue';
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {
shCollapseItem
},
data() {
return {
isEmpty: false,
referrerInfo: {}, //推荐人信息
twoTeamCount: 0, //二级成员
teamList: [], //团队列表
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1,
// 二级
childrenTeamList: [],
childrenCurrentPage: 1,
childrenLastPage: 1,
childrenLoad: false,
isRefresh: false
};
},
computed: {
...mapGetters(['userInfo', 'agentInfo'])
},
onLoad() {
this.getTeam();
this.getAgent();
},
methods: {
...mapActions(['getAgent']),
// 点击队员项
onTeamList(id, current) {
this.childrenTeamList = [];
this.childrenCurrentPage = 1;
this.childrenLastPage = 1;
this.childrenLoad = false;
this.isRefresh = false;
!this.teamList[current].isUnfold && this.teamList[current].child_user_count_1 && this.getChildrenTeam(id);
this.teamList.map((item, index) => {
if (index === current) {
item.isUnfold ? (item.isUnfold = false) : (item.isUnfold = true);
} else {
item.isUnfold = false;
}
});
},
// 团队列表
getTeam() {
let that = this;
that.loadStatus = 'loadmore';
that.$http('commission.team', {
page: that.currentPage
}).then(res => {
if (res.code === 1) {
that.referrerInfo = res.data.parent_user;
let arr = res.data.teams.data;
arr.map(item => {
item.isUnfold = false;
});
that.teamList = [...that.teamList, ...arr];
that.isEmpty = !that.teamList.length;
that.lastPage = res.data.teams.last_page;
that.loadStatus = that.currentPage < res.data.teams.last_page ? 'loadmore' : 'nomore';
}
});
},
// 二级队员
getChildrenTeam(id) {
let that = this;
that.$http(
'commission.team',
{
id: id,
page: that.childrenCurrentPage
},
'加载中...'
).then(res => {
if (res.code === 1) {
that.childrenTeamList = [...that.childrenTeamList, ...res.data.teams.data];
that.childrenLastPage = res.data.teams.last_page;
that.childrenLoad = that.childrenCurrentPage < res.data.teams.last_page;
}
});
},
// 二级加载更多
childrenLoadMore(id) {
if (!this.isRefresh) {
// 加载更多
if (this.childrenCurrentPage < this.childrenLastPage) {
this.isRefresh = true;
this.childrenCurrentPage += 1;
this.getChildrenTeam(id);
}
}
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getTeam();
}
}
}
};
</script>
<style lang="scss">
// 推荐人
.referrer-box {
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
margin-top: 10rpx;
.referrer-avatar {
width: 34rpx;
height: 34rpx;
border-radius: 50%;
}
}
// 二级加载更多按钮
.refresh-btn {
width: 100%;
line-height: 100rpx;
background: #ffffff;
border-radius: 25rpx;
font-size: 22rpx;
font-weight: 500;
color: #999999;
white-space: nowrap;
}
.refresh-active {
transform: rotate(360deg);
transition: all linear 0.5s;
}
// 头部卡片
.head_box {
background: url($IMG_URL+'/imgs/commission/card_bg.png') no-repeat;
background-size: 100% 100%;
padding-bottom: 30rpx;
}
// 团队信息总览
.team-data-box {
margin: 30rpx 20rpx 0;
.data-card {
width: 340rpx;
background: #ffffff;
border-radius: 20rpx;
padding: 20rpx;
.item-title {
font-size: 22rpx;
font-weight: 500;
color: #999999;
line-height: 30rpx;
margin-bottom: 10rpx;
}
.total-item {
margin-bottom: 20rpx;
}
.total-num {
font-size: 38rpx;
font-weight: 500;
color: #333333;
}
.category-num {
font-size: 26rpx;
font-weight: 500;
color: #333333;
}
}
}
// 团队列表
.team-box {
.team-list {
.team-children {
margin-left: 80rpx;
margin-right: 20rpx;
height: 132rpx;
border-bottom: 1rpx solid #eee;
.head-img {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-right: 38rpx;
}
.head-info {
.head-time {
font-size: 22rpx;
font-weight: 400;
color: #999999;
}
.name-box {
margin-bottom: 12rpx;
.name-text {
font-size: 24rpx;
font-weight: 500;
color: #666;
}
.tag-box {
background: rgba(0, 0, 0, 0.2);
border-radius: 21rpx;
line-height: 30rpx;
padding-right: 10rpx;
margin-left: 10rpx;
.tag-img {
width: 34rpx;
height: 34rpx;
margin-right: 6rpx;
border-radius: 50%;
}
.tag-title {
font-size: 18rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
line-height: 20rpx;
}
}
}
}
}
}
}
</style>

@ -0,0 +1,151 @@
<template>
<view class="sh-collapse-box">
<view class="collapse-head u-flex u-row-between">
<view class="u-flex" @tap="onArrow">
<image class="head-img" :src="avatar" mode=""></image>
<view class="head-info">
<view class="name-box u-flex">
<view class="name-text">{{ name }}</view>
<view class="grade-tag tag-box u-flex" v-if="level">
<image v-show="level.image" class="tag-img" :src="level.image" mode=""></image>
<text class="tag-title">{{ level.name }}</text>
</view>
</view>
<view class="u-flex">
<view class="head-time">{{ $u.timeFormat(dateTime, 'yyyy年mm月dd日') }}</view>
<view class="child-num u-m-l-30">下级成员:{{ childNum }}人</view>
</view>
</view>
</view>
<button v-if="childNum" class="u-reset-button arrow-btn" :class="{ 'arrow-active': showUnfold }" @tap="onArrow">
<view class="u-iconfont uicon-arrow-down u-m-l-20" style="color: #999;font-size: 26rpx;"></view>
</button>
</view>
<slot v-if="showUnfold" name="collapse-children"></slot>
</view>
</template>
<script>
/**
* 用户列表项
* @property {String} avatar - 头像
* @property {String} name - 昵称
* @property {String} level - 等级
* @property {String | Date} dateTime - 日期
* @event {Function} click - 点击箭头
*/
export default {
name: 'sh-collapse-item',
components: {},
data() {
return {};
},
props: {
avatar: {
type: String,
default: ''
},
name: {
type: String,
default: ''
},
level: {
type: Object,
default: null
},
dateTime: {
type: Number,
default: 0
},
isUnfold: {
type: Boolean,
default: false
},
childNum: {
type: Number,
default: 0
}
},
computed: {
showUnfold: {
get() {
return this.isUnfold;
},
set(val) {
return val;
}
}
},
methods: {
onArrow() {
this.$emit('click');
}
}
};
</script>
<style lang="scss">
.sh-collapse-box {
background-color: #fff;
.collapse-head {
padding: 0rpx 28rpx;
height: 132rpx;
border-bottom: 1rpx solid #eee;
.head-img {
width: 66rpx;
height: 66rpx;
border-radius: 50%;
margin-right: 45rpx;
}
.arrow-btn {
background: none;
padding: 0;
color: #c4c4c4;
transition: all linear 0.3s;
}
.arrow-active {
transform: rotate(180deg);
transform-origin: center center;
transition: all linear 0.3s;
}
.head-info {
.head-time,
.child-num {
font-size: 24rpx;
font-weight: 400;
color: #999999;
}
.name-box {
margin-bottom: 12rpx;
.name-text {
font-size: 26rpx;
font-weight: 500;
color: #111111;
}
.tag-box {
background: rgba(0, 0, 0, 0.2);
border-radius: 21rpx;
line-height: 30rpx;
padding: 5rpx 10rpx;
margin-left: 10rpx;
.tag-img {
width: 34rpx;
height: 34rpx;
margin-right: 6rpx;
border-radius: 50%;
}
.tag-title {
font-size: 18rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
line-height: 20rpx;
}
}
}
}
}
}
</style>

@ -0,0 +1,247 @@
<!-- 优惠劵详情 -->
<template>
<view class="page_box">
<!-- 标题栏 -->
<shopro-navbar back-icon-color="#fff" :background="{}" :backTextStyle="{ color: '#fff', fontSize: '36rpx', fontWeight: '500' }" backText="优惠券详情"></shopro-navbar>
<view class="content_box">
<scroll-view class="scroll-box" scroll-y="true" scroll-with-animation enable-back-to-top :scroll-into-view="scrollId" @scroll="onScroll">
<!-- 详情卡片 -->
<view class="coupon-box">
<view class="top u-flex-col u-col-center">
<view class="img-box u-flex u-row-center u-col-center"><image class="coupon-img" :src="$IMG_URL + '/imgs/coupon.png'" mode=""></image></view>
<view class="title">{{ couponDetail.amount || '0.00' }}元优惠券</view>
<view class="tip">满{{ couponDetail.enough || '0.00' }}元可用</view>
<button class="u-reset-button" :class="['can_use', 'can_get'].includes(btnStataus) || !btnStataus ? 'use-btn' : 'fail-btn'" @tap="goScroll">
{{ btnStatusText[btnStataus] || '立即领取' }}
</button>
<view class="time" v-if="couponDetail.usetime && couponDetail.usetime.start">
有效期:{{ $u.timeFormat(couponDetail.usetime.start, 'yyyy-mm-dd') }} 至 {{ $u.timeFormat(couponDetail.usetime.end, 'yyyy-mm-dd') }}
</view>
<view class="coupon-line"></view>
</view>
<view class="bottom">
<view class="notice-item">
<view class="notice-title">优惠券说明</view>
<view class="notice-detail">{{ couponDetail.description || '暂无说明~' }}</view>
</view>
</view>
</view>
<!-- 适用商品 -->
<view class="coupon-goods u-p-30" v-if="couponGoods.length">
<view class="coupon-goods-title" id="couponGoods">适用商品</view>
<view class="goods-list u-m-b-20 u-p-20" v-for="goods in couponGoods" :key="goods.id">
<shopro-mini-card
:image="goods.image"
:title="goods.title"
:subtitle="goods.subtitle"
:price="goods.price"
:originPrice="goods.original_price"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: goods.id } })"
></shopro-mini-card>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
couponDetail: {},
couponGoods: [],
scrollId: '',
nowTime: new Date().getTime(),
options: {},
btnStatusText: {
can_use: '立即使用',
used: '已使用',
expired: '已失效',
cannot_use: '暂不可用',
can_get: '立即领取',
cannot_get: '不可领取'
},
btnStataus: ''
};
},
computed: {},
onLoad() {
this.options = this.$Route.query;
this.getCouponDetail();
this.getCouponGoods();
},
methods: {
// 领取优惠劵
getCoupon() {
let that = this;
that.$http(
'coupons.get',
{
id: that.$Route.query.id
},
'领取中...'
).then(res => {
if (res.code === 1) {
this.options.userCouponId = res.data.id;
that.getCouponDetail();
that.$u.toast('领取成功')
}
});
},
// 优惠券详情
getCouponDetail() {
let that = this;
that.$http('coupons.detail', {
id: that.$Route.query.id,
user_coupons_id: that.options.userCouponId
}).then(res => {
if (res.code === 1) {
that.couponDetail = res.data;
if (res.data.status_code) {
this.btnStataus = res.data.status_code;
}
}
});
},
// 适用商品
getCouponGoods() {
let that = this;
that.$http('coupons.goods', {
id: that.$Route.query.id
}).then(res => {
if (res.code === 1) {
that.couponGoods = res.data.data;
}
});
},
onScroll() {
this.scrollId = '';
},
goScroll() {
if (!this.options.userCouponId) {
this.getCoupon();
} else {
if (this.couponDetail.goods_ids === '0' && this.btnStataus == 'can_use') {
this.$Router.push({
path: '/pages/goods/list'
});
}
this.scrollId = 'couponGoods';
}
}
}
};
</script>
<style lang="scss">
.page_box {
background: linear-gradient(180deg, rgba(240, 199, 133, 1), rgba(246, 214, 157, 1));
}
.coupon-box {
margin: 100rpx 30rpx 0;
border-radius: 20rpx;
background-color: #fff;
.top {
border-radius: 20rpx 20rpx 0 0;
background-image: radial-gradient(circle at bottom left, #f6d69d, #f6d69d 16rpx, transparent 17rpx),
radial-gradient(circle at bottom right, #f6d69d, #f6d69d 16rpx, transparent 17rpx);
padding: 110rpx 0 40rpx 0;
position: relative;
z-index: 5;
.coupon-line {
width: 95%;
border-bottom: 2rpx dashed #f3ce90;
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
z-index: 6;
}
.img-box {
width: 140rpx;
height: 140rpx;
border-radius: 50%;
background: #fff;
position: absolute;
left: 50%;
transform: translateX(-50%);
top: -70rpx;
.coupon-img {
width: 100rpx;
height: 100rpx;
}
}
.title {
font-size: 40rpx;
font-weight: bold;
line-height: 40rpx;
margin-bottom: 30rpx;
}
.tip {
font-size: 28rpx;
font-weight: 500;
margin-bottom: 50rpx;
}
.use-btn {
width: 386rpx;
line-height: 80rpx;
background: linear-gradient(90deg, rgba(240, 199, 133, 1), rgba(246, 214, 157, 1));
border-radius: 40rpx;
color: rgba(#fff, 0.9);
margin-bottom: 30rpx;
}
.fail-btn {
width: 386rpx;
line-height: 80rpx;
background: rgba(245, 245, 245, 1);
border-radius: 40rpx;
font-size: 30rpx;
font-weight: 500;
color: rgba(188, 188, 188, 1);
margin-bottom: 30rpx;
}
.time {
color: #999;
font-size: 26rpx;
}
}
.bottom {
border-radius: 0 0 20rpx 20rpx;
background-image: radial-gradient(circle at top left, #f6d69d, #f6d69d 16rpx, transparent 17rpx),
radial-gradient(circle at top right, #f6d69d, #f6d69d 16rpx, transparent 17rpx);
padding: 40rpx 30rpx;
.notice-item {
border-bottom: 1rpx solid #eeeeee;
min-height: 90rpx;
width: 100%;
.notice-title {
font-weight: 500;
font-size: 26rpx;
}
.notice-detail {
font-size: 24rpx;
color: #666;
margin-top: 10rpx;
padding-bottom: 10rpx;
text-indent: 30rpx;
}
}
}
}
// 优惠券商品
.coupon-goods {
.coupon-goods-title {
font-size: 30rpx;
font-weight: bold;
height: 80rpx;
}
.goods-list {
background: #fff;
border-radius: 20rpx;
}
}
</style>

@ -0,0 +1,176 @@
<!-- 优惠劵列表 -->
<template>
<view class="page_box">
<view class="head_box u-m-b-20">
<shopro-navbar back-icon-color="#333" :background="{ backgroundColor: '#fff' }">
<view slot="content" class="u-flex nav-wrap">
<view class="nav-item u-flex-1 u-flex-col u-row-center u-col-center" v-for="nav in navbarHeadState" :key="nav.value" @tap="onHead(nav.value)">
<view class="item-title" :class="{ 'title-active': navState === nav.value }">{{ nav.title }}</view>
<text class="nav-line" :class="{ 'line-active': navState === nav.value }"></text>
</view>
</view>
</shopro-navbar>
<view class="coupon-nav u-flex" v-show="navState === 'user'">
<button
class=" cu-btn nav-btn u-flex-col u-row-center u-col-center"
:class="{ 'btn-active': nav.id == stateCurrent }"
v-for="nav in couponsState"
:key="nav.id"
@tap="onNav(nav.id)"
>
<view class="item-title">{{ nav.title }}</view>
</button>
</view>
</view>
<view class="content_box">
<view class="coupon-list" v-for="(c, index) in couponList" :key="index" @tap="toCouponDetail(c)">
<shopro-coupon :state="stateCurrent" :couponData="c"></shopro-coupon>
</view>
<!-- 缺省页 -->
<shopro-empty marginTop="300rpx" v-if="isEmpty" :image="$IMG_URL + '/imgs/empty/empty_goods.png'" tipText="暂无此类优惠券"></shopro-empty>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
stateCurrent: 0,
isEmpty: false,
navState: 'event',
navbarHeadState: [
{
value: 'event',
title: '领券中心'
},
{
value: 'user',
title: '我的优惠券'
}
],
couponsState: [
{
id: 1,
title: '可使用'
},
{
id: 2,
title: '暂不可用'
},
{
id: 3,
title: '无效优惠券'
}
],
couponList: []
};
},
computed: {},
onLoad() {
this.getCouponList();
},
methods: {
onNav(id) {
if (this.stateCurrent !== id) {
this.stateCurrent = id;
this.couponList = [];
this.getCouponList();
}
},
onHead(value) {
if (this.navState !== value) {
this.navState = value;
if (value === 'event') {
this.stateCurrent = 0;
}
if (value === 'user') {
this.stateCurrent = 1;
}
this.couponList = [];
this.getCouponList();
}
},
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
getCouponList() {
let that = this;
that.$http('coupons.list', {
type: that.stateCurrent
}).then(res => {
if (res.code === 1) {
that.couponList = res.data;
that.isEmpty = !that.couponList.length;
}
});
},
//跳转优惠券详情
toCouponDetail(data) {
if (data.user_coupons_id) {
this.jump('/pages/app/coupon/detail', { id: data.id, userCouponId: data.user_coupons_id });
} else {
this.jump('/pages/app/coupon/detail', { id: data.id });
}
}
}
};
</script>
<style lang="scss">
.nav-item {
flex: 1;
height: 100%;
.item-title {
font-size: 30rpx;
font-weight: 400;
color: #666;
}
.title-active {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
.nav-line {
width: 128rpx;
height: 6rpx;
border-radius: 3rpx;
background: #fff;
}
.line-active {
background: #e9b664;
}
}
.nav-wrap {
/* #ifdef MP-WEIXIN */
width: 460rpx;
/* #endif */
/* #ifndef MP-WEIXIN */
width: 100%;
/* #endif */
}
.coupon-nav {
background: #fff;
height: 100rpx;
padding: 0 30rpx;
.nav-btn {
margin-right: 10rpx;
font-size: 26rpx;
color: #666;
}
.btn-active {
font-size: 26rpx;
color: #fff;
background-color: #faae0c;
}
}
.coupon-list {
margin: 30rpx 20rpx;
}
</style>

@ -0,0 +1,216 @@
<!-- 直播列表 -->
<template>
<view class="page_box">
<view class="head_box">
<view class="live-tab">
<view class="live-tab__item" v-for="tab in liveTab" :key="tab.title" @tap="selTab(tab.title)">
<view class="live-tab__item-name">{{ tab.name }}</view>
<text class="live-tab__item--link" :class="{ 'live-tab__item--active': tabCur === tab.title }"></text>
</view>
</view>
</view>
<view class="content_box">
<scroll-view enable-back-to-top scroll-y="true" @scrolltolower="loadMore" class="scroll-box">
<view class="u-waterfall" v-if="!isEmpty">
<view id="u-left-column" class="u-column">
<view class="u-flex u-row-center u-col-center u-m-b-20" v-for="live in leftList" :key="live.id">
<shopro-live-card :type="2" :detail="live"></shopro-live-card>
</view>
</view>
<view id="u-left-column" class="u-column">
<view class="u-flex u-row-center u-col-center u-m-b-20" v-for="live in rightList" :key="live.id">
<shopro-live-card :type="2" :detail="live"></shopro-live-card>
</view>
</view>
</view>
<!-- 更多 -->
<u-loadmore v-if="!isEmpty" height="80rpx" :status="loadStatus" color="#ccc" />
<!-- 置空页 -->
<u-empty :show="isEmpty" mode="list"></u-empty>
</scroll-view>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
tabCur: 'all',
isEmpty: false,
liveTab: [
{
title: 'all',
name: '全部'
},
{
title: 'notice',
name: '预告'
},
{
title: 'living',
name: '直播中'
},
{
title: 'lived',
name: '已结束'
}
],
liveList: [],
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1,
// 瀑布流 300-220
addTime: 100, //排序间隙时间
leftHeight: 0,
rightHeight: 0,
leftList: [],
rightList: [],
tempList: []
};
},
computed: {},
onLoad() {
this.getLiveList();
},
onHide() {},
methods: {
// 瀑布流相关
async splitData() {
if (!this.tempList.length) return;
let item = this.tempList[0];
if (!item) return;
// 分左右
if (this.leftHeight < this.rightHeight) {
this.leftHeight += item.goods.length ? 300 : 220;
this.leftList.push(item);
} else if (this.leftHeight > this.rightHeight) {
this.rightHeight += item.goods.length ? 300 : 220;
this.rightList.push(item);
} else {
this.leftHeight += item.goods.length ? 300 : 220;
this.leftList.push(item);
}
// 移除临时列表的第一项,如果临时数组还有数据,继续循环
this.tempList.splice(0, 1);
if (this.tempList.length) {
setTimeout(() => {
this.splitData();
}, this.addTime);
}
},
clear() {
this.leftList = [];
this.rightList = [];
this.leftHeight = 0;
this.rightHeight = 0;
this.tempList = [];
},
// 切换tab
selTab(cur) {
if (this.tabCur !== cur) {
this.tabCur = cur;
this.liveList = [];
this.currentPage = 1;
this.lastPage = 1;
this.clear();
this.getLiveList();
}
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getLiveList();
}
},
// 直播列表
getLiveList() {
let that = this;
that.loadStatus = 'loading';
that.$http('common.live', {
type: that.tabCur,
page: that.currentPage
}).then(res => {
if (res.code === 1) {
that.liveList = [...that.liveList, ...res.data.data];
that.isEmpty = !that.liveList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
that.tempList = res.data.data;
that.splitData();
}
});
}
}
};
</script>
<style lang="scss">
@mixin vue-flex($direction: row) {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: $direction;
/* #endif */
}
.u-waterfall {
@include vue-flex;
flex-direction: row;
align-items: flex-start;
}
.u-column {
@include vue-flex;
flex: 1;
flex-direction: column;
height: auto;
}
// tab
.live-tab {
width: 100%;
height: 96rpx;
background: #fff;
display: flex;
&__item {
flex: 1;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
}
&__item-name {
font-size: 28rpx;
font-weight: bold;
color: rgba(51, 51, 51, 1);
flex: 1;
display: flex;
justify-content: center;
align-items: center;
}
&__item--link {
width: 68rpx;
height: 4rpx;
background: transparent;
border-radius: 2rpx;
}
&__item--active {
width: 68rpx;
height: 4rpx;
background: rgba(213, 166, 90, 1);
border-radius: 2rpx;
}
}
// 瀑布流 list
.scroll-box {
margin: 20rpx auto;
width: 730rpx;
}
</style>

@ -0,0 +1,598 @@
<!-- 门店入驻 -->
<template>
<view class="apply-commission-wrap page_box">
<!-- 标题栏 -->
<view class="head-box">
<shopro-navbar back-icon-color="#fff" :background="{}"></shopro-navbar>
</view>
<!-- 表单 -->
<view class="apply-form content_box">
<u-form :model="model" :rules="rules" ref="uForm" :errorType="errorType">
<u-form-item :labelStyle="labelStyle"   label-width="150" label-position="left" label="姓名" prop="realname">
<u-input placeholder="请输入姓名" :placeholderStyle="placeholderStyle" v-model="model.realname" type="text"></u-input>
</u-form-item>
<u-form-item :labelStyle="labelStyle" label-position="left" label="手机号" prop="phone" label-width="150">
<u-input placeholder="请输入手机号" :placeholderStyle="placeholderStyle" v-model="model.phone" type="number"></u-input>
</u-form-item>
<u-form-item :labelStyle="labelStyle" label-width="150" label-position="left" label="申请名称" prop="name">
<u-input placeholder="请输入门店名称" :placeholderStyle="placeholderStyle" v-model="model.name" type="text"></u-input>
</u-form-item>
<u-form-item :labelStyle="labelStyle" prop="images" label="展示图片" label-position="top" label-width="150" :borderBottom="true">
<u-upload :placeholderStyle="placeholderStyle" :showProgress="false" @on-uploaded="uploadSuccess($event, 'storeImg')" @on-remove="uploadRemove($event, 'storeImg')" :action="`${$BASE_URL}/api/common/upload`" :header="{token: token}" :file-list="model.fileImages" width="148" height="148" maxCount="1"></u-upload>
</u-form-item>
<u-form-item :labelStyle="labelStyle"   label-width="150" label-position="left" label="营业时间" prop="openhours">
<u-input type="select" :select-open="selectShow" v-model="model.openhours" placeholder="请选择营业时间" :placeholderStyle="placeholderStyle" @click="onSelect('time')"></u-input>
</u-form-item>
<u-form-item :labelStyle="labelStyle" label-width="150" label-position="left" label="营业天数" prop="weeks">
<u-input type="select" placeholder="请选择营业天数" disabled :placeholderStyle="placeholderStyle" v-model="model.weeks" @click="onSelect('week')"></u-input>
</u-form-item>
<u-form-item :labelStyle="labelStyle" right-icon="map-fill" :right-icon-style="{color:'#4CB89D'}" label-width="150" label-position="left" label="所在地区" prop="area">
<u-input type="text" disabled v-model="model.area" placeholder="请点击定位" @click="chooseLocation"></u-input>
</u-form-item>
<u-form-item :border-bottom="false" :labelStyle="labelStyle" label-width="150" label-position="left" label="详细地址" prop="address">
<u-input type="textarea" border placeholder="请输入详细地址~" :placeholderStyle="placeholderStyle" v-model="model.address"></u-input>
</u-form-item>
<view class="agreement u-flex u-col-center">
<u-checkbox v-model="model.agreement" activeColor="#4CB89D" shape="circle" @change="onAgreement"></u-checkbox>
<view class="agreement-text" @tap="toProtocol">勾选代表同意《申请协议》</view>
</view>
<view class="u-flex u-row-center u-col-center"><button class="u-reset-button save-btn" @tap="onSubmit" :disabled="isFormEnd">确认提交</button></view>
</u-form>
</view>
<!-- 弹窗 -->
<u-select v-if="selectShow" :mode="selectMode" :list="selectList" v-model="selectShow" @confirm="selectConfirm"></u-select>
<!-- 选择星期 -->
<u-popup v-model="showWeeksModal" safe-area-inset-bottom mode="bottom">
<view class="week-modal">
<view class="u-flex u-row-between u-p-x-30 week-modal--head ">
<view></view>
<view class="action text-green" @tap="saveWeekModal">确定</view>
</view>
<view class="u-flex u-flex-wrap u-p-x-30 u-p-y-30 week-modal--content">
<view v-for="(item, index) in weekcheckbox" class="week-btn" :key="index">
<button @tap="onSelectWeek(index)" class="cu-btn" :class="!item.checked ? 'line-green' : 'bg-green'">{{ item.name }}</button>
</view>
</view>
</view>
</u-popup>
<!-- 权限验证 -->
<u-popup v-model="showNotice" mode="center" border-radius="20">
<view class="notice-modal">
<view class="img-wrap">
<image class="notice-img" :src="authNotice.img" mode=""></image>
</view>
<view class="notice-title">{{ authNotice.title }}</view>
<view class="notice-detail">{{ authNotice.detail || '' }}</view>
<button class="u-reset-button notice-btn" @tap="onAuthBtn(authNotice.btnPath)">{{ authNotice.btnText }}</button>
<button class="u-reset-button back-btn" @tap="$Router.back()">返回</button>
</view>
</u-popup>
</view>
</template>
<script>
import Auth from '@/shopro/permission/index.js';
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
import { MAP_KEY } from '@/env.js';
export default {
components: {},
data() {
return {
showNotice: false,
showWeeksModal: false, //星期弹窗
isFormEnd: false,
weekcheckbox: [{
value: '1',
name: '周一',
checked: false
},
{
value: '2',
name: '周二',
checked: false
},
{
value: '3',
name: '周三',
checked: false
},
{
value: '4',
name: '周四',
checked: false
},
{
value: '5',
name: '周五',
checked: false
},
{
value: '6',
name: '周六',
checked: false
},
{
value: '7',
name: '周日',
checked: false
}
],
authNotice: {},
// 表单
errorType: ['message'],
selectShow: false,
selectMode: 'mutil-column', // single-column, mutil-column, mutil-column-auto
selectType: '',
selectList: [],
labelStyle: {
'font-size': '28rpx',
'font-weight': '500',
color: '#333'
},
placeholderStyle: 'font-size: 28rpx;color:#c4c4c4;',
model: {
name: '', //门店名称
phone: '', //手机号
realname: '', //真实姓名
openhours: '', //营业时间
openweeks: '', //营业周期
weeks: '',
area: '',
area_id: '', //行政区域ID
address: '', //门店地址
latitude: '', //纬度
longitude: '', //京都
images: [], //门店图片
images_original: [],
fileImages: [],
agreement: false
},
rules: {
name: [{
required: true,
message: '请输入门店名称',
trigger: ['change', 'blur']
}],
realname: [{
required: true,
message: '请输入真实姓名',
trigger: ['change', 'blur']
}],
area: [{
required: true,
message: '请定位所在地区',
trigger: ['change', 'blur']
}],
phone: [{
required: true,
message: '请输入手机号',
trigger: ['change', 'blur']
},
{
validator: (rule, value, callback) => {
return this.$u.test.mobile(value);
},
message: '手机号码不正确',
// 触发器可以同时用blur和change,二者之间用英文逗号隔开
trigger: ['change', 'blur']
}
],
openhours: [{
required: true,
message: '请选择营业时间',
trigger: ['change', 'blur']
}],
weeks: [{
required: true,
message: '请选择营业天数',
trigger: ['change', 'blur']
}],
address: [{
required: true,
message: '请输入详细地址',
trigger: ['change', 'blur']
}]
},
token: uni.getStorageSync('token')
};
},
computed: {
...mapGetters(['initStore']),
selectWorkerTime() {
let mArr = [];
for (let i = 0; i <= 24; i++) {
let t = String(i).padStart(2, '0');
mArr.push({
value: `${t}:00`,
label: `${t}:00`
}, {
value: `${t}:30`,
label: `${t}:30`
});
}
mArr.pop();
return [mArr, mArr];
}
},
onLoad() {
this.getStoreInfo();
},
onReady() {
this.$refs.uForm.setRules(this.rules);
},
methods: {
// 选择星期
saveWeekModal() {
this.showWeeksModal = false;
let arr = [];
let arr2 = [];
this.weekcheckbox.forEach(item => {
if (item.checked) {
arr.push(item.value);
arr2.push(item.name);
}
});
this.model.openweeks = arr.join(',');
this.model.weeks = arr2.join(',');
},
// 选择星期
onSelectWeek(index) {
this.weekcheckbox[index].checked = !this.weekcheckbox[index].checked;
},
// 弹窗按钮
onAuthBtn(path) {
path &&
this.$Router.push({
path: path
});
this.showNotice = false;
},
// 上传图片成功
uploadSuccess(e, type) {
switch (type) {
case 'storeImg':
this.model.images = [];
e.forEach(item => {
this.model.images.push(item.response.data.url);
});
break;
default:
return;
}
},
// 移除图片
uploadRemove(index, type) {
switch (type) {
case 'storeImg':
this.model.images.splice(index, 1);
break;
default:
return;
}
},
// 地址选择
async chooseLocation() {
let authState = await new Auth('userLocation').check();
authState &&
uni.chooseLocation({
success: res => {
this.model.latitude = res.latitude;
this.model.longitude = res.longitude;
this.getLocationInfo();
},
fail: err => {
console.log(err);
}
});
},
//逆坐标解析
async getLocationInfo() {
this.chooseAddress = '定位中...';
const [error, res] = await uni.request({
url: `https://restapi.amap.com/v3/geocode/regeo?location=${this.model.longitude},${this.model.latitude}&key=${MAP_KEY}`
});
if (res.data.status === '1') {
const addressComponent = res.data.regeocode.addressComponent;
this.model.area_id = addressComponent.adcode;
this.model.area = `${addressComponent.province}-${addressComponent.city.length ? addressComponent.city : addressComponent.province}-${addressComponent.district.length ?addressComponent.district:addressComponent.township }`;
this.model.address = res.data.regeocode.formatted_address.replace(`${addressComponent.province}${addressComponent.city}${addressComponent.district}`, '');
} else {
console.log('%c逆地址解析失败,请检查是否在env中配置地图key', 'color:green;background:yellow');
}
},
onSelect(type) {
this.selectType = type;
switch (type) {
case 'time':
this.selectShow = true;
this.selectMode = 'mutil-column';
this.selectList = this.selectWorkerTime;
break;
case 'week':
this.showWeeksModal = true;
break;
default:
return;
}
},
// 选择时间
selectConfirm(e) {
switch (this.selectType) {
case 'time':
this.model.openhours = '';
e.map((val, index) => {
this.model.openhours += this.model.openhours == '' ? val.label : ' - ' + val.label;
});
break;
default:
return;
}
},
// 勾选同意
onAgreement(e) {
this.model.agreement = e.value;
},
// 门店详情
getStoreInfo() {
let that = this;
that.$http('store.shopInfo').then(res => {
if (res.code === 1) {
res.data && this.authStatus(res.data);
}
});
},
// 跳转门店协议
toProtocol() {
this.initStore.protocol && this.jump('/pages/public/richtext', { id: this.initStore.protocol })
},
// 初始化model
initModel() {
// 构建星期
let weeksArr = this.model.openweeks.split(',');
let weekTextArr = [];
this.weekcheckbox.forEach(item => {
if (weeksArr.includes(item.value)) {
weekTextArr.push(item.name);
item.checked = true;
}
});
this.model.weeks = weekTextArr.join(',');
// 构建省市区
if (this.model.province_name) {
this.model.area = `${this.model.province_name}-${this.model.city_name}-${this.model.area_name}`;
}
if (this.model.images) {
this.model.images.forEach(item => {
this.model.fileImages.push({
url: item
});
});
}
// 协议
this.$set(this.model, 'agreement', false);
},
// 状态鉴权
authStatus(data) {
switch (String(data.status)) {
case '0':
this.showNotice = true;
this.model = { ...this.model, ...data };
this.initModel();
this.authNotice = {
img: this.$IMG_URL + '/imgs/commission/auth_check.png',
title: '正在审核中',
detail: data.status_msg,
btnText: '修改信息',
btnPath: ''
};
break;
case '-1':
this.showNotice = true;
this.model = { ...this.model, ...data };
this.initModel();
this.authNotice = {
img: this.$IMG_URL + '/imgs/commission/auth_reject.png',
title: '您的申请已被驳回!',
detail: data.status_msg,
btnText: '重新申请',
btnPath: ''
};
break;
case '1':
this.showNotice = false;
break;
default:
}
},
// 申请门店
applyStore() {
let that = this;
this.isFormEnd = false;
that.$http('store.apply', that.model, '提交中...').then(res => {
this.isFormEnd = true;
if (res.code === 1) {
// #ifdef MP-WEIXIN
this.$store.commit('subscribeMessage', 'storeApply');
// #endif
uni.showToast({
title: res.msg,
success: () => {
that.$Router.back();
}
});
}
});
},
// 提交
onSubmit() {
let that = this;
if (!that.model.images.length) {
this.$u.toast('请上传门店图片');
return;
}
this.$refs.uForm.validate(valid => {
if (valid) {
if (!this.model.agreement) return this.$u.toast('请勾选协议');
this.applyStore();
} else {
this.$u.toast('请完善表单');
}
});
},
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
}
}
};
</script>
<style lang="scss">
.apply-commission-wrap {
background-color: #fff;
.head-box {
background: url($IMG_URL+'/imgs/user/sh_leader_apply_head.png') no-repeat;
background-size: 100% auto;
height: 370rpx;
}
}
// 表单
.apply-form {
width: 750rpx;
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
.agreement {
margin-top: 20rpx;
.agreement-text {
font-size: 24rpx;
font-weight: 500;
color: #4cb89d;
}
}
.save-btn {
width: 690rpx;
line-height: 86rpx;
background: linear-gradient(90deg, #2dae9c, #6bc29e);
box-shadow: 0px 7rpx 11rpx 2rpx rgba(61, 179, 156, 0.34);
border-radius: 43rpx;
font-weight: 500;
color: #ffffff;
margin: 30rpx 0;
}
}
// 星期
.week-modal {
.week-modal--head {
height: 100rpx;
border-bottom: 1rpx solid $u-border-color;
.text-cancel {
color: #ccc;
}
.text-green {
color: #18b566;
}
}
.week-modal--content {
.week-btn {
margin-right: 56rpx;
margin-bottom: 30rpx;
&:nth-of-type(4n) {
margin-right: 0;
}
}
}
}
// 提示
.notice-modal {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 612rpx;
min-height: 658rpx;
background: #ffffff;
padding: 30rpx;
border-radius: 20rpx;
.img-wrap {
margin-bottom: 50rpx;
.notice-img {
width: 180rpx;
height: 170rpx;
}
}
.notice-title {
font-size: 35rpx;
font-weight: bold;
color: #46351b;
margin-bottom: 28rpx;
}
.notice-detail {
font-size: 28rpx;
font-weight: 400;
color: #999999;
line-height: 36rpx;
margin-bottom: 50rpx;
}
.notice-btn {
width: 492rpx;
height: 70rpx;
line-height: 70rpx;
background: linear-gradient(90deg, #2dae9c, #6bc29e);
box-shadow: 0 7rpx 11rpx 2rpx rgba(61, 179, 156, 0.34);
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: #ffffff;
margin-bottom: 10rpx;
}
.back-btn {
width: 492rpx;
height: 70rpx;
line-height: 70rpx;
font-size: 28rpx;
font-weight: 500;
color: #3ab29c;
background: none;
}
}
</style>

@ -0,0 +1,227 @@
<!-- 门店订单详情 -->
<template>
<view class="order-detail-wrap">
<!-- 订单卡片 -->
<view class="card-box">
<view class="order-goods-item u-m-b-10" v-for="item in orderDetail.item" :key="item.id">
<shopro-mini-card :title="item.goods_title" :image="item.goods_image">
<template #describe>
<view class="order-sku u-ellipsis-1">
<text class="order-num">数量:{{ item.goods_num || 0 }};</text>
{{ item.goods_sku_text ? item.goods_sku_text : '' }}
</view>
</template>
<template #cardBottom>
<view class="card-price-box u-flex">
<text class="order-price font-OPPOSANS">¥{{ item.goods_price || 0 }}</text>
<button class="u-reset-button status-btn" v-if="item.status_name">{{ item.status_name }}</button>
</view>
</template>
</shopro-mini-card>
<view class="goods-phone card-item" v-show="item.ext_arr.dispatch_phone">
<text class="item-title">预留电话:</text>
<text class="item-content">{{ item.ext_arr.dispatch_phone }}</text>
</view>
<view class="goods-date card-item">
<text class="item-title" v-show="item.dispatch_type === 'selfetch'">到店/自提时间:</text>
<text class="item-title" v-show="item.dispatch_type === 'store'">配送时间:</text>
<text class="item-content">{{ item.ext_arr.dispatch_date || '' }}</text>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="order-detail-card">
<view class="card-title u-flex ">订单信息</view>
<view class="detial-content">
<view class="detail-item u-flex">
<view class="item-title">订单状态:</view>
<view class="item-content">{{ orderDetail.status_name }}</view>
</view>
<view class="detail-item u-flex">
<view class="item-title">订单编号:</view>
<view class="item-content">{{ orderDetail.order_sn }}</view>
</view>
<view class="detail-item u-flex">
<view class="item-title">下单时间:</view>
<view class="item-content">{{ orderDetail.paytime }}</view>
</view>
<view class="detail-item u-flex" v-if="orderDetail.remark">
<view class="item-title">备注:</view>
<view class="item-content">{{ orderDetail.remark }}</view>
</view>
<view class="detail-item address-item" v-if="orderType.includes('store')">
<view class="item-title">配送地址:</view>
<view class="item-content address-content">
{{ orderDetail.province_name || '' }}{{ orderDetail.city_name || '' }}{{ orderDetail.area_name || '' }}{{ orderDetail.address || '' }}
</view>
</view>
</view>
</view>
<view class="bottom-hack" v-if="orderDetail.status_code == 'nosend'">
<view class="bottom-box u-flex u-row-center u-col-center"><button class="cu-btn send-btn" @tap="sendOrder">发货</button></view>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
orderDetail: {},
orderType: []
};
},
computed: {},
onLoad() {
this.getOrderDetail();
},
methods: {
// 订单详情
getOrderDetail() {
let that = this;
that.$http('store.orderDetail', {
id: that.$Route.query.orderId,
store_id: uni.getStorageSync('storeId')
}).then(res => {
if (res.code === 1) {
that.orderDetail = res.data;
that.orderDetail.paytime = that.$u.timeFormat(res.data.paytime, 'yyyy-mm-dd hh:MM');
that.orderDetail.item.forEach(goods => {
that.orderType.push(goods.dispatch_type);
});
}
});
},
// 订单发货
sendOrder() {
let that = this;
that.$http(
'store.orderSend',
{
id: that.orderDetail.id,
store_id: uni.getStorageSync('storeId')
},
'发货中...'
).then(res => {
if (res.code === 1) {
that.$u.toast(res.msg);
that.getOrderDetail();
}
});
}
}
};
</script>
<style lang="scss">
.order-detail-wrap {
height: 100%;
.bottom-hack {
width: 750rpx;
height: 100rpx;
}
.bottom-box {
position: fixed;
width: 750rpx;
height: 100rpx;
bottom: 0;
left: 0;
background-color: #fff;
padding: 10rpx 0;
.send-btn {
width: 710rpx;
height: 80rpx;
background: linear-gradient(90deg, #2eae9c, #6cc29f);
border: 1rpx solid rgba(237, 237, 237, 1);
border-radius: 40rpx;
font-size: 30rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
}
}
}
// 订单卡片
.card-box {
margin-bottom: 20rpx;
background-color: #fff;
.order-goods-item {
border-bottom: 1rpx solid rgba(237, 237, 237, 1);
padding: 20rpx;
.card-item {
line-height: 60rpx;
.item-title {
font-size: 28rpx;
color: #999;
}
.item-content {
font-size: 26rpx;
color: #333;
}
}
}
.order-sku {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
width: 450rpx;
margin-bottom: 20rpx;
.order-num {
margin-right: 10rpx;
}
}
.card-price-box {
.status-btn {
height: 32rpx;
border: 1rpx solid rgba(207, 169, 114, 1);
border-radius: 15rpx;
font-size: 20rpx;
font-weight: 400;
color: rgba(168, 112, 13, 1);
padding: 0 10rpx;
margin-left: 20rpx;
background: rgba(233, 183, 102, 0.16);
}
.order-price {
font-size: 26rpx;
font-weight: 600;
color: rgba(51, 51, 51, 1);
}
}
}
.order-detail-card {
background-color: #fff;
margin: 20rpx 0;
.card-title {
font-size: 30rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
height: 80rpx;
padding: 0 20rpx;
border-bottom: 1rpx solid rgba(223, 223, 223, 0.5);
}
.detial-content {
padding: 20rpx;
.detail-item {
min-height: 60rpx;
.item-title {
font-size: 28rpx;
color: #999;
}
.item-content {
font-size: 26rpx;
color: #333;
}
}
.address-item {
display: flex;
.address-content {
width: 550rpx;
}
}
}
}
</style>

@ -0,0 +1,618 @@
<!-- 商家中心 -->
<template>
<view class="merchant-wrap">
<view class="shopinfo-box">
<!-- 标题栏 -->
<shopro-navbar
back-icon-color="#fff"
:background="{ background: `url(${$IMG_URL}/imgs/user/shop_headbg.png) no-repeat top center / 100% auto`}"
:backTextStyle="{ color: '#fff', fontSize: '36rpx', fontWeight: '500' }"
backText="商家中心"
></shopro-navbar>
<!-- 商家信息 -->
<view class="user-head u-flex u-row-between">
<view class="shop-info">
<view class="u-flex u-m-b-10" @tap="goStoreList">
<text class="shop-title u-m-r-20">{{ storeDetail.name }}</text>
<text class="iconfont icon-xiala"></text>
</view>
<view class="shop-address u-ellipsis-2" @tap="jump('/pages/app/merchant/info')">
{{ storeDetail.province_name || '' }}{{ storeDetail.city_name || '' }}{{ storeDetail.area_name || '' }}{{ storeDetail.address || '' }}
</view>
</view>
<button @tap="$Router.pushTab('/pages/index/user')" class="u-reset-button merchant-btn">切换个人版</button>
</view>
</view>
<!-- 核销卡片 -->
<view class="info-card-box u-flex u-row-between">
<view class="info-card">
<image class="card-bg" :src="$IMG_URL + '/imgs/user/shop_info.png'" mode="aspectFill"></image>
<view class="card-content u-flex-col">
<view class="card-title">输码核销</view>
<view class="u-flex card-detail-box" @tap="showInputModal = true">
<text class="iconfont icon-21shuru icon-color1"></text>
<text class="icon-color1 u-m-l-20">输码</text>
</view>
</view>
</view>
<view class="info-card" @tap="scanCode">
<image class="card-bg" :src="$IMG_URL + '/imgs/user/shop_qrcode.png'" mode="aspectFill"></image>
<view class="card-content u-flex-col">
<view class="card-title">扫码核销</view>
<view class="u-flex card-detail-box">
<text class="iconfont icon-icon-test1 icon-color2"></text>
<text class="icon-color2 u-m-l-20">扫码</text>
</view>
</view>
</view>
</view>
<view class="cancel-shop-box">
<!-- 筛选框 -->
<view class="cancel-nav u-flex">
<u-dropdown ref="uDropdown" activeColor="#4cb89d" :borderBottom="false">
<u-dropdown-item v-model="filter1Value" @change="changeFilter1" :title="filter1Label" :options="filterList1"></u-dropdown-item>
<u-dropdown-item v-model="filter2Value" @change="changeFilter2" :title="filter2Label" :options="filterList2"></u-dropdown-item>
</u-dropdown>
</view>
<!-- 销量 -->
<view class="sales-volume-box u-flex u-row-between u-p-30">
<view class="sales-volume u-flex u-row-center u-col-center">订单量(单):{{ orderInfo.total_num || 0 }}</view>
<view class="sales-volume u-flex u-row-center u-col-center">交易额(元):{{ orderInfo.total_money || 0 }}</view>
</view>
</view>
<!-- 订单列表 -->
<view class="order-list" v-for="order in storeOrderList" :key="order.order_sn" @tap="jump('/pages/app/merchant/detail', { orderId: order.id })">
<view class="order-head u-flex u-row-between">
<text class="no">订单编号:{{ order.order_sn }}</text>
<text class="state">{{ order.status_name }}</text>
</view>
<view class="goods-order" v-for="item in order.item" :key="item.id">
<shopro-mini-card :title="item.goods_title" :image="item.goods_image">
<template #describe>
<view class="order-sku u-ellipsis-1">
<text class="order-num">数量:{{ item.goods_num || 0 }};</text>
{{ item.goods_sku_text ? item.goods_sku_text : '' }}
</view>
</template>
<template #cardBottom>
<view class="order-price-box u-flex ">
<text class="order-price font-OPPOSANS">¥{{ item.goods_price || 0 }}</text>
<button class="u-reset-button status-btn" v-if="item.status_name">{{ item.status_name }}</button>
</view>
</template>
</shopro-mini-card>
</view>
<view class="order-bottom u-flex">
<view class="u-flex u-m-r-30" v-if="reduceAdd(order.item, 'discount_fee')">
<text class="total-price-title">优惠:</text>
<text class="total-price">{{ reduceAdd(order.item, 'discount_fee') }}</text>
</view>
<view class="u-flex">
<text class="total-price-title">实付款:</text>
<text class="total-price">{{ reduceAdd(order.item, 'pay_price') }}</text>
</view>
</view>
</view>
<!-- 置空页 -->
<u-empty :show="isEmpty" margin-top="160" mode="list"></u-empty>
<!-- 更多 -->
<u-loadmore v-if="storeOrderList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
<!-- 日期选择 -->
<u-calendar
v-model="showCalendar"
safeAreaInsetBottom
mode="range"
start-text="开始"
end-text="结束"
range-color="#4CB89D"
range-bg-color="rgba(76,184,157,0.13)"
active-bg-color="#4CB89D"
:customStyle="{ background: 'linear-gradient(90deg, #4cb89d, #4CB89D)', boxShadow: '0 7rpx 11rpx 2rpx rgba(4cb89d, 0.34)' }"
btnType="success"
@change="selDate"
></u-calendar>
<!-- 输码弹窗 -->
<u-popup v-model="showInputModal" mode="center" :closeable="true" close-icon-pos="top-left" border-radius="20">
<view class="modal-box u-flex-col u-col-center">
<view class="modal-head u-flex-col u-col-center">
<image class="modal-head-img" :src="$IMG_URL + '/imgs/modal/store_check.png'" mode=""></image>
<text class="modal-head-title">输码核销</text>
</view>
<input class="inp" type="number" v-model="qrcode" placeholder="在此输入核销码" placeholder-class="pl-inp" />
<button class="u-reset-button post-btn" @tap="onConfirm">核销</button>
</view>
</u-popup>
<!-- 禁用弹窗 -->
<u-modal
ref="uModal"
v-model="showModal"
:show-cancel-button="false"
confirm-color="#999"
confirm-text="返回"
content="当前门店已被禁用,是否返回重新选择?"
title="提示"
@confirm="$Router.back()"
></u-modal>
</view>
</template>
<script>
import { mapMutations, mapActions, mapState } from 'vuex';
import Auth from '@/shopro/permission/index.js';
export default {
components: {},
data() {
return {
isEmpty: false,
showModal: false,
// 筛选
filter1: 'today',
filter1Value: 0,
filter1Label: '今日',
filterList1: [
{ label: '今日', value: 0, parmas: 'today' },
{ label: '昨日', value: 1, parmas: 'yesterday' },
{ label: '本周', value: 2, parmas: 'week' },
{ label: '本月', value: 3, parmas: 'month' },
{ label: '自定义', value: 4, parmas: 'custom' }
],
filter2: 'all',
filter2Value: 0,
filter2Label: '全部',
filterList2: [
{ label: '全部', value: 0, parmas: 'all' },
{ label: '待发货', value: 1, parmas: 'nosend' },
{ label: '待完成', value: 2, parmas: 'noget' },
{ label: '已完成', value: 3, parmas: 'finish' }
],
custom: [], //自定义日期
storeOrderList: [], //订单商品列表
orderInfo: {}, //订单统计信息
storeDetail: {}, //门店信息
cancelType: '', //核销分类
scanCodes: '', //扫码内容。
showInputModal: false, //输码核销
qrcode: '', //输码
showCalendar: false, //日期选择
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1
};
},
computed: {},
onLoad(options) {
options && options.storeId && uni.setStorageSync('storeId', options.storeId);
this.getStoreDetail();
},
onShow() {
this.currentPage = 1;
this.lastPage = 1;
this.storeOrderList = [];
this.getStoreOrder();
},
onPullDownRefresh() {
this.currentPage = 1;
this.lastPage = 1;
this.storeOrderList = [];
this.getStoreOrder();
},
onReachBottom() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getStoreOrder();
}
},
methods: {
jump(path, query) {
this.$Router.push({
path: path,
query: query
});
},
/**
* 累加某字段值
* @param {Array} list - 源数据
* @param {String} key - 键名
*/
reduceAdd(list, key) {
return list.reduce((pre, cur) => (pre += Number(cur[key])), 0);
},
// 筛选
changeFilter1(e) {
if (e === 4) {
this.showCalendar = true;
} else {
this.filter1 = this.filterList1[e].parmas;
this.filter1Label = this.filterList1[e].label;
this.onFilter();
}
},
changeFilter2(e) {
this.filter2 = this.filterList2[e].parmas;
this.filter2Label = this.filterList2[e].label;
this.onFilter();
},
// 选择门店
goStoreList() {
this.$Router.replace({
path: '/pages/app/merchant/list'
});
},
// 获取门店信息
getStoreDetail() {
let that = this;
that.$http('store.info', {
store_id: uni.getStorageSync('storeId')
}).then(res => {
if (res.code === 1) {
that.storeDetail = res.data;
} else {
uni.removeStorageSync('storeId');
that.showModal = true;
}
});
},
// 扫码
async scanCode() {
let platform = this.$platform.get();
let authState = await new Auth('camera').check();
// #ifdef H5
if (platform === 'H5') {
this.$u.toast('普通浏览器不支持扫码功能,请使用小程序或微信内浏览器');
} else {
this.$wxsdk.scanQRCode(res => {
this.scanCodes = res.resultStr;
this.postOrderConfirm();
});
}
// #endif
// #ifndef H5
authState &&
uni.scanCode({
success: res => {
this.scanCodes = res.result;
this.postOrderConfirm();
},
fail: err => {
console.log(err);
}
});
// #endif
},
// 输码
onConfirm() {
this.showInputModal = false;
this.scanCodes = this.qrcode;
this.postOrderConfirm();
},
// 核销
postOrderConfirm() {
let that = this;
that.$http(
'store.orderConfirm',
{
codes: that.scanCodes,
store_id: uni.getStorageSync('storeId')
},
'核销中...'
).then(res => {
uni.vibrateLong({
success: () => {
that.$u.toast(res.msg);
}
});
if (res.code === 1) {
that.$u.toast(res.msg);
that.scanCodes = '';
that.storeOrderList = [];
that.qrcode = '';
that.getStoreOrder();
}
});
},
// 门店订单列表
getStoreOrder() {
let that = this;
that.loadStatus = 'loading';
that.$http(
'store.order',
{
date_type: that.filter1,
date: that.custom,
type: that.filter2,
page: that.currentPage,
store_id: uni.getStorageSync('storeId')
},
'加载中...'
).then(res => {
uni.stopPullDownRefresh();
if (res.code == 1) {
that.storeOrderList = [...that.storeOrderList, ...res.data.result.data];
that.isEmpty = !that.storeOrderList.length;
that.orderInfo = res.data;
that.lastPage = res.data.result.last_page;
that.loadStatus = that.currentPage < res.data.result.last_page ? 'loadmore' : 'nomore';
}
});
},
// 选择日期
selDate(e) {
this.custom = [];
this.custom.push(e.startDate);
this.custom.push(e.endDate);
this.isShowDropDown = false;
this.filter1Label = `${e.startDate.replace(/-/g, ':')}-${e.endDate.replace(/-/g, ':')}`;
this.onFilter();
},
// 选择筛选
onFilter(val, title) {
this.storeOrderList = [];
this.currentPage = 1;
this.getStoreOrder();
}
}
};
</script>
<style lang="scss">
// 遮罩
.mask {
width: 100%;
height: 100%;
position: fixed;
z-index: 20;
}
// 商户信息
.shopinfo-box {
background: url($IMG_URL+'/imgs/user/shop_headbg.png') no-repeat;
background-size: 100% 100%;
height: 320rpx;
.user-head {
padding-top: 10rpx;
.shop-info {
padding-left: 30rpx;
.shop-title {
font-size: 34rpx;
font-weight: bold;
color: rgba(255, 255, 255, 1);
}
.icon-xiala {
font-size: 34rpx;
color: rgba(255, 255, 255, 1);
}
.shop-address {
font-size: 28rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
width: 540rpx;
}
}
.merchant-btn {
padding: 0;
width: 136rpx;
line-height: 46rpx;
background: rgba(255, 255, 255, 1);
border-radius: 23rpx 0px 0px 23rpx;
font-size: 20rpx;
font-weight: 500;
color: #3eb49c;
}
}
}
// 卡片
.info-card-box {
background-color: #fff;
padding: 30rpx 20rpx;
.info-card {
position: relative;
width: 350rpx;
height: 165rpx;
border-radius: 10rpx;
overflow: hidden;
.card-bg {
width: 100%;
height: 100%;
}
.card-content {
position: absolute;
width: 100%;
height: 100%;
z-index: 3;
top: 0;
left: 0;
padding: 30rpx 0 0 30rpx;
.card-title {
font-size: 28rpx;
font-weight: bold;
color: rgba(255, 255, 255, 1);
}
.card-detail-box {
padding: 10rpx 20rpx;
height: 45rpx;
background: rgba(255, 255, 255, 1);
width: 150rpx;
border-radius: 23rpx;
margin-top: 20rpx;
.card-detail {
font-size: 22rpx;
font-weight: 500;
}
.icon-color1 {
color: #00b6ce;
font-size: 24rpx;
}
.icon-color2 {
color: #0f98f9;
font-size: 24rpx;
}
}
}
}
}
// 核销nav
.cancel-nav {
background: #fff;
margin: 10rpx 0 0;
position: relative;
z-index: 22;
padding: 0 20rpx;
}
// 销量
.sales-volume-box {
background-color: #fff;
.sales-volume {
width: 338rpx;
height: 72rpx;
background: rgba(76, 184, 157, 0.06);
border: 1rpx solid rgba(185, 227, 217, 1);
border-radius: 10rpx;
font-size: 24rpx;
font-weight: 400;
color: rgba(76, 184, 157, 1);
}
}
// 输码弹窗
.modal-box {
background: #fff;
width: 610rpx;
margin: 0 auto;
border-radius: 20rpx;
.modal-head {
width: 100%;
.modal-head-img {
width: 100%;
height: 213rpx;
}
.modal-head-title {
font-size: 35rpx;
font-weight: bold;
color: #343434;
line-height: 42rpx;
}
}
.inp {
width: 501rpx;
height: 78rpx;
border: 1rpx solid #e5e5e5;
margin: 60rpx auto 40rpx;
font-size: 28rpx;
font-weight: 400;
color: #6d5028;
padding-left: 20rpx;
.pl-inp {
color: #9a9a9a;
}
}
.post-btn {
width: 492rpx;
line-height: 70rpx;
background: linear-gradient(90deg, #2eae9c, #6cc29f);
box-shadow: 0px 7rpx 6rpx 0px rgba(#6cc29f, 0.22);
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
padding: 0;
margin-bottom: 60rpx;
}
}
// 订单列表
.order-list {
background: #fff;
margin: 20rpx 0;
padding: 0 20rpx;
.order-bottom {
justify-content: flex-end;
height: 80rpx;
padding: 0 20rpx;
.total-price-title {
color: #999999;
font-size: 24rpx;
}
.total-price {
color: #333;
font-size: 26rpx;
&::before {
content: '¥';
font-size: 20rpx;
}
}
}
.order-head {
padding: 0 25rpx;
height: 77rpx;
border-bottom: 1rpx solid #dfdfdf;
.no {
font-size: 26rpx;
color: #999;
}
.state {
font-size: 26rpx;
color: #a8700d;
}
}
.goods-order {
border-bottom: 1px solid rgba(223, 223, 223, 0.5);
padding: 20rpx;
margin-bottom: 20rpx;
.order-sku {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
width: 450rpx;
margin-bottom: 20rpx;
.order-num {
margin-right: 10rpx;
}
}
.order-price-box {
.status-btn {
height: 32rpx;
border: 1rpx solid rgba(207, 169, 114, 1);
border-radius: 15rpx;
font-size: 20rpx;
font-weight: 400;
color: rgba(168, 112, 13, 1);
padding: 0 10rpx;
margin-left: 20rpx;
background: rgba(233, 183, 102, 0.16);
}
.order-price {
font-size: 26rpx;
font-weight: 600;
color: rgba(51, 51, 51, 1);
}
}
}
}
</style>

@ -0,0 +1,115 @@
<!-- 商家信息 -->
<template>
<view class="merchant-info-box">
<label class="u-flex u-col-center form-item">
<text class="form-tilte">门店名称:</text>
<view class="form-content">{{ storeDetail.name }}</view>
</label>
<label class="u-flex u-col-center form-item">
<text class="form-tilte">联系电话:</text>
<view class="form-content">{{ storeDetail.phone }}</view>
</label>
<!-- 营业时间 -->
<label class="u-flex u-col-center form-item">
<text class="form-tilte">营业时间:</text>
<view class="form-content">{{ storeDetail.openhours }}</view>
</label>
<!-- 选择星期 -->
<view class="u-flex u-col-top form-item u-m-b-20">
<text class="form-tilte">营业星期:</text>
<view class="form-content u-flex u-flex-wrap u-row-left">
<button class="cu-btn sm u-m-r-10 u-m-b-10 u-m-l-0" :class="!storeWeek.includes(week.value)?'line-green':'bg-green'" v-for="week in weekList" :key="week.title">
{{ week.title }}
</button>
</view>
</view>
<!-- 选择省市 -->
<label class="u-flex u-col-top form-item ">
<text class="form-tilte">门店地址:</text>
<view class="form-content">{{ storeDetail.province_name }}{{ storeDetail.city_name }}{{ storeDetail.area_name }}{{ storeDetail.address }}</view>
</label>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
storeWeek: [],
storeDetail: {}, //门店信息
weekList: [
//星期
{
title: '周一',
value: '1'
},
{
title: '周二',
value: '2'
},
{
title: '周三',
value: '3'
},
{
title: '周四',
value: '4'
},
{
title: '周五',
value: '5'
},
{
title: '周六',
value: '6'
},
{
title: '周日',
value: '7'
}
]
};
},
computed: {},
onLoad() {
this.getStoreDetail();
},
methods: {
getStoreDetail() {
let that = this;
that.$http('store.info', {
store_id: uni.getStorageSync('storeId')
}).then(res => {
if (res.code === 1) {
that.storeDetail = res.data;
that.storeWeek = res.data.openweeks.split(',');
}
});
}
}
};
</script>
<style lang="scss">
.merchant-info-box {
border-top: 1rpx solid #f5f5f5;
.form-item {
background-color: #fff;
border-bottom: 1rpx solid #f5f5f5;
padding: 20rpx 30rpx;
.form-title {
font-size: 28rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
}
.form-content {
font-size: 28rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
flex: 1;
}
}
}
</style>

@ -0,0 +1,116 @@
<!-- 门店列表 -->
<template>
<view class="page_box store-list-wrap">
<view class="head_box"></view>
<view class="content_box">
<label v-for="store in storeList" :key="store.id" @tap="selStore(store.id)">
<view class="store-item u-flex u-row-between">
<view class="u-flex u-col-center">
<view class="img-box"><image class="store-img" :src="store.image_first" mode="aspectFill" lazy-load></image></view>
<view class="item-left u-flex-col u-row-left">
<text class="store-title">{{ store.name }}</text>
<text class="store-content">{{ store.city_name }}{{ store.area_name }}{{ store.address }}</text>
</view>
</view>
<u-checkbox :value="storeId == store.id" activeColor="#4CB89D" shape="circle"></u-checkbox>
</view>
</label>
</view>
<view class="foot_box u-flex u-row-between u-col-center"><button class="u-reset-button save-btn" @tap="saveStore">确认</button></view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
storeList: [],
storeId: uni.getStorageSync('storeId')
};
},
computed: {},
onLoad() {
this.getStoreAddress();
},
methods: {
// 选择门店
selStore(id) {
this.storeId = id;
},
// 确认门店
saveStore() {
uni.setStorageSync('storeId', this.storeId);
if (!this.storeId) {
this.$u.toast('请选选择门店');
} else {
this.$Router.replace({
path: '/pages/app/merchant/index'
});
}
},
// 获取门店列表
getStoreAddress() {
let that = this;
that.$http('store.list').then(res => {
if (res.code == 1) {
that.storeList = res.data;
}
});
}
}
};
</script>
<style lang="scss">
.store-item {
padding: 0 30rpx;
height: 180rpx;
background-color: #fff;
border-bottom: 1rpx solid rgba(223, 223, 223, 0.6);
width: 100%;
.img-box {
width: 100rpx;
height: 100rpx;
border-radius: 6rpx;
overflow: hidden;
margin-right: 20rpx;
.store-img {
width: 100rpx;
height: 100rpx;
border-radius: 6rpx;
}
}
.store-title {
font-size: 30rpx;
font-weight: 600;
color: rgba(52, 52, 52, 1);
margin-bottom: 10rpx;
}
.store-content {
font-size: 24rpx;
font-weight: 400;
color: rgba(102, 102, 102, 1);
}
}
.foot_box {
height: 100rpx;
background: rgba(255, 255, 255, 1);
padding: 0 20rpx;
.save-btn {
width: 710rpx;
line-height: 80rpx;
background: linear-gradient(90deg, #2eae9c, #6cc29f);
border: 1rpx solid rgba(238, 238, 238, 1);
font-size: 30rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
padding: 0;
border-radius: 40rpx;
}
}
</style>

@ -0,0 +1,105 @@
<!-- 积分商品列表 -->
<template>
<view class="score-wrap">
<view class="goods-wrap u-p-20 u-flex u-flex-wrap">
<view class="goods-item u-m-b-20" v-for="leftGoods in scoreList" :key="leftGoods.id">
<shopro-goods-card
:image="leftGoods.image"
:title="leftGoods.title"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: leftGoods.id, type: 'score' } })"
>
<template #cardBottom>
<view class="price-box u-flex u-row-between u-flex-wrap">
<view class="beans-box u-flex u-m-10">
<image class="bean-img u-m-r-10" :src="$IMG_URL + '/imgs/score/score.png'" mode=""></image>
{{ leftGoods.price }}
</view>
<view class="sales-box u-m-10">已兑换{{ leftGoods.sales }}件</view>
</view>
</template>
</shopro-goods-card>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty
v-if="isEmpty"
@click="$Router.pushTab('/pages/index/index')"
:image="$IMG_URL + '/imgs/empty/empty_goods.png'"
tipText="暂无积分商品"
btnText="去首页逛逛"
></shopro-empty>
<!-- 加载更多 -->
<u-loadmore v-if="!isEmpty" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</view>
</template>
<script>
export default {
components: {},
data() {
return {
scoreList: [],
isEmpty: false, //无数据
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1
};
},
onLoad() {
this.getScoreShopsList();
// 触底监听
uni.$on('uOnReachBottom', () => {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getScoreShopsList();
}
});
},
computed: {},
methods: {
//积分商品列表
getScoreShopsList() {
let that = this;
that.loadStatus = 'loading';
that.$http('goods.scoreList', {
page: that.currentPage
}).then(res => {
if (res.code == 1) {
that.scoreList = [...that.scoreList, ...res.data.data];
that.isEmpty = !that.scoreList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
}
}
};
</script>
<style lang="scss">
.goods-item {
margin-right: 20rpx;
margin-bottom: 20rpx;
&:nth-child(2n) {
margin-right: 0;
}
.price-box {
.beans-box {
font-size: 32upx;
font-weight: bold;
color: rgba(228, 141, 4, 1);
.bean-img {
width: 36upx;
height: 36upx;
}
}
.sales-box {
font-size: 22rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
}
}
}
</style>

@ -0,0 +1,210 @@
<!-- 添加评论 -->
<template>
<view class="commont-from-wrap">
<!-- 评价商品 -->
<view class="goods-card">
<shopro-mini-card :title="goodsDetail.goods_title" :image="goodsDetail.goods_image">
<template #describe>
<view class="order-sku u-ellipsis-1">
<text class="order-num">数量:{{ goodsDetail.goods_num || 0 }};</text>
{{ goodsDetail.goods_sku_text ? goodsDetail.goods_sku_text : '' }}
</view>
</template>
<template #cardBottom>
<view class="order-price-box u-flex ">
<text class="order-price font-OPPOSANS">¥{{ goodsDetail.goods_price || 0 }}</text>
<button class="u-reset-button status-btn" v-if="goodsDetail.status_name">{{ goodsDetail.status_name }}</button>
</view>
</template>
</shopro-mini-card>
</view>
<view class="form-item">
<view class="star-box u-flex">
<view class="star-title u-m-r-40">{{ starTip }}</view>
<u-rate v-model="star" minCount="1"></u-rate>
</view>
<view class="area-box">
<textarea class="inp-area" v-model="message" placeholder="宝贝满足你的期待吗?说说你的使用心得,分享给想买的他们吧~" placeholder-class="pl-style" />
<view class="img-box">
<u-upload
:showProgress="false"
@on-uploaded="uploadSuccess"
@on-remove="uploadRemove"
:action="`${$BASE_URL}/api/common/upload`"
:header="{token: token}"
width="138"
height="138"
maxCount="9"
></u-upload>
</view>
</view>
</view>
<view class="foot_box u-flex u-row-center u-col-center u-m-t-60"><button class="u-reset-button post-btn" @tap="subComment">发布</button></view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
imgList: [],
star: 5,
message: '',
goodsDetail: {},
token: uni.getStorageSync('token')
};
},
computed: {
starTip() {
let text = '';
if (this.star <= 1) {
text = '差评';
}
if (this.star > 1 && this.star <= 3) {
text = '中评';
}
if (this.star >= 4) {
text = '好评';
}
return text;
}
},
onLoad() {
this.getOrderItemDetail();
},
methods: {
// 订单详情
getOrderItemDetail() {
let that = this;
that.$http('order.itemDetail', {
id: that.$Route.query.orderId,
order_item_id: that.$Route.query.orderItemId
}).then(res => {
if (res.code === 1) {
that.goodsDetail = res.data;
}
});
},
// 上传图片成功
uploadSuccess(e) {
this.imgList = [];
e.forEach(item => {
this.imgList.push(item.response.data.url);
});
},
// 移除图片
uploadRemove(index) {
this.imgList.splice(index, 1);
},
subComment() {
let that = this;
that.$http('order.comment', {
id: that.$Route.query.orderId,
order_item_id: that.goodsDetail.id,
level: that.star,
content: that.message,
images: that.imgList
}, '提交中...').then(res => {
if (res.code === 1) {
that.$u.toast('评论发表成功');
that.$Router.back();
}
});
}
}
};
</script>
<style lang="scss">
// 评价商品
.goods-card {
margin: 10rpx 0;
padding: 20rpx;
background: #fff;
.order-sku {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
width: 450rpx;
margin-bottom: 20rpx;
.order-num {
margin-right: 10rpx;
}
}
.order-price-box {
.status-btn {
height: 32rpx;
border: 1rpx solid rgba(207, 169, 114, 1);
border-radius: 15rpx;
font-size: 20rpx;
font-weight: 400;
color: rgba(168, 112, 13, 1);
padding: 0 10rpx;
margin-left: 20rpx;
background: rgba(233, 183, 102, 0.16);
}
.order-price {
font-size: 26rpx;
font-weight: 600;
color: rgba(51, 51, 51, 1);
}
}
}
// 评论,选择图片
.form-item {
background: #ffff;
padding-bottom: 30rpx;
.star-box {
height: 100rpx;
padding: 0 25rpx;
}
.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;
.pl-style {
font-size: 26rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
line-height: 42rpx;
}
.inp-area {
font-size: 26rpx;
font-weight: 500;
color: #333;
line-height: 50rpx;
width: 100%;
}
.img-box {
display: flex;
align-items: center;
flex-wrap: wrap;
margin-top: 20rpx;
}
}
.post-btn {
width: 690rpx;
line-height: 80rpx;
background: linear-gradient(90deg, rgba(240, 199, 133, 1), rgba(246, 214, 157, 1));
border-radius: 40rpx;
color: rgba(#fff, 0.9);
margin-bottom: 30rpx;
}
</style>

@ -0,0 +1,126 @@
<!-- 评论列表 -->
<template>
<view class="page_box">
<view class="head_box u-flex">
<button :class="{ 'btn-active': typeCurrent === t.code }" class="u-reset-button type-btn" @tap="selType(t.code)" v-for="t in commentTypeList" :key="t.code">
{{ t.name }}({{ t.num }})
</button>
</view>
<view class="content_box">
<scroll-view scroll-y="true" enable-back-to-top @scrolltolower="loadMore" class="scroll-box">
<view class="comment-list">
<block v-for="comment in commentList" :key="comment.id"><sh-comment :comment="comment"></sh-comment></block>
</view>
<shopro-empty v-if="isEmpty" :image="$IMG_URL + '/imgs/empty/comment_empty.png'" tipText="暂无此类评价~"></shopro-empty>
<!-- 加载更多 -->
<u-loadmore v-if="commentList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</scroll-view>
</view>
</view>
</template>
<script>
import shComment from '../components/sh-comment.vue';
export default {
components: {
shComment
},
data() {
return {
isEmpty: false,
typeCurrent: 'all',
commentList: [],
commentTypeList: [],
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1
};
},
computed: {},
onLoad() {
this.getCommentType();
},
methods: {
selType(id) {
if (this.typeCurrent !== id) {
this.typeCurrent = id;
this.commentList = [];
this.currentPage = 1;
this.getCommentList();
}
},
// 评价类型
getCommentType() {
let that = this;
that.$http('goods.commentType', {
goods_id: that.$Route.query.goodsId
}).then(res => {
if (res.code === 1) {
that.commentTypeList = res.data;
that.getCommentList();
}
});
},
// 商品评论
getCommentList() {
let that = this;
that.loadStatus = 'loading';
that.$http('goods.commentList', {
goods_id: that.$Route.query.goodsId,
pre_page: 10,
page: that.currentPage,
type: that.typeCurrent
}, '加载中...').then(res => {
if (res.code === 1) {
that.commentList = [...that.commentList, ...res.data.data];
that.isEmpty = !that.commentList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getCommentList();
}
}
}
};
</script>
<style lang="scss">
.empty-box {
position: fixed;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
.head_box {
height: 114rpx;
background-color: #fff;
padding: 0 20rpx;
.type-btn {
width: 130rpx;
line-height: 62rpx;
background: rgba(238, 238, 238, 1);
border-radius: 31rpx;
border: none;
font-size: 28rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
padding: 0;
margin-right: 10rpx;
}
.btn-active {
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(229, 138, 0, 0.22);
color: #a8700d;
}
}
.comment-list {
margin-top: 20rpx;
}
</style>

@ -0,0 +1,157 @@
<template>
<view class="sh-activity-box">
<view class="u-flex activity-box u-col-top">
<view class="title">优惠</view>
<view class="activity-content u-flex-1">
<view class="tip-list u-flex u-flex-1 u-col-center" v-for="item in detail" :key="item.id" @tap="showActivity(item.type)">
<view class="u-flex u-flex-1 u-col-center">
<view class="title-tag cu-tag bg-red sm radius u-m-r-10">{{ item.title }}</view>
<view class="tag-box cu-tag line-red sm radius u-m-r-10" v-if="index < 3" v-for="(tag, index) in item.tags" :key="tag">{{ tag }}</view>
</view>
<view class="u-iconfont uicon-arrow-right" style="color: #bfbfbf;font-size: 28rpx;"></view>
</view>
</view>
</view>
<!-- 活动弹窗 -->
<u-popup v-if="showModal" v-model="showModal" @close="onClose" mode="bottom" border-radius="30" :closeable="true" close-icon-pos="top-right">
<view class="activity-modal-box page_box">
<view class="modal-head u-flex u-row-center u-col-center">
<text class="head-title">{{ activityMap[activityType].title }}</text>
</view>
<view class="modal-content content_box">
<view class="tip-list u-flex u-flex-1 u-col-center" @tap="toSelGoods">
<view class="u-flex u-flex-1 modal-item u-row-between u-col-center">
<view class="title-tag cu-tag bg-red sm radius u-m-r-10">{{ activityMap[activityType].title }}</view>
<view class="u-flex u-col-center">
<view class="tag-box cu-tag line-red sm radius u-m-r-10" v-for="(tag, index) in activityMap[activityType].tags" :key="tag">{{ tag }}</view>
</view>
</view>
<view class="u-iconfont uicon-arrow-right" style="color: #bfbfbf;font-size: 28rpx;"></view>
</view>
<view class="all-goods-num">共有{{ activityMap[activityType].num }}件商品参加此活动</view>
</view>
<view class="modal-foot u-flex u-row-center u-col-center"><button class=" u-reset-button serve-btn" @tap="toSelGoods">去凑单</button></view>
</view>
</u-popup>
</view>
</template>
<script>
/**
* 参与优惠活动卡片
* @property {Array} detail - 优惠活动详情列表
* @property {Boolean} showModal- 显隐
*/
export default {
components: {},
data() {
return {
showModal: false,
activityType: ''
};
},
props: {
detail: {
type: Array,
default: () => []
}
},
created() {},
computed: {
activityMap() {
let obj = {};
this.detail.forEach(item => {
obj[item.type] = item;
obj[item.type].num = item.goods_ids.split(',').length;
});
return obj;
}
},
methods: {
// 显示
showActivity(type) {
this.activityType = type;
this.showModal = true;
},
// 关闭
onClose() {
this.activityType = '';
this.showModal = false;
},
// 去凑单
toSelGoods() {
this.$Router.push({
path: '/pages/activity/discounts/list',
query: this.activityMap[this.activityType]
});
this.activityType = '';
this.showModal = false;
}
}
};
</script>
<style lang="scss">
.activity-box {
background: #fff;
padding: 30rpx 20rpx 0;
margin: 10rpx 0;
.title {
font-size: 28rpx;
color: #999;
margin-right: 20rpx;
}
.activity-content {
.tip-list {
font-size: 28rpx;
width: 100%;
padding-bottom: 30rpx;
}
}
}
// 弹窗
.activity-modal-box {
width: 750rpx;
min-height: 700rpx;
border-radius: 30rpx 30rpx 0 0;
background: #fff;
padding: 30rpx;
.serve-btn {
width: 690rpx;
line-height: 80rpx;
background: linear-gradient(90deg, rgba(240, 199, 133, 1), rgba(246, 214, 157, 1));
border-radius: 40rpx;
font-size: 30rpx;
color: rgba(#fff, 0.9);
margin-top: 40rpx;
}
.modal-head {
margin-bottom: 30rpx;
position: relative;
.head-title {
font-size: 32rpx;
font-weight: bold;
}
}
.modal-content {
overflow-y: auto;
.tip-list {
font-size: 28rpx;
width: 100%;
padding: 20rpx 0;
border-bottom: 1rpx solid #eeeeee;
}
.all-goods-num {
font-size: 28rpx;
font-weight: 500;
color: #333333;
padding: 20rpx 0;
}
}
}
</style>

@ -0,0 +1,100 @@
<template>
<view class="comment-box">
<view class="head u-flex u-row-between">
<view class="u-flex">
<image class="avatar" :src="comment.user ? comment.user.avatar : $IMG_URL + '/imgs/base_avatar.png'" mode="aspectFill"></image>
<view class="user-name u-ellipsis-1">{{ comment.user ? comment.user.nickname : '***' }}</view>
<u-rate :value="comment.level" disabled></u-rate>
</view>
<text class="time">{{ $u.timeFormat(comment.createtime, 'yyyy-mm-dd hh:MM') }}</text>
</view>
<view class="detail">{{ comment.content }}</view>
<view class="img-box u-flex u-row-center u-col-center">
<view class="nav u-flex u-flex-wrap">
<image
v-for="(img, index) in comment.images"
:key="index"
@tap.stop="tools.previewImage(comment.images, index)"
class="comment-img"
:src="img"
mode="aspectFill"
></image>
</view>
</view>
</view>
</template>
<script>
/**
* 评论信息卡片
* @property {Object} comment - 评论信息
*/
export default {
components: {},
data() {
return {};
},
props: {
comment: {}
},
computed: {},
methods: {}
};
</script>
<style lang="scss">
.comment-box {
padding: 30rpx 20rpx;
border-bottom: 1rpx solid #eee;
background: #fff;
.head {
margin-bottom: 20rpx;
.avatar {
width: 52rpx;
height: 52rpx;
border-radius: 50%;
background: #ccc;
}
.user-name {
font-size: 26rpx;
color: #999;
width: 140rpx;
margin: 0 20rpx;
}
.time {
font-size: 24rpx;
color: #c4c4c4;
}
}
.detail {
font-size: 26rpx;
font-weight: 500;
color: rgba(102, 102, 102, 1);
line-height: 42rpx;
}
.img-box {
margin-top: 30rpx;
position: relative;
.nav {
width: 670rpx;
}
.comment-img {
width: 160rpx;
height: 160rpx;
background: #ccc;
margin-right: 10rpx;
margin-bottom: 10rpx;
border-radius: 6rpx;
&:nth-child(4n) {
margin-right: 0;
}
}
}
}
</style>

@ -0,0 +1,75 @@
<template>
<view class="category-box u-m-b-10">
<swiper class="swiper-box" @change="onSwiper" circular :autoplay="false" :interval="3000" :duration="1000">
<swiper-item v-for="(c, index) in couponList" :key="c.id">
<view class="tab-list u-flex u-row-center u-col-center"><shopro-coupon :couponData="c" :state="0"></shopro-coupon></view>
</swiper-item>
</swiper>
<view class="category-dots">
<text :class="categoryCurrent === index ? 'category-dot-active' : 'category-dot'" v-for="(dot, index) in couponList.length" :key="index"></text>
</view>
</view>
</template>
<script>
/**
* 详情优惠券轮播 - 详情专用
* @property {Object} couponList - 优惠劵列表
*/
export default {
components: {},
data() {
return {
categoryCurrent: 0 //分类轮播下标
};
},
props: {
couponList: {}
},
computed: {},
methods: {
// 轮播
onSwiper(e) {
this.categoryCurrent = e.detail.current;
}
}
};
</script>
<style lang="scss">
.category-box {
padding-top: 20rpx;
background: #fff;
height: 230rpx;
position: relative;
.swiper-box {
height: 210rpx;
.tab-list {
/deep/.coupon-wrap {
width: 710rpx;
}
}
}
.category-dots {
display: flex;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 15rpx;
.category-dot {
width: 40rpx;
height: 3rpx;
background: #eeeeee;
margin-right: 10rpx;
}
.category-dot-active {
width: 40rpx;
height: 3rpx;
background: #a8700d;
margin-right: 10rpx;
}
}
}
</style>

@ -0,0 +1,168 @@
<template>
<view class="filter-box">
<view class="cu-modal" style="z-index: -1;" :class="{ show: showSel }" @tap="hideModal"></view>
<view class="navbar">
<view class="sel-box u-flex-col" v-show="showSel">
<view class="sel-item" @tap="onSel(0)" :class="{ 'sel-active': defaultOrder === 0 && filterIndex === 0 }">综合推荐</view>
<view class="sel-item" @tap="onSel(1)" :class="{ 'sel-active': defaultOrder === 1 && filterIndex === 0 }">扩展自定义排序</view>
</view>
<view class="nav-item u-flex-col" :class="{ current: filterIndex === 0 }" @tap="tabClick(0)">
<view class="title-box u-flex u-col-center">
<text class="filter-title">{{ defaultOrder === 1 ? '自定义' : '综合推荐' }}</text>
<view class="p-box"><text style="font-size: 22rpx;" :class="{ active: filterIndex === 0 }" class="u-iconfont uicon-arrow-up u-m-l-6"></text></view>
</view>
<view class="line" :class="{ 'line-active': filterIndex === 0 }"></view>
</view>
<view class="nav-item u-flex-col" :class="{ current: filterIndex === 1 }" @tap="tabClick(1)">
<view class="title-box u-flex"><text class="filter-title">销量</text></view>
<view class="line" :class="{ 'line-active': filterIndex === 1 }"></view>
</view>
<view class="nav-item u-flex-col" :class="{ current: filterIndex === 2 }" @tap="tabClick(2)">
<view class="title-box u-flex">
<text class="filter-title">价格</text>
<view class="p-box u-flex-col u-m-l-6">
<view
class="u-iconfont uicon-arrow-up"
:style="{ fontSize: '20rpx', lineHeight: '18rpx', color: priceOrder === 1 && filterIndex === 2 ? '#d5a65a' : '#333' }"
></view>
<view
class="u-iconfont uicon-arrow-down"
:style="{ fontSize: '20rpx', lineHeight: '18rpx', color: priceOrder === 2 && filterIndex === 2 ? '#d5a65a' : '#333' }"
></view>
</view>
</view>
<view class="line" :class="{ 'line-active': filterIndex === 2 }"></view>
</view>
<view class="nav-item u-flex-col" :class="{ current: filterIndex === 3 }" @tap="tabClick(3)">
<view class="title-box"><text class="filter-title">新品优先</text></view>
<view class="line" :class="{ 'line-active': filterIndex === 3 }"></view>
</view>
</view>
</view>
</template>
<script>
/**
* 商品筛选组件
*
*/
import { mapState, mapActions, mapMutations } from 'vuex';
export default {
components: {},
props: {},
data() {
return {
filterIndex: 0,
showSel: false, //综合选择
defaultOrder: 0, //综合
priceOrder: 0, //价格
salesOrder: 0, //销量
newProdcutOrder: 0 //新品优先
};
},
computed: {
filterData() {
const data = {
defaultOrder: this.defaultOrder,
priceOrder: this.priceOrder,
salesOrder: this.salesOrder,
newProdcutOrder: this.newProdcutOrder
};
return data;
}
},
onLoad() {},
methods: {
//筛选
tabClick(index) {
this.filterIndex = index;
index === 0 ? (this.showSel = !this.showSel) : (this.showSel = false);
index === 1 ? (this.salesOrder = 1) : (this.salesOrder = 0);
index === 2 ? (this.priceOrder = this.priceOrder === 1 ? 2 : 1) : (this.priceOrder = 0);
index === 3 ? (this.newProdcutOrder = 1) : (this.newProdcutOrder = 0);
this.$emit('change', this.filterData);
},
onSel(dot) {
this.defaultOrder = dot;
this.$emit('change', this.filterData);
this.showSel = false;
},
hideModal() {
this.showSel = false;
}
}
};
</script>
<style lang="scss">
.sel-box {
position: absolute;
width: 750rpx;
height: 140rpx;
background: rgba(246, 246, 246, 1);
border-radius: 0px 0px 20rpx 20rpx;
z-index: 999;
left: 50%;
transform: translateX(-50%);
bottom: -138rpx;
transition: all ease-out 0.2s;
.sel-item {
color: #333;
padding: 20rpx 40rpx 0;
font-size: 24rpx;
font-weight: 500;
}
.sel-active {
color: #d5a65a;
font-size: 26rpx;
font-weight: 600;
}
}
.filter-box {
width: 750rpx;
}
.navbar {
display: flex;
width: 750rpx;
height: 95rpx;
background: #fff;
border-bottom: 1upx solid #e6e6e6;
position: relative;
z-index: 999;
.nav-item {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 30rpx;
position: relative;
.filter-title {
font-size: 28rpx;
color: #333;
font-weight: 600;
}
.line {
width: 110rpx;
height: 4rpx;
background: transparent;
position: absolute;
bottom: 0;
z-index: 2;
}
.line-active {
background: rgba(213, 166, 90, 1);
}
.p-box {
display: flex;
flex-direction: column;
}
}
}
.current {
color: #d5a65a;
}
</style>

@ -0,0 +1,220 @@
<template>
<view class="group-people">
<block v-if="grouponTeamList.length">
<view class="into-title u-flex u-row-between">
<text>已有{{ grouponData.sales }}人参与活动</text>
<view class="u-iconfont uicon-arrow-right" style="color: #bfbfbf;font-size: 28rpx;" @tap="onMoreGrouponTeam"></view>
</view>
<view class="into-item u-flex u-row-between" v-for="(g, index) in grouponTeamList" :key="g.id" v-if="index < 2">
<view class="u-flex">
<image class="into-img" :src="g.leader.user_avatar" mode="aspectFill"></image>
<text class="into-name">{{ g.leader.user_nickname }}</text>
</view>
<view class="u-flex">
<view class="">
<view class="num">
还差
<text class="num-color">{{ g.num - g.current_num }}人</text>
成团
</view>
<view class="time" v-if="g.expiretime && g.time">剩余时间{{ g.time.h }}:{{ g.time.m }}:{{ g.time.s }}</view>
</view>
<button class="u-reset-button join-btn" @tap="jump('/pages/activity/groupon/detail', { id: g.id })">去参团</button>
</view>
</view>
</block>
<!-- 弹窗 -->
<u-popup v-model="showModal" mode="bottom" :closeable="true" close-icon-pos="top-right">
<view class="modal-box page_box">
<view class="modal-head u-flex u-row-center">
<text class="head-title">正在拼团</text>
</view>
<view class="modal-content content_box y-f">
<view class="into-item u-flex u-row-between" v-for="g in grouponTeamList" :key="g.id">
<view class="u-flex">
<image class="into-img" :src="g.leader.user_avatar" mode="aspectFill"></image>
<text class="into-name">{{ g.leader.user_nickname }}</text>
</view>
<view class="u-flex">
<view class="y-end">
<view class="num">
还差
<text class="num-color">{{ g.num - g.current_num }}人</text>
成团
</view>
<view class="time" v-if="g.expiretime && g.time">剩余时间{{ g.time.h }}:{{ g.time.m }}:{{ g.time.s }}</view>
</view>
<button class="u-reset-button join-btn" @tap="joinTeam('/pages/activity/groupon/detail', { id: g.id })">去参团</button>
</view>
</view>
</view>
<view class="modal-foot u-flex u-row-center u-col-center">仅显示10个正在拼团的人</view>
</view>
</u-popup>
</view>
</template>
<script>
/**
* 商品已拼团卡片
* @property {Object} grouponData - 商品已拼团信息
*/
let timer;
export default {
components: {},
data() {
return {
grouponTeamList: [], //原数组
showModal: false
};
},
props: {
grouponData: {}
},
computed: {},
beforeDestroy() {
clearInterval(timer);
timer=null
},
created() {
this.getGrouponItem();
let that = this;
timer = setInterval(() => {
that.grouponTeamList.forEach((item, index) => {
let nowTime = new Date().getTime();
let endTime = item.expiretime * 1000;
let t = (endTime - nowTime) / 1000;
let time = that.$tools.format(t);
that.$set(that.grouponTeamList[index], 'time', time);
});
}, 1000);
},
methods: {
hideModal() {
this.showModal = false;
},
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
joinTeam(path, parmas) {
this.showModal = false;
this.$Router.push({
path: path,
query: parmas
});
},
onMoreGrouponTeam() {
this.showModal = true;
},
// 拼购人
getGrouponItem() {
let that = this;
that.$http('goods.grouponItem', {
goods_id: that.grouponData.id,
activity_id: that.grouponData.activity.id
}).then(res => {
if (res.code === 1) {
that.grouponTeamList = res.data;
}
});
}
}
};
</script>
<style lang="scss">
// 弹窗
.modal-box {
width: 750rpx;
height: 800rpx;
border-radius: 30rpx 30rpx 0 0;
background: #fff;
.modal-head {
height: 92rpx;
border-bottom: 1px solid rgba(223, 223, 223, 0.5);
padding: 0 30rpx;
.head-title {
font-size: 32rpx;
font-weight: bold;
}
}
.modal-content {
padding: 0 30rpx;
}
.modal-foot {
font-size: 24rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
height: 80rpx;
border-top: 1px solid rgba(223, 223, 223, 0.5);
}
}
// 拼团人数
.group-people {
background-color: #fff;
padding: 0 20rpx;
margin-top: 10rpx;
.into-title {
height: 80rpx;
font-size: 26rpx;
font-weight: bold;
}
}
.into-item {
height: 120rpx;
width: 100%;
border-bottom: 1rpx solid #eee;
.into-img {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-right: 23rpx;
background-color: #ccc;
}
.into-name {
font-size: 28rpx;
font-weight: 500;
color: rgba(102, 102, 102, 1);
}
.num {
font-size: 26rpx;
color: #666;
.num-color {
font-size: 24rpx;
color: #a8700d;
}
}
.time {
font-size: 22rpx;
margin-top: 10rpx;
color: #999;
}
.join-btn {
width: 140rpx;
line-height: 60rpx;
background: linear-gradient(90deg, rgba(254, 131, 42, 1), rgba(255, 102, 0, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(255, 104, 4, 0.22);
border-radius: 30rpx;
padding: 0;
font-size: 26rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
margin-left: 40rpx;
}
}
</style>

@ -0,0 +1,377 @@
<template>
<view>
<!-- 正常商品 -->
<view class="normal-price-box" v-if="type !== 'score' && !detail.activity">
<view class="u-felx u-col-bottom">
<text class="unit font-OPPOSANS">¥</text>
<text class="price font-OPPOSANS">{{ detail.price }}</text>
<text class="notice">优惠价</text>
</view>
<view class="u-flex u-row-left price-bottom-box">
<view class="u-flex">
<view class="original-price font-OPPOSANS">原价:¥{{ detail.original_price }}</view>
<text class="line"></text>
<view class="sold font-OPPOSANS">已售:{{ detail.sales }}件</view>
</view>
</view>
</view>
<!-- 积分商品 -->
<view class="score-price-card" v-if="type === 'score'">
<view class="u-flex u-col-bottom">
<image class="score-img" :src="$IMG_URL + '/imgs/score/score.png'" mode=""></image>
<text class="price">{{ detail.price }}</text>
</view>
<view class="u-felx u-col-bottom price-bottom-box">
<view class="u-flex">
<view class="original-price font-OPPOSANS">价值:¥{{ detail.original_price }}</view>
<text class="line"></text>
<view class="sold font-OPPOSANS">已兑换:{{ detail.sales }}件</view>
</view>
</view>
</view>
<!-- 团购商品 -->
<view class="groupon-price-box" v-if="detail.activity && detail.activity.type === 'groupon'">
<view class="u-flex u-col-bottom">
<text class="unit font-OPPOSANS">¥</text>
<text class="price font-OPPOSANS">{{ detail.activity_type === 'groupon' ? detail.groupon_price : detail.price }}</text>
<text class="notice font-OPPOSANS">{{ detail.activity.rules.team_num }}人团</text>
</view>
<view class="u-flex u-row-between price-bottom-box">
<view class="u-flex">
<view class="original-price font-OPPOSANS">原价:¥{{ detail.original_price }}</view>
<text class="line"></text>
<view class="sold font-OPPOSANS">已拼:{{ detail.sales }}件</view>
</view>
<view class="count-down font-OPPOSANS" v-show="activityRules.status !== 'end'">
<text class="u-p-r-10" v-show="activityRules.status === 'waiting'">距开始</text>
<text class="u-p-r-10" v-show="activityRules.status === 'ing'">距结束</text>
{{ activityRules.countDownTime.h || '00' }} : {{ activityRules.countDownTime.m || '00' }} : {{ activityRules.countDownTime.s || '00' }}
</view>
<view class="count-down" v-show="activityRules.status === 'end'">活动已结束</view>
</view>
</view>
<!-- 秒杀商品 -->
<view class="seckill-price-box " :class="activityRules.status !== 'end' ? 'seckill-bg' : 'seckilled-bg'" v-if="detail.activity && detail.activity.type === 'seckill'">
<view class="u-flex u-row-between price-top-box">
<view class="u-flex">
<text class="unit font-OPPOSANS">¥</text>
<text class="price font-OPPOSANS">{{ detail.price }}</text>
<text class="notice">秒杀价</text>
</view>
<view class="count-down font-OPPOSANS" v-show="activityRules.status !== 'end'">
<text class="u-p-r-10" v-show="activityRules.status === 'waiting'">距开始</text>
<text class="u-p-r-10" v-show="activityRules.status === 'ing'">距结束</text>
{{ activityRules.countDownTime.h || '00' }} : {{ activityRules.countDownTime.m || '00' }} : {{ activityRules.countDownTime.s || '00' }}
</view>
<view class="count-down" v-show="activityRules.status === 'end'">活动已结束</view>
</view>
<view class="u-flex u-row-between price-bottom-box">
<view class="u-flex">
<view class="original-price font-OPPOSANS">原价:¥{{ detail.original_price }}</view>
<text class="line"></text>
<view class="sold font-OPPOSANS">已售:{{ detail.sales }}件</view>
</view>
<view class="express">
<view class="u-flex">
<view style="width:100rpx;" v-show="activityRules.status !== 'end'">
<u-line-progress height="14" :show-percent="false" :percent="percent" inactive-color="#a1071a" active-color="#fff"></u-line-progress>
</view>
<view class="progress-text" v-if="detail.stock > 0">仅剩{{ detail.stock }}件</view>
<view class="progress-text" v-else>已售罄</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 商品详情价格卡片
* @property {Object} detail - 商品详情
* @property {String} type - 商品分类
*/
let timer = null;
export default {
components: {},
data() {
return {
time: {}, //倒计时
activityRules: {
startTime: 0,
endTime: 0,
status: '',
countDownTime: 0
}
};
},
props: {
detail: Object,
type: ''
},
created() {
if (this.detail.activity && this.detail.activity.type) {
this.doActivityRules();
}
},
beforeDestroy() {
clearInterval(timer);
timer = null;
},
computed: {
// 百分比
percent() {
let unit = 0;
if (this.detail.id) {
unit = this.detail.stock + this.detail.sales > 0 ? ((this.detail.sales / (this.detail.sales + this.detail.stock)) * 100).toFixed(2) : 0;
}
return Number(unit);
}
},
methods: {
// 触发活动规则
doActivityRules() {
let that = this;
switch (that.detail.activity.type) {
case 'seckill':
case 'groupon':
that.activityRules.startTime = that.detail.activity.starttime * 1000;
that.activityRules.endTime = that.detail.activity.endtime * 1000;
that.countDown();
break;
}
},
// 计时器
countDown() {
let that = this;
let t = 0;
timer = setInterval(() => {
let nowTime = new Date().getTime();
//当前日期大于活动结束日期,活动结束
if (that.activityRules.endTime < nowTime) {
that.activityRules.status = 'end';
that.$emit('change', JSON.stringify(that.activityRules));
}
// 活动未结束
if (nowTime < that.activityRules.startTime) {
//当前日期小于开始时间,活动未开始
that.activityRules.status = 'waiting';
t = that.activityRules.startTime - nowTime;
} else if (nowTime > that.activityRules.endTime) {
//当前日期大于结束时间,活动结束
that.activityRules.status = 'end';
clearInterval(timer);
return;
} else {
// 活动进行中
that.activityRules.status = 'ing';
t = that.activityRules.endTime - nowTime;
}
that.activityRules.countDownTime = that.formatToHours(t / 1000);
t--;
that.$emit('change', JSON.stringify(that.activityRules));
}, 1000);
},
//时间格式化(格式化最大为小时)
formatToHours(t) {
let format = {
h: '00',
m: '00',
s: '00'
};
if (t > 0) {
let h = Math.floor(t / 3600);
let m = Math.floor((t / 60) % 60);
let s = Math.floor(t % 60);
format.h = h < 10 ? '0' + h : h;
format.m = m < 10 ? '0' + m : m;
format.s = s < 10 ? '0' + s : s;
}
return format;
}
}
};
</script>
<style lang="scss">
// 正常商品
.normal-price-box {
padding: 20rpx;
background: url($IMG_URL+'/imgs/detail/detail_price_normal_bg.png') no-repeat;
background-size: 100% 100%;
.unit,
.notice {
font-size: 24rpx;
color: #fff;
}
.price {
font-size: 36rpx;
color: #fff;
font-weight: bold;
margin: 0 10rpx;
}
.price-bottom-box {
font-size: 24rpx;
color: #fff;
font-weight: 500;
padding-top: 10rpx;
.original-price {
text-decoration: line-through;
}
.line {
margin: 0 20rpx;
display: inline-block;
width: 3rpx;
height: 24rpx;
background-color: #fff;
}
}
}
// 团购商品
.groupon-price-box {
padding: 20rpx;
background: url($IMG_URL+'/imgs/detail/detail_group_price_bg.png') no-repeat;
background-size: 100% 100%;
.unit,
.notice {
font-size: 24rpx;
color: rgba(#fff, 0.9);
}
.notice {
line-height: 40rpx;
border: 1rpx solid rgba(255, 255, 255, 1);
border-radius: 6rpx;
padding: 0 10rpx;
}
.price {
font-size: 36rpx;
color: rgba(#fff, 0.9);
font-weight: bold;
margin: 0 10rpx;
}
.price-bottom-box {
font-size: 24rpx;
color: rgba(#fff, 0.9);
font-weight: 500;
padding-top: 10rpx;
.line {
margin: 0 20rpx;
display: inline-block;
width: 3rpx;
height: 24rpx;
background-color: #fff;
}
.count-down {
font-size: 24rpx;
color: rgba(#fff, 0.9);
}
}
}
// 积分商品价格卡片
.score-price-card {
padding: 20rpx;
background: url($IMG_URL+'/imgs/detail/detail_price_score_bg.png') no-repeat;
background-size: 100% 100%;
width: 750rpx;
.notice {
font-size: 24rpx;
color: #fff;
}
.score-img {
width: 36rpx;
height: 36rpx;
}
.price {
font-size: 36rpx;
color: #fff;
font-weight: bold;
margin: 0 10rpx;
}
.price-bottom-box {
font-size: 24rpx;
color: #fff;
font-weight: 500;
padding-top: 10rpx;
.original-price {
text-decoration: line-through;
}
.line {
margin: 0 20rpx;
display: inline-block;
width: 3rpx;
height: 24rpx;
background-color: #fff;
}
}
}
// 秒杀商品
.seckill-bg {
background: url($IMG_URL+'/imgs/detail/detail_price_seckill_bg.png') no-repeat;
background-size: 100% 100%;
}
.seckilled-bg {
background: url($IMG_URL+'/imgs/detail/detail_price_seckilled_bg.png') no-repeat;
background-size: 100% 100%;
}
.seckill-price-box {
padding: 20rpx;
.unit,
.notice,
.count-down {
font-size: 24rpx;
color: rgba(#fff, 0.9);
}
.notice {
line-height: 40rpx;
border: 1rpx solid rgba(255, 255, 255, 1);
border-radius: 6rpx;
padding: 0 10rpx;
margin-left: 10rpx;
}
.price {
font-size: 36rpx;
color: rgba(#fff, 0.9);
font-weight: bold;
margin: 0 10rpx;
}
.price-bottom-box {
font-size: 24rpx;
color: rgba(#fff, 0.9);
font-weight: 500;
padding-top: 10rpx;
.original-price {
text-decoration: line-through;
}
.express {
.progress-text {
color: rgba(#fff, 0.9);
font-size: 24rpx;
margin-left: 10rpx;
}
}
.line {
margin: 0 20rpx;
display: inline-block;
width: 3rpx;
height: 24rpx;
background-color: #fff;
}
}
}
</style>

@ -0,0 +1,140 @@
<template>
<view class="sh-server-box">
<view class="u-flex u-row-between serve-box" @tap="showModal = true">
<view class="u-flex">
<text class="title">服务</text>
<view class="tip u-flex">
<view class="tip-list u-flex" v-for="(serve, index) in serveList" :key="serve.title">
<view class="u-flex" v-if="index < 3">
<image class="tip-img" :src="serve.image" mode=""></image>
<text>{{ serve.name }}</text>
</view>
</view>
</view>
</view>
<view class="u-iconfont uicon-arrow-right" style="color: #bfbfbf;font-size: 28rpx;"></view>
</view>
<u-popup v-model="showModal" mode="bottom" :closeable="true" close-icon-pos="top-right">
<view class=" server-modal">
<view class="server-modal-box page_box">
<view class="modal-head u-flex u-row-center u-col-center"><text class="head-title">服务保障</text></view>
<view class="modal-content content_box">
<view class="serve-list" v-for="serve in serveList" :key="serve.title">
<view class="title-box u-flex">
<image class="title-tag" :src="serve.image" mode=""></image>
<text class="serve-title">{{ serve.name }}</text>
</view>
<view class="serve-detail">{{ serve.description }}</view>
</view>
</view>
<view class="modal-foot u-flex u-row-center u-col-center"><button class=" u-reset-button serve-btn" @tap="showModal = false">确定</button></view>
</view>
</view>
</u-popup>
</view>
</template>
<script>
/**
* 商品服务卡片
* @property {Array} serveList - 商品服务列表
* @property {Boolean} showModal- 显隐
*/
export default {
components: {},
data() {
return {
showModal: false
};
},
props: {
serveList: {
type: Array,
default: () => []
}
},
computed: {},
methods: {}
};
</script>
<style lang="scss">
.serve-box {
line-height: 82rpx;
background: #fff;
padding: 0 20rpx;
margin: 10rpx 0;
.title {
font-size: 28rpx;
color: #999;
margin-right: 20rpx;
}
.tip {
.tip-list {
font-size: 28rpx;
margin-right: 20rpx;
.tip-img {
width: 30rpx;
height: 30rpx;
margin-right: 10rpx;
}
}
}
}
.server-modal-box {
width: 750rpx;
height: 100%;
border-radius: 30rpx 30rpx 0 0;
background: #fff;
padding: 30rpx;
.serve-btn {
width: 690rpx;
line-height: 80rpx;
background: linear-gradient(90deg, rgba(240, 199, 133, 1), rgba(246, 214, 157, 1));
border-radius: 40rpx;
font-size: 30rpx;
color: rgba(#fff, 0.9);
margin-top: 40rpx;
}
.modal-head {
margin-bottom: 30rpx;
position: relative;
.head-title {
font-size: 32rpx;
font-weight: bold;
}
}
.modal-content {
overflow-y: auto;
.serve-list {
padding-bottom: 40rpx;
.title-tag {
width: 38rpx;
height: 38rpx;
margin-right: 20rpx;
}
.serve-title {
font-size: 28rpx;
font-weight: bold;
}
.serve-detail {
text-align: left;
font-size: 26rpx;
color: #999;
line-height: 42rpx;
padding-left: 58rpx;
margin-top: 20rpx;
}
}
}
}
</style>

@ -0,0 +1,772 @@
<!-- 商品详情 -->
<template>
<view class="detail-wrap ">
<!-- 标题栏 -->
<view class="nav-box">
<view class="state-hack"></view>
<view :style="{ height: navbarHeight + 'px' }">
<view hover-class="back-hover" class="back-box u-m-x-30 u-flex u-row-center u-col-center" @tap="goBack">
<view class="u-iconfont uicon-arrow-left" style="color: #fff;font-size: 28rpx;"></view>
</view>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty v-if="showEmpty" :image="$IMG_URL + '/imgs/empty/empty_goods_null.png'" :tipText="showEmptyText" btnText="返回上一页" @click="$Router.back()"></shopro-empty>
<view class="detail_box" v-else>
<view class="detail-content">
<!-- 轮播 -->
<u-swiper :height="750" borderRadius="0" :list="goodsInfo.images" indicator-pos="bottomRight" mode="number" :interval="3000" @click="onGoodsSwiper"></u-swiper>
<!-- 价格卡片组 -->
<sh-price-card v-if="goodsInfo.id" :detail="goodsInfo" :type="detailType" @change="getActivityRules"></sh-price-card>
<!-- 标题 -->
<view class="u-m-b-10 title-box u-p-20 u-skeleton-fillet">
<view class="goods-title u-m-b-10 u-ellipsis-2">{{ goodsInfo.title }}</view>
<view class="sub-title u-ellipsis-2">{{ goodsInfo.subtitle }}</view>
</view>
<!-- 参与的活动 -->
<sh-activity v-if="goodsInfo.activity_discounts_tags && goodsInfo.activity_discounts_tags.length" :detail="goodsInfo.activity_discounts"></sh-activity>
<!-- 规格选择 -->
<view class="sku-box" @tap="showSku = true" v-if="activityRules.status !== 'waiting' && checkActivity(goodsInfo.activity_type, 'groupon') && goodsInfo.is_sku">
<view class="u-flex u-row-between u-col-center">
<view class="u-flex">
<text class="title">规格</text>
<text class="tip">{{ currentSkuText || '请选择规格' }}</text>
</view>
<text class="u-iconfont uicon-arrow-right" style="color: #bfbfbf;"></text>
</view>
</view>
<!-- 服务 -->
<sh-serve v-if="goodsInfo.service && goodsInfo.service.length" v-model="showServe" :serveList="goodsInfo.service"></sh-serve>
<!-- 优惠券 -->
<sh-coupon
v-if="goodsInfo.coupons && goodsInfo.coupons.length && goodsInfo.activity_type !== 'seckill' && goodsInfo.activity_type !== 'groupon' && detailType !== 'score'"
:couponList="goodsInfo.coupons"
></sh-coupon>
<!-- 拼团人-->
<sh-groupon
v-if="goodsInfo.activity && goodsInfo.activity.type === 'groupon' && goodsInfo.activity.rules.team_card === '1' && detailType !== 'score'"
:grouponData="goodsInfo"
></sh-groupon>
<!-- 拼团玩法 -->
<view
v-if="goodsInfo.activity && Number(goodsInfo.activity.richtext_id) && goodsInfo.activity_type !== 'seckill'"
class="groupon-play u-flex u-row-between u-p-l-20 u-p-r-20"
@tap="jump('/pages/public/richtext', { id: goodsInfo.activity.richtext_id })"
>
<view class="u-flex">
<text class="title">玩法</text>
<view class="description u-ellipsis-1">{{ goodsInfo.activity.richtext_title || '开团/参团·邀请好友·人满发货(不满退款' }}</view>
</view>
<view class="u-iconfont uicon-arrow-right" style="color:#bfbfbf ;font-size: 28rpx;"></view>
</view>
<!-- 选项卡 -->
<view class="tab-box u-flex">
<view class="tab-item u-flex-col u-row-center u-col-center" @tap="onTab(tab.id)" v-for="tab in tabList" :key="tab.id">
<view class="tab-title">
{{ tab.title }}
<text v-if="tab.id == 'tab2'" class="comment-num">({{ commentNum }})</text>
</view>
<text class="tab-line" :class="{ 'line-active': tabCurrent === tab.id }"></text>
</view>
</view>
<view class="tab-detail u-p-20 u-m-b-10">
<!-- 详情富文本 -->
<view class="rich-box " v-if="tabCurrent === 'tab0'"><u-parse :html="goodsInfo.content"></u-parse></view>
<!-- 参数 -->
<view class="goods-size" v-if="tabCurrent === 'tab1'">
<view class="table-box" v-if="goodsInfo.params && goodsInfo.params.length">
<view class="t-tr u-flex" v-for="t in goodsInfo.params" :key="t.title">
<view class="t-head u-flex">{{ t.title }}</view>
<view class="t-detail">{{ t.content }}</view>
</view>
</view>
</view>
<!-- 评价 -->
<view class="goods-comment" v-if="tabCurrent === 'tab2'">
<block v-for="comment in commentList" :key="comment.id"><sh-comment :comment="comment"></sh-comment></block>
<shopro-empty v-show="!commentList.length" marginTop="20rpx" :image="$IMG_URL + '/imgs/empty/comment_empty.png'" tipText="暂无评价~"></shopro-empty>
<view class="more-box u-flex u-row-center u-col-center" v-show="commentNum > 3">
<button class="u-reset-button more-btn u-flex u-row-center u-col-center" @tap="jump('/pages/goods/comment/comment-list', { goodsId: goodsInfo.id })">
查看全部
<view class="u-iconfont uicon-arrow-right" style="color:#d5a65a ;font-size: 28rpx;"></view>
</button>
</view>
</view>
</view>
</view>
<!-- 商品foot -->
<view class="tabbar-foot safe-area-inset-bottom" v-if="goodsInfo.id">
<view class="detail-foot_box safe-area-inset-bottom u-flex u-col-ceter u-row-around">
<!-- foot左侧 -->
<view class="left u-flex">
<!-- 积分foot -->
<view class="tools-item u-flex-col u-row-center u-col-center" @tap="goHome">
<image class="tool-img shopro-selector-circular" src="/static/images/tabbar/tabbar_home1.png" mode=""></image>
<text class="tool-title shopro-selector-rect">首页</text>
</view>
<!-- 非积分foot -->
<block v-if="detailType !== 'score'">
<view class="tools-item u-flex-col u-row-center u-col-center" @tap="onFavorite(goodsInfo.id)">
<image
class="tool-img"
:src="Boolean(goodsInfo.favorite) ? $IMG_URL + '/imgs/detail/detail_favorite_end.png' : $IMG_URL + '/imgs/detail/detail_favorite.png'"
mode=""
></image>
<text class="tool-title">收藏</text>
</view>
<view class="tools-item u-flex-col u-row-center u-col-center" @tap="onShare">
<image class="tool-img" :src="$IMG_URL + '/imgs/share/share.png'" mode=""></image>
<text class="tool-title">分享</text>
</view>
</block>
</view>
<!-- foot右侧 -->
<view class="detail-right">
<!-- 积分按钮 -->
<view class="detail-btn-box" v-if="detailType === 'score'"><button class="u-reset-button score-btn" @tap="goPay">立即兑换</button></view>
<view v-if="detailType !== 'score'">
<!-- 正常按钮 -->
<view class="detail-btn-box u-flex u-row-around" v-if="!goodsInfo.activity_type">
<button class=" u-reset-button tool-btn add-btn" @tap="addCart">加入购物车</button>
<button class=" u-reset-button tool-btn pay-btn" @tap="goPay">立即购买</button>
</view>
<!-- 活动按钮 -->
<view class="detail-btn-box u-row-around" v-if="activityRules.status && goodsInfo.activity_type">
<button class=" u-reset-button seckilled-btn" v-if="activityRules.status !== 'ing'">
<text v-if="activityRules.status == 'waiting'">暂未开始</text>
<text v-if="activityRules.status == 'end'">已结束</text>
</button>
<block v-else>
<!-- 活动中,秒杀 -->
<button class=" u-reset-button seckill-btn" v-if="goodsInfo.activity && goodsInfo.activity.type === 'seckill'" @tap="goSeckill">
立即秒杀
</button>
<!-- 活动中,拼团 -->
<view class="u-flex groupon-btn-box" v-if="goodsInfo.activity && goodsInfo.activity.type === 'groupon'">
<button
class="tool-btn u-reset-button add-btn u-flex-col u-row-center u-col-center"
@tap="payGroupon"
v-if="goodsInfo.activity.rules.is_alone === '1'"
>
<text class="price font-OPPOSANS">¥{{ goodsInfo.price }}</text>
<text class="price-title">单独购买</text>
</button>
<button
class=" tool-btn u-reset-button groupon-btn u-flex-col u-row-center u-col-center"
:style="goodsInfo.activity.rules.is_alone === '0' ? 'width:400rpx' : ''"
@tap="payGroupon('groupon')"
>
<text class="price font-OPPOSANS">¥{{ goodsInfo.groupon_price }}</text>
<text class="price-title">我要开团</text>
</button>
</view>
</block>
</view>
</view>
</view>
</view>
</view>
<!-- 规格弹窗 -->
<shopro-sku
v-if="showSku && goodsInfo.id"
v-model="showSku"
:goodsInfo="goodsInfo"
:activityRules="activityRules"
:buyType="goodsInfo.activity_type == 'seckill' || detailType === 'score' ? 'buy' : buyType"
:grouponBuyType="grouponBuyType"
:goodsType="detailType === 'score' ? 'score' : 'goods'"
@changeType="changeType"
@getSkuText="getSkuText"
></shopro-sku>
<!-- 分享组件 -->
<shopro-share v-model="showShare" :posterInfo="goodsInfo" :posterType="'goods'"></shopro-share>
<!-- 登录 -->
<shopro-auth-modal v-if="authType"></shopro-auth-modal>
</view>
</view>
</template>
<script>
let systemInfo = uni.getSystemInfoSync();
import shActivity from './components/sh-activity.vue';
import shPriceCard from './components/sh-price-card.vue';
import shServe from './components/sh-serve.vue';
import shGroupon from './components/sh-groupon.vue';
import shCoupon from './components/sh-coupon.vue';
import shComment from './components/sh-comment.vue';
import share from '@/shopro/share';
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {
shServe,
shPriceCard,
shGroupon,
shCoupon,
shComment,
shActivity
},
data() {
return {
// navbar
backIconName: 'arrow-left',
showSku: false, //是否显示规格弹窗
currentSkuText: '', //选中规格
detailType: '',
showShare: false,
buyType: 'sku',
grouponBuyType: 'alone', //拼团购买方式。
showEmpty: false,
showEmptyText: '',
showServe: false,
goodsInfo: {},
commentList: [], //商品评价列表
commentNum: 0, //商品评价总数
favorite: false,
activityRules: {},
currentSkuList: [],
confirmGoodsInfo: {},
swiperCurrent: 0, //轮播下标
tabCurrent: 'tab0',
tabList: [
{
id: 'tab0',
title: '商品详情'
},
{
id: 'tab1',
title: '规格参数'
},
{
id: 'tab2',
title: '用户评价'
}
]
};
},
// 是否登录
computed: {
...mapGetters(['isLogin', 'authType']),
navbarHeight() {
// #ifdef APP-PLUS || H5
return 48;
// #endif
// #ifdef MP
let height = systemInfo.platform == 'ios' ? 44 : 48;
return height;
// #endif
}
},
async onLoad() {
let that = this;
this.backIconName = getCurrentPages().length > 1 ? 'arrow-left' : 'home-fill';
const type = this.$Route.query.type;
this.detailType = type;
switch (type) {
case 'score':
await this.getScoreDetail();
break;
default:
await this.getGoodsDetail();
}
},
onUnload() {
share.setShareInfo();
},
methods: {
// goBack
// 微信直播商品跳转详情,需要用小程序原生接口才能返回。
goBack() {
let pageList = getCurrentPages();
if (pageList.length > 1) {
// #ifdef MP-WEIXIN
wx.navigateBack();
// #endif
// #ifndef MP-WEIXIN
uni.navigateBack();
// #endif
} else {
this.$Router.replaceAll('/pages/index/index');
}
},
// 轮播图预览
onGoodsSwiper(e) {
this.$tools.previewImage(this.goodsInfo.images, e);
},
getActivityRules(e) {
if (e) {
this.activityRules = JSON.parse(e);
}
},
// 检测
checkActivity(data, type) {
if (data) {
return !data.includes(type);
}
return true;
},
// 路由跳转
jump(path, parmas) {
this.showShare = false;
this.$Router.push({
path: path,
query: parmas
});
},
// 回到首页
goHome() {
this.$Router.replaceAll('/pages/index/index');
},
// 选项卡
onTab(id) {
this.tabCurrent = id;
},
// 积分详情
getScoreDetail() {
let that = this;
return new Promise((resolve, reject) => {
that.$http('goods.scoreDetail', {
id: that.$Route.query.id
}).then(res => {
if (res.code === 1) {
that.goodsInfo = res.data;
resolve(res.data);
}
if (res.code == 0) {
resolve(false);
that.$u.toast(res.msg);
}
});
});
},
// 商品详情
getGoodsDetail() {
let that = this;
return new Promise((resolve, reject) => {
that.$http(
'goods.detail',
{
id: that.$Route.query.id
},
'',
false
).then(res => {
if (res.code === 1) {
that.showEmpty = false;
that.goodsInfo = res.data;
if (that.goodsInfo.activity_type) {
that.activityRules.status = that.goodsInfo.activity.status_code;
}
share.setShareInfo({
title: that.goodsInfo.title,
desc: that.goodsInfo.subtitle,
image: that.goodsInfo.image,
params: {
page: 2,
pageId: that.$Route.query.id
}
});
that.getCommentList();
resolve(res.data);
} else {
resolve(false);
// that.$u.toast(res.msg);
that.showEmpty = true;
that.showEmptyText = res.msg;
}
});
});
},
// 商品评论
getCommentList() {
let that = this;
that.$http('goods.commentList', {
goods_id: that.goodsInfo.id,
per_page: 3,
type: 'all'
}).then(res => {
if (res.code === 1) {
that.commentList = res.data.data;
that.commentNum = res.data.total;
}
});
},
// 组件返回的type;
changeType(e) {
this.buyType = e;
},
// 组件返回的规格;
getSkuText(e) {
this.currentSkuText = e;
},
// 分享
onShare() {
this.showShare = true;
},
// 加入购物车
addCart() {
if (this.isLogin) {
this.buyType = 'cart';
this.showSku = true;
} else {
this.$store.dispatch('showAuthModal');
}
},
// 立即购买
goPay() {
if (this.isLogin) {
this.buyType = 'buy';
this.showSku = true;
} else {
this.$store.dispatch('showAuthModal');
}
},
// 拼团购买
payGroupon(type) {
if (this.isLogin) {
if (type === 'groupon') {
this.grouponBuyType = 'groupon';
} else {
this.grouponBuyType = 'alone';
}
this.buyType = 'buy';
this.showSku = true;
} else {
this.$store.dispatch('showAuthModal');
}
},
// 立即秒杀
goSeckill() {
!this.isLogin ? this.$store.dispatch('showAuthModal') : (this.showSku = true);
this.buyType = 'buy';
},
// 收藏
onFavorite(goodsId) {
let that = this;
that.$http('goods.favorite', {
goods_id: goodsId
}).then(res => {
if (res.code === 1) {
that.goodsInfo.favorite = res.data;
that.$u.toast(res.msg);
}
});
}
}
};
</script>
<style lang="scss">
// 标题栏
.nav-box {
position: fixed;
width: 100%;
min-height: 140rpx;
z-index: 888;
top: 0;
.back-box {
background-color: rgba(#000, 0.18);
width: 60rpx;
height: 60rpx;
border-radius: 50%;
margin-top: 14rpx;
}
.back-hover {
background-color: rgba(#fff, 0.18);
}
.state-hack {
width: 100%;
height: var(--status-bar-height);
/* #ifdef H5 */
height: 20rpx;
/* #endif */
}
}
// 拼团玩法
.groupon-play {
background: #fff;
line-height: 94rpx;
.title {
font-size: 28rpx;
color: #999;
}
.description {
font-size: 28rpx;
width: 500rpx;
margin-left: 30rpx;
}
}
// 选项卡
.tab-box {
height: 102rpx;
background: #fff;
border-bottom: 1rpx solid rgba(#dfdfdf, 0.8);
margin-top: 10rpx;
.tab-item {
flex: 1;
height: 100%;
position: relative;
font-size: 30rpx;
font-weight: bold;
.tab-line {
width: 123rpx;
height: 4rpx;
left: 50%;
bottom: 0;
transform: translateX(-50%);
background: transparent;
position: absolute;
z-index: 2;
}
.line-active {
background: rgba(168, 112, 13, 1);
}
}
}
// 选项卡内容
.tab-detail {
min-height: 300rpx;
background: #fff;
// 规格参数
.goods-size {
.table-box {
width: 710rpx;
margin: auto;
background: rgba(255, 255, 255, 1);
border: 1rpx solid rgba(223, 223, 223, 1);
.t-tr {
border-bottom: 1rpx solid rgba(223, 223, 223, 1);
&:last-child {
border-bottom: none;
}
.t-head {
font-size: 26rpx;
color: #999;
flex: 1;
padding: 15rpx 20rpx;
height: 100%;
}
.t-detail {
font-size: 26rpx;
border-left: 1rpx solid rgba(223, 223, 223, 1);
flex: 4;
padding: 15rpx 20rpx;
height: 100%;
}
}
}
}
// 富文本
.rich-box {
/deep/ img {
display: block;
}
}
// 评论
.goods-comment {
.more-box {
height: 100rpx;
background: #fff;
.more-btn {
width: 200rpx;
line-height: 60rpx;
border: 1rpx solid rgba(213, 166, 90, 1);
border-radius: 30rpx;
font-size: 26rpx;
font-weight: 400;
color: rgba(168, 112, 13, 1);
padding: 0;
background: #fff;
}
}
}
}
// 规格卡片
.sku-box {
line-height: 82rpx;
background: #fff;
padding: 0 20rpx;
margin: 10rpx 0;
font-size: 28rpx;
.title {
color: #999;
margin-right: 20rpx;
}
}
// 标题卡片
.title-box {
background-color: #fff;
.goods-title {
font-size: 28rpx;
font-weight: 600;
line-height: 42rpx;
}
.sub-title {
color: #a8700d;
font-size: 24rpx;
font-weight: 500;
line-height: 42rpx;
}
}
// 底部工具栏
.tabbar-foot {
min-height: 100rpx;
width: 100%;
}
.detail-foot_box {
min-height: calc(100rpx + env(safe-area-inset-bottom)) ;
border-top: 1rpx solid rgba(238, 238, 238, 1);
background-color: #fff;
width: 100%;
position: fixed;
bottom: 0;
z-index: 999;
.left,
.detail-right {
flex: 1;
}
.tools-item {
flex: 1;
height: 100%;
.tool-img {
width: 46rpx;
height: 46rpx;
}
.tool-title {
font-size: 22rpx;
line-height: 22rpx;
padding-top: 8rpx;
}
}
.detail-btn-box {
flex: 1;
.tool-btn {
font-size: 28rpx;
font-weight: 500;
color: rgba(#fff, 0.9);
width: 210rpx;
min-height: 70rpx;
border-radius: 35rpx;
padding: 0;
margin-right: 20rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.price {
font-size: 24rpx;
line-height: 24rpx;
font-weight: bold;
color: rgba(#fff, 0.9);
}
.price-title {
font-size: 20rpx;
line-height: 20rpx;
font-weight: 500;
color: rgba(#fff, 0.9);
padding-top: 8rpx;
}
}
.add-btn {
box-shadow: 0px 2rpx 5rpx 0px rgba(102, 103, 104, 0.46);
background: linear-gradient(90deg, rgba(103, 104, 105, 1), rgba(82, 82, 82, 1));
}
.pay-btn {
box-shadow: 0px 7rpx 6rpx 0px rgba(229, 138, 0, 0.22);
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
}
.score-btn {
width: 600rpx;
line-height: 80rpx;
background: linear-gradient(90deg, rgba(49, 133, 243, 1), rgba(80, 205, 242, 1));
box-shadow: 0px 7px 6px 0px rgba(80, 205, 242, 0.2);
border-radius: 40rpx;
font-size: 30rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
margin-right: 20rpx;
}
.seckill-btn {
width: 432rpx;
line-height: 70rpx;
background: linear-gradient(93deg, rgba(208, 19, 37, 1), rgba(237, 60, 48, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(#ed3c30, 0.22);
font-size: 28rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
border-radius: 35rpx;
padding: 0;
margin-right: 20rpx;
}
.seckilled-btn {
width: 432rpx;
line-height: 70rpx;
background: rgba(221, 221, 221, 1);
font-size: 28rpx;
font-weight: 500;
color: #999999;
border-radius: 35rpx;
padding: 0;
margin-right: 20rpx;
}
.groupon-btn {
width: 210rpx;
height: 70rpx;
background: linear-gradient(90deg, rgba(254, 131, 42, 1), rgba(255, 102, 0, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(255, 104, 4, 0.22);
border-radius: 35rpx;
}
}
}
</style>

@ -0,0 +1,224 @@
<!-- 商品列表 -->
<template>
<view class="list-box">
<view class="head-box">
<!-- 标题栏 -->
<shopro-navbar>
<view class="u-flex-1 u-flex u-col-center u-m-x-20" slot="content">
<u-search placeholder="请输入关键字" @change="onSearch" @search="onSearch" @clear="clearSearch" v-model="searchVal" :showAction="false" height="60"></u-search>
</view>
</shopro-navbar>
<!-- 筛选栏 -->
<sh-filter @change="onFilter"></sh-filter>
</view>
<view class="u-waterfall u-p-16" v-if="!isEmpty">
<view id="u-left-column" class="u-column">
<view class="goods-item u-m-b-16 u-flex u-row-center u-col-center" v-for="leftGoods in leftList" :key="leftGoods.id">
<shopro-goods-card
:detail="leftGoods"
:type="leftGoods.activity_type"
:image="leftGoods.image"
:title="leftGoods.title"
:subtitle="leftGoods.subtitle"
:price="leftGoods.price"
:originPrice="leftGoods.original_price"
:sales="leftGoods.sales"
:tagTextList="leftGoods.activity_discounts_tags"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: leftGoods.id } })"
></shopro-goods-card>
</view>
</view>
<view id="u-right-column" class="u-column">
<view class="goods-item u-m-b-16 u-flex u-row-center u-col-center" v-for="rightGoods in rightList" :key="rightGoods.id">
<shopro-goods-card
:detail="rightGoods"
:type="rightGoods.activity_type"
:image="rightGoods.image"
:title="rightGoods.title"
:subtitle="rightGoods.subtitle"
:price="rightGoods.price"
:originPrice="rightGoods.original_price"
:sales="rightGoods.sales"
:tagTextList="rightGoods.activity_discounts_tags"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: rightGoods.id } })"
></shopro-goods-card>
</view>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty v-if="isEmpty" :image="$IMG_URL + '/imgs/empty/empty_goods.png'" tipText="暂无该商品,还有更多好货等着你噢~"></shopro-empty>
<!-- 加载更多 -->
<u-loadmore v-show="!isEmpty" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
<!-- 登录弹窗 -->
<shopro-auth-modal></shopro-auth-modal>
</view>
</template>
<script>
import shFilter from './components/sh-filter.vue';
import { mapMutations, mapActions, mapState } from 'vuex';
let systemInfo = uni.getSystemInfoSync();
let historyTag = uni.getStorageSync('searchHistoryArr') ? JSON.parse(uni.getStorageSync('searchHistoryArr')) : [];
export default {
components: {
shFilter
},
data() {
return {
isEmpty: false,
goodsList: [],
searchVal: '',
listParams: {
category_id: 0,
keywords: '',
page: 1
},
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
lastPage: 1,
// 瀑布流 350-330
addTime: 100, //排序间隙时间
leftHeight: 0,
rightHeight: 0,
leftList: [],
rightList: [],
tempList: []
};
},
// 触底加载更多
onReachBottom() {
if (this.listParams.page < this.lastPage) {
this.listParams.page += 1;
this.getGoodsList();
}
},
onLoad() {
if (this.$Route.query.id) {
this.listParams.category_id = this.$Route.query.id;
this.getGoodsList();
} else if (this.$Route.query.hasOwnProperty('keywords')) {
this.listParams.keywords = this.$Route.query.keywords;
this.searchVal = this.$Route.query.keywords;
!this.$Route.query.keywords && this.getGoodsList();
} else {
this.getGoodsList();
}
},
methods: {
// 瀑布流相关
async splitData() {
if (!this.tempList.length) return;
let item = this.tempList[0];
if (!item) return;
// 分左右
if (this.leftHeight < this.rightHeight) {
this.leftHeight += item.activity_discounts_tags.length ? 350 : 330;
this.leftList.push(item);
} else if (this.leftHeight > this.rightHeight) {
this.rightHeight += item.activity_discounts_tags.length ? 350 : 330;
this.rightList.push(item);
} else {
this.leftHeight += item.activity_discounts_tags.length ? 350 : 330;
this.leftList.push(item);
}
// 移除临时列表的第一项,如果临时数组还有数据,继续循环
this.tempList.splice(0, 1);
if (this.tempList.length) {
setTimeout(() => {
this.splitData();
}, this.addTime);
}
},
clear() {
this.leftList = [];
this.rightList = [];
this.leftHeight = 0;
this.rightHeight = 0;
this.tempList = [];
},
onFilter(e) {
this.listParams.order = e;
this.goodsList = [];
this.listParams.page = 1;
this.lastPage = 1;
this.clear();
this.$u.debounce(this.getGoodsList);
},
// 键盘搜索,输入搜索
onSearch() {
this.goodsList = [];
this.listParams.page = 1;
this.lastPage = 1;
this.listParams.keywords = this.searchVal;
this.clear();
this.$u.debounce(this.getGoodsList);
},
// 队列
getArr(list, item) {
let arr = list;
let length = 10; //队列长度
arr.length < length ? arr.unshift(item) : arr.pop();
return arr;
},
// 清除搜索框
clearSearch() {
this.searchVal = '';
this.clear();
},
// 商品列表
getGoodsList() {
let that = this;
that.loadStatus = 'loading';
that.$http('goods.lists', that.listParams, '加载中...').then(res => {
if (this.searchVal && !historyTag.includes(this.searchVal)) {
let searchHistoryArr = JSON.stringify(this.getArr(historyTag, this.searchVal));
uni.setStorageSync('searchHistoryArr', searchHistoryArr);
}
if (res.code === 1) {
that.goodsList = [...that.goodsList, ...res.data.data];
that.isEmpty = !that.goodsList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.listParams.page < res.data.last_page ? 'loadmore' : 'nomore';
that.tempList = res.data.data;
that.splitData();
}
});
}
}
};
</script>
<style lang="scss">
@mixin vue-flex($direction: row) {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: $direction;
/* #endif */
}
.u-waterfall {
@include vue-flex;
flex-direction: row;
align-items: flex-start;
}
.u-column {
@include vue-flex;
flex: 1;
flex-direction: column;
height: auto;
}
.head-box {
position: -webkit-sticky;
position: sticky;
top: 0;
z-index: 998;
background: #fff;
}
</style>

@ -0,0 +1,371 @@
<!-- 购物车 -->
<template>
<view class="page_box">
<!-- 总数 -->
<view class="head_box" v-show="!isEmpty">
<view class="tool-box u-flex u-row-between">
<view class="count-box">
共
<text class="all-num">{{ cartList.length }}</text>
件商品
</view>
<button class="u-reset-button set-btn" @tap="isTool = !isTool">{{ isTool ? '完成' : '编辑' }}</button>
</view>
</view>
<view class="content_box">
<!-- 列表 -->
<u-checkbox-group @change="checkboxGroupChange" activeColor="#e9b461" v-if="!isEmpty">
<view class="collect-list u-flex u-row-left u-col-center" v-for="(g, index) in cartList" :key="index">
<u-checkbox class="u-p-l-10" :name="g.goods_id" shape="circle" v-model="g.checked">
<view style="height: 160rpx"></view>
</u-checkbox>
<view class="goods-wrap">
<view class="lose-box"
v-if="g.cart_type === 'invalid' || (g.cart_type === 'activity' && !isActivityPay)">
<text v-if="g.cart_type === 'invalid'" class="iconfont icon-yishixiao"></text>
<view v-if="g.cart_type === 'activity' && !isActivityPay"
class="invalid-tips u-flex u-row-center u-col-center">活动商品,仅支持单独购买</view>
</view>
<shopro-mini-card :image="g.goods.image" :title="g.goods.title"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: g.goods.id } })">
<template #describe>
<view class="order-sku u-ellipsis-1">
<text
class="order-num">{{ g.sku_price && g.sku_price.goods_sku_text ? g.sku_price.goods_sku_text : '' }}</text>
</view>
</template>
<template #cardBottom>
<view class="order-price-box u-flex u-row-between" @tap.stop>
<text
class="order-price font-OPPOSANS">¥{{ g.sku_price ? g.sku_price.price : 0 }}</text>
<u-number-box :value="g.goods_num" :long-press="false" :min="0" :step="1"
:index="index" :max="!g.sku_price ? 0: (g.sku_price.stock > 999 ? 999 : g.sku_price.stock)" @min="onMin(g)"
@minus="changeNum($event, g)" @plus="changeNum($event, g)"
@change="changeNum($event, g)">
</u-number-box>
</view>
</template>
</shopro-mini-card>
</view>
</view>
</u-checkbox-group>
<!-- 数据为空 -->
<shopro-empty v-if="isEmpty" :image="$IMG_URL + '/imgs/empty/empty_cart.png'" tipText="购物车空空如也,快去逛逛吧~">
</shopro-empty>
</view>
<!-- 底部按钮 -->
<view class="foot_box " v-show="!isEmpty">
<view class="tools-box u-flex u-row-between">
<u-checkbox @change="onAllSel" activeColor="#e9b461" shape="circle" :value="allSelected">
全选({{ totalCount.totalNum }})</u-checkbox>
<view class="u-flex">
<view class="price font-OPPOSANS" v-show="!isTool">¥{{ totalCount.totalPrice.toFixed(2) }}</view>
<button class="u-reset-button pay-btn" :disabled="!isSel" v-show="!isTool" @tap="onPay">结算</button>
<button class="u-reset-button del-btn" v-show="isTool" @tap="goodsDelete">删除</button>
</view>
</view>
</view>
<!-- <shopro-tabbar></shopro-tabbar> -->
</view>
</template>
<script>
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
let timer = null;
export default {
components: {},
data() {
return {
maxStep: 999,
isTool: false
};
},
computed: {
...mapGetters(['totalCount', 'isSel', 'isActivityPay', 'cartList', 'allSelected', 'authType', 'isLogin']),
isEmpty() {
return !this.cartList.length;
}
},
onShow() {
this.isLogin && this.getCartList();
},
onHide() {
this.isTool = false;
},
methods: {
...mapActions(['getCartList', 'changeCartList']),
// 到达最小值
onMin(g) {
uni.showModal({
title: '删除提示',
confirmColor: '#f0c785',
content: `是否确认从购物车中删除此商品?`,
success: res => {
res.confirm && this.changeCartList({ ids: [g.id], art: 'delete' });
}
});
},
// 更改商品数
async changeNum(e, g) {
uni.showLoading({
mask: true
});
e.value > 0 && this.changeCartList({ ids: [g.id], goodsNum: e.value, art: 'change' });
uni.hideLoading();
await this.getCartList();
},
// 单选
checkboxGroupChange(e) {
this.$store.commit('checkCartList');
},
// 路由跳转
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 全选
onAllSel() {
let that = this;
that.$store.commit('changeAllSellect'); //按钮切换全选。
that.$store.commit('getAllSellectCartList', that.allSelected); //列表全选
},
// 结算
onPay() {
let that = this;
let { cartList } = this;
if (this.isSel) {
let confirmcartList = [];
let isActivity = false;
for (let item of this.cartList) {
if (item.checked) {
if (item.cart_type === 'invalid') {
this.$u.toast('商品已失效');
return false;
}
if (item.cart_type === 'activity') {
isActivity = true;
}
confirmcartList.push({
goods_id: item.goods_id,
sku_price_id: item.sku_price_id,
goods_price: item.sku_price ? item.sku_price.price : 0,
goods_num: item.goods_num
});
}
}
if (confirmcartList.length > 1 && isActivity) {
this.$u.toast('活动商品只能单独购买');
return false;
}
that.jump('/pages/order/confirm', { goodsList: confirmcartList, from: 'cart' });
}
},
// 删除
goodsDelete() {
let that = this;
let { cartList } = this;
let selectedIdsArray = [];
cartList.map(item => {
if (item.checked) {
selectedIdsArray.push(item.id);
}
});
this.changeCartList({ ids: selectedIdsArray, art: 'delete' });
}
}
};
</script>
<style lang="scss">
// 总计商品
.head_box {
.tool-box {
height: 90rpx;
padding: 0 30rpx;
background: #f7f5f6;
.count-box {
font-size: 26rpx;
color: #999;
.all-num {
color: #a8700d;
}
}
.set-btn {
background: none;
font-size: 26rpx;
color: #a8700d;
}
}
}
// 空白页
.empty-box {
flex: 1;
width: 100%;
height: 100%;
}
// 购物车项
.collect-list {
background: #fff;
width: 750rpx;
margin-bottom: 20rpx;
padding: 20rpx 10rpx;
// 商品卡片包裹
.goods-wrap {
position: relative;
.order-sku {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
width: 440rpx;
margin-bottom: 20rpx;
.order-num {
margin-right: 10rpx;
}
}
.order-price-box {
.status-btn {
height: 32rpx;
border: 1rpx solid rgba(207, 169, 114, 1);
border-radius: 15rpx;
font-size: 20rpx;
font-weight: 400;
color: rgba(168, 112, 13, 1);
padding: 0 10rpx;
margin-left: 20rpx;
background: rgba(233, 183, 102, 0.16);
}
.order-price {
font-size: 26rpx;
font-weight: 600;
color: #ff0000;
}
}
.lose-box {
position: absolute;
z-index: 10;
width: 100%;
height: 100%;
background-color: rgba(#fff, 0.8);
.icon-yishixiao {
position: absolute;
bottom: 0rpx;
right: 80rpx;
font-size: 140rpx;
line-height: 140rpx;
color: #dbdbdb;
transform: rotate(-30deg);
}
.invalid-tips {
position: absolute;
top: 0;
right: 0;
left: 0;
bottom: 0;
margin: auto;
width: 400rpx;
height: 60rpx;
border-radius: 30rpx;
color: #fff;
background-color: rgba(#000, 0.35);
}
}
}
.tag-box {
.tag1 {
line-height: 36rpx;
padding: 0 8rpx;
font-size: 18rpx;
color: rgba(#fff, 0.9);
background: #f39800;
display: inline-block;
box-sizing: border-box;
}
.tag2 {
line-height: 34rpx;
padding: 0 8rpx;
font-size: 18rpx;
color: rgba(#f39800, 0.9);
background: #fff;
border-top: 1rpx solid #f39800;
border-right: 1rpx solid #f39800;
border-bottom: 1rpx solid #f39800;
display: inline-block;
box-sizing: border-box;
}
}
.price-box {
width: 420rpx;
.price {
color: #e1212b;
}
}
}
.tools-box {
height: 100rpx;
width: 750rpx;
padding: 0 20rpx;
background: #fff;
.check-all {
font-size: 26rpx;
.check-all-radio {
transform: scale(0.7);
color: #e9b564;
}
}
.price {
color: #ff0000;
font-size: 500;
margin-right: 30rpx;
}
.pay-btn {
width: 200rpx;
line-height: 70rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(229, 138, 0, 0.22);
border-radius: 35rpx;
padding: 0;
color: rgba(#fff, 0.9);
}
.del-btn {
width: 200rpx;
line-height: 70rpx;
background: linear-gradient(90deg, rgba(244, 71, 57, 1) 0%, rgba(255, 102, 0, 1) 100%);
border-radius: 35rpx;
padding: 0;
color: rgba(#fff, 0.9);
}
}
</style>

@ -0,0 +1,73 @@
<!-- 分类 -->
<template>
<view class="category-box">
<!-- 三级分类 -->
<three-catgory :categoryStyleId="categoryStyleId" v-if="categoryType === 4"></three-catgory>
<!-- 二级分类 -->
<two-catgory :categoryStyleId="categoryStyleId" v-if="categoryType === 3"></two-catgory>
<!-- 一级分类-->
<one-catgory :categoryStyleId="categoryStyleId" v-if="categoryType === 2"></one-catgory>
<!--直接购买,点餐 -->
<takeout-catgory :categoryStyleId="categoryStyleId" v-if="categoryType === 1"></takeout-catgory>
<!-- 登录提示 -->
<shopro-auth-modal v-if="authType"></shopro-auth-modal>
<!-- <shopro-tabbar></shopro-tabbar> -->
</view>
</template>
<script>
import takeoutCatgory from './category/takeout-catgory.vue';
import threeCatgory from './category/three-catgory.vue';
import twoCatgory from './category/two-catgory.vue';
import oneCatgory from './category/one-catgory.vue';
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {
takeoutCatgory,
threeCatgory,
twoCatgory,
oneCatgory
},
data() {
return {
categoryType: 0, //1:快速购买,2:一级分类,3:二级分类,4:三级分类
categoryStyleId: 0 //分类Id
};
},
computed: {
...mapGetters(['authType'])
},
onLoad() {
this.getCategory();
},
methods: {
/**
* 获取分类数据
* type4:三级分类, type3:二级分类 ,type2:一级分类,type1:快速购买
*/
getCategory() {
this.$http('category.info', {
id: this.$Route.query.id ? this.$Route.query.id : 0
}).then(res => {
if (res.code === 1) {
if (res.data?.type) {
this.categoryType = Number(res.data.type);
this.categoryStyleId = Number(res.data.id);
uni.setNavigationBarTitle({
title: res.data?.name
});
}
}
});
}
}
};
</script>
<style lang="scss">
.category-box {
height: 100%;
flex: 1;
overflow: hidden;
}
</style>

@ -0,0 +1,265 @@
<template>
<view class="content_box">
<view class="u-flex u-col-center wrapper-box">
<view class="scroll-box" style="background-color: #F6F6F6;">
<scroll-view class="left u-flex-col u-col-center" enable-back-to-top scroll-y>
<view class="type-list u-ellipsis-1" :class="[{ 'list-active': listId == index }]" v-for="(item, index) in categoryData" :key="index" @tap="onType(index)">
<view class="line" :class="[{ 'line-active': listId == index }]"></view>
{{ item.name }}
</view>
<view class="hack-tabbar"></view>
</scroll-view>
</view>
<view style="height: 100%;width: 100%;">
<scroll-view @scrolltolower="loadMore" scroll-y class="scroll-box" enable-back-to-top scroll-with-animation>
<view class="right" v-if="categoryData.length">
<image class="type-img" v-if="categoryData[listId].image" :src="categoryData[listId].image" mode="aspectFill"></image>
<view class="item-list">
<view class="item-box u-flex">
<view
class="u-flex-col u-col-center goods-item"
@tap="jump('/pages/goods/detail', { id: goods.id })"
v-for="(goods, index1) in goodsList"
:key="goods.id"
>
<image class="item-img" lazy-load :src="goods.image" mode="aspectFill"></image>
<text class="item-title u-ellipsis-1 ">{{ goods.title }}</text>
<view class="item-price">{{ goods.price }}</view>
</view>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty
v-if="isEmpty"
marginTop="200rpx"
:image="$IMG_URL + '/imgs/empty/empty_goods.png'"
tipText="暂无该商品,还有更多好货等着你噢~"
></shopro-empty>
<!-- 加载更多 -->
<u-loadmore v-if="!isEmpty" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
<view class="hack-tabbar"></view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {},
data() {
return {
listId: 0,
categoryData: {},
categoryID: 0, //分类id
isEmpty: false,
currentPage: 1,
lastPage: 1,
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
goodsList: [] //商品数据
};
},
props: {
categoryStyleId: {
//分类样式ID
type: Number,
default: 0
}
},
computed: {},
created() {
console.log('%c当前分类:一级分类', 'color:green;background:yellow');
this.getCategory();
},
methods: {
// 获取分类
getCategory() {
this.$http('category.detail', {
id: this.categoryStyleId
}).then(res => {
if (res.code === 1) {
this.categoryData = res.data.children;
this.categoryID = res.data.children[0].id;
this.getGoodsList();
}
});
},
// 获取分类商品
getGoodsList() {
let that = this;
that.$http('goods.lists', {
category_id: that.categoryID,
page: that.currentPage
}, '加载中...').then(res => {
if (res.code === 1) {
that.goodsList = [...that.goodsList, ...res.data.data];
that.isEmpty = !that.goodsList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
},
// 商品底部
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getGoodsList();
}
},
onType(index) {
this.listId = index;
this.categoryID = this.categoryData[index].id;
this.goodsList = [];
this.currentPage = 1;
this.getGoodsList();
},
// 路由跳转
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
}
}
};
</script>
<style lang="scss">
.hack-tabbar {
height: calc(100rpx + env(safe-area-inset-bottom) / 2);
width: 100%;
}
.content_box {
margin-top: 1upx;
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
}
.wrapper-box {
flex: 1;
margin-top: 1upx;
height: 100%;
}
.scroll-box {
height: 100%;
flex: 1;
background: #fff;
}
.left {
width: 200upx;
height: 100%;
flex: 1;
.list-active {
background: #fff;
color: #333333 !important;
font-weight: bold !important;
}
.line-active {
background: #e6b873;
}
.type-list {
height: 84upx;
position: relative;
width: 200rpx;
padding-left: 16rpx;
line-height: 84rpx;
font-size: 28upx;
font-weight: 400;
color: rgba(102, 102, 102, 1);
.line {
width: 10upx;
height: 100%;
position: absolute;
left: 0;
}
}
}
.right {
padding: 0 30upx;
flex: 1;
height: 100%;
.type-img {
width: 505rpx;
height: 150rpx;
background: #ccc;
margin: 20rpx 0;
border-radius: 10rpx;
}
.item-list {
.type-box {
height: 84rpx;
.type-title {
font-size: 28rpx;
font-weight: bold;
}
.more {
font-size: 26rpx;
color: #999;
}
}
.item-box {
flex-wrap: wrap;
width: 504rpx;
.goods-item {
margin-right: 12rpx;
margin-bottom: 12rpx;
background: #ffffff;
box-shadow: 0px 0px 20rpx 4rpx rgba(199, 199, 199, 0.22);
border-radius: 10rpx;
&:nth-child(2n) {
margin-right: 0;
}
.item-img {
width: 245rpx;
height: 246rpx;
border-radius: 10rpx 10rpx 0px 0px;
background: #f5f5f5;
}
.item-title {
font-size: 24rpx;
line-height: 24rpx;
margin-top: 10rpx;
width: 200rpx;
text-align: left;
margin: 20rpx;
}
.item-price {
font-size: 28rpx;
font-weight: 500;
color: #ff3000;
text-align: left;
width: 200rpx;
margin: 0 20rpx 20rpx;
&::before {
content: '¥';
font-size: 22rpx;
}
}
}
}
}
}
</style>

@ -0,0 +1,603 @@
<template>
<view class="catgory-wrap">
<view class="u-flex wrapper-box">
<!-- 左侧分类列表 -->
<scroll-view scroll-y scroll-with-animation class="u-tab-view menu-scroll-view"
:scroll-top="scrollLeftTop">
<view v-for="(item, index) in tabbarList" :key="index" class="u-tab-item u-ellipsis-1"
:class="[currentTab == index ? 'u-tab-item-active' : '']" :data-current="index"
@tap.stop="swichMenu(index)">
<text class="menu-name">{{ item.name }}</text>
</view>
<view class="hack-tabbar"></view>
</scroll-view>
<!-- 右侧商品列表 -->
<view class="right-wrap ">
<scroll-view scroll-y class="right-box menu-scroll-view" @scrolltolower="loadMore"
v-if="tabbarList.length">
<view class="u-flex u-row-center" v-if="tabbarList[currentTab].image">
<image class="category-img" :src="tabbarList[currentTab].image" mode="aspectFill"></image>
</view>
<view class="category-title">{{ tabbarList[currentTab].name }}</view>
<view class="sh-big-card-wrap u-m-20 " v-for="(item, index) in goodsList" :key="item.id">
<view class="big-goods-card" @tap="jump('/pages/goods/detail', { id: item.id })">
<view class="img-wrap u-m-b-20">
<image class="goods-img" :src="item.image" mode="aspectFill"></image>
<image class="goods-tag" v-if="item.is_hot && !item.activity"
:src="$IMG_URL + '/goods/goods_hot_tag.png'" mode=""></image>
</view>
<view class="goods-title u-p-x-20 u-m-b-10 u-ellipsis-2">
<view v-if="item.activity_type" class=" sm cu-tag radius title-tag u-m-r-10"
:style="{ backgroundColor: typeMap[item.activity_type].tagBg, color: '#fff' }">
{{ typeMap[item.activity_type].text }}
</view>
{{ item.title }}
</view>
<view class="goods-subtitle u-p-x-20 u-m-b-10 u-ellipsis-1">{{ item.subtitle }}</view>
<!-- 满减,满折 -->
<view class="u-p-x-20" v-if="item.activity_discounts_tags.length">
<view class="tag-box u-m-b-10 u-m-r-10"
v-for="(tag, index1) in item.activity_discounts_tags" :key="index1">{{ tag }}</view>
</view>
<view class="goods-progress u-p-x-20 u-flex u-m-b-10">
<view style="width:230rpx;">
<u-line-progress height="16" :show-percent="false" :percent="Number(item.percent)"
active-color="#e6b873"></u-line-progress>
</view>
<view class="googs-send-num u-m-l-20">已售{{ item.sales }}件</view>
</view>
<view class="u-flex u-row-between u-p-x-20">
<view class="price-box u-flex font-OPPOSANS">
<view class="price">{{ item.price }}</view>
<view class="origin-price u-m-l-10">¥{{ item.original_price }}</view>
</view>
<view class="cart-box">
<!-- 单规格 -->
<view class="" v-if="!item.activity">
<view v-if="!item.is_sku">
<button v-if="!isCart(item.id)"
:style="item.activity_type ? typeMap[item.activity_type].btnBg : ''"
@tap.stop="addCart(item.sku_price[0])" class="u-reset-button cart-btn">
加入购物车
</button>
<view class="num-step" @tap.stop v-else>
<u-number-box :value="checkCart[item.id].num" :long-press="false"
:min="0" :max="maxStep" :step="1" :index="index"
@min="onMin(item.id)" @plus="plus($event, item.sku_price[0])"
@change="onChangeNum($event, item.sku_price[0])"></u-number-box>
</view>
</view>
<!-- 多规格 -->
<button class="u-reset-button item-btn sel-sku "
:style="item.activity_type ? typeMap[item.activity_type].btnBg : ''"
@tap.stop="selSku(item)" v-else>
选规格
</button>
</view>
<button class="u-reset-button item-btn sel-sku"
:style="item.activity_type ? typeMap[item.activity_type].btnBg : ''"
v-else>立即抢购</button>
</view>
</view>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty v-if="isEmpty" marginTop="200rpx" :image="$IMG_URL + '/imgs/empty/empty_goods.png'"
tipText="暂无该商品,还有更多好货等着你噢~"></shopro-empty>
<!-- 加载更多 -->
<u-loadmore v-if="goodsList.length" height="80rpx" :status="loadStatus" icon-type="flower"
color="#ccc" />
<view class="hack-tabbar"></view>
</scroll-view>
</view>
</view>
<!-- 规格弹窗 -->
<shopro-sku v-if="showSku && goodsInfo.id" v-model="showSku" :goodsInfo="goodsInfo" :buyType="'cart'">
</shopro-sku>
</view>
</template>
<script>
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {},
data() {
return {
showSku: false, //是否显示规格弹窗
isEmpty: false,
currentTab: 0,
categoryID: 0, //分类id
scrollLeftTop: 0, //左边滚动距离
menuHeight: 0, // 左边菜单的高度
menuItemHeight: 0, // 左边菜单item的高度
tabbarList: [], //左侧分类列表
goodsList: [], //商品列表
goodsInfo: {}, //点击商品详情
numberDisabled: false, //购物车计数器
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1,
maxStep: 999,
typeMap: {
seckill: {
text: '秒杀',
tagBg: '#FF5854',
goodsBg: '/imgs/tag/seckill_x_bg.png',
btnText: '去抢购',
btnBg: 'background: linear-gradient(90deg, #D01325, #ED3C30);'
},
groupon: {
text: '拼团',
tagBg: '#FE832A',
goodsBg: '/imgs/tag/groupon_x_bg.png',
btnText: '马上拼',
btnBg: 'background: linear-gradient(90deg, #FF6600 0%, #FE832A 100%);'
}
}
};
},
props: {
categoryStyleId: {
type: Number,
default: 0
}
},
computed: {
...mapGetters(['totalCount', 'isSel', 'cartNum', 'cartList', 'isLogin']),
// 购物车检测
checkCart() {
let obj = {};
this.cartList.forEach(item => {
obj[item.goods_id] = {
num: item.goods_num,
cartOrderId: item.id
};
});
return obj;
}
},
async created() {
console.log('%c当前分类:快速购买', 'color:green;background:yellow');
await this.getCategory();
let res = await this.getGoodsList();
},
methods: {
...mapActions(['getCartList', 'changeCartList', 'addCartGoods']),
// 跳转详情
toGoodDetail(id) {
this.$Router.push({ path: '/pages/goods/detail', query: { id: id } });
},
// 百分比
getProgress(sales, stock) {
let unit = '';
if (stock + sales > 0) {
let num = (sales / (sales + stock)) * 100;
unit = num.toFixed(2) + '%';
} else {
unit = '0%';
}
return unit;
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getGoodsList();
}
},
// 获取分类
async getCategory() {
const tabbarData = await this.$http('category.detail', {
id: this.categoryStyleId
});
this.tabbarList = tabbarData.data.children;
this.categoryID = this.tabbarList[0]?.id;
},
// 获取分类商品
getGoodsList() {
let that = this;
that.loadStatus = 'loading';
that.$http(
'goods.lists', {
category_id: that.categoryID,
page: that.currentPage
},
'加载中...'
)
.then(res => {
if (res.code === 1) {
that.goodsList = [...that.goodsList, ...res.data.data];
that.goodsList.map(item => {
item.percent = item.stock + item.sales > 0 ? ((item.sales / (item.sales + item
.stock)) * 100).toFixed(2) : 0;
});
that.isEmpty = !that.goodsList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
})
.then(() => {
that.isLogin && that.getCartList();
});
},
// 点击左边的栏目切换
async swichMenu(index) {
if (index == this.currentTab) return;
this.currentTab = index;
this.categoryID = this.tabbarList[index].id;
this.currentPage = 1;
this.lastPage = 1;
this.goodsList = [];
await this.getGoodsList();
// 如果为0,意味着尚未初始化
if (this.menuHeight == 0 || this.menuItemHeight == 0) {
await this.getElRect('menu-scroll-view', 'menuHeight');
await this.getElRect('u-tab-item', 'menuItemHeight');
}
// 将菜单菜单活动item垂直居中
this.scrollTop = index * this.menuItemHeight + this.menuItemHeight / 2 - this.menuHeight / 2;
},
// 获取一个目标元素的高度
getElRect(elClass, dataVal) {
new Promise((resolve, reject) => {
const query = uni.createSelectorQuery().in(this);
query
.select('.' + elClass)
.fields({ size: true }, res => {
// 如果节点尚未生成,res值为null,循环调用执行
if (!res) {
setTimeout(() => {
this.getElRect(elClass);
}, 10);
return;
}
this[dataVal] = res.height;
})
.exec();
});
},
// 加入购物车
addCart(goods) {
let obj = {
goods_id: goods.goods_id,
goods_num: 1,
sku_price_id: goods.id,
goods_price: goods.price
};
let confirmGoodsList = {
list: [obj],
from: 'goods'
};
this.addCartGoods(confirmGoodsList).then(res => {
if (res.code === 1) {
this.$u.toast(res.msg);
}
});
},
// 检测商品在购物车中的下标
checkGoodsIndex(id) {
let cIndex = 0;
this.cartList.forEach((item, index) => {
if (id == item.goods_id) {
cIndex = index;
}
});
return cIndex;
},
// 到达最小值
onMin(goodsId) {
const that = this;
let cartGoodId = 0;
cartGoodId = this.cartList.filter(item => item.goods_id === goodsId)[0].id;
uni.showModal({
title: '删除提示',
confirmColor: '#f0c785',
content: `是否确认从购物车中删除此商品?`,
success: res => {
res.confirm && this.changeCartList({ ids: [cartGoodId], art: 'delete' });
}
});
},
// 增加
plus(e, sku) {
if (e.value >= sku.stock) {
this.maxStep = sku.stock > 999 ? 999 : sku.stock;
this.$u.toast('库存不足');
return;
}
if (this.detail.activity_type === 'seckill' || this.detail.activity_type === 'groupon') {
let rules = this.detail.activity.rules;
if (rules.limit_buy != 0 && e.value >= rules.limit_buy) {
this.maxStep = rules.limit_buy;
this.$u.toast('本次活动最多购买' + rules.limit_buy + '件');
return;
}
}
},
// 更改商品数
// 更改商品数
async onChangeNum(e, sku) {
let gIndex = this.checkGoodsIndex(sku.goods_id);
if (e.value != this.checkCart[sku.goods_id].num) {
uni.showLoading({
mask: true
});
this.$set(this.cartList[gIndex], 'goods_num', +e.value);
await this.changeCartList({
ids: [this.checkCart[sku.goods_id].cartOrderId],
goodsNum: +e.value,
art: 'change'
});
await uni.hideLoading();
}
},
// 检测是否为购物车商品
isCart(id) {
let goodsId = id + '';
return Object.keys(this.checkCart).includes(goodsId);
},
// 添加购物车
async selSku(info) {
this.goodsInfo = {};
this.getGoodsDetail(info.id);
this.showSku = true;
},
// 商品详情
getGoodsDetail(id) {
let that = this;
that.$http('goods.detail', {
id: id
}).then(res => {
if (res.code === 1) {
that.goodsInfo = res.data;
}
});
},
// 路由跳转
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
}
}
};
</script>
<style lang="scss" scoped>
.hack-tabbar {
height: calc(100rpx + env(safe-area-inset-bottom) / 2);
width: 100%;
}
// 最外层结构包裹
.catgory-wrap {
height: calc(100vh);
/* #ifdef H5 */
height: calc(100vh - var(--window-top));
/* #endif */
display: flex;
flex-direction: column;
}
.wrapper-box {
flex: 1;
display: flex;
overflow: hidden;
// 左侧menu
.u-tab-view {
width: 200rpx;
height: 100%;
.u-tab-item {
height: 84rpx;
background: #f6f6f6;
width: 200rpx;
padding-left: 16rpx;
font-size: 26rpx;
color: #444;
font-weight: 400;
line-height: 84rpx;
}
.u-tab-item-active {
position: relative;
color: #000;
font-size: 30rpx;
font-weight: bold;
background: #fff;
}
.u-tab-item-active::before {
content: '';
position: absolute;
border-left: 4px solid #e6b873;
height: 32rpx;
left: 0;
top: 26rpx;
}
}
}
// 右侧商品滚动
.right-wrap {
background-color: #fff;
height: 100%;
width: 100%;
.category-img {
width: 505rpx;
height: 150rpx;
background: #ccc;
margin: 20rpx auto;
border-radius: 10rpx;
}
.category-title {
font-size: 30rpx;
font-weight: bold;
color: #000;
margin: 20rpx;
}
.right-box {
height: 100%;
}
}
.big-goods-card {
background-color: #fff;
border-radius: 20rpx;
width: 100%;
box-shadow: 0 0 8rpx 2rpx rgba(199, 199, 199, 0.3);
padding-bottom: 20rpx;
.img-wrap {
position: relative;
overflow: hidden;
height: 256rpx;
width: 100%;
.goods-img {
width: 100%;
height: 256rpx;
border-radius: 20rpx 20rpx 0 0;
background: #f5f5f5;
}
.goods-tag {
position: absolute;
top: 16rpx;
left: 16rpx;
width: 88rpx;
height: 88rpx;
}
}
.goods-title {
font-size: 30rpx;
font-weight: bold;
color: #333333;
line-height: 36rpx;
padding-top: 6rpx;
.title-tag {
transform: scale(0.9);
position: relative;
top: -6rpx;
}
}
.goods-subtitle {
font-size: 24rpx;
font-weight: 400;
color: #999999;
line-height: 36rpx;
width: 480rpx;
}
.tag-box {
border: 1rpx solid #ff0000;
display: inline-block;
font-size: 20rpx;
line-height: 30rpx;
padding: 0 10rpx;
color: #ff0000;
border-radius: 8rpx;
}
.goods-progress {
.cu-progress {
width: 200rpx;
height: 20rpx;
background: rgba(#ff6361, 0.2);
overflow: visible;
position: relative;
.progress--ing {
background: #ff6361;
border-radius: 20rpx;
}
.round-tag {
position: absolute;
bottom: 0;
width: 20rpx;
height: 28rpx;
margin-left: -10rpx;
.round-tag-img {
width: 20rpx;
height: 28rpx;
}
}
}
.googs-send-num {
font-size: 24rpx;
font-weight: 400;
color: #bebebe;
}
}
.price-box {
vertical-align: text-bottom;
.price {
font-size: 36rpx;
color: #ff0000;
font-weight: 600;
&::before {
content: '¥';
font-size: 28rpx;
}
vertical-align: text-bottom;
}
.origin-price {
vertical-align: text-bottom;
font-size: 26rpx;
font-weight: 400;
text-decoration: line-through;
color: #c4c4c4;
}
}
.cart-box {
.cart-btn,
.sel-sku {
width: 172rpx;
line-height: 58rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(229, 138, 0, 0.22);
border-radius: 30rpx;
padding: 0;
font-size: 26rpx;
font-weight: 500;
color: #ffffff;
}
}
}
</style>

@ -0,0 +1,209 @@
<template>
<view class="content_box">
<view class="u-flex u-col-center wrapper-box">
<view class="scroll-box" style="background-color: #F6F6F6;">
<scroll-view class="left u-flex-col u-col-center" enable-back-to-top scroll-y>
<view class="type-list u-ellipsis-1" :class="[{ 'list-active': listId == index }]" v-for="(item, index) in categoryData" :key="index" @tap="onType(index)">
<view class="line" :class="[{ 'line-active': listId == index }]"></view>
{{ item.name }}
</view>
<view class="hack-tabbar"></view>
</scroll-view>
</view>
<view style="height: 100%;width: 100%;">
<scroll-view scroll-y class="scroll-box" enable-back-to-top scroll-with-animation>
<view class="right" v-if="categoryData.length">
<image class="type-img" v-if="categoryData[listId].image" :src="categoryData[listId].image" mode="aspectFill"></image>
<view class="item-list" v-for="(list, index1) in categoryData[listId].children" :key="index1">
<view class="type-box u-flex u-row-between u-col-center">
<text class="type-title">{{ list.name }}</text>
<view class="more u-flex u-col-center" @tap="jump('/pages/goods/list', { id: list.id })">
<text>查看更多</text>
<view class="u-iconfont uicon-arrow-right" style="color: #999;font-size: 28rpx;"></view>
</view>
</view>
<view class="item-box u-flex">
<view class="u-flex-col goods-item" @tap="jump('/pages/goods/list', { id: mlist.id })" v-for="(mlist, index2) in list.children" :key="index2">
<image class="item-img" lazy-load :src="mlist.image" mode="aspectFill"></image>
<view class="item-title u-ellipsis-1 ">{{ mlist.name }}</view>
</view>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty
v-show="!categoryData[listId].children.length"
:image="$IMG_URL + '/imgs/empty/empty_goods.png'"
tipText="暂无该商品,还有更多好货等着你噢~"
></shopro-empty>
<view class="hack-tabbar"></view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {},
data() {
return {
listId: 0,
categoryData: {}
};
},
computed: {},
props: {
categoryStyleId: {
type: Number,
default: 0
}
},
async created() {
console.log('%c当前分类:三级分类', 'color:green;background:yellow');
await this.getCategory();
},
methods: {
getCategory() {
this.$http('category.detail', {
id: this.categoryStyleId
}).then(res => {
if (res.code === 1) {
this.categoryData = res.data.children;
}
});
},
onType(id) {
this.listId = id;
},
// 路由跳转
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
}
}
};
</script>
<style lang="scss">
.hack-tabbar {
height: calc(100rpx + env(safe-area-inset-bottom) / 2);
width: 100%;
}
.content_box {
margin-top: 1upx;
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
}
.wrapper-box {
flex: 1;
margin-top: 1upx;
height: 100%;
}
.scroll-box {
height: 100%;
flex: 1;
background: #fff;
}
.left {
width: 200upx;
height: 100%;
flex: 1;
.list-active {
background: #fff;
color: #333333 !important;
font-weight: bold !important;
}
.line-active {
background: #e6b873;
}
.type-list {
height: 84upx;
position: relative;
width: 200rpx;
padding-left: 16rpx;
line-height: 84rpx;
font-size: 28upx;
font-weight: 400;
color: rgba(102, 102, 102, 1);
.line {
width: 10upx;
height: 100%;
position: absolute;
left: 0;
}
}
}
.right {
padding: 0 30upx;
flex: 1;
height: 100%;
.type-img {
width: 505rpx;
height: 150rpx;
background: #ccc;
border-radius: 10rpx;
margin-top: 30rpx;
}
.item-list {
.type-box {
height: 84rpx;
.type-title {
font-size: 28rpx;
font-weight: bold;
}
.more {
font-size: 26rpx;
color: #999;
}
}
.item-box {
flex-wrap: wrap;
.goods-item {
margin-right: 20rpx;
margin-bottom: 20rpx;
&:nth-child(3n) {
margin-right: 0;
}
.item-img {
width: 150rpx;
height: 150rpx;
background: #f5f5f5;
border-radius: 6rpx;
}
.item-title {
font-size: 24rpx;
line-height: 24rpx;
margin-top: 10rpx;
width: 150rpx;
text-align: center;
}
}
}
}
}
</style>

@ -0,0 +1,213 @@
<template>
<view class="content_box">
<view class="u-flex u-col-center wrapper-box">
<view class="scroll-box" style="background-color: #F6F6F6;">
<scroll-view class="left u-flex-col u-col-center" enable-back-to-top scroll-y>
<view
class="type-list u-flex u-col-center"
:class="[{ 'list-active': listId == index }]"
v-for="(item, index) in categoryData"
:key="index"
@tap="onType(index)"
>
<view class="u-ellipsis-1 list-item" :class="[{ 'line-active': listId == index }]">{{ item.name }}</view>
</view>
<view class="hack-tabbar"></view>
</scroll-view>
</view>
<view style="height: 100%;width: 100%;">
<scroll-view scroll-y class="scroll-box" enable-back-to-top scroll-with-animation>
<view class="right" v-if="categoryData.length">
<image class="type-img" v-if="categoryData[listId].image" :src="categoryData[listId].image" lazy-load mode="aspectFill"></image>
<view class="type-box u-flex u-col-center u-row-center">
<view class="u-iconfont uicon-minus" style="color: #d3d3d3;font-size: 28rpx;"></view>
<text class="type-title">{{ categoryData[listId].name }}</text>
<view class="u-iconfont uicon-minus" style="color: #d3d3d3;font-size: 28rpx;"></view>
</view>
<view class="item-list">
<view class="item-box u-flex">
<view
class="u-flex-col u-col-center goods-item"
@tap="jump('/pages/goods/list', { id: list.id })"
v-for="(list, index1) in categoryData[listId].children"
:key="index1"
>
<image class="item-img" lazy-load :src="list.image" mode="aspectFill"></image>
<view class="item-title u-ellipsis-1 ">{{ list.name }}</view>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty
v-show="!categoryData[listId].children.length"
:image="$IMG_URL + '/imgs/empty/empty_goods.png'"
marginTop="200rpx"
tipText="暂无该商品,还有更多好货等着你噢~"
></shopro-empty>
<view class="hack-tabbar"></view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {},
data() {
return {
listId: 0,
categoryData: {}
};
},
computed: {},
props: {
categoryStyleId: {
type: Number,
default: 0
}
},
created() {
console.log('%c当前分类:二级分类', 'color:green;background:yellow');
this.getCategory();
},
methods: {
getCategory() {
this.$http('category.detail', {
id: this.categoryStyleId
}).then(res => {
if (res.code === 1) {
this.categoryData = res.data.children;
}
});
},
onType(id) {
this.listId = id;
},
// 路由跳转
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
}
}
};
</script>
<style lang="scss">
.hack-tabbar {
height: calc(100rpx + env(safe-area-inset-bottom) / 2);
width: 100%;
}
.content_box {
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;
}
.wrapper-box {
flex: 1;
margin-top: 1upx;
height: 100%;
}
.scroll-box {
height: 100%;
flex: 1;
background: #fff;
}
.left {
width: 200upx;
height: 100%;
flex: 1;
.list-active {
background: #fff;
color: #ffff !important;
}
.list-item {
width: 180rpx;
height: 64rpx;
line-height: 64rpx;
padding-left: 14rpx;
}
.line-active {
width: 180rpx;
height: 64rpx;
background: #e6b873;
border-radius: 0rpx 32rpx 32rpx 0rpx;
color: #fff;
}
.type-list {
height: 100rpx;
position: relative;
width: 200rpx;
font-size: 28upx;
font-weight: 400;
color: rgba(102, 102, 102, 1);
}
}
.right {
padding: 0 30upx;
flex: 1;
height: 100%;
.type-img {
width: 505rpx;
height: 150rpx;
background: #ccc;
margin-top: 30rpx;
border-radius: 10rpx;
}
.type-box {
height: 84rpx;
.type-title {
font-size: 28rpx;
font-weight: bold;
padding: 0 16rpx;
}
.more {
font-size: 26rpx;
color: #999;
}
}
.item-list {
.item-box {
flex-wrap: wrap;
.goods-item {
margin-right: 20rpx;
margin-bottom: 20rpx;
&:nth-child(3n) {
margin-right: 0;
}
.item-img {
width: 150rpx;
height: 150rpx;
border-radius: 6rpx;
background: #f5f5f5;
}
.item-title {
font-size: 24rpx;
margin-top: 10rpx;
width: 150rpx;
text-align: center;
}
}
}
}
}
</style>

@ -0,0 +1,174 @@
<template>
<view class="adv-box u-m-y-10 u-m-x-20">
<!-- 模板1-->
<view class="u-flex" v-if="advType === 1">
<image style="width:710rpx;height:220rpx;border-radius: 10rpx;" @tap="jump(advList[0].path)" :src="advList[0].image" mode="aspectFill"></image>
</view>
<!-- 模板2-->
<view class="type1 u-flex" v-if="advType === 2">
<image class="type1-img u-m-r-10" @tap="jump(advList[0].path)" :src="advList[0].image" mode="aspectFill"></image>
<image class="type1-img" @tap="jump(advList[1].path)" :src="advList[1].image" mode="aspectFill"></image>
</view>
<!-- 模板3-->
<view class="type2 u-flex" v-if="advType === 3">
<view class="type2-img1 u-m-r-10"><image class="type2-img1 " @tap="jump(advList[0].path)" :src="advList[0].image" mode="aspectFill"></image></view>
<view class="type2-box">
<image class="type2-img2" @tap="jump(advList[1].path)" :src="advList[1].image" mode="aspectFill"></image>
<image class="type2-img2" @tap="jump(advList[2].path)" :src="advList[2].image" mode="aspectFill"></image>
</view>
</view>
<!-- 模板4-->
<view class="type3 u-flex" v-if="advType === 4">
<view class="type3-box">
<image class="type3-img1" @tap="jump(advList[0].path)" :src="advList[0].image" mode="aspectFill"></image>
<image class="type3-img1" @tap="jump(advList[1].path)" :src="advList[1].image" mode="aspectFill"></image>
</view>
<view class="type3-img2 u-m-l-10"><image class="type3-img2" @tap="jump(advList[2].path)" :src="advList[2].image" mode="aspectFill"></image></view>
</view>
<!-- 模板5-->
<view class="type4 " v-if="advType === 5">
<view class="type4-box u-flex">
<image class="type4-img1 u-m-r-10" @tap="jump(advList[0].path)" :src="advList[0].image" mode="aspectFill"></image>
<image class="type4-img1" @tap="jump(advList[1].path)" :src="advList[1].image" mode="aspectFill"></image>
</view>
<view class="type4-img2 u-m-t-10"><image class="type4-img2" @tap="jump(advList[2].path)" :src="advList[2].image" mode="aspectFill"></image></view>
</view>
<!-- 模板6-->
<view class="type5" v-if="advType === 6">
<view class="type5-img1 u-m-b-10"><image class="type5-img1" @tap="jump(advList[0].path)" :src="advList[0].image" mode="aspectFill"></image></view>
<view class="type5-box u-flex">
<image class="type5-img2 u-m-r-10" @tap="jump(advList[1].path)" :src="advList[1].image" mode="aspectFill"></image>
<image class="type5-img2" @tap="jump(advList[2].path)" :src="advList[2].image" mode="aspectFill"></image>
</view>
</view>
<!-- 模板7-->
<view class="type6" v-if="advType === 7">
<view class="u-flex type6-box1 u-m-b-10">
<image class="type6-img1 u-m-r-10" @tap="jump(advList[0].path)" :src="advList[0].image" mode="aspectFill"></image>
<image class="type6-img1" @tap="jump(advList[1].path)" :src="advList[1].image" mode="aspectFill"></image>
</view>
<view class="u-flex type6-box2">
<image class="type6-img2 u-m-r-10" @tap="jump(advList[2].path)" :src="advList[2].image" mode="aspectFill"></image>
<image class="type6-img2 u-m-r-10" @tap="jump(advList[3].path)" :src="advList[3].image" mode="aspectFill"></image>
<image class="type6-img2" @tap="jump(advList[4].path)" :src="advList[4].image" mode="aspectFill"></image>
</view>
</view>
</view>
</template>
<script>
/**
* 自定义之广告魔方
* @property {Object} detail -广告魔方信息
*/
export default {
components: {},
data() {
return {
advType: this.detail.style ,// 模板样式
advList:this.detail.list // adv数据
};
},
props: {
detail: {}
},
computed: {},
created() {},
methods: {
// 路由跳转
jump(path) {
this.$tools.routerTo(path);
}
}
};
</script>
<style lang="scss">
@mixin grid($row: 1) {
width: (710 - ($row - 1) * 10) / $row + rpx;
border-radius: 10rpx;
}
.adv-box {
.type1 {
.type1-img {
@include grid(2);
height: 220rpx;
}
}
.type2 {
.type2-img1 {
height: 350rpx;
@include grid(2);
}
.type2-box {
height: 350rpx;
@include grid(2);
.type2-img2 {
height: 170rpx;
@include grid(2);
}
}
}
.type3 {
.type3-box {
height: 350rpx;
@include grid(2);
.type3-img1 {
height: 170rpx;
@include grid(2);
}
}
.type3-img2 {
height: 350rpx;
@include grid(2);
}
}
.type4 {
.type4-box {
.type4-img1 {
height: 170rpx;
@include grid(2);
}
}
.type4-img2 {
@include grid(1);
height: 220rpx;
}
}
.type5 {
.type5-img1 {
@include grid(1);
height: 220rpx;
}
.type5-box {
.type5-img2 {
height: 170rpx;
@include grid(2);
}
}
}
.type6 {
.type6-box1 {
.type6-img1 {
height: 170rpx;
@include grid(2);
}
}
.type6-box2 {
.type6-img2 {
height: 170rpx;
@include grid(3);
}
}
}
}
</style>

@ -0,0 +1,69 @@
<template>
<!-- 轮播 -->
<view class="banner-swiper-wrap u-m-b-10" :style="{ padding: `${Py}rpx ${Px}rpx` }">
<swiper
:style="{ minHeight: height + 'rpx', height: height + 'rpx' }"
class="screen-swiper square-dot"
:indicator-dots="true"
:circular="true"
:autoplay="true"
interval="5000"
duration="500"
>
<swiper-item :style="{ borderRadius: borderRadius + 'rpx' }" v-for="(item, index) in list" :key="index" @tap="onSwiper(index)">
<image :src="item.image" mode="aspectFill"></image>
</swiper-item>
</swiper>
</view>
</template>
<script>
/**
* shBanner-轮播卡片
* @property {Array} list 轮播图数据,
* @property {String Number} height 轮播图组件高度,单位rpx(默认250)
* @property {String} borderRadius 圆角值
* @event {Function} click 点击轮播图时触发
*/
export default {
components: {},
data() {
return {};
},
props: {
Px: {
type: [Number, String],
default: 0
},
Py: {
type: [Number, String],
default: 0
},
// 轮播图的数据,格式如:[{image: 'xxxx', title: 'xxxx'},{image: 'yyyy', title: 'yyyy'}],其中title字段可选
list: {
type: Array,
default() {
return [];
}
},
// 圆角值
borderRadius: {
type: [Number, String],
default: 0
},
// list的高度,单位rpx
height: {
type: [Number, String],
default: 250
}
},
computed: {},
methods: {
onSwiper(e) {
this.$tools.routerTo(this.list[e].path);
}
}
};
</script>
<style lang="scss" scoped></style>

@ -0,0 +1,348 @@
<template>
<!-- 分类选项卡 -->
<view class="category-tabs-wrap">
<!-- 吸顶 -->
<view class="u-sticky-wrap" :class="[elClass]" :style="{ height: fixed ? height + 'px' : 'auto', backgroundColor: isSticky ? '#fff' : '#f6f6f6' }">
<view
class="u-sticky"
:style="{ position: fixed ? 'fixed' : 'static', top: stickyTop + 'px', left: left + 'px', width: width == 'auto' ? 'auto' : width + 'px', zIndex: 1109 }"
>
<view class="tabs-wrap u-p-y-20" :style="isSticky ? 'border-bottom:1px solid #eee;background-color:#fff' : ''">
<scroll-view scroll-x class="tabs-content" scroll-with-animation :scroll-left="scrollLeft">
<view class="u-flex u-row-cetner u-flex-1 u-col-center">
<view class="tab-item u-flex-col u-col-center u-row-center" v-for="(item, index) in tabsList" :key="index" @tap="tabChange(index)">
<view class="tab-title u-m-y-6" :class="{ 'title-active': tabCur == index }">{{ item.name }}</view>
<view class="tab-des" :class="{ 'des-active': tabCur == index }">{{ item.description }}</view>
</view>
</view>
</scroll-view>
</view>
</view>
</view>
<!-- 瀑布流 -->
<view class="u-waterfall u-p-16" v-if="styleType == 1">
<view id="u-left-column" class="u-column">
<view class="goods-item u-m-b-16 u-flex u-row-center u-col-center" v-for="leftGoods in leftList" :key="leftGoods.id">
<shopro-goods-card
:detail="leftGoods"
:type="leftGoods.activity_type || ''"
:image="leftGoods.image"
:title="leftGoods.title"
:subtitle="leftGoods.subtitle"
:price="leftGoods.price"
:originPrice="leftGoods.original_price"
:tagTextList="leftGoods.activity_discounts_tags"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: leftGoods.id } })"
></shopro-goods-card>
</view>
</view>
<view id="u-right-column" class="u-column">
<view class="goods-item u-m-b-16 u-flex u-row-center u-col-center" v-for="rightGoods in rightList" :key="rightGoods.id">
<shopro-goods-card
:detail="rightGoods"
:type="rightGoods.activity_type || ''"
:image="rightGoods.image"
:title="rightGoods.title"
:subtitle="rightGoods.subtitle"
:price="rightGoods.price"
:originPrice="rightGoods.original_price"
:tagTextList="rightGoods.activity_discounts_tags"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: rightGoods.id } })"
></shopro-goods-card>
</view>
</view>
</view>
<!-- m -->
<view class="big-card-wrap u-p-10" v-if="styleType == 2">
<block v-for="item in goodsList" :key="item.id">
<sh-goods-card
:detail="item"
:type="item.activity_type || ''"
:image="item.image"
:title="item.title"
:subtitle="item.subtitle"
:price="item.price"
:originPrice="item.original_price"
:sales="item.sales"
:tagTextList="item.activity_discounts_tags"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: item.id } })"
></sh-goods-card>
</block>
</view>
<view class="x-c" style="width: 100%;">
<!-- 缺省页 -->
<shopro-empty v-if="isEmpty" marginTop="200rpx" :image="$IMG_URL + '/imgs/empty/empty_goods.png'" tipText="暂无该商品,还有更多好货等着你噢~"></shopro-empty>
<!-- 更多 -->
<u-loadmore v-show="!isEmpty" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</view>
</view>
</template>
<script>
/**
* category-tabs
* @description 一个可以吸顶的分类列表
* @property {Boolean} enable = false - 是否吸顶,tabbar项中,组件不会自动销毁,需要自行开关
* @property {Array} tabsList - 分类列表
* @property {Array | Number} styleType - 卡片类型
*/
let systemInfo = uni.getSystemInfoSync();
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
let menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
import shGoodsCard from '../components/sh-goods-card.vue';
export default {
components: {
shGoodsCard
},
data() {
return {
// 吸顶相关
fixed: false,
height: 'auto',
elClass: this.$u.guid(),
left: 0,
width: 'auto',
stickyTop: 0,
isSticky: false, //是否吸顶
// 瀑布流 350-330
addTime: 100, //排序间隙时间
leftHeight: 0,
rightHeight: 0,
leftList: [],
rightList: [],
tempList: [],
tabCur: 0,
scrollLeft: 0,
isEmpty: true,
categoryId: this.tabsList[0]?.id,
goodsList: [],
tabCurrent: 0,
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1
};
},
watch: {
enable(val) {
if (val == false) {
this.fixed = false;
this.disconnectObserver('contentObserver');
} else {
this.initObserver();
}
}
},
props: {
tabsList: {
type: Array,
default: () => []
},
styleType: {
type: [String, Number],
default: 1
},
enable: {
type: Boolean,
default: false
}
},
created() {
this.getGoodsList();
// 触底监听
uni.$on('uOnReachBottom', data => {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getGoodsList();
}
});
},
mounted() {
this.initObserver();
},
beforeDestroy() {
this.disconnectObserver('contentObserver');
},
methods: {
// 瀑布流相关
async splitData() {
if (!this.tempList.length) return;
let item = this.tempList[0];
if (!item) return;
// 分左右
if (this.leftHeight < this.rightHeight) {
this.leftHeight += item.activity_discounts_tags.length ? 350 : 330;
this.leftList.push(item);
} else if (this.leftHeight > this.rightHeight) {
this.rightHeight += item.activity_discounts_tags.length ? 350 : 330;
this.rightList.push(item);
} else {
this.leftHeight += item.activity_discounts_tags.length ? 350 : 330;
this.leftList.push(item);
}
// 移除临时列表的第一项,如果临时数组还有数据,继续循环
this.tempList.splice(0, 1);
if (this.tempList.length) {
setTimeout(() => {
this.splitData();
}, this.addTime);
}
},
clear() {
this.leftList = [];
this.rightList = [];
this.leftHeight = 0;
this.rightHeight = 0;
this.tempList = [];
},
// 吸顶相关
initObserver() {
if (!this.enable) return;
// #ifdef APP-PLUS || H5
this.stickyTop = systemInfo.statusBarHeight + 44;
// #endif
// #ifdef MP
let height = systemInfo.platform == 'ios' ? 44 : 48;
let top = systemInfo.statusBarHeight + height;
this.stickyTop = systemInfo.statusBarHeight + height;
// #endif
this.disconnectObserver('contentObserver');
this.$uGetRect('.' + this.elClass).then(res => {
this.height = res.height;
this.left = res.left;
this.width = res.width;
this.$nextTick(() => {
this.observeContent();
});
});
},
observeContent() {
this.disconnectObserver('contentObserver');
const contentObserver = this.createIntersectionObserver({
thresholds: [0.95, 0.98, 1]
});
contentObserver.relativeToViewport({
top: -this.stickyTop
});
contentObserver.observe('.' + this.elClass, res => {
if (!this.enable) return;
this.setFixed(res.boundingClientRect.top);
});
this.contentObserver = contentObserver;
},
setFixed(top) {
const fixed = top < this.stickyTop;
if (fixed) {
this.isSticky = true;
} else if (this.fixed) {
this.isSticky = false;
}
this.fixed = fixed;
},
disconnectObserver(observerName) {
const observer = this[observerName];
observer && observer.disconnect();
},
// 商品列表
getGoodsList() {
let that = this;
that.$http('goods.lists', {
category_id: that.categoryId,
page: that.currentPage
}).then(res => {
if (res.code === 1) {
that.goodsList = [...that.goodsList, ...res.data.data];
that.tempList = res.data.data;
that.isEmpty = !that.goodsList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
that.splitData();
}
});
},
// tabs
tabChange(index) {
this.tabCur = index;
this.scrollLeft = (index - 1) * 60;
this.categoryId = this.tabsList[index].id;
this.styleType === 1 && this.clear();
this.goodsList = [];
this.currentPage = 1;
this.lastPage = 1;
this.loadStatus = 'loadmore';
this.getGoodsList();
}
}
};
</script>
<style lang="scss" scoped>
@mixin vue-flex($direction: row) {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: $direction;
/* #endif */
}
.category-tabs-wrap {
min-height: 1000rpx;
// 吸顶
.u-sticky-wrap {
background-color: #fff;
}
// 瀑布流
.u-waterfall {
@include vue-flex;
flex-direction: row;
align-items: flex-start;
}
.u-column {
@include vue-flex;
flex: 1;
flex-direction: column;
height: auto;
}
}
// 滑动分类
.tabs-content {
white-space: nowrap;
.tab-item {
min-height: 90rpx;
display: inline-block;
margin: 0 10rpx;
padding: 0 20rpx;
.tab-title {
font-size: 28rpx;
font-weight: 600;
color: #333333;
}
.title-active {
color: #a8700d;
}
.tab-des {
font-size: 22rpx;
font-weight: 400;
color: #999999;
text-align: center;
}
.des-active {
background: linear-gradient(90deg, #e9b461, #eecc89);
border-radius: 15rpx;
padding: 2rpx 10rpx;
color: #ffffff;
}
}
}
</style>

@ -0,0 +1,56 @@
<template>
<view class="sh-user-menu-box u-m-b-10">
<view class="menu-item u-flex u-row-between u-border-bottom" hover-stay-time="150" hover-class="u-cell-hover" v-for="(item, index) in list" :key="index" @tap="jump(item)">
<view class="menu-head u-flex u-col-center">
<image class="menu-img u-m-r-10" :src="item.image" mode=""></image>
<view class="menu-title ">{{ item.name }}</view>
</view>
<view class="u-iconfont uicon-arrow-right" style="color: #969799;font-size: 28rpx;"></view>
</view>
</view>
</template>
<script>
/**
*shCell-功能列表
* @property {Array} list - 列表信息
*/
export default {
components: {},
data() {
return {};
},
props: {
list: {
type: Array,
default: () => {
return [];
}
}
},
computed: {},
methods: {
jump(data) {
this.$tools.routerTo(data.path);
}
}
};
</script>
<style lang="scss" scoped>
.menu-item {
background-color: #fff;
width: 100%;
padding: 26rpx 32rpx;
font-size: 28rpx;
line-height: 54rpx;
.menu-img {
width: 36rpx;
height: 36rpx;
}
.menu-title {
font-size: 26rpx;
color: #999;
}
}
</style>

@ -0,0 +1,276 @@
<template>
<!-- 首页优惠券卡片 -->
<view class="coupon-box u-m-b-10 u-p-20" v-if="detail.ids && couponList.length">
<view class="head-box u-flex u-row-between u-m-b-20">
<view class="head-title u-ellipsis-1">领券专区</view>
<view class="head-more u-flex u-col-center" @tap="$Router.push('/pages/app/coupon/list')">
<text class="more-text u-m-r-10">查看更多</text>
<text class="iconfont icon-youjiantou-tianchong more-icon"></text>
</view>
</view>
<scroll-view class="groupon-scroll" scroll-anchoring scroll-x scroll-with-animation>
<view class="groupon-card-wrap u-flex ">
<view :class="{ 'gray-wrap': item.status_code === 'cannot_get' }" v-for="(item, index) in couponList" :key="item.id">
<!-- mini -->
<view class="coupon-card u-flex u-row-between u-m-r-16" v-if="couponType === 2" :style="{ background: `linear-gradient(to right, ${bgColor1}, ${bgColor2})` }">
<view class="card-left u-flex-col u-row-center u-p-l-30">
<view class="price u-m-b-10" :style="{ color: priceColor }">{{ item.amount }}</view>
<view class="notice" :style="{ color: color }">满{{ item.enough }}元可用</view>
<view class="notice u-m-b-10" :style="{ color: color }">仅剩{{ item.stock }}张</view>
</view>
<view class="card-right u-p-y-20 u-p-r-10 u-flex-col u-row-center u-col-center">
<button class="u-reset-button get-btn u-m-b-10" :style="{ color: color }" @tap="getCoupon(item.id, index)">
{{ item.status_code === 'cannot_get' ? '不可领取' : '领券购买' }}
</button>
</view>
</view>
<!-- big -->
<view v-if="couponType === 1" class="u-p-r-16">
<view class="coupon-wrap " :style="{ background: `linear-gradient(to right, ${bgColor1}, ${bgColor2})` }">
<view class="coupon-item u-flex u-row-between">
<view class="coupon-left u-flex-col ">
<view class="sum-box">
<text class="unit " :style="{ color: priceColor }">¥</text>
<text class="sum " :style="{ color: priceColor }">{{ item.amount }}</text>
<text class="sub " :style="{ color: priceColor }">{{ item.name }}</text>
</view>
<view class="notice " :style="{ color: color }">满{{ item.enough }}元可用</view>
<view class="notice" :style="{ color: color }">
有效期:{{ $u.timeFormat(item.usetime.start, 'yyyy-mm-dd') }} 至 {{ $u.timeFormat(item.usetime.end, 'yyyy-mm-dd') }}
</view>
</view>
<view class="coupon-right u-flex-col">
<button class="get-btn" :style="{ color: bgColor1 }" @tap.stop="getCoupon(item.id, index)">
{{ item.status_code === 'cannot_get' ? '不可领取' : '立即领取' }}
</button>
<view class="surplus-num" :style="{ color: color }">仅剩{{ item.stock }}张</view>
</view>
</view>
</view>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
/**
* 自定义之优惠券卡片
* @property {Object} detail - 优惠券信息
*/
export default {
components: {},
data() {
return {
currentIndex: 0,
couponList: [],
couponType: this.detail.style,
priceColor: this.detail.pricecolor,
color: this.detail.color,
bgColor1: this.detail.bgcolor1,
bgColor2: this.detail.bgcolor2
};
},
props: {
detail: {}
},
computed: {},
created() {
this.detail.ids && this.getCouponsList();
},
methods: {
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 优惠券列表
getCouponsList() {
let that = this;
that.$http('coupons.templateList', {
ids: that.detail.ids
}).then(res => {
if (res.code === 1) {
that.couponList = res.data;
}
});
},
// 领取
getCoupon(id, index) {
let that = this;
uni.showLoading({
title: '领取中'
});
that.$http('coupons.get', {
id: id
}).then(res => {
uni.hideLoading();
if (res.code === 1) {
that.couponList[index].stock -= 1;
this.$u.toast('领取成功');
}
});
}
}
};
</script>
<style lang="scss" scoped>
// 失效
.gray-wrap {
filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1) !important;
-webkit-filter: grayscale(100%) !important;
-moz-filter: grayscale(100%) !important;
-ms-filter: grayscale(100%) !important;
-o-filter: grayscale(100%) !important;
filter: grayscale(100%) !important;
filter: gray !important;
}
.groupon-scroll {
height: 170rpx;
width: 730rpx;
.groupon-card-wrap {
height: 170rpx;
width: 730rpx;
}
}
.coupon-box {
background-color: #fff;
.head-box {
.head-title {
width: 300rpx;
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.head-more {
.more-text {
font-size: 22rpx;
font-weight: 500;
color: #333333;
}
.more-icon {
color: #333333;
font-size: 24rpx;
}
}
}
.coupon-card {
width: 343rpx;
height: 170rpx;
background: linear-gradient(90deg, #f8dca5, #efc480);
border-radius: 10rpx;
mask: url($IMG_URL+'/imgs/coupon_mini_bg.png');
-webkit-mask-box-image: url($IMG_URL+'/imgs/coupon_mini_bg.png');
mask-size: 100% 100%;
.card-left {
height: 100%;
width: 260rpx;
.price {
color: #4f3a1e;
font-size: 30rpx;
font-weight: bold;
&::before {
content: '¥';
font-size: 20rpx;
}
}
.notice {
font-size: 22rpx;
font-weight: 500;
color: #a8700d;
}
}
.card-right {
height: 100%;
width: 60rpx;
.get-btn {
font-size: 24rpx;
font-weight: 500;
text-align: right;
color: #a8700d;
writing-mode: vertical-lr; /*从左向右 从右向左是 writing-mode: vertical-rl;*/
writing-mode: tb-lr; /*IE浏览器的从左向右 从右向左是 writing-mode: tb-rl;*/
}
}
}
}
// 未领取,已领取
.coupon-wrap {
mask: url($IMG_URL+'/imgs/coupon_bg1.png');
-webkit-mask-box-image: url($IMG_URL+'/imgs/coupon_bg1.png');
mask-size: 100% 100%;
position: relative;
border-radius: 10rpx;
width: 710rpx;
height: 170rpx;
background: linear-gradient(to right, $u-type-warning-disabled, $u-type-warning);
.coupon-item {
width: 100%;
height: 168rpx;
border-radius: 10rpx;
.coupon-left {
height: 100%;
justify-content: center;
padding-left: 40rpx;
.unit {
font-size: 24rpx;
color: #fff;
}
.sum {
font-size: 55rpx;
font-weight: bold;
color: #fff;
line-height: 55rpx;
padding-right: 10rpx;
}
.sub {
font-size: 26rpx;
color: #fff;
}
.notice {
font-size: 22rpx;
color: rgba(#fff, 0.7);
margin-top: 6rpx;
}
}
.coupon-right {
height: 100%;
width: 220rpx;
justify-content: center;
align-items: center;
.get-btn {
width: 142rpx;
height: 54rpx;
background: #ffffff;
border-radius: 27rpx;
padding: 0;
font-size: 24rpx;
font-weight: 500;
color: $u-type-warning;
}
.surplus-num {
font-size: 22rpx;
font-weight: 500;
color: #fff;
margin-top: 14rpx;
}
}
}
}
</style>

@ -0,0 +1,356 @@
<!-- 横版大图,商品卡片 -->
<template>
<view class="big-goods u-flex u-p-20 u-col-top u-m-b-16" @tap="click"
:style="type ? 'background-image: url(' + $IMG_URL + typeMap[type].goodsBg + ')' : ''">
<image class="goods-img" lazy-load fade-show :src="image" mode="aspectFill"></image>
<view class=" card-right u-m-l-20 u-flex-col u-row-between">
<view class="">
<view class="goods-title u-ellipsis-1 u-m-t-10 u-m-b-10">
<view v-if="type" class=" sm cu-tag radius title-tag u-m-r-10"
:style="{ backgroundColor: typeMap[type].tagBg, color: '#fff' }">{{ typeMap[type].text }}</view>
{{ title }}
</view>
<view v-show="subtitle" class="subtitle-text u-m-b-10 u-ellipsis-1">{{ subtitle }}</view>
</view>
<view class="u-m-b-20" v-if="tagTextList.length">
<view class="tag-box u-m-r-10" v-for="(item, index) in tagTextList" :key="index">{{ item }}</view>
</view>
<view class=" u-flex u-row-between u-col-center">
<view class="font-OPPOSANS">
<view class="price">{{ price }}</view>
<view class="origin-price">{{ originPrice }}</view>
</view>
<!-- 加入购物车 -->
<view class="cart-box">
<!-- 单规格 -->
<view class="" v-if="!detail.is_sku">
<button v-if="!isCart(detail.id)" @tap.stop="addCart(detail.sku_price[0])"
class="u-reset-button buy-btn u-flex u-row-center u-col-center"
:style="type ? typeMap[type].btnBg : ''">
{{ type ? typeMap[type].btnText : '去购买' }}
</button>
<view class="num-step" @tap.stop v-else>
<u-number-box :value="checkCart[detail.id].num" :min="0" :step="1" :long-press="false"
:max="detail.sku_price[0].stock > 999 ? 999 : detail.sku_price[0].stock" @min="onMin"
@plus="plus($event, detail.sku_price[0])"
@change="onChangeNum($event, detail.sku_price[0])"></u-number-box>
</view>
</view>
<!-- 多规格 -->
<button class="u-reset-button item-btn buy-btn" :style="type ? typeMap[type].btnBg : ''"
@tap.stop="selSku(detail)" v-else>
{{ type ? typeMap[type].btnText : '去购买' }}
</button>
</view>
</view>
</view>
<!-- 规格弹窗 -->
<shopro-sku v-if="showSku && goodsInfo.id" v-model="showSku" :goodsInfo="goodsInfo" buyType="cart"></shopro-sku>
</view>
</template>
<script>
/**
*shoproGoodsCard - 商品列表卡片
* @property {Object} detail - 商品详情
* @property {String} type - 商品类型
* @property {String} image - 商品图片
* @property {String} title - 商品标题
* @property {String} subtitle - 商品副标题
* @property {String | Number} price - 商品价格
* @property {String | Number} originPrice - 商品原价
* @property {String | Number} sales - 商品销量
* @property {Array} tagTextList - 活动标签
* @event {Function} click 商品被点击
*/
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {},
data() {
return {
showSku: false,
goodsInfo: {}, //商品详情
typeMap: {
seckill: {
text: '秒杀',
tagBg: '#FF5854',
goodsBg: '/imgs/tag/seckill_x_bg.png',
btnText: '去抢购',
btnBg: 'background: linear-gradient(90deg, #D01325, #ED3C30);'
},
groupon: {
text: '拼团',
tagBg: '#FE832A',
goodsBg: '/imgs/tag/groupon_x_bg.png',
btnText: '马上拼',
btnBg: 'background: linear-gradient(90deg, #FF6600 0%, #FE832A 100%);'
}
}
};
},
computed: {
...mapGetters(['cartList', 'checkCart'])
},
props: {
detail: {
type: Object,
default: () => {
return {};
}
},
type: {
type: [String, null],
default: ''
},
image: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
subtitle: {
type: String,
default: ''
},
price: {
type: [String, Number],
default: ''
},
originPrice: {
type: [String, Number],
default: ''
},
sales: {
type: [String, Number],
default: ''
},
tagTextList: {
type: Array,
default: () => []
}
},
mounted() {},
methods: {
...mapActions(['getCartList', 'changeCartList', 'addCartGoods']),
//点击商品
click() {
this.$emit('click');
},
// 检测是否为购物车商品
isCart(id) {
return Object.keys(this.checkCart).includes(id + '');
},
// 检测商品在购物车中的下标
checkGoodsIndex(id) {
let cIndex = 0;
this.cartList.forEach((item, index) => {
if (id == item.goods_id) {
cIndex = index;
}
});
return cIndex;
},
// 更改商品数
async onChangeNum(e, sku) {
let gIndex = this.checkGoodsIndex(sku.goods_id);
if (e.value != this.checkCart[sku.goods_id].num) {
uni.showLoading({
mask: true
});
this.$set(this.cartList[gIndex], 'goods_num', +e.value);
await this.changeCartList({
ids: [this.checkCart[sku.goods_id].cartOrderId],
goodsNum: +e.value,
art: 'change'
});
await uni.hideLoading();
}
},
// 到达最小值
onMin() {
const that = this;
let cartGoodId = 0;
cartGoodId = this.cartList.filter(item => item.goods_id === that.detail.id)[0].id;
uni.showModal({
title: '删除提示',
confirmColor: '#f0c785',
content: `是否确认从购物车中删除此商品?`,
success: res => {
res.confirm && this.changeCartList({ ids: [cartGoodId], art: 'delete' });
}
});
},
// 增加
plus(e, sku) {
if (e.value >= sku.stock) {
this.$u.toast('库存不足');
return;
}
if (this.detail.activity_type === 'seckill' || this.detail.activity_type === 'groupon') {
let rules = this.detail.activity.rules;
if (rules.limit_buy != 0 && e.value >= rules.limit_buy) {
this.$u.toast('本次活动最多购买' + rules.limit_buy + '件');
return;
}
}
},
// 添加购物车,多规格
async selSku(info) {
if (this.detail.activity_type) {
this.$Router.push({ path: '/pages/goods/detail', query: { id: this.detail.id } });
return;
}
this.goodsInfo = {};
this.getGoodsDetail(info.id);
this.showSku = true;
},
// 商品详情
getGoodsDetail(id) {
let that = this;
that.$http('goods.detail', {
id: id
}).then(res => {
if (res.code === 1) {
that.goodsInfo = res.data;
}
});
},
// 加入购物车
addCart(sku) {
if (sku.stock <= 0) {
this.$u.toast('库存不足');
return;
}
if (this.detail.activity_type) {
this.$Router.push({ path: '/pages/goods/detail', query: { id: this.detail.id } });
return;
}
let confirmGoodsList = {
list: [{
goods_id: sku.goods_id,
goods_num: 1,
sku_price_id: sku.id,
goods_price: sku.price
}],
from: 'goods'
};
this.addCartGoods(confirmGoodsList).then(res => {
if (res.code === 1) {
this.$u.toast(res.msg);
}
});
}
}
};
</script>
<style lang="scss">
// 大商品卡片
.big-goods {
width: 710rpx;
min-height: 260rpx;
background: #ffffff;
border-radius: 20rpx;
background-repeat: no-repeat;
background-size: 100% 100%;
.goods-img {
width: 260rpx;
height: 260rpx;
background-color: #f5f5f5;
border-radius: 6rpx;
}
.card-right {
width: 430rpx;
height: 220rpx;
}
.goods-title {
font-size: 26rpx;
font-weight: 600;
width: 400rpx;
color: #000000;
padding-top: 6rpx;
.title-tag {
transform: scale(0.9);
position: relative;
top: -6rpx;
}
}
.subtitle-text {
font-size: 22rpx;
width: 400rpx;
font-weight: 500;
color: #666666;
}
.tag-box {
border: 1rpx solid #ff0000;
display: inline-block;
font-size: 20rpx;
line-height: 30rpx;
padding: 0 10rpx;
color: #ff0000;
border-radius: 8rpx;
}
// 购物车
.cart-box {
.cart-btn {
width: 54rpx;
height: 54rpx;
border-radius: 50%;
padding: 0;
background: linear-gradient(90deg, #e9b461, #eecc89);
}
.buy-btn {
width: 120rpx;
line-height: 50rpx;
background: linear-gradient(90deg, #e9b461, #eecc89);
border-radius: 25rpx;
font-size: 24rpx;
font-weight: 500;
color: #ffffff;
}
}
// 价格
.price {
color: #ff0000;
font-weight: 600;
&::before {
content: '¥';
font-size: 20rpx;
}
}
.origin-price {
color: #c4c4c4;
font-size: 24rpx;
text-decoration: line-through;
&::before {
content: '¥';
font-size: 20rpx;
}
}
}
</style>

@ -0,0 +1,134 @@
<template>
<!-- 产品分类导航 -->
<view class="wrap">
<view class="menu-category-box u-m-b-10" :style="list.length <= oneRowNum ? `height:180rpx` : `height:380rpx`">
<swiper
class="menu-swiper-box"
@change="onSwiper"
:style="list.length <= oneRowNum ? `height:160rpx` : `height:380rpx`"
circular
:autoplay="false"
:interval="3000"
:duration="1000"
>
<swiper-item class="menu-swiper-item" v-for="(itemList, index) in newList" :key="index">
<view class="menu-tab-box u-flex u-flex-wrap">
<view
class="tab-list u-flex-col u-col-center u-row-center"
:style="{ width: 100 / oneRowNum + '%' }"
v-for="(item, index) in itemList"
:key="index"
@tap="$tools.routerTo(item.path)"
>
<image class="tab-img" :src="item.image"></image>
<text class="">{{ item.name }}</text>
</view>
</view>
</swiper-item>
</swiper>
<view class="menu-category-dots" v-if="newList.length > 1">
<text :class="categoryCurrent === index ? 'category-dot-active' : 'category-dot'" v-for="(dot, index) in newList.length" :key="index"></text>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* shGridSwiper-滑动宫格列表
* @property {Array} list - 列表数据
* @property {Number|String} oneRowNum - 单行数量
*/
export default {
components: {},
data() {
return {
categoryCurrent: 0 ,//分类轮播下标
};
},
props: {
list: {
type: Array,
default: () => {
return [];
}
},
oneRowNum: {
type: [Number, String],
default: 5
}
},
computed: {
newList() {
if (this.list.length) {
let data = this.$tools.splitData(this.list, this.oneRowNum * 2);
return data;
}
}
},
methods: {
// 轮播
onSwiper(e) {
this.categoryCurrent = e.detail.current;
},
jump(path, query) {
this.$Router.push({
path: path,
query: query
});
}
}
};
</script>
<style lang="scss">
// 产品分类
.menu-category-box,
.menu-swiper-box {
position: relative;
background: #fff;
.menu-swiper-item {
background: #fff;
height: 100%;
width: 100%;
}
.menu-tab-box {
.tab-list {
font-size: 26rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
margin: 20rpx 0;
.tab-img {
width: 98rpx;
height: 98rpx;
margin-bottom: 10rpx;
}
}
}
.menu-category-dots {
display: flex;
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: 10rpx;
.category-dot {
width: 12rpx;
height: 12rpx;
background: #eeeeee;
border-radius: 50%;
margin-right: 10rpx;
}
.category-dot-active {
width: 12rpx;
height: 12rpx;
background: #e9b461;
border-radius: 50%;
margin-right: 10rpx;
}
}
}
</style>

@ -0,0 +1,60 @@
<template>
<view class="grid-wap u-m-b-10 u-flex u-flex-wrap u-col-center" v-if="list.length">
<view class="grid-item u-flex-col u-row-center u-col-center" v-for="(menu, index) in list" :key="index" @tap="jump(menu)">
<image class="tool-img" :src="menu.image" mode="aspectFill"></image>
<view class="item-title">{{ menu.name }}</view>
</view>
</view>
</template>
<script>
/**
* shGrid-宫格列表
* @property {Array} list - 宫格列表
*/
export default {
components: {},
data() {
return {};
},
computed: {},
props: {
list: {
type: Array,
default: () => {
return [];
}
}
},
created() {},
methods: {
jump(data) {
this.$tools.routerTo(data.path);
}
}
};
</script>
<style lang="scss">
// 宫格
.grid-wap {
background: #fff;
width: 750rpx;
padding: 30rpx 0 0;
.grid-item {
width: 25%;
margin-bottom: 40rpx;
.tool-img {
width: 44rpx;
height: 44rpx;
}
.item-title {
font-size: 26rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
line-height: 24rpx;
padding-top: 20rpx;
}
}
}
</style>

@ -0,0 +1,319 @@
<template>
<!-- 活动商品 -->
<view class="activity-wrap u-p-x-20 u-p-b-20 u-m-b-10 groupon-card" v-if="showActivity">
<!-- 标题栏 -->
<view class="title-box u-flex u-row-between u-p-y-20 groupon-title">
<view class="u-flex u-col-center">
<view class="title-text u-m-r-20 u-ellipsis-1">{{ detail.name }}</view>
</view>
<view class="more-box u-flex" @tap="$Router.push('/pages/activity/groupon/list')">
<text class="more-text u-m-r-10">更多拼团</text>
<text class="iconfont icon-youjiantou-tianchong more-icon"></text>
</view>
</view>
<!-- 活动商品 -->
<!-- m -->
<scroll-view v-if="grouponType === 1" class="scroll-box" scroll-x scroll-anchoring>
<view class="goods-box u-flex">
<view class="min-goods u-m-r-14" v-for="mgoods in goodsList" :key="mgoods.id" @tap="jump('/pages/goods/detail', { id: mgoods.id })">
<view class="img-box"><image class="img" :src="mgoods.image" mode=""></image></view>
<view class="mgoods-card-bottom u-p-20">
<view class="goods-title u-m-b-10 u-ellipsis-1">{{ mgoods.title }}</view>
<view class="price u-m-b-10">{{ mgoods.groupon_price }}</view>
<view class="groupon-num-box u-flex u-col-center" v-if="mgoods.buyers.length">
<view class="avatar-box"><image class="avatar-img u-m-r-10" :src="mgoods.buyers[0].avatar" mode=""></image></view>
<view class="groupon-num-text">{{ mgoods.sales }}人正在拼</view>
</view>
<view class="original-price" v-else>¥{{ mgoods.original_price }}</view>
</view>
</view>
</view>
</scroll-view>
<!--b-->
<view
v-if="grouponType === 2"
class="big-goods u-flex u-p-20 u-col-top u-m-b-16"
v-for="item in goodsList"
:key="item.id"
@tap="jump('/pages/goods/detail', { id: item.id })"
>
<image class="goods-img" :src="item.image" mode="aspectFill"></image>
<view class=" card-right u-m-l-20 u-flex-col u-row-between">
<view class="">
<view class="goods-title u-ellipsis-1 u-m-t-10 u-m-b-10">
<view class=" sm cu-tag radius title-tag u-m-r-10" style="{ backgroundColor: #FF6600, color: '#fff' }">拼团</view>
{{ item.title }}
</view>
<view v-show="item.subtitle" class="subtitle-text u-m-b-10 u-ellipsis-1">{{ item.subtitle }}</view>
</view>
<view class="u-flex u-m-y-20">
<view class="sell-box">
<text class=" hot-icon iconfont icon-icon-test"></text>
<text class="sell-num">已拼{{ item.sales }}件</text>
</view>
<text class="group-num">{{ item.activity.rules.team_num || 0 }}人团</text>
</view>
<view class=" u-flex u-row-between u-col-center">
<view class="u-flex u-col-bottom font-OPPOSANS">
<view class="price u-m-r-10">{{ item.groupon_price }}</view>
<view class="origin-price">{{ item.original_price }}</view>
</view>
<button class="u-reset-button buy-btn" @tap.stop="$Router.push({ path: '/pages/goods/detail', query: { id: item.id } })">马上拼</button>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 自定义之拼团样式组件
* @property {Object} detail - 秒杀商品信息
*/
export default {
components: {},
data() {
return {
timestamp: 0, //倒计时
goodsList: [],
showActivity: true, //是否显示活动。
grouponType: this.detail.style
};
},
props: {
detail: {
type: Object,
default: () => {
return {};
}
}
},
watch: {},
computed: {},
created() {
this.getActivityGoodsList();
},
methods: {
// 路由跳转
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 获取拼团商品
getActivityGoodsList() {
let that = this;
that.$http('goods.activity', {
activity_id: that.detail.id,
need_buyer: 1
}).then(res => {
if (res.code === 1) {
that.goodsList = res.data.goods.data;
that.showActivity = that.goodsList.length;
} else {
that.showActivity = false;
console.log(`%cerr:拼团活动已结束`, 'color:green;background:yellow');
}
});
}
}
};
</script>
<style lang="scss" scoped>
.groupon-card {
background: linear-gradient(#faecca 20%, #ffffff 30%, #ffffff 100%);
}
.groupon-title {
background: url($IMG_URL+'/imgs/tag/groupon_title_bg.png') no-repeat;
background-position: center top;
background-size: 100% auto;
}
.activity-wrap {
background-color: #fff;
min-height: 300rpx;
.title-box {
.title-text {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.more-box {
.more-text {
font-size: 22rpx;
font-weight: 500;
color: #333333;
}
.more-icon {
font-size: 24rpx;
color: #333333;
}
}
}
.scroll-box,
.goods-box {
height: 380rpx;
width: 100%;
}
}
// 小商品卡片
.min-goods {
width: 220rpx;
height: 380rpx;
background: #fffaef;
box-shadow: 0px 7rpx 8rpx 1rpx rgba(162, 117, 27, 0.24);
border-radius: 10rpx;
.img-box {
width: 220rpx;
height: 220rpx;
overflow: hidden;
position: relative;
border-radius: 10rpx 10rpx 0 0;
.img {
width: 220rpx;
height: 220rpx;
background-color: #ccc;
}
}
.mgoods-card-bottom {
height: 160rpx;
background: url($IMG_URL+'/imgs/tag/groupon_goods_bg.png') no-repeat;
background-position: bottom center;
background-size: 100% 100%;
}
.goods-title {
font-size: 26rpx;
font-weight: 500;
color: #000000;
width: 180rpx;
line-height: 26rpx;
}
.price {
font-size: 30rpx;
font-weight: 500;
color: #ff0000;
&::before {
content: '¥';
font-size: 24rpx;
}
}
.original-price {
font-size: 20rpx;
font-weight: 500;
text-decoration: line-through;
color: #c4c4c4;
}
.groupon-num-box {
.avatar-box {
direction: rtl;
unicode-bidi: bidi-override;
height: 30rpx;
.avatar-img {
width: 30rpx;
height: 30rpx;
border-radius: 50%;
background-color: #f5f5f5;
}
}
.groupon-num-text {
font-size: 18rpx;
font-weight: 500;
color: #e9b461;
}
}
}
// 大商品卡片
.big-goods {
width: 710rpx;
min-height: 260rpx;
background: #ffffff;
box-shadow: 0px 7rpx 8rpx 1rpx #fffaef;
border-radius: 20rpx;
.goods-img {
width: 220rpx;
height: 220rpx;
border-radius: 6rpx;
}
.card-right {
width: 430rpx;
height: 100%;
}
.goods-title {
font-size: 26rpx;
font-weight: 600;
width: 400rpx;
color: #000000;
vertical-align: middle;
.title-tag {
transform: scale(0.9);
}
}
.subtitle-text {
font-size: 22rpx;
width: 400rpx;
font-weight: 500;
color: #666666;
}
.buy-btn {
width: 120rpx;
line-height: 50rpx;
background: linear-gradient(90deg, #ff6600 0%, #fe832a 100%);
border-radius: 25rpx;
font-size: 24rpx;
font-weight: 500;
color: #ffffff;
}
// 拼团
.sell-box {
background: rgba(#f9efd6, 0.3);
border-radius: 16rpx;
line-height: 32rpx;
.sell-num {
font-size: 20rpx;
font-weight: 400;
color: #ff6904;
}
.hot-icon {
font-size: 26rpx;
color: #ff6904;
margin-right: 8rpx;
}
}
.group-num {
font-size: 20rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
margin-left: 20rpx;
}
// 价格
.price {
color: #ff0000;
font-weight: 600;
&::before {
content: '¥';
font-size: 20rpx;
}
}
.origin-price {
color: #c4c4c4;
font-size: 24rpx;
text-decoration: line-through;
&::before {
content: '¥';
font-size: 20rpx;
}
}
}
</style>

@ -0,0 +1,227 @@
<template>
<!-- 为你推荐 -->
<view class="hot-goods u-m-b-10 u-p-x-16">
<view class="u-waterfall" v-if="goodsType === 1">
<view id="u-left-column" class="u-column">
<view class="goods-item u-m-b-16 u-flex u-row-center u-col-center" v-for="leftGoods in leftList" :key="leftGoods.id">
<shopro-goods-card
:detail="leftGoods"
:type="leftGoods.activity_type"
:image="leftGoods.image"
:title="leftGoods.title"
:subtitle="leftGoods.subtitle"
:price="leftGoods.price"
:originPrice="leftGoods.original_price"
:sales="leftGoods.sales"
:tagTextList="leftGoods.activity_discounts_tags"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: leftGoods.id } })"
></shopro-goods-card>
</view>
</view>
<view id="u-right-column" class="u-column">
<view class="goods-item u-m-b-16 u-flex u-row-center u-col-center" v-for="rightGoods in rightList" :key="rightGoods.id">
<shopro-goods-card
:detail="rightGoods"
:type="rightGoods.activity_type"
:image="rightGoods.image"
:title="rightGoods.title"
:subtitle="rightGoods.subtitle"
:price="rightGoods.price"
:originPrice="rightGoods.original_price"
:sales="rightGoods.sales"
:tagTextList="rightGoods.activity_discounts_tags"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: rightGoods.id } })"
></shopro-goods-card>
</view>
</view>
</view>
<!-- m -->
<view class="big-card-wrap u-p-10" v-if="goodsType === 2">
<block v-for="item in goodsList" :key="item.id">
<sh-goods-card
:detail="item"
:type="item.activity_type"
:image="item.image"
:title="item.title"
:subtitle="item.subtitle"
:price="item.price"
:originPrice="item.original_price"
:sales="item.sales"
:tagTextList="item.activity_discounts_tags"
@click="$Router.push({ path: '/pages/goods/detail', query: { id: item.id } })"
></sh-goods-card>
</block>
</view>
<button v-show="total > perPage" class="u-reset-button refresh-btn u-m-y-20 u-flex u-col-center u-row-center" @tap.stop="loadMore">
<text class="u-m-r-6 u-iconfont uicon-reload" style="font-size: 28rpx;color: #999" :class="{ 'refresh-active': isRefresh }"></text>
{{ listParams.page >= lastPage ? '收起' : '加载更多' }}
</button>
</view>
</template>
<script>
/**
* 自定义之为你推荐
* @property {Object} detail - 推荐商品信息
*/
import shGoodsCard from './sh-goods-card.vue';
export default {
components: {
shGoodsCard
},
data() {
return {
listParams: {
page: 1
}, //当前分页
lastPage: 1, //总分页
total: 0, //总商品数
perPage: 0, //单页商品数
goodsList: [],
isRefresh: false,
// 瀑布流 350-330
addTime: 100, //排序间隙时间
leftHeight: 0,
rightHeight: 0,
leftList: [],
rightList: [],
tempList: [],
goodsType: this.detail.style // 商品模板
};
},
props: {
detail: {
type: Object,
default: () => {}
}
},
created() {
if (this.detail.id) {
this.listParams.category_id = this.detail.id;
this.getGoodsList();
}
if (this.detail.ids) {
this.listParams.goods_ids = this.detail.ids;
this.getGoodsList();
}
},
methods: {
// 瀑布流相关
async splitData() {
if (!this.tempList.length) return;
let item = this.tempList[0];
if (!item) return;
// 分左右
if (this.leftHeight < this.rightHeight) {
this.leftHeight += item.activity_discounts_tags?.length ? 350 : 330;
this.leftList.push(item);
} else if (this.leftHeight > this.rightHeight) {
this.rightHeight += item.activity_discounts_tags?.length ? 350 : 330;
this.rightList.push(item);
} else {
this.leftHeight += item.activity_discounts_tags?.length ? 350 : 330;
this.leftList.push(item);
}
// 移除临时列表的第一项,如果临时数组还有数据,继续循环
this.tempList.splice(0, 1);
if (this.tempList.length) {
setTimeout(() => {
this.splitData();
}, this.addTime);
}
},
clear() {
this.leftList = [];
this.rightList = [];
this.leftHeight = 0;
this.rightHeight = 0;
this.tempList = [];
},
// 商品列表
getGoodsList() {
let that = this;
that.$http('goods.lists', this.listParams).then(res => {
if (res.code === 1) {
this.lastPage = res.data.last_page;
this.total = res.data.total;
this.perPage = res.data.per_page;
this.isRefresh = false;
that.goodsList = [...that.goodsList, ...res.data.data];
that.tempList = res.data.data;
that.goodsList.length && that.splitData();
}
});
},
// 加载更多
loadMore() {
if (!this.isRefresh) {
// 加载更多
if (this.listParams.page < this.lastPage) {
this.isRefresh = true;
this.listParams.page += 1;
this.getGoodsList();
} else {
// 重置为1页数据
this.listParams.page = 1;
this.lastPage = 1;
this.goodsList = [];
this.clear();
this.getGoodsList();
}
}
}
}
};
</script>
<style lang="scss">
@mixin vue-flex($direction: row) {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: $direction;
/* #endif */
}
.u-waterfall {
@include vue-flex;
flex-direction: row;
align-items: flex-start;
}
.u-column {
@include vue-flex;
flex: 1;
flex-direction: column;
height: auto;
}
.u-image {
width: 100%;
}
// 为你推荐
.hot-goods {
background: none;
.refresh-btn {
margin-left: 50%;
transform: translateX(-50%);
width: 156rpx;
line-height: 50rpx;
background: #ffffff;
border-radius: 25rpx;
font-size: 22rpx;
font-weight: 500;
color: #999999;
white-space: nowrap;
}
.refresh-active {
transform: rotate(360deg);
transition: all linear 0.5s;
}
}
</style>

@ -0,0 +1,108 @@
<template>
<view class="live-el u-m-p-20 u-m-b-10" v-if="showLive">
<view class="head">
<text class="head-title">{{ detail.name }}</text>
<view class="head-more u-flex u-col-center" @tap="$Router.push('/pages/app/live/list')">
<text class="u-m-r-10 more-text">更多直播</text>
<text class="iconfont icon-youjiantou-tianchong more-icon"></text>
</view>
</view>
<!-- 大图 -->
<view v-if="liveType == 1" v-for="live in liveList" :key="live.id"><shopro-live-card :type="1" :detail="live"></shopro-live-card></view>
<!-- 小图 -->
<view class="content-two" v-if="liveType == 2">
<view class="content-two__item" v-for="live in liveList" :key="live.id">
<shopro-live-card :type="2" :detail="live" :wh="345">
<block slot="liveGoods"><text></text></block>
</shopro-live-card>
</view>
</view>
</view>
</template>
<script>
/**
* 自定义之直播卡片
* @property {Object} detail - 直播信息
*/
export default {
components: {},
data() {
return {
liveList: [],
showLive: this.detail.ids,
liveType: this.detail.style
};
},
props: {
detail: {}
},
created() {
this.showLive && this.getLiveList();
},
computed: {},
methods: {
// 直播列表
getLiveList() {
let that = this;
that.$http('common.live', {
type: 'all',
ids: that.detail.ids
}).then(res => {
if (res.code === 1) {
that.liveList = res.data;
}
});
},
goRoom(live) {
wx.navigateTo({
url: `plugin-private://wx2b03c6e691cd7370/pages/live-player-plugin?room_id=${live.room_id}`
});
}
}
};
</script>
<style lang="scss">
.live-el {
background: #fff;
padding: 30rpx 20rpx;
.head {
display: flex;
justify-content: space-between;
align-items: center;
&-title {
font-size: 32rpx;
font-weight: bold;
color: rgba(51, 51, 51, 1);
}
&-more {
.more-text {
font-size: 22rpx;
font-weight: 500;
color: #333333;
}
.more-icon {
font-size: 24rpx;
color: #333333;
}
}
}
// 双图直播
.content-two {
width: 710rpx;
display: flex;
flex-wrap: wrap;
&__item {
margin-right: 20rpx;
margin-top: 20rpx;
width: 345rpx;
&:nth-child(2n) {
margin-right: 0;
}
}
}
}
</style>

@ -0,0 +1,150 @@
<template>
<!-- 订单卡片 -->
<view class="sh-order-box u-flex u-m-b-10 u-p-r-20">
<view class="order-box u-flex">
<view class="order-item u-flex-col " @tap="jump('/pages/order/list', { type: order.type })" v-for="order in orderNav" :key="order.id">
<view class="u-flex-col item-box u-col-center">
<image class="order-img" :src="$IMG_URL + order.img" mode=""></image>
<text class="item-title">{{ order.title }}</text>
<view class="badge" v-if="orderNum && orderNum[order.type]">{{ orderNum[order.type] }}</view>
</view>
</view>
<view class="order-item u-flex-col " @tap="jump('/pages/order/after-sale/list')">
<view class="u-flex-col item-box u-col-center">
<image class="order-img" :src="$IMG_URL + '/imgs/user/tab55.png'" mode=""></image>
<text class="item-title">退换货</text>
</view>
</view>
</view>
<view class="order-item u-flex-col all-order " @tap="jump('/pages/order/list', { type: 'all' })">
<image class="cut-off--line" :src="$IMG_URL + '/imgs/user/cut_off_line.png'" mode=""></image>
<view class="u-flex-col item-box u-col-center">
<image class="order-img" :src="$IMG_URL + '/imgs/user/all_order.png'" mode="aspectFill"></image>
<text class="item-title">全部订单</text>
</view>
</view>
</view>
</template>
<script>
/**
* 订单中心卡片
*
*/
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {},
data() {
return {
orderNav: [
{
id: 1,
title: '待付款',
img: '/imgs/user/tab11.png',
type: 'nopay'
},
// {
// id: 2,
// title: '待发货',
// img: this.$IMG_URL + '/imgs/user/tab22.png',
// type: 'nosend'
// },
{
id: 3,
title: '待收货',
img: '/imgs/user/tab33.png',
type: 'noget'
},
{
id: 4,
title: '待评价',
img: '/imgs/user/tab44.png',
type: 'nocomment'
}
// {
// id: 5,
// title: '退换货',
// img: this.$IMG_URL + '/imgs/user/tab55.png',
// type: 'aftersale'
// }
]
};
},
computed: {
...mapGetters(['isLogin', 'userOtherData']),
orderNum() {
return this.userOtherData.order_num || 0;
}
},
methods: {
jump(path, query) {
this.$Router.push({
path: path,
query: query
});
}
}
};
</script>
<style lang="scss">
.badge {
position: absolute;
/* #ifndef APP-NVUE */
display: inline-flex;
/* #endif */
justify-content: center;
align-items: center;
line-height: 24rpx;
padding: 4rpx 10rpx;
border-radius: 100rpx;
color: #fff;
font-size: 24rpx;
z-index: 9;
background-color: $u-type-error;
transform: scale(0.8);
transform-origin: center center;
left: 100rpx;
top: -6rpx;
white-space: nowrap;
}
// 订单卡片
.sh-order-box {
height: 180rpx;
background: #fff;
.order-box {
flex: 4;
}
.all-order {
position: relative;
.cut-off--line {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: (750rpx/5) - 15rpx;
width: 30rpx;
height: 136rpx;
}
}
.order-item {
flex: 1;
.item-box {
position: relative;
}
.order-img {
width: 46rpx;
height: 46rpx;
}
.item-title {
font-size: 26rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
line-height: 24rpx;
margin-top: 20rpx;
}
}
}
</style>

@ -0,0 +1,46 @@
<template>
<view class="sh-richtext-box u-m-b-10" v-if="richText.content"><u-parse :html="richText.content"></u-parse></view>
</template>
<script>
/**
* 自定义之富文本卡片
* @property {Number|String} id - 富文本id
*/
export default {
components: {},
data() {
return {
richText: ''
};
},
computed: {},
props: {
richId: {
type: [Number, String],
default: 0
}
},
created() {
this.richId && this.getRichText();
},
methods: {
getRichText() {
this.$http('common.richText', {
id: this.richId
}).then(res => {
if (res.code === 1) {
this.richText = res.data;
}
});
}
}
};
</script>
<style lang="scss">
.sh-richtext-box {
background: #fff;
padding: 30rpx;
}
</style>

@ -0,0 +1,46 @@
<template>
<view class="search u-flex u-row-center u-col-center u-m-b-10 u-p-x-20" @tap="$Router.push('/pages/public/search')">
<view class="search-content u-flex u-col-center">
<text class="u-iconfont uicon-search search-icon"></text>
<view class="pl-text">搜索</view>
</view>
</view>
</template>
<script>
/**
* 自定义之搜索样式卡片
* @property {Object} detail - 搜索信息
*/
export default {
components: {},
data() {
return {};
},
computed: {},
methods: {}
};
</script>
<style lang="scss">
.search {
height: 100rpx;
width: 750rpx;
background: #fff;
.search-content {
width: 710rpx;
padding: 0 18rpx;
background-color: #f2f2f2;
border-radius: 100rpx;
height: 64rpx;
.search-icon {
color: #606266;
size: 34rpx;
}
.pl-text {
color: $u-tips-color;
margin-left: 10rpx;
}
}
}
</style>

@ -0,0 +1,300 @@
<template>
<!-- 活动商品 -->
<view class="activity-wrap u-p-x-20 u-p-b-20 u-m-b-10 seckill-card" v-if="showActivity">
<!-- 标题栏 -->
<view class="title-box u-flex u-row-between u-p-y-20 seckill-title">
<view class="u-flex u-col-center">
<view class="title-text u-m-r-20 u-ellipsis-1">{{ detail.name }}</view>
<u-count-down
class="count-down-demo"
:timestamp="timestamp"
separator-color="#ffbbbb "
bg-color="#ffbbbb "
ref="uCountDown"
color="#fff"
@end="seckillEnd"
autoplay
></u-count-down>
</view>
<view class="more-box u-flex" @tap="$Router.push('/pages/activity/seckill/list')">
<text class="more-text u-m-r-10">更多抢购</text>
<text class="iconfont icon-youjiantou-tianchong more-icon"></text>
</view>
</view>
<!-- 活动商品 -->
<!-- m -->
<scroll-view v-if="seckillType === 1" class="scroll-box" scroll-x scroll-anchoring>
<view class="goods-box u-flex">
<view class="min-goods u-m-r-14" v-for="mgoods in goodsList" :key="mgoods.id" @tap="jump('/pages/goods/detail', { id: mgoods.id })">
<view class="img-box"><image class="img" :src="mgoods.image" mode=""></image></view>
<view class="mgoods-card-bottom u-p-20">
<view class="goods-title u-m-b-10 u-ellipsis-1">{{ mgoods.title }}</view>
<view class="price-box font-OPPOSANS">
<view class="price u-m-b-10">{{ mgoods.price }}</view>
<view class="original-price">¥{{ mgoods.original_price }}</view>
</view>
</view>
</view>
</view>
</scroll-view>
<!--b-->
<view
v-if="seckillType === 2"
class="big-goods u-flex u-p-20 u-col-top u-m-b-16"
v-for="item in goodsList"
:key="item.id"
@tap="jump('/pages/goods/detail', { id: item.id })"
>
<image class="goods-img" :src="item.image" mode="aspectFill"></u-image>
<view class=" card-right u-m-l-20 u-flex-col u-row-between">
<view class="">
<view class="goods-title u-ellipsis-1 u-m-t-10 u-m-b-10">
<view class=" sm cu-tag bg-red radius title-tag u-m-r-10" >秒杀</view>
{{ item.title }}
</view>
<view v-show="item.subtitle" class="subtitle-text u-m-b-10 u-ellipsis-1">{{ item.subtitle }}</view>
</view>
<view class="u-flex u-m-y-20">
<u-line-progress
style="width:210rpx;"
height="18"
:show-percent="false"
:percent="Number(item.percent)"
inactive-color=" #e7e7e7"
active-color="#ffbbbb "
></u-line-progress>
<view class="progress-text">已售出{{ item.sales }}件</view>
</view>
<view class=" u-flex u-row-between u-col-center">
<view class="u-flex u-col-bottom">
<view class="price u-m-r-10">{{ item.price }}</view>
<view class="origin-price">{{ item.original_price }}</view>
</view>
<button class="u-reset-button buy-btn">去抢购</button>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 自定义之秒杀样式组件
* @property {Object} detail - 秒杀商品信息
*/
export default {
components: {},
data() {
return {
timestamp: 0, //倒计时
goodsList: [],
showActivity: true, //是否显示活动。
seckillType: this.detail.style
};
},
props: {
detail: {
type: Object,
default: () => {
return {};
}
}
},
watch: {},
computed: {},
created() {
this.getActivityGoodsList();
},
methods: {
// 路由跳转
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 秒杀计时结束
seckillEnd() {
this.showActivity = false;
},
// 获取秒杀商品
getActivityGoodsList() {
let that = this;
that.$http('goods.activity', {
activity_id: that.detail.id
}).then(res => {
if (res.code === 1) {
that.goodsList = res.data.goods.data;
that.goodsList.map(item => {
item.percent = item.stock + item.sales > 0 ? ((item.sales / (item.sales + item.stock)) * 100).toFixed(2) : 0;
});
let nowTime = new Date().getTime();
let endTime = res.data.endtime * 1000;
that.timestamp = (endTime - nowTime) / 1000;
that.timestamp > 0 ? that.$refs.uCountDown.start() : (that.showActivity = false);
} else {
that.showActivity = false;
console.log(`%cerr:秒杀活动已结束`, 'color:green;background:yellow');
}
});
}
}
};
</script>
<style lang="scss" scoped>
.seckill-card {
background: linear-gradient(#ffebec 20%, #fff 30%, #fff 100%);
}
.seckill-title {
background: url($IMG_URL+'/imgs/tag/seckill_title_bg.png') no-repeat;
background-position: center top;
background-size: 100% auto;
}
.activity-wrap {
background-color: #fff;
min-height: 300rpx;
.title-box {
.title-text {
font-size: 32rpx;
font-weight: bold;
color: #333333;
}
.more-box {
.more-text {
font-size: 22rpx;
font-weight: 500;
color: #333333;
}
.more-icon {
font-size: 24rpx;
color: #333333;
}
}
}
.scroll-box,
.goods-box {
height: 380rpx;
width: 100%;
}
}
// 小商品卡片
.min-goods {
width: 220rpx;
height: 380rpx;
background: #fff7f7;
box-shadow: 0px 7rpx 7rpx 0px rgba(255, 80, 94, 0.32);
border-radius: 10rpx;
.img-box {
width: 220rpx;
height: 220rpx;
overflow: hidden;
position: relative;
border-radius: 10rpx 10rpx 0 0;
.img {
width: 220rpx;
height: 220rpx;
background-color: #ccc;
}
}
.mgoods-card-bottom {
height: 160rpx;
background: url($IMG_URL+'/imgs/tag/seckill_goods_bg.png') no-repeat;
background-position: bottom center;
background-size: 100% 100%;
}
.goods-title {
font-size: 26rpx;
font-weight: 500;
color: #000000;
line-height: 26rpx;
}
.price-box {
.price {
font-size: 30rpx;
font-weight: 500;
color: #ff0000;
&::before {
content: '¥';
font-size: 24rpx;
}
}
.original-price {
font-size: 20rpx;
font-weight: 500;
text-decoration: line-through;
color: #c4c4c4;
}
}
}
// 大商品卡片
.big-goods {
width: 710rpx;
min-height: 260rpx;
background: #ffffff;
box-shadow: 0px 7rpx 8rpx 1rpx rgba(254, 76, 29, 0.05);
border-radius: 20rpx;
.goods-img{
width: 220rpx;
height: 220rpx;
border-radius: 6rpx;
}
.card-right {
width: 430rpx;
height: 100%;
}
.goods-title {
font-size: 26rpx;
font-weight: 600;
width: 400rpx;
color: #000000;
}
.subtitle-text {
font-size: 22rpx;
width: 400rpx;
font-weight: 500;
color: #666666;
}
.buy-btn {
width: 120rpx;
line-height: 50rpx;
background: linear-gradient(90deg, #d01325, #ed3c30);
border-radius: 25rpx;
font-size: 24rpx;
font-weight: 500;
color: #ffffff;
}
.progress-text {
color: #c4c4c4;
font-size: 20rpx;
margin-left: 25rpx;
}
// 价格
.price {
color: #ff0000;
font-weight: 600;
&::before {
content: '¥';
font-size: 20rpx;
}
}
.origin-price {
color: #c4c4c4;
font-size: 24rpx;
text-decoration: line-through;
&::before {
content: '¥';
font-size: 20rpx;
}
}
}
</style>

@ -0,0 +1,64 @@
<template>
<view class="sh-title-card u-m-b-10">
<view class="title-box">
<image class="title-bg" :src="bgImage" mode="aspectFill"></image>
<view class="title-text" :style="{ color: titleColor }">{{ title }}</view>
</view>
</view>
</template>
<script>
/**
* shTitleCard 组件标题栏
* @property {String} bgImage - 背景图
* @property {String} title - 标题
* @property {String} titleColor - 标题颜色
*/
export default {
components: {},
data() {
return {};
},
computed: {},
props: {
bgImage: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
titleColor: {
type: String,
default: ''
}
},
methods: {}
};
</script>
<style lang="scss">
.sh-title-card {
width: 750rpx;
}
.title-box {
width: 710rpx;
height: 88rpx;
margin: 0 auto;
position: relative;
border-radius: 30rpx;
.title-bg {
width: 100%;
height: 100%;
}
.title-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-weight: bold;
}
}
</style>

@ -0,0 +1,106 @@
<template>
<!-- 钱包卡片 -->
<view class="sh-wallet-box u-flex u-m-b-10 u-p-r-20">
<view class="u-flex wallet-left">
<view class="wallet-item u-flex-col u-col-center" @tap="jump('/pages/user/wallet/index')">
<text class="wallet-item__detail item-balance u-ellipsis-1">{{ userInfo.money || '0' }}</text>
<text class="wallet-item__title">账户余额</text>
</view>
<view class="wallet-item u-flex-col u-col-center" @tap="jump('/pages/user/wallet/score-balance')">
<text class="wallet-item__detail item-score u-ellipsis-1">{{ userInfo.score || '0' }}</text>
<text class="wallet-item__title">积分</text>
</view>
<view class="wallet-item u-flex-col u-col-center" @tap="jump('/pages/app/coupon/list')">
<text class="wallet-item__detail item-coupon u-ellipsis-1">{{ userOtherData.coupons_num || '0' }}</text>
<text class="wallet-item__title">优惠券</text>
</view>
</view>
<view class="wallet-item u-flex-col wallet-right u-col-center" @tap="jump('/pages/user/wallet/index')">
<image class="cut-off--line" :src="$IMG_URL + '/imgs/user/cut_off_line.png'" mode=""></image>
<image class="wallet-img" :src="$IMG_URL + '/imgs/user/wallet.png'" mode="aspectFill"></image>
<text class="wallet-item__title">我的钱包</text>
</view>
</view>
</template>
<script>
/**
* 钱包样式卡片
*/
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {},
data() {
return {};
},
computed: {
...mapGetters(['userInfo', 'userOtherData'])
},
methods: {
jump(path, query) {
this.$Router.push({
path: path,
query: query
});
}
}
};
</script>
<style lang="scss">
// 钱包卡片
.sh-wallet-box {
background: #fff;
height: 180rpx;
position: relative;
.wallet-left {
flex: 4;
}
.wallet-right {
position: relative;
.cut-off--line {
position: absolute;
top: 50%;
transform: translateY(-50%);
right: (750rpx/5) - 15rpx;
width: 30rpx;
height: 136rpx;
}
}
.wallet-item {
flex: 1;
.wallet-img {
width: 46rpx;
height: 46rpx;
}
.wallet-item__detail {
font-size: 28rpx;
width: 180rpx;
text-align: center;
font-weight: 500;
color: rgba(168, 112, 13, 1);
}
.wallet-item__title {
font-size: 26rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
margin-top: 20rpx;
}
.item-balance::after {
content: '元';
font-size: 16rpx;
margin-left: 4rpx;
}
.item-score::after {
content: '个';
font-size: 14rpx;
margin-left: 4rpx;
}
.item-coupon::after {
content: '张';
font-size: 16rpx;
margin-left: 4rpx;
}
}
}
</style>

@ -0,0 +1,215 @@
<!-- 首页 -->
<template>
<view class="home-wrap u-m-b-20">
<!-- 空白页 -->
<!-- #ifdef APP-PLUS -->
<u-no-network @retry="init"></u-no-network>
<!-- #endif -->
<shopro-empty
v-if="!hasTemplate"
:image="$IMG_URL + '/imgs/empty/template_empty.png'"
tipText="暂未找到模板,请前往装修~"
></shopro-empty>
<view v-else-if="isConnected && isRefresh" class="content-box">
<!-- 导航栏 -->
<home-head
v-if="headSwiperList && headSwiperList.length"
:scrollTop="scrollTop"
borderRadius="0"
:navTitle="initShop.name"
:list="headSwiperList"
></home-head>
<!-- 自定义模块 -->
<view class="template-box">
<block v-for="(item, index) in homeTemplate" :key="item.id">
<!-- 轮播 -->
<sh-banner
v-if="item.type === 'banner' && index !== 0"
:Px="item.content.x"
:Py="item.content.y"
:borderRadius="item.content.radius"
:height="item.content.height"
:list="item.content.list"
></sh-banner>
<!-- 搜索 -->
<sh-search v-if="item.type === 'search'"></sh-search>
<!-- 滑动宫格 -->
<sh-grid-swiper
v-if="item.type === 'menu'"
:list="item.content.list"
:oneRowNum="item.content.style"
></sh-grid-swiper>
<!-- 推荐商品 -->
<sh-hot-goods
v-if="item.type === 'goods-list' || item.type === 'goods-group'"
:detail="item.content"
></sh-hot-goods>
<!-- 广告魔方 -->
<sh-adv v-if="item.type === 'adv'" :detail="item.content"></sh-adv>
<!-- 优惠券 -->
<sh-coupon v-if="item.type === 'coupons'" :detail="item.content"></sh-coupon>
<!-- 秒杀-->
<sh-seckill v-if="item.type === 'seckill'" :detail="item.content"></sh-seckill>
<!-- 拼团 -->
<sh-groupon v-if="item.type === 'groupon'" :detail="item.content"></sh-groupon>
<!-- 富文本 -->
<sh-richtext v-if="item.type === 'rich-text'" :richId="item.content.id"></sh-richtext>
<!-- 功能标题 -->
<sh-title-card
v-if="item.type === 'title-block'"
:title="item.content.name"
:bgImage="item.content.image"
:titleColor="item.content.color"
></sh-title-card>
<!-- 直播 -->
<!-- #ifdef MP-WEIXIN -->
<sh-live v-if="item.type === 'live' && HAS_LIVE" :detail="item.content"></sh-live>
<!-- #endif -->
</block>
</view>
<!-- 分类选项卡 -->
<sh-category-tabs
v-if="categoryTabsData && categoryTabsData.category_arr && categoryTabsData.category_arr.length"
:enable="enable"
:styleType="categoryTabsData.style"
:tabsList="categoryTabsData.category_arr"
></sh-category-tabs>
<!-- 登录提示 -->
<shopro-auth-modal></shopro-auth-modal>
<!-- 悬浮按钮 -->
<shopro-float-btn></shopro-float-btn>
<!-- 连续弹窗提醒 -->
<shopro-notice-modal v-if="!showPrivacy && isLogin"></shopro-notice-modal>
<!-- 隐私协议 -->
<!-- #ifdef APP-PLUS -->
<privacy-modal v-if="initShop && initShop.name" v-model="showPrivacy"></privacy-modal>
<!-- #endif -->
<!-- #ifdef H5 -->
<view class="tabbar-hack" style="height: 120rpx; width:100%;"></view>
<!-- #endif -->
</view>
<!-- <shopro-tabbar></shopro-tabbar> -->
</view>
</template>
<script>
import shBanner from './components/sh-banner.vue';
import shGridSwiper from './components/sh-grid-swiper.vue';
import shHotGoods from './components/sh-hot-goods.vue';
import shAdv from './components/sh-adv.vue';
import shCoupon from './components/sh-coupon.vue';
import shSeckill from './components/sh-seckill.vue';
import shGroupon from './components/sh-groupon.vue';
import shRichtext from './components/sh-richtext.vue';
import shTitleCard from './components/sh-title-card.vue';
import shSearch from './components/sh-search.vue';
import shCategoryTabs from './components/sh-category-tabs.vue';
import privacyModal from './index/privacy-modal.vue';
import homeHead from './index/home-head.vue';
// #ifdef MP-WEIXIN
import { HAS_LIVE } from '@/env';
import shLive from './components/sh-live.vue';
// #endif
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
export default {
components: {
shBanner,
shGridSwiper,
shGroupon,
shHotGoods,
shAdv,
shCoupon,
shSeckill,
shRichtext,
shTitleCard,
shSearch,
shCategoryTabs,
privacyModal,
homeHead,
// #ifdef MP-WEIXIN
shLive
// #endif
},
data() {
return {
// #ifdef MP-WEIXIN
HAS_LIVE: HAS_LIVE,
// #endif
isRefresh: true,
enable: false, //是否开启吸顶。
isConnected: true, //是否有网
showPrivacy: false, //协议
scrollTop: 0
};
},
computed: {
...mapGetters(['initShop', 'homeTemplate', 'hasTemplate', 'isLogin']),
// 头部模块数据
headSwiperList() {
if (this.homeTemplate?.length) {
return this.homeTemplate[0]?.content?.list;
}
},
// 分类选项卡数据
categoryTabsData() {
if (this.homeTemplate?.length) {
return this.homeTemplate[this.homeTemplate.length - 1]?.content;
}
}
},
onPullDownRefresh() {
this.init();
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
},
onShow() {
let that = this;
this.enable = true;
this.isLogin && this.getCartList();
// 网络变化检测
uni.onNetworkStatusChange(res => {
this.isConnected = res.isConnected;
res.isConnected && this.init();
});
},
onHide() {
this.enable = false;
},
onLoad() {
// #ifdef APP-VUE
// plus.runtime.disagreePrivacy();
console.log(plus.runtime.isAgreePrivacy(), 1111111111);
// app隐私协议弹窗
if (!plus.runtime.isAgreePrivacy()) {
this.showPrivacy = true;
this.showNoticeModal = false;
}
// #endif
},
methods: {
...mapActions(['appInit', 'getTemplate', 'getCartList']),
// 初始化
init() {
this.isRefresh = false;
return Promise.all([this.getTemplate()]).then(() => {
uni.stopPullDownRefresh();
this.isRefresh = true;
});
}
}
};
</script>
<style lang="scss"></style>

@ -0,0 +1,244 @@
<template>
<!-- 首页标题栏,轮播图整合插件 -->
<view class="banner-swiper-wrap u-m-b-10">
<!-- 标题栏 -->
<view class="u-navbar u-navbar-fixed" :style="[navbarStyle]">
<view class="nav-bg" :style="[navBg]"></view>
<view class="u-status-bar" :style="{ height: statusBarHeight + 'px' }"></view>
<view class="u-navbar-inner" :style="[navbarInnerStyle]">
<view class="u-back-wrap">
<view class="u-icon-wrap u-back-text u-line-1" :style="[navTitleStyle, navTitleColor]">
{{ navTitle || '' }}
</view>
</view>
<view
hover-class="hover-search"
class="search-box u-flex u-row-center u-col-center u-m-r-30"
@tap="$Router.push('/pages/public/search')"
>
<view class="u-iconfont uicon-search" :style="[navTitleColor]"></view>
</view>
</view>
</view>
<!-- 轮播组件 -->
<swiper
class="screen-swiper square-dot"
@change="onChange"
style="height: 520rpx"
:indicator-dots="true"
:circular="true"
:autoplay="true"
interval="5000"
duration="500"
>
<swiper-item v-for="(item, index) in list" :key="index" @tap="onSwiper(index)">
<image :src="item.image" mode="aspectFill"></image>
</swiper-item>
</swiper>
</view>
</template>
<script>
// 获取系统状态栏的高度
let systemInfo = uni.getSystemInfoSync();
let menuButtonInfo = {};
// #ifdef MP-WEIXIN || MP-BAIDU || MP-TOUTIAO || MP-QQ
menuButtonInfo = uni.getMenuButtonBoundingClientRect();
// #endif
/**
* home-head-轮播卡片,主要为了处理数据
* @property {Array} list 轮播图数据,
* @property {String} mode 指示器模式
* @property {String} navTitle 项目名称
*/
export default {
components: {},
data() {
return {
navBgImage: '',
changeNavBackground: false,
swiperCur: 0,
statusBarHeight: systemInfo.statusBarHeight,
navBg: { opacity: 0 },
navTitleColor: {
color: '#fff'
}
};
},
props: {
scrollTop: {
type: [String, Number],
default: 0
},
// 轮播图的数据,格式如:[{image: 'xxxx', title: 'xxxx'},{image: 'yyyy', title: 'yyyy'}],其中title字段可选
list: {
type: Array,
default() {
return [];
}
},
navTitle: {
type: String,
default: 'Shopro商城'
},
navTitleStyle: {
type: Object,
default: () => {
return {
color: '#fff',
fontSize: '38rpx'
};
}
}
},
watch: {
scrollTop(newValue, oldValue) {
let top = newValue;
this.navBg = {
opacity: top > this.navbarHeight ? 1 : top * 0.01
};
this.navTitleColor = {
color: top > this.navbarHeight ? '#000' : '#fff'
};
this.setStatusStyle(top > this.navbarHeight ? 'dark' : 'light');
}
},
computed: {
// 导航栏内部盒子的样式
navbarInnerStyle() {
let style = {};
style.height = this.navbarHeight + 'px';
// #ifdef MP
let rightButtonWidth = systemInfo.windowWidth - menuButtonInfo.left;
style.marginRight = rightButtonWidth + 'px';
// #endif
return style;
},
// 整个导航栏的样式
navbarStyle() {
let style = {};
style.zIndex = this.$u.zIndex.navbar;
style.background = 'none';
Object.assign(style, this.background);
return style;
},
navbarHeight() {
// #ifdef APP-PLUS || H5
return 44;
// #endif
// #ifdef MP
return systemInfo.platform == 'ios' ? 44 : 48;
// #endif
}
},
created() {
this.navBgImage = this.list[0].image;
},
methods: {
onSwiper(e) {
this.$tools.routerTo(this.list[e].path);
},
onChange(e) {
this.swiperCur = e.detail.current;
},
setStatusStyle(status) {
// #ifdef H5
return false;
// #endif
if (status == 'light') {
uni.setNavigationBarColor({
frontColor: '#ffffff',
backgroundColor: '#000000',
animation: {
duration: 200,
timingFunc: 'easeIn'
}
});
// #ifdef APP-PLUS
plus.navigator.setStatusBarStyle('light');
// #endif
} else {
uni.setNavigationBarColor({
frontColor: '#000000',
backgroundColor: '#ffffff',
animation: {
duration: 200,
timingFunc: 'easeIn'
}
});
// #ifdef APP-PLUS
plus.navigator.setStatusBarStyle('dark');
// #endif
}
}
}
};
</script>
<style lang="scss" scoped>
@mixin vue-flex($direction: row) {
/* #ifndef APP-NVUE */
display: flex;
flex-direction: $direction;
/* #endif */
}
// 轮播
.banner-swiper-wrap {
height: 520rpx;
position: relative;
z-index: 100;
.search-box {
width: 66rpx;
height: 66rpx;
background: rgba(#000, 0.18);
border-radius: 100%;
}
.hover-search {
background: rgba(#fff, 0.18);
}
}
.u-navbar {
width: 100%;
}
.u-navbar-fixed {
position: fixed;
left: 0;
right: 0;
top: 0;
z-index: 991;
.nav-bg {
position: absolute;
left: 0;
top: 0;
z-index: 980;
width: 100%;
height: 100%;
background-color: #fff;
box-shadow: 0px 4px 6px rgba(140, 140, 140, 0.32);
}
}
.u-status-bar {
width: 100%;
}
.u-navbar-inner {
@include vue-flex;
justify-content: space-between;
position: relative;
align-items: center;
position: relative;
z-index: 990;
}
.u-back-wrap {
@include vue-flex;
align-items: center;
flex: 1;
flex-grow: 0;
padding: 14rpx 14rpx 14rpx 24rpx;
}
</style>

@ -0,0 +1,155 @@
<template>
<u-popup
v-model="showModal"
@close="close"
:border-radius="20"
mode="center"
length="auto"
:closeable="false"
>
<view class="service-contract-wrap u-flex-col u-row-center u-col-center">
<image
class="service-head-img"
:src="$IMG_URL + '/imgs/modal/servece_head.png'"
mode="widthFix"
></image>
<view class="service-title">用户隐私协议概况</view>
<view class="service-content">
感谢您使用{{
initShop.name
}},我们非常重视您的个人信息和隐私保护,在您使用服务前,请仔细阅读
<text
style="color: #eab866"
@tap="
jump('/pages/public/richtext', { id: initShop.privacy_protocol })
"
>《{{ initShop.name }}隐私协议》</text
>
,我们将会严格按照经您同意的各项条款使用您的个人信息,以便为您提供更好的服务。
</view>
<view class="service-tip"
>如您同意此条款,请点击“同意”并开始使用我们的产品和服务,我们将尽全力保护您的个人信息安全。</view
>
<view class="btn-box u-flex u-row-center u-col-center"
><button class="u-reset-button agree-btn" @tap="close">
知道了
</button></view
>
</view>
</u-popup>
</template>
<script>
import { mapMutations, mapActions, mapState, mapGetters } from "vuex";
export default {
components: {},
data() {
return {};
},
props: {
value: {},
},
computed: {
...mapGetters(["initShop"]),
showModal: {
get() {
return this.value;
},
set(val) {
this.$emit("input", val);
},
},
},
methods: {
close() {
this.showModal = false;
// #ifdef APP-VUE
plus.runtime.agreePrivacy();
// #endif
},
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas,
});
},
},
};
</script>
<style lang="scss">
.service-contract-wrap {
background-color: #fff;
position: relative;
left: 50%;
transform: translateX(-50%);
min-height: 850rpx;
border-radius: 20rpx;
width: 610rpx;
.service-head-img {
width: 610rpx;
}
.service-title {
font-size: 35rpx;
font-weight: bold;
color: rgba(255, 255, 255, 1);
line-height: 42rpx;
background: linear-gradient(
180deg,
rgba(62, 48, 25, 1) 0%,
rgba(109, 80, 40, 1) 100%
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 30rpx;
}
.service-content {
text-align: left;
font-size: 26rpx;
font-weight: 500;
color: rgba(102, 102, 102, 1);
line-height: 50rpx;
padding: 0 40rpx;
}
.service-tip {
text-align: left;
font-size: 26rpx;
font-weight: 500;
color: rgba(154, 154, 154, 1);
line-height: 50rpx;
padding: 0 40rpx;
}
.btn-box {
padding: 40rpx;
.cancel-btn {
width: 248rpx;
height: 70rpx;
border: 1rpx solid rgba(234, 182, 99, 1);
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: rgba(234, 182, 99, 1);
background-color: #fff;
}
.agree-btn {
width: 300rpx;
line-height: 70rpx;
background: linear-gradient(
90deg,
rgba(233, 181, 97, 1),
rgba(238, 204, 138, 1)
);
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
}
}
}
</style>

@ -0,0 +1,143 @@
<!-- 个人中心 -->
<template>
<view class="personal-wrap">
<!-- 个人信息卡片 -->
<userinfo-card v-if="userHeadData && userHeadData.style" :scrollTop="scrollTop" :detail="userHeadData" @onShare="onShare"></userinfo-card>
<!-- 自定义模块 -->
<view v-for="(item, index) in userTemplate" :key="item.id">
<!-- 轮播 -->
<sh-banner
v-if="item.type === 'banner' && index !== 0"
:Px="item.content.x"
:Py="item.content.y"
:borderRadius="item.content.radius"
:height="item.content.height"
:list="item.content.list"
></sh-banner>
<!-- 滑动宫格 -->
<sh-grid-swiper v-if="item.type === 'menu'" :list="item.content.list" :oneRowNum="item.content.style"></sh-grid-swiper>
<!-- 广告魔方 -->
<sh-adv v-if="item.type === 'adv'" :detail="item.content"></sh-adv>
<!-- 推荐商品 -->
<sh-hot-goods v-if="item.type === 'goods-list' || item.type === 'goods-group'" :detail="item.content"></sh-hot-goods>
<!-- 富文本 -->
<sh-richtext v-if="item.type === 'rich-text'" :richId="item.content.id"></sh-richtext>
<!-- 功能列表 -->
<sh-cell v-if="item.type === 'nav-list'" :list="item.content.list"></sh-cell>
<!-- 九宫格列表 -->
<sh-grid v-if="item.type === 'grid-list'" :list="item.content.list"></sh-grid>
<!-- 钱包 -->
<sh-wallet v-if="item.type === 'wallet-card'"></sh-wallet>
<!-- 订单卡片 -->
<sh-order-card v-if="item.type === 'order-card'"></sh-order-card>
</view>
<!-- copyright -->
<view class="copyright-box u-flex-col u-row-center u-col-center u-p-t-80 u-p-b-50" v-if="initShop.copyright">
<view class="copyright-text">{{ initShop.copyright[0] }}</view>
<view class="copyright-text">{{ initShop.copyright[1] }}</view>
</view>
<!-- #ifdef H5 -->
<view class="tabbar-hack" style="height: 120rpx; width:100%;"></view>
<!-- #endif -->
<!-- 关注弹窗 -->
<shopro-float-btn></shopro-float-btn>
<!-- 登录提示 -->
<shopro-auth-modal></shopro-auth-modal>
<!-- 分享组件 -->
<shopro-share v-model="showShare" posterType="user"></shopro-share>
<!-- <shopro-tabbar></shopro-tabbar> -->
</view>
</template>
<script>
import shBanner from './components/sh-banner.vue';
import shGridSwiper from './components/sh-grid-swiper.vue';
import shAdv from './components/sh-adv.vue';
import shRichtext from './components/sh-richtext.vue';
import shCell from './components/sh-cell.vue';
import shGrid from './components/sh-grid.vue';
import shOrderCard from './components/sh-order-card.vue';
import shWallet from './components/sh-wallet.vue';
import shHotGoods from './components/sh-hot-goods.vue';
import userinfoCard from './user/userinfo-card.vue';
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
import share from '@/shopro/share';
export default {
components: {
shBanner,
shGridSwiper,
shAdv,
shRichtext,
shCell,
shGrid,
shWallet,
shOrderCard,
shHotGoods,
userinfoCard
},
data() {
return {
scrollTop: 0,
showShare: false,
enable: false //是否开启吸顶。
};
},
computed: {
...mapGetters(['initShop', 'userTemplate', 'isLogin', 'userInfo', 'authType']),
userHeadData() {
if (this.userTemplate?.length) {
return this.userTemplate[0].content;
}
}
},
// 下拉刷新
onPullDownRefresh() {
this.init();
},
onPageScroll(e) {
this.scrollTop = e.scrollTop;
},
onShow() {
if (this.isLogin) {
this.init();
this.getUserData();
}
this.enable = true;
},
methods: {
...mapActions(['getUserInfo', 'showAuthModal', 'getUserData']),
onShare() {
this.showShare = true;
uni.hideTabBar();
},
// 初始化
init() {
this.getUserInfo()
.then(res => {
uni.stopPullDownRefresh();
})
.catch(e => {
uni.stopPullDownRefresh();
});
}
}
};
</script>
<style lang="scss">
.copyright-box {
.copyright-text {
font-size: 22rpx;
font-weight: 500;
color: #c4c4c4;
}
}
</style>

@ -0,0 +1,342 @@
<template>
<view>
<view class="sh-userinfo-box">
<view class="head-wrap"
:style="{ background: detail.image ? `url(${detail.image}) no-repeat center / 100% 100%` : detail.color }">
<!-- 标题栏 -->
<shopro-navbar backIconName="" backText="我的" :backTextStyle="{
color: '#fff',
fontSize: '40rpx',
fontWeight: '500'
}" :background="navBackground">
<view slot="right" class="u-flex u-row-center u-col-center u-m-r-20" v-if="userOtherData.is_store"
@tap="goStore">
<button class="u-reset-button merchant-btn">切换商家版</button>
</view>
</shopro-navbar>
<view class="user-head u-flex u-row-between">
<view class="u-flex">
<!-- 个人信息 -->
<view class="info-box">
<view class="u-flex" @tap="$Router.push('/pages/user/info')">
<view class="head-img-wrap">
<image class="head-img" :src="userInfo.avatar || $IMG_URL + '/imgs/base_avatar.png'"
mode="aspectFill"></image>
<!-- 同步信息 -->
<block v-if="showRefresh">
<button @tap.stop="showModal = true"
class="u-reset-button u-flex u-row-center u-col-center refresh-btn">
<view class="u-iconfont uicon-reload" style="color: #fff;font-size: 24rpx;">
</view>
</button>
</block>
</view>
<text class="user-name u-ellipsis-1">{{ userInfo.nickname || '请登录~' }}</text>
</view>
</view>
<!-- 等级 -->
<view v-if="userInfo.group" class="grade-tag tag-box u-flex">
<image v-if="userInfo.group.image" class="tag-img" :src="userInfo.group.image" mode="">
</image>
<text class="tag-title">{{ userInfo.group.name }}</text>
</view>
</view>
<view class="u-flex" v-if="userInfo.nickname">
<button class=" u-reset-button code-btn" @tap="onShare"><text
class="iconfont icon-qrcode"></text></button>
</view>
</view>
</view>
</view>
<!-- 绑定手机 -->
<view class="notice-box u-flex u-row-between u-p-30"
v-if="userInfo.verification && !userInfo.verification.mobile" @tap="bindMobile">
<view class="notice-detail">点击绑定手机号,确保账户安全</view>
<button class="u-reset-button bindPhone">去绑定</button>
</view>
<!-- 更新信息 -->
<view class="cu-modal" :class="{ show: showModal }" @tap="showModal = false">
<view class="cu-dialog" style="width: 600rpx;">
<view class="modal-box">
<view class="modal-head">提示</view>
<view class="modal-content">更新微信信息?</view>
<view class="modal-bottom u-flex u-col-center">
<button class="u-reset-button modal-btn cancel-btn" :hover-stay-time="100"
hover-class="btn-hover" @tap="showModal = false">取消</button>
<button class="u-reset-button modal-btn save-btn" :hover-stay-time="100"
hover-class="btn-hover" @tap="refreshWechatUser">确定</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 自定义之个人信息
* @property {Object} detail - 个人信息
*/
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
import wechat from '@/shopro/wechat/wechat';
export default {
components: {},
data() {
return {
showModal: false,
platform: this.$platform.get(), //当前平台。
isFixed: false,
navBackground: {
background: 'none'
}
};
},
mounted() { },
props: {
detail: {},
scrollTop: {
type: Number,
default: 0
}
},
watch: {
scrollTop(val) {
let backgroundText = this.detail.image ? `url(${this.detail.image})` : this.detail.color;
this.isFixed = val > 50 ? true : false;
this.navBackground.background = val > 50 ? backgroundText : 'none';
}
},
computed: {
...mapGetters(['isLogin', 'userInfo', 'userOtherData']),
showRefresh() {
if (this.isLogin) {
if (this.platform === 'wxOfficialAccount') {
return this.userInfo.verification?.wxOfficialAccount;
}
// if (this.platform === 'wxMiniProgram') {
// return this.userInfo.verification.wxMiniProgram;
// }
if (this.platform === 'App') {
return this.userInfo.verification.wxOpenPlatform;
}
}
return false;
}
},
methods: {
...mapActions(['getUserInfo', 'showAuthModal']),
jump(path, query) {
this.$Router.push({
path: path,
query: query
});
},
// 点击分享
onShare() {
this.$emit('onShare');
},
// 同步用户信息
async refreshWechatUser() {
this.showModal = false;
let token = await wechat.refresh();
token && this.getUserInfo(token);
},
// 跳转门店
goStore() {
if (this.userOtherData.store_id) {
uni.setStorageSync('storeId', this.userOtherData.store_id);
this.jump('/pages/app/merchant/index', { storeId: this.userOtherData.store_id });
} else {
if (uni.getStorageSync('storeId')) {
this.jump('/pages/app/merchant/index');
} else {
this.jump('/pages/app/merchant/list');
}
}
// #ifdef MP-WEIXIN
this.$store.commit('subscribeMessage', 'store');
// #endif
},
// 绑定手机号
bindMobile() {
this.showAuthModal('bindMobile');
}
}
};
</script>
<style lang="scss">
// 更新信息
.modal-box {
width: 600rpx;
.btn-hover {
background-color: rgb(230, 230, 230);
}
.modal-head {
padding-top: 48rpx;
font-weight: 500;
text-align: center;
color: $u-main-color;
}
.modal-content {
padding: 48rpx;
font-size: 30rpx;
text-align: center;
color: $u-content-color;
}
.modal-bottom {
.modal-btn {
flex: 1;
height: 100rpx;
line-height: 100rpx;
font-size: 32rpx;
box-sizing: border-box;
cursor: pointer;
text-align: center;
border-radius: 4rpx;
}
.cancel-btn {
color: #666666;
}
.save-btn {
color: #e9b461;
}
}
}
.sh-userinfo-box {
position: relative;
height: calc(var(--status-bar-height) + 300rpx);
.head-wrap {
powidth: 100%;
height: 100%;
.merchant-btn {
width: 178rpx;
height: 64rpx;
line-height: 64rpx;
background: #ffffff;
border-radius: 32rpx;
font-size: 26rpx;
font-weight: 500;
color: #a8700d;
}
}
.user-head {
width: 100%;
padding-top: 30rpx;
.info-box {
padding-left: 30rpx;
.head-img-wrap {
position: relative;
.refresh-btn {
position: absolute;
padding: 0;
background: none;
width: 40rpx;
height: 40rpx;
border-radius: 50%;
background: #c9912c;
top: -10rpx;
right: 10rpx;
}
}
.head-img {
width: 94rpx;
height: 94rpx;
border-radius: 50%;
background: #ccc;
margin-right: 25rpx;
overflow: hidden;
}
.user-name {
font-size: 30rpx;
font-weight: 500;
color: #fff;
line-height: 30rpx;
width: 180rpx;
}
}
.tag-box {
background: rgba(0, 0, 0, 0.2);
border-radius: 22rpx;
margin-left: 10rpx;
.tag-img {
width: 40rpx;
height: 40rpx;
display: inline-block;
border-radius: 50%;
}
.tag-title {
font-size: 20rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
line-height: 40rpx;
padding: 0 10rpx;
}
}
.code-btn {
padding: 30rpx;
.icon-qrcode {
font-size: 50rpx;
color: #fff;
}
}
}
.wallet {
font-size: 20rpx;
padding-left: 140rpx;
.money {
margin-right: 40rpx;
}
}
}
// 绑定微信公众号
.notice-box {
width: 750rpx;
height: 70rpx;
background: rgba(253, 239, 216, 1);
padding: 0 35rpx;
.notice-detail {
font-size: 24rpx;
font-weight: 400;
color: rgba(204, 149, 59, 1);
}
.bindPhone {
width: 135rpx;
line-height: 52rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
border-radius: 26rpx;
padding: 0;
font-size: 26rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
}
}
</style>

@ -0,0 +1,182 @@
<!-- 自定义页面 -->
<template>
<view class="home-wrap u-m-b-20">
<!-- 空白页 -->
<u-no-network @retry="init"></u-no-network>
<shopro-empty v-if="isEmpty" :image="$IMG_URL + '/imgs/empty/template_empty.png'" tipText="暂未找到模板,请前往装修~"></shopro-empty>
<view v-else-if="isRefresh" class="content-box">
<!-- 自定义模块 -->
<view class="template-box">
<block v-for="(item, index) in viewTemplate" :key="item.id">
<!-- 轮播 -->
<sh-banner
v-if="item.type === 'banner'"
:Px="item.content.x"
:Py="item.content.y"
:borderRadius="item.content.radius"
:height="item.content.height"
:list="item.content.list"
></sh-banner>
<!-- 搜索 -->
<sh-search v-if="item.type === 'search'"></sh-search>
<!-- 滑动宫格 -->
<sh-grid-swiper v-if="item.type === 'menu'" :list="item.content.list" :oneRowNum="item.content.style"></sh-grid-swiper>
<!-- 推荐商品 -->
<sh-hot-goods v-if="item.type === 'goods-list' || item.type === 'goods-group'" :detail="item.content"></sh-hot-goods>
<!-- 广告魔方 -->
<sh-adv v-if="item.type === 'adv'" :detail="item.content"></sh-adv>
<!-- 优惠券 -->
<sh-coupon v-if="item.type === 'coupons'" :detail="item.content"></sh-coupon>
<!-- 秒杀-->
<sh-seckill v-if="item.type === 'seckill'" :detail="item.content"></sh-seckill>
<!-- 拼团 -->
<sh-groupon v-if="item.type === 'groupon'" :detail="item.content"></sh-groupon>
<!-- 富文本 -->
<sh-richtext v-if="item.type === 'rich-text'" :richId="item.content.id"></sh-richtext>
<!-- 功能列表 -->
<sh-cell v-if="item.type === 'nav-list'" :list="item.content.list"></sh-cell>
<!-- 九宫格列表 -->
<sh-grid v-if="item.type === 'grid-list'" :detail="item.content.list"></sh-grid>
<!-- 功能标题 -->
<sh-title-card v-if="item.type === 'title-block'" :title="item.content.name" :bgImage="item.content.image" :titleColor="item.content.color"></sh-title-card>
<!-- 钱包 -->
<sh-wallet v-if="item.type === 'wallet-card'"></sh-wallet>
<!-- 订单卡片 -->
<sh-order-card v-if="item.type === 'order-card'"></sh-order-card>
<!-- 直播 -->
<!-- #ifdef MP-WEIXIN -->
<sh-live v-if="item.type === 'live' && HAS_LIVE" :detail="item.content"></sh-live>
<!-- #endif -->
</block>
</view>
<!-- 分类选项卡 -->
<sh-category-tabs
v-if="categoryTabsData && categoryTabsData.category_arr && categoryTabsData.category_arr.length"
:enable="enable"
:styleType="categoryTabsData.style"
:tabsList="categoryTabsData.category_arr"
></sh-category-tabs>
<!-- 登录提示 -->
<shopro-auth-modal></shopro-auth-modal>
<!-- #ifdef H5 -->
<view class="tabbar-hack" style="height: 120rpx; width:100%;"></view>
<!-- #endif -->
</view>
</view>
</template>
<script>
import shBanner from './components/sh-banner.vue';
import shGridSwiper from './components/sh-grid-swiper.vue';
import shHotGoods from './components/sh-hot-goods.vue';
import shAdv from './components/sh-adv.vue';
import shCoupon from './components/sh-coupon.vue';
import shSeckill from './components/sh-seckill.vue';
import shGroupon from './components/sh-groupon.vue';
import shRichtext from './components/sh-richtext.vue';
import shCell from './components/sh-cell.vue';
import shGrid from './components/sh-grid.vue';
import shTitleCard from './components/sh-title-card.vue';
import shOrderCard from './components/sh-order-card.vue';
import shWallet from './components/sh-wallet.vue';
import shSearch from './components/sh-search.vue';
import shCategoryTabs from './components/sh-category-tabs.vue';
import { mapMutations, mapActions, mapState, mapGetters } from 'vuex';
// #ifdef MP-WEIXIN
import { HAS_LIVE } from '@/env';
import shLive from './components/sh-live.vue';
// #endif
// #ifdef H5
let listenMove = document.body; //禁止手机h5下拉刷新带动整个页面。
let handle = function(e) {
e.preventDefault();
};
// #endif
export default {
components: {
shBanner,
shGridSwiper,
shGroupon,
shHotGoods,
shAdv,
shCoupon,
shSeckill,
shRichtext,
shCell,
shGrid,
shTitleCard,
shWallet,
shOrderCard,
shSearch,
shCategoryTabs,
// #ifdef MP-WEIXIN
shLive
// #endif
},
data() {
return {
// #ifdef MP-WEIXIN
HAS_LIVE: HAS_LIVE,
// #endif
enable: false, //是否开启吸顶。
isScorll: false,
viewTemplate: [], //自定义模板数据
isEmpty: false,
isRefresh: false, //刷新
categoryTabsData: {} //分类选项卡
};
},
computed: {
...mapGetters(['isLogin'])
},
onPullDownRefresh() {
this.getViewTemplate();
},
onPageScroll(e) {
this.isScorll = e.scrollTop > 100 ? true : false;
},
onShow() {
let that = this;
this.enable = true;
this.isLogin && this.getCartList();
},
onHide() {
this.enable = false;
},
onLoad() {
this.getViewTemplate();
},
methods: {
...mapActions(['getCartList']),
// 初始化
getViewTemplate() {
let that = this;
that.isRefresh = false;
that.$http('common.custom', {
custom_id: that.$Route.query.id
}).then(res => {
uni.stopPullDownRefresh();
that.isRefresh = true;
if (res.code == 1) {
that.viewTemplate = res.data.template;
that.isEmpty = !that.viewTemplate.length;
that.categoryTabsData = that.viewTemplate[that.viewTemplate.length - 1].content;
uni.setNavigationBarTitle({
title: res.data.name
});
}
});
}
}
};
</script>
<style lang="scss"></style>

@ -0,0 +1,329 @@
<!-- 售后详情 -->
<template>
<view class="page_box">
<view class="content_box">
<!-- 步骤条 -->
<view class="steps-box"><u-steps :current="stepsCurrent" :list="steps" activeColor="#39b54a" mode="number" icon="checkmark"></u-steps></view>
<!-- 服务状态 -->
<view class="status-box u-flex u-row-between" @tap="jump('/pages/order/after-sale/log', { aftersaleId: aftersaleDetail.id })">
<view class="">
<view class="status-text">{{ aftersaleDetail.aftersale_status_desc }}</view>
<view class="status-time">{{ aftersaleDetail.updatetime }}</view>
</view>
<text class="u-iconfont uicon-arrow-right" style="color: #666"></text>
</view>
<!-- 退款金额 -->
<view class="aftersale-money u-flex u-row-between">
<view class="aftersale-money--title">退款总额</view>
<view class="aftersale-money--num" v-if="Number(aftersaleDetail.refund_fee)">¥{{ aftersaleDetail.refund_fee }}</view>
<view class="aftersale-money--num" v-else>未退款</view>
</view>
<!-- 服务商品 -->
<view class="order-shop">
<shopro-mini-card :title="aftersaleDetail.goods_title" :image="aftersaleDetail.goods_image">
<template #describe>
<view class="order-sku u-ellipsis-1">
<text class="order-num">数量:{{ aftersaleDetail.goods_num || 0 }};</text>
{{ aftersaleDetail.goods_sku_text ? aftersaleDetail.goods_sku_text : '' }}
</view>
</template>
<template #cardBottom>
<view class="card-price-box u-flex">
<text class="order-price">¥{{ aftersaleDetail.goods_price || 0 }}</text>
<button class="u-reset-button status-btn" v-if="aftersaleDetail.status_name">{{ aftersaleDetail.status_name }}</button>
</view>
</template>
</shopro-mini-card>
</view>
<!-- 服务内容 -->
<view class="aftersale-content">
<view class="aftersale-item u-flex">
<view class="item-title">服务单号:</view>
<view class="item-content">{{ aftersaleDetail.aftersale_sn }}</view>
<button class="u-reset-button copy-btn" @tap="onCopy(aftersaleDetail.aftersale_sn)">复制</button>
</view>
<view class="aftersale-item u-flex">
<view class="item-title">申请时间:</view>
<view class="item-content">{{ aftersaleDetail.createtime }}</view>
</view>
<view class="aftersale-item u-flex">
<view class="item-title">售后类型:</view>
<view class="item-content">{{ aftersaleDetail.type_text }}</view>
</view>
<view class="aftersale-item u-flex" v-if="aftersaleLog && aftersaleLog.length">
<view class="item-title">申请原因:</view>
<view class="item-content">{{ aftersaleLog[aftersaleLog.length - 1].reason }}</view>
</view>
<view class="aftersale-item x-f" v-if="aftersaleLog && aftersaleLog.length">
<view class="item-title">相关描述:</view>
<view class="item-content">{{ aftersaleLog[aftersaleLog.length - 1].content }}</view>
</view>
</view>
</view>
<view class="foot_box">
<block v-for="orderBtn in aftersaleDetail.btns" :key="orderBtn">
<button v-if="orderBtn === 'cancel'" @tap.stop="onCancel(aftersaleDetail.id)" class="u-reset-button btn">取消</button>
<button v-if="orderBtn === 'delete'" @tap.stop="onDelete(aftersaleDetail.id)" class="u-reset-button btn delete-btn">删除</button>
</block>
<button class="u-reset-button contcat-btn btn" @tap="onService">联系客服</button>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
stepsCurrent: 0,
steps: [
{
name: '提交申请'
},
{
name: '进行中'
},
{
name: '完成'
}
],
aftersaleDetail: {}, //售后详情
aftersaleLog: [] //售后记录
};
},
computed: {},
onLoad() {
this.getAftersaleDetail();
},
methods: {
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 跳转客服
onService() {
this.$Router.push('/pages/public/chat/index');
},
// 复制
onCopy(code) {
let that = this;
uni.setClipboardData({
data: code,
success: function(data) {
that.$u.toast('已复制到剪切板');
}
});
},
// 设置步骤条
setSteps(step) {
if (step >= 0 && step < 2) {
this.stepsCurrent = step;
} else {
this.stepsCurrent = 2;
let name = '';
switch (step) {
case 2:
name = '完成';
break;
case -1:
name = '取消';
break;
case -2:
name = '驳回';
break;
default:
break;
}
this.steps[2].name = name;
}
},
// 服务单详情
getAftersaleDetail() {
let that = this;
that.$http('order.aftersaleDetail', {
id: that.$Route.query.aftersaleId
}).then(res => {
if (res.code === 1) {
that.aftersaleDetail = res.data;
that.setSteps(res.data.aftersale_status);
that.aftersaleLog = res.data.logs;
that.aftersaleDetail.createtime = that.$u.timeFormat(res.data.createtime, 'yyyy-mm-dd hh:MM');
that.aftersaleDetail.updatetime = that.$u.timeFormat(res.data.updatetime, 'yyyy-mm-dd hh:MM');
}
});
},
//取消
onCancel(aftersaleId) {
let that = this;
that.$http(
'order.cancelAftersaleOrder',
{
id: aftersaleId
},
'取消中...'
).then(res => {
if (res.code === 1) {
that.$Router.back();
}
});
},
// 删除
onDelete(aftersaleId) {
let that = this;
uni.showModal({
title: '删除订单',
content: '确定要删除这个订单么?',
cancelText: '取消',
confirmText: '删除',
success: res => {
if (res.confirm) {
that.$http('order.deleteAftersaleOrder', {
id: aftersaleId
}).then(res => {
if (res.code === 1) {
that.$Router.back();
}
});
}
}
});
}
}
};
</script>
<style lang="scss">
// 步骤条
.steps-box {
height: 190rpx;
padding: 60rpx 0 0;
background: #fff;
}
// 服务状态
.status-box {
background-color: #fff;
border-radius: 20rpx 20rpx 0px 0px;
padding: 20rpx;
margin-top: -20rpx;
.status-text {
font-size: 28rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
margin-bottom: 20rpx;
}
.status-time {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
}
}
// 退款金额
.aftersale-money {
background-color: #fff;
height: 98rpx;
padding: 0 20rpx;
margin: 20rpx 0;
.aftersale-money--title {
font-size: 28rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
}
.aftersale-money--num {
font-size: 28rpx;
font-weight: 500;
color: rgba(225, 33, 43, 1);
}
}
// order-shop
.order-shop {
padding: 20rpx;
background-color: #fff;
margin-bottom: 20rpx;
.order-sku {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
width: 450rpx;
margin-bottom: 20rpx;
.order-num {
margin-right: 10rpx;
}
}
.card-price-box {
.status-btn {
height: 32rpx;
border: 1rpx solid rgba(207, 169, 114, 1);
border-radius: 15rpx;
font-size: 20rpx;
font-weight: 400;
color: rgba(168, 112, 13, 1);
padding: 0 10rpx;
margin-left: 20rpx;
background: rgba(233, 183, 102, 0.16);
}
.order-price {
font-size: 26rpx;
font-weight: 600;
color: rgba(51, 51, 51, 1);
}
}
}
// 服务内容
.aftersale-content {
background-color: #fff;
padding: 20rpx;
.aftersale-item {
height: 60rpx;
.copy-btn {
background: none;
color: #b38436;
font-size: 26rpx;
padding: 0 20rpx;
}
.item-tilte {
color: #333;
font-size: 28rpx;
}
.item-content {
color: #999999;
font-size: 28rpx;
}
}
}
// 底部功能
.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;
font-weight: 400;
color: rgba(51, 51, 51, 1);
}
.delete-btn {
background: #ffeeee;
color: #e50808;
}
}
</style>

@ -0,0 +1,329 @@
<!-- 售后列表 -->
<template>
<view class="page_box">
<!-- tab -->
<view class="head_box">
<view class="order-nav u-flex">
<view class="nav-item u-flex-col u-flex-1 u-col-center" v-for="nav in orderState" :key="nav.id" @tap="onNav(nav.type)">
<view class="item-title">{{ nav.title }}</view>
<text class="nav-line" :class="{ 'line-active': orderType === nav.type }"></text>
</view>
</view>
</view>
<view class="content_box">
<scroll-view scroll-y="true" enable-back-to-top @scrolltolower="loadMore" class="scroll-box">
<view class="order-list" v-for="(order, orderIndex) in orderList" :key="order.id" @tap="jump('/pages/order/after-sale/detail', { aftersaleId: order.id })">
<view class="order-head u-flex u-row-between">
<text class="no">服务单号:{{ order.aftersale_sn }}</text>
<view class="order-status u-flex">
<text class="status-text">{{ order.aftersale_status_text }}</text>
</view>
</view>
<view class="order-content">
<shopro-mini-card :title="order.goods_title" :image="order.goods_image">
<template #describe>
<view class="order-sku u-ellipsis-1">
<text class="order-num">数量:{{ order.goods_num || 0 }};</text>
{{ order.goods_sku_text ? order.goods_sku_text : '' }}
</view>
</template>
<template #cardBottom>
<view class="card-price-box u-flex">
<text class="order-price font-OPPOSANS">¥{{ order.goods_price || 0 }}</text>
<button class="u-reset-button status-btn">{{ order.type_text }}</button>
</view>
</template>
</shopro-mini-card>
</view>
<view class="order-bottom">
<view class="serve-status u-flex u-row-between" @tap.stop="jump('/pages/order/after-sale/log', { aftersaleId: order.id })">
<view class="u-flex">
<view class="serve-title">{{ order.aftersale_status_text }}</view>
<view class="serve-content">{{ order.aftersale_status_desc }}</view>
</view>
<text class="u-iconfont uicon-arrow-right" style="color: #666"></text>
</view>
<view class="btn-box u-flex" v-for="orderBtn in order.btns" :key="orderBtn">
<button v-if="orderBtn === 'cancel'" @tap.stop="onCancel(order.id, orderIndex)" class="u-reset-button obtn">取消</button>
<button v-if="orderBtn === 'delete'" style="background:#FFEEEE;color:#E50808" @tap.stop="onDelete(order.id, orderIndex)" class="u-reset-button obtn">
删除
</button>
</view>
</view>
</view>
<!-- 缺省页 -->
<shopro-empty v-if="isEmpty" :image="$IMG_URL + '/imgs/empty/empty_groupon.png'" tipText="暂无相关记录~"></shopro-empty>
<!-- 更多 -->
<u-loadmore v-if="orderList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</scroll-view>
</view>
<view class="foot_box"></view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
orderType: 'all', //分类id
isEmpty: false,
orderList: [], //售后列表
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1,
orderState: [
{
id: 0,
title: '全部',
type: 'all'
},
{
id: 1,
title: '处理中',
type: 'ing'
},
{
id: 2,
title: '已完成',
type: 'finish'
}
]
};
},
computed: {},
onLoad() {},
onShow() {
this.orderList = [];
this.currentPage = 1;
this.lastPage = 1;
this.getAftersaleList();
},
methods: {
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 切换导航
onNav(type) {
if (this.orderType !== type) {
this.orderType = type;
this.currentPage = 1;
this.lastPage = 1;
this.orderList = [];
this.getAftersaleList();
}
},
// 售后列表
getAftersaleList() {
let that = this;
that.loadStatus = 'loading';
that.$http('order.aftersaleList', {
type: that.orderType,
page: that.currentPage
}, '加载中...').then(res => {
if (res.code === 1) {
that.orderList = [...that.orderList, ...res.data.data];
that.isEmpty = !that.orderList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
},
//取消
onCancel(aftersaleId, orderIndex) {
let that = this;
that.$http(
'order.cancelAftersaleOrder',
{
id: aftersaleId
},
'取消中...'
).then(res => {
if (res.code === 1) {
this.$u.toast(res.msg);
this.orderList.splice(orderIndex, 1);
}
});
},
// 删除
onDelete(aftersaleId, orderIndex) {
let that = this;
uni.showModal({
title: '删除订单',
content: '确定要删除这个订单么?',
cancelText: '取消',
confirmText: '删除',
success: res => {
if (res.confirm) {
that.$http(
'order.deleteAftersaleOrder',
{
id: aftersaleId
},
'删除中...'
).then(res => {
if (res.code === 1) {
this.$u.toast(res.msg);
this.orderList.splice(orderIndex, 1);
}
});
}
}
});
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getAftersaleList();
}
}
}
};
</script>
<style lang="scss">
// 导航切换
.order-nav {
background: #fff;
height: 80rpx;
border-top: 1rpx solid #f5f5f5;
.nav-item {
flex: 1;
.item-title {
font-size: 30rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
line-height: 76rpx;
}
.nav-line {
width: 100rpx;
height: 4rpx;
background: #fff;
}
.line-active {
background: rgba(230, 184, 115, 1);
}
}
}
// 售后列表卡片
.order-list {
background: #fff;
margin: 20rpx 0;
.order-bottom {
padding-bottom: 20rpx;
.serve-status {
height: 94rpx;
background: rgba(246, 246, 246, 1);
border-radius: 4rpx;
margin: 0 20rpx 20rpx;
padding: 0 20rpx;
.serve-title {
font-size: 26rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
margin-right: 30rpx;
}
.serve-content {
font-size: 20rpx;
font-weight: 400;
color: rgba(102, 102, 102, 1);
}
}
.btn-box {
justify-content: flex-end;
}
.obtn {
width: 160rpx;
line-height: 60rpx;
background: rgba(238, 238, 238, 1);
border-radius: 30rpx;
font-size: 26rpx;
font-weight: 400;
color: rgba(102, 102, 102, 1);
margin-right: 20rpx;
padding: 0;
}
}
.order-head {
padding: 0 25rpx;
height: 77rpx;
border-bottom: 1rpx solid #dfdfdf;
.no {
font-size: 26rpx;
color: #999;
}
.order-status {
.iconfont {
color: #f83942;
font-size: 30rpx;
font-weight: 600;
}
.status-text {
font-size: 26rpx;
font-weight: 400;
color: #f83942;
margin-left: 10rpx;
}
}
}
.order-content {
padding: 20rpx;
.order-sku {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
width: 450rpx;
margin-bottom: 20rpx;
.order-num {
margin-right: 10rpx;
}
}
.card-price-box {
.status-btn {
height: 32rpx;
border: 1rpx solid rgba(207, 169, 114, 1);
border-radius: 15rpx;
font-size: 20rpx;
font-weight: 400;
color: rgba(168, 112, 13, 1);
padding: 0 10rpx;
margin-left: 20rpx;
background: rgba(233, 183, 102, 0.16);
}
.order-price {
font-size: 26rpx;
font-weight: 600;
color: rgba(51, 51, 51, 1);
}
}
}
.goods-order {
border-bottom: 1px solid rgba(223, 223, 223, 0.5);
padding: 20rpx 20rpx 0;
margin-bottom: 20rpx;
}
.goods-bottom {
background: #fff;
padding-bottom: 30rpx;
}
}
</style>

@ -0,0 +1,96 @@
<!-- 售后进行 -->
<template>
<view class="log-box wrap">
<u-time-line>
<u-time-line-item v-for="(log, index) in aftersaleLog" :key="log.id" nodeTop="4">
<template v-slot:node>
<view class="u-node" :style="index === 0 ? 'background: #19be6b;' : ''"><text class="u-iconfont uicon-bell-fill" style="color: #fff;font-size: 26rpx;"></text></view>
</template>
<template v-slot:content>
<view class="u-order-title unacive u-m-b-20">{{ log.reason }}</view>
<view class="u-order-desc u-m-b-10" v-show="log.content"><u-parse :html="log.content"></u-parse></view>
<view class="item-img-box u-flex u-flex-wrap" v-show="log.images.length">
<block v-for="(img, index) in log.images" :key="index"><image :src="img" class="log-img" mode="aspectFill"></image></block>
</view>
<view class="u-order-time">{{ $u.timeFormat(log.createtime, 'yyyy-mm-dd hh:MM') }}</view>
</template>
</u-time-line-item>
</u-time-line>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
aftersaleLog: []
};
},
computed: {},
onLoad() {
this.getAftersaleDetail();
},
methods: {
// 服务单详情
getAftersaleDetail() {
let that = this;
that.$http('order.aftersaleDetail', {
id: that.$Route.query.aftersaleId
}).then(res => {
if (res.code === 1) {
that.aftersaleLog = res.data.logs;
}
});
}
}
};
</script>
<style lang="scss" scoped>
.wrap {
padding: 24rpx 24rpx 24rpx 40rpx;
background-color: #fff;
}
.u-node {
width: 44rpx;
height: 44rpx;
border-radius: 100rpx;
display: flex;
justify-content: center;
align-items: center;
background: #d0d0d0;
}
.u-order-title {
color: #333333;
font-weight: bold;
font-size: 32rpx;
}
.u-order-title.unacive {
color: rgb(150, 150, 150);
}
.u-order-desc {
color: rgb(150, 150, 150);
font-size: 28rpx;
margin-bottom: 6rpx;
}
.item-img-box {
.log-img {
display: block;
width: 140rpx;
height: 140rpx;
background-color: #ccc;
margin-right: 20rpx;
margin-bottom: 20rpx;
border-radius: 6rpx;
}
}
.u-order-time {
color: rgb(200, 200, 200);
font-size: 26rpx;
}
</style>

@ -0,0 +1,432 @@
<!-- 申请售后 -->
<template>
<view class="refund-wrap">
<!-- 售后商品 -->
<view class="goods-box">
<shopro-mini-card :title="orderItemDetail.goods_title" :image="orderItemDetail.goods_image">
<template #describe>
<view class="order-sku u-ellipsis-1">
<text class="order-num">数量:{{ orderItemDetail.goods_num || 0 }};</text>
{{ orderItemDetail.goods_sku_text ? orderItemDetail.goods_sku_text : '' }}
</view>
</template>
<template #cardBottom>
<view class="card-price-box u-flex">
<text class="order-price font-OPPOSANS">¥{{ orderItemDetail.goods_price || 0 }}</text>
<button class="u-reset-button status-btn" v-if="orderItemDetail.status_name">{{ orderItemDetail.status_name }}</button>
</view>
</template>
</shopro-mini-card>
</view>
<!-- 售后类型 -->
<view class="refund-item">
<view class="item-title">请选择售后类型</view>
<u-radio-group v-model="refundType" active-color="#d89f64" width="100%">
<u-radio shape="circle" v-for="(item, index) in refundTypeList" :key="index" :name="item.value">{{ item.title }}</u-radio>
</u-radio-group>
</view>
<!-- 申请原因 -->
<view class="goods-item u-flex u-row-between" @tap="showModal = true">
<text class="item-title">选择申请原因</text>
<view class="u-flex refund-cause">
<text class="u-m-r-20" style="color: #333;" v-if="refundCause">{{ refundCause }}</text>
<text class="u-m-r-20" v-else>请选择申请原因~</text>
<text class="u-iconfont uicon-arrow-right" style="color: #666"></text>
</view>
</view>
<!-- 联系方式 -->
<view class="refund-item u-m-b-20">
<view class="item-title">联系方式</view>
<view class="input-box u-flex"><input type="number" class="item-input" v-model="phone" placeholder="请输入您的联系电话" placeholder-class="input--pl" /></view>
</view>
<!-- 留言 -->
<view class="refund-item u-m-b-20">
<view class="item-title">相关描述</view>
<view class="describe-box">
<textarea
class="describe-content"
v-model="refundContent"
maxlength="120"
placeholder="客官~请描述您遇到的问题,建议上传照片"
placeholder-class="input--pl"
fixed
></textarea>
<view class="upload-img">
<u-upload
:showProgress="false"
@on-uploaded="uploadSuccess"
@on-remove="uploadRemove"
:action="`${$BASE_URL}/api/common/upload`"
:header="{token: token}"
width="140"
height="140"
maxCount="9"
></u-upload>
</view>
</view>
</view>
<!-- 底部按钮 -->
<view class="foot-wrap safe-area-inset-bottom">
<view class="foot_box u-flex u-row-between safe-area-inset-bottom">
<button class="u-reset-button contcat-btn" @tap="onService">联系客服</button>
<button class="u-reset-button sub-btn" @tap="postAftersale">提交</button>
</view>
</view>
<!-- 申请原因弹窗 -->
<u-popup v-model="showModal" safe-area-inset-bottom @close="showModal = false" mode="bottom" :closeable="true" close-icon-pos="top-left">
<view class="modal-box page_box">
<view class="modal-head head_box u-flex u-row-center u-col-center">{{ refundList.title }}</view>
<view class="modal-content content_box">
<view class="u-flex-col u-p-20">
<u-radio-group v-model="refundCause" active-color="#d89f64" width="100%">
<u-radio class="u-m-y-10" shape="circle" v-for="item in refundList.list" :key="item.id" :name="item.val">
<text class="u-p-l-20">{{ item.val }}</text>
</u-radio>
</u-radio-group>
</view>
</view>
<view class="modal-foot foot_box u-flex u-row-center u-col-center"><button class="u-reset-button close-btn" @tap="showModal = false">确定</button></view>
</view>
</u-popup>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
showModal: false,
imgList: [], //图片地址
orderId: 0, //订单ID
orderItemDetail: {}, //订单信息
refundType: '', //售后类型
refundCause: '', //退款原因
refundContent: '', //相关描述
phone: '', //联系方式
refundTypeList: [
//售后类型
{
title: '退款',
value: 'refund'
},
{
title: '退货',
value: 'return'
},
{
title: '其他',
value: 'other'
}
],
refundList: {
//申请原因
title: '申请原因',
list: [
{
id: 1,
val: '卖家发错货了'
},
{
id: 2,
val: '退运费'
},
{
id: 3,
val: '大小/重量于商品描述不符'
},
{
id: 4,
val: '生产日期、保质期与商品不符'
},
{
id: 5,
val: '质量问题'
}
]
},
token: uni.getStorageSync('token')
};
},
computed: {},
onLoad() {
this.getOrderItemDetail();
},
methods: {
// 跳转客服
onService() {
this.$Router.push('/pages/public/chat/index');
},
// 上传图片成功
uploadSuccess(e) {
this.imgList = [];
e.forEach(item => {
this.imgList.push(item.response.data.url);
});
},
// 移除图片
uploadRemove(index) {
this.imgList.splice(index, 1);
},
// 订单item详情
getOrderItemDetail() {
let that = this;
that.$http('order.itemDetail', {
id: that.$Route.query.orderId,
order_item_id: that.$Route.query.orderItemId
}).then(res => {
if (res.code === 1) {
that.orderItemDetail = res.data;
}
});
},
// 提交售后
postAftersale() {
let that = this;
that.$http('order.aftersale', {
type: that.refundType,
order_id: that.$Route.query.orderId,
order_item_id: that.$Route.query.orderItemId,
reason: that.refundCause,
content: that.refundContent,
images: that.imgList,
phone: that.phone
}, '申请中...').then(res => {
if (res.code === 1) {
// #ifdef MP-WEIXIN
that.$store.commit('subscribeMessage', 'aftersale');
// #endif
that.$Router.back();
}
});
}
}
};
</script>
<style lang="scss">
// 售后项目
.refund-item {
background-color: #fff;
border-bottom: 1rpx solid #f5f5f5;
padding: 30rpx;
.item-title {
font-size: 30rpx;
font-weight: bold;
color: rgba(51, 51, 51, 1);
margin-bottom: 20rpx;
}
// 留言
.describe-box {
width: 690rpx;
background: rgba(249, 250, 251, 1);
border-radius: 20rpx;
.describe-content {
width: 660rpx;
padding: 30rpx;
height: 200rpx;
font-size: 24rpx;
font-weight: 400;
color: #333;
}
}
.input--pl {
font-size: 24rpx;
font-weight: 400;
color: rgba(177, 179, 199, 1);
}
// 联系方式
.input-box {
height: 84rpx;
background: rgba(249, 250, 251, 1);
border-radius: 20rpx;
.item-input {
padding: 0 30rpx;
flex: 1;
font-size: 28rpx;
font-weight: 400;
color: #333;
}
}
}
.goods-box {
background: #fff;
padding: 20rpx;
margin-bottom: 20rpx;
.order-sku {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
width: 450rpx;
margin-bottom: 20rpx;
.order-num {
margin-right: 10rpx;
}
}
.card-price-box {
.status-btn {
height: 32rpx;
border: 1rpx solid rgba(207, 169, 114, 1);
border-radius: 15rpx;
font-size: 20rpx;
font-weight: 400;
color: rgba(168, 112, 13, 1);
padding: 0 10rpx;
margin-left: 20rpx;
background: rgba(233, 183, 102, 0.16);
}
.order-price {
font-size: 26rpx;
font-weight: 600;
color: rgba(51, 51, 51, 1);
}
}
}
.goods-item {
height: 95rpx;
background: #fff;
padding: 0 25rpx;
margin-bottom: 20rpx;
// 售后原因
.refund-cause {
font-size: 26rpx;
font-weight: 400;
color: rgba(177, 179, 199, 1);
}
&:nth-of-type(2n) {
margin-bottom: 0;
border-top: 0;
}
.item-title {
font-size: 28rpx;
}
.price {
font-size: 28rpx;
color: #a8700d;
}
}
.upload-img {
padding: 25rpx;
.upload-title {
font-size: 28rpx;
}
}
.img-box {
display: flex;
align-items: center;
flex-wrap: wrap;
.choose-img,
.preview-box {
width: 110rpx;
height: 110rpx;
background: rgba(249, 250, 251, 1);
border: 1rpx solid rgba(223, 223, 223, 1);
margin-right: 20rpx;
margin-bottom: 20rpx;
position: relative;
&:nth-child(5n) {
margin-right: 0;
}
.preview-img {
width: 100%;
height: 100%;
}
}
}
.foot-wrap {
height: 100rpx;
width: 100%;
}
.foot_box {
height: 100rpx;
background-color: #fff;
padding: 0 30rpx;
position: fixed;
width: 100%;
bottom: 0;
left: 0;
border-top: 1rpx solid #f5f5f5;
.sub-btn {
width: 340rpx;
line-height: 74rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
border: 1rpx solid rgba(238, 238, 238, 1);
border-radius: 38rpx;
color: rgba(#fff, 0.9);
font-size: 28rpx;
}
.contcat-btn {
width: 340rpx;
line-height: 74rpx;
background: rgba(238, 238, 238, 1);
border-radius: 38rpx;
font-size: 28rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
}
}
.modal-box {
width: 750rpx;
height: 680rpx;
border-radius: 30rpx 30rpx 0 0;
background: #fff;
.modal-head {
height: 100rpx;
font-size: 30rpx;
}
.modal-content {
font-size: 28rpx;
.sel-item {
height: 100rpx;
width: 100%;
padding: 0 20rpx;
border-bottom: 1rpx solid rgba(223, 223, 223, 0.5);
.sel-radio {
transform: scale(0.7);
}
}
}
.modal-foot {
.close-btn {
width: 710rpx;
line-height: 80rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
border: 1rpx solid rgba(238, 238, 238, 1);
border-radius: 40rpx;
color: rgba(#fff, 0.9);
}
}
}
</style>

@ -0,0 +1,116 @@
<template>
<view class="select-coupon-wrap ">
<view class="coupon-item u-flex u-row-between">
<view class="item-title">优惠券</view>
<view class="u-flex" @tap="showModal = true">
<text class="price" v-if="couponList.length">{{ title }}</text>
<text class="sel-coupon" v-else>暂无优惠券</text>
<text class="u-iconfont uicon-arrow-right" style="color: #bfbfbf"></text>
</view>
</view>
<u-popup v-model="showModal" mode="bottom" :closeable="true" close-icon-pos="top-right" border-radius="30">
<view class="modal-box page_box">
<view class="modal-head u-flex u-row-center u-col-center"><text class="head-title">选择优惠券</text></view>
<view class="modal-content content_box u-flex-col u-p-20">
<u-radio-group v-model="radio" @change="radioGroupChange" width="100%" activeColor="#e9b564">
<view class="radio-item">
<u-radio :name="0"><text class="coupon-title u-p-l-10">不使用优惠券</text></u-radio>
</view>
<view class="radio-item" v-for="(radio, index) in couponList" :key="radio.user_coupons_id">
<u-radio :name="index + 1">
<text class="coupon-title u-p-l-10">{{ radio.name }}:{{ `满${radio.enough}减${radio.amount}` }}</text>
</u-radio>
</view>
</u-radio-group>
</view>
<button class="u-reset-button serve-btn" @tap="showModal = false">确定</button>
</view>
</u-popup>
</view>
</template>
<script>
/**
* 选择优惠券模态框
* @property {Object} couponList - 可用优惠券列表数据
*
*/
export default {
components: {},
data() {
return {
showModal: false,
radio: 0,
title: '选择优惠券'
};
},
props: {
couponList: {
type: Array,
default: () => []
}
},
computed: {},
methods: {
radioGroupChange(index) {
this.title = index > 0 ? '-¥' + this.couponList[index - 1].amount : '选择优惠券';
this.$emit('change', index - 1);
}
}
};
</script>
<style lang="scss" scoped>
// 优惠券项
.coupon-item {
border-top: 1rpx solid rgba(#dfdfdf, 0.5);
height: 100rpx;
background: #fff;
padding: 0 25rpx;
.item-title {
font-size: 28rpx;
margin-right: 20rpx;
}
.price {
font-size: 26rpx;
color: #ff0000;
margin-right: 20rpx;
}
.sel-coupon {
font-size: 26rpx;
color: #c4c4c4;
margin-right: 20rpx;
}
}
// 模态框
.modal-box {
width: 750rpx;
height: 700rpx;
background: #fff;
padding: 30rpx;
// 按钮
.serve-btn {
width: 690rpx;
line-height: 80rpx;
background: linear-gradient(90deg, rgba(240, 199, 133, 1), rgba(246, 214, 157, 1));
border-radius: 40rpx;
color: rgba(#fff, 0.9);
margin-top: 40rpx;
}
.radio-item {
width: 690rpx;
margin-bottom: 20rpx;
}
// 标题
.modal-head {
margin-bottom: 30rpx;
.head-title {
font-size: 32rpx;
font-weight: bold;
}
}
}
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,742 @@
<!-- 订单详情 -->
<template>
<view class="order-detail-wrap">
<!-- 订单状态 -->
<view class="detail-head" :style="orderDetail.consignee ? '' : 'height:120rpx'">
<view class="state-box u-flex">
<image class="state-img" :src="$IMG_URL + '/imgs/order/order_state1.png'" mode=""></image>
<text>{{ orderDetail.status_desc }}</text>
</view>
</view>
<!-- 收货地址 -->
<view class="address-wrap" v-if="orderDetail.consignee">
<view class="order-address-box">
<view class="u-flex">
<text class="address-username">{{ orderDetail.consignee }}</text>
<text class="address-phone">{{ orderDetail.phone }}</text>
</view>
<view class="address-detail">{{ orderDetail.province_name }}{{ orderDetail.city_name }}{{ orderDetail.area_name }}{{ orderDetail.address }}</view>
</view>
</view>
<view class="detail-goods">
<!-- 订单信息 -->
<view class="order-list" v-for="order in orderDetail.item" :key="order.id">
<view class="order-card" @tap="jump('/pages/goods/detail', { id: order.goods_id })">
<shopro-mini-card :title="order.goods_title" :image="order.goods_image">
<template #describe>
<view class="order-sku u-ellipsis-1">
<text class="order-num">数量:{{ order.goods_num || 0 }};</text>
{{ order.goods_sku_text ? order.goods_sku_text : '' }}
</view>
</template>
<template #cardBottom>
<view class="card-price-box u-flex">
<text class="order-price font-OPPOSANS">¥{{ order.goods_price || 0 }}</text>
<button class="u-reset-button status-btn" v-if="order.status_name">{{ order.status_name }}</button>
</view>
</template>
</shopro-mini-card>
</view>
<!-- 配送方式 -->
<view class="express-type-box u-flex u-row-between">
<view class="u-flex">
<view class="express-type--title">配送:</view>
<view class="express-type--content">{{ expressType[order.dispatch_type] }}</view>
</view>
<!-- 发货详情 -->
<view
class="u-flex express-type--detail"
v-if="order.dispatch_type !== 'express'"
@tap="goDistribution(order.dispatch_type, orderDetail.id, order.id, order.dispatch_status)"
>
<text>详情</text>
<text class="u-iconfont uicon-arrow-right" style="color: #666;"></text>
</view>
</view>
<view class="order-bottom u-flex">
<!-- 退款原因 -->
<view class="refund_msg" v-if="order.refund_msg">
<text class="refund-title">退款原因:</text>
{{ order.refund_msg }}
</view>
<view class="btn-box" v-for="(btn, index) in order.btns" :key="btn">
<button @tap.stop="onConfirm(orderDetail.id, order.id)" class="u-reset-button btn1" :class="{ btn2: index + 1 === order.btns.length }" v-if="btn === 'get'">
确认收货
</button>
<button
@tap.stop="onComment(orderDetail.id, order.id)"
class="u-reset-button btn1"
:class="{ btn2: index + 1 === order.btns.length }"
v-if="btn === 'comment'"
>
去评价
</button>
<button
@tap.stop="jump('/pages/goods/detail', { id: order.goods_id })"
class="u-reset-button btn1"
:class="{ btn2: index + 1 === order.btns.length }"
v-if="btn === 'buy_again'"
>
再次购买
</button>
<button
@tap="onAftersaleDetail(order.ext_arr.aftersale_id)"
class="u-reset-button btn1"
:class="{ btn2: index + 1 === order.btns.length }"
v-if="btn === 'aftersale_info'"
>
售后详情
</button>
<button
@tap.stop="onAftersale(orderDetail.id, order.id)"
class="u-reset-button btn1"
:class="{ btn2: index + 1 === order.btns.length }"
v-if="btn === 'aftersale'"
>
申请售后
</button>
<button
@tap.stop="onAftersale(orderDetail.id, order.id)"
class="u-reset-button btn1"
:class="{ btn2: index + 1 === order.btns.length }"
v-if="btn === 're_aftersale'"
>
重新售后
</button>
</view>
</view>
</view>
</view>
<!-- 订单信息 -->
<view class="notice-box" v-if="orderDetail.id">
<view class="notice-box__content">
<view class="notice-item--center u-flex">
<text class="title">订单编号:</text>
<text class="detail">{{ orderDetail.order_sn }}</text>
<button class="u-reset-button copy-btn" @tap="onCopy(orderDetail.order_sn)">复制</button>
</view>
<view class="notice-item u-flex">
<text class="title">下单时间:</text>
<text class="detail">{{ orderDetail.createtime }}</text>
</view>
<view class="notice-item u-flex" v-if="orderDetail.status > 0">
<text class="title">支付方式:</text>
<text class="detail">{{ payType[orderDetail.pay_type] }}</text>
</view>
<view class="notice-item u-flex" v-if="orderDetail.status > 0">
<text class="title">支付时间:</text>
<text class="detail">{{ orderDetail.paytime }}</text>
</view>
</view>
</view>
<!-- 价格信息 -->
<view class="order-price-box u-m-b-20" v-if="orderDetail.id">
<view class="notice-item u-flex u-row-between">
<text class="title">商品总额</text>
<text class="detail font-OPPOSANS">¥{{ orderDetail.goods_amount }}</text>
</view>
<view class="notice-item u-flex u-row-between">
<text class="title">运费</text>
<text class="detail font-OPPOSANS">¥{{ orderDetail.dispatch_amount }}</text>
</view>
<view class="notice-item u-flex u-row-between">
<text class="title">优惠金额</text>
<text class="detail font-OPPOSANS">-¥{{ orderDetail.discount_fee }}</text>
</view>
<view class="notice-item u-flex u-row-between" v-if="orderDetail.score_fee">
<text class="title">积分</text>
<text class="detail font-OPPOSANS">-{{ orderDetail.score_fee }}积分</text>
</view>
<view class="notice-item all-rpice-item u-flex" style="width: 100%;">
<text class="title">{{ orderDetail.status <= 0 ? '需付款' : '实付款' }}:</text>
<text class="detail all-price font-OPPOSANS">¥{{ orderDetail.total_fee }}</text>
</view>
</view>
<!-- 底部按钮 -->
<view class="foot-wrap safe-area-inset-bottom">
<view class="foot_box safe-area-inset-bottom">
<view class="money-box u-flex ">
<text class="money-title">共{{ allNum || '0' }}件商品 合计:</text>
<text class="all-price font-OPPOSANS">¥{{ orderDetail.total_fee || '0.00' }}</text>
</view>
<view class="btn-box u-flex">
<view class="" v-for="btn in orderDetail.btns" :key="btn">
<button v-if="btn === 'cancel'" @tap.stop="onCancel(orderDetail.id)" class="u-reset-button obtn1">取消订单</button>
<button v-if="btn === 'pay'" @tap.stop="onPay(orderDetail.id)" class="u-reset-button obtn2">付款</button>
<button v-if="btn === 'groupon'" @tap.stop="jump('/pages/activity/groupon/detail', { id: orderDetail.ext_arr.groupon_id })" class="u-reset-button obtn2">
拼团详情
</button>
<button v-if="btn === 'delete'" @tap.stop="onDelete(orderDetail.id)" class="u-reset-button delete-btn">删除</button>
<button v-if="btn === 'express'" @tap.stop="onExpress(orderDetail.id)" class="u-reset-button obtn1">查看物流</button>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
time: 0,
orderDetail: {},
orderStatus: {
'-2': '已关闭',
'-1': '已取消',
'0': '未付款',
'1': '已付款',
'2': '已完成'
},
payType: {
wallet: '余额支付',
wechat: '微信支付',
alipay: '支付宝支付',
iospay: 'ApplePay'
},
expressType: {
express: '物流快递',
selfetch: '到店/自提',
store: '商家配送',
autosend: '自动发货'
}
};
},
onShow() {
this.getOrderDetail();
},
onLoad() {},
computed: {
allNum() {
if (this.orderDetail.item) {
let items = this.orderDetail.item;
let allPrice = 0;
items.forEach(p => {
allPrice += p.goods_num;
});
return allPrice;
}
}
},
methods: {
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 详情发货信息
goDistribution(dispatchType, orderId, orderItemId) {
this.jump('/pages/order/express/distribution-detail', { expressType: dispatchType, orderId: orderId, orderItemId: orderItemId });
},
// 订单详情
getOrderDetail() {
let that = this;
that.$http('order.detail', {
id: that.$Route.query.id
}).then(res => {
if (res.code === 1) {
that.orderDetail = res.data;
that.orderDetail.createtime = that.$u.timeFormat(res.data.createtime, 'yyyy-mm-dd hh:MM');
that.orderDetail.paytime = that.$u.timeFormat(res.data.paytime, 'yyyy-mm-dd hh:MM');
}
});
},
// 复制
onCopy(code) {
let that = this;
uni.setClipboardData({
data: code,
success: function(data) {
that.$u.toast('已复制到剪切板');
}
});
},
// 确认收货
onConfirm(id, itemId) {
let that = this;
uni.showModal({
title: '提示',
confirmColor: '#f0c785',
content: '确定收到货了吗?',
success: res => {
if (res.confirm) {
that.$http(
'order.confirm',
{
id: id,
order_item_id: itemId
},
'确认中...'
).then(res => {
if (res.code === 1) {
that.getOrderDetail();
}
});
}
}
});
},
// 申请售后
onAftersale(orderId, orderItemId) {
this.$Router.push({
path: '/pages/order/after-sale/refund',
query: { orderId: orderId, orderItemId: orderItemId }
});
},
// 售后详情
onAftersaleDetail(id) {
this.jump('/pages/order/after-sale/detail', { aftersaleId: id });
},
// 取消订单
onCancel(id) {
let that = this;
that.$http(
'order.cancel',
{
id: id
},
'取消中...'
).then(res => {
if (res.code === 1) {
that.$Router.back();
}
});
},
// 立即购买
onPay(id) {
uni.navigateTo({
url: `/pages/order/payment/method?orderId=${id}`
});
},
// 删除订单
onDelete(orderId) {
let that = this;
uni.showModal({
title: '删除订单',
content: '确定要删除这个订单么?',
cancelText: '取消',
confirmText: '删除',
success: res => {
if (res.confirm) {
that.$http('order.deleteOrder', {
id: orderId
}).then(res => {
if (res.code === 1) {
that.$Router.back();
}
});
}
}
});
},
// 待评价
onComment(orderId, orderItemId) {
this.jump('/pages/goods/comment/add-comment', { orderId: orderId, orderItemId: orderItemId });
},
// 查看物流
onExpress(orderId, orderItemId) {
let that = this;
that.$http('order.expressList', {
order_id: orderId
}).then(res => {
if (res.code === 1) {
if (res.data.length == 1) {
this.jump('/pages/order/express/express-detail', { orderId: orderId, expressId: res.data[0].id });
} else if (res.data.length > 1) {
this.jump('/pages/order/express/express-list', { orderId: orderId });
} else {
this.$u.toast('暂无包裹~');
}
}
});
}
}
};
</script>
<style lang="scss">
.detail-head {
background: linear-gradient(0deg, rgba(239, 196, 128, 1) 0%, rgba(248, 220, 165, 1) 100%) no-repeat;
background-size: 100% 180rpx;
height: 180rpx;
.state-box {
padding: 30rpx 40rpx;
color: rgba(#fff, 0.9);
.state-img {
width: 60rpx;
height: 60rpx;
// background: #ccc;
margin-right: 40rpx;
}
}
}
// 收货地址
.address-wrap {
position: relative;
background-color: #fff;
min-height: 160rpx;
width: 100%;
margin-bottom: 20rpx;
}
.order-address-box {
width: 710rpx;
left: 50%;
transform: translateX(-50%);
top: -50rpx;
background-color: #fff;
min-height: 160rpx;
border-radius: 20rpx;
padding: 20rpx;
font-size: 30rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
position: absolute;
.address-username {
margin-right: 20rpx;
}
.address-detail {
font-size: 26rpx;
font-weight: 500;
color: rgba(153, 153, 153, 1);
margin-top: 20rpx;
}
}
.detail-goods {
margin-bottom: 20rpx;
.order-list {
margin-bottom: 20rpx;
background-color: #fff;
padding: 0 20rpx;
.order-card {
padding: 20rpx 0;
.order-sku {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
width: 450rpx;
margin-bottom: 20rpx;
.order-num {
margin-right: 10rpx;
}
}
.card-price-box {
.status-btn {
height: 32rpx;
border: 1rpx solid rgba(207, 169, 114, 1);
border-radius: 15rpx;
font-size: 20rpx;
font-weight: 400;
color: rgba(168, 112, 13, 1);
padding: 0 10rpx;
margin-left: 20rpx;
background: rgba(233, 183, 102, 0.16);
}
.order-price {
font-size: 26rpx;
font-weight: 600;
color: rgba(51, 51, 51, 1);
}
}
}
// 配送方式
.express-type-box {
width: 710rpx;
height: 90rpx;
background: rgba(247, 247, 247, 1);
border-radius: 10rpx;
padding: 0 20rpx;
.express-type--title {
font-size: 28rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
}
.express-type--content {
font-size: 26rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
}
.express-type--detail {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
line-height: 30rpx;
}
}
.refund_msg {
font-size: 28rpx;
color: #999;
flex: 1;
text-align: left;
}
.order-bottom {
background: #fff;
justify-content: flex-end;
padding: 20rpx 0;
.btn1 {
width: 160rpx;
line-height: 60rpx;
border: 1rpx solid rgba(223, 223, 223, 1);
border-radius: 30rpx;
font-size: 26rpx;
background: #fff;
padding: 0;
margin-right: 20rpx;
}
.btn2 {
width: 160rpx;
line-height: 60rpx;
border: 1rpx solid rgba(213, 166, 90, 1) !important;
border-radius: 30rpx;
font-size: 26rpx;
color: #d5a65a !important;
padding: 0;
background: #fff;
margin-right: 20rpx;
}
}
}
}
// 拼团项目
.group-box {
background: #fff;
padding: 40rpx 0;
height: 250rpx;
margin-bottom: 20rpx;
.tip-box {
font-size: 28rpx;
}
.title-box {
font-size: 26rpx;
font-weight: bold;
color: #333;
.group-num {
color: #f8002c;
}
.count-down-tip {
font-size: 24rpx;
padding-left: 10rpx;
}
.time-box {
font-size: 18rpx;
.count-text {
display: inline-block;
background-color: #383a46;
color: #fff;
font-size: 18rpx;
border-radius: 2rpx;
padding: 0 5rpx;
height: 28rpx;
text-align: center;
line-height: 28rpx;
margin: 0 6rpx;
}
}
}
.group-people {
.img-box {
position: relative;
margin-right: 20rpx;
.tag {
position: absolute;
line-height: 36rpx;
background: linear-gradient(132deg, rgba(243, 223, 177, 1), rgba(243, 223, 177, 1), rgba(236, 190, 96, 1));
border-radius: 18rpx;
padding: 0 10rpx;
white-space: nowrap;
font-size: 24rpx;
color: #784f06;
z-index: 2;
top: -10rpx;
}
.avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: #ccc;
}
}
}
}
// 收货信息、订单信息。
.notice-box {
background: #fff;
margin-bottom: 20rpx;
.notice-box__head {
font-size: 30rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
line-height: 80rpx;
border-bottom: 1rpx solid #dfdfdf;
padding: 0 25rpx;
}
.notice-box__content {
padding: 25rpx;
.self-pickup-box {
width: 100%;
.self-pickup--img {
width: 200rpx;
height: 200rpx;
margin: 40rpx 0;
}
}
}
.notice-item--center {
margin-bottom: 10rpx;
}
.notice-item,
.notice-item--center {
height: 50rpx;
align-items: flex-start;
.title {
font-size: 28rpx;
color: #999;
text-align: justify;
text-align-last: justify;
text-justify: distribute-all-lines;
width: 140rpx;
}
.detail {
font-size: 28rpx;
color: #333;
flex: 1;
}
.copy-btn {
width: 100rpx;
line-height: 50rpx;
border-radius: 25rpx;
padding: 0;
background: rgba(238, 238, 238, 1);
font-size: 22rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
margin-left: 30rpx;
}
}
.notice-item--center {
align-items: center;
}
}
// 订单价格信息
.order-price-box {
background-color: #fff;
padding: 20rpx;
margin-bottom: 20rpx;
.notice-item {
height: 50rpx;
.title {
font-size: 28rpx;
color: #999;
}
.detail {
font-size: 28rpx;
color: #333;
}
}
.all-rpice-item {
justify-content: flex-end;
.all-price {
font-size: 26rpx;
color: #ff0000;
}
}
}
.foot-wrap {
width: 750rpx;
height: 140rpx;
}
.foot_box {
width: 750rpx;
min-height: 100rpx;
background: rgba(255, 255, 255, 1);
border: 1rpx solid rgba(238, 238, 238, 1);
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: center;
position: fixed;
bottom: 0;
left: 0;
.money-box {
height: 60rpx;
padding: 0 20rpx;
.money-title {
font-size: 26rpx;
color: #999999;
}
.all-price {
color: #333;
font-size: 26rpx;
}
}
.btn-box {
justify-content: flex-end;
margin-bottom: 20rpx;
.delete-btn {
background: #ffeeee;
color: #e50808;
width: 160rpx;
line-height: 60rpx;
border-radius: 30rpx;
font-size: 26rpx;
font-weight: 400;
margin-right: 20rpx;
padding: 0;
}
.obtn1 {
width: 160rpx;
line-height: 60rpx;
background: rgba(238, 238, 238, 1);
border-radius: 30rpx;
font-size: 26rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
margin-right: 20rpx;
padding: 0;
}
.obtn2 {
width: 160rpx;
line-height: 60rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(229, 138, 0, 0.22);
border-radius: 30rpx;
margin-right: 20rpx;
font-size: 26rpx;
font-weight: 400;
color: #fff;
padding: 0;
}
}
}
</style>

@ -0,0 +1,407 @@
<!-- 自提配送详情,自动发货详情 -->
<template>
<view class="page_box">
<view class="content_box">
<view class="card-wrap" :style="expressType !== 'selfetch' ? 'border-bottom-left-radius:0;border-bottom-right-radius:0;' : ''">
<!-- 商品卡片 -->
<view class="order-card-box">
<shopro-mini-card :title="itemDetail.goods_title" :image="itemDetail.goods_image">
<template #describe>
<view class="order-sku u-ellipsis-1">
<text class="order-num">数量:{{ itemDetail.goods_num || 0 }};</text>
{{ itemDetail.goods_sku_text ? itemDetail.goods_sku_text : '' }}
</view>
</template>
<template #cardBottom>
<view class="card-price-box u-flex">
<text class="order-price font-OPPOSANS">¥{{ itemDetail.goods_price || 0 }}</text>
<button class="u-reset-button status-btn" v-if="itemDetail.status_name">{{ itemDetail.status_name }}</button>
</view>
</template>
</shopro-mini-card>
</view>
<!-- 到店自提 -->
<view class="u-flex-col u-col-center u-m-b-20 u-p-y-20" v-if="expressType == 'selfetch' && itemDetail.status_code !== 'refund_finish' && qrcodeList.length">
<image class="qr-code--img u-m-y-20" :src="allqrcodepath" mode=""></image>
<view class="all-qrcode-title u-m-b-20">总核销码</view>
<view class="detail-item">
<view class="item-title u-flex">核销码</view>
<view class="u-flex u-row-between u-m-y-20" v-for="code in qrcodeList" :key="code.code">
<view>
<text :style="code.status_code !== 'nouse' ? 'color:#999' : ''" class="item-content">{{ code.code }}</text>
<text :style="code.status_code !== 'nouse' ? 'color:#999' : ''" class="item-status u-m-x-30">{{ code.status_name }}</text>
</view>
<button class="u-reset-button check-code" v-if="code.status_code == 'nouse'" @tap="checkCode(code.code)">查看</button>
</view>
<view class="item-tip">为保障您的权益,未到店消费前请不要将提货码提供给商家</view>
</view>
</view>
</view>
<!-- 自提 -->
<view class="detail-item u-p-20 " v-if="expressType == 'selfetch'">
<view class="item-title">{{ storeInfo.name }}</view>
<view class="u-flex u-row-between">
<view class="u-m-r-20" style="flex: 3;">
<view class="item-content">
{{ storeInfo.province_name || '' }}{{ storeInfo.city_name || '' }}{{ storeInfo.area_name || '' }}{{ storeInfo.address || '' }}
</view>
<view class="item-content">营业时间:{{ storeInfo.openhours || '' }}</view>
</view>
<view class="u-flex-col u-col-center location-box" style="flex: 1;" @tap="openStoreMap">
<text class="u-iconfont uicon-map-fill" style="color: #4fbbff;font-size: 34rpx;"></text>
<text class="location-text u-m-t-10">到这去</text>
</view>
</view>
</view>
<!-- 配送 -->
<view
class="detail-item u-p-20"
:style="expressType !== 'selfetch' ? 'border-top-left-radius:0;border-top-right-radius:0;' : ''"
v-if="expressType == 'store' && itemDetail.order"
>
<view class="item-title">配送信息</view>
<view class="item-content">配送地址:{{ itemDetail.order.city_name || '' }}{{ itemDetail.order.area_name || '' }}{{ itemDetail.order.address || '' }}</view>
<view class="item-content">配送时间:{{ itemDetail.ext_arr.dispatch_date || '' }}</view>
</view>
<!-- 自动 -->
<view class="detail-item u-p-20" :style="expressType !== 'selfetch' ? 'border-top-left-radius:0;border-top-right-radius:0;' : ''" v-if="expressType == 'autosend'">
<view class="item-title">发货信息</view>
<view v-if="itemDetail.ext_arr && itemDetail.ext_arr.autosend_type == 'params'" v-for="item in autosendList" :key="item.value" class="item-content">
{{ item.name }}:{{ item.value }}
</view>
<view v-if="itemDetail.ext_arr && itemDetail.ext_arr.autosend_type == 'text'" class="item-content">{{ itemDetail.ext_arr.autosend_content }}</view>
<view v-if="!(itemDetail.ext_arr && itemDetail.ext_arr.autosend_type)" style="color: #999;" class="item-content">暂未发货~</view>
</view>
</view>
<view class="foot_box u-flex u-row-center u-col-center u-p-b-20" v-if="expressType == 'selfetch' || expressType == 'store'">
<button class="service-btn u-reset-button u-flex u-col-center u-row-center" @tap="onService">
<text class="u-iconfont uicon-phone-fill u-m-r-10" style="color: #fff;font-size: 40rpx;"></text>
联系商家
</button>
</view>
<!-- 二维码弹窗 -->
<u-popup v-model="showQrcode" border-radius="20" mode="center">
<view class="qr-code-modal u-flex-col u-row-center u-col-center">
<image class="qr-code-img" :src="qrcodepath" mode=""></image>
<view class="qr-code-text">核销码:{{ qrcode }}</view>
<button class="u-reset-button hide-qrcode" @tap="showQrcode = false">关闭</button>
</view>
</u-popup>
<!-- 二维码绘制 -->
<view class="hideCanvasView">
<canvas class="hideCanvas" canvas-id="qrcode_img" :style="{ width: (poster.width || 1) + 'px', height: (poster.height || 1) + 'px' }"></canvas>
</view>
</view>
</template>
<script>
import _app from '@/shopro/poster/QS-SharePoster/app.js';
import { getSharePoster } from '@/shopro/poster/QS-SharePoster/QS-SharePoster.js';
export default {
components: {},
data() {
return {
poster: {},
canvasId: 'qrcode_img',
qrcode: '', //核销总码
qrcodeList: [], //核销码列表
storeInfo: {}, //自提点信息
qrcodepath: '', //单核销码
allqrcodepath: '', //总核销码
autosendList: [], //自动发货列表。
itemDetail: {}, //订单商品详情
expressType: 'express',
barTitle: {
express: '物流快递',
selfetch: '到店/自提',
store: '商家配送',
autosend: '自动发货'
},
showQrcode: false //二维码弹窗
};
},
computed: {},
onLoad() {
this.expressType = this.$Route.query.expressType;
uni.setNavigationBarTitle({
title: this.barTitle[this.expressType]
});
this.getItemGoodsDetail();
},
onPullDownRefresh() {
this.getItemGoodsDetail();
},
methods: {
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 自提打开地图
openStoreMap() {
uni.openLocation({
latitude: +this.storeInfo.latitude,
longitude: +this.storeInfo.longitude,
success: function() {
console.log('success');
},
fail: err => {
console.log(err);
}
});
},
// 跳转客服
onService() {
this.$tools.callPhone(this.itemDetail.store.phone);
},
// 查看核销码
checkCode(code) {
this.qrcode = code;
this.shareFc('qrcode').then(res => {
this.showQrcode = true;
});
},
// 订单详情
getItemGoodsDetail() {
let that = this;
that.$http('order.itemDetail', {
id: that.$Route.query.orderId,
order_item_id: that.$Route.query.orderItemId,
type: 'dispatch'
}).then(res => {
uni.stopPullDownRefresh();
if (res.code === 1) {
that.itemDetail = res.data;
that.qrcodeList = res.data.verify;
that.storeInfo = res.data.store;
if (res.data.ext_arr.autosend_content && res.data.ext_arr.autosend_type == 'params') {
that.autosendList = JSON.parse(res.data.ext_arr.autosend_content);
}
if (that.expressType == 'selfetch') {
that.qrcode = encodeURIComponent('all:' + that.$Route.query.orderId + '-' + that.$Route.query.orderItemId)
if (that.qrcodeList.length) {
that.shareFc();
}
}
}
});
},
// 绘制二维码
async shareFc(type) {
let that = this;
try {
console.log('准备生成:' + new Date());
const d = await getSharePoster({
_this: this, //若在组件中使用 必传
posterCanvasId: this.canvasId, //canvasId
delayTimeScale: 20, //延时系数
drawDelayTime: 500, //draw延时时间
background: {
width: 100,
height: 100,
backgroundColor: '#fff'
},
drawArray: ({ bgObj, type, bgScale }) => {
return new Promise((rs, rj) => {
rs([
{
type: 'qrcode',
text: this.qrcode,
size: bgObj.width,
dx: 0,
dy: 0,
correctLevel: 1
}
]);
});
},
setCanvasWH: ({ bgObj, type, bgScale }) => {
// 为动态设置画布宽高的方法,
this.poster = bgObj;
}
});
if (type == 'qrcode') {
this.qrcodepath = d.poster.tempFilePath;
} else {
this.allqrcodepath = d.poster.tempFilePath;
}
} catch (e) {
_app.hideLoading();
_app.showToast(JSON.stringify(e));
}
}
}
};
</script>
<style lang="scss">
// 二维码
.hideCanvasView {
position: relative;
.hideCanvas {
position: fixed;
top: -99999upx;
left: -99999upx;
z-index: -99999;
}
}
// 二维码弹窗
.qr-code-modal {
width: 610rpx;
border-radius: 20rpx;
background-color: #fff;
margin: 0 auto;
.qr-code-img {
margin: 100rpx;
width: 400rpx;
height: 400rpx;
}
.qr-code-text {
color: #999;
font-size: 28rpx;
}
.hide-qrcode {
width: 492rpx;
line-height: 70rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(229, 138, 0, 0.22);
border-radius: 35rpx;
font-size: 28rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
margin: 30rpx;
}
}
.content_box {
padding: 20rpx;
}
.no-radius {
border-radius: 0 !important;
}
.card-wrap {
background-color: #fff;
padding: 20rpx 20rpx 0;
border-radius: 20rpx;
}
.order-card-box {
height: 200rpx;
border-bottom: 2rpx solid rgba(238, 238, 238, 0.6);
.order-sku {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
width: 450rpx;
margin-bottom: 20rpx;
.order-num {
margin-right: 10rpx;
}
}
.card-price-box {
.status-btn {
height: 32rpx;
border: 1rpx solid rgba(207, 169, 114, 1);
border-radius: 15rpx;
font-size: 20rpx;
font-weight: 400;
color: rgba(168, 112, 13, 1);
padding: 0 10rpx;
margin-left: 20rpx;
background: rgba(233, 183, 102, 0.16);
}
.order-price {
font-size: 26rpx;
font-weight: 600;
color: rgba(51, 51, 51, 1);
}
}
}
.qr-code--img {
width: 200rpx;
height: 200rpx;
}
.detail-item {
background: rgba(255, 255, 255, 1);
border-radius: 20rpx;
width: 100%;
.item-title {
font-size: 28rpx;
font-weight: bold;
color: rgba(51, 51, 51, 1);
height: 60rpx;
}
.item-content {
font-size: 26rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
line-height: 50rpx;
}
.item-status {
font-size: 26rpx;
font-weight: 400;
color: rgba(179, 132, 54, 1);
}
.item-tip {
font-size: 24rpx;
font-weight: 400;
color: rgba(196, 196, 196, 1);
}
.check-code {
background: none;
border-radius: 25rpx;
border: 1rpx solid rgba(179, 132, 54, 1);
padding: 0;
width: 100rpx;
line-height: 50rpx;
font-size: 28rpx;
color: rgba(179, 132, 54, 1);
}
.location-box {
height: 100%;
justify-content: center;
position: relative;
&::before {
content: '';
width: 2rpx;
height: 94rpx;
background: #eeeeee;
position: absolute;
top: 50%;
transform: translateY(-50%);
left: 0;
}
.location-icon {
color: #4fbbff;
margin-bottom: 20rpx;
}
.location-text {
color: #999;
font-size: 26rpx;
}
}
}
.service-btn {
padding: 0;
width: 710rpx;
line-height: 80rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(229, 138, 0, 0.22);
border-radius: 40rpx;
font-size: 30rpx;
font-weight: 500;
color: rgba(255, 255, 255, 1);
}
</style>

@ -0,0 +1,217 @@
<!-- 物流信息 -->
<template>
<view class="map-box">
<!-- 商品卡片 -->
<view class="oilStation-top">
<view class="u-flex u-row-between express-card__head">
<view class="u-flex">
<view class="img-box">
<image class="goods-image" :src="firstGoods.goods_image" mode="aspectFill"></image>
<view class="shop-tag">{{ goodsList.length || 0 }}件商品</view>
</view>
<text class="express-status">{{ firstGoods.status_name || '' }}</text>
</view>
</view>
<view class="express-sn u-flex u-col-center ">
<text class="u-m-r-30">{{ expressDetail.express_name || '' }}
{{ expressDetail.express_no || '' }}</text>
<text class="u-iconfont uicon-file-text" style="color: #666;font-size: 32rpx;"
@tap="copyCode(expressDetail.express_no)"></text>
</view>
</view>
<!-- 物流时间轴 -->
<view class="oilStation-bottom">
<view class="u-p-y-30">
<view v-if="exrpessLog.length" class="express-item u-flex u-col-center"
v-for="(log, index) in exrpessLog" :key="log.id">
<view class="item-left">
<view class="day">{{ log.changedate.split(' ')[0]}}</view>
<view class="time">{{ log.changedate.split(' ')[1] }}</view>
</view>
<view class="item-right">
<image class="express-tag"
:src="$IMG_URL + `/imgs/order/express${index == 0 ? log.status + '' + log.status : log.status}.png`"
mode="aspectFill"></image>
<view class="express-title">{{ log.status_name }}</view>
<view class="express-detail">{{ log.content }}</view>
</view>
</view>
<view v-if="!exrpessLog.length" class="no-log u-flex u-row-center u-col-center">暂无物流信息~</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
expressDetail: {}, //物流信息
exrpessLog: [], //包裹轨迹
firstGoods: {}, //商品列表
goodsList: []
};
},
computed: {},
onLoad() {
this.getExpressDetail();
},
methods: {
// 复制物流码
copyCode(code) {
let that = this;
uni.setClipboardData({
data: code,
success: () => {
that.$u.toast('已复制到剪切板');
}
});
},
// 获取物流信息
getExpressDetail() {
let that = this;
that.$http('order.expressDetail', {
id: that.$Route.query.expressId,
order_id: that.$Route.query.orderId
}).then(res => {
if (res.code === 1) {
that.expressDetail = res.data;
that.goodsList = res.data.item;
that.exrpessLog = res.data.log;
that.firstGoods = res.data.item[0];
}
});
}
}
};
</script>
<style lang="scss">
/* 物流卡片 */
.oilStation-top {
justify-content: center;
align-items: center;
flex-direction: column;
background: #fff;
margin: 20rpx;
background: rgba(255, 255, 255, 1);
box-shadow: 0 1rpx 18rpx 0 rgba(184, 184, 184, 0.55);
border-radius: 20rpx;
.express-card__head {
padding: 20rpx;
}
.img-box {
position: relative;
width: 129rpx;
height: 129rpx;
border-radius: 20rpx;
overflow: hidden;
background: #ccc;
.goods-image {
width: 129rpx;
height: 129rpx;
}
}
.shop-tag {
position: absolute;
width: 129rpx;
height: 30rpx;
background: rgba(0, 0, 0, 0.6);
font-size: 18rpx;
font-weight: 400;
color: rgba(255, 255, 255, 1);
text-align: center;
bottom: 0;
z-index: 5;
}
.express-status {
font-size: 30rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
margin-left: 20rpx;
}
.express-sn {
width: 100%;
height: 65rpx;
background: rgba(250, 251, 255, 1);
border-radius: 0 0 20rpx 20rpx;
font-size: 24rpx;
font-weight: 500;
color: rgba(102, 102, 102, 1);
padding: 0 30rpx;
}
}
.oilStation-bottom {
justify-content: space-between;
align-items: center;
flex-direction: row;
background: #fff;
width: 710rpx;
margin: 0 20rpx 20rpx;
border-radius: 20rpx;
box-shadow: 0 1rpx 18rpx 0 rgba(184, 184, 184, 0.55);
padding: 0 30rpx;
}
// 物流步骤条
.no-log {
width: 100%;
height: 100rpx;
color: #999;
}
.express-item {
align-items: flex-start;
.item-left {
.day {
font-size: 26rpx;
white-space: nowrap;
}
.time {
font-size: 20rpx;
white-space: nowrap;
}
}
.item-right {
position: relative;
padding-left: 54rpx;
margin-left: 54rpx;
border-left: 1rpx solid #e7e7e7;
padding-bottom: 40rpx;
.express-tag {
width: 54rpx;
height: 54rpx;
position: absolute;
left: -27rpx;
top: 0;
}
.express-title {
font-size: 28rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
margin-bottom: 10rpx;
}
.express-detail {
width: 400rpx;
font-size: 24rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
}
}
}
</style>

@ -0,0 +1,114 @@
<!-- 物流分包 -->
<template>
<view class="express-list-wrap u-p-b-30">
<view class="tip-box u-flex u-col-center u-p-x-20">{{ expressList.length }}个包裹已发出</view>
<view class="express-list u-m-b-20" v-for="express in expressList" :key="express.id" @tap="toExpressDetail(express.id)">
<view class="u-flex u-row-between list-head u-p-x-20">
<text class="list-status" v-if="express.log.length">{{ express.log[0].status_name }}</text>
<view class="express-name">{{ express.express_name }}:{{ express.express_no }}</view>
</view>
<view class="list-content u-p-20">
<view class="express-detail u-m-b-20" v-if="express.log.length">{{ express.log[0].content }}</view>
<view class="goods-box u-flex u-col-center">
<view class="goods-img-box" v-for="img in express.item" :key="img.id"><image class="goods-img" :src="img.goods_image" mode="aspectFill"></image></view>
</view>
<view class="all-goods">共{{ express.item.length }}件商品</view>
</view>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
expressList: []
};
},
computed: {},
onLoad() {
this.getExpressList();
},
methods: {
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// 物流详情
toExpressDetail(id) {
this.jump('/pages/order/express/express-detail', { orderId: this.$Route.query.orderId, expressId: id });
},
// 包裹列表
getExpressList() {
let that = this;
that.$http('order.expressList', {
order_id: that.$Route.query.orderId
}).then(res => {
if (res.code === 1) {
that.expressList = res.data;
}
});
}
}
};
</script>
<style lang="scss">
.tip-box {
width: 750rpx;
height: 84rpx;
font-size: 30rpx;
font-weight: 500;
color: rgba(168, 112, 13, 1);
background: rgba(255, 226, 182, 1);
}
// 物流包裹
.express-list {
background-color: #fff;
.list-head {
height: 76rpx;
border-bottom: 1rpx solid rgba(223, 223, 223, 0.5);
.list-status {
font-size: 26rpx;
font-weight: 500;
color: rgba(168, 112, 13, 1);
}
.express-name {
font-size: 26rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
}
}
.list-content {
.express-detail {
font-size: 28rpx;
font-weight: 500;
color: rgba(51, 51, 51, 1);
}
.goods-box {
flex-wrap: wrap;
.goods-img-box {
width: 160rpx;
height: 160rpx;
overflow: hidden;
margin-right: 22rpx;
margin-bottom: 22rpx;
&:nth-child(4n) {
margin-right: 0;
}
.goods-img {
width: 100%;
height: 100%;
}
}
}
}
}
</style>

@ -0,0 +1,351 @@
<!-- 自提点 -->
<template>
<view class="map-box">
<!-- 标题栏 -->
<shopro-navbar>
<view class="search-wrap u-flex" slot="content">
<u-search
class="u-m-r-20"
placeholder="输入自提点名称"
@change="onSearch"
@search="onSearch"
@clear="clearSearch"
v-model="searchVal"
:showAction="false"
height="60"
></u-search>
</view>
</shopro-navbar>
<map id="map" :style="{ width: '750rpx', height: mapHeight + 'px' }" :latitude="latitude" :longitude="longitude" show-location @tap="onMap"></map>
<view class="dragLayer safe-area-inset-bottom" :class="moveCard">
<view class="oilStation">
<!-- 上下箭头 -->
<view class="oilStation-top">
<view class="touch-guide u-flex u-row-center u-col-center" @tap="onShowCard">
<image class="touch-jintou" :src="$IMG_URL + (showCard ? '/imgs/order/arrows2.png' : '/imgs/order/arrows1.png')" mode=""></image>
</view>
</view>
<!-- 自提点列表 -->
<scroll-view class="oilStation-bottom" enable-back-to-top scroll-y @scrolltolower="loadMore">
<view class=" list-content">
<u-radio-group style="width: 100%;" v-model="storeId" active-color="#e9b561">
<view style="width: 100%;" @tap="selStoreAddress(address.id, index)" v-for="(address, index) in addressList" :key="address.id">
<view class="address-item u-flex u-col-center u-row-between">
<view class="address-left">
<!-- 门店 -->
<view class="address-name">{{ address.name }}</view>
<!-- 时间 -->
<view class="time-box ">
<view class=" u-flex u-col-center">
<text class="u-iconfont uicon-clock u-m-r-20" style="color: #999;"></text>
{{ address.openhours }}
</view>
<view class="u-flex u-col-center u-p-l-30 u-m-t-20">
<text v-for="week in address.openweeks_arr" :key="week">{{ weekMap[week] }}、</text>
</view>
</view>
<!-- 地址 -->
<view class="address-detail u-flex u-col-center">
<text class="u-iconfont uicon-map u-m-r-20" style="color: #999;"></text>
{{ address.province_name }}{{ address.city_name }}{{ address.area_name }}{{ address.address }}
</view>
<!-- 电话 -->
<view class="address-phone u-flex u-col-center">
<text class="u-iconfont uicon-phone u-m-r-20" style="color: #999;"></text>
{{ address.phone }}
</view>
</view>
<view class="address-right u-flex-col u-row-center u-col-center">
<u-radio :name="address.id"></u-radio>
<text class="address-distance u-m-t-10">{{ address.distance_text || 0 }}</text>
</view>
</view>
</view>
</u-radio-group>
<!-- 更多 -->
<u-loadmore v-if="addressList.length" height="80rpx" :status="loadStatus" icon-type="flower" color="#ccc" />
</view>
</scroll-view>
<view class="foot_box u-flex u-row-center u-col-center"><button class="u-reset-button save-btn" @tap="onSave">确认</button></view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
longitude: 0,
latitude: 0,
mapHeight: 0,
showCard: false,
moveCard: 'dragLayer-bottom',
distance: 0,
storeId: 0, //默认门店ID
addressList: [], //门店列表
storeInfo: [], //当前门店信息
weekMap: {
1: '周一',
2: '周二',
3: '周三',
4: '周四',
5: '周五',
6: '周六',
7: '周日'
},
loadStatus: 'loadmore',
currentPage: 1,
lastPage: 1,
searchVal: ''
};
},
computed: {},
onLoad(options) {
this.latitude = this.$Route.query.lat;
this.longitude = this.$Route.query.lng;
this.storeId = this.$Route.query.storeId;
this.getStoreAddress();
},
onReady() {
this.editHeight();
},
methods: {
// 选中门店
onSave() {
if (this.storeId) {
uni.$emit('SELECT_STORE', { storeInfo: JSON.stringify(this.storeInfo) });
this.$Router.back();
} else {
this.$u.toast('请选择门店');
}
},
// 搜索
onSearch() {
this.addressList = [];
this.$u.debounce(this.getStoreAddress());
},
// 清除搜索框
clearSearch() {
this.searchVal = '';
this.onSearch();
},
// 设置地图,卡片高度。
editHeight() {
let that = this;
uni.getSystemInfo({
success: res => {
let view = uni
.createSelectorQuery()
.in(this)
.select('.dragLayer');
view.fields(
{
size: true,
scrollOffset: true
},
data => {
that.mapHeight = res.windowHeight - data.height;
//#ifdef APP-PLUS
that.mapHeight = res.windowHeight - data.height - 40;
//#endif
}
).exec();
}
});
},
// 点击地图
onMap() {
this.showCard = false;
this.moveCard = 'dragLayer-bottom';
},
// 选择门店
selStoreAddress(id, index) {
this.storeId = id;
this.latitude = this.addressList[index].latitude;
this.longitude = this.addressList[index].longitude;
this.storeInfo = this.addressList[index];
},
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getStoreAddress();
}
},
// 获取商品支持的自提点。
getStoreAddress() {
let that = this;
that.loadStatus = 'loading';
that.$http('goods.storeAddress', {
id: that.$Route.query.goodsId,
latitude: that.$Route.query.lat,
longitude: that.$Route.query.lng,
keyword: that.searchVal,
page: that.currentPage,
per_page: 3
}).then(res => {
if (res.code == 1) {
that.addressList = [...that.addressList, ...res.data.data];
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
if (this.currentPage === 1) {
this.storeId = res.data.data[0].id;
this.storeInfo = res.data.data[0];
}
}
});
},
// 点击显隐
onShowCard() {
this.showCard = !this.showCard;
this.moveCard = this.showCard ? 'dragLayer-top' : 'dragLayer-bottom';
}
}
};
</script>
<style lang="scss">
.search-wrap {
/* #ifdef MP-WEIXIN */
width: 460rpx;
/* #endif */
/* #ifndef MP-WEIXIN */
width: 100%;
/* #endif */
}
/* 浮层 */
.dragLayer {
width: 750rpx;
position: fixed;
z-index: 996;
background-color: #fff;
border-top-left-radius: 20rpx;
border-top-right-radius: 20rpx;
bottom: 0;
box-shadow: 0px 1rpx 18rpx 0px rgba(83, 83, 83, 0.35);
display: flex;
height: 100%;
}
// 点击上下
.dragLayer-bottom {
height: 450rpx !important;
/*#ifdef APP-PLUS */
height: 800rpx !important;
/*#endif*/
transition: all ease-in-out 0.2s;
}
.dragLayer-top {
height: 800rpx !important;
transition: all ease-in-out 0.2s;
}
.oilStation {
width: 690rpx;
margin: 0 30rpx;
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
}
.oilStation-top {
justify-content: center;
align-items: center;
flex-direction: column;
}
.touch-guide {
justify-content: center;
align-items: center;
height: 100rpx;
}
.touch-jintou {
width: 60rpx;
height: 8rpx;
}
// 确认门店
.foot_box {
height: 100rpx;
background-color: #fff;
.save-btn {
color: #fff;
width: 690rpx;
line-height: 80rpx;
background: linear-gradient(90deg, rgba(233, 181, 97, 1), rgba(238, 204, 138, 1));
box-shadow: 1rpx 7rpx 6rpx 0px rgba(229, 138, 0, 0.22);
border-radius: 40rpx;
font-size: 28rpx;
}
}
// 自提点列表
.oilStation-bottom {
justify-content: space-between;
align-items: center;
flex-direction: row;
flex: 1;
height: 100%;
overflow: hidden;
}
.list-content {
background-color: #fff;
overflow-x: hidden;
}
.address-item {
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: #eef1f4;
padding: 15rpx 0 15rpx;
width: 100%;
justify-content: space-between;
align-items: center;
flex-direction: row;
}
.address-name {
font-size: 30rpx;
font-weight: 600;
color: rgba(51, 51, 51, 1);
margin-bottom: 30rpx;
}
.time-box,
.address-detail,
.address-phone {
flex-direction: row;
align-items: center;
font-size: 24rpx;
font-weight: 400;
color: #666;
margin-bottom: 20rpx;
}
.address-icon {
color: #bdbdbd;
margin-right: 10rpx;
font-size: 30rpx;
}
.address-left {
flex: 1;
}
.address-right {
align-items: center;
color: #a8700e;
font-size: 24rpx;
margin-bottom: 20rpx;
white-space: nowrap;
}
.more {
width: 750rpx;
height: 90rpx;
justify-content: center;
align-items: center;
}
.more-text {
font-size: 24rpx;
color: #ccc;
}
</style>

@ -0,0 +1,407 @@
<template>
<view class="page_box">
<view class="head_box">
<!-- tab -->
<view class="order-nav u-flex">
<view class="nav-item u-flex-col u-flex-1 u-col-center" v-for="nav in orderState" :key="nav.id"
@tap="onNav(nav.type)">
<view class="item-title">{{ nav.title }}</view>
<text class="nav-line" :class="{ 'line-active': orderType === nav.type }"></text>
</view>
</view>
</view>
<view class="content_box">
<scroll-view scroll-y="true" enable-back-to-top @scrolltolower="loadMore" class="scroll-box">
<!-- 订单列表 -->
<view class="order-list" v-for="(order, orderIndex) in orderList" :key="order.id"
@tap.stop="jump('/pages/order/detail', { id: order.id })">
<view class="order-head u-flex u-row-between">
<text class="no">订单编号:{{ order.order_sn }}</text>
<text class="state">{{ order.status_name }}</text>
</view>
<view class="goods-order" v-for="goods in order.item" :key="goods.id">
<view class="order-content">
<shopro-mini-card :title="goods.goods_title" :image="goods.goods_image">
<template #describe>
<view class="order-sku u-ellipsis-1">
<text class="order-num">数量:{{ goods.goods_num || 0 }};</text>
{{ goods.goods_sku_text ? goods.goods_sku_text : '' }}
</view>
</template>
<template #cardBottom>
<view class="order-price-box u-flex ">
<text class="order-price font-OPPOSANS">¥{{ goods.goods_price || 0 }}</text>
<button class="u-reset-button status-btn"
v-if="goods.status_name">{{ goods.status_name }}</button>
</view>
</template>
</shopro-mini-card>
</view>
</view>
<view class="order-bottom">
<view class="all-msg u-flex font-OPPOSANS">
优惠:
<text class="all-unit">¥</text>
{{ order.discount_fee }} ,运费:
<text class="all-unit">¥</text>
{{ order.dispatch_amount }} ,{{ order.status <= 0 ? '需付款' : '实付款' }}:
<view class="all-money font-OPPOSANS">{{ order.total_fee }}</view>
</view>
<!-- 按钮 -->
<view class="btn-box u-flex" v-if="order.btns.length">
<block v-for="orderBtn in order.btns" :key="orderBtn">
<button v-if="orderBtn === 'cancel'" @tap.stop="onCancel(order.id, orderIndex)"
class="u-reset-button obtn1">取消订单</button>
<button v-if="orderBtn === 'pay'" @tap.stop="onPay(order.id)"
class="u-reset-button obtn2">立即支付</button>
<button v-if="orderBtn === 'groupon'"
@tap.stop="jump('/pages/activity/groupon/detail', { id: order.ext_arr.groupon_id })"
class="u-reset-button obtn2">
拼团详情
</button>
<button v-if="orderBtn === 'delete'" @tap.stop="onDelete(order.id, orderIndex)"
class="u-reset-button obtn3">删除</button>
<button v-if="orderBtn === 'express'" @tap.stop="onExpress(order.id, orderIndex)"
class="u-reset-button obtn1">查看物流</button>
</block>
</view>
</view>
</view>
<!-- 空白页 -->
<shopro-empty v-if="isEmpty" :image="$IMG_URL + '/imgs/empty/empty_groupon.png'"
tipText="暂无商品,还有更多好货等着你噢~"></shopro-empty>
<!-- 更多 -->
<u-loadmore v-show="orderList.length" height="80rpx" :status="loadStatus" icon-type="flower"
color="#ccc" />
</scroll-view>
</view>
</view>
</template>
<script>
export default {
components: {},
data() {
return {
isEmpty: false,
loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
currentPage: 1,
lastPage: 1,
orderType: 'all',
orderList: [],
orderState: [{
id: 0,
title: '全部',
type: 'all'
},
{
id: 1,
title: '待付款',
type: 'nopay'
},
{
id: 2,
title: '待发货',
type: 'nosend'
},
{
id: 3,
title: '待收货',
type: 'noget'
},
{
id: 4,
title: '待评价',
type: 'nocomment'
}
]
};
},
onShow() {
if (this.$Route.query.type) {
this.orderType = this.$Route.query.type;
}
this.orderList = [];
this.currentPage = 1;
this.lastPage = 1;
this.getOrderList();
},
methods: {
jump(path, parmas) {
this.$Router.push({
path: path,
query: parmas
});
},
// tab切换
onNav(id) {
if (this.orderType !== id) {
this.orderType = id;
this.orderList = [];
this.currentPage = 1;
this.lastPage = 1;
this.getOrderList();
}
},
// 订单列表
getOrderList() {
let that = this;
that.loadStatus = 'loading';
that.$http('order.index', {
type: that.orderType,
page: that.currentPage
}, '加载中...').then(res => {
if (res.code === 1) {
that.orderList = [...that.orderList, ...res.data.data];
that.isEmpty = !that.orderList.length;
that.lastPage = res.data.last_page;
that.loadStatus = that.currentPage < res.data.last_page ? 'loadmore' : 'nomore';
}
});
},
// 加载更多
loadMore() {
if (this.currentPage < this.lastPage) {
this.currentPage += 1;
this.getOrderList();
}
},
// 删除订单
onDelete(orderId, orderIndex) {
let that = this;
uni.showModal({
title: '删除订单',
content: '确定要删除这个订单么?',
cancelText: '取消',
confirmText: '删除',
success: res => {
if (res.confirm) {
that.$http('order.deleteOrder', {
id: orderId
},
'删除中...'
).then(res => {
if (res.code === 1) {
that.$u.toast(res.msg);
that.orderList.splice(orderIndex, 1);
}
});
}
}
});
},
// 取消订单
onCancel(id, orderIndex) {
let that = this;
that.$http('order.cancel', {
id: id
},
'取消中...'
).then(res => {
if (res.code === 1) {
that.$u.toast(res.msg);
this.orderList.splice(orderIndex, 1);
}
});
},
// 立即购买
onPay(id) {
uni.navigateTo({
url: `/pages/order/payment/method?orderId=${id}&orderType=goods`
});
},
// 查看物流
onExpress(orderId) {
let that = this;
that.$http('order.expressList', {
order_id: orderId
}).then(res => {
if (res.code === 1) {
if (res.data.length == 1) {
this.jump('/pages/order/express/express-detail', { orderId: orderId, expressId: res
.data[0].id });
} else if (res.data.length > 1) {
this.jump('/pages/order/express/express-list', { orderId: orderId });
} else {
that.$u.toast('暂无包裹~');
}
}
});
}
}
};
</script>
<style lang="scss">
.order-nav {
background: #fff;
height: 80rpx;
.nav-item {
flex: 1;
.item-title {
font-size: 30rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
line-height: 76rpx;
}
.nav-line {
width: 100rpx;
height: 4rpx;
background: #fff;
}
.line-active {
background: rgba(230, 184, 115, 1);
}
}
}
.order-list {
background: #fff;
margin: 20rpx 0;
.order-bottom {
padding-bottom: 20rpx;
.btn-box {
justify-content: flex-end;
}
.all-msg {
font-size: 24rpx;
color: #999;
justify-content: flex-end;
margin-bottom: 10rpx;
padding: 0 30rpx;
.all-unit {
font-size: 20rpx;
}
.all-money {
font-size: 26rpx;
color: #333;
font-weight: 500;
&::before {
content: '¥';
font-size: 20rpx;
}
}
}
.obtn1 {
width: 160rpx;
line-height: 60rpx;
background: rgba(238, 238, 238, 1);
border-radius: 30rpx;
font-size: 26rpx;
font-weight: 400;
color: rgba(51, 51, 51, 1);
margin-right: 20rpx;
padding: 0;
}
.obtn2 {
width: 160rpx;
line-height: 60rpx;
background: linear-gradient(90deg, rgba(233, 180, 97, 1), rgba(238, 204, 137, 1));
box-shadow: 0px 7rpx 6rpx 0px rgba(229, 138, 0, 0.22);
border-radius: 30rpx;
margin-right: 20rpx;
font-size: 26rpx;
font-weight: 400;
color: #fff;
padding: 0;
}
.obtn3 {
background: #ffeeee;
color: #e50808;
width: 160rpx;
line-height: 60rpx;
border-radius: 30rpx;
margin-right: 20rpx;
font-size: 26rpx;
font-weight: 400;
padding: 0;
}
}
.order-head {
padding: 0 25rpx;
height: 77rpx;
border-bottom: 1rpx solid #dfdfdf;
.no {
font-size: 26rpx;
color: #999;
}
.state {
font-size: 26rpx;
color: #a8700d;
}
}
.goods-order {
border-bottom: 1px solid rgba(223, 223, 223, 0.5);
padding: 20rpx 20rpx 0;
margin-bottom: 20rpx;
.order-content {
padding-bottom: 20rpx;
.order-sku {
font-size: 24rpx;
font-weight: 400;
color: rgba(153, 153, 153, 1);
width: 450rpx;
margin-bottom: 20rpx;
.order-num {
margin-right: 10rpx;
}
}
.order-price-box {
.status-btn {
height: 32rpx;
border: 1rpx solid rgba(207, 169, 114, 1);
border-radius: 15rpx;
font-size: 20rpx;
font-weight: 400;
color: rgba(168, 112, 13, 1);
padding: 0 10rpx;
margin-left: 20rpx;
background: rgba(233, 183, 102, 0.16);
}
.order-price {
font-size: 26rpx;
font-weight: 600;
color: rgba(51, 51, 51, 1);
}
}
}
}
}
</style>

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

Loading…
Cancel
Save