@@ -0,0 +1,12 @@
+unpackage/*
+node_modules/*
+.idea/*
+deploy.sh
+.hbuilderx/
+.vscode/
+**/.DS_Store
+.env
+yarn.lock
+package-lock.json
+*.keystore
+pnpm-lock.yaml
@@ -0,0 +1,6 @@
+/unpackage/*
+/node_modules/**
+/uni_modules/**
+/public/*
+**/*.svg
+**/*.sh
@@ -0,0 +1,10 @@
+{
+ "printWidth": 100,
+ "semi": true,
+ "vueIndentScriptAndStyle": true,
+ "singleQuote": true,
+ "trailingComma": "all",
+ "proseWrap": "never",
+ "htmlWhitespaceSensitivity": "strict",
+ "endOfLine": "auto"
+}
@@ -0,0 +1,39 @@
+<script setup>
+ import { onLaunch, onShow, onError } from '@dcloudio/uni-app';
+ import { ShoproInit } from './sheep';
+
+ onLaunch(() => {
+ // 隐藏原生导航栏 使用自定义底部导航
+ uni.hideTabBar();
+ // 加载Shopro底层依赖
+ ShoproInit();
+ });
+ onError((err) => {
+ console.log('AppOnError:', err);
+ onShow((options) => {
+ // #ifdef APP-PLUS
+ // 获取urlSchemes参数
+ const args = plus.runtime.arguments;
+ if (args) {
+ }
+ // 获取剪贴板
+ uni.getClipboardData({
+ success: (res) => { },
+ // #endif
+ // #ifdef MP-WEIXIN
+ // 确认收货回调结果
+ console.log(options,'options');
+</script>
+<style lang="scss">
+ @import '@/sheep/scss/index.scss';
+</style>
@@ -0,0 +1,21 @@
+MIT License
+Copyright (c) 2022 lidongtony
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
@@ -0,0 +1,56 @@
+**严肃声明:现在、未来都不会有商业版本,所有代码全部开源!!**
+**「我喜欢写代码,乐此不疲」**
+**「我喜欢做开源,以此为乐」**
+我 🐶 在上海艰苦奋斗,早中晚在 top3 大厂认真搬砖,夜里为开源做贡献。
+如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
+## 🐶 新手必读
+* 演示地址:<https://doc.iocoder.cn/mall-preview/>
+* 启动文档:<https://doc.iocoder.cn/quick-start/>
+* 视频教程:<https://doc.iocoder.cn/video/>
+## 🐯 商城简介
+**芋道商城**,基于 [芋道开发平台](https://github.com/YunaiV/ruoyi-vue-pro) 构建,以开发者为中心,打造中国第一流的 Java 开源商城系统,全部开源,个人与企业可 100% 免费使用。
+> 有任何问题,或者想要的功能,可以在 Issues 中提给艿艿。
+>
+> 😜 给项目点点 Star 吧,这对我们真的很重要!
+
+* 基于 uni-app + Vue3 开发,支持微信小程序、微信公众号、H5 移动端,未来会支持支付宝小程序、抖音小程序等
+* 支持 SaaS 多租户,可满足商品、订单、支付、会员、优惠券、秒杀、拼团、砍价、分销、积分等多种经营需求
+## 🔥 后端架构
+支持 Spring Boot、Spring Cloud 两种架构:
+① Spring Boot 单体架构:<https://github.com/YunaiV/ruoyi-vue-pro>
+
+② Spring Cloud 微服务架构:<https://github.com/YunaiV/yudao-cloud>
+
+## 🐱 移动端预览
+
+## 🐶 管理端预览
+
+
+
+
+
@@ -0,0 +1,3 @@
+ "prompt" : "template"
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <meta
+ name="viewport"
+ content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
+ />
+ <title></title>
+ <!--preload-links-->
+ <!--app-context-->
+ </head>
+ <body>
+ <div id="app"><!--app-html--></div>
+ <script type="module" src="/main.js"></script>
+ </body>
+</html>
@@ -0,0 +1,9 @@
+ "compilerOptions": {
+ "jsx": "preserve",
+ "baseUrl": ".",
+ "paths": {
+ "@/*": ["./*"]
@@ -0,0 +1,15 @@
+import App from './App';
+import { createSSRApp } from 'vue';
+import { setupPinia } from './sheep/store';
+export function createApp() {
+ const app = createSSRApp(App);
+ setupPinia(app);
+ return {
+ app,
+ };
@@ -0,0 +1,243 @@
+ "name": "芋道商城",
+ "appid": "__UNI__5EA4FAF",
+ "description": "基于 uni-app + Vue3 技术驱动的在线商城系统,内含诸多功能与丰富的活动,期待您的使用和反馈。",
+ "versionName": "1.8.3",
+ "versionCode": 183,
+ "transformPx": false,
+ "app-plus": {
+ "usingComponents": true,
+ "nvueCompiler": "uni-app",
+ "nvueStyleCompiler": "uni-app",
+ "compilerVersion": 3,
+ "nvueLaunchMode": "fast",
+ "splashscreen": {
+ "alwaysShowBeforeRender": true,
+ "waiting": true,
+ "autoclose": true,
+ "delay": 0
+ },
+ "safearea": {
+ "bottom": {
+ "offset": "none"
+ "modules": {
+ "Payment": {},
+ "Share": {},
+ "VideoPlayer": {},
+ "OAuth": {}
+ "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_MOCK_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.GET_TASKS\"/>",
+ "<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_LOGS\"/>",
+ "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+ "<uses-permission android:name=\"android.permission.READ_SMS\"/>",
+ "<uses-permission android:name=\"android.permission.RECEIVE_BOOT_COMPLETED\"/>",
+ "<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
+ "<uses-permission android:name=\"android.permission.SEND_SMS\"/>",
+ "<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\"/>",
+ "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+ "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+ "<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
+ "<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
+ "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
+ "<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
+ "<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>"
+ ],
+ "minSdkVersion": 21,
+ "schemes": "shopro",
+ "abiFilters": [
+ "armeabi-v7a",
+ "arm64-v8a",
+ "x86"
+ ]
+ "ios": {
+ "urlschemewhitelist": [
+ "baidumap",
+ "iosamap"
+ "dSYMs": false,
+ "privacyDescription": {
+ "NSPhotoLibraryUsageDescription": "需要同意访问您的相册选取图片才能完善该条目",
+ "NSPhotoLibraryAddUsageDescription": "需要同意访问您的相册才能保存该图片",
+ "NSCameraUsageDescription": "需要同意访问您的摄像头拍摄照片才能完善该条目",
+ "NSUserTrackingUsageDescription": "开启追踪并不会获取您在其它站点的隐私信息,该行为仅用于标识设备,保障服务安全和提升浏览体验"
+ "urltypes": "shopro",
+ "capabilities": {
+ "entitlements": {
+ "com.apple.developer.associated-domains": [
+ "applinks:shopro.sheepjs.com"
+ "idfa": true
+ "sdkConfigs": {
+ "speech": {
+ "ifly": {}
+ "ad": {},
+ "oauth": {
+ "apple": {},
+ "weixin": {
+ "appid": "wxae7a0c156da9383b",
+ "UniversalLinks": "https://shopro.sheepjs.com/uni-universallinks/__UNI__082C0BA/"
+ "payment": {
+ "__platform__": [
+ "ios",
+ "android"
+ "alipay": {
+ "share": {
+ "orientation": [
+ "portrait-primary"
+ "androidStyle": "common",
+ "iosStyle": "common",
+ "useOriginalMsgbox": true
+ "icons": {
+ "hdpi": "",
+ "xhdpi": "",
+ "xxhdpi": "",
+ "xxxhdpi": ""
+ "appstore": "",
+ "ipad": {
+ "app": "",
+ "app@2x": "",
+ "notification": "",
+ "notification@2x": "",
+ "proapp@2x": "",
+ "settings": "",
+ "settings@2x": "",
+ "spotlight": "",
+ "spotlight@2x": ""
+ "iphone": {
+ "app@3x": "",
+ "notification@3x": "",
+ "settings@3x": "",
+ "spotlight@2x": "",
+ "spotlight@3x": ""
+ "quickapp": {},
+ "quickapp-native": {
+ "icon": "/static/logo.png",
+ "package": "com.example.demo",
+ "features": [
+ {
+ "name": "system.clipboard"
+ "quickapp-webview": {
+ "minPlatformVersion": 1070,
+ "versionName": "1.0.0",
+ "versionCode": 100
+ "mp-weixin": {
+ "appid": "wx63c280fe3248a3e7",
+ "setting": {
+ "urlCheck": false,
+ "minified": true,
+ "postcss": true
+ "optimization": {
+ "subPackages": true
+ "plugins": {},
+ "lazyCodeLoading": "requiredComponents",
+ "usingComponents": {},
+ "permission": {},
+ "requiredPrivateInfos": [
+ "chooseAddress"
+ "mp-alipay": {
+ "usingComponents": true
+ "mp-baidu": {
+ "mp-toutiao": {
+ "mp-jd": {
+ "h5": {
+ "template": "index.html",
+ "router": {
+ "mode": "hash",
+ "base": "./"
+ "maps": {}
+ "async": {
+ "timeout": 20000
+ "title": "芋道商城",
+ "treeShaking": {
+ "enable": true
+ "vueVersion": "3",
+ "_spaceID": "192b4892-5452-4e1d-9f09-eee1ece40639",
+ "locale": "zh-Hans",
+ "fallbackLocale": "zh-Hans"
@@ -0,0 +1,104 @@
+ "id": "shopro",
+ "name": "shopro",
+ "displayName": "芋道商城",
+ "version": "2.0.1",
+ "description": "芋道商城,一套代码,同时发行到iOS、Android、H5、微信小程序多个平台,请使用手机扫码快速体验强大功能",
+ "scripts": {
+ "prettier": "prettier --write \"{pages,sheep}/**/*.{js,json,tsx,css,less,scss,vue,html,md}\""
+ "repository": "https://github.com/sheepjs/shop.git",
+ "keywords": [
+ "商城",
+ "B2C",
+ "商城模板"
+ "author": "",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/sheepjs/shop/issues"
+ "homepage": "https://github.com/dcloudio/hello-uniapp#readme",
+ "dcloudext": {
+ "category": [
+ "前端页面模板",
+ "uni-app前端项目模板"
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ "sourcecode": {
+ "contact": {
+ "qq": ""
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ "npmurl": ""
+ "uni_modules": {
+ "dependencies": [],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "u",
+ "aliyun": "u"
+ "client": {
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "u"
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ "小程序": {
+ "微信": "y",
+ "阿里": "u",
+ "百度": "u",
+ "字节跳动": "u",
+ "QQ": "u",
+ "京东": "u"
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ "Vue": {
+ "vue2": "u",
+ "vue3": "y"
+ "dependencies": {
+ "@hyoga/uni-socket.io": "^1.0.1",
+ "dayjs": "^1.11.7",
+ "lodash": "^4.17.21",
+ "luch-request": "^3.0.8",
+ "pinia": "^2.0.33",
+ "pinia-plugin-persist-uni": "^1.2.0",
+ "qs-canvas": "^1.0.11",
+ "weixin-js-sdk": "^1.6.0"
+ "devDependencies": {
+ "prettier": "^2.8.7",
+ "vconsole": "^3.15.0"
@@ -0,0 +1,661 @@
+ "easycom": {
+ "autoscan": true,
+ "custom": {
+ "^s-(.*)": "@/sheep/components/s-$1/s-$1.vue",
+ "^su-(.*)": "@/sheep/ui/su-$1/su-$1.vue"
+ "pages": [
+ // {
+ // "path": "pages/index/index",
+ // "aliasPath": "/",
+ // "style": {
+ // "navigationBarTitleText": "首页",
+ // "enablePullDownRefresh": true
+ // },
+ // "meta": {
+ // "auth": false,
+ // "sync": true,
+ // "title": "首页",
+ // "group": "商城"
+ // }
+ "path": "pages/index/category",
+ "style": {
+ "navigationBarTitleText": "商品分类"
+ "meta": {
+ "sync": true,
+ "title": "商品分类",
+ "group": "商城"
+ "path": "pages/index/user",
+ "navigationBarTitleText": "个人中心",
+ "enablePullDownRefresh": true
+ "title": "个人中心",
+ "path": "pages/index/cart",
+ "navigationBarTitleText": "购物车"
+ // "title": "购物车",
+ "path": "pages/index/login",
+ "navigationBarTitleText": "登录"
+ "path": "pages/index/search",
+ "navigationBarTitleText": "搜索"
+ "title": "搜索",
+ "path": "pages/index/page",
+ "navigationBarTitleText": ""
+ "auth": false,
+ "title": "自定义页面",
+ "subPackages": [{
+ "root": "pages/goods",
+ "pages": [{
+ "path": "index",
+ "navigationBarTitleText": "商品详情"
+ "title": "普通商品",
+ "group": "商品"
+ "path": "groupon",
+ "navigationBarTitleText": "拼团商品"
+ "title": "拼团商品",
+ "path": "seckill",
+ "navigationBarTitleText": "秒杀商品"
+ "title": "秒杀商品",
+ "path": "list",
+ "navigationBarTitleText": "商品列表"
+ "title": "商品列表",
+ "path": "comment/add",
+ "navigationBarTitleText": "评价商品"
+ "auth": true
+ "path": "comment/list",
+ "navigationBarTitleText": "商品评价"
+ "root": "pages/order",
+ "path": "detail",
+ "navigationBarTitleText": "订单详情"
+ "auth": true,
+ "title": "订单详情"
+ "path": "confirm",
+ "navigationBarTitleText": "确认订单"
+ "title": "确认订单"
+ "navigationBarTitleText": "我的订单",
+ "title": "用户订单",
+ "group": "订单中心"
+ "path": "aftersale/apply",
+ "navigationBarTitleText": "申请售后"
+ "title": "申请售后"
+ "path": "aftersale/return-delivery",
+ "navigationBarTitleText": "退货物流"
+ "title": "退货物流"
+ "path": "aftersale/list",
+ "navigationBarTitleText": "售后列表"
+ "title": "售后订单",
+ "path": "aftersale/detail",
+ "navigationBarTitleText": "售后详情"
+ "title": "售后详情"
+ "path": "aftersale/log",
+ "navigationBarTitleText": "售后进度"
+ "title": "售后进度"
+ "path": "express/log",
+ "navigationBarTitleText": "物流轨迹"
+ "title": "物流轨迹"
+ "root": "pages/user",
+ "path": "info",
+ "navigationBarTitleText": "我的信息"
+ "title": "用户信息",
+ "group": "用户中心"
+ "path": "goods-collect",
+ "navigationBarTitleText": "我的收藏"
+ "title": "商品收藏",
+ "path": "goods-log",
+ "navigationBarTitleText": "我的足迹"
+ "title": "浏览记录",
+ "path": "address/list",
+ "navigationBarTitleText": "收货地址"
+ "title": "地址管理",
+ "path": "address/edit",
+ "navigationBarTitleText": "编辑地址"
+ "title": "编辑地址"
+ "path": "wallet/money",
+ "navigationBarTitleText": "我的余额"
+ "title": "用户余额",
+ "path": "wallet/score",
+ "navigationBarTitleText": "我的积分"
+ "title": "用户积分",
+ "root": "pages/commission",
+ "navigationBarTitleText": "分销"
+ "title": "分销中心",
+ "group": "分销商城"
+ "path": "wallet",
+ "navigationBarTitleText": "我的佣金"
+ "title": "用户佣金",
+ "group": "分销中心"
+ "path": "goods",
+ "navigationBarTitleText": "推广商品"
+ "title": "推广商品",
+ "path": "order",
+ "navigationBarTitleText": "分销订单"
+ "title": "分销订单",
+ "path": "team",
+ "navigationBarTitleText": "我的团队"
+ "title": "我的团队",
+ }, {
+ "path": "promoter",
+ "navigationBarTitleText": "推广人排行榜"
+ "title": "推广人排行榜",
+ "path": "commission-ranking",
+ "navigationBarTitleText": "佣金排行榜"
+ "title": "佣金排行榜",
+ "path": "withdraw",
+ "navigationBarTitleText": "申请提现"
+ "title": "申请提现",
+ "root": "pages/app",
+ "path": "sign",
+ "navigationBarTitleText": "签到中心"
+ "title": "签到中心",
+ "group": "应用"
+ }]
+ "root": "pages/public",
+ "path": "setting",
+ "navigationBarTitleText": "系统设置"
+ "title": "系统设置",
+ "group": "通用"
+ "path": "richtext",
+ "navigationBarTitleText": "富文本"
+ "title": "富文本",
+ "path": "faq",
+ "navigationBarTitleText": "常见问题"
+ "title": "常见问题",
+ "path": "error",
+ "navigationBarTitleText": "错误页面"
+ "path": "webview",
+ "root": "pages/coupon",
+ "navigationBarTitleText": "领券中心"
+ "title": "领券中心",
+ "group": "优惠券"
+ "navigationBarTitleText": "优惠券"
+ "title": "优惠券详情",
+ "root": "pages/chat",
+ "navigationBarTitleText": "客服"
+ "title": "客服",
+ "group": "客服"
+ "root": "pages/pay",
+ "navigationBarTitleText": "收银台"
+ "path": "result",
+ "navigationBarTitleText": "支付结果"
+ "path": "recharge",
+ "navigationBarTitleText": "充值余额"
+ "title": "充值余额",
+ "group": "支付"
+ "path": "recharge-log",
+ "navigationBarTitleText": "充值记录"
+ "title": "充值记录",
+ "root": "pages/activity",
+ "path": "groupon/detail",
+ "navigationBarTitleText": "拼团详情"
+ "path": "groupon/order",
+ "navigationBarTitleText": "我的拼团",
+ "title": "拼团订单",
+ "group": "营销活动"
+ "navigationBarTitleText": "营销商品"
+ "title": "营销商品",
+ "path": "groupon/list",
+ "navigationBarTitleText": "拼团活动"
+ "title": "拼团活动",
+ "path": "seckill/list",
+ "navigationBarTitleText": "秒杀活动"
+ "title": "秒杀活动",
+ "globalStyle": {
+ "navigationBarTextStyle": "black",
+ "navigationBarTitleText": "芋道商城",
+ "navigationBarBackgroundColor": "#FFFFFF",
+ "backgroundColor": "#FFFFFF",
+ "navigationStyle": "custom"
+ "tabBar": {
+ "list": [
+ // "pagePath": "pages/index/index"
+ "pagePath": "pages/index/category"
+ "pagePath": "pages/index/cart"
+ "pagePath": "pages/index/user"
@@ -0,0 +1,508 @@
+<!-- 拼团订单的详情 -->
+<template>
+ <s-layout title="拼团详情" class="detail-wrap" :navbar="state.data && !state.loading ? 'inner': 'normal'" :onShareAppMessage="shareInfo">
+ <view v-if="state.loading"></view>
+ <view v-if="state.data && !state.loading">
+ <!-- 团长信息 + 活动信息 -->
+ <view
+ class="recharge-box"
+ v-if="state.data.headRecord"
+ :style="[
+ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx',
+ paddingTop: Number(statusBarHeight + 108) + 'rpx',
+ ]"
+ >
+ <s-goods-item
+ class="goods-box"
+ :img="state.data.headRecord.picUrl"
+ :title="state.data.headRecord.spuName"
+ :price="state.data.headRecord.combinationPrice"
+ priceColor="#E1212B"
+ @tap="
+ sheep.$router.go('/pages/goods/groupon', {
+ id: state.data.headRecord.activityId
+ })
+ "
+ :style="[{ top: Number(statusBarHeight + 108) + 'rpx' }]"
+ <template #groupon>
+ <view class="ss-flex">
+ <view class="sales-title">{{ state.data.headRecord.userSize }}人团</view>
+ <view class="num-title ss-m-l-20">已拼{{ state.data.headRecord.userCount }}件</view>
+ </view>
+ </template>
+ </s-goods-item>
+ <view class="countdown-box detail-card ss-p-t-44 ss-flex-col ss-col-center">
+ <!-- 情况一:拼团成功 -->
+ <view v-if="state.data.headRecord.status === 1">
+ <view v-if="state.data.orderId">
+ <view class="countdown-title ss-flex">
+ <text class="cicon-check-round" />
+ 恭喜您~拼团成功
+ <view v-else>
+ <text class="cicon-info" />
+ 抱歉~该团已满员
+ <!-- 情况二:拼团失败 -->
+ <view v-if="state.data.headRecord.status === 2">
+ <text class="cicon-info"></text>
+ {{ state.data.orderId ? '拼团超时,已自动退款' : '该团已解散' }}
+ <!-- 情况三:拼团进行中 -->
+ <view v-if="state.data.headRecord.status === 0">
+ <view v-if="state.data.headRecord.expireTime <= new Date().getTime()">
+ 拼团已结束,请关注下次活动
+ <view class="countdown-title ss-flex" v-else>
+ 还差
+ <view class="num">{{ state.data.headRecord.userSize - state.data.headRecord.userCount }}人</view>
+ 拼团成功
+ <view class="ss-flex countdown-time">
+ <view class="countdown-h ss-flex ss-row-center">{{ endTime.h }}</view>
+ <view class="ss-m-x-4">:</view>
+ <view class="countdown-num ss-flex ss-row-center">
+ {{ endTime.m }}
+ {{ endTime.s }}
+ <!-- 拼团的记录列表,展示每个参团人 -->
+ <view class="ss-m-t-60 ss-flex ss-flex-wrap ss-row-center">
+ <!-- 团长 -->
+ <view class="header-avatar ss-m-r-24 ss-m-b-20">
+ <image :src="sheep.$url.cdn(state.data.headRecord.avatar)" class="avatar-img"></image>
+ <view class="header-tag ss-flex ss-col-center ss-row-center">团长</view>
+ <!-- 团员 -->
+ class="header-avatar ss-m-r-24 ss-m-b-20"
+ v-for="item in state.data.memberRecords"
+ :key="item.id"
+ <image :src="sheep.$url.cdn(item.avatar)" class="avatar-img"></image>
+ class="header-tag ss-flex ss-col-center ss-row-center"
+ v-if="item.is_leader == '1'"
+ 团长
+ <!-- 还有几个坑位 -->
+ <view class="default-avatar ss-m-r-24 ss-m-b-20" v-for="item in state.remainNumber" :key="item">
+ <image
+ :src="sheep.$url.static('/static/img/shop/avatar/unknown.png')"
+ class="avatar-img"
+ ></image>
+ <!-- 情况一:拼团成功;情况二:拼团失败 -->
+ v-if="state.data.headRecord.status === 1 || state.data.headRecord.status === 2"
+ class="ss-m-t-40 ss-flex ss-row-center"
+ <button
+ class="ss-reset-button order-btn"
+ v-if="state.data.orderId"
+ @tap="onDetail(state.data.orderId)"
+ 查看订单
+ </button>
+ <button class="ss-reset-button join-btn" v-else @tap="onCreateGroupon"> 我要开团 </button>
+ <!-- 情况三:拼团进行中,查看订单或参加或邀请好友或参加 -->
+ <view v-if="state.data.headRecord.status === 0" class="ss-m-t-40 ss-flex ss-row-center">
+ class="ss-reset-button join-btn"
+ class="ss-reset-button disabled-btn"
+ v-else
+ disabled
+ 去参团
+ <view v-else class="ss-flex ss-row-center">
+ :disabled="endTime.ms <= 0"
+ @tap="onShare"
+ 邀请好友来拼团
+ @tap="onJoinGroupon()"
+ 立即参团
+ <!-- TODO 芋艿:这里暂时没接入 -->
+ <view v-if="state.data.goods">
+ <s-select-groupon-sku
+ :show="state.showSelectSku"
+ :goodsInfo="state.data.goods"
+ :grouponAction="state.grouponAction"
+ :grouponNum="state.grouponNum"
+ @buy="onBuy"
+ @change="onSkuChange"
+ @close="state.showSelectSku = false"
+ <s-empty v-if="!state.data && !state.loading" icon="/static/goods-empty.png" />
+ </s-layout>
+</template>
+ import { computed, reactive } from 'vue';
+ import sheep from '@/sheep';
+ import { onLoad } from '@dcloudio/uni-app';
+ import { useDurationTime } from '@/sheep/hooks/useGoods';
+ import { showShareModal } from '@/sheep/hooks/useModal';
+ import { isEmpty } from 'lodash';
+ import CombinationApi from "@/sheep/api/promotion/combination";
+ const headerBg = sheep.$url.css('/static/img/shop/user/withdraw_bg.png');
+ const statusBarHeight = sheep.$platform.device.statusBarHeight * 2;
+ const state = reactive({
+ data: {}, // 拼团详情
+ loading: true,
+ grouponAction: 'create',
+ showSelectSku: false,
+ grouponNum: 0,
+ number: 0,
+ activity: {},
+ combinationHeadId: null, // 拼团团长编号
+ // todo 芋艿:分享要再接下
+ const shareInfo = computed(() => {
+ if (isEmpty(state.data)) return {};
+ return sheep.$platform.share.getShareInfo(
+ title: state.data.headRecord.spuName,
+ image: sheep.$url.cdn(state.data.headRecord.picUrl),
+ desc: state.data.goods?.subtitle,
+ params: {
+ page: '5',
+ query: state.data.id,
+ type: 'groupon', // 邀请拼团海报
+ title: state.data.headRecord.spuName, // 商品标题
+ image: sheep.$url.cdn(state.data.headRecord.picUrl), // 商品主图
+ price: state.data.goods?.price, // 商品价格
+ original_price: state.data.goods?.original_price, // 商品原价
+ );
+ // 订单详情
+ function onDetail(orderId) {
+ sheep.$router.go('/pages/order/detail', {
+ id: orderId,
+ // 去开团 TODO 芋艿:这里没接入
+ function onCreateGroupon() {
+ state.grouponAction = 'create';
+ state.grouponId = 0;
+ state.showSelectSku = true;
+ // 规格变更 TODO 芋艿:这里没接入
+ function onSkuChange(e) {
+ state.selectedSkuPrice = e;
+ // 立即参团 TODO 芋艿:这里没接入
+ function onJoinGroupon() {
+ state.grouponAction = 'join';
+ state.grouponId = state.data.activityId;
+ state.combinationHeadId = state.data.id;
+ state.grouponNum = state.data.num;
+ // 立即购买 TODO 芋艿:这里没接入
+ function onBuy(sku) {
+ sheep.$router.go('/pages/order/confirm', {
+ data: JSON.stringify({
+ order_type: 'goods',
+ combinationActivityId: state.data.activity.id,
+ combinationHeadId: state.combinationHeadId,
+ items: [
+ skuId: sku.id,
+ count: sku.count,
+ }),
+ const endTime = computed(() => {
+ return useDurationTime(state.data.headRecord.expireTime);
+ // 获取拼团团队详情
+ async function getGrouponDetail(id) {
+ const { code, data } = await CombinationApi.getCombinationRecordDetail(id);
+ if (code === 0) {
+ state.data = data;
+ const remainNumber = Number(state.data.headRecord.userSize - state.data.headRecord.userCount);
+ state.remainNumber = remainNumber > 0 ? remainNumber : 0;
+ // 获取活动信息
+ const { data: activity } = await CombinationApi.getCombinationActivity(data.headRecord.activityId);
+ state.activity = activity;
+ } else {
+ state.data = null;
+ state.loading = false;
+ function onShare() {
+ showShareModal();
+ onLoad((options) => {
+ getGrouponDetail(options.id);
+<style lang="scss" scoped>
+ .recharge-box {
+ position: relative;
+ margin-bottom: 120rpx;
+ background: v-bind(headerBg) center/750rpx 100%
+ no-repeat,
+ linear-gradient(115deg, #f44739 0%, #ff6600 100%);
+ border-radius: 0 0 5% 5%;
+ height: 100rpx;
+ .goods-box {
+ width: 710rpx;
+ border-radius: 20rpx;
+ position: absolute;
+ left: 20rpx;
+ box-sizing: border-box;
+ .sales-title {
+ height: 32rpx;
+ background: rgba(#ffe0e2, 0.29);
+ border-radius: 16rpx;
+ font-size: 24rpx;
+ font-weight: 400;
+ padding: 6rpx 20rpx;
+ color: #f7979c;
+ .num-title {
+ color: #999999;
+ .countdown-time {
+ font-size: 26rpx;
+ font-weight: 500;
+ color: #383a46;
+ .countdown-h {
+ font-family: OPPOSANS;
+ color: #ffffff;
+ padding: 0 4rpx;
+ margin-left: 16rpx;
+ height: 40rpx;
+ background: linear-gradient(90deg, #ff6000 0%, #fe832a 100%);
+ border-radius: 6rpx;
+ .countdown-num {
+ width: 40rpx;
+ .countdown-box {
+ // height: 364rpx;
+ background: #ffffff;
+ border-radius: 10rpx;
+ .countdown-title {
+ font-size: 28rpx;
+ color: #333333;
+ .cicon-check-round {
+ color: #42b111;
+ margin-right: 24rpx;
+ .cicon-info {
+ color: #d71e08;
+ .num {
+ color: #ff6000;
+ .header-avatar {
+ width: 86rpx;
+ height: 86rpx;
+ background: #ececec;
+ border-radius: 50%;
+ border: 4rpx solid #edc36c;
+ .avatar-img {
+ width: 100%;
+ height: 100%;
+ .header-tag {
+ width: 72rpx;
+ height: 36rpx;
+ line-height: nor;
+ background: linear-gradient(132deg, #f3dfb1, #f3dfb1, #ecbe60);
+ left: 4rpx;
+ top: -36rpx;
+ .default-avatar {
+ .user-avatar {
+ .order-btn {
+ width: 668rpx;
+ height: 70rpx;
+ border: 2rpx solid #dfdfdf;
+ border-radius: 35rpx;
+ line-height: normal;
+ .disabled-btn {
+ background: #dddddd;
+ .join-btn {
+ box-shadow: 0px 8rpx 6rpx 0px rgba(255, 104, 4, 0.22);
+ color: #fff;
+ .detail-cell-wrap {
+ padding: 10rpx 20rpx;
+ border-top: 2rpx solid #dfdfdf;
+ background-color: #fff;
+ // min-height: 60rpx;
+ .label-text {
+ .cell-content {
+ color: $dark-6;
+ .right-forwrad-icon {
+ color: $dark-9;
@@ -0,0 +1,225 @@
+<!-- 拼团活动列表 -->
+ <s-layout navbar="inner" :bgStyle="{ color: '#FE832A' }">
+ <view class="page-bg" :style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]" />
+ <view class="list-content">
+ <!-- 参团会员统计 -->
+ <view class="content-header ss-flex-col ss-col-center ss-row-center">
+ <view class="content-header-title ss-flex ss-row-center">
+ v-for="(item, index) in state.summaryData.avatars"
+ :key="index"
+ class="picture"
+ :style="index === 6 ? 'position: relative' : 'position: static'"
+ <span class="avatar" :style="`background-image: url(${item})`" />
+ <span v-if="index === 6 && state.summaryData.avatars.length > 3" class="mengceng">
+ <i>···</i>
+ </span>
+ <text class="pic_count">{{ state.summaryData.userCount || 0 }}人参与</text>
+ <scroll-view
+ class="scroll-box"
+ :style="{ height: pageHeight + 'rpx' }"
+ scroll-y="true"
+ :scroll-with-animation="false"
+ :enable-back-to-top="true"
+ <view class="goods-box ss-m-b-20" v-for="item in state.pagination.list" :key="item.id">
+ <s-goods-column
+ class=""
+ size="lg"
+ :data="item"
+ :grouponTag="true"
+ @click="sheep.$router.go('/pages/goods/groupon', { id: item.id })"
+ <template v-slot:cart>
+ <button class="ss-reset-button cart-btn">去拼团</button>
+ </s-goods-column>
+ <uni-load-more
+ v-if="state.pagination.total > 0"
+ :status="state.loadStatus"
+ :content-text="{
+ contentdown: '上拉加载更多',
+ }"
+ @tap="loadMore"
+ </scroll-view>
+ import { reactive } from 'vue';
+ import { onLoad, onReachBottom } from '@dcloudio/uni-app';
+ import CombinationApi from '@/sheep/api/promotion/combination';
+ const { safeAreaInsets, safeArea } = sheep.$platform.device;
+ const sysNavBar = sheep.$platform.navbar;
+ const pageHeight =
+ (safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sysNavBar - 350;
+ const headerBg = sheep.$url.css('/static/img/shop/goods/groupon-header.png');
+ pagination: {
+ list: [],
+ total: 0,
+ pageNo: 1,
+ pageSize: 10,
+ loadStatus: '',
+ summaryData: {},
+ // 加载统计数据
+ const getSummary = async () => {
+ const { data } = await CombinationApi.getCombinationRecordSummary();
+ state.summaryData = data;
+ // 加载活动列表
+ async function getList() {
+ state.loadStatus = 'loading';
+ const { data } = await CombinationApi.getCombinationActivityPage({
+ pageNo: state.pagination.pageNo,
+ pageSize: state.pagination.pageSize,
+ data.list.forEach((activity) => {
+ state.pagination.list.push({ ...activity, price: activity.combinationPrice });
+ state.pagination.total = data.total;
+ state.loadStatus = state.pagination.list.length < state.pagination.total ? 'more' : 'noMore';
+ // 加载更多
+ function loadMore() {
+ if (state.loadStatus === 'noMore') {
+ return;
+ state.pagination.pageNo++;
+ getList();
+ // 上拉加载更多
+ onReachBottom(() => loadMore());
+ // 页面初始化
+ onLoad(() => {
+ getSummary();
+ .page-bg {
+ height: 458rpx;
+ margin-top: -88rpx;
+ background: v-bind(headerBg) no-repeat;
+ background-size: 100% 100%;
+ .list-content {
+ z-index: 3;
+ margin: -190rpx 20rpx 0 20rpx;
+ background: #fff;
+ border-radius: 20rpx 20rpx 0 0;
+ .content-header {
+ background: linear-gradient(180deg, #fff4f7, #ffe4d1);
+ .content-header-title {
+ font-size: 30rpx;
+ color: #ff2923;
+ line-height: 30rpx;
+ .more {
+ right: 30rpx;
+ top: 0;
+ .picture {
+ display: inline-table;
+ .avatar {
+ width: 38rpx;
+ height: 38rpx;
+ vertical-align: middle;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ background-repeat: no-repeat;
+ background-size: cover;
+ background-position: 0 0;
+ margin-right: -10rpx;
+ box-shadow: 0 0 0 1px #fe832a;
+ .pic_count {
+ margin-left: 30rpx;
+ font-size: 22rpx;
+ width: auto;
+ height: auto;
+ background: linear-gradient(90deg, #ff6600 0%, #fe832a 100%);
+ border-radius: 19rpx;
+ padding: 4rpx 14rpx;
+ .mengceng {
+ line-height: 36rpx;
+ background: rgba(51, 51, 51, 0.6);
+ text-align: center;
+ opacity: 1;
+ left: -2rpx;
+ top: 2rpx;
+ i {
+ font-style: normal;
+ font-size: 20rpx;
+ .scroll-box {
+ height: 900rpx;
+ .cart-btn {
+ bottom: 10rpx;
+ right: 20rpx;
+ z-index: 11;
+ height: 50rpx;
+ line-height: 50rpx;
+ padding: 0 20rpx;
+ border-radius: 25rpx;
@@ -0,0 +1,239 @@
+<!-- 我的拼团订单列表 -->
+ <s-layout title="我的拼团">
+ <su-sticky bgColor="#fff">
+ <su-tabs
+ :list="tabMaps"
+ :scrollable="false"
+ @change="onTabsChange"
+ :current="state.currentTab"
+ ></su-tabs>
+ </su-sticky>
+ <s-empty v-if="state.pagination.total === 0" icon="/static/goods-empty.png" />
+ <view v-if="state.pagination.total > 0">
+ class="order-list-card-box bg-white ss-r-10 ss-m-t-14 ss-m-20"
+ v-for="record in state.pagination.list"
+ :key="record.id"
+ <view class="order-card-header ss-flex ss-col-center ss-row-between ss-p-x-20">
+ <view class="order-no">拼团编号:{{ record.id }}</view>
+ <view class="ss-font-26" :class="formatOrderColor(record)">
+ {{ tabMaps.find((item) => item.value === record.status).name }}
+ <view class="border-bottom">
+ :img="record.picUrl"
+ :title="record.spuName"
+ :price="record.combinationPrice"
+ <view class="sales-title"> {{ record.userSize }} 人团 </view>
+ <view class="order-card-footer ss-flex ss-row-right ss-p-x-20">
+ class="detail-btn ss-reset-button"
+ @tap="sheep.$router.go('/pages/order/detail', { id: record.orderId })"
+ 订单详情
+ class="tool-btn ss-reset-button"
+ :class="{ 'ui-BG-Main-Gradient': record.status === 0 }"
+ @tap="sheep.$router.go('/pages/activity/groupon/detail', { id: record.id })"
+ {{ record.status === 0 ? '邀请拼团' : '拼团详情' }}
+ import { onLoad, onReachBottom, onPullDownRefresh } from '@dcloudio/uni-app';
+ import _ from 'lodash';
+ import {formatOrderColor} from "@/sheep/hooks/useGoods";
+ import { resetPagination } from '@/sheep/util';
+ // 数据
+ currentTab: 0,
+ pageSize: 5,
+ deleteOrderId: 0,
+ const tabMaps = [
+ name: '全部',
+ name: '进行中',
+ value: 0,
+ name: '拼团成功',
+ value: 1,
+ name: '拼团失败',
+ value: 2,
+ ];
+ // 切换选项卡
+ function onTabsChange(e) {
+ resetPagination(state.pagination);
+ state.currentTab = e.index;
+ getGrouponList();
+ // 获取订单列表
+ async function getGrouponList() {
+ const { code, data } = await CombinationApi.getCombinationRecordPage({
+ status: tabMaps[state.currentTab].value,
+ if (code !== 0) {
+ state.pagination.list = _.concat(state.pagination.list, data.list)
+ if (options.type) {
+ state.currentTab = options.type;
+ onReachBottom(() => {
+ loadMore();
+ //下拉刷新
+ onPullDownRefresh(() => {
+ setTimeout(function () {
+ uni.stopPullDownRefresh();
+ }, 800);
+ .swiper-box {
+ flex: 1;
+ .swiper-item {
+ .order-list-card-box {
+ .order-card-header {
+ height: 80rpx;
+ .order-no {
+ .order-card-footer {
+ .detail-btn {
+ width: 210rpx;
+ height: 66rpx;
+ border-radius: 33rpx;
+ margin-right: 20rpx;
+ .tool-btn {
+ background: #f6f6f6;
+ .invite-btn {
+ background: linear-gradient(90deg, #fe832a, #ff6600);
+ .warning-color {
+ color: #faad14;
+ .danger-color {
+ color: #ff3000;
+ .success-color {
+ color: #52c41a;
@@ -0,0 +1,206 @@
+<!-- 指定满减送的活动列表 -->
+ <s-layout class="activity-wrap" :title="state.activityInfo.title">
+ <!-- 活动信息 -->
+ <view class="ss-flex ss-col-top tip-box">
+ <view class="type-text ss-flex ss-row-center">满减:</view>
+ <view class="ss-flex-1">
+ <view class="tip-content" v-for="item in state.activityInfo.rules" :key="item">
+ {{ formatRewardActivityRule(state.activityInfo, item) }}
+ <image class="activity-left-image" src="/static/activity-left.png" />
+ <image class="activity-right-image" src="/static/activity-right.png" />
+ <!-- 商品信息 -->
+ <view class="ss-flex ss-flex-wrap ss-p-x-20 ss-m-t-20 ss-col-top">
+ <view class="goods-list-box">
+ <view class="left-list" v-for="item in state.leftGoodsList" :key="item.id">
+ class="goods-md-box"
+ size="md"
+ @click="sheep.$router.go('/pages/goods/index', { id: item.id })"
+ @getHeight="mountMasonry($event, 'left')"
+ <button class="ss-reset-button cart-btn"> </button>
+ <view class="right-list" v-for="item in state.rightGoodsList" :key="item.id">
+ @getHeight="mountMasonry($event, 'right')"
+ <button class="ss-reset-button cart-btn" />
+ import RewardActivityApi from '@/sheep/api/promotion/rewardActivity';
+ import { formatRewardActivityRule } from '@/sheep/hooks/useGoods';
+ import SpuApi from '@/sheep/api/product/spu';
+ activityId: 0, // 获得编号
+ activityInfo: {}, // 获得信息
+ total: 1,
+ pageSize: 8,
+ leftGoodsList: [],
+ rightGoodsList: [],
+ // 加载瀑布流
+ let count = 0;
+ let leftHeight = 0;
+ let rightHeight = 0;
+ function mountMasonry(height = 0, where = 'left') {
+ if (!state.pagination.list[count]) return;
+ if (where === 'left') {
+ leftHeight += height;
+ rightHeight += height;
+ if (leftHeight <= rightHeight) {
+ state.leftGoodsList.push(state.pagination.list[count]);
+ state.rightGoodsList.push(state.pagination.list[count]);
+ count++;
+ // 加载商品信息
+ // 处理拓展参数
+ const params = {};
+ if (state.activityInfo.productScope === 2) {
+ params.ids = state.activityInfo.productSpuIds.join(',');
+ } else if (state.activityInfo.productScope === 3) {
+ params.categoryIds = state.activityInfo.productSpuIds.join(',');
+ // 请求数据
+ const { code, data } = await SpuApi.getSpuPage({
+ ...params
+ state.pagination.list = _.concat(state.pagination.list, data.list);
+ mountMasonry();
+ // 加载活动信息
+ async function getActivity(id) {
+ const { code, data } = await RewardActivityApi.getRewardActivity(id);
+ state.activityInfo = data;
+ onLoad(async (options) => {
+ state.activityId = options.activityId;
+ await getActivity(state.activityId);
+ await getList(state.activityId);
+ .goods-list-box {
+ width: 50%;
+ .left-list {
+ margin-right: 10rpx;
+ margin-bottom: 20rpx;
+ .right-list {
+ margin-left: 10rpx;
+ .tip-box {
+ background: #fff0e7;
+ padding: 20rpx;
+ .activity-left-image {
+ bottom: 0;
+ left: 0;
+ width: 58rpx;
+ .activity-right-image {
+ right: 0;
+ .type-text {
+ line-height: 42rpx;
+ .tip-content {
@@ -0,0 +1,385 @@
+<!-- 秒杀活动列表 -->
+ <s-layout navbar="inner" :bgStyle="{ color: 'rgb(245,28,19)' }">
+ <!--顶部背景图-->
+ class="page-bg"
+ :style="[{ marginTop: '-' + Number(statusBarHeight + 88) + 'rpx' }]"
+ ></view>
+ <!-- 时间段轮播图 -->
+ <view class="header" v-if="activeTimeConfig?.sliderPicUrls?.length > 0">
+ <swiper indicator-dots="true" autoplay="true" :circular="true" interval="3000" duration="1500"
+ indicator-color="rgba(255,255,255,0.6)" indicator-active-color="#fff">
+ <block v-for="(picUrl, index) in activeTimeConfig.sliderPicUrls" :key="index">
+ <swiper-item class="borRadius14">
+ <image :src="picUrl" class="slide-image borRadius14" lazy-load />
+ </swiper-item>
+ </block>
+ </swiper>
+ <!-- 时间段列表 -->
+ <view class="flex align-center justify-between ss-p-25">
+ <!-- 左侧图标 -->
+ <view class="time-icon">
+ <!-- TODO 芋艿:图片统一维护 -->
+ <image class="ss-w-100 ss-h-100" src="http://mall.yudao.iocoder.cn/static/images/priceTag.png" />
+ <scroll-view class="time-list" :scroll-into-view="activeTimeElId" scroll-x scroll-with-animation>
+ <view v-for="(config, index) in timeConfigList" :key="index"
+ :class="['item', { active: activeTimeIndex === index}]"
+ :id="`timeItem${index}`"
+ @tap="handleChangeTimeConfig(index)">
+ <!-- 活动起始时间 -->
+ <view class="time">{{ config.startTime }}</view>
+ <!-- 活动状态 -->
+ <view class="status">{{ config.status }}</view>
+ <!-- 内容区 -->
+ <!-- 活动倒计时 -->
+ <view class="content-header-box ss-flex ss-row-center">
+ <view class="countdown-box ss-flex" v-if="activeTimeConfig?.status === TimeStatusEnum.STARTED">
+ <view class="countdown-title ss-m-r-12">距结束</view>
+ <view class="ss-flex countdown-h">{{ countDown.h }}</view>
+ <view class="countdown-num ss-flex ss-row-center">{{ countDown.m }}</view>
+ <view class="countdown-num ss-flex ss-row-center">{{ countDown.s }}</view>
+ <view v-else> {{ activeTimeConfig?.status }} </view>
+ <!-- 活动列表 -->
+ <view class="goods-box ss-m-b-20" v-for="activity in activityList" :key="activity.id">
+ :data="{ ...activity, price: activity.seckillPrice }"
+ :goodsFields="goodsFields"
+ :seckillTag="true"
+ @click="sheep.$router.go('/pages/goods/seckill', { id: activity.id })"
+ <!-- 抢购进度 -->
+ <template #activity>
+ <view class="limit">限量 <text class="ss-m-l-5">{{ activity.stock}} {{activity.unitName}}</text></view>
+ <su-progress :percentage="activity.percent" strokeWidth="10" textInside isAnimate />
+ <!-- 抢购按钮 -->
+ <template #cart>
+ <button :class="['ss-reset-button cart-btn', { disabled: activeTimeConfig.status === TimeStatusEnum.END }]">
+ <span v-if="activeTimeConfig?.status === TimeStatusEnum.WAIT_START">未开始</span>
+ <span v-else-if="activeTimeConfig?.status === TimeStatusEnum.STARTED">马上抢</span>
+ <span v-else>已结束</span>
+ v-if="activityTotal > 0"
+ :status="loadStatus"
+ import {reactive, computed, ref, nextTick} from 'vue';
+ import SeckillApi from "@/sheep/api/promotion/seckill";
+ import dayjs from "dayjs";
+ import {TimeStatusEnum} from "@/sheep/util/const";
+ // 计算页面高度
+ const pageHeight = (safeArea.height + safeAreaInsets.bottom) * 2 + statusBarHeight - sheep.$platform.navbar - 350;
+ const headerBg = sheep.$url.css('/static/img/shop/goods/seckill-header.png');
+ // 商品控件显示的字段(不显示库存、销量。改为显示自定义的进度条)
+ const goodsFields = {
+ name: { show: true },
+ introduction: { show: true },
+ price: { show: true },
+ marketPrice: { show: true },
+ //#region 时间段
+ // 时间段列表
+ const timeConfigList = ref([])
+ // 查询时间段
+ const getSeckillConfigList = async () => {
+ const { data } = await SeckillApi.getSeckillConfigList()
+ const now = dayjs();
+ const today = now.format('YYYY-MM-DD')
+ // 判断时间段的状态
+ data.forEach((config, index) => {
+ const startTime = dayjs(`${today} ${config.startTime}`)
+ const endTime = dayjs(`${today} ${config.endTime}`)
+ if (now.isBefore(startTime)) {
+ config.status = TimeStatusEnum.WAIT_START;
+ } else if (now.isAfter(endTime)) {
+ config.status = TimeStatusEnum.END;
+ config.status = TimeStatusEnum.STARTED;
+ activeTimeIndex.value = index;
+ timeConfigList.value = data
+ // 默认选中进行中的活动
+ handleChangeTimeConfig(activeTimeIndex.value);
+ // 滚动到进行中的时间段
+ scrollToTimeConfig(activeTimeIndex.value)
+ // 滚动到指定时间段
+ const activeTimeElId = ref('') // 当前选中的时间段的元素ID
+ const scrollToTimeConfig = (index) => {
+ nextTick(() => activeTimeElId.value = `timeItem${index}`)
+ // 切换时间段
+ const activeTimeIndex = ref(0) // 当前选中的时间段的索引
+ const activeTimeConfig = computed(() => timeConfigList.value[activeTimeIndex.value]) // 当前选中的时间段
+ const handleChangeTimeConfig = (index) => {
+ activeTimeIndex.value = index
+ // 查询活动列表
+ activityPageParams.pageNo = 1
+ activityList.value = []
+ getActivityList();
+ // 倒计时
+ const countDown = computed(() => {
+ const endTime = activeTimeConfig.value?.endTime
+ if (endTime) {
+ return useDurationTime(`${dayjs().format('YYYY-MM-DD')} ${endTime}`);
+ //#endregion
+ //#region 分页查询活动列表
+ const activityPageParams = reactive({
+ id: 0, // 时间段 ID
+ pageNo: 1, // 页码
+ pageSize: 5, // 每页数量
+ const activityTotal = ref(0) // 活动总数
+ const activityList = ref([]) // 活动列表
+ const loadStatus = ref('') // 页面加载状态
+ async function getActivityList() {
+ loadStatus.value = 'loading';
+ const { data } = await SeckillApi.getSeckillActivityPage(activityPageParams)
+ data.list.forEach(activity => {
+ // 计算抢购进度
+ activity.percent = parseInt(100 * (activity.totalStock - activity.stock) / activity.totalStock);
+ activityList.value = activityList.value.concat(...data.list);
+ activityTotal.value = data.total;
+ loadStatus.value = activityList.value.length < activityTotal.value ? 'more' : 'noMore';
+ if (loadStatus.value !== 'noMore') {
+ activityPageParams.pageNo += 1
+ onLoad(async () => {
+ await getSeckillConfigList()
+ // 顶部背景图
+ // 时间段轮播图
+ .header {
+ height: 330rpx;
+ margin: -276rpx auto 0 auto;
+ border-radius: 14rpx;
+ overflow: hidden;
+ swiper{
+ height: 330rpx !important;
+ image {
+ img{
+ // 时间段列表:左侧图标
+ .time-icon {
+ width: 75rpx;
+ .time-list {
+ width: 596rpx;
+ white-space: nowrap;
+ // 时间段
+ .item {
+ display: inline-block;
+ color: #666;
+ margin-right: 30rpx;
+ width: 130rpx;
+ // 开始时间
+ .time {
+ font-size: 36rpx;
+ font-weight: 600;
+ color: #333;
+ // 选中的时间段
+ &.active {
+ color: var(--ui-BG-Main);
+ // 状态
+ .status {
+ height: 30rpx;
+ border-radius: 15rpx;
+ width: 128rpx;
+ background: linear-gradient(90deg, var(--ui-BG-Main) 0%, var(--ui-BG-Main-gradient) 100%);
+ // 内容区
+ margin: 0 20rpx 0 20rpx;
+ height: 150rpx;
+ background: linear-gradient(180deg, #fff4f7, #ffe6ec);
+ .content-header-box {
+ width: 678rpx;
+ height: 64rpx;
+ background: rgba($color: #fff, $alpha: 0.66);
+ border-radius: 32px;
+ // 场次倒计时内容
+ line-height: 28rpx;
+ // 场次倒计时
+ color: rgba(#ed3c30, 0.23);
+ // 场次倒计时:小时部分
+ background: rgba(#ed3c30, 0.23);
+ // 场次倒计时:分钟、秒
+ // 活动列表
+ // 活动
+ // 抢购按钮
+ height: 44rpx;
+ &.disabled {
+ background: $gray-b;
+ // 秒杀限量商品数
+ .limit {
+ margin-bottom: 5rpx;
@@ -0,0 +1,63 @@
+ <view class="goods ss-flex">
+ <image class="image" :src="sheep.$url.cdn(goodsData.image)" mode="aspectFill"> </image>
+ <view class="title ss-line-2">
+ {{ goodsData.title }}
+ <view v-if="goodsData.subtitle" class="subtitle ss-line-1">
+ {{ goodsData.subtitle }}
+ <view class="price ss-m-t-8">
+ ¥{{ isArray(goodsData.price) ? goodsData.price[0] : goodsData.price }}
+ import { isArray } from 'lodash';
+ const props = defineProps({
+ goodsData: {
+ type: Object,
+ default: {},
+ .goods {
+ border-radius: 12rpx;
+ .image {
+ width: 116rpx;
+ height: 116rpx;
+ flex-shrink: 0;
+ .title {
+ line-height: 32rpx;
+ .subtitle {
+ color: #999;
+ .price {
@@ -0,0 +1,122 @@
+ <view class="order">
+ <view class="top ss-flex ss-row-between">
+ <span>{{ orderData.order_sn }}</span>
+ <span>{{ orderData.create_time.split(' ')[1] }}</span>
+ <template v-if="from != 'msg'">
+ <view class="bottom ss-flex" v-for="item in orderData.items" :key="item">
+ <image class="image" :src="sheep.$url.cdn(item.goods_image)" mode="aspectFill"> </image>
+ {{ item.goods_title }}
+ <view v-if="item.goods_num" class="num ss-m-b-10"> 数量:{{ item.goods_num }} </view>
+ <view class="ss-flex ss-row-between ss-m-t-8">
+ <span class="price">¥{{ item.goods_price }}</span>
+ <span class="status">{{ orderData.status_text }}</span>
+ <template v-else>
+ <view class="bottom ss-flex" v-for="item in [orderData.items[0]]" :key="item">
+ <view class="title title-1 ss-line-1">
+ <view class="order-total ss-flex ss-row-between ss-m-t-8">
+ <span>共{{ orderData.items.length }}件商品</span>
+ <span>合计 ¥{{ orderData.pay_fee }}</span>
+ <view class="ss-flex ss-row-right ss-m-t-8">
+ from: String,
+ orderData: {
+ .order {
+ .top {
+ line-height: 40rpx;
+ border-bottom: 1px solid rgba(223, 223, 223, 0.5);
+ .bottom {
+ &:last-of-type {
+ margin-bottom: 0;
+ &.title-1 {
+ width: 300rpx;
+ .order-total {
@@ -0,0 +1,152 @@
+ <su-popup :show="show" showClose round="10" backgroundColor="#eee" @close="emits('close')">
+ <view class="select-popup">
+ <view class="title">
+ <span>{{ mode == 'goods' ? '我的浏览' : '我的订单' }}</span>
+ :scroll-with-animation="true"
+ :show-scrollbar="false"
+ @scrolltolower="loadmore"
+ class="item"
+ v-for="item in state.pagination.data"
+ :key="item"
+ @tap="emits('select', { type: mode, data: item })"
+ <template v-if="mode == 'goods'">
+ <GoodsItem :goodsData="item.goods" />
+ <template v-if="mode == 'order'">
+ <OrderItem :orderData="item" />
+ <uni-load-more :status="state.loadStatus" :content-text="{ contentdown: '上拉加载更多' }" />
+ </su-popup>
+ import { reactive, watch } from 'vue';
+ import GoodsItem from './goods.vue';
+ import OrderItem from './order.vue';
+ import OrderApi from '@/sheep/api/trade/order';
+ import SpuHistoryApi from '@/sheep/api/product/history';
+ const emits = defineEmits(['select', 'close']);
+ mode: {
+ type: String,
+ default: 'goods',
+ show: {
+ type: Boolean,
+ default: false,
+ watch(
+ () => props.mode,
+ () => {
+ state.pagination.data = [];
+ if (props.mode) {
+ getList(state.pagination.page);
+ data: [],
+ current_page: 1,
+ last_page: 1,
+ async function getList(page, list_rows = 5) {
+ const res =
+ props.mode == 'goods'
+ ? await SpuHistoryApi.getBrowseHistoryPage({
+ page,
+ list_rows,
+ : await OrderApi.getOrderPage({
+ let orderList = _.concat(state.pagination.data, res.data.data);
+ state.pagination = {
+ ...res.data,
+ data: orderList,
+ if (state.pagination.current_page < state.pagination.last_page) {
+ state.loadStatus = 'more';
+ state.loadStatus = 'noMore';
+ function loadmore() {
+ if (state.loadStatus !== 'noMore') {
+ getList(state.pagination.current_page + 1);
+ .select-popup {
+ max-height: 600rpx;
+ line-height: 100rpx;
+ padding: 0 26rpx;
+ span {
+ font-size: 32rpx;
+ &::after {
+ content: '';
+ display: block;
+ height: 2px;
+ z-index: 1;
+ bottom: -15px;
+ background: var(--ui-BG-Main);
+ pointer-events: none;
+ height: 500rpx;
+ margin: 26rpx 26rpx 0;
+ :deep() {
+ width: 140rpx;
+ height: 140rpx;
@@ -0,0 +1,58 @@
+export const emojiList = [
+ { name: '[笑掉牙]', file: 'xiaodiaoya.png' },
+ { name: '[可爱]', file: 'keai.png' },
+ { name: '[冷酷]', file: 'lengku.png' },
+ { name: '[闭嘴]', file: 'bizui.png' },
+ { name: '[生气]', file: 'shengqi.png' },
+ { name: '[惊恐]', file: 'jingkong.png' },
+ { name: '[瞌睡]', file: 'keshui.png' },
+ { name: '[大笑]', file: 'daxiao.png' },
+ { name: '[爱心]', file: 'aixin.png' },
+ { name: '[坏笑]', file: 'huaixiao.png' },
+ { name: '[飞吻]', file: 'feiwen.png' },
+ { name: '[疑问]', file: 'yiwen.png' },
+ { name: '[开心]', file: 'kaixin.png' },
+ { name: '[发呆]', file: 'fadai.png' },
+ { name: '[流泪]', file: 'liulei.png' },
+ { name: '[汗颜]', file: 'hanyan.png' },
+ { name: '[惊悚]', file: 'jingshu.png' },
+ { name: '[困~]', file: 'kun.png' },
+ { name: '[心碎]', file: 'xinsui.png' },
+ { name: '[天使]', file: 'tianshi.png' },
+ { name: '[晕]', file: 'yun.png' },
+ { name: '[啊]', file: 'a.png' },
+ { name: '[愤怒]', file: 'fennu.png' },
+ { name: '[睡着]', file: 'shuizhuo.png' },
+ { name: '[面无表情]', file: 'mianwubiaoqing.png' },
+ { name: '[难过]', file: 'nanguo.png' },
+ { name: '[犯困]', file: 'fankun.png' },
+ { name: '[好吃]', file: 'haochi.png' },
+ { name: '[呕吐]', file: 'outu.png' },
+ { name: '[龇牙]', file: 'ziya.png' },
+ { name: '[懵比]', file: 'mengbi.png' },
+ { name: '[白眼]', file: 'baiyan.png' },
+ { name: '[饿死]', file: 'esi.png' },
+ { name: '[凶]', file: 'xiong.png' },
+ { name: '[感冒]', file: 'ganmao.png' },
+ { name: '[流汗]', file: 'liuhan.png' },
+ { name: '[笑哭]', file: 'xiaoku.png' },
+ { name: '[流口水]', file: 'liukoushui.png' },
+ { name: '[尴尬]', file: 'ganga.png' },
+ { name: '[惊讶]', file: 'jingya.png' },
+ { name: '[大惊]', file: 'dajing.png' },
+ { name: '[不好意思]', file: 'buhaoyisi.png' },
+ { name: '[大闹]', file: 'danao.png' },
+ { name: '[不可思议]', file: 'bukesiyi.png' },
+ { name: '[爱你]', file: 'aini.png' },
+ { name: '[红心]', file: 'hongxin.png' },
+ { name: '[点赞]', file: 'dianzan.png' },
+ { name: '[恶魔]', file: 'emo.png' },
+];
+export let emojiPage = {};
+emojiList.forEach((item, index) => {
+ if (!emojiPage[Math.floor(index / 30) + 1]) {
+ emojiPage[Math.floor(index / 30) + 1] = [];
+ emojiPage[Math.floor(index / 30) + 1].push(item);
+});
@@ -0,0 +1,870 @@
+ <s-layout class="chat-wrap" title="客服" navbar="inner">
+ <div class="status">
+ {{ socketState.isConnect ? customerServiceInfo.title : '网络已断开,请检查网络后刷新重试' }}
+ </div>
+ <div class="page-bg" :style="{ height: sys_navBar + 'px' }"></div>
+ <view class="chat-box" :style="{ height: pageHeight + 'px' }">
+ :style="{ height: pageHeight + 'px' }"
+ :scroll-into-view="chat.scrollInto"
+ class="loadmore-btn ss-reset-button"
+ v-if="
+ chatList.length &&
+ chatHistoryPagination.lastPage > 1 &&
+ loadingMap[chatHistoryPagination.loadStatus].title
+ @click="onLoadMore"
+ {{ loadingMap[chatHistoryPagination.loadStatus].title }}
+ <i
+ class="loadmore-icon sa-m-l-6"
+ :class="loadingMap[chatHistoryPagination.loadStatus].icon"
+ ></i>
+ <view class="message-item ss-flex-col" v-for="(item, index) in chatList" :key="index">
+ <view class="ss-flex ss-row-center ss-col-center">
+ <!-- 日期 -->
+ <view v-if="item.from !== 'system' && showTime(item, index)" class="date-message">
+ {{ formatTime(item.date) }}
+ <!-- 系统消息 -->
+ <view v-if="item.from === 'system'" class="system-message">
+ {{ item.content.text }}
+ <!-- 常见问题 -->
+ <view v-if="item.mode === 'template' && item.content.list.length" class="template-wrap">
+ <view class="title">猜你想问</view>
+ v-for="(item, index) in item.content.list"
+ @click="onTemplateList(item)"
+ * {{ item.title }}
+ (item.from === 'customer_service' && item.mode !== 'template') ||
+ item.from === 'customer'
+ class="ss-flex ss-col-top"
+ :class="[
+ item.from === 'customer_service'
+ ? `ss-row-left`
+ : item.from === 'customer'
+ ? `ss-row-right`
+ : '',
+ <!-- 客服头像 -->
+ v-show="item.from === 'customer_service'"
+ class="chat-avatar ss-m-r-24"
+ :src="
+ sheep.$url.cdn(item?.sender?.avatar) ||
+ sheep.$url.static('/static/img/shop/chat/default.png')
+ mode="aspectFill"
+ <!-- 发送状态 -->
+ <span
+ item.from === 'customer' &&
+ index == chatData.chatList.length - 1 &&
+ chatData.isSendSucces !== 0
+ class="send-status"
+ v-if="chatData.isSendSucces == -1"
+ class="loading"
+ :src="sheep.$url.static('/static/img/shop/chat/loading.png')"
+ <!-- <image
+ v-if="chatData.isSendSucces == 1"
+ class="warning"
+ :src="sheep.$url.static('/static/img/shop/chat/warning.png')"
+ @click="onAgainSendMessage(item)"
+ ></image> -->
+ <!-- 内容 -->
+ <template v-if="item.mode === 'text'">
+ <view class="message-box" :class="[item.from]">
+ <div
+ class="message-text ss-flex ss-flex-wrap"
+ @click="onRichtext"
+ v-html="replaceEmoji(item.content.text)"
+ ></div>
+ <template v-if="item.mode === 'image'">
+ <view class="message-box" :class="[item.from]" :style="{ width: '200rpx' }">
+ <su-image
+ class="message-img"
+ isPreview
+ :previewList="[sheep.$url.cdn(item.content.url)]"
+ :current="0"
+ :src="sheep.$url.cdn(item.content.url)"
+ :height="200"
+ :width="200"
+ ></su-image>
+ <template v-if="item.mode === 'goods'">
+ <GoodsItem
+ :goodsData="item.content.item"
+ sheep.$router.go('/pages/goods/index', {
+ id: item.content.item.id,
+ <template v-if="item.mode === 'order'">
+ <OrderItem
+ from="msg"
+ :orderData="item.content.item"
+ <!-- user头像 -->
+ v-show="item.from === 'customer'"
+ class="chat-avatar ss-m-l-24"
+ :src="sheep.$url.cdn(customerUserInfo.avatar)"
+ </image>
+ <view id="scrollBottom"></view>
+ <su-fixed bottom>
+ <view class="send-wrap ss-flex">
+ <view class="left ss-flex ss-flex-1">
+ <uni-easyinput
+ class="ss-flex-1 ss-p-l-22"
+ :inputBorder="false"
+ :clearable="false"
+ v-model="chat.msg"
+ placeholder="请输入你要咨询的问题"
+ ></uni-easyinput>
+ <text class="sicon-basic bq" @tap.stop="onTools('emoji')"></text>
+ <text
+ v-if="!chat.msg"
+ class="sicon-edit"
+ :class="{ 'is-active': chat.toolsMode == 'tools' }"
+ @tap.stop="onTools('tools')"
+ ></text>
+ <button v-if="chat.msg" class="ss-reset-button send-btn" @tap="onSendMessage">
+ 发送
+ </su-fixed>
+ <su-popup
+ :show="chat.showTools"
+ @close="
+ chat.showTools = false;
+ chat.toolsMode = '';
+ <view class="ss-modal-box ss-flex-col">
+ <text></text>
+ <view class="content ss-flex ss-flex-1">
+ <template v-if="chat.toolsMode == 'emoji'">
+ <swiper
+ class="emoji-swiper"
+ :indicator-dots="true"
+ circular
+ indicator-active-color="#7063D2"
+ indicator-color="rgba(235, 231, 255, 1)"
+ :autoplay="false"
+ :interval="3000"
+ :duration="1000"
+ <swiper-item v-for="emoji in emojiPage" :key="emoji">
+ <view class="ss-flex ss-flex-wrap">
+ <template v-for="item in emoji" :key="item">
+ class="emoji-img"
+ :src="sheep.$url.cdn(`/static/img/chat/emoji/${item.file}`)"
+ @tap="onEmoji(item)"
+ <view class="image">
+ <s-uploader
+ file-mediatype="image"
+ :imageStyles="{ width: 50, height: 50, border: false }"
+ @select="onSelect({ type: 'image', data: $event })"
+ class="icon"
+ :src="sheep.$url.static('/static/img/shop/chat/image.png')"
+ </s-uploader>
+ <view>图片</view>
+ <view class="goods" @tap="onShowSelect('goods')">
+ :src="sheep.$url.static('/static/img/shop/chat/goods.png')"
+ <view>商品</view>
+ <view class="order" @tap="onShowSelect('order')">
+ :src="sheep.$url.static('/static/img/shop/chat/order.png')"
+ <view>订单</view>
+ <SelectPopup
+ :mode="chat.selectMode"
+ :show="chat.showSelect"
+ @select="onSelect"
+ @close="chat.showSelect = false"
+ import { computed, reactive, toRefs } from 'vue';
+ import { emojiList, emojiPage } from './emoji.js';
+ import SelectPopup from './components/select-popup.vue';
+ import GoodsItem from './components/goods.vue';
+ import OrderItem from './components/order.vue';
+ import { useChatWebSocket } from './socket';
+ const {
+ socketInit,
+ state: chatData,
+ socketSendMsg,
+ formatChatInput,
+ socketHistoryList,
+ onDrop,
+ onPaste,
+ getFocus,
+ // upload,
+ getUserToken,
+ // socketTest,
+ showTime,
+ formatTime,
+ } = useChatWebSocket();
+ const chatList = toRefs(chatData).chatList;
+ const customerServiceInfo = toRefs(chatData).customerServerInfo;
+ const chatHistoryPagination = toRefs(chatData).chatHistoryPagination;
+ const customerUserInfo = toRefs(chatData).customerUserInfo;
+ const socketState = toRefs(chatData).socketState;
+ const sys_navBar = sheep.$platform.navbar;
+ const chatConfig = computed(() => sheep.$store('app').chat);
+ const { screenHeight, safeAreaInsets, safeArea, screenWidth } = sheep.$platform.device;
+ const pageHeight = safeArea.height - 44 - 35 - 50;
+ const chatStatus = {
+ online: {
+ text: '在线',
+ colorVariate: '#46c55f',
+ offline: {
+ text: '离线',
+ colorVariate: '#b5b5b5',
+ busy: {
+ text: '忙碌',
+ colorVariate: '#ff0e1b',
+ const loadingMap = {
+ loadmore: {
+ title: '查看更多',
+ icon: 'el-icon-d-arrow-left',
+ nomore: {
+ title: '没有更多了',
+ icon: '',
+ loading: {
+ title: '加载中... ',
+ icon: 'el-icon-loading',
+ const onLoadMore = () => {
+ chatHistoryPagination.value.page < chatHistoryPagination.value.lastPage && socketHistoryList();
+ const chat = reactive({
+ msg: '',
+ scrollInto: '',
+ showTools: false,
+ toolsMode: '',
+ showSelect: false,
+ selectMode: '',
+ chatStyle: {
+ mode: 'inner',
+ color: '#F8270F',
+ type: 'color',
+ alwaysShow: 1,
+ src: '',
+ list: {},
+ // 点击工具栏开关
+ function onTools(mode) {
+ if (!socketState.value.isConnect) {
+ sheep.$helper.toast(socketState.value.tip || '您已掉线!请返回重试');
+ if (!chat.toolsMode || chat.toolsMode === mode) {
+ chat.showTools = !chat.showTools;
+ chat.toolsMode = mode;
+ if (!chat.showTools) {
+ function onShowSelect(mode) {
+ chat.showSelect = true;
+ chat.selectMode = mode;
+ async function onSelect({ type, data }) {
+ let msg = '';
+ switch (type) {
+ case 'image':
+ const { path, fullurl } = await sheep.$api.app.upload(data.tempFiles[0].path, 'default');
+ msg = {
+ from: 'customer',
+ mode: 'image',
+ date: new Date().getTime(),
+ content: {
+ url: fullurl,
+ path: path,
+ break;
+ case 'goods':
+ mode: 'goods',
+ item: {
+ id: data.goods.id,
+ title: data.goods.title,
+ image: data.goods.image,
+ price: data.goods.price,
+ stock: data.goods.stock,
+ case 'order':
+ mode: 'order',
+ id: data.id,
+ order_sn: data.order_sn,
+ create_time: data.create_time,
+ pay_fee: data.pay_fee,
+ items: data.items.filter((item) => ({
+ goods_id: item.goods_id,
+ goods_title: item.goods_title,
+ goods_image: item.goods_image,
+ goods_price: item.goods_price,
+ })),
+ status_text: data.status_text,
+ if (msg) {
+ socketSendMsg(msg, () => {
+ scrollBottom();
+ // scrollBottom();
+ chat.showSelect = false;
+ chat.selectMode = '';
+ function onAgainSendMessage(item) {
+ if (!item) return;
+ const data = {
+ mode: 'text',
+ content: item.content,
+ socketSendMsg(data, () => {
+ function onSendMessage() {
+ if (!chat.msg) return;
+ text: chat.msg,
+ setTimeout(() => {
+ chat.msg = '';
+ }, 100);
+ // 点击猜你想问
+ function onTemplateList(e) {
+ text: e.title,
+ customData: {
+ question_id: e.id,
+ function onEmoji(item) {
+ chat.msg += item.name;
+ function selEmojiFile(name) {
+ for (let index in emojiList) {
+ if (emojiList[index].name === name) {
+ return emojiList[index].file;
+ return false;
+ function replaceEmoji(data) {
+ let newData = data;
+ if (typeof newData !== 'object') {
+ let reg = /\[(.+?)\]/g; // [] 中括号
+ let zhEmojiName = newData.match(reg);
+ if (zhEmojiName) {
+ zhEmojiName.forEach((item) => {
+ let emojiFile = selEmojiFile(item);
+ newData = newData.replace(
+ item,
+ `<img class="chat-img" style="width: 24px;height: 24px;margin: 0 3px;" src="${sheep.$url.cdn(
+ '/static/img/chat/emoji/' + emojiFile,
+ )}"/>`,
+ return newData;
+ function scrollBottom() {
+ let timeout = null;
+ chat.scrollInto = '';
+ clearTimeout(timeout);
+ timeout = setTimeout(() => {
+ chat.scrollInto = 'scrollBottom';
+ const { error } = await getUserToken();
+ if (error === 0) {
+ socketInit(chatConfig.value, () => {
+ socketState.value.isConnect = false;
+ background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
+ background-size: 750rpx 100%;
+ .chat-wrap {
+ // :deep() {
+ // .ui-navbar-box {
+ // background: linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
+ padding: 0 30rpx;
+ background: var(--ui-BG-Main-opacity-1);
+ display: flex;
+ align-items: center;
+ .chat-box {
+ padding: 0 20rpx 0;
+ .loadmore-btn {
+ width: 98%;
+ height: 40px;
+ font-size: 12px;
+ color: #8c8c8c;
+ .loadmore-icon {
+ transform: rotate(90deg);
+ .message-item {
+ margin-bottom: 33rpx;
+ .date-message,
+ .system-message {
+ width: fit-content;
+ padding: 8rpx 16rpx;
+ margin-bottom: 16rpx;
+ background-color: var(--ui-BG-3);
+ .chat-avatar {
+ width: 70rpx;
+ .send-status {
+ margin-right: 8rpx;
+ .loading {
+ width: 32rpx;
+ -webkit-animation: rotating 2s linear infinite;
+ animation: rotating 2s linear infinite;
+ @-webkit-keyframes rotating {
+ 0% {
+ transform: rotateZ(0);
+ 100% {
+ transform: rotateZ(360deg);
+ @keyframes rotating {
+ .warning {
+ .message-box {
+ max-width: 50%;
+ font-size: 16px;
+ line-height: 20px;
+ // max-width: 500rpx;
+ white-space: normal;
+ word-break: break-all;
+ word-wrap: break-word;
+ &.customer_service {
+ .imgred {
+ .imgred,
+ img {
+ .goods,
+ max-width: 500rpx;
+ .message-img {
+ width: 100px;
+ height: 100px;
+ .template-wrap {
+ // width: 100%;
+ padding: 20rpx 24rpx;
+ margin-bottom: 29rpx;
+ .error-img {
+ width: 400rpx;
+ height: 400rpx;
+ #scrollBottom {
+ height: 120rpx;
+ .send-wrap {
+ padding: 18rpx 20rpx;
+ .left {
+ border-radius: 32rpx;
+ background: var(--ui-BG-1);
+ .bq {
+ font-size: 50rpx;
+ .sicon-edit {
+ transform: rotate(0deg);
+ transition: all linear 0.2s;
+ &.is-active {
+ transform: rotate(45deg);
+ .send-btn {
+ width: 100rpx;
+ height: 60rpx;
+ line-height: 60rpx;
+ border-radius: 30rpx;
+ margin-left: 11rpx;
+ .content {
+ align-content: space-around;
+ border-top: 1px solid #dfdfdf;
+ padding: 20rpx 0 0;
+ .emoji-swiper {
+ height: 280rpx;
+ .emoji-img {
+ width: 50rpx;
+ margin: 10rpx;
+ .image,
+ width: 33.3%;
+ flex-direction: column;
+ justify-content: center;
+ .icon {
+ margin-bottom: 21rpx;
+ .uni-file-picker__container {
+ .file-picker__box {
+ display: none;
+<style>
+ .chat-img {
+ width: 24px;
+ height: 24px;
+ margin: 0 3px;
+ .full-img {
+ object-fit: cover;
+ border-radius: 6px;
@@ -0,0 +1,821 @@
+import { reactive, ref, unref } from 'vue';
+import sheep from '@/sheep';
+// import chat from '@/sheep/api/chat';
+import dayjs from 'dayjs';
+import io from '@hyoga/uni-socket.io';
+export function useChatWebSocket(socketConfig) {
+ let SocketIo = null;
+ // chat状态数据
+ chatDotNum: 0, //总状态红点
+ chatList: [], //会话信息
+ customerUserInfo: {}, //用户信息
+ customerServerInfo: {
+ //客服信息
+ title: '连接中...',
+ state: 'connecting',
+ avatar: null,
+ nickname: '',
+ socketState: {
+ isConnect: true, //是否连接成功
+ isConnecting: false, //重连中,不允许新的socket开启。
+ tip: '',
+ chatHistoryPagination: {
+ page: 0, //当前页
+ list_rows: 10, //每页条数
+ last_id: 0, //最后条ID
+ lastPage: 0, //总共多少页
+ loadStatus: 'loadmore', //loadmore-加载前的状态,loading-加载中的状态,nomore-没有更多的状态
+ templateChatList: [], //猜你想问
+ chatConfig: {}, // 配置信息
+ isSendSucces: -1, // 是否发送成功 -1=发送中|0=发送成功|1发送失败
+ /**
+ * 连接初始化
+ * @param {Object} config - 配置信息
+ * @param {Function} callBack -回调函数,有新消息接入,保持底部
+ */
+ const socketInit = (config, callBack) => {
+ state.chatConfig = config;
+ if (SocketIo && SocketIo.connected) return; // 如果socket已经连接,返回false
+ if (state.socketState.isConnecting) return; // 重连中,返回false
+ // 启动初始化
+ SocketIo = io(config.chat_domain, {
+ reconnection: true, // 默认 true 是否断线重连
+ reconnectionAttempts: 5, // 默认无限次 断线尝试次数
+ reconnectionDelay: 1000, // 默认 1000,进行下一次重连的间隔。
+ reconnectionDelayMax: 5000, // 默认 5000, 重新连接等待的最长时间 默认 5000
+ randomizationFactor: 0.5, // 默认 0.5 [0-1],随机重连延迟时间
+ timeout: 20000, // 默认 20s
+ transports: ['websocket', 'polling'], // websocket | polling,
+ ...config,
+ // 监听连接
+ SocketIo.on('connect', async (res) => {
+ socketReset(callBack);
+ // socket连接
+ // 用户登录
+ // 顾客登录
+ console.log('socket:connect');
+ // 监听消息
+ SocketIo.on('message', (res) => {
+ if (res.error === 0) {
+ const { message, sender } = res.data;
+ state.chatList.push(formatMessage(res.data.message));
+ // 告诉父级页面
+ // window.parent.postMessage({
+ // chatDotNum: ++state.chatDotNum
+ // })
+ callBack && callBack();
+ // 监听客服接入成功
+ SocketIo.on('customer_service_access', (res) => {
+ editCustomerServerInfo({
+ title: res.data.customer_service.name,
+ state: 'online',
+ avatar: res.data.customer_service.avatar,
+ // callBack && callBack()
+ // 监听排队等待
+ SocketIo.on('waiting_queue', (res) => {
+ title: res.data.title,
+ state: 'waiting',
+ avatar: '',
+ // 监听没有客服在线
+ SocketIo.on('no_customer_service', (res) => {
+ title: '暂无客服在线...',
+ // 监听客服上线
+ SocketIo.on('customer_service_online', (res) => {
+ // 监听客服下线
+ SocketIo.on('customer_service_offline', (res) => {
+ state: 'offline',
+ // 监听客服忙碌
+ SocketIo.on('customer_service_busy', (res) => {
+ state: 'busy',
+ // 监听客服断开链接
+ SocketIo.on('customer_service_break', (res) => {
+ title: '客服服务结束',
+ state.socketState.isConnect = false;
+ state.socketState.tip = '当前服务已结束';
+ // 监听自定义错误 custom_error
+ SocketIo.on('custom_error', (error) => {
+ title: error.msg,
+ console.log('custom_error:', error);
+ // 监听错误 error
+ SocketIo.on('error', (error) => {
+ console.log('error:', error);
+ // 重连失败 connect_error
+ SocketIo.on('connect_error', (error) => {
+ console.log('connect_error');
+ // 连接上,但无反应 connect_timeout
+ SocketIo.on('connect_timeout', (error) => {
+ console.log(error, 'connect_timeout');
+ // 服务进程销毁 disconnect
+ SocketIo.on('disconnect', (error) => {
+ console.log(error, 'disconnect');
+ // 服务重启重连上reconnect
+ SocketIo.on('reconnect', (error) => {
+ console.log(error, 'reconnect');
+ // 开始重连reconnect_attempt
+ SocketIo.on('reconnect_attempt', (error) => {
+ state.socketState.isConnecting = true;
+ title: `重连中,第${error}次尝试...`,
+ console.log(error, 'reconnect_attempt');
+ // 重新连接中reconnecting
+ SocketIo.on('reconnecting', (error) => {
+ console.log(error, 'reconnecting');
+ // 重新连接错误reconnect_error
+ SocketIo.on('reconnect_error', (error) => {
+ console.log('reconnect_error');
+ // 重新连接失败reconnect_failed
+ SocketIo.on('reconnect_failed', (error) => {
+ state.socketState.isConnecting = false;
+ title: `重连失败,请刷新重试~`,
+ console.log(error, 'reconnect_failed');
+ // setTimeout(() => {
+ state.isSendSucces = 1;
+ // }, 500)
+ // 重置socket
+ const socketReset = (callBack) => {
+ state.chatList = [];
+ state.chatHistoryList = [];
+ state.chatHistoryPagination = {
+ page: 0,
+ per_page: 10,
+ last_id: 0,
+ totalPage: 0,
+ socketConnection(callBack); // 连接
+ // 退出连接
+ const socketClose = () => {
+ SocketIo.emit('customer_logout', {}, (res) => {
+ console.log('socket:退出', res);
+ // 测试事件
+ const socketTest = () => {
+ SocketIo.emit('test', {}, (res) => {
+ console.log('test:test', res);
+ // 发送消息
+ const socketSendMsg = (data, sendMsgCallBack) => {
+ state.isSendSucces = -1;
+ state.chatList.push(data);
+ sendMsgCallBack && sendMsgCallBack();
+ SocketIo.emit(
+ 'message',
+ message: formatInput(data),
+ ...data.customData,
+ (res) => {
+ state.isSendSucces = res.error;
+ // console.log(res, 'socket:send');
+ // sendMsgCallBack && sendMsgCallBack()
+ // 连接socket,存入sessionId
+ const socketConnection = (callBack) => {
+ 'connection',
+ auth: 'user',
+ token: uni.getStorageSync('socketUserToken') || '',
+ session_id: uni.getStorageSync('socketSessionId') || '',
+ socketCustomerLogin(callBack);
+ uni.setStorageSync('socketSessionId', res.data.session_id);
+ // uni.getStorageSync('socketUserToken') && socketLogin(uni.getStorageSync(
+ // 'socketUserToken')) // 如果有用户token,绑定
+ state.customerUserInfo = res.data.chat_user;
+ state.socketState.isConnect = true;
+ title: `服务器异常!`,
+ // 用户id,获取token
+ const getUserToken = async (id) => {
+ const res = await chat.unifiedToken();
+ uni.setStorageSync('socketUserToken', res.data.token);
+ // SocketIo && SocketIo.connected && socketLogin(res.data.token)
+ return res;
+ const socketLogin = (token) => {
+ 'login',
+ token: token,
+ console.log(res, 'socket:login');
+ const socketCustomerLogin = (callBack) => {
+ 'customer_login',
+ room_id: state.chatConfig.room_id,
+ state.templateChatList = res.data.questions.length ? res.data.questions : [];
+ state.chatList.push({
+ from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间
+ mode: 'template', // goods,order,image,text,system
+ date: new Date().getTime(), //时间
+ //内容
+ list: state.templateChatList,
+ res.error === 0 && socketHistoryList(callBack);
+ // 获取历史消息
+ const socketHistoryList = (historyCallBack) => {
+ state.chatHistoryPagination.loadStatus = 'loading';
+ state.chatHistoryPagination.page += 1;
+ SocketIo.emit('messages', state.chatHistoryPagination, (res) => {
+ state.chatHistoryPagination.total = res.data.messages.total;
+ state.chatHistoryPagination.lastPage = res.data.messages.last_page;
+ state.chatHistoryPagination.page = res.data.messages.current_page;
+ res.data.messages.data.forEach((item) => {
+ item.message_type && state.chatList.unshift(formatMessage(item));
+ state.chatHistoryPagination.loadStatus =
+ state.chatHistoryPagination.page < state.chatHistoryPagination.lastPage
+ ? 'loadmore'
+ : 'nomore';
+ if (state.chatHistoryPagination.last_id == 0) {
+ state.chatHistoryPagination.last_id = res.data.messages.data.length
+ ? res.data.messages.data[0].id
+ : 0;
+ state.chatHistoryPagination.page === 1 && historyCallBack && historyCallBack();
+ // 历史记录之后,猜你想问
+ // state.chatList.push({
+ // from: 'customer_service', // 用户customer右 | 顾客customer_service左 | 系统system中间
+ // mode: 'template', // goods,order,image,text,system
+ // date: new Date().getTime(), //时间
+ // content: { //内容
+ // list: state.templateChatList
+ // 修改客服信息
+ const editCustomerServerInfo = (data) => {
+ state.customerServerInfo = {
+ ...state.customerServerInfo,
+ ...data,
+ * ================
+ * 工具函数 ↓
+ * ===============
+ * 是否显示时间
+ * @param {*} item - 数据
+ * @param {*} index - 索引
+ const showTime = (item, index) => {
+ if (unref(state.chatList)[index + 1]) {
+ let dateString = dayjs(unref(state.chatList)[index + 1].date).fromNow();
+ if (dateString === dayjs(unref(item).date).fromNow()) {
+ dateString = dayjs(unref(item).date).fromNow();
+ return true;
+ * 格式化时间
+ * @param {*} time - 时间戳
+ const formatTime = (time) => {
+ let diffTime = new Date().getTime() - time;
+ if (diffTime > 28 * 24 * 60 * 1000) {
+ return dayjs(time).format('MM/DD HH:mm');
+ if (diffTime > 360 * 28 * 24 * 60 * 1000) {
+ return dayjs(time).format('YYYY/MM/DD HH:mm');
+ return dayjs(time).fromNow();
+ * 获取焦点
+ * @param {*} virtualNode - 节点信息 ref
+ const getFocus = (virtualNode) => {
+ if (window.getSelection) {
+ let chatInput = unref(virtualNode);
+ chatInput.focus();
+ let range = window.getSelection();
+ range.selectAllChildren(chatInput);
+ range.collapseToEnd();
+ } else if (document.selection) {
+ let range = document.selection.createRange();
+ range.moveToElementText(chatInput);
+ range.collapse(false);
+ range.select();
+ * 文件上传
+ * @param {Blob} file -文件数据流
+ * @return {path,fullPath}
+ const upload = (name, file) => {
+ return new Promise((resolve, reject) => {
+ let data = new FormData();
+ data.append('file', file, name);
+ data.append('group', 'chat');
+ ajax({
+ url: '/upload',
+ method: 'post',
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ data,
+ success: function (res) {
+ resolve(res);
+ error: function (err) {
+ reject(err);
+ * 粘贴到输入框
+ * @param {*} e - 粘贴内容
+ * @param {*} uploadHttp - 上传图片地址
+ const onPaste = async (e) => {
+ let paste = e.clipboardData || window.clipboardData;
+ let filesArr = Array.from(paste.files);
+ filesArr.forEach(async (child) => {
+ if (child && child.type.includes('image')) {
+ e.preventDefault(); //阻止默认
+ let file = child;
+ const img = await readImg(file);
+ const blob = await compressImg(img, file.type);
+ const { data } = await upload(file.name, blob);
+ let image = `<img class="full-url" src='${data.fullurl}'>`;
+ document.execCommand('insertHTML', false, image);
+ document.execCommand('insertHTML', false, paste.getData('text'));
+ * 拖拽到输入框
+ const onDrop = async (e) => {
+ let filesArr = Array.from(e.dataTransfer.files);
+ let image = `<img class="full-url" src='${data.fullurl}' >`;
+ ElMessage({
+ message: '禁止拖拽非图片资源',
+ type: 'warning',
+ * 解析富文本输入框内容
+ * @param {*} virtualNode -节点信息
+ * @param {Function} formatInputCallBack - cb 回调
+ const formatChatInput = (virtualNode, formatInputCallBack) => {
+ let res = '';
+ let elemArr = Array.from(virtualNode.childNodes);
+ elemArr.forEach((child, index) => {
+ if (child.nodeName === '#text') {
+ //如果为文本节点
+ res += child.nodeValue;
+ if (
+ //文本节点的后面是图片,并且不是emoji,分开发送。输入框中的图片和文本表情分开。
+ elemArr[index + 1] &&
+ elemArr[index + 1].nodeName === 'IMG' &&
+ elemArr[index + 1].name !== 'emoji'
+ ) {
+ text: filterXSS(res),
+ formatInputCallBack && formatInputCallBack(data);
+ res = '';
+ } else if (child.nodeName === 'BR') {
+ res += '<br/>';
+ } else if (child.nodeName === 'IMG') {
+ // 有emjio 和 一般图片
+ // 图片解析后直接发送,不跟文字表情一组
+ if (child.name !== 'emoji') {
+ let srcReg = /src=[\'\']?([^\'\']*)[\'\']?/i;
+ let src = child.outerHTML.match(srcReg);
+ url: src[1],
+ path: src[1].replace(/http:\/\/[^\/]*/, ''),
+ // 非表情图片跟文字一起发送
+ res += child.outerHTML;
+ } else if (child.nodeName === 'DIV') {
+ res += `<div style='width:200px; white-space: nowrap;'>${child.outerHTML}</div>`;
+ if (res) {
+ unref(virtualNode).innerHTML = '';
+ * 状态回调
+ * @param {*} res -接口返回数据
+ const callBackNotice = (res) => {
+ ElNotification({
+ title: 'socket',
+ message: res.msg,
+ showClose: true,
+ type: res.error === 0 ? 'success' : 'warning',
+ duration: 1200,
+ * 格式化发送信息
+ * @param {Object} message
+ * @returns obj - 消息对象
+ const formatInput = (message) => {
+ let obj = {};
+ switch (message.mode) {
+ case 'text':
+ obj = {
+ message_type: 'text',
+ message: message.content.text,
+ message_type: 'image',
+ message: message.content.path,
+ message_type: 'goods',
+ message: message.content.item,
+ message_type: 'order',
+ default:
+ return obj;
+ * 格式化接收信息
+ * @param {*} message
+ const formatMessage = (message) => {
+ switch (message.message_type) {
+ case 'system':
+ from: 'system', // 用户customer左 | 顾客customer_service右 | 系统system中间
+ mode: 'system', // goods,order,image,text,system
+ date: message.create_time * 1000, //时间
+ text: message.message,
+ from: message.sender_identify,
+ mode: message.message_type,
+ sender: message.sender,
+ messageId: message.id,
+ url: sheep.$url.cdn(message.message),
+ item: message.message,
+ * file 转换为 img
+ * @param {*} file - file 文件
+ * @returns img - img标签
+ const readImg = (file) => {
+ const img = new Image();
+ const reader = new FileReader();
+ reader.onload = function (e) {
+ img.src = e.target.result;
+ reader.onerror = function (e) {
+ reject(e);
+ reader.readAsDataURL(file);
+ img.onload = function () {
+ resolve(img);
+ img.onerror = function (e) {
+ * 压缩图片
+ *@param img -被压缩的img对象
+ * @param type -压缩后转换的文件类型
+ * @param mx -触发压缩的图片最大宽度限制
+ * @param mh -触发压缩的图片最大高度限制
+ * @returns blob - 文件流
+ const compressImg = (img, type = 'image/jpeg', mx = 1000, mh = 1000, quality = 1) => {
+ const canvas = document.createElement('canvas');
+ const context = canvas.getContext('2d');
+ const { width: originWidth, height: originHeight } = img;
+ // 最大尺寸限制
+ const maxWidth = mx;
+ const maxHeight = mh;
+ // 目标尺寸
+ let targetWidth = originWidth;
+ let targetHeight = originHeight;
+ if (originWidth > maxWidth || originHeight > maxHeight) {
+ if (originWidth / originHeight > 1) {
+ // 宽图片
+ targetWidth = maxWidth;
+ targetHeight = Math.round(maxWidth * (originHeight / originWidth));
+ // 高图片
+ targetHeight = maxHeight;
+ targetWidth = Math.round(maxHeight * (originWidth / originHeight));
+ canvas.width = targetWidth;
+ canvas.height = targetHeight;
+ context.clearRect(0, 0, targetWidth, targetHeight);
+ // 图片绘制
+ context.drawImage(img, 0, 0, targetWidth, targetHeight);
+ canvas.toBlob(
+ function (blob) {
+ resolve(blob);
+ type,
+ quality,
+ compressImg,
+ readImg,
+ formatMessage,
+ formatInput,
+ callBackNotice,
+ socketClose,
+ upload,
+ state,
+ socketTest,
@@ -0,0 +1,125 @@
+<!-- 分销账户:展示基本统计信息 -->
+ <view class="account-card">
+ <view class="account-card-box">
+ <view class="ss-flex ss-row-between card-box-header">
+ <view class="header-title ss-m-r-16">账户信息</view>
+ class="ss-reset-button look-btn ss-flex"
+ @tap="state.showMoney = !state.showMoney"
+ <uni-icons
+ :type="state.showMoney ? 'eye-filled' : 'eye-slash-filled'"
+ color="#A57A55"
+ size="20"
+ <view class="ss-flex" @tap="sheep.$router.go('/pages/user/wallet/commission')">
+ <view class="header-title ss-m-r-4">查看明细</view>
+ <text class="cicon-play-arrow" />
+ <!-- 收益 -->
+ <view class="card-content ss-flex">
+ <view class="ss-flex-1 ss-flex-col ss-col-center">
+ <view class="item-title">当前佣金(元)</view>
+ <view class="item-detail">
+ {{ state.showMoney ? fen2yuan(state.summary.brokeragePrice || 0) : '***' }}
+ <view class="item-title">昨天的佣金(元)</view>
+ {{ state.showMoney ? fen2yuan(state.summary.yesterdayPrice || 0) : '***' }}
+ <view class="item-title">累计已提(元)</view>
+ {{ state.showMoney ? fen2yuan(state.summary.withdrawPrice || 0) : '***' }}
+ import { computed, reactive, onMounted } from 'vue';
+ import BrokerageApi from '@/sheep/api/trade/brokerage';
+ import { fen2yuan } from '@/sheep/hooks/useGoods';
+ const userInfo = computed(() => sheep.$store('user').userInfo);
+ showMoney: false,
+ summary: {},
+ onMounted(async () => {
+ let { code, data } = await BrokerageApi.getBrokerageUserSummary();
+ state.summary = data || {}
+ .account-card {
+ width: 694rpx;
+ margin: 0 auto;
+ padding: 2rpx;
+ background: linear-gradient(180deg, #ffffff 0.88%, #fff9ec 100%);
+ .account-card-box {
+ background: #ffefd6;
+ .card-box-header {
+ height: 72rpx;
+ box-shadow: 0px 2px 6px #f2debe;
+ .header-title {
+ color: #a17545;
+ .cicon-play-arrow {
+ .card-content {
+ height: 190rpx;
+ background: #fdfae9;
+ .item-title {
+ color: #cba67e;
+ margin-bottom: 24rpx;
+ .item-detail {
+ font-weight: bold;
+ color: #692e04;
@@ -0,0 +1,160 @@
+<!-- 提现方式的 select 组件 -->
+ <su-popup :show="show" class="ss-checkout-counter-wrap" @close="hideModal">
+ <view class="ss-modal-box bg-white ss-flex-col">
+ <view class="modal-header ss-flex-col ss-col-left">
+ <text class="modal-title ss-m-b-20">选择提现方式</text>
+ <view class="modal-content ss-flex-1 ss-p-b-100">
+ <radio-group @change="onChange">
+ <label
+ class="container-list ss-p-l-34 ss-p-r-24 ss-flex ss-col-center ss-row-center"
+ v-for="(item, index) in typeList"
+ <view class="container-icon ss-flex ss-m-r-20">
+ <image :src="sheep.$url.static(item.icon)" />
+ <view class="ss-flex-1">{{ item.title }}</view>
+ <radio
+ :value="item.value"
+ color="var(--ui-BG-Main)"
+ :checked="item.value === state.currentValue"
+ :disabled="!methods.includes(parseInt(item.value))"
+ </label>
+ </radio-group>
+ <view class="modal-footer ss-flex ss-row-center ss-col-center">
+ <button class="ss-reset-button save-btn" @tap="onConfirm">确定</button>
+ modelValue: {
+ default() {},
+ methods: { // 开启的提现方式
+ type: Array,
+ default: [],
+ const emits = defineEmits(['update:modelValue', 'change', 'close']);
+ currentValue: '',
+ const typeList = [
+ // icon: '/static/img/shop/pay/wechat.png', // TODO 芋艿:后续给个 icon
+ title: '钱包余额',
+ value: '1',
+ icon: '/static/img/shop/pay/wechat.png',
+ title: '微信零钱',
+ value: '2',
+ icon: '/static/img/shop/pay/alipay.png',
+ title: '支付宝账户',
+ value: '3',
+ icon: '/static/img/shop/pay/bank.png',
+ title: '银行卡转账',
+ value: '4',
+ function onChange(e) {
+ state.currentValue = e.detail.value;
+ const onConfirm = async () => {
+ if (state.currentValue === '') {
+ sheep.$helper.toast('请选择提现方式');
+ // 赋值
+ emits('update:modelValue', {
+ type: state.currentValue
+ // 关闭弹窗
+ emits('close');
+ const hideModal = () => {
+ .ss-modal-box {
+ border-radius: 30rpx 30rpx 0 0;
+ max-height: 1000rpx;
+ .modal-header {
+ padding: 60rpx 40rpx 40rpx;
+ .modal-title {
+ .close-icon {
+ top: 10rpx;
+ font-size: 46rpx;
+ opacity: 0.2;
+ .modal-content {
+ overflow-y: auto;
+ .container-list {
+ height: 96rpx;
+ border-bottom: 2rpx solid rgba(#dfdfdf, 0.5);
+ .container-icon {
+ width: 36rpx;
+ .modal-footer {
+ .save-btn {
+ border-radius: 40rpx;
+ color: $white;
@@ -0,0 +1,101 @@
+<!-- 分销权限弹窗:再没有权限时,进行提示 -->
+ :show="state.show"
+ type="center"
+ round="10"
+ @close="state.show = false"
+ :isMaskClick="false"
+ maskBackgroundColor="rgba(0, 0, 0, 0.7)"
+ <view class="notice-box">
+ <view class="img-wrap">
+ class="notice-img"
+ :src="sheep.$url.static('/static/img/shop/commission/forbidden.png')"
+ <view class="notice-title"> 抱歉!您没有分销权限 </view>
+ <view class="notice-detail"> 该功能暂不可用 </view>
+ class="ss-reset-button notice-btn ui-Shadow-Main ui-BG-Main-Gradient"
+ @tap="sheep.$router.back()"
+ 知道了
+ <button class="ss-reset-button back-btn" @tap="sheep.$router.back()"> 返回 </button>
+ import { onShow } from '@dcloudio/uni-app';
+ show: false,
+ onShow(async () => {
+ // 读取是否有分销权限
+ const { code, data } = await BrokerageApi.getBrokerageUser();
+ if (code === 0 && !data?.brokerageEnabled) {
+ state.show = true;
+ .notice-box {
+ width: 612rpx;
+ min-height: 658rpx;
+ padding: 30rpx;
+ .img-wrap {
+ margin-bottom: 50rpx;
+ .notice-img {
+ width: 180rpx;
+ height: 170rpx;
+ .notice-title {
+ font-size: 35rpx;
+ margin-bottom: 28rpx;
+ .notice-detail {
+ .notice-btn {
+ width: 492rpx;
+ line-height: 70rpx;
+ margin-bottom: 10rpx;
+ .back-btn {
+ color: var(--ui-BG-Main-gradient);
+ background: none;
@@ -0,0 +1,113 @@
+<!-- 分销商信息 -->
+ <!-- 用户资料 -->
+ <view class="user-card ss-flex ss-col-bottom">
+ <view class="card-top ss-flex ss-row-between">
+ <view class="head-img-box">
+ <image class="head-img" :src="sheep.$url.cdn(userInfo.avatar)" mode="aspectFill"></image>
+ <view class="ss-flex-col">
+ <view class="user-name">{{ userInfo.nickname }}</view>
+ const headerBg = sheep.$url.css('/static/img/shop/commission/background.png');
+ // 用户资料卡片
+ .user-card {
+ width: 690rpx;
+ height: 192rpx;
+ margin: -88rpx 20rpx 0 20rpx;
+ padding-top: 88rpx;
+ .head-img-box {
+ background: #fce0ad;
+ .head-img {
+ width: 92rpx;
+ height: 92rpx;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+ .card-top {
+ padding-bottom: 34rpx;
+ .user-name {
+ .log-btn {
+ width: 84rpx;
+ height: 42rpx;
+ border: 2rpx solid rgba(#ffffff, 0.33);
+ border-radius: 21rpx;
+ .look-btn {
+ .user-info-box {
+ .tag-box {
+ background: #ff6000;
+ border-radius: 18rpx;
+ .tag-img {
+ margin-left: -2rpx;
+ .tag-title {
+ padding: 0 10rpx;
@@ -0,0 +1,165 @@
+<!-- 分销首页:明细列表 -->
+ <view class="distribution-log-wrap">
+ <view class="header-box">
+ <image class="header-bg" :src="sheep.$url.static('/static/img/shop/commission/title2.png')" />
+ <view class="ss-flex header-title">
+ <view class="title">实时动态</view>
+ <text class="cicon-forward" />
+ <scroll-view scroll-y="true" @scrolltolower="loadmore" class="scroll-box log-scroll"
+ scroll-with-animation="true">
+ <view v-if="state.pagination.list">
+ <view class="log-item-box ss-flex ss-row-between" v-for="item in state.pagination.list" :key="item.id">
+ <view class="log-item-wrap">
+ <view class="log-item ss-flex ss-ellipsis-1 ss-col-center">
+ <view class="ss-flex ss-col-center">
+ <image class="log-img" :src="sheep.$url.static('/static/img/shop/avatar/notice.png')" mode="aspectFill" />
+ <view class="log-text ss-ellipsis-1">
+ {{ item.title }} {{ fen2yuan(item.price) }} 元
+ <text class="log-time">{{ dayjs(item.createTime).fromNow() }}</text>
+ <!-- 加载更多 -->
+ <uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" color="#333333"
+ @tap="loadmore" />
+ import dayjs from 'dayjs';
+ import { fen2yuan } from '../../../sheep/hooks/useGoods';
+ pageSize: 1,
+ async function getLog() {
+ const { code, data } = await BrokerageApi.getBrokerageRecordPage({
+ pageSize: state.pagination.pageSize
+ getLog();
+ .distribution-log-wrap {
+ .header-box {
+ height: 76rpx;
+ .header-bg {
+ top: 24rpx;
+ .cicon-forward {
+ .log-scroll {
+ height: 600rpx;
+ padding: 10rpx 20rpx 0;
+ border-radius: 0 0 12rpx 12rpx;
+ .log-item-box {
+ .log-time {
+ // margin-left: 30rpx;
+ text-align: right;
+ color: #c4c4c4;
+ .loadmore-wrap {
+ // line-height: 80rpx;
+ .log-item {
+ // background: rgba(#ffffff, 0.2);
+ border-radius: 24rpx;
+ padding: 6rpx 20rpx 6rpx 12rpx;
+ .log-img {
+ .log-text {
+ max-width: 480rpx;
@@ -0,0 +1,138 @@
+<!-- 分销:商菜单栏 -->
+ <view class="menu-box ss-flex-col">
+ <image class="header-bg" :src="sheep.$url.static('/static/img/shop/commission/title1.png')" />
+ <view class="title">功能专区</view>
+ <text class="cicon-forward"></text>
+ <view class="menu-list ss-flex ss-flex-wrap">
+ <view v-for="(item, index) in state.menuList" :key="index" class="item-box ss-flex-col ss-col-center"
+ @tap="sheep.$router.go(item.path)">
+ <image class="menu-icon ss-m-b-10" :src="sheep.$url.static(item.img)" mode="aspectFill"></image>
+ <view>{{ item.title }}</view>
+ menuList: [{
+ img: '/static/img/shop/commission/commission_icon1.png',
+ title: '我的团队',
+ path: '/pages/commission/team',
+ img: '/static/img/shop/commission/commission_icon2.png',
+ title: '佣金明细',
+ path: '/pages/commission/wallet',
+ img: '/static/img/shop/commission/commission_icon3.png',
+ title: '分销订单',
+ path: '/pages/commission/order',
+ img: '/static/img/shop/commission/commission_icon4.png',
+ title: '推广商品',
+ path: '/pages/commission/goods',
+ // img: '/static/img/shop/commission/commission_icon5.png',
+ // title: '我的资料',
+ // path: '/pages/commission/apply',
+ // isAgentFrom: true,
+ // todo @芋艿:邀请海报需要登录后的个人数据
+ img: '/static/img/shop/commission/commission_icon7.png',
+ title: '邀请海报',
+ path: 'action:showShareModal',
+ // TODO @芋艿:缺少 icon
+ // img: '/static/img/shop/commission/commission_icon7.png',
+ title: '推广排行',
+ path: '/pages/commission/promoter',
+ title: '佣金排行',
+ path: '/pages/commission/commission-ranking',
+ .menu-box {
+ margin-top: 20rpx;
+ .menu-list {
+ padding: 50rpx 0 10rpx 0;
+ .item-box {
+ width: 25%;
+ margin-bottom: 40rpx;
+ .menu-icon {
+ width: 68rpx;
+ height: 68rpx;
+ .menu-title {
@@ -0,0 +1,150 @@
+<!-- 分销商品列表 -->
+ <s-layout title="推广商品" :onShareAppMessage="state.shareInfo">
+ <view class="goods-item ss-m-20" v-for="item in state.pagination.list" :key="item.id">
+ :img="item.picUrl"
+ :title="item.name"
+ :subTitle="item.introduction"
+ :price="item.price"
+ :originPrice="item.marketPrice"
+ priceColor="#333"
+ @tap="sheep.$router.go('/pages/goods/index', { id: item.id })"
+ <template #rightBottom>
+ <view class="ss-flex ss-row-between">
+ <view class="commission-num" v-if="item.brokerageMinPrice === undefined">预计佣金:计算中</view>
+ <view class="commission-num" v-else-if="item.brokerageMinPrice === item.brokerageMaxPrice">
+ 预计佣金:{{ fen2yuan(item.brokerageMinPrice) }}
+ <view class="commission-num" v-else>
+ 预计佣金:{{ fen2yuan(item.brokerageMinPrice) }} ~ {{ fen2yuan(item.brokerageMaxPrice) }}
+ class="ss-reset-button share-btn ui-BG-Main-Gradient"
+ @tap.stop="onShareGoods(item)"
+ 分享赚
+ <s-empty
+ v-if="state.pagination.total === 0"
+ icon="/static/goods-empty.png"
+ text="暂无推广商品"
+ import $share from '@/sheep/platform/share';
+ import { fen2yuan } from '../../sheep/hooks/useGoods';
+ shareInfo: {},
+ // TODO 芋艿:分享的接入
+ function onShareGoods(goodsInfo) {
+ state.shareInfo = $share.getShareInfo(
+ title: goodsInfo.title,
+ image: sheep.$url.cdn(goodsInfo.image),
+ desc: goodsInfo.subtitle,
+ page: '2',
+ query: goodsInfo.id,
+ type: 'goods', // 商品海报
+ title: goodsInfo.title, // 商品标题
+ image: sheep.$url.cdn(goodsInfo.image), // 商品主图
+ price: goodsInfo.price[0], // 商品价格
+ original_price: goodsInfo.original_price, // 商品原价
+ async function getGoodsList() {
+ let { code, data } = await SpuApi.getSpuPage({
+ // 补充分佣金额
+ data.list.forEach((item) => {
+ BrokerageApi.getProductBrokeragePrice(item.id).then((res) => {
+ item.brokerageMinPrice = res.data.brokerageMinPrice;
+ item.brokerageMaxPrice = res.data.brokerageMaxPrice;
+ getGoodsList();
+ .goods-item {
+ .commission-num {
+ color: $red;
+ .share-btn {
+ width: 120rpx;
@@ -0,0 +1,37 @@
+<!-- 分销中心 -->
+ <s-layout navbar="inner" class="index-wrap" title="分销中心" :bgStyle="bgStyle" onShareAppMessage>
+ <!-- 分销商信息 -->
+ <commission-info />
+ <!-- 账户信息 -->
+ <account-info />
+ <!-- 菜单栏 -->
+ <commission-menu />
+ <!-- 分销记录 -->
+ <commission-log />
+ <!-- 权限弹窗 -->
+ <commission-auth />
+ import commissionInfo from './components/commission-info.vue';
+ import accountInfo from './components/account-info.vue';
+ import commissionLog from './components/commission-log.vue';
+ import commissionMenu from './components/commission-menu.vue';
+ import commissionAuth from './components/commission-auth.vue';
+ const state = reactive({});
+ const bgStyle = {
+ color: '#F7D598',
+ :deep(.page-main) {
+ background-size: 100% 100% !important;
@@ -0,0 +1,331 @@
+<!-- 分销 - 订单明细 -->
+ <s-layout title="分销订单" :class="state.scrollTop ? 'order-warp' : ''" navbar="inner">
+ class="header-box"
+ <!-- 团队数据总览 -->
+ <view class="team-data-box ss-flex ss-col-center ss-row-between" style="width: 100%">
+ <view class="data-card" style="width: 100%">
+ <view class="total-item" style="width: 100%">
+ <view class="item-title" style="text-align: center">累计推广订单(单)</view>
+ <view class="total-num" style="text-align: center">
+ {{ state.totals }}
+ <!-- tab -->
+ </su-tabs>
+ <!-- 订单 -->
+ <view class="order-box">
+ <view class="order-item" v-for="item in state.pagination.list" :key="item">
+ <view class="order-header">
+ <view class="no-box ss-flex ss-col-center ss-row-between">
+ <text class="order-code">订单编号:{{ item.bizId }}</text>
+ <text class="order-state">
+ {{
+ item.status === 0 ? '待结算'
+ : item.status === 1 ? '已结算' : '已取消'
+ }}
+ ( 佣金 {{ fen2yuan(item.price) }} 元 )
+ </text>
+ <view class="order-from ss-flex ss-col-center ss-row-between">
+ <view class="from-user ss-flex ss-col-center">
+ <text>{{ item.title }}</text>
+ <view class="order-time">
+ {{ sheep.$helper.timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') }}
+ <!-- 数据为空 -->
+ <s-empty v-if="state.pagination.total === 0" icon="/static/order-empty.png" text="暂无订单" />
+ import { onPageScroll } from '@dcloudio/uni-app';
+ onPageScroll((e) => {
+ state.scrollTop = e.scrollTop <= 100;
+ totals: 0, // 累计推广订单(单)
+ scrollTop: false,
+ value: 'all',
+ name: '待结算',
+ value: '0', // 待结算
+ name: '已结算',
+ value: '1', // 已结算
+ getOrderList();
+ async function getOrderList() {
+ let { code, data } = await BrokerageApi.getBrokerageRecordPage({
+ pageNo: state.pagination.pageSize,
+ bizType: 1, // 获得推广佣金
+ status: state.currentTab > 0 ? state.currentTab : undefined,
+ if (state.currentTab === 0) {
+ state.totals = data.total;
+ padding: 0 20rpx 20rpx 20rpx;
+ width: 750rpx;
+ background: v-bind(headerBg) no-repeat,
+ linear-gradient(90deg, var(--ui-BG-Main), var(--ui-BG-Main-gradient));
+ // 团队信息总览
+ .team-data-box {
+ .data-card {
+ width: 305rpx;
+ .total-item {
+ margin-bottom: 30rpx;
+ .total-num {
+ font-size: 38rpx;
+ .category-num {
+ // 直推
+ .direct-box {
+ .direct-item {
+ width: 340rpx;
+ margin-bottom: 6rpx;
+ .item-value {
+ // 订单
+ .order-box {
+ .order-item {
+ margin: 20rpx;
+ .order-footer {
+ .order-header {
+ .no-box {
+ .order-code {
+ .order-state {
+ .order-from {
+ .from-user {
+ color: #666666;
+ width: 26rpx;
+ height: 26rpx;
+ .order-time {
+ .commission-box {
+ .name {
+ &::before {
+ content: '¥';
+ .order-status {
+ margin-left: 20rpx;
@@ -0,0 +1,581 @@
+<!-- 页面 TODO 芋艿:该页面的实现代码需要优化,包括 js 和 css,以及相关的样式设计 -->
+ <s-layout title="我的团队" :class="state.scrollTop ? 'team-wrap' : ''" navbar="inner">
+ <view class="promoter-list">
+ class="promoterHeader bg-color"
+ style="backgroundcolor: #e93323 !important; height: 218rpx; color: #fff"
+ <view class="headerCon acea-row row-between" style="padding: 28px 29px 0 29px">
+ <view>
+ <view class="name" style="color: #fff">推广人数</view>
+ <text class="num" style="color: #fff">
+ state.summary.firstBrokerageUserCount + state.summary.secondBrokerageUserCount ||
+ 0
+ 人
+ <view class="iconfont icon-tuandui" />
+ <view style="padding: 0 30rpx">
+ <view class="nav acea-row row-around l1">
+ <view :class="state.level == 1 ? 'item on' : 'item'" @click="setType(1)">
+ 一级({{ state.summary.firstBrokerageUserCount || 0 }})
+ <view :class="state.level == 2 ? 'item on' : 'item'" @click="setType(2)">
+ 二级({{ state.summary.secondBrokerageUserCount || 0 }})
+ class="search acea-row row-between-wrapper"
+ style="display: flex; height: 100rpx; align-items: center"
+ <view class="input">
+ <input
+ placeholder="点击搜索会员名称"
+ v-model="state.nickname"
+ confirm-type="search"
+ name="search"
+ @confirm="submitForm"
+ src="/static/images/search.png"
+ mode=""
+ style="width: 60rpx; height: 64rpx"
+ @click="submitForm"
+ <view class="list">
+ <view class="sortNav acea-row row-middle" style="display: flex; align-items: center">
+ class="sortItem"
+ @click="setSort('userCount', 'asc')"
+ v-if="sort === 'userCountDESC'"
+ 团队排序
+ <!-- TODO 芋艿:看看怎么从项目里拿出去 -->
+ <image src="/static/images/sort1.png" />
+ @click="setSort('userCount', 'desc')"
+ v-else-if="sort === 'userCountASC'"
+ <image src="/static/images/sort3.png" />
+ <view class="sortItem" @click="setSort('userCount', 'desc')" v-else>
+ <image src="/static/images/sort2.png" />
+ <view class="sortItem" @click="setSort('price', 'asc')" v-if="sort === 'priceDESC'">
+ 金额排序
+ @click="setSort('price', 'desc')"
+ v-else-if="sort === 'priceASC'"
+ <view class="sortItem" @click="setSort('price', 'desc')" v-else>
+ @click="setSort('orderCount', 'asc')"
+ v-if="sort === 'orderCountDESC'"
+ 订单排序
+ @click="setSort('orderCount', 'desc')"
+ v-else-if="sort === 'orderCountASC'"
+ <view class="sortItem" @click="setSort('orderCount', 'desc')" v-else>
+ <block v-for="(item, index) in state.pagination.list" :key="index">
+ <view class="item acea-row row-between-wrapper" style="display: flex">
+ class="picTxt acea-row row-between-wrapper"
+ style="display: flex; align-items: center"
+ <view class="pictrue">
+ <image :src="item.avatar" />
+ <view class="text">
+ <view class="name line1">{{ item.nickname }}</view>
+ 加入时间:
+ {{ sheep.$helper.timeFormat(item.brokerageTime, 'yyyy-mm-dd hh:MM:ss') }}
+ class="right"
+ style="
+ margin-left: auto;
+ <text class="num font-color">{{ item.brokerageUserCount || 0 }} </text>人
+ <text class="num">{{ item.orderCount || 0 }}</text
+ >单</view
+ <text class="num">{{ item.brokeragePrice || 0 }}</text
+ >元
+ <block v-if="state.pagination.list.length === 0">
+ <view style="text-align: center">暂无推广人数</view>
+ <!-- <home></home> -->
+ <!-- <view class="header-box" :style="[
+ ]">
+ <view v-if="userInfo.parent_user" class="referrer-box ss-flex ss-col-center">
+ 推荐人:
+ <image class="referrer-avatar ss-m-r-10" :src="sheep.$url.cdn(userInfo.parent_user.avatar)"
+ mode="aspectFill">
+ {{ userInfo.parent_user.nickname }}
+ <view class="team-data-box ss-flex ss-col-center ss-row-between">
+ <view class="data-card">
+ <view class="total-item">
+ <view class="item-title">团队总人数(人)</view>
+ <view class="total-num">
+ {{ (state.summary.firstBrokerageUserCount+ state.summary.secondBrokerageUserCount)|| 0 }}
+ <view class="category-item ss-flex">
+ <view class="item-title">一级成员</view>
+ <view class="category-num">{{ state.summary.firstBrokerageUserCount || 0 }}</view>
+ <view class="item-title">二级成员</view>
+ <view class="category-num">{{ state.summary.secondBrokerageUserCount || 0 }}</view>
+ <view class="item-title">团队分销商人数(人)</view>
+ <view class="total-num">{{ agentInfo?.child_agent_count_all || 0 }}</view>
+ <view class="item-title">一级分销商</view>
+ <view class="category-num">{{ agentInfo?.child_agent_count_1 || 0 }}</view>
+ <view class="item-title">二级分销商</view>
+ <view class="category-num">{{ agentInfo?.child_agent_count_2 || 0 }}</view>
+ <view class="list-box">
+ <uni-list :border="false">
+ <uni-list-chat v-for="item in state.pagination.data" :key="item.id" :avatar-circle="true"
+ :title="item.nickname" :avatar="sheep.$url.cdn(item.avatar)"
+ :note="filterUserNum(item.agent?.child_user_count_1)">
+ <view class="chat-custom-right">
+ <view v-if="item.avatar" class="tag-box ss-flex ss-col-center">
+ <image class="tag-img" :src="sheep.$url.cdn(item.avatar)" mode="aspectFill">
+ <text class="tag-title">{{ item.nickname }}</text>
+ class="time-text">{{ sheep.$helper.timeFormat(item.brokerageTime, 'yyyy-mm-dd hh:MM:ss') }}</text>
+ </uni-list-chat>
+ </uni-list>
+ <s-empty v-if="state.pagination.total === 0" icon="/static/data-empty.png" text="暂无团队信息">
+ </s-empty> -->
+ import { computed, reactive, ref } from 'vue';
+ // const agentInfo = computed(() => sheep.$store('user').agentInfo);
+ let sort = ref();
+ // ↓ 新 ui 逻辑
+ level: 1,
+ nickname: ref(''),
+ sortKey: '',
+ isAsc: '',
+ function filterUserNum(num) {
+ if (_.isNil(num)) {
+ return '';
+ return `下级团队${num}人`;
+ function submitForm() {
+ state.pagination.list = [];
+ getTeamList();
+ async function getTeamList() {
+ let { code, data } = await BrokerageApi.getBrokerageUserChildSummaryPage({
+ level: state.level,
+ 'sortingField.order': state.isAsc,
+ 'sortingField.field': state.sortKey,
+ nickname: state.nickname,
+ function setType(e) {
+ state.level = e + '';
+ function setSort(sortKey, isAsc) {
+ sort = sortKey + isAsc.toUpperCase();
+ state.isAsc = isAsc;
+ state.sortKey = sortKey;
+ await getTeamList();
+ // 概要数据
+ let { data } = await BrokerageApi.getBrokerageUserSummary();
+ state.summary = data;
+ .l1 {
+ line-height: 86rpx;
+ color: #282828;
+ border-bottom: 1rpx solid #eee;
+ border-top-left-radius: 14rpx;
+ border-top-right-radius: 14rpx;
+ justify-content: space-around;
+ .list-box {
+ .chat-custom-right {
+ .time-text {
+ background: rgba(0, 0, 0, 0.2);
+ padding: 5rpx 10rpx;
+ width: 34rpx;
+ height: 34rpx;
+ margin-right: 6rpx;
+ font-size: 18rpx;
+ color: rgba(255, 255, 255, 1);
+ line-height: 20rpx;
+ // 推荐人
+ .referrer-box {
+ .referrer-avatar {
+ .promoter-list .nav {
+ margin-top: -30rpx;
+ .promoter-list .nav .item.on {
+ border-bottom: 5rpx solid;
+ // $theme-color
+ color: red;
+ .promoter-list .search {
+ padding: 0 24rpx;
+ border-bottom-left-radius: 14rpx;
+ border-bottom-right-radius: 14rpx;
+ .promoter-list .search .input {
+ width: 592rpx;
+ border-radius: 50rpx;
+ background-color: #f5f5f5;
+ .promoter-list .search .input input {
+ width: 610rpx;
+ .promoter-list .search .input .placeholder {
+ color: #bbb;
+ .promoter-list .search .input .iconfont {
+ right: 28rpx;
+ transform: translateY(-50%);
+ .promoter-list .search .iconfont {
+ color: #515151;
+ .promoter-list .list {
+ .promoter-list .list .sortNav {
+ .promoter-list .list .sortNav .sortItem {
+ .promoter-list .list .sortNav .sortItem image {
+ width: 24rpx;
+ height: 24rpx;
+ margin-left: 6rpx;
+ vertical-align: -3rpx;
+ .promoter-list .list .item {
+ height: 152rpx;
+ .promoter-list .list .item .picTxt .pictrue {
+ width: 106rpx;
+ height: 106rpx;
+ .promoter-list .list .item .picTxt .pictrue image {
+ border: 3rpx solid #fff;
+ box-shadow: 0 0 10rpx #aaa;
+ .promoter-list .list .item .picTxt .text {
+ // width: 304rpx;
+ margin-left: 14rpx;
+ .promoter-list .list .item .picTxt .text .name {
+ margin-bottom: 13rpx;
+ .promoter-list .list .item .right {
+ .promoter-list .list .item .right .num {
+ margin-right: 7rpx;
@@ -0,0 +1,470 @@
+<!-- 分销 - 佣金明细 -->
+ <s-layout class="wallet-wrap" title="佣金">
+ <!-- 钱包卡片 -->
+ <view class="header-box ss-flex ss-row-center ss-col-center">
+ <view class="card-box ui-BG-Main ui-Shadow-Main">
+ <view class="card-head ss-flex ss-col-center">
+ <view class="card-title ss-m-r-10">当前佣金(元)</view>
+ <view @tap="state.showMoney = !state.showMoney" class="ss-eye-icon"
+ :class="state.showMoney ? 'cicon-eye' : 'cicon-eye-off'" />
+ <view class="ss-flex ss-row-between ss-col-center ss-m-t-30">
+ <view class="money-num">{{ state.showMoney ? fen2yuan(state.summary.withdrawPrice || 0) : '*****' }}</view>
+ <view class="ss-m-r-20">
+ <button class="ss-reset-button withdraw-btn" @tap="sheep.$router.go('/pages/commission/withdraw')">
+ 提现
+ <button class="ss-reset-button balance-btn ss-m-l-20" @tap="state.showModal = true">
+ 转余额
+ <view class="loading-money">
+ <view class="loading-money-title">冻结佣金</view>
+ <view class="loading-money-num">
+ {{ state.showMoney ? fen2yuan(state.summary.frozenPrice || 0) : '*****' }}
+ <view class="loading-money ss-m-l-100">
+ <view class="loading-money-title">可提现佣金</view>
+ {{ state.showMoney ? fen2yuan(state.summary.brokeragePrice || 0) : '*****' }}
+ <su-sticky>
+ <!-- 统计 -->
+ <view class="filter-box ss-p-x-30 ss-flex ss-col-center ss-row-between">
+ <uni-datetime-picker v-model="state.date" type="daterange" @change="onChangeTime" :end="state.today">
+ <button class="ss-reset-button date-btn">
+ <text>{{ dateFilterText }}</text>
+ <text class="cicon-drop-down ss-seldate-icon" />
+ </uni-datetime-picker>
+ <view class="total-box">
+ <!-- TODO 芋艿:这里暂时不考虑做 -->
+ <!-- <view class="ss-m-b-10">总收入¥{{ state.pagination.income.toFixed(2) }}</view> -->
+ <!-- <view>总支出¥{{ (-state.pagination.expense).toFixed(2) }}</view> -->
+ <su-tabs :list="tabMaps" @change="onChangeTab" :scrollable="false" :current="state.currentTab" />
+ <s-empty v-if="state.pagination.total === 0" icon="/static/data-empty.png" text="暂无数据"></s-empty>
+ <!-- 转余额弹框 -->
+ <su-popup :show="state.showModal" type="bottom" round="20" @close="state.showModal = false" showClose>
+ <view class="ss-p-x-20 ss-p-y-30">
+ <view class="model-title ss-m-b-30 ss-m-l-20">转余额</view>
+ <view class="model-subtitle ss-m-b-100 ss-m-l-20">将您的佣金转到余额中继续消费</view>
+ <view class="input-box ss-flex ss-col-center border-bottom ss-m-b-70 ss-m-x-20">
+ <view class="unit">¥</view>
+ <uni-easyinput :inputBorder="false" class="ss-flex-1 ss-p-l-10" v-model="state.price" type="number"
+ placeholder="请输入金额" />
+ <button class="ss-reset-button model-btn ui-BG-Main-Gradient ui-Shadow-Main" @tap="onConfirm">
+ 确定
+ <!-- 钱包记录 -->
+ <view class="wallet-list ss-flex border-bottom" v-for="item in state.pagination.list" :key="item.id">
+ <view class="title-box ss-flex ss-row-between ss-m-b-20">
+ <text class="title ss-line-1">{{ item.title }}</text>
+ <view class="money">
+ <text v-if="item.price >= 0" class="add">+{{ fen2yuan(item.price) }}</text>
+ <text v-else class="minus">{{ fen2yuan(item.price) }}</text>
+ <text class="time">{{ sheep.$helper.timeFormat(item.createTime, 'yyyy-mm-dd hh:MM:ss') }}</text>
+ <!-- <u-gap></u-gap> -->
+ <uni-load-more v-if="state.pagination.total > 0" :status="state.loadStatus" :content-text="{
+ }" />
+ const headerBg = sheep.$url.css('/static/img/shop/user/wallet_card_bg.png');
+ summary: {}, // 分销信息
+ today: '',
+ date: [],
+ price: undefined,
+ showModal: false,
+ const tabMaps = [{
+ name: '分佣',
+ value: '1', // BrokerageRecordBizTypeEnum.ORDER
+ name: '提现',
+ value: '2', // BrokerageRecordBizTypeEnum.WITHDRAW
+ const dateFilterText = computed(() => {
+ if (state.date[0] === state.date[1]) {
+ return state.date[0];
+ return state.date.join('~');
+ async function getLogList() {
+ bizType: tabMaps[state.currentTab].value,
+ 'createTime[0]': state.date[0] + ' 00:00:00',
+ 'createTime[1]': state.date[1] + ' 23:59:59',
+ function onChangeTab(e) {
+ getLogList();
+ function onChangeTime(e) {
+ state.date[0] = e[0];
+ state.date[1] = e[e.length - 1];
+ // 确认操作(转账到余额)
+ async function onConfirm() {
+ if (state.price <= 0) {
+ sheep.$helper.toast('请输入正确的金额');
+ uni.showModal({
+ title: '提示',
+ content: '确认把您的佣金转入到余额钱包中?',
+ success: async function(res) {
+ if (!res.confirm) {
+ const { code } = await BrokerageApi.createBrokerageWithdraw({
+ type: 1, // 钱包
+ price: state.price * 100,
+ state.showModal = false;
+ await getAgentInfo();
+ onChangeTab({
+ index: 1
+ async function getAgentInfo() {
+ const { code, data } = await BrokerageApi.getBrokerageUserSummary();
+ state.today = dayjs().format('YYYY-MM-DD');
+ state.date = [state.today, state.today];
+ if (options.type === 2) { // 切换到“提现” tab 下
+ state.currentTab = 1;
+ getAgentInfo();
+ // 钱包
+ background-color: $white;
+ .card-box {
+ min-height: 300rpx;
+ padding: 40rpx;
+ z-index: 2;
+ .card-head {
+ .ss-eye-icon {
+ font-size: 40rpx;
+ .money-num {
+ .reduce-num {
+ .withdraw-btn {
+ border-radius: 30px;
+ .balance-btn {
+ border: 1px solid $white;
+ .loading-money {
+ margin-top: 56rpx;
+ .loading-money-title {
+ .loading-money-num {
+ color: #fefefe;
+ // 筛选
+ .filter-box {
+ background-color: $bg-page;
+ .total-box {
+ .date-btn {
+ line-height: 54rpx;
+ border-radius: 27rpx;
+ .ss-seldate-icon {
+ // tab
+ .wallet-tab-card {
+ .tab-item {
+ .tab-title {
+ .cur-tab-title {
+ font-weight: $font-weight-bold;
+ .tab-line {
+ width: 60rpx;
+ height: 6rpx;
+ transform: translateX(-50%);
+ bottom: 2rpx;
+ background-color: var(--ui-BG-Main);
+ // 钱包记录
+ .wallet-list {
+ background-color: #ffff;
+ background: $gray-c;
+ justify-content: space-between;
+ align-items: flex-start;
+ color: $dark-3;
+ color: $gray-c;
+ .money {
+ .add {
+ .minus {
+ .model-title {
+ .model-subtitle {
+ color: #c2c7cf;
+ .model-btn {
+ .input-box {
+ .unit {
+ font-size: 48rpx;
+ .uni-easyinput__placeholder-class {
@@ -0,0 +1,427 @@
+<!-- 分佣提现 -->
+ <s-layout title="申请提现" class="withdraw-wrap" navbar="inner">
+ <view class="page-bg"></view>
+ class="wallet-num-box ss-flex ss-col-center ss-row-between"
+ <view class="">
+ <view class="num-title">可提现金额(元)</view>
+ <view class="wallet-num">{{ fen2yuan(state.brokerageInfo.brokeragePrice) }}</view>
+ <button class="ss-reset-button log-btn" @tap="sheep.$router.go('/pages/commission/wallet', { type: 2 })">
+ 提现记录
+ <!-- 提现输入卡片-->
+ <view class="draw-card">
+ <view class="bank-box ss-flex ss-col-center ss-row-between ss-m-b-30">
+ <view class="name">提现至</view>
+ <view class="bank-list ss-flex ss-col-center" @tap="onAccountSelect(true)">
+ <view v-if="!state.accountInfo.type" class="empty-text">请选择提现方式</view>
+ <view v-if="state.accountInfo.type === '1'" class="empty-text">钱包余额</view>
+ <view v-if="state.accountInfo.type === '2'" class="empty-text">微信零钱</view>
+ <view v-if="state.accountInfo.type === '3'" class="empty-text">支付宝账户</view>
+ <view v-if="state.accountInfo.type === '4'" class="empty-text">银行卡转账</view>
+ <!-- 提现金额 -->
+ <view class="card-title">提现金额</view>
+ <view class="input-box ss-flex ss-col-center border-bottom">
+ class="ss-flex-1 ss-p-l-10"
+ v-model="state.accountInfo.price"
+ type="number"
+ placeholder="请输入提现金额"
+ <!-- 提现账号 -->
+ <view class="card-title" v-show="['2', '3', '4'].includes(state.accountInfo.type)">
+ 提现账号
+ class="input-box ss-flex ss-col-center border-bottom"
+ v-show="['2', '3', '4'].includes(state.accountInfo.type)"
+ <view class="unit" />
+ v-model="state.accountInfo.accountNo"
+ placeholder="请输入提现账号"
+ <!-- 收款码 -->
+ <view class="card-title" v-show="['2', '3'].includes(state.accountInfo.type)">收款码</view>
+ class="input-box ss-flex ss-col-center"
+ v-show="['2', '3'].includes(state.accountInfo.type)"
+ <view class="upload-img">
+ v-model:url="state.accountInfo.accountQrCodeUrl"
+ fileMediatype="image"
+ limit="1"
+ mode="grid"
+ :imageStyles="{ width: '168rpx', height: '168rpx' }"
+ <!-- 持卡人姓名 -->
+ <view class="card-title" v-show="state.accountInfo.type === '4'">持卡人</view>
+ v-show="state.accountInfo.type === '4'"
+ v-model="state.accountInfo.name"
+ placeholder="请输入持卡人姓名"
+ <!-- 提现银行 -->
+ <view class="card-title" v-show="state.accountInfo.type === '4'">提现银行</view>
+ v-model="state.accountInfo.bankName"
+ placeholder="请输入提现银行"
+ <!-- 开户地址 -->
+ <view class="card-title" v-show="state.accountInfo.type === '4'">开户地址</view>
+ v-model="state.accountInfo.bankAddress"
+ placeholder="请输入开户地址"
+ <button class="ss-reset-button save-btn ui-BG-Main-Gradient ui-Shadow-Main" @tap="onConfirm">
+ 确认提现
+ <!-- 提现说明 -->
+ <view class="draw-notice">
+ <view class="title ss-m-b-30">提现说明</view>
+ <view class="draw-list"> 最低提现金额 {{ fen2yuan(state.minPrice) }} 元 </view>
+ <view class="draw-list">
+ 冻结佣金:<text>¥{{ fen2yuan(state.brokerageInfo.frozenPrice) }}</text>
+ (每笔佣金的冻结期为 {{ state.frozenDays }} 天,到期后可提现)
+ <!-- 选择提现账户 -->
+ <account-type-select
+ :show="state.accountSelect"
+ @close="onAccountSelect(false)"
+ v-model="state.accountInfo"
+ :methods="state.withdrawTypes"
+ import { computed, reactive, onBeforeMount } from 'vue';
+ import accountTypeSelect from './components/account-type-select.vue';
+ import TradeConfigApi from '@/sheep/api/trade/config';
+ const userStore = sheep.$store('user');
+ const userInfo = computed(() => userStore.userInfo);
+ accountInfo: {
+ // 提现表单
+ type: undefined,
+ accountNo: undefined,
+ accountQrCodeUrl: undefined,
+ name: undefined,
+ bankName: undefined,
+ bankAddress: undefined,
+ accountSelect: false,
+ brokerageInfo: {}, // 分销信息
+ frozenDays: 0, // 冻结天数
+ minPrice: 0, // 最低提现金额
+ withdrawTypes: [], // 提现方式
+ // 打开提现方式的弹窗
+ const onAccountSelect = (e) => {
+ state.accountSelect = e;
+ // 提交提现
+ // 参数校验
+ debugger;
+ !state.accountInfo.price ||
+ state.accountInfo.price > state.brokerageInfo.price ||
+ state.accountInfo.price <= 0
+ sheep.$helper.toast('请输入正确的提现金额');
+ if (!state.accountInfo.type) {
+ // 提交请求
+ let { code } = await BrokerageApi.createBrokerageWithdraw({
+ ...state.accountInfo,
+ price: state.accountInfo.price * 100,
+ // 提示
+ title: '操作成功',
+ content: '您的提现申请已成功提交',
+ cancelText: '继续提现',
+ confirmText: '查看记录',
+ success: (res) => {
+ if (res.confirm) {
+ sheep.$router.go('/pages/commission/wallet', { type: 2 })
+ getBrokerageUser();
+ state.accountInfo = {};
+ // 获得分销配置
+ async function getWithdrawRules() {
+ let { code, data } = await TradeConfigApi.getTradeConfig();
+ if (data) {
+ state.minPrice = data.brokerageWithdrawMinPrice || 0;
+ state.frozenDays = data.brokerageFrozenDays || 0;
+ state.withdrawTypes = data.brokerageWithdrawTypes;
+ // 获得分销信息
+ async function getBrokerageUser() {
+ const { data, code } = await BrokerageApi.getBrokerageUser();
+ state.brokerageInfo = data;
+ onBeforeMount(() => {
+ getWithdrawRules();
+ getBrokerageUser()
+ .uni-input-input {
+ font-family: OPPOSANS !important;
+ .wallet-num-box {
+ padding: 0 40rpx 80rpx;
+ background: var(--ui-BG-Main) v-bind(headerBg) center/750rpx 100% no-repeat;
+ .wallet-num {
+ font-size: 60rpx;
+ width: 170rpx;
+ border: 1rpx solid $white;
+ padding: 0;
+ // 提现输入卡片
+ .draw-card {
+ min-height: 560rpx;
+ margin: -60rpx 30rpx 30rpx 30rpx;
+ .card-title {
+ .bank-box {
+ .bank-list {
+ .empty-text {
+ width: 624rpx;
+ :deep(.uni-easyinput__content-input) {
+ width: 616rpx;
+ margin-top: 80rpx;
+ .bind-box {
+ .placeholder-text {
+ .add-btn {
+ background-color: var(--ui-BG-Main-light);
+ // 提现说明
+ .draw-notice {
+ width: 684rpx;
+ border: 2rpx solid #fffaee;
+ margin: 20rpx 32rpx 0 32rpx;
+ .draw-list {
+ line-height: 46rpx;
@@ -0,0 +1,378 @@
+<!-- 优惠券详情 -->
+ <s-layout title="优惠券详情">
+ <view class="bg-white">
+ <!-- 详情卡片 -->
+ <view class="detail-wrap ss-p-20">
+ <view class="detail-box">
+ <view class="tag-box ss-flex ss-col-center ss-row-center">
+ class="tag-image"
+ :src="sheep.$url.static('/static/img/shop/app/coupon_icon.png')"
+ mode="aspectFit"
+ <view class="top ss-flex-col ss-col-center">
+ <view class="title ss-m-t-50 ss-m-b-20 ss-m-x-20">{{ state.coupon.name }}</view>
+ <view class="subtitle ss-m-b-50">
+ 满 {{ fen2yuan(state.coupon.usePrice) }} 元,
+ {{ state.coupon.discountType === 1
+ ? '减 ' + fen2yuan(state.coupon.discountPrice) + ' 元'
+ : '打 ' + state.coupon.discountPercent / 10.0 + ' 折' }}
+ class="ss-reset-button ss-m-b-30"
+ :class="state.coupon.canTake || state.coupon.status === 1
+ ? 'use-btn' // 优惠劵模版(可领取)、优惠劵(可使用)
+ : 'disable-btn'
+ :disabled="!state.coupon.canTake"
+ @click="getCoupon"
+ <text v-if="state.id > 0">{{ state.coupon.canTake ? '立即领取' : '已领取' }}</text>
+ <text v-else>
+ {{ state.coupon.status === 1 ? '立即使用' : state.coupon.status === 2 ? '已使用' : '已过期' }}
+ <view class="time ss-m-y-30" v-if="state.coupon.validityType === 2">
+ 有效期:领取后 {{ state.coupon.fixedEndTerm }} 天内可用
+ <view class="time ss-m-y-30" v-else>
+ 有效期: {{ sheep.$helper.timeFormat(state.coupon.validStartTime, 'yyyy-mm-dd') }} 至
+ {{ sheep.$helper.timeFormat(state.coupon.validEndTime, 'yyyy-mm-dd') }}
+ <view class="coupon-line ss-m-t-14"></view>
+ <view class="bottom">
+ <view class="type ss-flex ss-col-center ss-row-between ss-p-x-30">
+ <view>优惠券类型</view>
+ <view>{{ state.coupon.discountType === 1 ? '满减券' : '折扣券' }}</view>
+ <!-- TODO 芋艿:可优化,增加优惠劵的描述 -->
+ <uni-collapse>
+ <uni-collapse-item title="优惠券说明" v-if="state.coupon.description">
+ <view class="content ss-p-b-20">
+ <text class="des ss-p-l-30">{{ state.coupon.description }}</text>
+ </uni-collapse-item>
+ </uni-collapse>
+ <!-- 适用商品 -->
+ class="all-user ss-flex ss-row-center ss-col-center"
+ v-if="state.coupon.productScope === 1"
+ 全场通用
+ <su-sticky v-else bgColor="#fff">
+ <view class="goods-title ss-p-20">
+ {{ state.coupon.productScope === 2 ? '指定商品可用' : '指定分类可用' }}
+ :scrollable="true"
+ :list="state.tabMaps"
+ v-if="state.coupon.productScope === 3"
+ <!-- 指定商品 -->
+ <view v-if="state.coupon.productScope === 2">
+ <view v-for="(item, index) in state.pagination.list" :key="index">
+ class="ss-m-20"
+ :goodsFields="{
+ title: { show: true },
+ subtitle: { show: true },
+ original_price: { show: true },
+ sales: { show: true },
+ stock: { show: false },
+ <!-- 指定分类 -->
+ <view v-if="state.coupon.productScope === 3">
+ ></s-goods-column>
+ v-if="state.pagination.total > 0 && state.coupon.productScope === 3"
+ v-if="state.coupon.productScope === 3 && state.pagination.total === 0"
+ paddingTop="0"
+ icon="/static/soldout-empty.png"
+ text="暂无商品"
+ import CouponApi from '@/sheep/api/promotion/coupon';
+ import CategoryApi from '@/sheep/api/product/category';
+ id: 0, // 优惠劵模版编号 templateId
+ couponId: 0, // 用户优惠劵编号 couponId
+ coupon: {}, // 优惠劵信息
+ categoryId: 0, // 选中的商品分类编号
+ tabMaps: [], // 指定分类时,每个分类构成一个 tab
+ currentTab: 0, // 选中的 tabMaps 下标
+ state.categoryId = e.value;
+ getGoodsListByCategory();
+ async function getGoodsListByCategory() {
+ categoryId: state.categoryId,
+ // 获得商品列表,指定商品范围
+ async function getGoodsListById() {
+ const { data, code } = await SpuApi.getSpuListByIds(state.coupon.productScopeValues.join(','));
+ state.pagination.list = data;
+ // 获得分类列表
+ async function getCategoryList() {
+ const { data, code } = await CategoryApi.getCategoryListByIds(state.coupon.productScopeValues.join(','));
+ state.tabMaps = data.map((category) => ({ name: category.name, value: category.id }));
+ // 加载第一个分类的商品列表
+ if (state.tabMaps.length > 0) {
+ state.categoryId = state.tabMaps[0].value;
+ await getGoodsListByCategory();
+ // 领取优惠劵
+ async function getCoupon() {
+ const { code } = await CouponApi.takeCoupon(state.id);
+ uni.showToast({
+ title: '领取成功',
+ getCouponContent();
+ }, 1000);
+ // 加载优惠劵信息
+ async function getCouponContent() {
+ const { code, data } = state.id > 0 ? await CouponApi.getCouponTemplate(state.id)
+ : await CouponApi.getCoupon(state.couponId);
+ state.coupon = data;
+ // 不同指定范围,加载不同数据
+ if (state.coupon.productScope === 2) {
+ await getGoodsListById();
+ } else if (state.coupon.productScope === 3) {
+ await getCategoryList();
+ state.id = options.id;
+ state.couponId = options.couponId;
+ getCouponContent(state.id, state.couponId);
+ .goods-title {
+ font-size: 34rpx;
+ .detail-wrap {
+ background: linear-gradient(
+ 180deg,
+ var(--ui-BG-Main),
+ var(--ui-BG-Main-gradient),
+ #fff
+ .detail-box {
+ // background-color: var(--ui-BG);
+ margin-top: 100rpx;
+ background: var(--ui-BG);
+ top: -70rpx;
+ z-index: 6;
+ .tag-image {
+ width: 104rpx;
+ height: 104rpx;
+ -webkit-mask: radial-gradient(circle at 16rpx 100%, #0000 16rpx, red 0) -16rpx;
+ padding: 110rpx 0 0 0;
+ z-index: 5;
+ .use-btn {
+ width: 386rpx;
+ line-height: 80rpx;
+ .disable-btn {
+ background: #e5e5e5;
+ .coupon-line {
+ width: 95%;
+ border-bottom: 2rpx solid #eeeeee;
+ border-radius: 0 0 20rpx 20rpx;
+ -webkit-mask: radial-gradient(circle at 16rpx 0%, #0000 16rpx, red 0) -16rpx;
+ padding: 40rpx 30rpx;
+ .type {
+ .des {
+ .all-user {
+ height: 300rpx;
@@ -0,0 +1,218 @@
+<!-- 优惠券中心 -->
+ <s-layout title="优惠券" :bgStyle="{ color: '#f2f2f2' }">
+ icon="/static/coupon-empty.png"
+ text="暂无优惠券"
+ <!-- 情况一:领劵中心 -->
+ <template v-if="state.currentTab === 0">
+ <view v-for="item in state.pagination.list" :key="item.id">
+ <s-coupon-list
+ @tap="sheep.$router.go('/pages/coupon/detail', { id: item.id })"
+ <template #default>
+ class="ss-reset-button card-btn ss-flex ss-row-center ss-col-center"
+ :class="!item.canTake ? 'border-btn' : ''"
+ @click.stop="getBuy(item.id)"
+ :disabled="!item.canTake"
+ {{ item.canTake ? '立即领取' : '已领取' }}
+ </s-coupon-list>
+ <!-- 情况二:我的优惠劵 -->
+ type="user"
+ @tap="sheep.$router.go('/pages/coupon/detail', { couponId: item.id })"
+ :class=" item.status !== 1 ? 'disabled-btn': ''"
+ :disabled="item.status !== 1"
+ @click.stop="sheep.$router.go('/pages/coupon/detail', { couponId: item.id })"
+ {{ item.status === 1 ? '立即使用' : item.status === 2 ? '已使用' : '已过期' }}
+ }" @tap="loadMore" />
+ currentTab: 0, // 当前 tab
+ type: '1',
+ pageSize: 5
+ name: '领券中心',
+ name: '已领取',
+ name: '已使用',
+ name: '已失效',
+ // TODO yunai:
+ state.type = e.value;
+ resetPagination(state.pagination)
+ getData();
+ getCoupon();
+ // 获得优惠劵模版列表
+ async function getData() {
+ const { data, code } = await CouponApi.getCouponTemplatePage({
+ // 获得我的优惠劵
+ const { data, code } = await CouponApi.getCouponPage({
+ status: state.type
+ async function getBuy(id) {
+ const { code } = await CouponApi.takeCoupon(id);
+ onLoad((Option) => {
+ // 领劵中心
+ if (Option.type === 'all' || !Option.type) {
+ // 我的优惠劵
+ Option.type === 'geted'
+ ? (state.currentTab = 1)
+ : Option.type === 'used'
+ ? (state.currentTab = 2)
+ : (state.currentTab = 3);
+ state.type = state.currentTab;
+ .card-btn {
+ // width: 144rpx;
+ padding: 0 16rpx;
+ .border-btn {
+ background: linear-gradient(90deg, var(--ui-BG-Main-opacity-4), var(--ui-BG-Main-light));
+ color: #fff !important;
+ background: #cccccc;
+ background-color: #cccccc !important;
@@ -0,0 +1,145 @@
+<!-- 评价 -->
+ <s-layout title="评价">
+ <view v-for="(item, index) in state.orderInfo.items" :key="item.id">
+ <view class="commont-from-wrap">
+ <!-- 评价商品 -->
+ :title="item.spuName"
+ :skuText="item.properties.map((property) => property.valueName).join(' ')"
+ :price="item.payPrice"
+ :num="item.count"
+ <view class="form-item">
+ <!-- 评分 -->
+ <view class="star-box ss-flex ss-col-center">
+ <view class="star-title ss-m-r-40">商品质量</view>
+ <uni-rate v-model="state.commentList[index].descriptionScores" />
+ <view class="star-title ss-m-r-40">服务态度</view>
+ <uni-rate v-model="state.commentList[index].benefitScores" />
+ <!-- 评价 -->
+ <view class="area-box">
+ <uni-easyinput :inputBorder="false" type="textarea" maxlength="120" autoHeight
+ v-model="state.commentList[index].content"
+ placeholder="宝贝满足你的期待吗?说说你的使用心得,分享给想买的他们吧~" />
+ <!-- TODO 芋艿:文件上传 -->
+ <view class="img-box">
+ <s-uploader v-model:url="state.commentList[index].images" fileMediatype="image"
+ limit="9" mode="grid" :imageStyles="{ width: '168rpx', height: '168rpx' }" />
+ <!-- TODO 芋艿:是否匿名 -->
+ <su-fixed bottom placeholder>
+ <view class="foot_box ss-flex ss-row-center ss-col-center">
+ <button class="ss-reset-button post-btn ui-BG-Main-Gradient ui-Shadow-Main" @tap="onSubmit">
+ 发布
+ orderInfo: {},
+ commentList: [],
+ id: null
+ async function onSubmit() {
+ // 顺序提交评论
+ for (const comment of state.commentList) {
+ await OrderApi.createOrderItemComment(comment);
+ // 都评论好,返回
+ sheep.$router.back();
+ if (!options.id) {
+ sheep.$helper.toast(`缺少订单信息,请检查`);
+ return
+ const { code, data } = await OrderApi.getOrder(state.id);
+ sheep.$helper.toast('无待评价订单');
+ // 处理评论
+ data.items.forEach((item) => {
+ state.commentList.push({
+ anonymous: false,
+ orderItemId: item.id,
+ descriptionScores: 5,
+ benefitScores: 5,
+ content: '',
+ picUrls: []
+ state.orderInfo = data;
+ // 评价商品
+ .goods-card {
+ margin: 10rpx 0;
+ // 评论,选择图片
+ .form-item {
+ .star-box {
+ padding: 0 25rpx;
+ .star-title {
+ .area-box {
+ min-height: 306rpx;
+ background: rgba(249, 250, 251, 1);
+ padding: 28rpx;
+ margin: auto;
+ .img-box {
+ .post-btn {
+ color: rgba(#fff, 0.9);
@@ -0,0 +1,167 @@
+<!-- 商品评论的分页 -->
+ <s-layout title="全部评价">
+ :list="state.type"
+ <!-- 评论列表 -->
+ <view class="ss-m-t-20">
+ <view class="list-item" v-for="item in state.pagination.list" :key="item">
+ <comment-item :item="item" />
+ <s-empty v-if="state.pagination.total === 0" text="暂无数据" icon="/static/data-empty.png" />
+ <!-- 下拉 -->
+ import CommentApi from '@/sheep/api/product/comment';
+ import commentItem from '../components/detail/comment-item.vue';
+ id: 0, // 商品 SPU 编号
+ type: [
+ { type: 0, name: '全部' },
+ { type: 1, name: '好评' },
+ { type: 2, name: '中评' },
+ { type: 3, name: '差评' },
+ currentTab: 0, // 选中的 TAB
+ // 加载列表
+ state.pagination.pageNo = 1;
+ state.pagination.total = 0;
+ let res = await CommentApi.getCommentPage(
+ state.id,
+ state.pagination.pageNo,
+ state.pagination.pageSize,
+ state.type[state.currentTab].type,
+ if (res.code !== 0) {
+ // 合并列表
+ state.pagination.list = _.concat(state.pagination.list, res.data.list);
+ state.pagination.total = res.data.total;
+ .list-item {
+ padding: 32rpx 30rpx 20rpx 20rpx;
+ width: 52rpx;
+ height: 52rpx;
+ .nickname {
+ .create-time {
+ .content-title {
+ .content-img {
+ width: 174rpx;
+ height: 174rpx;
+ .cicon-info-o {
+ .foot-title {
+ .btn-box {
+ border-top: 2rpx solid #eee;
+ .tab-btn {
+ height: 62rpx;
+ background: #eeeeee;
+ border-radius: 31rpx;
+ border: 1px solid #e5e5e5;
@@ -0,0 +1,94 @@
+<!-- 商品评论项 -->
+ <!-- 用户评论 -->
+ <view class="user ss-flex ss-m-b-14">
+ <view class="ss-m-r-20 ss-flex">
+ <image class="avatar" :src="item.userAvatar"></image>
+ <view class="nickname ss-m-r-20">{{ item.userNickname }}</view>
+ <uni-rate :readonly="true" v-model="item.scores" size="18" />
+ <view class="content"> {{ item.content }} </view>
+ <view class="ss-m-t-24" v-if="item.picUrls?.length">
+ <scroll-view class="scroll-box" scroll-x scroll-anchoring>
+ <view v-for="(picUrl, index) in item.picUrls" :key="picUrl" class="ss-m-r-10">
+ class="content-img"
+ :previewList="item.picUrls"
+ :current="index"
+ :src="picUrl"
+ :height="120"
+ :width="120"
+ <!-- 商家回复 -->
+ <view class="ss-m-t-20 reply-box" v-if="item.replyTime">
+ <view class="reply-title">商家回复:</view>
+ <view class="reply-content">{{ item.replyContent }}</view>
+ width: 636rpx;
+ .reply-box {
+ background: #f8f8f8;
+ border-radius: 8rpx;
+ padding: 16rpx;
+ .reply-title {
+ left: 16rpx;
+ top: 16rpx;
+ .reply-content {
+ text-indent: 128rpx;
@@ -0,0 +1,100 @@
+ <su-fixed bottom placeholder :val="44">
+ <view v-for="activity in props.activityList" :key="activity.id">
+ <!-- TODO 芋艿:拼团 -->
+ class="activity-box ss-p-x-38 ss-flex ss-row-between ss-col-center"
+ :class="activity.type === 1 ? 'seckill-box' : 'groupon-box'"
+ <view class="activity-title ss-flex">
+ <view class="ss-m-r-16">
+ v-if="activity.type === 1"
+ :src="sheep.$url.static('/static/img/shop/goods/seckill-icon.png')"
+ class="activity-icon"
+ v-else-if="activity.type === 3"
+ :src="sheep.$url.static('/static/img/shop/goods/groupon-icon.png')"
+ <view>该商品正在参与{{ activity.name }}活动</view>
+ <button class="ss-reset-button activity-go" @tap="onActivity(activity)"> GO </button>
+ // TODO 芋艿:这里要迁移下;
+ const seckillBg = sheep.$url.css('/static/img/shop/goods/seckill-tip-bg.png');
+ const grouponBg = sheep.$url.css('/static/img/shop/goods/groupon-tip-bg.png');
+ activityList: {
+ default() {
+ return [];
+ function onActivity(activity) {
+ const type = activity.type;
+ const typePath = type === 1 ? 'seckill' :
+ type === 2 ? 'TODO 拼团' : 'groupon';
+ sheep.$router.go(`/pages/goods/${typePath}`, {
+ id: activity.id,
+ .activity-box {
+ .activity-title {
+ .activity-icon {
+ .activity-go {
+ //秒杀卡片
+ .seckill-box {
+ background: v-bind(seckillBg) no-repeat;
+ .groupon-box {
+ background: v-bind(grouponBg) no-repeat;
@@ -0,0 +1,31 @@
+ <!-- SKU 选择的提示框 -->
+ <detail-cell label="选择" :value="value" />
+ import { computed } from 'vue';
+ import detailCell from './detail-cell.vue';
+ sku: {
+ type: Object
+ const value = computed(() => {
+ if (!props.sku?.id) {
+ return '请选择商品规格';
+ let str = '';
+ props.sku.properties.forEach(property => {
+ str += property.propertyName + ':' + property.valueName + ' ';
+ return str;
@@ -0,0 +1,60 @@
+<!-- 商品详情:cell 组件 -->
+ <view class="detail-cell-wrap ss-flex ss-col-center ss-row-between" @tap="onClick">
+ <view class="label-text">{{ label }}</view>
+ <view class="cell-content ss-line-1 ss-flex-1">{{ value }}</view>
+ <button class="ss-reset-button">
+ <text class="_icon-forward right-forwrad-icon"></text>
+ * 详情 cell
+ *
+ label: {
+ default: '',
+ value: {
+ const emits = defineEmits(['click']);
+ // 点击
+ const onClick = () => {
+ emits('click');
+ margin-right: 38rpx;
@@ -0,0 +1,106 @@
+<!-- 商品评论的卡片 -->
+ <view class="detail-comment-card bg-white">
+ <view class="card-header ss-flex ss-col-center ss-row-between ss-p-b-30">
+ <view class="line"></view>
+ <view class="title ss-m-l-20 ss-m-r-10">评价</view>
+ <view class="des">({{ state.total }})</view>
+ class="ss-flex ss-col-center"
+ @tap="sheep.$router.go('/pages/goods/comment/list', { id: goodsId })"
+ v-if="state.commentList.length > 0"
+ <button class="ss-reset-button more-btn">查看全部</button>
+ <view class="card-content">
+ <view class="comment-box ss-p-y-30" v-for="item in state.commentList" :key="item.id">
+ v-if="state.commentList.length === 0"
+ icon="/static/comment-empty.png"
+ text="期待您的第一个评价"
+ import { reactive, onBeforeMount } from 'vue';
+ import commentItem from './comment-item.vue';
+ goodsId: {
+ type: [Number, String],
+ default: 0,
+ commentList: [], // 评论列表,只展示最近的 3 条
+ total: 0, // 总评论数
+ async function getComment(id) {
+ const { data } = await CommentApi.getCommentPage(id, 1, 3, 0);
+ state.commentList = data.list;
+ state.total = data.total;
+ getComment(props.goodsId);
+ .detail-comment-card {
+ margin: 0 20rpx 20rpx 20rpx;
+ padding: 20rpx 20rpx 0 20rpx;
+ .card-header {
+ .line {
+ width: 6rpx;
+ background: linear-gradient(180deg, var(--ui-BG-Main) 0%, var(--ui-BG-Main-gradient) 100%);
+ border-radius: 3rpx;
+ .more-btn {
+ margin-top: 4rpx;
+ .comment-box {
+ &:last-child {
+ border: none;
@@ -0,0 +1,52 @@
+<!-- 商品详情:描述卡片 -->
+ <view class="detail-content-card bg-white ss-m-x-20 ss-p-t-20">
+ <view class="card-header ss-flex ss-col-center ss-m-b-30 ss-m-l-20">
+ <view class="title ss-m-l-20 ss-m-r-20">详情</view>
+ <mp-html :content="content" />
+ const { safeAreaInsets } = sheep.$platform.device;
+ .detail-content-card {
@@ -0,0 +1,256 @@
+<!-- 商品详情:商品/评价/详情的 nav -->
+ <su-fixed alway :bgStyles="{ background: '#fff' }" :val="0" noNav opacity :placeholder="false">
+ <su-status-bar />
+ class="ui-bar ss-flex ss-col-center ss-row-between ss-p-x-20"
+ :style="[{ height: sys_navBar - sys_statusBar + 'px' }]"
+ <!-- 左 -->
+ <view class="icon-box ss-flex">
+ <view class="icon-button icon-button-left ss-flex ss-row-center" @tap="onClickLeft">
+ <text class="sicon-back" v-if="hasHistory" />
+ <text class="sicon-home" v-else />
+ <view class="icon-button icon-button-right ss-flex ss-row-center" @tap="onClickRight">
+ <text class="sicon-more" />
+ <!-- 中 -->
+ <view class="detail-tab-card ss-flex-1" :style="[{ opacity: state.tabOpacityVal }]">
+ <view class="tab-box ss-flex ss-col-center ss-row-around">
+ class="tab-item ss-flex-1 ss-flex ss-row-center ss-col-center"
+ v-for="item in state.tabList"
+ :key="item.value"
+ @tap="onTab(item)"
+ <view class="tab-title" :class="state.curTab === item.value ? 'cur-tab-title' : ''">
+ {{ item.label }}
+ <view v-show="state.curTab === item.value" class="tab-line"></view>
+ <!-- #ifdef MP -->
+ <view :style="[capsuleStyle]"></view>
+ <!-- #endif -->
+ import throttle from '@/sheep/helper/throttle.js';
+ import { showMenuTools, closeMenuTools } from '@/sheep/hooks/useModal';
+ const sys_statusBar = sheep.$platform.device.statusBarHeight;
+ const capsuleStyle = {
+ width: sheep.$platform.capsule.width + 'px',
+ height: sheep.$platform.capsule.height + 'px',
+ tabOpacityVal: 0,
+ curTab: 'goods',
+ tabList: [
+ label: '商品',
+ value: 'goods',
+ to: 'detail-swiper-selector',
+ label: '评价',
+ value: 'comment',
+ to: 'detail-comment-selector',
+ label: '详情',
+ value: 'detail',
+ to: 'detail-content-selector',
+ const emits = defineEmits(['clickLeft']);
+ const hasHistory = sheep.$router.hasHistory();
+ function onClickLeft() {
+ if (hasHistory) {
+ sheep.$router.go('/pages/index/index');
+ emits('clickLeft');
+ function onClickRight() {
+ showMenuTools();
+ let commentCard = {
+ top: 0,
+ bottom: 0,
+ function getCommentCardNode() {
+ return new Promise((res, rej) => {
+ uni.createSelectorQuery()
+ .select('.detail-comment-selector')
+ .boundingClientRect((data) => {
+ commentCard.top = data.top;
+ commentCard.bottom = data.top + data.height;
+ res(data);
+ res(null);
+ .exec();
+ function onTab(tab) {
+ let scrollTop = 0;
+ if (tab.value === 'comment') {
+ scrollTop = commentCard.top - sys_navBar + 1;
+ } else if (tab.value === 'detail') {
+ scrollTop = commentCard.bottom - sys_navBar + 1;
+ uni.pageScrollTo({
+ scrollTop,
+ duration: 200,
+ state.tabOpacityVal = e.scrollTop > sheep.$platform.navbar ? 1 : e.scrollTop * 0.01;
+ if (commentCard.top === 0) {
+ throttle(() => {
+ getCommentCardNode();
+ }, 50);
+ if (e.scrollTop < commentCard.top - sys_navBar) {
+ state.curTab = 'goods';
+ } else if (
+ e.scrollTop >= commentCard.top - sys_navBar &&
+ e.scrollTop <= commentCard.bottom - sys_navBar
+ state.curTab = 'comment';
+ state.curTab = 'detail';
+ .icon-box {
+ box-shadow: 0px 0px 4rpx rgba(51, 51, 51, 0.08), 0px 4rpx 6rpx 2rpx rgba(102, 102, 102, 0.12);
+ width: 134rpx;
+ height: 56rpx;
+ margin-left: 8rpx;
+ border: 1px solid rgba(#fff, 0.4);
+ width: 2rpx;
+ background: #e5e5e7;
+ .sicon-back {
+ color: #000;
+ .sicon-home {
+ .sicon-more {
+ .icon-button {
+ width: 67rpx;
+ &-left:hover {
+ background: rgba(0, 0, 0, 0.16);
+ border-radius: 30rpx 0px 0px 30rpx;
+ &-right:hover {
+ border-radius: 0px 30rpx 30rpx 0px;
+ .left-box {
+ .circle {
+ background: rgba(#fff, 0.6);
+ border: 1rpx solid #ebebeb;
+ z-index: -1;
+ .right {
+ background: rgba(#ffffff, 0.6);
+ .detail-tab-card {
+ z-index: 12;
@@ -0,0 +1,40 @@
+<!-- 秒杀商品:抢购进度 -->
+ <view class="progress-title ss-m-r-10"> 已抢{{ percent }}% </view>
+ <view class="progress-box ss-flex ss-col-center">
+ <view class="progerss-active" :style="{ width: percent < 10 ? '10%' : percent + '%' }">
+ percent: {
+ type: Number,
+ .progress-title {
+ .progress-box {
+ width: 168rpx;
+ height: 18rpx;
+ border-radius: 9rpx;
+ .progerss-active {
+ background: linear-gradient(86deg, #f60600, #d00500);
@@ -0,0 +1,177 @@
+ class="skeleton-wrap"
+ :class="['theme-' + sys.mode, 'main-' + sys.theme, 'font-' + sys.fontSize]"
+ <view class="skeleton-banner"></view>
+ <view class="container-box">
+ <view class="container-box-strip title ss-m-b-58"></view>
+ <view class="container-box-strip ss-m-b-20"></view>
+ <view class="container-box-strip w-364"></view>
+ <view class="ss-flex ss-row-between ss-m-b-34">
+ <view class="container-box-strip w-380"></view>
+ <view class="circle"></view>
+ <view class="container-box-strip w-556"></view>
+ <view class="container-box-strip w-198 ss-m-b-42"></view>
+ <view class="circle ss-m-r-12"></view>
+ <view class="container-box-strip w-252"></view>
+ <su-fixed bottom placeholder bg="bg-white">
+ <view class="ui-tabbar-box">
+ <view class="foot ss-flex ss-col-center">
+ <view class="ss-m-r-54 ss-m-l-32">
+ <view class="rec ss-m-b-8"></view>
+ <view class="oval"></view>
+ <view class="ss-m-r-54">
+ <view class="ss-m-r-50">
+ <button class="ss-reset-button add-btn ui-Shadow-Main"></button>
+ <button class="ss-reset-button buy-btn ui-Shadow-Main"></button>
+ const sys = computed(() => sheep.$store('sys'));
+ @keyframes loading {
+ opacity: 0.5;
+ 50% {
+ .skeleton-wrap {
+ height: 100vh;
+ .skeleton-banner {
+ height: calc(100vh - 882rpx);
+ background: #f4f4f4;
+ .container-box {
+ padding: 24rpx 18rpx;
+ margin: 14rpx 20rpx;
+ animation: loading 1.4s ease infinite;
+ .container-box-strip {
+ background: #f3f3f1;
+ width: 470rpx;
+ .w-364 {
+ width: 364rpx;
+ .w-380 {
+ width: 380rpx;
+ .w-556 {
+ width: 556rpx;
+ .w-198 {
+ width: 198rpx;
+ .w-252 {
+ width: 252rpx;
+ .ui-tabbar-box {
+ box-shadow: 0px -6px 10px 0px rgba(51, 51, 51, 0.2);
+ .foot {
+ .rec {
+ .oval {
+ height: 22rpx;
+ border-radius: 11rpx;
+ width: 214rpx;
+ border-radius: 40rpx 0 0 40rpx;
+ .buy-btn {
+ border-radius: 0 40rpx 40rpx 0;
@@ -0,0 +1,169 @@
+<!-- 商品详情的底部导航 -->
+ <view class="ui-tabbar ss-flex ss-col-center ss-row-between">
+ v-if="collectIcon"
+ class="detail-tabbar-item ss-flex ss-flex-col ss-row-center ss-col-center"
+ @tap="onFavorite"
+ <block v-if="modelValue.favorite">
+ class="item-icon"
+ :src="sheep.$url.static('/static/img/shop/goods/collect_1.gif')"
+ <view class="item-title">已收藏</view>
+ <block v-else>
+ :src="sheep.$url.static('/static/img/shop/goods/collect_0.png')"
+ <view class="item-title">收藏</view>
+ v-if="serviceIcon"
+ @tap="onChat"
+ :src="sheep.$url.static('/static/img/shop/goods/message.png')"
+ <view class="item-title">客服</view>
+ v-if="shareIcon"
+ @tap="showShareModal"
+ :src="sheep.$url.static('/static/img/shop/goods/share.png')"
+ <view class="item-title">分享</view>
+ <slot></slot>
+ * 底部导航
+ * @property {String} bg - 背景颜色Class
+ * @property {String} ui - 自定义样式Class
+ * @property {Boolean} noFixed - 是否定位
+ * @property {Boolean} topRadius - 上圆角
+ import FavoriteApi from '@/sheep/api/product/favorite';
+ // 接收参数
+ bg: {
+ default: 'bg-white',
+ bgStyles: {
+ ui: {
+ noFixed: {
+ topRadius: {
+ collectIcon: {
+ default: true,
+ serviceIcon: {
+ shareIcon: {
+ async function onFavorite() {
+ // 情况一:取消收藏
+ if (props.modelValue.favorite) {
+ const { code } = await FavoriteApi.deleteFavorite(props.modelValue.id);
+ sheep.$helper.toast('取消收藏');
+ props.modelValue.favorite = false;
+ // 情况二:添加收藏
+ const { code } = await FavoriteApi.createFavorite(props.modelValue.id);
+ sheep.$helper.toast('收藏成功');
+ props.modelValue.favorite = true;
+ const onChat = () => {
+ sheep.$router.go('/pages/chat/index', {
+ id: props.modelValue.id,
+ .ui-tabbar {
+ height: 50px;
+ .detail-tabbar-item {
+ .item-icon {
+ margin-top: 12rpx;
@@ -0,0 +1,141 @@
+<!-- 拼团活动参团记录卡片 -->
+ <view v-if="state.list.length > 0" class="groupon-list detail-card ss-p-x-20">
+ <view class="join-activity ss-flex ss-row-between ss-m-t-30">
+ <!-- todo: 接口缺少总数 -->
+ <view class="">已有{{ state.list.length }}人参与活动</view>
+ v-for="(record, index) in state.list"
+ class="ss-m-t-40 ss-flex ss-row-between border-bottom ss-p-b-30"
+ <image :src="sheep.$url.cdn(record.avatar)" class="user-avatar"></image>
+ <view class="user-nickname ss-m-l-20 ss-line-1">{{ record.nickname }}</view>
+ <view class="ss-flex-col ss-col-bottom ss-m-r-20">
+ <view class="title ss-flex ss-m-b-14">
+ <view class="num">{{ record.userSize - record.userCount }}人</view>
+ 成团
+ <view class="end-time">{{ endTime(record.expireTime) }}</view>
+ <button class="ss-reset-button go-btn" @tap.stop="onJoinGroupon(record)"> 去参团 </button>
+ import { onMounted, reactive } from 'vue';
+ // 去参团
+ const emits = defineEmits(['join']);
+ function onJoinGroupon(record) {
+ emits('join', record);
+ // 结束时间或状态
+ function endTime(time) {
+ const durationTime = useDurationTime(time);
+ if (durationTime.ms <= 0) {
+ return '该团已解散';
+ let timeText = '剩余 ';
+ timeText += `${durationTime.h}时`;
+ timeText += `${durationTime.m}分`;
+ timeText += `${durationTime.s}秒`;
+ return timeText;
+ // 初始化
+ // 查询参团记录
+ // status = 0 表示未成团
+ const { data } = await CombinationApi.getHeadCombinationRecordList(props.modelValue.id, 0 , 10);
+ state.list = data;
+ .detail-card {
+ .groupon-list {
+ .join-activity {
+ border-radius: 60rpx;
+ .user-nickname {
+ width: 160rpx;
+ .end-time {
+ .go-btn {
@@ -0,0 +1,103 @@
+<!-- 页面;暂时没用到 -->
+ <view class="list-goods-card ss-flex-col" @tap="onClick">
+ <view class="md-img-box">
+ <image class="goods-img md-img-box" :src="sheep.$url.cdn(img)" mode="aspectFill"></image>
+ <view class="md-goods-content ss-flex-col ss-row-around">
+ <view class="md-goods-title ss-line-2 ss-m-x-20 ss-m-t-6 ss-m-b-16">{{ title }}</view>
+ <view class="md-goods-subtitle ss-line-1 ss-p-y-10 ss-p-20">{{ subTitle }}</view>
+ <view class="ss-flex ss-col-center ss-row-between ss-m-b-16 ss-m-x-20">
+ <view class="md-goods-price text-price">{{ price }}</view>
+ <view class="goods-origin-price text-price">{{ originPrice }}</view>
+ <view class="sales-text">已售{{ sales }}件</view>
+ img: {
+ subTitle: {
+ title: {
+ price: {
+ type: [String, Number],
+ originPrice: {
+ sales: {
+ .goods-img {
+ .sales-text {
+ .goods-origin-price {
+ text-decoration: line-through;
+ .list-goods-card {
+ width: 344rpx;
+ box-shadow: 0 0 20rpx 4rpx rgba(199, 199, 199, 0.22);
+ .md-img-box {
+ height: 344rpx;
+ .md-goods-title {
+ .md-goods-subtitle {
+ background-color: var(--ui-BG-Main-tag);
+ .md-goods-price {
@@ -0,0 +1,93 @@
+ <su-fixed
+ alway
+ :bgStyles="{ background: '#fff' }"
+ :val="0"
+ noNav
+ :opacity="false"
+ placeholder
+ index="10090"
+ <view class="left-box">
+ class="_icon-back back-icon"
+ @tap="toBack"
+ :style="[{ color: state.iconColor }]"
+ <uni-search-bar
+ class="ss-flex-1"
+ radius="33"
+ :placeholder="placeholder"
+ cancelButton="none"
+ :focus="true"
+ v-model="state.searchVal"
+ @confirm="onSearch"
+ <!-- 右 -->
+ <view class="right">
+ <text class="sicon-more" :style="[{ color: state.iconColor }]" @tap="showMenuTools" />
+ import { showMenuTools } from '@/sheep/hooks/useModal';
+ margin: '0 ' + (sheep.$platform.device.windowWidth - sheep.$platform.capsule.right) + 'px',
+ iconColor: '#000',
+ searchVal: '',
+ placeholder: {
+ default: '搜索内容',
+ const emits = defineEmits(['searchConfirm']);
+ // 返回
+ const toBack = () => {
+ // 搜索
+ const onSearch = () => {
+ emits('searchConfirm', state.searchVal);
+ const onTab = (item) => {};
+ .back-icon {
@@ -0,0 +1,532 @@
+<!-- 拼团商品详情 -->
+ <s-layout :onShareAppMessage="shareInfo" navbar="goods">
+ <!-- 标题栏 -->
+ <detailNavbar />
+ <!-- 骨架屏 -->
+ <detailSkeleton v-if="state.skeletonLoading" />
+ <!-- 下架/售罄提醒 -->
+ v-else-if="state.goodsInfo === null || state.activity.status !== 0 || state.activity.endTime < new Date().getTime()"
+ text="活动不存在或已结束"
+ showAction
+ actionText="返回上一页"
+ @clickAction="sheep.$router.back()"
+ <view class="detail-swiper-selector">
+ <!-- 商品图轮播 -->
+ <su-swiper
+ class="ss-m-b-14"
+ :list="state.goodsSwiper"
+ dotStyle="tag"
+ imageMode="widthFix"
+ dotCur="bg-mask-40"
+ :seizeHeight="750"
+ <!-- 价格+标题 -->
+ <view class="title-card detail-card ss-m-y-14 ss-m-x-20 ss-p-x-20 ss-p-y-34">
+ <view class="ss-flex ss-row-between ss-m-b-60">
+ <view class="price-box ss-flex ss-col-bottom ss-m-b-18">
+ <view class="price-text ss-m-r-16">
+ {{ fen2yuan(state.activity.price || state.goodsInfo.price) }}
+ <view class="tig ss-flex ss-col-center">
+ <view class="tig-icon ss-flex ss-col-center ss-row-center">
+ <view class="groupon-tag">
+ :src="sheep.$url.static('/static/img/shop/goods/groupon-tag.png')"
+ <view class="tig-title">拼团价</view>
+ class="origin-price ss-flex ss-col-center"
+ v-if="state.goodsInfo.price"
+ 单买价:
+ <view class="origin-price-text">
+ {{ fen2yuan(state.goodsInfo.price) }}
+ <view class="countdown-box" v-if="endTime.ms > 0">
+ <view class="countdown-title ss-m-b-20">距结束仅剩</view>
+ <view class="ss-flex countdown-h">{{ endTime.h }}</view>
+ <view class="countdown-num ss-flex ss-row-center">{{ endTime.m }}</view>
+ <view class="countdown-num ss-flex ss-row-center">{{ endTime.s }}</view>
+ <view class="countdown-title" v-else> 活动已结束 </view>
+ <view class="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo.name }}</view>
+ <view class="subtitle-text ss-line-1">{{ state.goodsInfo.introduction }}</view>
+ <!-- 功能卡片 -->
+ <view class="detail-cell-card detail-card ss-flex-col">
+ <!-- 规格 -->
+ <detail-cell-sku :sku="state.selectedSkuPrice" @tap="state.showSelectSku = true" />
+ <!-- 参团列表 -->
+ <groupon-card-list v-model="state.activity" @join="onJoinGroupon" />
+ <!-- 规格与数量弹框 -->
+ :goodsInfo="state.goodsInfo"
+ @close="onSkuClose"
+ <detail-comment-card class="detail-comment-selector" :goodsId="state.goodsId" />
+ <!-- 详情 -->
+ <detail-content-card class="detail-content-selector" :content="state.goodsInfo.description" />
+ <!-- 商品tabbar -->
+ <!-- TODO: 已售罄、预热 判断 设计-->
+ <detail-tabbar v-model="state.goodsInfo">
+ <view class="buy-box ss-flex ss-col-center ss-p-r-20">
+ class="ss-reset-button origin-price-btn ss-flex-col"
+ @tap="sheep.$router.go('/pages/goods/index', { id: state.goodsInfo.id })"
+ <view class="btn-price">{{ fen2yuan(state.goodsInfo.marketPrice) }}</view>
+ <view>原价购买</view>
+ class="ss-reset-button btn-tox ss-flex-col"
+ @tap="onCreateGroupon"
+ :class="
+ state.activity.status === 0 && state.goodsInfo.stock !== 0
+ ? 'check-btn-box'
+ : 'disabled-btn-box'
+ :disabled="state.goodsInfo.stock === 0 || state.activity.status !== 0"
+ <view class="btn-price">{{ fen2yuan(state.activity.price || state.goodsInfo.price) }}</view>
+ <view v-if="state.activity.startTime > new Date().getTime()">未开始</view>
+ <view v-else-if="state.activity.endTime <= new Date().getTime()">已结束</view>
+ <view v-if="state.goodsInfo.stock === 0">已售罄</view>
+ <view v-else>立即开团</view>
+ </detail-tabbar>
+ import { reactive, computed } from 'vue';
+ import { onLoad, onPageScroll } from '@dcloudio/uni-app';
+ import detailNavbar from './components/detail/detail-navbar.vue';
+ import detailCellSku from './components/detail/detail-cell-sku.vue';
+ import detailTabbar from './components/detail/detail-tabbar.vue';
+ import detailSkeleton from './components/detail/detail-skeleton.vue';
+ import detailCommentCard from './components/detail/detail-comment-card.vue';
+ import detailContentCard from './components/detail/detail-content-card.vue';
+ import grouponCardList from './components/groupon/groupon-card-list.vue';
+ import {useDurationTime, formatGoodsSwiper, fen2yuan} from '@/sheep/hooks/useGoods';
+ import SpuApi from "@/sheep/api/product/spu";
+ const headerBg = sheep.$url.css('/static/img/shop/goods/groupon-bg.png');
+ const btnBg = sheep.$url.css('/static/img/shop/goods/groupon-btn.png');
+ const disabledBtnBg = sheep.$url.css(
+ '/static/img/shop/goods/activity-btn-disabled.png',
+ onPageScroll(() => {});
+ skeletonLoading: true, // 骨架屏
+ goodsId: 0, // 商品ID
+ goodsInfo: {}, // 商品信息
+ goodsSwiper: [], // 商品轮播图
+ showSelectSku: false, // 显示规格弹框
+ selectedSkuPrice: {}, // 选中的规格价格
+ activity: {}, // 团购活动
+ grouponId: 0, // 团购ID
+ grouponNum: 0, // 团购人数
+ grouponAction: 'create', // 团购操作
+ return useDurationTime(state.activity.endTime);
+ // 规格变更
+ function onSkuClose() {
+ state.showSelectSku = false;
+ // 发起拼团
+ * 去参团
+ * @param record 团长的团购记录
+ state.grouponId = record.activityId;
+ state.combinationHeadId = record.id;
+ state.grouponNum = record.userSize;
+ // 立即购买
+ combinationActivityId: state.activity.id,
+ // 分享信息
+ // TODO @芋艿:分享的接入
+ if (isEmpty(state.activity)) return {};
+ title: state.activity.name,
+ image: sheep.$url.cdn(state.goodsInfo.picUrl),
+ page: '3',
+ query: state.activity.id,
+ title: state.activity.name, // 商品标题
+ image: sheep.$url.cdn(state.goodsInfo.picUrl), // 商品主图
+ price: fen2yuan(state.goodsInfo.price), // 商品价格
+ marketPrice: fen2yuan(state.goodsInfo.marketPrice), // 商品原价
+ // 非法参数
+ state.goodsInfo = null;
+ state.grouponId = options.id;
+ const { code, data: activity } = await CombinationApi.getCombinationActivity(state.grouponId);
+ const { data: spu } = await SpuApi.getSpuDetail(activity.spuId);
+ state.goodsId = spu.id;
+ activity.products.forEach(product => {
+ spu.price = Math.min(spu.price, product.combinationPrice); // 设置 SPU 的最低价格
+ // 关闭骨架屏
+ state.skeletonLoading = false;
+ state.goodsInfo = spu;
+ state.grouponNum = activity.userSize;
+ state.goodsSwiper = formatGoodsSwiper(state.goodsInfo.sliderPicUrls);
+ // 未找到商品
+ // 价格标题卡片
+ .title-card {
+ // height: 320rpx;
+ background-image: v-bind(headerBg);
+ .price-box {
+ .price-text {
+ .origin-price {
+ opacity: 0.7;
+ .origin-price-text {
+ .tig {
+ border: 2rpx solid #ffffff;
+ border-radius: 4rpx;
+ width: 126rpx;
+ .tig-icon {
+ border-radius: 4rpx 0 0 4rpx;
+ .groupon-tag {
+ .tig-title {
+ background: rgba(#000000, 0.1);
+ .title-text {
+ .subtitle-text {
+ opacity: 0.9;
+ // 购买
+ .buy-box {
+ .disabled-btn-box[disabled] {
+ background-color: transparent;
+ .check-btn-box {
+ width: 248rpx;
+ margin-left: -36rpx;
+ background-image: v-bind(btnBg);
+ border-radius: 0px 40rpx 40rpx 0px;
+ .disabled-btn-box {
+ background-image: v-bind(disabledBtnBg);
+ .origin-price-btn {
+ width: 236rpx;
+ background: rgba(#ff5651, 0.1);
+ border-radius: 40rpx 0px 0px 40rpx;
+ .btn-title {
+ .btn-price {
+ .more-item-box {
+ .more-item {
+ width: 156rpx;
+ height: 58rpx;
+ .more-item-hover {
+ background: rgba(#ffefe5, 0.32);
+ background: v-bind(grouponBg)
+ no-repeat;
+ //活动卡片
+ .model-box {
@@ -0,0 +1,414 @@
+ <s-empty v-else-if="state.goodsInfo === null" text="商品不存在或已下架" icon="/static/soldout-empty.png" showAction
+ actionText="再逛逛" actionUrl="/pages/goods/list" />
+ <!-- 商品轮播图 -->
+ <su-swiper class="ss-m-b-14" isPreview :list="formatGoodsSwiper(state.goodsInfo.sliderPicUrls)"
+ otStyle="tag" imageMode="widthFix" dotCur="bg-mask-40" :seizeHeight="750" />
+ <view class="title-card detail-card ss-p-y-40 ss-p-x-20">
+ <view class="ss-flex ss-row-between ss-col-center ss-m-b-26">
+ <view class="price-box ss-flex ss-col-bottom">
+ {{ fen2yuan(state.selectedSku.price || state.goodsInfo.price) }}
+ <view class="origin-price-text" v-if="state.goodsInfo.marketPrice > 0">
+ {{ fen2yuan(state.selectedSku.marketPrice || state.goodsInfo.marketPrice) }}
+ <view class="sales-text">
+ {{ formatSales('exact', state.goodsInfo.salesCount) }}
+ <view class="discounts-box ss-flex ss-row-between ss-m-b-28">
+ <!-- 满减送/限时折扣活动的提示 -->
+ <div class="tag-content">
+ <view class="tag-box ss-flex">
+ <view class="tag ss-m-r-10" v-for="promos in state.activityInfo"
+ :key="promos.id" @tap="onActivity">
+ {{ promos.name }}
+ <!-- 优惠劵 -->
+ <view class="get-coupon-box ss-flex ss-col-center ss-m-l-20" @tap="state.showModel = true"
+ v-if="state.couponInfo.length">
+ <view class="discounts-title ss-m-r-8">领券</view>
+ <detail-cell-sku v-model="state.selectedSku.goods_sku_text" :sku="state.selectedSku"
+ @tap="state.showSelectSku = true" />
+ <s-select-sku :goodsInfo="state.goodsInfo" :show="state.showSelectSku" @addCart="onAddCart"
+ @buy="onBuy" @change="onSkuChange" @close="state.showSelectSku = false" />
+ <!-- 活动跳转:拼团/秒杀/砍价活动 -->
+ <detail-activity-tip v-if="state.activityList.length > 0" :activity-list="state.activityList" />
+ <!-- 详情 tabbar -->
+ <view class="buy-box ss-flex ss-col-center ss-p-r-20" v-if="state.goodsInfo.stock > 0">
+ <button class="ss-reset-button add-btn ui-Shadow-Main" @tap="state.showSelectSku = true">
+ 加入购物车
+ <button class="ss-reset-button buy-btn ui-Shadow-Main" @tap="state.showSelectSku = true">
+ 立即购买
+ <view class="buy-box ss-flex ss-col-center ss-p-r-20" v-else>
+ <button class="ss-reset-button disabled-btn" disabled> 已售罄 </button>
+ <!-- 优惠劵弹窗 -->
+ <s-coupon-get v-model="state.couponInfo" :show="state.showModel" @close="state.showModel = false"
+ @get="onGet" />
+ <!-- 满减送/限时折扣活动弹窗 -->
+ <s-activity-pop v-model="state.activityInfo" :show="state.showActivityModel"
+ @close="state.showActivityModel = false" />
+ import {
+ reactive,
+ computed
+ } from 'vue';
+ onLoad,
+ onPageScroll
+ } from '@dcloudio/uni-app';
+ import ActivityApi from '@/sheep/api/promotion/activity';
+ import { formatSales, formatGoodsSwiper, fen2yuan } from '@/sheep/hooks/useGoods';
+ import detailActivityTip from './components/detail/detail-activity-tip.vue';
+ goodsId: 0,
+ skeletonLoading: true, // SPU 加载中
+ goodsInfo: {}, // SPU 信息
+ showSelectSku: false, // 是否展示 SKU 选择弹窗
+ selectedSku: {}, // 选中的 SKU
+ showModel: false, // 是否展示 Coupon 优惠劵的弹窗
+ couponInfo: [], // 可领取的 Coupon 优惠劵的列表
+ showActivityModel: false, // 【满减送/限时折扣】是否展示 Activity 营销活动的弹窗
+ activityInfo: [], // 【满减送/限时折扣】可参与的 Activity 营销活动的列表
+ activityList: [], // 【秒杀/拼团/砍价】可参与的 Activity 营销活动的列表
+ state.selectedSku = e;
+ // 添加购物车
+ function onAddCart(e) {
+ if (!e.id) {
+ sheep.$helper.toast('请选择商品规格');
+ console.log(e,'我是商品规格')
+ sheep.$store('cart').add(e);
+ function onBuy(e) {
+ if (!state.selectedSku.id) {
+ items: [{
+ skuId: e.id,
+ count: e.goods_num
+ }],
+ // TODO 芋艿:后续清理掉这 2 参数
+ deliveryType: 1,
+ pointStatus: false,
+ // 营销活动
+ function onActivity() {
+ state.showActivityModel = true;
+ // 立即领取
+ async function onGet(id) {
+ // TODO 芋艿:待测试
+ if (isEmpty(state.goodsInfo)) return {};
+ return sheep.$platform.share.getShareInfo({
+ title: state.goodsInfo.name,
+ image: sheep.$url.cdn(state.goodsInfo.image),
+ desc: state.goodsInfo.subtitle,
+ query: state.goodsInfo.id,
+ title: state.goodsInfo.name, // 商品标题
+ // image: sheep.$url.cdn(state.goodsInfo.image), // 商品主图
+ original_price: fen2yuan(state.goodsInfo.maretPrice), // 商品原价
+ }, );
+ const { code, data } = await CouponApi.getCouponTemplateList(state.goodsId, 2, 10);
+ state.couponInfo = data;
+ state.goodsId = options.id;
+ // 1. 加载商品信息
+ SpuApi.getSpuDetail(state.goodsId).then((res) => {
+ if (res.code !== 0 || !res.data) {
+ // 加载到商品
+ state.goodsInfo = res.data;
+ // 加载是否收藏
+ FavoriteApi.isFavoriteExists(state.goodsId, 'goods').then((res) => {
+ state.goodsInfo.favorite = res.data;
+ // 2. 加载优惠劵信息
+ // 3. 加载营销活动信息
+ ActivityApi.getActivityListBySpuId(state.goodsId).then((res) => {
+ res.data.forEach(activity => {
+ if ([1, 2, 3].includes(activity.type)) { // 情况一:拼团/秒杀/砍价
+ state.activityList.push(activity);
+ } else if (activity.type === 5) { // 情况二:满减送
+ state.activityInfo.push(activity);
+ } else { // 情况三:限时折扣 TODO 芋艿
+ console.log('待实现!优先级不高');
+ font-size: 42rpx;
+ .discounts-box {
+ .tag-content {
+ min-width: 0;
+ text-overflow: ellipsis;
+ .tag {
+ padding: 4rpx 10rpx;
+ background: var(--ui-BG-Main-tag);
+ .discounts-title {
+ width: 428rpx;
+ background: #999999;
+ height: 60vh;
+ .model-content {
+ height: 56vh;
@@ -0,0 +1,362 @@
+ <s-layout navbar="normal" :leftWidth="0" :rightWidth="0" tools="search" :defaultSearch="state.keyword"
+ @search="onSearch">
+ <!-- 筛选 -->
+ <su-tabs :list="state.tabList" :scrollable="false" @change="onTabsChange"
+ :current="state.currentTab" />
+ <view class="list-icon" @tap="state.iconStatus = !state.iconStatus">
+ <text v-if="state.iconStatus" class="sicon-goods-list" />
+ <text v-else class="sicon-goods-card" />
+ <!-- 弹窗 -->
+ <su-popup :show="state.showFilter" type="top" round="10" :space="sys_navBar + 38" backgroundColor="#F6F6F6"
+ :zIndex="10" @close="state.showFilter = false">
+ <view class="filter-list-box">
+ <view class="filter-item" v-for="(item, index) in state.tabList[state.currentTab].list"
+ :key="item.value" :class="[{ 'filter-item-active': index === state.curFilter }]"
+ @tap="onFilterItem(index)">
+ <!-- 情况一:单列布局 -->
+ <view v-if="state.iconStatus && state.pagination.total > 0" class="goods-list ss-m-t-20">
+ <view class="ss-p-l-20 ss-p-r-20 ss-m-b-20" v-for="item in state.pagination.list" :key="item.id">
+ :topRadius="10"
+ :bottomRadius="10"
+ <!-- 情况二:双列布局 -->
+ <view v-if="!state.iconStatus && state.pagination.total > 0"
+ class="ss-flex ss-flex-wrap ss-p-x-20 ss-m-t-20 ss-col-top">
+ <s-empty v-if="state.pagination.total === 0" icon="/static/soldout-empty.png" text="暂无商品" />
+ const emits = defineEmits(['close', 'change']);
+ pageSize: 6,
+ currentSort: undefined,
+ currentOrder: undefined,
+ currentTab: 0, // 当前选中的 tab
+ curFilter: 0, // 当前选中的 list 筛选项
+ showFilter: false,
+ iconStatus: false, // true - 单列布局;false - 双列布局
+ keyword: '',
+ categoryId: 0,
+ tabList: [{
+ name: '综合推荐',
+ list: [{
+ label: '综合推荐'
+ label: '价格升序',
+ sort: 'price',
+ order: true,
+ label: '价格降序',
+ order: false,
+ name: '销量',
+ sort: 'salesCount',
+ order: false
+ name: '新品优先',
+ value: 'createTime',
+ leftGoodsList: [], // 双列布局 - 左侧商品
+ rightGoodsList: [], // 双列布局 - 右侧商品
+ // 处理双列布局 leftGoodsList + rightGoodsList
+ if (!state.pagination.list[count]) {
+ // 清空列表
+ function emptyList() {
+ state.leftGoodsList = [];
+ state.rightGoodsList = [];
+ count = 0;
+ leftHeight = 0;
+ rightHeight = 0;
+ function onSearch(e) {
+ state.keyword = e;
+ emptyList();
+ getList(state.currentSort, state.currentOrder);
+ // 如果点击的是【综合推荐】,则直接展开或者收起筛选项
+ if (state.tabList[e.index].list) {
+ state.showFilter = !state.showFilter;
+ state.showFilter = false;
+ // 如果点击的是【销量】或者【新品优先】,则直接切换 tab
+ if (e.index === state.currentTab) {
+ state.currentSort = e.sort;
+ state.currentOrder = e.order;
+ getList(e.sort, e.order);
+ // 点击 tab 的 list 筛选项
+ const onFilterItem = (val) => {
+ // 如果点击的是当前的筛选项,则直接收起筛选项,不要加载数据
+ // 这里选择 tabList[0] 的原因,是目前只有它有 list
+ if (state.currentSort === state.tabList[0].list[val].sort
+ && state.currentOrder === state.tabList[0].list[val].order) {
+ // 设置筛选条件
+ state.curFilter = val;
+ state.tabList[0].name = state.tabList[0].list[val].label;
+ state.currentSort = state.tabList[0].list[val].sort;
+ state.currentOrder = state.tabList[0].list[val].order;
+ // 清空 + 加载数据
+ sortField: state.currentSort,
+ sortAsc: state.currentOrder,
+ keyword: state.keyword,
+ state.categoryId = options.categoryId;
+ state.keyword = options.keyword;
+ &:nth-last-of-type(1) {
+ margin-bottom: 0 !important;
+ &:nth-child(2n) {
+ margin-right: 0;
+ .list-icon {
+ width: 80rpx;
+ .sicon-goods-card {
+ .sicon-goods-list {
+ .list-filter-tabs {
+ .filter-list-box {
+ padding: 28rpx 52rpx;
+ .filter-item {
+ &:nth-last-child(1) {
+ .filter-item-active {
@@ -0,0 +1,555 @@
+<!-- 秒杀商品详情 -->
+ v-else-if="state.goodsInfo === null || state.goodsInfo.activity_type !== 'seckill'"
+ actionText="再逛逛"
+ actionUrl="/pages/goods/list"
+ <view class="title-card ss-m-y-14 ss-m-x-20 ss-p-x-20 ss-p-y-34">
+ <view class="price-box ss-flex ss-row-between ss-m-b-18">
+ <text class="cicon-alarm"></text>
+ <view class="tig-title">秒杀价</view>
+ <view class="origin-price ss-flex ss-col-center" v-if="state.goodsInfo.marketPrice">
+ 原价
+ <detail-progress :percent="state.percent" />
+ <view class="title-text ss-line-2 ss-m-b-6">{{ state.goodsInfo?.name }}</view>
+ <detail-cell-sku
+ :sku="state.selectedSku"
+ @tap="state.showSelectSku = true"
+ <s-select-seckill-sku
+ v-model="state.goodsInfo"
+ :single-limit-count="activity.singleLimitCount"
+ <detail-comment-card class="detail-comment-selector" :goodsId="state.goodsInfo.id" />
+ <!-- 详情tabbar -->
+ <!-- TODO: 缺货中 已售罄 判断 设计-->
+ v-if="state.goodsInfo.marketPrice"
+ <button v-else class="ss-reset-button origin-price-btn ss-flex-col">
+ class="no-original"
+ :class="state.goodsInfo.stock === 0 || timeStatusEnum !== TimeStatusEnum.STARTED ? '' : ''"
+ 秒杀价
+ class="ss-reset-button btn-box ss-flex-col"
+ timeStatusEnum === TimeStatusEnum.STARTED && state.goodsInfo.stock != 0
+ :disabled="state.goodsInfo.stock === 0 || timeStatusEnum !== TimeStatusEnum.STARTED"
+ <view class="btn-price">{{ fen2yuan(state.goodsInfo.price) }}</view>
+ <view v-if="timeStatusEnum === TimeStatusEnum.STARTED">
+ <view v-else>立即秒杀</view>
+ <view v-else>{{ timeStatusEnum }}</view>
+ import {reactive, computed, ref} from 'vue';
+ import {isEmpty, min} from 'lodash';
+ import detailProgress from './components/detail/detail-progress.vue';
+ import {getTimeStatusEnum, TimeStatusEnum} from "@/sheep/util/const";
+ const headerBg = sheep.$url.css('/static/img/shop/goods/seckill-bg.png');
+ const btnBg = sheep.$url.css('/static/img/shop/goods/seckill-btn.png');
+ skeletonLoading: true,
+ goodsInfo: {},
+ goodsSwiper: [],
+ selectedSku: {},
+ showModel: false,
+ percent: 0,
+ price: '',
+ return useDurationTime(activity.value.endTime);
+ buy_type: 'seckill',
+ seckillActivityId: activity.value.id,
+ // 分享信息 TODO 芋艿:待接入
+ if (isEmpty(activity)) return {};
+ title: activity.value.name,
+ page: '4',
+ query: activity.value.id,
+ title: activity.value.name, // 商品标题
+ price: state.goodsInfo.price, // 商品价格
+ marketPrice: state.goodsInfo.marketPrice, // 商品原价
+ const activity = ref()
+ const timeStatusEnum = ref('')
+ // 查询活动
+ const getActivity = async (id) => {
+ const { data } = await SeckillApi.getSeckillActivity(id)
+ activity.value = data
+ timeStatusEnum.value = getTimeStatusEnum(activity.startTime, activity.endTime)
+ // 查询商品
+ await getSpu(data.spuId)
+ const getSpu = async (id) => {
+ const { data } = await SpuApi.getSpuDetail(id)
+ // 模拟
+ data.activity_type = 'seckill'
+ state.goodsInfo = data
+ // 处理轮播图
+ // 默认显示最低价
+ state.goodsInfo.price = min([state.goodsInfo.price, ...activity.value.products.map(spu => spu.seckillPrice)])
+ // 价格、库存使用活动的
+ data.skus.forEach(sku => {
+ const product = activity.value.products.find(product => product.skuId === sku.id);
+ if (product) {
+ sku.price = product.seckillPrice;
+ sku.stock = Math.min(sku.stock, product.stock);
+ } else { // 找不到可能是没配置,则不能发起秒杀
+ sku.stock = 0;
+ // 设置限购数量
+ if (activity.value.totalLimitCount > 0 && activity.value.singleLimitCount > 0) {
+ sku.limitCount = Math.min(activity.value.totalLimitCount, activity.value.singleLimitCount);
+ } else if (activity.value.totalLimitCount > 0) {
+ sku.limitCount = activity.value.totalLimitCount;
+ } else if (activity.value.singleLimitCount > 0) {
+ sku.limitCount = activity.value.singleLimitCount;
+ getActivity(options.id)
+ .cicon-alarm {
+ color: #fc6e6f;
+ .discounts-tag {
+ // background: rgba(#2aae67, 0.05);
+ .no-original {
@@ -0,0 +1,196 @@
+ <s-layout title="购物车" tabbar="/pages/index/cart" :bgStyle="{ color: '#fff' }">
+ <s-empty v-if="state.list.length === 0" text="购物车空空如也,快去逛逛吧~" icon="/static/cart-empty.png" />
+ <!-- 头部 -->
+ <view class="cart-box ss-flex ss-flex-col ss-row-between" v-if="state.list.length">
+ <view class="cart-header ss-flex ss-col-center ss-row-between ss-p-x-30">
+ <view class="header-left ss-flex ss-col-center ss-font-26">
+ 共
+ <text class="goods-number ui-TC-Main ss-flex">{{ state.list.length }}</text>
+ 件商品
+ <view class="header-right">
+ <button v-if="state.editMode" class="ss-reset-button" @tap="state.editMode = false">
+ 取消
+ <button v-else class="ss-reset-button ui-TC-Main" @tap="state.editMode = true">
+ 编辑
+ <view class="cart-content ss-flex-1 ss-p-x-30 ss-m-b-40">
+ <view class="goods-box ss-r-10 ss-m-b-14" v-for="item in state.list" :key="item.id">
+ <label class="check-box ss-flex ss-col-center ss-p-l-10" @tap="onSelectSingle(item.id)">
+ <radio :checked="state.selectedIds.includes(item.id)" color="var(--ui-BG-Main)"
+ style="transform: scale(0.8)" @tap.stop="onSelectSingle(item.id)" />
+ <s-goods-item :title="item.spu.name" :img="item.spu.picUrl || item.goods.image"
+ :price="item.sku.price"
+ :skuText="item.sku.properties.length>1? item.sku.properties.reduce((items2,items)=>items2.valueName+' '+items.valueName):item.sku.properties[0].valueName"
+ priceColor="#FF3000" :titleWidth="400">
+ <template v-if="!state.editMode" v-slot:tool>
+ <su-number-box :min="0" :max="item.sku.stock" :step="1" v-model="item.count" @change="onNumberChange($event, item)" />
+ <!-- 底部 -->
+ <su-fixed bottom :val="48" placeholder v-if="state.list.length > 0" :isInset="false">
+ <view class="cart-footer ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom">
+ <view class="footer-left ss-flex ss-col-center">
+ <label class="check-box ss-flex ss-col-center ss-p-r-30" @tap="onSelectAll">
+ <radio :checked="state.isAllSelected" color="var(--ui-BG-Main)"
+ style="transform: scale(0.8)" @tap.stop="onSelectAll" />
+ <view class="ss-m-l-8"> 全选 </view>
+ <text>合计:</text>
+ <view class="text-price price-text">
+ {{ fen2yuan(state.totalPriceSelected) }}
+ <view class="footer-right">
+ <button v-if="state.editMode" class="ss-reset-button ui-BG-Main-Gradient pay-btn ui-Shadow-Main"
+ @tap="onDelete">
+ 删除
+ <button v-else class="ss-reset-button ui-BG-Main-Gradient pay-btn ui-Shadow-Main"
+ @tap="onConfirm">
+ 去结算
+ {{ state.selectedIds?.length ? `(${state.selectedIds.length})` : '' }}
+ const cart = sheep.$store('cart');
+ editMode: false,
+ list: computed(() => cart.list),
+ selectedList: [],
+ selectedIds: computed(() => cart.selectedIds),
+ isAllSelected: computed(() => cart.isAllSelected),
+ totalPriceSelected: computed(() => cart.totalPriceSelected),
+ // 单选选中
+ function onSelectSingle(id) {
+ cart.selectSingle(id);
+ // 全选
+ function onSelectAll() {
+ cart.selectAll(!state.isAllSelected);
+ // 结算
+ function onConfirm() {
+ let items = []
+ let goods_list = [];
+ state.selectedList = state.list.filter((item) => state.selectedIds.includes(item.id));
+ state.selectedList.map((item) => {
+ // 此处前端做出修改
+ items.push({
+ skuId: item.sku.id,
+ count: item.count,
+ cartId: item.id,
+ goods_list.push({
+ // goods_id: item.goods_id,
+ goods_id: item.spu.id,
+ // goods_num: item.goods_num,
+ goods_num: item.count,
+ // 商品价格id真没有
+ // goods_sku_price_id: item.goods_sku_price_id,
+ // return;
+ if (goods_list.length === 0) {
+ sheep.$helper.toast('请选择商品');
+ // order_type: 'goods',
+ // goods_list,
+ items,
+ // from: 'cart',
+ function onNumberChange(e, cartItem) {
+ if (e === 0) {
+ cart.delete(cartItem.id);
+ if (cartItem.goods_num === e) return;
+ cartItem.goods_num = e;
+ cart.update({
+ goods_id: cartItem.id,
+ goods_num: e,
+ goods_sku_price_id: cartItem.goods_sku_price_id,
+ async function onDelete() {
+ cart.delete(state.selectedIds);
+ :deep(.ui-fixed) {
+ .cart-box {
+ .cart-header {
+ background-color: #f6f6f6;
+ position: fixed;
+ top: v-bind('sys_navBar') rpx;
+ z-index: 1000;
+ .cart-footer {
+ .pay-btn {
+ .cart-content {
+ margin-top: 70rpx;
@@ -0,0 +1,2055 @@
+ <view class="hx-store" @touchstart="touchStart">
+ <hx-navbar
+ :fixed="true"
+ :color="['#ffffff','#888888']"
+ barPlaceholder="hidden"
+ transparent="auto"
+ :back="false"
+ :rightSlot="false"
+ :background-color="[245,245,245]"
+ :pageScroll.sync="pageScroll">
+ <block slot="left">
+ <view class="" style="margin-left: 6px; font-size: 22px;" @click="navBack">
+ <i class="hxicon-back"></i>
+ <view class="ctn">
+ <view class="searchCtn" :style="{'width':navSearchWidth + '%','background-color':'rgba(230,230,230,'+ navSearchBgOpacity +')'}">
+ <i class="hxicon-search"></i>
+ <input confirm-type="search" class="input" type="text" placeholder="输入搜索关键词" style="font-size: 14px;" :style="{'opacity':navSearchBgOpacity ,'color':navSearchColor}">
+ <view class="leftBox" style="font-size: 22px;">
+ <i class="hxicon-favor"></i>
+ <i class="hxicon-more"></i>
+ <view class="jrNull"></view>
+ </hx-navbar>
+ <!-- 只需要绑定购物车位置即可 -->
+ <flyInCart ref="inCart" :cartBasketRect="cartBasketRect"></flyInCart>
+ <!-- <view class="header">
+ <image class="header-bg" :src="storeData.banner" mode=""></image>
+ <view :class="showStoreBox ? 'header-bg-gray' : 'header-bg-black'"></view>
+ <view class="header-top-Placeholder" ></view>
+ <view class="container storeInfo hx-shadow" :style="{height:storeInfoBoxHeight + 'px'}">
+ <image class="storeAvatar hx-shadow" :src="storeData.avatar" mode=""></image>
+ <view class="hx-txt-18 hx-color-black hx-txt-weigth hx-mb-10 ">
+ 十里桃花
+ <view class="hx-txt-14 hx-color-black ">
+ 店家说明,本店放心吃,地方名才
+ <view class="shrink-box">
+ <i class="hxicon" :class="showStoreBox ? 'icon-fold' : 'icon-unfold'" @click="showStoreBox = !showStoreBox"></i>
+ </view> -->
+ <!-- 主体 -->
+ <view class="main" :style="{height: mainHeight}" >
+ <!-- <view class="" :style="{display:showZz}" style="position: absolute;top: 0;bottom: 0;left: 0;width: 100%; background: #3F536E;z-index: 999;opacity: 0.5;">
+ 遮罩
+ <view class="tabs-box" :style="{'top': 'calc(44px + ' + statusBarHeight + 'px)','background-color':'rgba(245,245,245,'+ navSearchBgOpacity +')'}">
+ <view class="" style="width: 210px; height: 100%;">
+ <view class="hx-tabs">
+ <view class="hx-tabs-item" v-for="(item,i) in tabs" :key="i" :class="{'hx-tabs-active': swiperCurrent == i}" @click="swiperChange(i)" :style="{transition: transtionTime + 'ms'}">
+ <text>{{item.name}}</text>
+ <view class="hx-tabs-slider-box" :style="{transition: transtionTime + 'ms',left:swiperCurrentSliderLeft + 'px'}">
+ <view class="hx-tabs-slider"></view>
+ id="mainSwiper"
+ style="height: 100%;margin-top: 55px;"
+ :current="swiperCurrent"
+ :duration="transtionTime"
+ @transition="transition"
+ @animationfinish="animationfinish">
+ <!-- 购物 -->
+ <swiper-item class="swiper-item" >
+ <view class="scroll-items">
+ <view class="category-list">
+ <!-- 左侧分类导航 -->
+ <scroll-view :scroll-top="menuScrollTop" :scroll-y="goodsBoxScroll" class="left" >
+ <view v-for="item in categoryList" :key="item.id" class="row" :class="{active: item.id == menuCurrentId}" @click="showCategory(item)">
+ {{item.name}}
+ <view class="row-number" v-if="item.number">
+ {{item.number}}
+ <!-- 右侧子导航 -->
+ <scroll-view scroll-with-animation :scroll-y="goodsBoxScroll" class="right" @scroll="asideScroll" :scroll-top="tabScrollTop" >
+ <view class="goodsListBox">
+ <view class="category" v-for="item in categoryList" :key="item.id" :id="'goodsBox'+item.id" >
+ <view class="s-item">{{item.name}}</view>
+ <view class="list" >
+ <view class="box" v-for="(rowData,i) in goodsList" :key="rowData.id" >
+ <!-- v-if="item.id == rowData.type_id" -->
+ <!-- 商品列表 -->
+ <!-- <m-store-pro @touchOnGoods="touchOnGoods" :rowData="box"></m-store-pro>
+ -->
+ <view class="m-store-item">
+ <view class="m-img" @click="hrefGoodsInfo(rowData.id)">
+ <image style="width: 100%;height: 100%;" :src="rowData.img" mode="aspectFit"></image>
+ <view class="m-text">
+ <view class="m-title" @click="hrefGoodsInfo(rowData.id)">
+ {{rowData.name}}
+ <view class="m-descripe">
+ {{rowData.descripe}}
+ <block v-if="rowData.form">
+ <view class="m-price-box">
+ <view class="symbol">¥</view>
+ <view class="m-price">{{rowData.form_child[0].price}}</view>
+ <view class="m-old-price" v-if="rowData.form_child[0].oldprice">
+ <text>¥{{rowData.form_child[0].oldprice}}</text>
+ <view class="m-price">{{rowData.price}}</view>
+ <view class="m-old-price" v-if="rowData.oldprice">
+ <text>¥{{rowData.oldprice}}</text>
+ <view class="m-distance" >
+ <view class="miniBtn" @click="showForm(rowData)">
+ <text>选规格</text>
+ <!-- #ifdef APP-PLUS || H5 -->
+ <view class="num" v-if="getCartGoodsNum(rowData)">{{getCartGoodsNum(rowData)}}</view>
+ <view :class="'addEle_' + i" class="jumpPosition"></view>
+ <hx-number-box @change="addGoodsChange" :value="rowData.number" :rowData="rowData" :clickTime="animaTime" @addChange="touchOnAddGoods('.addEle_' + i,rowData)"></hx-number-box>
+ <!-- <image @click="touchOnAddGoods('.addEle_' + i,rowData)" style="width:20px;height: 20px;" src="../../static/img/icon/shop_icon_buy.png" mode="aspectFit"></image>
+ <scroll-view scroll-y style="height: 100%;width: 100%;background-color: #ffffff;" @scroll="asideScroll" :scroll-y="goodsBoxScroll" >
+ <view class="scroll-items evaluate-box" >
+ <view class="evaluate-box-header">
+ <view class="evaluate-box-body">
+ <hx-comment :listData="commentList"></hx-comment>
+ <!-- 商家 -->
+ <view class="scroll-items business-box">
+ <view class="info-list hx-mt-15">
+ <view class="info-list-container">
+ <i class="hxicon-location"></i>
+ <text>{{ storeData.address }}</text>
+ <view class="info-list hx-mt-15 ">
+ <view class="info-list-container hx-bb">
+ <i class="hxicon-time"></i>
+ <text>配送时间:{{ storeData.delivery_time }}</text>
+ <view class=" info-list">
+ <view class="info-list-container" @click="goCall(storeData.telephone)">
+ <i class="hxicon-phone"></i>
+ <text style="flex: 1;">商家电话:{{ storeData.telephone }}</text>
+ <text>拨打</text><i class="hxicon-right"></i>
+ <view class="info-list-container" @click="showStoreBoxFunc">
+ <i class="hxicon-new" style="color:#ff3333"></i>
+ <text style="flex: 1;">商家当前热门活动</text>
+ <text>查看</text><i class="hxicon-right"></i>
+ <!-- 购物车 -->
+ <view class="foot" @touchmove.stop.prevent="mpClear" :style="{height: footHeight}" v-if="showFoot">
+ <view class="zz" @click="hideShoppingCar"></view>
+ <view class="btn-box">
+ <view class="btn-box-left" @click="contact">
+ <view class="imgBox">
+ <image src="../../static/store/contact.png" mode=""></image>
+ <text>联系商家</text>
+ <view class="btn-box-line"></view>
+ <view class="btn-box-center" @click="showShoppingCar">
+ <view class="cart" :animation="cartAnimationData">
+ <view class="tag cartNum" v-if="goodsTotalNumber>0">{{goodsTotalNumber}}</view>
+ <image :src="goodsTotalNumber ? '/static/store/cart.png' : '/static/store/cart2.png'" mode=""></image>
+ <view class="priceBox">
+ <view class="hx-txt-18 hx-color-white" v-if="goodsTotalPrice>0">
+ ¥{{goodsTotalPrice}}
+ <view class="hx-txt-10 hx-color-gray">
+ 另需配送费¥{{shippingDees}}
+ <view class="btn-box-right">
+ <view class="jiesuan" v-if="goodsTotalPrice>0 && goodsTotalPrice >= startingPrice" @click="jiesuan">
+ <view class="pscj hx-txt-10 hx-color-gray" v-else>
+ <text v-if="startingPrice>0">差¥{{-(goodsTotalPrice-startingPrice)}}起送</text>
+ <view class="cart-box" :style="{display: showCar ? 'flex' : 'none'}">
+ <view class="box-container rebate-box" v-if="showDiscount">
+ <text>已享100减25</text>
+ <view class="box-container operating-box">
+ <view class="operating-box_right">
+ <view class="operating-box_left clear" @click="clearShoppingCart">
+ <i class="hxicon-delete"></i>
+ <text>清空购物车</text>
+ <view class=" goods-box">
+ <view class="" style="flex: 1;">
+ <scroll-view scroll-y="true" class="goods-list-scroll" :scroll-top="carGoodsScrollTop">
+ <view class="goods-list">
+ <view class="box" v-for="(rowData,i) in shoppCart" :key="rowData.id">
+ <!-- v-if="rowData.number>0" -->
+ <view class="m-img">
+ <view class="m-title">
+ {{rowData.current_form ? rowData.form.name + ":" + rowData.current_form.name : rowData.descripe}}
+ <view class="m-price-box" >
+ <view :class="'addEle2_' + rowData.id" class="jumpPosition">
+ <hx-number-box @change="addGoodsChange" :value="rowData.number" :rowData="rowData" :clickTime="animaTime" @addChange="touchOnAddGoods('.addEle2_' + rowData.id,rowData)"></hx-number-box>
+ <!-- v-if="showFormBox" -->
+ <!-- 多规格 -->
+ <uni-popup ref="popup" type="center" @change="popupChange">
+ <view class="form-main" v-if="currentGoodsData.name">
+ <view class="form-main_ctn" @click.stop.prevent="mpClear" >
+ <view class="godos_tit"><text>{{currentGoodsData.name}}</text></view>
+ <view class="gg_tit">
+ <text>{{currentGoodsData.form.name}}</text>
+ <view class="gg_box">
+ <block v-for="form_child in currentGoodsData.form_child" :key="form_child.id" v-if="form_child.pid == currentGoodsData.form.id">
+ <view class="item" :class="{'active': form_child.select}" @click="selectGoodsForm(currentGoodsData,form_child)">{{form_child.name}}</view>
+ <view class="select_gg">
+ <text class="lable">已选规格:</text>
+ <view class="select_gg_box">
+ <block v-for="form_child in currentGoodsData.form_child" :key="form_child.id" v-if="form_child.select == true">
+ <view class="gg-item">
+ <text>{{form_child.name}} </text>
+ <text class="gg-item-cut">,</text>
+ <view class="price_box">
+ <text>¥</text>
+ <block v-for="(form_child,i) in currentGoodsData.form_child" :key="i" v-if="form_child.select == true">
+ <text class="price">{{form_child.price}}</text>
+ <view class="form-btn-box">
+ <block v-if="!currentGoodsData.number">
+ <view id="ggAddBtn" class="add-btn" @click="formFirstAddGoods()">
+ <i class="hxicon-add" id="eleAdd"></i>
+ <text>加入购物车</text>
+ <view class="addEle_gg jumpPosition"></view>
+ <hx-number-box @change="formAddGoodsChange" :value="currentGoodsData.number" :rowData="currentGoodsData" :clickTime="animaTime + 200" @addChange="touchOnAddGoods('.addEle_gg',currentGoodsData)"></hx-number-box>
+ <view class="close" @click="hiddenForm()">
+ <i class="hxicon-close"></i>
+ </uni-popup>
+<script>
+ // import uniIcons from '@/components/uni-icons/uni-icons.vue';
+ // import hxNavbar from '@/components/hx-navbar/hx-navbar.vue';
+ // import jumpBall from '@/components/hx-jump-ball/hx-jump-ball.vue';
+ // import hxNumberBox from "@/components/uni-number-box/uni-number-box.vue";
+ // import hxComment from "@/components/hx-comment/hx-comment.vue";
+ // // 加入购物车动画组件
+ // import flyInCart from '@/components/flyInCart.vue'
+ //引入测试数据
+ import testData from './components/testdata.js';
+ handleTree
+ } from '@/sheep/util';
+ var statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
+ export default {
+ components: {
+ // jumpBall,
+ // hxNavbar,
+ // uniIcons,
+ // hxNumberBox,
+ // hxComment,
+ // flyInCart
+ data() {
+ pageScroll:{},
+ //商家信息
+ storeData: [],
+ //商品列表
+ goodsList: [],
+ //商品分类信息列表
+ categoryList: [],
+ //评论列表
+ navSearchWidth: 3,
+ navSearchBgOpacity: 0,
+ navSearchColor: '#ffffff',
+ navHeadHeight: 44,
+ //默认禁止商品栏滚动
+ goodsBoxScroll: false,
+ statusBarHeight,
+ //动画时间
+ animaTime: 300,
+ //商家盒子高度
+ storeInfoBoxHeight: 100,
+ //展开门店信息容器
+ showStoreBox: false,
+ num:1,
+ element: [],
+ cartAnimation: {},
+ cartAnimationData: {},
+ //tabs
+ tabs: [
+ {name:'购物'},
+ {name:'评价'},
+ {name:'商家'},
+ swiperCurrent: 0,
+ dx: 0,
+ swiperCurrentSliderLeft: 0,
+ transtionTime:100,
+ //所有购物车
+ shoppingCartAll:[],
+ //显示购物车
+ showFoot:true,
+ //配送费
+ shippingDees: 0,
+ //配送起步价
+ startingPrice:0,
+ //购物车商品价格合计
+ goodsTotalPrice: 0,
+ //购物车商品数量合计
+ goodsTotalNumber: 0,
+ //当前门店购物车
+ shoppCart: [],
+ //foot 高度
+ footHeight: '0',
+ showCar: false,
+ //购物车中商品滚动条位置
+ carGoodsScrollTop: 0,
+ //购物车缓存 Storage 名称
+ shoppingCartStorageName: 'shopping_cart',
+ sizeCalcState: false,
+ tabScrollTop: 0,
+ mainHeight: 0,
+ menuCurrentId:1,
+ //购物车显示折扣
+ showDiscount:true,
+ //手指触摸滑动
+ touchData:{},
+ //页面滚动条距离顶部的距离
+ pageScrollTop: 0,
+ menuScrollTop: 0,
+ //多规格当前产品
+ currentGoodsData: {},
+ //显示规格容器
+ showFormBox: false,
+ //显示规格动画
+ formAnimationData:{},
+ //购物车位置数据
+ cartBasketRect:{},
+ navStatus: true,
+ isBack:true,
+ async onLoad(option) {
+ const that = this
+ uni.showLoading({
+ title: '加载中'
+ code,
+ data
+ } = await CategoryApi.getCategoryList();
+ //模拟请求数据
+ setTimeout(()=>{
+ // that.storeData = testData.storeData,
+ // that.goodsList = testData.goodsData;
+ that.categoryList =handleTree(data);
+ // that.commentList = testData.commentData,
+ this.init();
+ uni.hideLoading();
+ },200)
+ },500)
+ onReady() {
+ let sysInfo = uni.getSystemInfoSync();
+ //屏幕高度 - 头部导航高度 - 状态栏高度 - 分页高度
+ this.mainHeight = sysInfo.screenHeight - 43 - statusBarHeight - 40 + 'px';
+ let q = uni.createSelectorQuery()
+ setTimeout(function(){
+ q.select('.cart').boundingClientRect(data => {
+ that.cartBasketRect = data
+ }).exec();
+ },100)
+ that.calcSize();
+ onBackPress(event) {
+ if(this.showFormBox){
+ this.hiddenForm()
+ return true
+ if(!this.isBack){
+ this.$refs.popup.close()
+ onShow() {
+ mounted() {
+ let that = this
+ watch:{
+ showStoreBox(val,oldVal){
+ if(val == true){
+ this.showStoreBoxFunc();
+ }else{
+ this.hiddenStoreBoxFunc();
+ onPageScroll(e) {
+ let top = e.scrollTop
+ that.pageScrollTop = e.scrollTop
+ that.pageScroll = e
+ if(top < 120){
+ if(that.navSearchWidth >= 3){
+ if(top<3){
+ that.navSearchWidth = 3
+ that.navSearchBgOpacity = 0
+ that.navSearchColor = '#ffffff'
+ let n = top * (120/100)
+ if(n > 100){
+ n = 100
+ that.navSearchWidth = n
+ //that.navSearchBgOpacity =1
+ that.navSearchBgOpacity = top * (1/100)
+ that.navSearchColor = '#888888'
+ that.navSearchWidth = 100
+ that.navSearchBgOpacity = 1
+ let view = uni.createSelectorQuery().select(".main");
+ view.fields({
+ rect: true
+ }, data => {
+ if(data != null){
+ if(data.top <= statusBarHeight+44){
+ that.goodsBoxScroll = true
+ that.goodsBoxScroll = false
+ methods: {
+ init(){
+ let that = this;
+ //假设这是从后台获取的商品数据
+ let goods = this.goodsList;
+ //商品初始化
+ // for(let i in goods){
+ // goods[i].purchases = 3
+ that.shoppCart = []
+ let carts = uni.getStorageSync(that.shoppingCartStorageName) || [];
+ console.log(carts);
+ //根据缓存数据 获取购物车中属于本商店的商品
+ for(let i in carts){
+ if(carts[i].store_id == that.storeData.store_id){
+ that.shoppCart = carts[i].shopping_cart ? carts[i].shopping_cart : [];
+ that.goodsTotalPrice = 0;
+ that.goodsTotalNumber = 0;
+ for(let i in that.shoppCart){
+ for(let j in goods){
+ if(goods[j].id == that.shoppCart[i].id){
+ goods[j].number = that.shoppCart[i].number
+ //计算商品总价
+ that.goodsTotalPrice += that.shoppCart[i].total
+ //商品总数量
+ that.goodsTotalNumber += that.shoppCart[i].number
+ //初始化商品列表
+ that.goodsList = goods;
+ //初始化起步价和配送费
+ that.starting_price = that.storeData.starting_price;
+ that.shipping_dees = that.storeData.shipping_dees;
+ that.setLabelNumber();
+ navBack(){
+ if(getCurrentPages().length>1){
+ uni.navigateBack();
+ // #ifdef H5
+ history.back()
+ // #ifndef H5
+ uni.reLaunch({
+ url: '/pages/index/index'
+ popupChange(e){
+ this.isBack = !e.show
+ //-----------------------------------------------------------------------------------
+ //显示 规格
+ showForm(goods){
+ var that = this;
+ let goodsCart = that.getStoreCart();
+ if(goodsCart){
+ let currentGoods = null
+ for(let i in goodsCart){
+ if(goodsCart[i].id == goods.id){
+ currentGoods = goodsCart[i]
+ break
+ if(currentGoods){
+ let selectStatus = false
+ for (let i in goods.form_child){
+ if(goods.form_child[i].id == currentGoods.current_form.id){
+ if(!selectStatus){
+ goods.form_child[i].select = true
+ goods.number = currentGoods.number
+ selectStatus = true
+ goods.form_child[i].select = false
+ that.currentGoodsData = goods
+ that.$refs.popup.open()
+ //隐藏规格
+ hiddenForm(){
+ this.$refs.popup.close();
+ formFirstAddGoods(){
+ that.formAddGoodsChange(1,that.currentGoodsData)
+ that.touchOnAddGoods('#ggAddBtn',that.currentGoodsData)
+ //加入购物车
+ formAddGoodsChange(number,goodsData){
+ if(number >= 1){
+ for(let i in goodsData.form_child){
+ if(goodsData.form_child[i].select == true ){
+ that.currentGoodsData.current_form = goodsData.form_child[i]
+ that.addGoodsChange(number,that.currentGoodsData)
+ //选择规格
+ selectGoodsForm(goods,formChild){
+ let data = goods.form_child
+ let n = 0
+ for (var i in data){
+ if(data[i].id == formChild.id){
+ data[i].select = true
+ data[i].select = false
+ if(goodsCart[i].id == goods.id && goodsCart[i].current_form.id == formChild.id){
+ n = goodsCart[i].number
+ goods.number = n
+ this.currentGoodsData = goods
+ //获取该商品在购物车的数量
+ getCartGoodsNum(goods){
+ let cart = this.getStoreCart()
+ if(cart){
+ for(let i in cart){
+ if(cart[i].id == goods.id){
+ n += cart[i].number
+ return n
+ //获取门店购物车
+ getStoreCart(){
+ return that.shoppCart
+ //---------------------------------------------------------------------------------
+ swiperChange(index) {
+ this.swiperCurrent = index;
+ this.swiperCurrentSliderLeft= 70 * index;
+ transition({ detail: { dx } }) {
+ // this.$refs.tabs.setDx(dx);
+ animationfinish({detail: { current }}) {
+ /* this.$refs.tabs.setFinishCurrent(current); */
+ this.swiperCurrent = current;
+ this.current = current;
+ this.swiperChange(current);
+ this.showFoot = current == 0 && this.showStoreBox != true ? true : false;
+ //一级分类点击
+ showCategory(item){
+ that.isBoxScroll = true;
+ that.menuCurrentId = item.id;
+ let index = that.categoryList.findIndex(sitem=>sitem.id === item.id);
+ that.tabScrollTop = that.categoryList[index].top;
+ that.isBoxScroll = false
+ //右侧栏滚动
+ asideScroll(e){
+ const that = this;
+ const scrollTop = Math.round(e.detail.scrollTop);
+ that.calcSize()
+ const tabs = that.categoryList.filter(item=>item.top <= scrollTop).reverse();
+ if(tabs.length > 0){
+ that.menuCurrentId = tabs[0].id;
+ const menuNum = that.categoryList.length
+ const cNum = tabs.length
+ // 定位在第4个分类,当分类滑动到第4格时将不再变换位置。
+ const n = 4
+ if(cNum>n){
+ that.menuScrollTop = (cNum - n) * 45
+ that.menuScrollTop = 0
+ //计算右侧栏每个tab的高度等信息
+ calcSize(event){
+ let h = 0;
+ // if(this.sizeCalcState){
+ // return false
+ this.categoryList.forEach(item=>{
+ let view = uni.createSelectorQuery().select("#goodsBox" + item.id);
+ size: true
+ item.top = h;
+ h += Math.round(data.height);
+ item.bottom = h;
+ this.sizeCalcState = true;
+ //小球跳跃动画
+ touchOnAddGoods(ele,data){
+ q.select(ele).boundingClientRect(res => {
+ that.$refs.inCart.addToCart(res,data.id);
+ //新增商品计算价格
+ addGoodsChange(number,rowData){
+ number = Number(number)
+ let shoppCart = [];
+ let totalPrice = 0;
+ let totalNumber = 0;
+ let existedGoods = false;
+ //门店第一次添加商品
+ let isFirstAddGoods = true;
+ //是否为有规格的商品
+ let isFormGoods = false
+ if(rowData.current_form){
+ isFormGoods = true
+ let deleteGoods = null
+ if(carts.length != 0){
+ isFirstAddGoods = false
+ shoppCart = carts[i].shopping_cart ? carts[i].shopping_cart : [];
+ //检查该商品是否为第一次添加,
+ for(var i in shoppCart){
+ // if(shoppCart[i].id == rowData.id){
+ // // 是多规格商品
+ // if(isFormGoods){
+ // //规格相同
+ // if(shoppCart[i].current_form.id == rowData.current_form.id){
+ // //数量为0,在购物车中移除该商品
+ // if(number <= 0){
+ // deleteGoods = shoppCart[i];
+ // break;
+ // }else{
+ // // 直接修改商品数量,并计算出单品合计
+ // shoppCart[i].price = rowData.current_form.price
+ // shoppCart[i].oldprice = rowData.current_form.oldprice
+ // shoppCart[i].total = number * rowData.current_form.price
+ // // 不是多规格商品
+ if(shoppCart[i].id == rowData.id){
+ if(isFormGoods){
+ //相同商品比较规格是否也相同
+ if(shoppCart[i].current_form.id == rowData.current_form.id){
+ existedGoods = true;
+ if(existedGoods){
+ //在购物车中移除该商品
+ if(number <= 0){
+ deleteGoods = shoppCart[i];
+ //非第一次添加,直接修改商品数量,并计算出单品合计
+ shoppCart[i].price = rowData.current_form.price
+ shoppCart[i].oldprice = rowData.current_form.oldprice
+ shoppCart[i].total = number * rowData.current_form.price
+ shoppCart[i].price = rowData.price
+ shoppCart[i].total = number * rowData.price
+ shoppCart[i].oldprice = rowData.oldprice
+ shoppCart[i].number = rowData.number = number
+ if(deleteGoods != null){
+ if(carts){
+ var index = shoppCart.indexOf(deleteGoods);
+ if (index > -1) {
+ shoppCart.splice(index, 1);
+ carts[i].shopping_cart = shoppCart
+ uni.setStorageSync(that.shoppingCartStorageName,carts);
+ that.storeData.shopping_cart = []
+ uni.setStorageSync(that.shoppingCartStorageName,that.storeData);
+ //第一次添加
+ if(!existedGoods){
+ if(rowData.form){
+ rowData.price = rowData.current_form.price
+ rowData.oldprice = rowData.current_form.oldprice
+ rowData.total = number * rowData.current_form.price
+ rowData.total = number * rowData.price
+ rowData.number = number;
+ shoppCart.push(rowData);
+ //计算总商品数量和总价
+ //总价
+ totalPrice += shoppCart[i].total
+ totalNumber += shoppCart[i].number
+ //更改商品列表中的已购买数量
+ for(let i in that.goodsList){
+ if(that.goodsList[i].id == rowData.id){
+ that.goodsList[i] = rowData
+ that.goodsTotalPrice = totalPrice
+ that.goodsTotalNumber = totalNumber
+ that.shoppCart = shoppCart;
+ that.storeData.shopping_cart = shoppCart;
+ if(isFirstAddGoods){
+ carts.push(that.storeData)
+ if(that.goodsTotalNumber == 0){
+ that.hideShoppingCar();
+ //购物车商品数据缓存至本地
+ //计算每类商品购买的总数
+ setLabelNumber(){
+ //计算每一类购买商品的总数量
+ for(let j in that.categoryList){
+ let n = 0;
+ for(var i in that.shoppCart){
+ if(that.shoppCart[i].type_id == that.categoryList[j].id){
+ n += that.shoppCart[i].number;
+ that.categoryList[j].number = n;
+ //去结算
+ jiesuan(){
+ this.navTo("/pages/order/preview?sid=" + this.storeData.store_id)
+ navTo(url){
+ if(that.navStatus){
+ that.navStatus = false
+ uni.navigateTo({
+ url: url,
+ complete:function(){
+ that.navStatus = true
+ //联系商家
+ contact(){
+ title:"",
+ content:"联系商家"
+ showStoreBoxFunc(){
+ this.storeInfoBoxHeight = uni.getSystemInfoSync().screenHeight-136;
+ this.$set(this.$data,'showFoot',false);
+ this.showStoreBox = true;
+ duration:0,
+ scrollTop:0
+ hiddenStoreBoxFunc(){
+ this.storeInfoBoxHeight = 100;
+ this.showStoreBox = false;
+ if(this.swiperCurrent == 0){
+ this.$set(this.$data,'showFoot',true);
+ mpClear(e) {
+ // TODO nvue 取消冒泡
+ e.stopPropagation()
+ showShoppingCar(){
+ if(this.goodsTotalNumber == 0){
+ this.footHeight = '100%';
+ this.showCar = true;
+ this.carGoodsScrollTop = 0;
+ //隐藏购物车
+ hideShoppingCar(){
+ this.footHeight = '0';
+ this.showCar = false;
+ //清空该门店的购物车
+ clearShoppingCart(){
+ that.shoppCart = [];
+ that.storeData.shopping_cart = [];
+ for(let i in that.shoppingCartAll){
+ if(that.shoppingCartAll[i].store_id == that.storeData.store_id){
+ that.shoppingCartAll[i] = that.storeData
+ uni.setStorageSync(that.shoppingCartStorageName,that.shoppingCartAll);
+ that.goodsList[i].number = 0;
+ for(let i in that.categoryList){
+ that.categoryList[i].number = 0;
+ hrefGoodsInfo(id){
+ this.navTo('/pages/product/product?id=' + id)
+ //拨打电话
+ goCall(phone){
+ if(!phone){
+ uni.makePhoneCall({
+ phoneNumber: phone //仅为示例
+ touchStart(e){
+ this.touchData.clientX=e.changedTouches[0].clientX;
+ this.touchData.clientY=e.changedTouches[0].clientY;
+ //主题颜色
+ $hx-theme-color: #FFC107;
+ $hx-theme-color-light: #FFEB3B;
+ .add{
+ right: 60upx;
+ top: 300upx;
+ z-index: 999;
+ .ctn{
+ /* border: 1px solid #e3e3e3; */
+ justify-content: flex-end;
+ .searchCtn{
+ border-radius: 80upx;
+ padding: 8upx 12upx;
+ line-height: 44upx;
+ background: #e0e0e0;
+ min-width: 22px;
+ .leftBox{
+ width: 53px;
+ flex: none;
+ margin: 0 8px;
+ .jrNull{
+ /* #ifdef MP */
+ width: 95px;
+ /* #endif */
+ page{
+ .hx-bb{
+ border-bottom: 1px solid $uni-border-color;
+ .hx-txt-10{
+ font-size: 10px;
+ .hx-txt-12{
+ .hx-txt-14{
+ font-size: 14px;
+ .hx-txt-16{
+ .hx-txt-18{
+ font-size: 18px;
+ .hx-txt-22{
+ font-size: 22px;
+ .hx-color-gray{
+ color: #bbbbbb;
+ .hx-color-white{
+ color: #FFFFFF;
+ .hx-color-black{
+ .hx-txt-weigth{
+ .hx-mb-10{
+ margin-bottom: 10px;
+ .hx-mt-15{
+ margin-top: 15px;
+ .hx-shadow{
+ box-shadow: 0px 6upx 16upx rgba(173, 173, 173, 0.2);
+ .miniBtn{
+ padding: 0 12px;
+ border-radius: 20px;
+ line-height: 24px;
+ background: linear-gradient(100deg, #FFEB3B, #FFC107);
+ .num{
+ top: -10px;
+ width: 18px;
+ height: 18px;
+ line-height: 18px;
+ background-color: #ff5722;
+ .hx-store{
+ .header{
+ min-height: 230px;
+ &-bg{
+ height: 142px;
+ &-bg-black{
+ top: 142px;
+ background-color: #ffffff;
+ transition: background-color 0.2s;
+ &-bg-gray{
+ bottom: -16px;
+ background-color: #afafaf;
+ &-top-Placeholder{
+ height: 105px;
+ .storeInfo{
+ padding: 12px;
+ margin-bottom: 4px;
+ transition: all 0.2s;
+ .shrink-box{
+ font-size: 20px;
+ color: #a2a8ab;
+ .storeAvatar{
+ width: 50px;
+ right: 16px;
+ top: -25px;
+ border-radius: 4px;
+ .container{
+ margin: 0 32upx;
+ .tabs-box{
+ position: sticky;
+ top: calc(44px + var(--status-bar-height));
+ z-index: 10;
+ background-color: white;
+ border-bottom: 1px solid #efefef;
+ .hx-tabs{
+ height:100%;
+ &-item{
+ flex-direction: row;
+ width: 70px;
+ color:#666666;
+ text{
+ &-active{
+ color:#333333;
+ &-slider-box{
+ &-slider{
+ background: #f6d200;
+ width: 30px;
+ height: 3px;
+ .main{
+ #mainSwiper{
+ background-color: #eeeeee;
+ top: calc(40px + 44px + var(--status-bar-height));
+ .scroll-items{
+ // 商品列表样式
+ .category-list{
+ padding-bottom: 50px;
+ .left,.right{
+ top:0;
+ bottom: 0upx;
+ .left{
+ width: 24%;
+ left: 0upx;
+ background-color: #f6f3f3;
+ .row{
+ height: 45px;
+ .text{
+ color:#999999;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ padding: 0 16px;
+ &-number{
+ right: 4px;
+ top: 4px;
+ background: #ff5722;
+ &.active{
+ .row:last-child{
+ margin-bottom: 200upx;
+ .right{
+ width: 76%;
+ left: 24%;
+ .goodsListBox{
+ padding-bottom: 100px;
+ .category{
+ // width: 94%;
+ padding: 0 15px 10px 15px;
+ .s-item{
+ line-height: 45px;
+ color: #555555;
+ z-index: 18;
+ .list:last-child{
+ .list{
+ margin-bottom: 20px;
+ flex-wrap: wrap;
+ .box:first-child{
+ .m-store-item{
+ margin-top: 0;
+ .box{
+ align-items: flex-end;
+ margin-bottom: 15px;
+ .m-img{
+ flex: 0 0 85px;
+ height: 85px;
+ background: #eee;
+ .m-text{
+ padding: 0 6px;
+ align-content: space-between;
+ .m-title{
+ color:#555555;
+ height: 21px;
+ .m-descripe{
+ margin-top: 5px;
+ height: 35px;
+ .m-price-box{
+ .symbol{
+ color:#ff582b;
+ .m-price{
+ top: 2px;
+ .m-old-price{
+ margin-left: 3px;
+ margin-top: 5upx;
+ font-weight: normal;
+ .m-distance{
+ bottom: -4px;
+ z-index: 16;
+ color:#b2b2b2;
+ font-size: 20upx;
+ .jumpPosition{
+ bottom: 23px;
+ width: 28px;
+ height: 28px;
+ .evaluate-box{
+ .business-box{
+ .info-list{
+ padding: 0 15px;
+ &-container{
+ line-height: 46px;
+ height: 46px;
+ [class*="hxicon-"]{
+ color: #a2a2a2;
+ margin-right: 8px;
+ float: right;
+ color: #dddddd;
+ align-items:center;
+ .foot{
+ justify-content:center;
+ align-items : center;
+ /*width: calc(100% - 32px);*/
+ .btn-box{
+ position:absolute;
+ bottom: 15px;
+ margin:0;
+ width: calc(100% - 32px);
+ z-index: 9;
+ &-left{
+ background: #222222;
+ border-top-left-radius:50px;
+ border-top-right-radius:9px;
+ border-bottom-left-radius:50px;
+ border-bottom-right-radius:9px;
+ padding-left: 6upx;
+ flex-direction:column;
+ align-self: center;
+ color: #f6d200;
+ .imgBox{
+ image{
+ width: 20px;
+ height: 20px;
+ &-line{
+ width: 2px;
+ &-center{
+ flex: auto;
+ justify-content:flex-start;
+ border-top-left-radius:8upx;
+ border-bottom-left-radius:8upx;
+ padding-left: 10upx;
+ .cart{
+ width: 36px;
+ height: 36px;
+ .tag{
+ right: 12upx;
+ top: 16upx;
+ background-color: #ff4000;
+ .priceBox{
+ &-right{
+ .pscj{
+ border-top-right-radius:100upx;
+ border-bottom-right-radius:100upx;
+ .jiesuan{
+ font-size: 28upx;
+ background: linear-gradient(45deg, $hx-theme-color-light, $hx-theme-color);
+ color: #222222;
+ .zz{
+ background-color: rgba(0,0,0,.7);
+ .cart-box{
+ justify-content: flex-start;
+ flex-flow: column;
+ max-height: 66%;
+ padding-bottom: 62px;
+ border-top-left-radius: 16px;
+ border-top-right-radius: 16px;
+ .rebate-box{
+ height: 30px;
+ background: #FFC107;
+ color: #FF9900;
+ line-height: 30px;
+ .box-container{
+ padding:0 16px;
+ .operating-box{
+ line-height: 40px;
+ border-bottom: 1px solid #f6f6f6;
+ &_right{
+ &_left{
+ .goods-box{
+ .goods-list-scroll{
+ .goods-list{
+ padding-top: 15px;
+ padding-bottom: 15px;
+ .form-main{
+ justify-content: left;
+ padding: 15px;
+ .form-main_ctn{
+ padding: 23px 22px 22px 22px;
+ border-radius: 8px;
+ .godos_tit{
+ margin-top: 4px;
+ margin-bottom: 6px;
+ .gg_tit{
+ margin-top: 8px;
+ color: #555;
+ .gg_box{
+ font-size: 12;
+ .item{
+ margin-right: 14px;
+ margin-bottom: 14px;
+ border: 1px solid #f1f1f1;
+ padding: 4px 6px;
+ .item.active{
+ border-color: #ffe081;
+ background-color: #fff0b7;
+ .select_gg{
+ margin: 26px -12px 0;
+ padding: 6px 12px;
+ background-color: #f9f9f9;
+ .lable{
+ .select_gg_box{
+ .gg-item{
+ .gg-item-cut{
+ margin-right: 3px;
+ .gg-item:last-child{
+ .bottom{
+ margin-top: 12px;
+ .price_box{
+ lign-items: baseline;
+ color: #ff582b;
+ .price{
+ font-size: 24px;
+ .form-btn-box{
+ .add-btn{
+ border-radius: 50px;
+ background-color: #ffce3c;
+ i{
+ margin-left: -4px;
+ color: #363636;
+ .close{
+ margin-left: -20px;
+ bottom: -70px;
+ width: 40px;
+ line-height: 43px;
@@ -0,0 +1,1067 @@
+<!-- 商品分类列表 -->
+ <s-layout title="分类" :bgStyle="{ color: '#fff' }">
+ <view class="s-category">
+ <view class="three-level-wrap ss-flex ss-col-top" :style="[{ height: pageHeight + 'px' }]">
+ <!-- 商品分类(左) -->
+ <scroll-view class="side-menu-wrap" scroll-y :style="[{ height: pageHeight + 'px' }]">
+ <view class="menu-item ss-flex" v-for="(item, index) in state.categoryList" :key="item.id"
+ :class="[{ 'menu-item-active': index === state.activeMenu }]" @tap="onMenu(index)">
+ <view class="menu-title ss-line-1">
+ {{ item.name }}
+ <!-- 商品分类(右) -->
+ <scroll-view scroll-with-animation :scroll-y="goodsBoxScroll" class="right" @scroll="asideScroll"
+ :scroll-top="tabScrollTop">
+ <view class="category" v-for="item in state.categoryList" :key="item.id" :id="'goodsBox'+item.id">
+ <view class="box" v-for="(rowData,i) in item.children" :key="rowData.id" >
+ <!-- -->
+ <view class="m-store-item" v-if="item.id == rowData.parentId">
+ <image style="width: 100%;height: 100%;" :src="rowData.image"
+ alt="Product Image"></image>
+ <template v-if="rowData.form">
+ <span>¥{{rowData.form_child[0].oldprice}}</span>
+ <span>¥{{rowData.oldprice}}</span>
+ <view class="m-distance">
+ <span>选规格</span>
+ <view class="num" v-if="getCartGoodsNum(rowData)">
+ {{getCartGoodsNum(rowData)}}
+ <hx-number-box @change="addGoodsChange" :value="rowData.number"
+ :rowData="rowData" :clickTime="animaTime"
+ @addChange="touchOnAddGoods('.addEle_' + i,rowData)"></hx-number-box>
+ <!-- </scroll-view> -->
+ <!-- <image v-if="state.categoryList[state.activeMenu].picUrl" class="banner-img"
+ :src="sheep.$url.cdn(state.categoryList[state.activeMenu].picUrl)" mode="widthFix" />
+ <first-one v-if="state.style === 'first_one'" :pagination="state.pagination" />
+ <first-two v-if="state.style === 'first_two'" :pagination="state.pagination" />
+ <second-one v-if="state.style === 'second_one'" :data="state.categoryList"
+ :activeMenu="state.activeMenu" /> -->
+ <uni-load-more v-if="
+ (state.style === 'first_one' || state.style === 'first_two') &&
+ state.pagination.total > 0
+ " :status="state.loadStatus" :content-text="{
+ contentdown: '点击查看更多',
+ <!-- 下方购物车 -->
+ <view class="foot" @touchmove.stop.prevent="mpClear" :style="{height: 50}" v-if="state.showFoot">
+ <view class="box" v-for="(rowData,i) in shoppCart" :key="rowData.id" >
+ import secondOne from './components/second-one.vue';
+ import firstOne from './components/first-one.vue';
+ import firstTwo from './components/first-two.vue';
+ import hxNumberBox from './components/uni-number-box.vue';
+ onLaunch,
+ onReachBottom
+ computed,
+ onMounted
+ style: 'second_one', // first_one(一级 - 样式一), first_two(二级 - 样式二), second_one(二级)
+ categoryList: [], // 商品分类树
+ goodsList:[],//分类下的商品
+ activeMenu: 0, // 选中的一级菜单,在 categoryList 的下标
+ // 商品分页
+ list: [], // 商品列表
+ total: [], // 商品总数
+ // const showTabbar = ref(true);
+ safeArea
+ } = sheep.$platform.device;
+ const pageHeight = computed(() => safeArea.height - 44 - 50);
+ onMounted(() => {
+ // 当组件挂载完成后隐藏底部TabBar
+ uni.hideTabBar({
+ animation: false, // 是否需要动画效果,默认为 false
+ success: function () {
+ console.log('TabBar已隐藏');
+ fail: function (err) {
+ console.error('隐藏TabBar失败:', err);
+ // 加载商品分类
+ state.categoryList = handleTree(data);
+ console.log(state.categoryList,'state.categoryListstate.categoryList')
+ // state.categoryList = [
+ // {id: 1,name: '烧烤'},
+ // {id: 2,name: '生鲜'},
+ // {id: 3,name: '绿蔬'},
+ // {id: 4,name: '肉类'},
+ // {id: 5,name: '川味'},
+ // {id: 6,name: '粤菜'},
+ // {id: 7,name: '湘菜'},
+ // {id: 8,name: '西餐'},
+ // {id: 9,name: '饮料'},
+ // {id: 10,name: '糕点'},
+ // {id: 11,name: '凉菜'},
+ // {id: 12,name: '火锅'},
+ // {id: 13,name: '干锅'}
+ // ];
+ // state.goodsList = [{
+ // id: 1,
+ // type_id:1,
+ // name:'白果王水果沙拉',
+ // descripe:"脆糯营养,口感好,健康绿色",
+ // img:'//imgs.1op.cn/i/hxshop/goods/14.jpg',
+ // price:"",
+ // oldprice:"",
+ // //规格
+ // form: {id:1,name:"尺寸"},
+ // form_child:[
+ // {id:81,pid:1,name:"8寸500g", price:"46", oldprice:"100", select:true},
+ // {id:82,pid:1,name:"10寸600g", price:"97", oldprice:"100",select:false},
+ // {id:83,pid:1,name:"12寸800g", price:"135", oldprice:"100",select:false},
+ // {id:84,pid:1,name:"四川麻辣", price:"12", oldprice:"100",select:false},
+ // {id:85,pid:1,name:"香辣", price:"20", oldprice:"100",select:false},
+ // {id:86,pid:1,name:"卤香", price:"90", oldprice:"100",select:false},
+ // {id:87,pid:1,name:"鲜甜广味", price:"80", oldprice:"100",select:false},
+ // {id:88,pid:1,name:"镇店茴香味", price:"100", oldprice:"100",select:false}
+ // ]
+ // id: 2,
+ // type_id:2,
+ // name:'精品烤山药',
+ // img: '//imgs.1op.cn/i/hxshop/goods/12.jpg',
+ // {id:81,pid:1,name:"8寸500g", price:"78", oldprice:"100", select:true},
+ // id: 3,
+ // name:'川味毛血旺',
+ // img: '//imgs.1op.cn/i/hxshop/goods/11.jpg',
+ // price:"4",
+ // id: 4,
+ // type_id:3,
+ // name:'吐鲁番烤全羊',
+ // img: '//imgs.1op.cn/i/hxshop/goods/10.jpg',
+ // oldprice:""
+ // id: 5,
+ // name:'红烧肉',
+ // img: '//imgs.1op.cn/i/hxshop/goods/9.jpg',
+ // id: 6,
+ // type_id:4,
+ // name:'新疆特色辣子鸡',
+ // img: '//imgs.1op.cn/i/hxshop/goods/8.jpg',
+ // id: 106,
+ // name:'新疆特色羊排',
+ // id: 7,
+ // type_id:5,
+ // name:'绝味海鲜拼盘',
+ // img: '//imgs.1op.cn/i/hxshop/goods/7.jpg',
+ // id: 8,
+ // name:'金色香糯大粽子',
+ // img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',
+ // id: 9,
+ // name:'马梓林香香鸡',
+ // img: '//imgs.1op.cn/i/hxshop/goods/5.jpg',
+ // id: 10,
+ // type_id:6,
+ // name:'草莓味莫普氏蛋糕',
+ // img: '//imgs.1op.cn/i/hxshop/goods/4.jpg',
+ // id: 23,
+ // id: 24,
+ // id: 25,
+ // type_id:7,
+ // id: 26,
+ // id: 27,
+ // {id: 28,type_id:8,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',price:"4",oldprice:""},
+ // {id: 29,type_id:8,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/5.jpg',price:"4",oldprice:""},
+ // {id: 30,type_id:8,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/4.jpg',price:"4",oldprice:""},
+ // {id: 31,type_id:9,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/16.jpg',price:"4",oldprice:""},
+ // {id: 32,type_id:9,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/17.jpg',price:"4",oldprice:""},
+ // {id: 33,type_id:9,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/15.jpg',price:"4",oldprice:""},
+ // {id: 46,type_id:8,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',price:"4",oldprice:""},
+ // {id: 34,type_id:10,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/14.jpg',price:"4",oldprice:""},
+ // {id: 35,type_id:10,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/13.jpg',price:"4",oldprice:""},
+ // {id: 45,type_id:8,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',price:"4",oldprice:""},
+ // {id: 36,type_id:10,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/12.jpg',price:"4",oldprice:""},
+ // {id: 37,type_id:10,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/11.jpg',price:"4",oldprice:""},
+ // {id: 38,type_id:11,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/10.jpg',price:"4",oldprice:""},
+ // {id: 44,type_id:8,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',price:"4",oldprice:""},
+ // {id: 39,type_id:12,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/9.jpg',price:"4",oldprice:""},
+ // {id: 40,type_id:12,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/8.jpg',price:"4",oldprice:""},
+ // {id: 13,type_id:13,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',price:"4",oldprice:""},
+ // {id: 41,type_id:12,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/7.jpg',price:"4",oldprice:""},
+ // {id: 42,type_id:13,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/1.jpg',price:"4",oldprice:""}
+ // 选中菜单
+ const onMenu = (val) => {
+ state.activeMenu = val;
+ if (state.style === 'first_one' || state.style === 'first_two') {
+ // 加载商品列表
+ const res = await SpuApi.getSpuPage({
+ categoryId: state.categoryList[state.activeMenu].id,
+ // 加载更多商品
+ await getList();
+ // 如果是 first 风格,需要加载商品分页
+ onMenu(0);
+ console.log('Component is shown');
+ // 隐藏原生tabBar
+ .s-category {
+ .side-menu-wrap {
+ width: 200rpx;
+ padding-left: 12rpx;
+ .menu-item {
+ height: 88rpx;
+ margin-left: 28rpx;
+ z-index: 0;
+ width: 64rpx;
+ height: 12rpx;
+ background: linear-gradient(90deg,
+ var(--ui-BG-Main-light)) !important;
+ left: -64rpx;
+ &.menu-item-active {
+ border-radius: 20rpx 0 0 20rpx;
+ bottom: -20rpx;
+ width: 20rpx;
+ height: 20rpx;
+ background: radial-gradient(circle at 0 100%, transparent 20rpx, #fff 0);
+ top: -20rpx;
+ background: radial-gradient(circle at 0% 0%, transparent 20rpx, #fff 0);
+ .goodsListBox {
+ .category {
+ .s-item {
+ .list:last-child {
+ .list {
+ .box:first-child {
+ .m-store-item {
+ .box {
+ .m-img {
+ .m-text {
+ .m-title {
+ .m-descripe {
+ .m-price-box {
+ .symbol {
+ .m-price {
+ .m-old-price {
+ .m-distance {
+ color: #b2b2b2;
+ .jumpPosition {
+ width: calc(100vw - 100px);
+ padding: 10px;
+ .banner-img {
+ width: calc(100vw - 130px);
+ border-radius: 5px;
+ // background-color: rgba(0,0,0,.7);
@@ -0,0 +1,370 @@
+ <s-layout title="分类" tabbar="/pages/index/category" :bgStyle="{ color: '#fff' }">
+ <view style="background-color: #fff;display: flex;justify-content: flex-end;">
+ <view style="background-color:#fff;width: 200rpx;">
+ <uni-search-bar v-if="!navbar" class="ss-flex-1" radius="40" placeholder="请输入" cancelButton="none"
+ clearButton="none" @confirm="onSearch" v-model="state.searchVal" />
+ <scroll-view class="goods-list-box" scroll-y :style="[{ height: pageHeight + 'px' }]"
+ v-if="state.pagination.list?.length">
+ <view v-if=" state.pagination.total > 0" class="goods-list ss-m-t-20">
+ <view class="ss-p-l-20 ss-p-r-20 ss-m-b-20" v-for="(item,index) in state.pagination.list"
+ :key="item.id">
+ <s-goods-column class="" size="lg" :data="item" :index="index" :topRadius="10" :bottomRadius="10"
+ @click="sheep.$router.go('/pages/goods/index', { id: item.id })" />
+ v-if="state.categoryList[state.activeMenu].picUrl"
+ class="banner-img"
+ :src="sheep.$url.cdn(state.categoryList[state.activeMenu].picUrl)"
+ mode="widthFix"
+ <second-one
+ v-if="state.style === 'second_one'"
+ :data="state.categoryList"
+ :activeMenu="state.activeMenu"
+ /> -->
+ <!-- v-if="state.list.length > 0" -->
+ <su-fixed bottom :val="48" placeholder :isInset="false">
+ <view class="cart-footer ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom" @click="goCartList">
+ <!-- <label class="check-box ss-flex ss-col-center ss-p-r-30" @tap="onSelectAll">
+ </label> -->
+ {{ state.totalPriceSelected?fen2yuan(state.totalPriceSelected):0 }}
+ <button v-else class="ss-reset-button ui-BG-Main-Gradient pay-btn ui-Shadow-Main" @tap.stop="onConfirm">
+ import hxNumberBox from './components/uni-number-box';
+ import sGoodsColumn from './components/s-goods-column.vue';
+ reactive
+ fen2yuan
+ } from '../../sheep/hooks/useGoods';
+ // uni.hideTabBar();
+ console.log(state.categoryList[state.activeMenu], 'state.categoryList[state.activeMenu]')
+ state.pagination.list = _.concat(state.pagination.list, res.data.list)
+ console.log(val, 'valllll')
+ console.log(state.style, 'state.style')
+ // if (state.style === 'first_one' || state.style === 'first_two') {
+ // state.pagination.pageNo = 1;
+ // state.pagination.list = [];
+ // state.pagination.total = 0;
+ // state.pagination.list = _.concat(state.pagination.list, res.data.list);
+ state.pagination.list = res.data.list
+ function goCartList(){
+ url:'/pages/index/cart'
+ // const id=state.categoryList[state.activeMenu].id
+ // state.categoryId = id;
+ // state.keyword = options.keyword;
+ background-color: #313131;
+ border-radius: 49rpx;
@@ -0,0 +1,26 @@
+<!-- 分类展示:first-one 风格 -->
+ <view class="goods-box" v-for="item in pagination.list" :key="item.id">
+ size="sl"
+ pagination: Object,
@@ -0,0 +1,66 @@
+<!-- 分类展示:first-two 风格 -->
+ <view class="ss-flex flex-wrap">
+ <view class="goods-box" v-for="item in pagination?.list" :key="item.id">
+ <view @click="sheep.$router.go('/pages/goods/index', { id: item.id })">
+ <view class="goods-img">
+ <image class="goods-img" :src="item.picUrl" mode="aspectFit" />
+ <view class="goods-content">
+ <view class="goods-title ss-line-1 ss-m-b-28">{{ item.title }}</view>
+ <view class="goods-price">¥{{ fen2yuan(item.price) }}</view>
+ width: calc((100% - 20rpx) / 2);
+ height: 246rpx;
+ border-radius: 10rpx 10rpx 0px 0px;
+ .goods-content {
+ box-shadow: 0px 0px 20rpx 4rpx rgba(199, 199, 199, 0.22);
+ padding: 20rpx 0 32rpx 16rpx;
+ border-radius: 0 0 10rpx 10rpx;
+ .goods-price {
+ color: #e1212b;
+ &:nth-child(2n + 1) {
@@ -0,0 +1,744 @@
+<!-- 页面 -->
+ <view class="ss-goods-wrap">
+ <!-- lg卡片:横向型,一行放一个,图片左内容右边 -->
+ <view v-if="size === 'lg'" class="lg-goods-card ss-flex ss-col-stretch" :style="[elStyles]" @tap="onClick">
+ <view v-if="tagStyle.show" class="tag-icon-box">
+ <image class="tag-icon" :src="sheep.$url.cdn(tagStyle.src || tagStyle.imgUrl)"></image>
+ <view v-if="seckillTag" class="seckill-tag ss-flex ss-row-center"> 秒杀 </view>
+ <view v-if="grouponTag" class="groupon-tag ss-flex ss-row-center">
+ <view class="tag-icon">拼团</view>
+ <image class="lg-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFill"></image>
+ <view class="lg-goods-content ss-flex-1 ss-flex-col ss-p-b-10 ss-p-t-20">
+ <view v-if="goodsFields.title?.show || goodsFields.name?.show" class="lg-goods-title ss-line-2"
+ :style="[{ color: titleColor }]">
+ {{ data.title || data.name }}
+ <!-- <view v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
+ class="lg-goods-subtitle ss-m-t-10 ss-line-1"
+ :style="[{ color: subTitleColor, background: subTitleBackground }]">
+ {{ data.subtitle || data.introduction }}
+ <view class="ss-m-t-8 ss-flex ss-col-center ss-flex-wrap">
+ <view class="sales-text">{{ salesAndStock }}</view>
+ <view style="display: flex;justify-content: space-between;">
+ <slot name="activity">
+ <view v-if="data.promos?.length" class="tag-box ss-flex ss-col-center">
+ <view class="activity-tag ss-m-r-10" v-for="item in data.promos" :key="item.id">
+ {{ item.title }}
+ </slot>
+ <view class="ss-flex ss-m-t-10 ss-center">
+ <view v-if="goodsFields.price?.show"
+ class="lg-goods-price ss-m-r-12 ss-flex ss-col-bottom font-OPPOSANS"
+ :style="[{ color: goodsFields.price.color }]">
+ <text class="ss-font-24">{{ priceUnit }}</text>
+ {{ isArray(data.price) ? fen2yuan(data.price[0]) : fen2yuan(data.price) }}
+ <slot name="cart">
+ <view class="buy-box ss-flex ss-col-center ss-row-center" v-if="buttonShow">
+ class="cicon-add-round"
+ :class="{
+ 'uni-numbox--disabled': inputValue >= max || disabled,
+ 'groupon-btn': activity === 'groupon',
+ 'seckill-btn': activity === 'seckill',
+ @click.stop="formFirstAddGoods(data.skus[0].id)"
+ v-if="!state.selectedSku.goods_num"
+ <view @click.stop v-else>
+ <su-number-box :min="0" :max="data.stock" :step="1" v-model="state.selectedSku.goods_num"
+ @change="formAddGoodsChange(data.skus[0].id,$event)"/>
+ <!-- <block v-if="!state.selectedSku.goods_num">
+ <view id="ggAddBtn" class="add-btn" @click="formFirstAddGoods(data.id)">
+ <hx-number-box @change="formAddGoodsChange(data.id,$event)"
+ :value="state.selectedSku.goods_num"></hx-number-box>
+ <!-- :rowData="currentGoodsData" :clickTime="animaTime + 200" @addChange="touchOnAddGoods('.addEle_gg',currentGoodsData)" -->
+ <!-- <view
+ v-if="(goodsFields.original_price?.show||goodsFields.marketPrice?.show) &&( data.original_price > 0|| data.marketPrice > 0)"
+ class="goods-origin-price ss-flex ss-col-bottom font-OPPOSANS"
+ :style="[{ color: originPriceColor }]">
+ <text class="price-unit ss-font-20">{{ priceUnit }}</text>
+ <view class="ss-m-l-8">{{ fen2yuan(data.marketPrice) }}</view>
+ <!-- <view class="ss-m-t-8 ss-flex ss-col-center ss-flex-wrap">
+ <!-- state.selectedSku.stock -->
+ <!-- sl卡片:竖向型,一行放一个,图片上内容下边 -->
+ <view v-if="size === 'sl'" class="sl-goods-card ss-flex-col" :style="[elStyles]" @tap="onClick">
+ <image class="sl-img-box" :src="sheep.$url.cdn(data.image || data.picUrl)" mode="aspectFill"></image>
+ <view class="sl-goods-content">
+ <view v-if="goodsFields.title?.show || goodsFields.name?.show" class="sl-goods-title ss-line-1"
+ <view v-if="goodsFields.subtitle?.show || goodsFields.introduction?.show"
+ class="sl-goods-subtitle ss-m-t-16"
+ <view v-if="data.promos?.length" class="tag-box ss-flex ss-col-center ss-flex-wrap">
+ <view class="activity-tag ss-m-r-10 ss-m-t-16" v-for="item in data.promos" :key="item.id">
+ <view v-if="goodsFields.price?.show" class="ss-flex ss-col-bottom font-OPPOSANS">
+ <view class="sl-goods-price ss-m-r-12" :style="[{ color: goodsFields.price.color }]">
+ <text class="price-unit ss-font-24">{{ priceUnit }}</text>
+ class="goods-origin-price ss-m-t-16 font-OPPOSANS ss-flex"
+ <view class="ss-m-t-16 ss-flex ss-flex-wrap">
+ <view class="buy-box ss-flex ss-col-center ss-row-center">去购买</view>
+ * 商品卡片
+ * @property {Array} size = [xs | sm | md | lg | sl ] - 列表数据
+ * @property {String} tag - md及以上才有
+ * @property {String} img - 图片
+ * @property {String} background - 背景色
+ * @property {String} topRadius - 上圆角
+ * @property {String} bottomRadius - 下圆角
+ * @property {String} title - 标题
+ * @property {String} titleColor - 标题颜色
+ * @property {Number} titleWidth = 0 - 标题宽度,默认0,单位rpx
+ * @property {String} subTitle - 副标题
+ * @property {String} subTitleColor - 副标题颜色
+ * @property {String} subTitleBackground - 副标题背景
+ * @property {String | Number} price - 价格
+ * @property {String} priceColor - 价格颜色
+ * @property {String | Number} originPrice - 原价/划线价
+ * @property {String} originPriceColor - 原价颜色
+ * @property {String | Number} sales - 销售数量
+ * @property {String} salesColor - 销售数量颜色
+ * @slots activity - 活动插槽
+ * @slots cart - 购物车插槽,默认包含文字,背景色,文字颜色 || 图片 || 行为
+ * @event {Function()} click - 点击卡片
+ getCurrentInstance,
+ onMounted,
+ nextTick,
+ watch
+ import $store from '@/sheep/store';
+ import CartApi from '@/sheep/api/trade/cart';
+ showAuthModal,
+ showShareModal
+ } from '@/sheep/hooks/useModal';
+ fen2yuan,
+ formatSales
+ } from '@/sheep/hooks/useGoods';
+ formatStock
+ import goodsCollectVue from '@/pages/user/goods-collect.vue';
+ import hxNumberBox from '@/pages/index/components/uni-number-box';
+ isArray
+ } from 'lodash';
+ selectedSku: {
+ goods_num: 0,
+ id: ''
+ }, // 选中的 SKU
+ () => state.selectedSku,
+ (newVal) => {
+ emits('change', newVal);
+ immediate: true, // 立即执行
+ deep: true, // 深度监听
+ function formFirstAddGoods(id){
+ console.log('我走了第一次新增购物车')
+ state.selectedSku.id = id
+ state.selectedSku.goods_num = 1
+ sheep.$store('cart').add(state.selectedSku);
+ function formAddGoodsChange(id,e) {
+ console.log('我走了更新购物车',id,e)
+ if(e<1){
+ // 走删除
+ CartApi.deleteCart(id)
+ // sheep.$store('cart').delete(id);
+ state.selectedSku.goods_id = id
+ state.selectedSku.goods_num = e
+ console.log( state.selectedSku, 'state.selectedSku')
+ sheep.$store('cart').updateCount(state.selectedSku);
+ function onNumberAdd(id) {
+ // 页面登录拦截
+ if (!$store('user').isLogin) {
+ console.log(88888888, '啊啊啊啊')
+ showAuthModal();
+ console.log(id, state.selectedSku.goods_num, 'state.selectedSku.goods_num')
+ // 输入框改变数量
+ function onNumberChange(e) {
+ e.stopPropagation();
+ console.log(e, '777')
+ if (e === 0) return;
+ if (state.selectedSku.goods_num === e) return;
+ state.selectedSku.goods_num = e;
+ // 加入购物车
+ function onAddCart() {
+ if (state.selectedSku.id <= 0) {
+ sheep.$helper.toast('请选择规格');
+ if (state.selectedSku.stock <= 0) {
+ sheep.$helper.toast('库存不足');
+ goodsFields: {
+ type: [Array, Object],
+ default () {
+ // 商品价格
+ show: true
+ // 库存
+ stock: {
+ // 商品名称
+ name: {
+ // 商品介绍
+ introduction: {
+ // 市场价
+ marketPrice: {
+ // 销量
+ salesCount: {
+ tagStyle: {
+ data: {
+ index: {
+ size: {
+ default: 'sl',
+ background: {
+ bottomRadius: {
+ titleWidth: {
+ titleColor: {
+ default: '#333',
+ priceColor: {
+ originPriceColor: {
+ default: '#C4C4C4',
+ priceUnit: {
+ default: '¥',
+ subTitleColor: {
+ default: '#999999',
+ subTitleBackground: {
+ buttonShow: {
+ seckillTag: {
+ grouponTag: {
+ // 组件样式
+ const elStyles = computed(() => {
+ background: props.background,
+ 'border-top-left-radius': props.topRadius + 'px',
+ 'border-top-right-radius': props.topRadius + 'px',
+ 'border-bottom-left-radius': props.bottomRadius + 'px',
+ 'border-bottom-right-radius': props.bottomRadius + 'px',
+ // 格式化销量、库存信息
+ const salesAndStock = computed(() => {
+ let text = [];
+ if (props.goodsFields.salesCount?.show) {
+ text.push(formatSales(props.data.sales_show_type, props.data.salesCount));
+ if (props.goodsFields.stock?.show) {
+ text.push(formatStock(props.data.stock_show_type, props.data.stock));
+ return text.join(' | ');
+ // 返回事件
+ const emits = defineEmits(['click', 'getHeight']);
+ // 获取卡片实时高度
+ proxy
+ } = getCurrentInstance();
+ const elId = `sheep_${Math.ceil(Math.random() * 10e5).toString(36)}`;
+ function getGoodsPriceCardWH() {
+ if (props.size === 'md') {
+ const view = uni.createSelectorQuery().in(proxy);
+ view.select(`#${elId}`).fields({
+ size: true,
+ scrollOffset: true
+ view.exec((data) => {
+ let totalHeight = 0;
+ const goodsPriceCard = data[0];
+ if (props.data.image_wh) {
+ totalHeight =
+ (goodsPriceCard.width / props.data.image_wh.w) * props.data.image_wh.h +
+ goodsPriceCard.height;
+ totalHeight = goodsPriceCard.width;
+ emits('getHeight', totalHeight);
+ nextTick(() => {
+ getGoodsPriceCardWH();
+ .tag-icon-box {
+ .tag-icon {
+ .seckill-tag {
+ background: linear-gradient(90deg, #ff5854 0%, #ff2621 100%);
+ border-radius: 10rpx 0px 10rpx 0px;
+ background: linear-gradient(90deg, #fe832a 0%, #ff6600 100%);
+ .price-unit {
+ margin-right: -4px;
+ display: table;
+ transform: scale(0.8);
+ margin-left: 0rpx;
+ .activity-tag {
+ color: #ff0000;
+ border: 1px solid rgba(#ff0000, 0.25);
+ // xs
+ .xs-goods-card {
+ // max-width: 375rpx;
+ .xs-img-box {
+ height: 128rpx;
+ .xs-goods-title {
+ .xs-goods-price {
+ // sm
+ .sm-goods-card {
+ // width: 223rpx;
+ .sm-img-box {
+ // width: 228rpx;
+ height: 208rpx;
+ .sm-goods-content {
+ padding: 20rpx 16rpx;
+ .sm-goods-title {
+ .sm-goods-price {
+ // md
+ .md-goods-card {
+ width: 54rpx;
+ height: 54rpx;
+ background: linear-gradient(90deg, #fe8900, #ff5e00);
+ bottom: 50rpx;
+ .cart-icon {
+ width: 30rpx;
+ // lg
+ .lg-goods-card {
+ .lg-img-box {
+ width: 190rpx;
+ .lg-goods-title {
+ // line-height: 36rpx;
+ // width: 410rpx;
+ .lg-goods-subtitle {
+ // line-height: 30rpx;
+ .lg-goods-price {
+ // position: absolute;
+ bottom: 20rpx;
+ // width: 120rpx;
+ // background: linear-gradient(90deg, #fe8900, #ff5e00);
+ .cicon-add-round {
+ font-size: 44rpx;
+ // sl
+ .sl-goods-card {
+ .sl-goods-content {
+ padding: 20rpx 20rpx;
+ .sl-img-box {
+ height: 360rpx;
+ .sl-goods-title {
+ .sl-goods-subtitle {
+ .sl-goods-price {
+ width: 148rpx;
+ .ss-center {
@@ -0,0 +1,80 @@
+<!-- 分类展示:second-one 风格 -->
+ <!-- 一级分类的名字 -->
+ <view class="title-box ss-flex ss-col-center ss-row-center ss-p-b-30">
+ <view class="title-line-left" />
+ <view class="title-text ss-p-x-20">{{ props.data[activeMenu].name }}</view>
+ <view class="title-line-right" />
+ <!-- 二级分类的名字 -->
+ <view class="goods-item-box ss-flex ss-flex-wrap ss-p-b-20">
+ class="goods-item"
+ v-for="item in props.data[activeMenu].children"
+ sheep.$router.go('/pages/goods/list', {
+ categoryId: item.id,
+ <image class="goods-img" :src="item.picUrl" mode="aspectFill" />
+ <view class="ss-p-10">
+ <view class="goods-title ss-line-1">{{ item.name }}</view>
+ default: () => ({}),
+ activeMenu: [Number, String],
+ .title-box {
+ .title-line-left,
+ .title-line-right {
+ width: 15px;
+ height: 1px;
+ background: #d2d2d2;
+ width: calc((100% - 20px) / 3);
+ margin-right: 10px;
+ &:nth-of-type(3n) {
+ width: calc((100vw - 140px) / 3);
+ height: calc((100vw - 140px) / 3);
@@ -0,0 +1,627 @@
+//商家信息
+const storeData = {
+ //商家唯一标识
+ store_id: '168',
+ //商家名称
+ store_name: '小太阳商店',
+ //头像
+ avatar: '//imgs.1op.cn/i/hxshop/avatar/3.png',
+ //横幅图片
+ banner: '//imgs.1op.cn/i/hxshop/banner/banner.jpg',
+ //详情
+ info: '店内用了本市最好的鲜肉,排骨;配料未本店祖传秘方,吃一次将永不忘。',
+ //商家住址
+ address: '新疆阿克苏是他北路2号',
+ //地标或社区
+ community: '天府名城',
+ //配送时间
+ delivery_time: '11:00~20:30',
+ //联系电话
+ telephone: '18299989916',
+ //商家购物车
+ shopping_cart: [],
+ shipping_dees: 0,
+ starting_price:30
+//首页门店列表
+const storeList = [
+ //商户标识
+ store_id: '852',
+ //店名
+ name: '田东日式料理',
+ //门头
+ avatar: '//imgs.1op.cn/i/hxshop/goods/14.jpg',
+ //小区
+ community: '秦阳店',
+ //评分
+ mark: '4.8',
+ //月售
+ monthly_sales: 356,
+ //门店距离,按米计算
+ distance: 500,
+ //起送价
+ starting_price: 3,
+ shipping_dees: 15,
+ goods:[{
+ //id
+ id: '235',
+ //商品名称
+ name: '画画酱酱面',
+ //主图
+ main_pic: '//imgs.1op.cn/i/hxshop/goods/10.jpg',
+ //标签 【招牌,爆款,推荐】等等
+ tag: '招牌',
+ //现价
+ price: 45,
+ //原价
+ old_price: 65,
+ ,{id: '236', name: '白色米', main_pic: '//imgs.1op.cn/i/hxshop/goods/6.jpg', tag: '招牌', price: 45, old_price: 65,}
+ ,{id: '237', name: '小羊肉', main_pic: '//imgs.1op.cn/i/hxshop/goods/2.jpg', tag: '爆款', price: 45, old_price: 65,}
+ ,{id: '238', name: '烤鸡翅', main_pic: '//imgs.1op.cn/i/hxshop/goods/3.jpg', tag: '爆款', price: 45, old_price: 65,}
+ ,{id: '239', name: '爆爆鱼', main_pic: '//imgs.1op.cn/i/hxshop/goods/4.jpg', tag: '推荐', price: 45, old_price: 65,}
+ ,{id: '240', name: '生吃肉票', main_pic: '//imgs.1op.cn/i/hxshop/goods/5.jpg', tag: '推荐', price: 45, old_price: 65,}
+ ,{
+ store_id: '853',
+ name: '十里飘香烧烤',
+ avatar: '//imgs.1op.cn/i/hxshop/goods/2.jpg',
+ community: '',
+ goods:[
+ {id: '236', name: '可乐鸡翅buibui爽', main_pic: '//imgs.1op.cn/i/hxshop/goods/7.jpg', tag: '招牌', price: 45, old_price: 65,}
+ ,{id: '237', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/8.jpg', tag: '爆款', price: 45, old_price: 65,}
+ ,{id: '238', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/9.jpg', tag: '爆款', price: 45, old_price: 65,}
+ ,{id: '239', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/10.jpg', tag: '推荐', price: 45, old_price: 65,}
+ ,{id: '240', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/11.jpg', tag: '推荐', price: 45, old_price: 65,}
+ name: '德克士',
+ avatar: '//imgs.1op.cn/i/hxshop/goods/3.jpg',
+ {id: '236', name: '可乐鸡翅buibui爽', main_pic: '//imgs.1op.cn/i/hxshop/goods/12.jpg', tag: '招牌', price: 45, old_price: 65,}
+ ,{id: '237', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/13.jpg', tag: '爆款', price: 45, old_price: 65,}
+ ,{id: '238', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/14.jpg', tag: '爆款', price: 45, old_price: 65,}
+ ,{id: '239', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/15.jpg', tag: '推荐', price: 45, old_price: 65,}
+ ,{id: '240', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/16.jpg', tag: '推荐', price: 45, old_price: 65,}
+ store_id: '854',
+ name: '五点快餐',
+ avatar: '//imgs.1op.cn/i/hxshop/goods/4.jpg',
+ community: '天北花园',
+ {id: '236', name: '可乐鸡翅buibui爽', main_pic: '//imgs.1op.cn/i/hxshop/goods/17.jpg', tag: '招牌', price: 45, old_price: 65,}
+ ,{id: '237', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/1.jpg', tag: '爆款', price: 45, old_price: 65,}
+ ,{id: '238', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/3.jpg', tag: '爆款', price: 45, old_price: 65,}
+ ,{id: '239', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/5.jpg', tag: '推荐', price: 45, old_price: 65,}
+ ,{id: '240', name: '百富汉堡', main_pic: '//imgs.1op.cn/i/hxshop/goods/7.jpg', tag: '推荐', price: 45, old_price: 65,}
+//评论数据
+const commentData = [{
+ header_img: "//imgs.1op.cn/i/hxshop/avatar/3.png",
+ user_name: "测试1",
+ rate:5,
+ create_time: "2019.04.12",
+ content: "好评",
+ imgs:[
+ '//imgs.1op.cn/i/hxshop/goods/7.jpg',
+ '//imgs.1op.cn/i/hxshop/goods/1.jpg',
+ '//imgs.1op.cn/i/hxshop/goods/9.jpg',
+ '//imgs.1op.cn/i/hxshop/goods/3.jpg'
+ content: "中评",
+ header_img: "//imgs.1op.cn/i/hxshop/avatar/2.png",
+ user_name: "测试2",
+ rate:4,
+ // imgs:[]
+ content: "",
+ user_name: "测试3",
+ rate:2,
+ },{
+ user_name: "小蚂蚁",
+ '//imgs.1op.cn/i/hxshop/goods/12.jpg',
+ '//imgs.1op.cn/i/hxshop/goods/16.jpg',
+ '//imgs.1op.cn/i/hxshop/goods/7.jpg'
+ header_img: "//imgs.1op.cn/i/hxshop/avatar/4.png",
+ user_name: "沙漠骆驼",
+ rate:3.5,
+ header_img: "//imgs.1op.cn/i/hxshop/avatar/5.png",
+ user_name: "莫思",
+ rate:2.3,
+ }];
+//商品数据
+const goodsData= [{
+ id: 1,
+ type_id:1,
+ name:'白果王水果沙拉',
+ descripe:"脆糯营养,口感好,健康绿色",
+ img:'//imgs.1op.cn/i/hxshop/goods/14.jpg',
+ price:"",
+ oldprice:"",
+ //规格
+ form: {id:1,name:"尺寸"},
+ form_child:[
+ {id:81,pid:1,name:"8寸500g", price:"46", oldprice:"100", select:true},
+ {id:82,pid:1,name:"10寸600g", price:"97", oldprice:"100",select:false},
+ {id:83,pid:1,name:"12寸800g", price:"135", oldprice:"100",select:false},
+ {id:84,pid:1,name:"四川麻辣", price:"12", oldprice:"100",select:false},
+ {id:85,pid:1,name:"香辣", price:"20", oldprice:"100",select:false},
+ {id:86,pid:1,name:"卤香", price:"90", oldprice:"100",select:false},
+ {id:87,pid:1,name:"鲜甜广味", price:"80", oldprice:"100",select:false},
+ {id:88,pid:1,name:"镇店茴香味", price:"100", oldprice:"100",select:false}
+ id: 2,
+ type_id:2,
+ name:'精品烤山药',
+ img: '//imgs.1op.cn/i/hxshop/goods/12.jpg',
+ {id:81,pid:1,name:"8寸500g", price:"78", oldprice:"100", select:true},
+ id: 3,
+ name:'川味毛血旺',
+ img: '//imgs.1op.cn/i/hxshop/goods/11.jpg',
+ price:"4",
+ id: 4,
+ type_id:3,
+ name:'吐鲁番烤全羊',
+ img: '//imgs.1op.cn/i/hxshop/goods/10.jpg',
+ oldprice:""
+ id: 5,
+ name:'红烧肉',
+ img: '//imgs.1op.cn/i/hxshop/goods/9.jpg',
+ id: 6,
+ type_id:4,
+ name:'新疆特色辣子鸡',
+ img: '//imgs.1op.cn/i/hxshop/goods/8.jpg',
+ id: 106,
+ name:'新疆特色羊排',
+ id: 7,
+ type_id:5,
+ name:'绝味海鲜拼盘',
+ img: '//imgs.1op.cn/i/hxshop/goods/7.jpg',
+ id: 8,
+ name:'金色香糯大粽子',
+ img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',
+ id: 9,
+ name:'马梓林香香鸡',
+ img: '//imgs.1op.cn/i/hxshop/goods/5.jpg',
+ id: 10,
+ type_id:6,
+ name:'草莓味莫普氏蛋糕',
+ img: '//imgs.1op.cn/i/hxshop/goods/4.jpg',
+ id: 23,
+ id: 24,
+ id: 25,
+ type_id:7,
+ id: 26,
+ id: 27,
+ {id: 28,type_id:8,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',price:"4",oldprice:""},
+ {id: 29,type_id:8,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/5.jpg',price:"4",oldprice:""},
+ {id: 30,type_id:8,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/4.jpg',price:"4",oldprice:""},
+ {id: 31,type_id:9,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/16.jpg',price:"4",oldprice:""},
+ {id: 32,type_id:9,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/17.jpg',price:"4",oldprice:""},
+ {id: 33,type_id:9,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/15.jpg',price:"4",oldprice:""},
+ {id: 46,type_id:8,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',price:"4",oldprice:""},
+ {id: 34,type_id:10,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/14.jpg',price:"4",oldprice:""},
+ {id: 35,type_id:10,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/13.jpg',price:"4",oldprice:""},
+ {id: 45,type_id:8,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',price:"4",oldprice:""},
+ {id: 36,type_id:10,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/12.jpg',price:"4",oldprice:""},
+ {id: 37,type_id:10,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/11.jpg',price:"4",oldprice:""},
+ {id: 38,type_id:11,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/10.jpg',price:"4",oldprice:""},
+ {id: 44,type_id:8,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',price:"4",oldprice:""},
+ {id: 39,type_id:12,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/9.jpg',price:"4",oldprice:""},
+ {id: 40,type_id:12,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/8.jpg',price:"4",oldprice:""},
+ {id: 13,type_id:13,name:'金色香糯大粽子',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/6.jpg',price:"4",oldprice:""},
+ {id: 41,type_id:12,name:'马梓林香香鸡',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/7.jpg',price:"4",oldprice:""},
+ {id: 42,type_id:13,name:'草莓味莫普氏蛋糕',descripe:"脆糯营养,口感好,健康绿色",img: '//imgs.1op.cn/i/hxshop/goods/1.jpg',price:"4",oldprice:""}
+//商品种类数据
+const categoryData = [
+ {id: 1,name: '烧烤'},
+ {id: 2,name: '生鲜'},
+ {id: 3,name: '绿蔬'},
+ {id: 4,name: '肉类'},
+ {id: 5,name: '川味'},
+ {id: 6,name: '粤菜'},
+ {id: 7,name: '湘菜'},
+ {id: 8,name: '西餐'},
+ {id: 9,name: '饮料'},
+ {id: 10,name: '糕点'},
+ {id: 11,name: '凉菜'},
+ {id: 12,name: '火锅'},
+ {id: 13,name: '干锅'}
+//商品详细
+const goodsInfo = {
+ //商品id
+ id: 18,
+ //商品类id
+ name:'新疆特色辣子鸡排饭',
+ //商品描述-就是掌柜描述,
+ descripe:"口味可以自己选,有香辣、蒜香、葱香、孜然、老麻口味",
+ img:'//imgs.1op.cn/i/hxshop/goods/7.jpg',
+ //滚动图片
+ banner_img:['//imgs.1op.cn/i/hxshop/goods/14.jpg','//imgs.1op.cn/i/hxshop/goods/7.jpg','//imgs.1op.cn/i/hxshop/goods/12.jpg'],
+ price:"23",
+ oldprice:"44",
+ //月销售
+ monthly_sales: "566",
+ //商品标签
+ goods_tag:['约800克','香辣','特色菜','营养美食'],
+ detail:[
+ tit:'掌柜描述',
+ txt:'口味可以自己选,有香辣、蒜香、葱香、孜然、老麻口味',
+ tit:'主料',
+ txt:'鸡胸排,大葱',
+ tit:'菜系',
+ txt:'新疆特色菜',
+ tit:'口味',
+ txt:'香辣',
+ //......更多
+ //图文
+ desc: `
+ <view style="width:100%">
+ <image style="width:100%;display:block;" src="//imgs.1op.cn/i/hxshop/goods/14.jpg" />
+ <image style="width:100%;display:block;" src="//imgs.1op.cn/i/hxshop/goods/7.jpg" />
+ <image style="width:100%;display:block;" src="//imgs.1op.cn/i/hxshop/goods/6.jpg" />
+ <image style="width:100%;display:block;" src="//imgs.1op.cn/i/hxshop/goods/3.jpg" />
+ <image style="width:100%;display:block;" src="//imgs.1op.cn/i/hxshop/goods/1.jpg" />
+ `,
+};
+//商品评价
+const goodsEva = {
+ //总评价数
+ sum: '386',
+ //好评
+ praise: '306',
+ //差评
+ bad_review: '80',
+ //评价标签
+ eva_tag: {
+ //有图
+ 'exist_pic': '62',
+ //赞
+ 'appreciate': '96',
+ //踩
+ 'oppose': '16',
+ //其他标签
+ 'other': ['92%人口味满意','300人希望再次购买']
+ eva_list: [
+ //用户名
+ name: '白色的太阳',
+ avatar: '//imgs.1op.cn/i/hxshop/avatar/4.png',
+ //评论时间
+ time: '2020.03.12',
+ //点赞或踩商品,没有投票【0】、赞【1】、踩【2】
+ point: 1,
+ //评价内容
+ content: "味道好极了,家里人超爱吃,希望下次能多放点辣椒,我们家吃辣",
+ //上传的图片
+ pic: ['//imgs.1op.cn/i/hxshop/goods/12.jpg','//imgs.1op.cn/i/hxshop/goods/13.jpg','//imgs.1op.cn/i/hxshop/goods/15.jpg']
+ name: '匿名用户',
+ avatar: '//imgs.1op.cn/i/hxshop/avatar/2.png',
+ time: '2020.03.16',
+ point: 2,
+ pic: []
+ avatar: '//imgs.1op.cn/i/hxshop/avatar/6.png',
+ point: 0,
+ content: "一般般",
+// 订单
+const ordersData = [
+ id: 'MS2020041101',
+ store_id: 168,
+ tag:'../../static/img/index/yd.png',
+ store_name: '肯德基',
+ community: '阿克苏友好店',
+ avatar: 'https://imgs.1op.cn/i/hxshop/avatar/2.png',
+ create_time: '2020-4-11 19:51',
+ total: 122.45,
+ //订单状态 [1已取消,2待支付,3待收货,4待评价,5完成,6退款中,7退款完成]
+ status: 3,
+ id: 'MS2020041102',
+ store_id: 186,
+ tag:'../../static/img/index/sc.png',
+ store_name: '美食大龙虾*烧烤虾尾',
+ goods_name: '小龙虾+鸡翅+可乐+田螺',
+ avatar: 'https://imgs.1op.cn/i/hxshop/avatar/5.png',
+ status: 2,
+ id: 'MS2020041103',
+ store_id: 183,
+ tag:'../../static/img/index/sg.png',
+ store_name: '辣椒小海鲜',
+ avatar: 'https://imgs.1op.cn/i/hxshop/avatar/9.png',
+ status: 1,
+ id: 'MS2020041104',
+ store_id: 182,
+ tag:'../../static/img/index/cs.png',
+ store_name: '特色冒菜-四川爆啦',
+ community: '特卖店',
+ status: 4,
+ id: 'MS2020041105',
+ store_id: 181,
+ store_name: '绝味黑鸭脖',
+ status: 5,
+ id: 'MS2020041106',
+ store_id: 180,
+ store_name: '天天来食府',
+ status: 6,
+ id: 'MS2020041107',
+ store_id: 170,
+ community: '兴隆店',
+ avatar: 'https://imgs.1op.cn/i/hxshop/avatar/10.png',
+ status: 7,
+]
+export default {
+ storeData,
+ storeList,
+ commentData,
+ goodsData,
+ categoryData,
+ goodsInfo,
+ goodsEva,
+ ordersData
@@ -0,0 +1,209 @@
+ <view class="hx-numbox">
+ <view @click="_calcValue('minus')" class="hx-numbox__minus" v-if="inputValue>0">
+ <text class="hx-numbox--text" :class="{ 'hx-numbox--disabled': inputValue <= min || disabled }">-</text>
+ <input :disabled="true" @blur="_onBlur" class="hx-numbox__value" type="number" v-model="inputValue" v-if="inputValue>0"/>
+ <view @click="_calcValue('plus')" class="hx-numbox__plus" >
+ <text class="hx-numbox--text" :class="{ 'hx-numbox--disabled': inputValue >= max || disabled }">+</text>
+ name: "hxNumberBox",
+ props: {
+ default: 0
+ min: {
+ max: {
+ default: 100
+ step: {
+ default: 1
+ disabled: {
+ default: false
+ rowData: {
+ default: ()=>{
+ return {}
+ clickTime: {
+ inputValue: 0,
+ addStaus: true,
+ watch: {
+ value(val) {
+ this.inputValue = +val;
+ inputValue(newVal, oldVal) {
+ if (+newVal !== +oldVal) {
+ //this.$emit("change", newVal,this.rowData);
+ /* if(+newVal > +oldVal){
+ } */
+ created() {
+ this.inputValue = +this.value;
+ _calcValue(type) {
+ if (this.disabled) {
+ const scale = this._getDecimalScale();
+ let value = this.inputValue * scale;
+ let step = this.step * scale;
+ if (type === "minus") {
+ this.$emit("lessChange", this.inputValue,this.rowData);
+ value -= step;
+ if (value < this.min) {
+ if(value > this.max){
+ value = this.max
+ } else if (type === "plus") {
+ this.$emit("addChange", this.inputValue,this.rowData);
+ if(that.clickTime > 0){
+ if(!this.addStaus){
+ this.addStaus = false;
+ that.addStaus = true;
+ },that.clickTime)
+ value += step;
+ if (value > this.max) {
+ if(value < this.min){
+ value = this.min
+ this.inputValue = String(value / scale);
+ this.$emit("change", this.inputValue,this.rowData);
+ _getDecimalScale() {
+ let scale = 1;
+ // 浮点型
+ if (~~this.step !== this.step) {
+ scale = Math.pow(10, (this.step + "").split(".")[1].length);
+ return scale;
+ _onBlur(event) {
+ let value = event.detail.value;
+ if (!value) {
+ // this.inputValue = 0;
+ value = +value;
+ value = this.max;
+ } else if (value < this.min) {
+ value = this.min;
+ this.inputValue = value;
+ $box-height: 28px;
+ $box-line-height: 22px;
+ $box-width: 28px;
+ .hx-numbox {
+ /* #ifndef APP-NVUE */
+ height: $box-height;
+ line-height: $box-height;
+ width: 85px;
+ .hx-numbox__value {
+ font-size: $uni-font-size-lg;
+ .hx-numbox__minus {
+ width: $box-width - 1;
+ height: $box-height - 1;
+ font-size: 30px;
+ color: $uni-text-color;
+ background-color: #f8f8f8;
+ border: 1px solid #d8d8d8;
+ color: #868686;
+ .hx-numbox__plus {
+ width: $box-width;
+ .hx-numbox--text {
+ .hx-numbox--disabled {
+ color: $uni-text-color-disable;
@@ -0,0 +1,87 @@
+<!-- 首页,支持店铺装修 -->
+ <view v-if="template">
+ <s-layout title="首页" navbar="custom" tabbar="/pages/index/index" :bgStyle="template.page"
+ :navbarStyle="template.navigationBar" onShareAppMessage>
+ <s-block v-for="(item, index) in template.components" :key="index" :styles="item.property.style">
+ <s-block-item :type="item.id" :data="item.property" :styles="item.property.style" />
+ </s-block>
+ onPageScroll,
+ onPullDownRefresh
+ const template = computed(() => sheep.$store('app').template?.home);
+ // 在此处拦截改变一下首页轮播图 此处先写死后期复活 放到启动函数里
+ // (async function() {
+ // console.log('原代码首页定制化数据',template)
+ // let {
+ // data
+ // } = await index2Api.decorate();
+ // console.log('首页导航配置化过高无法兼容',JSON.parse(data[1].value))
+ // 改变首页底部数据 但是没有通过数组id获取商品数据接口
+ // data: datas
+ // } = await index2Api.spids();
+ // template.value.data[9].data.goodsIds = datas.list.map(item => item.id);
+ // template.value.data[0].data.list = JSON.parse(data[0].value).map(item => {
+ // return {
+ // src: item.picUrl,
+ // url: item.url,
+ // title: item.name,
+ // type: "image"
+ // }())
+ // #ifdef MP
+ // 小程序识别二维码
+ if (options.scene) {
+ const sceneParams = decodeURIComponent(options.scene).split('=');
+ options[sceneParams[0]] = sceneParams[1];
+ // 预览模板
+ if (options.templateId) {
+ sheep.$store('app').init(options.templateId);
+ // 解析分享信息
+ if (options.spm) {
+ $share.decryptSpm(options.spm);
+ // 进入指定页面(完整页面路径)
+ if (options.page) {
+ sheep.$router.go(decodeURIComponent(options.page));
+ // 下拉刷新
+ sheep.$store('app').init();
+ setTimeout(function() {
+<style></style>
@@ -0,0 +1,38 @@
+<!-- 微信公众号的登录回调页 -->
+ <!-- 空登陆页 -->
+ <view />
+ // 将 search 参数赋值到 options 中,方便下面解析
+ new URLSearchParams(location.search).forEach((value, key) => {
+ options[key] = value;
+ const event = options.event;
+ const code = options.code;
+ const state = options.state;
+ if (event === 'login') { // 场景一:登录
+ const res = await sheep.$platform.useProvider().login(code, state);
+ } else if (event === 'bind') { // 场景二:绑定
+ sheep.$platform.useProvider().bind(code, state);
+ // 检测 H5 登录回调
+ let returnUrl = uni.getStorageSync('returnUrl');
+ if (returnUrl) {
+ uni.removeStorage({key:'returnUrl'});
+ location.replace(returnUrl);
+ uni.switchTab({
+ url: '/',
@@ -0,0 +1,51 @@
+<!-- 自定义页面:支持装修 -->
+ <s-layout
+ :title="state.name"
+ navbar="custom"
+ :bgStyle="state.page"
+ :navbarStyle="state.navigationBar"
+ onShareAppMessage
+ showLeftButton
+ <s-block v-for="(item, index) in state.components" :key="index" :styles="item.property.style">
+ import DiyApi from '@/sheep/api/promotion/diy';
+ name: '',
+ components: [],
+ navigationBar: {},
+ page: {},
+ let id = options.id
+ // 小程序预览自定义页面
+ id = sceneParams[1];
+ const { code, data } = await DiyApi.getDiyPage(id);
+ state.name = data.name;
+ state.components = data.property?.components;
+ state.navigationBar = data.property?.navigationBar;
+ state.page = data.property?.page;
@@ -0,0 +1,119 @@
+<!-- 搜索界面 -->
+ <s-layout class="set-wrap" title="搜索" :bgStyle="{ color: '#FFF' }">
+ <view class="ss-p-x-24">
+ placeholder="请输入关键字"
+ @confirm="onSearch($event.value)"
+ <view class="ss-flex ss-row-between ss-col-center">
+ <view class="serach-history">搜索历史</view>
+ <button class="clean-history ss-reset-button" @tap="onDelete"> 清除搜索历史 </button>
+ <view class="ss-flex ss-col-center ss-row-left ss-flex-wrap">
+ class="history-btn ss-reset-button"
+ @tap="onSearch(item)"
+ v-for="(item, index) in state.historyList"
+ {{ item }}
+ historyList: [],
+ function onSearch(keyword) {
+ if (!keyword) {
+ saveSearchHistory(keyword);
+ // 前往商品列表(带搜索条件)
+ sheep.$router.go('/pages/goods/list', { keyword });
+ // 保存搜索历史
+ function saveSearchHistory(keyword) {
+ // 如果关键词在搜索历史中,则把此关键词先移除
+ if (state.historyList.includes(keyword)) {
+ state.historyList.splice(state.historyList.indexOf(keyword), 1);
+ // 置顶关键词
+ state.historyList.unshift(keyword);
+ // 最多保留 10 条记录
+ if (state.historyList.length >= 10) {
+ state.historyList.length = 10;
+ uni.setStorageSync('searchHistory', state.historyList);
+ function onDelete() {
+ content: '确认清除搜索历史吗?',
+ state.historyTag = [];
+ uni.removeStorageSync('searchHistory');
+ state.historyList = uni.getStorageSync('searchHistory') || [];
+ .serach-title {
+ .uni-searchbar {
+ padding-left: 0;
+ .serach-history {
+ .clean-history {
+ .history-btn {
+ padding: 0 38rpx;
+ background: #f5f6f8;
+ max-width: 690rpx;
+ margin: 0 20rpx 20rpx 0;
@@ -0,0 +1,42 @@
+<!-- 个人中心:支持装修 -->
+ title="我的"
+ tabbar="/pages/index/user"
+ :bgStyle="template.page"
+ :navbarStyle="template.navigationBar"
+ import { onShow, onPageScroll, onPullDownRefresh } from '@dcloudio/uni-app';
+ const template = computed(() => sheep.$store('app').template.user);
+ const isLogin = computed(() => sheep.$store('user').isLogin);
+ onShow(() => {
+ sheep.$store('user').updateUserData();
@@ -0,0 +1,357 @@
+<!-- 售后申请 -->
+ <s-layout title="申请售后">
+ <!-- 售后商品 -->
+ <view class="goods-box">
+ :img="state.item.picUrl"
+ :title="state.item.spuName"
+ :skuText="state.item.properties?.map((property) => property.valueName).join(' ')"
+ :price="state.item.price"
+ :num="state.item.count"
+ <uni-forms ref="form" v-model="formData" :rules="rules" label-position="top">
+ <!-- 售后类型 -->
+ <view class="refund-item">
+ <view class="item-title ss-m-b-20">售后类型</view>
+ <radio-group @change="onRefundChange">
+ class="ss-flex ss-col-center ss-p-y-10"
+ v-for="(item, index) in state.wayList"
+ :checked="formData.type === item.value"
+ style="transform: scale(0.8)"
+ <view class="item-value ss-m-l-8">{{ item.text }}</view>
+ <!-- 退款金额 -->
+ <view class="refund-item ss-flex ss-col-center ss-row-between" @tap="state.showModal = true">
+ <text class="item-title">退款金额</text>
+ <view class="ss-flex refund-cause ss-col-center">
+ <text class="ss-m-r-20">¥{{ fen2yuan(state.item.payPrice) }}</text>
+ <!-- 申请原因 -->
+ <text class="item-title">申请原因</text>
+ <text class="ss-m-r-20" v-if="formData.applyReason">{{ formData.applyReason }}</text>
+ <text class="ss-m-r-20" v-else>请选择申请原因~</text>
+ <text class="cicon-forward" style="height: 28rpx"></text>
+ <!-- 留言 -->
+ <view class="item-title ss-m-b-20">相关描述</view>
+ <view class="describe-box">
+ class="describe-content"
+ type="textarea"
+ maxlength="120"
+ autoHeight
+ v-model="formData.applyDescription"
+ placeholder="客官~请描述您遇到的问题,建议上传照片"
+ <!-- TODO 芋艿:上传的测试 -->
+ v-model:url="formData.images"
+ limit="9"
+ </uni-forms>
+ <!-- 底部按钮 -->
+ <view class="foot-wrap">
+ <view class="foot_box ss-flex ss-col-center ss-row-between ss-p-x-30">
+ <button class="ss-reset-button contcat-btn" @tap="sheep.$router.go('/pages/chat/index')">
+ 联系客服
+ <button class="ss-reset-button ui-BG-Main-Gradient sub-btn" @tap="submit">提交</button>
+ <!-- 申请原因弹窗 -->
+ <su-popup :show="state.showModal" round="10" :showClose="true" @close="state.showModal = false">
+ <view class="modal-box page_box">
+ <view class="modal-head item-title head_box ss-flex ss-row-center ss-col-center">
+ 申请原因
+ <view class="modal-content content_box">
+ class="radio ss-flex ss-col-center"
+ v-for="item in state.reasonList"
+ <view class="ss-flex-1 ss-p-20">{{ item }}</view>
+ :value="item"
+ :checked="item === state.currentValue"
+ <view class="modal-foot foot_box ss-flex ss-row-center ss-col-center">
+ <button class="ss-reset-button close-btn ui-BG-Main-Gradient" @tap="onReason">
+ import { reactive, ref } from 'vue';
+ import AfterSaleApi from '@/sheep/api/trade/afterSale';
+ const form = ref(null);
+ orderId: 0, // 订单编号
+ itemId: 0, // 订单项编号
+ order: {}, // 订单
+ item: {}, // 订单项
+ config: {}, // 交易配置
+ // 售后类型
+ wayList: [
+ text: '仅退款',
+ value: '10',
+ text: '退款退货',
+ value: '20',
+ reasonList: [], // 可选的申请原因数组
+ showModal: false, // 是否显示申请原因弹窗
+ currentValue: '' // 当前选择的售后原因
+ const formData = reactive({
+ way: '',
+ applyReason: '',
+ applyDescription: '',
+ images: [],
+ const rules = reactive({});
+ // 提交表单
+ async function submit() {
+ sheep.$platform.useProvider('wechat').subscribeMessage('order_aftersale_change');
+ let data = {
+ orderItemId: state.itemId,
+ refundPrice: state.item.payPrice,
+ ...formData,
+ const { code } = await AfterSaleApi.createAfterSale(data);
+ title: '申请成功',
+ sheep.$router.go('/pages/order/aftersale/list');
+ // 选择售后类型
+ function onRefundChange(e) {
+ formData.way = e.detail.value;
+ // 清理理由
+ state.reasonList =
+ formData.way === '10'
+ ? state.config.afterSaleRefundReasons || []
+ : state.config.afterSaleReturnReasons || [];
+ formData.applyReason = '';
+ state.currentValue = '';
+ // 选择申请原因
+ // 确定
+ function onReason() {
+ formData.applyReason = state.currentValue;
+ // 解析参数
+ if (!options.orderId || !options.itemId) {
+ state.orderId = options.orderId;
+ state.itemId = parseInt(options.itemId);
+ // 读取订单信息
+ const { code, data } = await OrderApi.getOrder(state.orderId);
+ state.order = data;
+ state.item = data.items.find((item) => item.id === state.itemId) || {};
+ // 设置选项
+ if (state.order.status === 10) {
+ state.wayList.splice(1, 1);
+ // 读取配置
+ state.config = (await TradeConfigApi.getTradeConfig()).data;
+ color: rgba(51, 51, 51, 1);
+ // margin-bottom: 20rpx;
+ // 售后项目
+ .refund-item {
+ border-bottom: 1rpx solid #f5f5f5;
+ // 留言
+ .describe-box {
+ .describe-content {
+ height: 200rpx;
+ // 联系方式
+ height: 84rpx;
+ .foot-wrap {
+ .foot_box {
+ .sub-btn {
+ width: 336rpx;
+ line-height: 74rpx;
+ border-radius: 38rpx;
+ .contcat-btn {
+ background: rgba(238, 238, 238, 1);
+ .modal-box {
+ // height: 680rpx;
+ .modal-head {
+ .modal-foot {
+ .close-btn {
+ .success-box {
+ width: 600rpx;
+ padding: 90rpx 0 64rpx 0;
+ font-size: 96rpx;
+ color: #04b750;
+ .success-title {
+ .success-btn {
+ background: linear-gradient(90deg, var(--ui-BG-Main-gradient), var(--ui-BG-Main));
@@ -0,0 +1,342 @@
+<!-- 售后详情 -->
+ <s-layout title="售后详情" :navbar="!isEmpty(state.info) && state.loading ? 'inner' : 'normal'">
+ <view class="content_box" v-if="!isEmpty(state.info) && state.loading">
+ <!-- 步骤条 -->
+ <view class="steps-box ss-flex" :style="[
+ paddingTop: Number(statusBarHeight + 88) + 'rpx',
+ <view class="steps-item" v-for="(item, index) in state.list" :key="index">
+ <text class="sicon-circleclose"
+ v-if="state.list.length - 1 === index && [61, 62, 63].includes(state.info.status)" />
+ <text class="sicon-circlecheck" v-else
+ :class="state.active >= index ? 'activity-color' : 'info-color'" />
+ <view v-if="state.list.length - 1 !== index" class="line"
+ :class="state.active >= index ? 'activity-bg' : 'info-bg'" />
+ <view class="steps-item-title" :class="state.active >= index ? 'activity-color' : 'info-color'">
+ <!-- 服务状态 -->
+ <view class="status-box ss-flex ss-col-center ss-row-between ss-m-x-20"
+ @tap="sheep.$router.go('/pages/order/aftersale/log', { id: state.id })">
+ <view class="status-text">
+ {{ formatAfterSaleStatusDescription(state.info) }}
+ <view class="status-time">
+ {{ sheep.$helper.timeFormat(state.info.updateTime, 'yyyy-mm-dd hh:MM:ss') }}
+ <text class="ss-iconfont _icon-forward" style="color: #666" />
+ <view class="aftersale-money ss-flex ss-col-center ss-row-between">
+ <view class="aftersale-money--title">退款总额</view>
+ <view class="aftersale-money--num">¥{{ fen2yuan(state.info.refundPrice) }}</view>
+ <!-- 服务商品 -->
+ <view class="order-shop">
+ :img=" state.info.picUrl"
+ :title=" state.info.spuName"
+ :titleWidth="480"
+ :skuText="state.info.properties.map((property) => property.valueName).join(' ')"
+ :num=" state.info.count"
+ <!-- 服务内容 -->
+ <view class="aftersale-content">
+ <view class="aftersale-item ss-flex ss-col-center">
+ <view class="item-title">服务单号:</view>
+ <view class="item-content ss-m-r-16">{{ state.info.no }}</view>
+ <button class="ss-reset-button copy-btn" @tap="onCopy">复制</button>
+ <view class="item-title">申请时间:</view>
+ <view class="item-content">
+ {{ sheep.$helper.timeFormat(state.info.createTime, 'yyyy-mm-dd hh:MM:ss') }}
+ <view class="item-title">售后类型:</view>
+ <view class="item-content">{{ state.info.way === 10 ? '仅退款' : '退款退货' }}</view>
+ <view class="item-title">申请原因:</view>
+ <view class="item-content">{{ state.info.applyReason }}</view>
+ <view class="item-title">相关描述:</view>
+ <view class="item-content">{{ state.info.applyDescription }}</view>
+ <!-- 操作区 -->
+ <s-empty v-if="isEmpty(state.info) && state.loading" icon="/static/order-empty.png" text="暂无该订单售后详情" />
+ <su-fixed bottom placeholder bg="bg-white" v-if="!isEmpty(state.info)">
+ <view class="foot_box">
+ <button class="ss-reset-button btn" v-if="state.info.buttons?.includes('cancel')"
+ @tap="onApply(state.info.id)">
+ 取消申请
+ <button class="ss-reset-button btn" v-if="state.info.buttons?.includes('delivery')"
+ @tap="sheep.$router.go('/pages/order/aftersale/return-delivery', { id: state.info.id })">
+ 填写退货
+ <button class="ss-reset-button contcat-btn btn" @tap="sheep.$router.go('/pages/chat/index')">
+ import { fen2yuan, formatAfterSaleStatusDescription, handleAfterSaleButtons } from '@/sheep/hooks/useGoods';
+ const headerBg = sheep.$url.css('/static/img/shop/order/order_bg.png');
+ id: 0, // 售后编号
+ info: {}, // 收货信息
+ loading: false,
+ active: 0, // 在 list 的激活位置
+ title: '提交申请',
+ title: '处理中',
+ title: '完成'
+ }], // 时间轴
+ function onApply(id) {
+ content: '确定要取消此申请吗?',
+ const { code } = await AfterSaleApi.cancelAfterSale(id);
+ await getDetail(id);
+ // 复制
+ const onCopy = () => {
+ sheep.$helper.copyText(state.info.no);
+ async function getDetail(id) {
+ state.loading = true;
+ const { code, data } = await AfterSaleApi.getAfterSale(id);
+ state.info = null;
+ state.info = data;
+ handleAfterSaleButtons(state.info);
+ // 处理时间轴
+ if ([10].includes(state.info.status)) {
+ state.active = 0;
+ } else if ([20, 30].includes(state.info.status)) {
+ state.active = 1;
+ } else if ([40, 50].includes(state.info.status)) {
+ state.active = 2;
+ } else if ([61, 62, 63].includes(state.info.status)) {
+ getDetail(options.id);
+ // 步骤条
+ .steps-box {
+ padding-left: 72rpx;
+ .steps-item {
+ .sicon-circleclose {
+ .sicon-circlecheck {
+ .steps-item-title {
+ margin-top: 16rpx;
+ .activity-color {
+ .info-color {
+ color: rgba(#fff, 0.4);
+ .activity-bg {
+ .info-bg {
+ background: rgba(#fff, 0.4);
+ width: 270rpx;
+ height: 4rpx;
+ // 服务状态
+ .status-box {
+ border-radius: 20rpx 20rpx 0px 0px;
+ margin-top: -20rpx;
+ .status-text {
+ .status-time {
+ color: rgba(153, 153, 153, 1);
+ // 退款金额
+ .aftersale-money {
+ height: 98rpx;
+ .aftersale-money--title {
+ .aftersale-money--num {
+ // order-shop
+ .order-shop {
+ // 服务内容
+ .aftersale-content {
+ margin: 0 20rpx;
+ .aftersale-item {
+ .copy-btn {
+ .item-content {
+ // 底部功能
+ .btn {
@@ -0,0 +1,187 @@
+<!-- 售后列表 -->
+ <s-layout title="售后列表">
+ <su-tabs :list="tabMaps" :scrollable="false" @change="onTabsChange" :current="state.currentTab" />
+ <s-empty v-if="state.pagination.total === 0" icon="/static/data-empty.png" text="暂无数据" />
+ <!-- 列表 -->
+ <view class="list-box ss-m-y-20" v-for="order in state.pagination.list" :key="order.id"
+ @tap="sheep.$router.go('/pages/order/aftersale/detail', { id: order.id })">
+ <view class="order-head ss-flex ss-col-center ss-row-between">
+ <text class="no">服务单号:{{ order.no }}</text>
+ <text class="state">{{ formatAfterSaleStatus(order) }}</text>
+ :img="order.picUrl"
+ :title="order.spuName"
+ :skuText="order.properties.map((property) => property.valueName).join(' ')"
+ :price="order.refundPrice"
+ <view class="apply-box ss-flex ss-col-center ss-row-between border-bottom ss-p-x-20">
+ <view class="title ss-m-r-20">{{ order.way === 10 ? '仅退款' : '退款退货' }}</view>
+ <view class="value">{{ formatAfterSaleStatusDescription(order) }}</view>
+ <text class="_icon-forward"></text>
+ <view class="tool-btn-box ss-flex ss-col-center ss-row-right ss-p-r-20">
+ <!-- TODO 功能缺失:填写退货信息 -->
+ <button class="ss-reset-button tool-btn" @tap.stop="onApply(order.id)"
+ v-if="order?.buttons.includes('cancel')">取消申请</button>
+ import { formatAfterSaleStatus, formatAfterSaleStatusDescription, handleAfterSaleButtons } from '@/sheep/hooks/useGoods';
+ showApply: false,
+ pageSize: 10
+ // TODO 芋艿:优化点,增加筛选
+ // name: '申请中',
+ // value: 'nooper',
+ // name: '处理中',
+ // value: 'ing',
+ // name: '已完成',
+ // value: 'completed',
+ // name: '已拒绝',
+ // value: 'refuse',
+ // 获取售后列表
+ let { data, code } = await AfterSaleApi.getAfterSalePage({
+ // type: tabMaps[state.currentTab].value,
+ data.list.forEach(order => handleAfterSaleButtons(order));
+ function onApply(orderId) {
+ const { code } = await AfterSaleApi.cancelAfterSale(orderId);
+ await getOrderList();
+ .order-head {
+ height: 77rpx;
+ .apply-box {
+ height: 82rpx;
+ .value {
+ .tool-btn-box {
@@ -0,0 +1,77 @@
+<!-- 售后日志的每一项展示 -->
+ <view class="log-item ss-flex">
+ <view class="log-icon ss-flex-col ss-col-center ss-m-r-20">
+ <text class="cicon-title" :class="index === 0 ? 'activity-color' : ''" />
+ <view v-if="data.length - 1 !== index" class="line" />
+ <view class="text">{{ item.content }}</view>
+ <view class="date">
+ type: Object, // 当前日志
+ type: Number, // item 在 data 的下标
+ type: Object, // 日志列表
+ align-items: stretch;
+ .log-icon {
+ height: inherit;
+ .cicon-title {
+ color: #dfdfdf;
+ color: #60bd45;
+ width: 1px;
+ background: #dfdfdf;
+ .text {
+ .richtext {
+ margin: 20rpx 0 0 0;
+ .date {
+<!-- 售后日志列表 -->
+ <s-layout title="售后进度">
+ <view class="log-box">
+ <view v-for="(item, index) in state.list" :key="item.id">
+ <log-item :item="item" :index="index" :data="state.list" />
+ import logItem from './log-item.vue';
+ const { data } = await AfterSaleApi.getAfterSaleLogList(id);
+ state.aftersaleId = options.id;
+ .log-box {
+ padding: 24rpx 24rpx 24rpx 40rpx;
@@ -0,0 +1,194 @@
+ <s-layout title="退货物流">
+ <form @submit="subRefund" report-submit='true'>
+ <view class='apply-return'>
+ <view class='list borRadius14'>
+ <view class='item acea-row row-between-wrapper' style="display: flex;align-items: center;">
+ <view>物流公司</view>
+ <picker mode='selector' class='num' @change="bindPickerChange" :value="state.expressIndex"
+ :range="state.expresses" range-key="name">
+ <view class="picker acea-row row-between-wrapper">
+ <view class='reason'>{{ state.expresses[state.expressIndex].name }}</view>
+ <!-- TODO 芋艿:这里样式有问题,少了 > 按钮 -->
+ <text class='iconfont icon-jiantou' />
+ </picker>
+ <view class='item textarea acea-row row-between' style="display: flex;align-items: center;">
+ <view>物流单号</view>
+ <input placeholder='请填写物流单号' class='num' name="logisticsNo"
+ placeholder-class='placeholder' />
+ <button class='returnBnt bg-color ss-reset-button ui-BG-Main-Gradient sub-btn'
+ form-type="submit"
+ style="background: linear-gradient(90deg,var(--ui-BG-Main),var(--ui-BG-Main-gradient))!important">提交</button>
+ </form>
+ import DeliveryApi from '@/sheep/api/trade/delivery';
+ expressIndex: 0, // 选中的 expresses 下标
+ expresses: [], // 可选的快递列表
+ function bindPickerChange(e) {
+ state.expressIndex = e.detail.value;
+ async function subRefund(e) {
+ id: state.id,
+ logisticsId: state.expresses[state.expressIndex].id,
+ logisticsNo: e.detail.value.logisticsNo,
+ const { code } = await AfterSaleApi.deliveryAfterSale(data);
+ title: '填写退货成功',
+ sheep.$router.go('/pages/order/aftersale/detail', { id: state.id });
+ // 获得快递物流列表
+ async function getExpressList() {
+ const { code, data } = await DeliveryApi.getDeliveryExpressList();
+ state.expresses = data;
+ onLoad(options => {
+ getExpressList();
+ .apply-return {
+ padding: 20rpx 30rpx 70rpx 30rpx;
+ .apply-return .list {
+ margin-top: 18rpx;
+ padding: 0 24rpx 70rpx 24rpx;
+ .apply-return .list .item {
+ min-height: 90rpx;
+ .apply-return .list .item .num {
+ margin-left: 27rpx;
+ // width: 227rpx;
+ // text-align: right;
+ .apply-return .list .item .num .picker .reason {
+ width: 385rpx;
+ .apply-return .list .item .num .picker .iconfont {
+ margin-top: 2rpx;
+ .apply-return .list .item.textarea {
+ padding: 24rpx 0;
+ .apply-return .list .item textarea {
+ .apply-return .list .item .placeholder {
+ .apply-return .list .item .title {
+ height: 95rpx;
+ .apply-return .list .item .title .tip {
+ .apply-return .list .item .upload {
+ padding-bottom: 36rpx;
+ .apply-return .list .item .upload .pictrue {
+ margin: 22rpx 23rpx 0 0;
+ height: 156rpx;
+ .apply-return .list .item .upload .pictrue:nth-of-type(4n) {
+ .apply-return .list .item .upload .pictrue image {
+ .apply-return .list .item .upload .pictrue .icon-guanbi1 {
+ font-size: 45rpx;
+ top: -10rpx;
+ right: -10rpx;
+ .apply-return .list .item .upload .pictrue .icon-icon25201 {
+ color: #bfbfbf;
+ .apply-return .list .item .upload .pictrue:nth-last-child(1) {
+ border: 1rpx solid #ddd;
+ .apply-return .returnBnt {
+ margin: 43rpx auto;
@@ -0,0 +1,408 @@
+ <s-layout title="确认订单" >
+ <!-- TODO:这个判断先删除 v-if="state.orderInfo.need_address === 1" -->
+ <view class="bg-white address-box ss-m-b-14 ss-r-b-10" @tap="onSelectAddress">
+ <s-address-item :item="state.addressInfo" :hasBorderBottom="false">
+ <view class="ss-rest-button">
+ <text class="_icon-forward" />
+ </s-address-item>
+ <view class="order-card-box ss-m-b-14">
+ v-for="item in state.orderInfo.items"
+ :key="item.skuId"
+ marginBottom="10"
+ <view class="order-item ss-flex ss-col-center ss-row-between ss-p-x-20 bg-white ss-r-10">
+ <view class="item-title">订单备注</view>
+ maxlength="20"
+ placeholder="建议留言前先与商家沟通"
+ v-model="state.orderPayload.remark"
+ <!-- 价格信息 -->
+ <view class="bg-white total-card-box ss-p-20 ss-m-b-14 ss-r-10">
+ <view class="total-box-content border-bottom">
+ <view class="order-item ss-flex ss-col-center ss-row-between">
+ <view class="item-title">商品金额</view>
+ <text class="item-value ss-m-r-24">
+ ¥{{ fen2yuan(state.orderInfo.price.totalPrice) }}
+ <!-- TODO 芋艿:接入积分 -->
+ class="order-item ss-flex ss-col-center ss-row-between"
+ v-if="state.orderPayload.order_type === 'score'"
+ <view class="item-title">扣除积分</view>
+ :src="sheep.$url.static('/static/img/shop/goods/score1.svg')"
+ class="score-img"
+ <text class="item-value ss-m-r-24">{{ state.orderInfo.score_amount }}</text>
+ <view class="item-title">运费</view>
+ +¥{{ fen2yuan(state.orderInfo.price.deliveryPrice) }}
+ <!-- 优惠劵:只有 type = 0 普通订单(非拼团、秒杀、砍价),才可以使用优惠劵 -->
+ v-if="state.orderInfo.type === 0"
+ <view class="item-title">优惠券</view>
+ <view class="ss-flex ss-col-center" @tap="state.showCoupon = true">
+ <text class="item-value text-red" v-if="state.orderPayload.couponId > 0">
+ -¥{{ fen2yuan(state.orderInfo.price.couponPrice) }}
+ class="item-value"
+ :class="state.couponInfo.length > 0 ? 'text-red' : 'text-disabled'"
+ state.couponInfo.length > 0 ? state.couponInfo.length + ' 张可用' : '暂无可用优惠券'
+ <text class="_icon-forward item-icon" />
+ v-if="state.orderInfo.price.discountPrice > 0"
+ <view class="item-title">活动优惠</view>
+ <!-- @tap="state.showDiscount = true" TODO 芋艿:后续要把优惠信息打进去 -->
+ <text class="item-value text-red">
+ -¥{{ fen2yuan(state.orderInfo.price.discountPrice) }}
+ v-if="state.orderInfo.price.vipPrice > 0"
+ <view class="item-title">会员优惠</view>
+ -¥{{ fen2yuan(state.orderInfo.price.vipPrice) }}
+ <view class="total-box-footer ss-font-28 ss-flex ss-row-right ss-col-center ss-m-r-28">
+ <view class="total-num ss-m-r-20">
+ 共{{ state.orderInfo.items.reduce((acc, item) => acc + item.count, 0) }}件
+ <view>合计:</view>
+ <view class="total-num text-red"> ¥{{ fen2yuan(state.orderInfo.price.payPrice) }} </view>
+ <!-- 选择优惠券弹框 -->
+ <s-coupon-select
+ v-model="state.couponInfo"
+ :show="state.showCoupon"
+ @confirm="onSelectCoupon"
+ @close="state.showCoupon = false"
+ <!-- 满额折扣弹框 TODO 芋艿:后续要把优惠信息打进去 -->
+ <s-discount-list
+ v-model="state.orderInfo"
+ :show="state.showDiscount"
+ @close="state.showDiscount = false"
+ <su-fixed bottom :opacity="false" bg="bg-white" placeholder :noFixed="false" :index="200">
+ <view class="footer-box border-top ss-flex ss-row-between ss-p-x-20 ss-col-center">
+ <view class="total-box-footer ss-flex ss-col-center">
+ <view class="total-num ss-font-30 text-red">
+ ¥{{ fen2yuan(state.orderInfo.price.payPrice) }}
+ class="ss-reset-button ui-BG-Main-Gradient ss-r-40 submit-btn ui-Shadow-Main"
+ @tap="onConfirm"
+ 提交订单
+ orderPayload: {},
+ orderInfo: {
+ items: [], // 商品项列表
+ price: {}, // 价格信息
+ addressInfo: {}, // 选择的收货地址
+ showCoupon: false, // 是否展示优惠劵
+ couponInfo: [], // 优惠劵列表
+ showDiscount: false, // 是否展示营销活动
+ // 选择地址
+ function onSelectAddress() {
+ uni.$once('SELECT_ADDRESS', (e) => {
+ changeConsignee(e.addressInfo);
+ sheep.$router.go('/pages/user/address/list');
+ // 更改收货人地址&计算订单信息
+ async function changeConsignee(addressInfo = {}) {
+ if (!isEmpty(addressInfo)) {
+ state.addressInfo = addressInfo;
+ await getOrderInfo();
+ // 选择优惠券
+ async function onSelectCoupon(couponId) {
+ state.orderPayload.couponId = couponId || 0;
+ state.showCoupon = false;
+ // 提交订单
+ if (!state.addressInfo.id) {
+ sheep.$helper.toast('请选择收货地址');
+ submitOrder();
+ // 创建订单&跳转
+ async function submitOrder() {
+ const { code, data } = await OrderApi.createOrder({
+ items: state.orderPayload.items,
+ couponId: state.orderPayload.couponId,
+ addressId: state.addressInfo.id,
+ deliveryType: 1, // TODO 芋艿:需要支持【门店自提】
+ pointStatus: false, // TODO 芋艿:需要支持【积分选择】
+ combinationActivityId: state.orderPayload.combinationActivityId,
+ combinationHeadId: state.orderPayload.combinationHeadId,
+ seckillActivityId: state.orderPayload.seckillActivityId
+ // 更新购物车列表,如果来自购物车
+ if (state.orderPayload.items[0].cartId > 0) {
+ sheep.$store('cart').getList();
+ // 跳转到支付页面
+ sheep.$router.redirect('/pages/pay/index', {
+ id: data.payOrderId,
+ // 检查库存 & 计算订单价格
+ async function getOrderInfo() {
+ // 计算价格
+ const { data, code } = await OrderApi.settlementOrder({
+ // 设置收货地址
+ if (state.orderInfo.address) {
+ state.addressInfo = state.orderInfo.address;
+ // 获取可用优惠券
+ async function getCoupons() {
+ const { code, data } = await CouponApi.getMatchCouponList(
+ state.orderInfo.price.payPrice,
+ state.orderInfo.items.map((item) => item.spuId),
+ state.orderPayload.items.map((item) => item.skuId),
+ state.orderPayload.items.map((item) => item.categoryId),
+ if (!options.data) {
+ sheep.$helper.toast('参数不正确,请检查!');
+ state.orderPayload = JSON.parse(options.data);
+ await getCoupons();
+ .uni-input-wrapper {
+ width: 320rpx;
+ .uni-easyinput__content-input {
+ text-align: right !important;
+ padding-right: 0 !important;
+ .uni-easyinput__content {
+ display: flex !important;
+ align-items: center !important;
+ justify-content: right !important;
+ .score-img {
+ margin: 0 4rpx;
+ .text-disabled {
+ .remark-input {
+ .item-placeholder {
+ .total-box-footer {
+ height: 90rpx;
+ .footer-box {
+ .submit-btn {
+ width: 240rpx;
+ .goto-pay-text {
+ .cancel-btn {
+ background-color: #e5e5e5;
+ .cicon-checkbox {
+ .cicon-box {
+ .bg-red{
+ background-color: #EC3534;
@@ -0,0 +1,738 @@
+ <s-layout title="确认订单">
+ <view class="bgc"></view>
+ <view style="padding: 0 30rpx;">
+ <!-- <view class="head tab-right-active">
+ <view class="tab-box" v-if="state.tabIndex>0">
+ <view class="item left" :class="{'active':state.tabIndex == 1}" @click="state.tabIndex = 1">
+ <view class="tit-box">
+ <text>配送</text>
+ <view class="item right" :class="{'active':state.tabIndex == 2}" @click="state.tabIndex=2">
+ <text>到店自取</text>
+ <view class="bg-white address-box ss-m-b-14 ss-r-b-10 tab2-box" v-if="state.tabIndex == 2">
+ <view class="top-address">
+ 我是商家的预留地址
+ <view class="under-box">
+ <view class="time font">
+ <view>自取时间</view>
+ <view class="chooseTime" @click="chooseTime">
+ <picker mode="time" :value="state.time" :end="'23:59'" @change="bindTimeChange" >
+ <view>{{state.time}}</view>
+ <view class="line">
+ <view class="tel font">
+ <view>预留电话</view>
+ <view class="edit-icon">
+ <view class="ss-flex ss-col-center width">
+ <input maxlength="20" v-model="state.tel"
+ :inputBorder="false" :clearable="false" :focus="state.focus" @blur="onBlur"/>
+ <image :src="sheep.$url.static('/static/img/shop/user/address/edit.png')" @click="editTel"/>
+ <s-goods-item v-for="item in state.orderInfo.items" :key="item.skuId" :img="item.picUrl"
+ :title="item.spuName" :skuText="item.properties.map((property) => property.valueName).join(' ')"
+ :price="item.price" :num="item.count" marginBottom="10" />
+ <uni-easyinput maxlength="20" placeholder="建议留言前先与商家沟通" v-model="state.orderPayload.remark"
+ :inputBorder="false" :clearable="false" />
+ <view class="bg-white total-card-box ss-p-20 ss-m-b-14 ss-r-10" v-if="false">
+ <view class="order-item ss-flex ss-col-center ss-row-between"
+ v-if="state.orderPayload.order_type === 'score'">
+ <image :src="sheep.$url.static('/static/img/shop/goods/score1.svg')" class="score-img" />
+ <view class="order-item ss-flex ss-col-center ss-row-between" v-if="state.orderInfo.type === 0">
+ <text class="item-value" :class="state.couponInfo.length > 0 ? 'text-red' : 'text-disabled'"
+ v-else>
+ v-if="state.orderInfo.price.discountPrice > 0">
+ v-if="state.orderInfo.price.vipPrice > 0">
+ <s-coupon-select v-model="state.couponInfo" :show="state.showCoupon" @confirm="onSelectCoupon"
+ @close="state.showCoupon = false" />
+ <s-discount-list v-model="state.orderInfo" :show="state.showDiscount" @close="state.showDiscount = false" />
+ <button class="ss-reset-button ui-BG-Main-Gradient ss-r-40 submit-btn ui-Shadow-Main" @tap="onConfirm">
+ ref,
+ onLoad
+ isEmpty
+ tabIndex: 1, //头部选项卡 参数为 0不显示选项卡,1,2
+ // time:
+ hours: computed(() => new Date().getHours()),
+ time: computed(() => {
+ const hours = new Date().getHours();
+ const minutes = new Date().getMinutes();
+ return `${hours < 10 ? '0' + hours : hours}:${minutes < 10 ? '0' + minutes : minutes}`;
+ tel:'18827761619',
+ focus:false
+ function bindTimeChange(e) {
+ console.log(e,'eeeeeeeee')
+ const selectedTime = e.target.value;
+ const currentTime = state.time
+ console.log(selectedTime,currentTime,'currentTime')
+ if (selectedTime < currentTime) {
+ console.log('进来了小于当前时间')
+ // 用户选择的时间早于当前时间,您可以进行相应的处理,比如提示用户或重置为当前时间
+ state.time = currentTime;
+ console.log(state.time,'小于小于')
+ console.log('进来了大于大于')
+ state.time = selectedTime;
+ console.log(state.time,'大于大于')
+ function editTel(){
+ console.log('点击了')
+ // 获取输入框元素
+ state.focus=true
+ function onBlur() {
+ console.log('我失焦了');
+ validatePhoneNumber(state.tel);
+ function validatePhoneNumber(phoneNumber) {
+ const phonePattern = /^1[0-9]{10}$/; // 手机号正则表达式
+ if (phonePattern.test(phoneNumber)) {
+ console.log('手机号合法');
+ // uni.showToast({
+ // title: '手机号合法',
+ // });
+ icon: 'none',
+ title: '手机号不合法',
+ state.tel=''
+ console.log('手机号不合法');
+ } = await OrderApi.createOrder({
+ deliveryType: state.tabIndex, // TODO 芋艿:需要支持【门店自提】
+ code
+ } = await OrderApi.settlementOrder({
+ } = await CouponApi.getMatchCouponList(
+ .bg-red {
+ .bgc {
+ background: linear-gradient(180deg, #EC3534 0%, rgba(236, 53, 52, 0) 100%);
+ .head {
+ // background: linear-gradient(to bottom, #ffc107, #f1f1f1);
+ padding-top: 20px;
+ .tab-box {
+ // margin: 0 12px;
+ .tit-box {
+ line-height: 36px;
+ border-top-left-radius: 6px;
+ border-top-right-radius: 6px;
+ background-color: rgba(#ffffff, .6);
+ .item:first-child {
+ .item:last-child {
+ .active {
+ width: 51%;
+ background-color: rgba(#ffffff, 1);
+ .left .tit-box {
+ // margin-left: 12px;
+ margin-right: -12px;
+ padding-right: 12px;
+ .right .tit-box {
+ // margin-right: 12px;
+ margin-left: -12px;
+ padding-left: 12px;
+ .left.active .tit-box {
+ margin: 0;
+ .right.active .tit-box {
+ .left.active .tit-box:after {
+ right: -12px;
+ border-top: 12px solid transparent;
+ border-right: 12px solid transparent;
+ border-bottom: 36px solid #ffffff;
+ .right.active .tit-box:after {
+ left: -12px;
+ border-left: 12px solid transparent;
+ .head-bottom {
+ border-bottom-left-radius: 6px;
+ border-bottom-right-radius: 6px;
+ .tab {
+ .t-top {
+ &-box {
+ .txt1 {
+ color: #888;
+ .icon1 {
+ margin-left: 8px;
+ &-box:active {
+ .t-bb {
+ margin: 0 12px;
+ height: 0.5px;
+ background-color: #E4E7ED;
+ .t-bottom {
+ .row1 {
+ //padding: 12px 0 0;
+ color: #ff9800;
+ .row2 {
+ margin-top: 6px;
+ .head-bottom.selectOne {
+ border-top-left-radius: 0;
+ border-top-right-radius: px;
+ .head-bottom.selectTow {
+ border-top-right-radius: 0;
+ .tab2-box {
+ padding: 30rpx 20rpx;
+ .top-address {
+ color: #313131;
+ .under-box {
+ .font {
+ color: #7C7C7C;
+ background-color: #D8D8D8;
+ margin: 0 14rpx;
+ .chooseTime {
+ .tel {
+ .edit-icon {
+ margin-left: 18rpx;
+ .width{
+ width:200rpx;
@@ -0,0 +1,634 @@
+<!-- 订单详情 -->
+ <s-layout title="订单详情" class="index-wrap" navbar="inner">
+ <!-- 订单状态 TODO -->
+ class="state-box ss-flex-col ss-col-center ss-row-right"
+ <view class="ss-flex ss-m-t-32 ss-m-b-20">
+ state.orderInfo.status_code == 'unpaid' ||
+ state.orderInfo.status === 10 || // 待发货
+ state.orderInfo.status_code == 'nocomment'
+ class="state-img"
+ :src="sheep.$url.static('/static/img/shop/order/order_loading.png')"
+ state.orderInfo.status_code == 'completed' ||
+ state.orderInfo.status_code == 'refund_agree'
+ :src="sheep.$url.static('/static/img/shop/order/order_success.png')"
+ v-if="state.orderInfo.status_code == 'cancel' || state.orderInfo.status_code == 'closed'"
+ :src="sheep.$url.static('/static/img/shop/order/order_close.png')"
+ v-if="state.orderInfo.status_code == 'noget'"
+ :src="sheep.$url.static('/static/img/shop/order/order_express.png')"
+ <view class="ss-font-30">{{ formatOrderStatus(state.orderInfo) }}</view>
+ <view class="ss-font-26 ss-m-x-20 ss-m-b-70">{{
+ formatOrderStatusDescription(state.orderInfo)
+ }}</view>
+ <!-- 收货地址 -->
+ <view class="order-address-box" v-if="state.orderInfo.receiverAreaId > 0">
+ <text class="address-username">
+ {{ state.orderInfo.receiverName }}
+ <text class="address-phone">{{ state.orderInfo.receiverMobile }}</text>
+ <view class="address-detail">
+ {{ state.orderInfo.receiverAreaName }} {{ state.orderInfo.receiverDetailAddress }}
+ class="detail-goods"
+ :style="[{ marginTop: state.orderInfo.receiverAreaId > 0 ? '0' : '-40rpx' }]"
+ <!-- 订单信 -->
+ <view class="order-list" v-for="item in state.orderInfo.items" :key="item.goods_id">
+ <view class="order-card">
+ @tap="onGoodsDetail(item.skuId)"
+ <template #tool>
+ class="ss-reset-button apply-btn"
+ v-if="[10, 20, 30].includes(state.orderInfo.status) && item.afterSaleStatus === 0"
+ @tap.stop="
+ sheep.$router.go('/pages/order/aftersale/apply', {
+ orderId: state.orderInfo.id,
+ itemId: item.id,
+ 申请售后
+ v-if="item.afterSaleStatus === 10"
+ sheep.$router.go('/pages/order/aftersale/detail', {
+ id: item.afterSaleId,
+ 退款中
+ v-if="item.afterSaleStatus === 20"
+ 退款成功
+ <template #priceSuffix>
+ <button class="ss-reset-button tag-btn" v-if="item.status_text">
+ {{ item.status_text }}
+ <!-- 订单信息 -->
+ <view class="notice-box__content">
+ <view class="notice-item--center">
+ <view class="ss-flex ss-flex-1">
+ <text class="title">订单编号:</text>
+ <text class="detail">{{ state.orderInfo.no }}</text>
+ <view class="notice-item">
+ <text class="title">下单时间:</text>
+ <text class="detail">
+ {{ sheep.$helper.timeFormat(state.orderInfo.createTime, 'yyyy-mm-dd hh:MM:ss') }}
+ <view class="notice-item" v-if="state.orderInfo.payTime">
+ <text class="title">支付时间:</text>
+ {{ sheep.$helper.timeFormat(state.orderInfo.payTime, 'yyyy-mm-dd hh:MM:ss') }}
+ <text class="title">支付方式:</text>
+ <text class="detail">{{ state.orderInfo.payChannelName || '-' }}</text>
+ <view class="order-price-box">
+ <view class="notice-item ss-flex ss-row-between">
+ <text class="title">商品总额</text>
+ <text class="detail">¥{{ fen2yuan(state.orderInfo.totalPrice) }}</text>
+ <text class="title">运费</text>
+ <text class="detail">¥{{ fen2yuan(state.orderInfo.deliveryPrice) }}</text>
+ <!-- TODO 芋艿:优惠劵抵扣、积分抵扣 -->
+ <view class="notice-item ss-flex ss-row-between" v-if="state.orderInfo.couponPrice > 0">
+ <text class="title">优惠劵金额</text>
+ <text class="detail">-¥{{ fen2yuan(state.orderInfo.couponPrice) }}</text>
+ <view class="notice-item ss-flex ss-row-between" v-if="state.orderInfo.discountPrice > 0">
+ <text class="title">活动优惠</text>
+ <text class="detail">¥{{ fen2yuan(state.orderInfo.discountPrice) }}</text>
+ <view class="notice-item ss-flex ss-row-between" v-if="state.orderInfo.vipPrice > 0">
+ <text class="title">会员优惠</text>
+ <text class="detail">-¥{{ fen2yuan(state.orderInfo.vipPrice) }}</text>
+ <view class="notice-item all-rpice-item ss-flex ss-m-t-20">
+ <text class="title">{{ state.orderInfo.payStatus ? '已付款' : '需付款' }}</text>
+ <text class="detail all-price">¥{{ fen2yuan(state.orderInfo.payPrice) }}</text>
+ class="notice-item all-rpice-item ss-flex ss-m-t-20"
+ v-if="state.orderInfo.refundPrice > 0"
+ <text class="title">已退款</text>
+ <text class="detail all-price">¥{{ fen2yuan(state.orderInfo.refundPrice) }}</text>
+ <!-- TODO: 查看物流、等待成团、评价完后返回页面没刷新页面 -->
+ <su-fixed bottom placeholder bg="bg-white" v-if="state.orderInfo.buttons?.length">
+ <view class="footer-box ss-flex ss-col-center ss-row-right">
+ class="ss-reset-button cancel-btn"
+ v-if="state.orderInfo.buttons?.includes('cancel')"
+ @tap="onCancel(state.orderInfo.id)"
+ 取消订单
+ class="ss-reset-button pay-btn ui-BG-Main-Gradient"
+ v-if="state.orderInfo.buttons?.includes('pay')"
+ @tap="onPay(state.orderInfo.payOrderId)"
+ 继续支付
+ <!-- TODO 芋艿:拼团接入 -->
+ v-if="state.orderInfo.buttons?.includes('combination')"
+ sheep.$router.go('/pages/activity/groupon/detail', {
+ id: state.orderInfo.ext.groupon_id,
+ 拼团详情
+ v-if="state.orderInfo.buttons?.includes('express')"
+ @tap="onExpress(state.orderInfo.id)"
+ 查看物流
+ v-if="state.orderInfo.buttons?.includes('confirm')"
+ @tap="onConfirm(state.orderInfo.id)"
+ 确认收货
+ v-if="state.orderInfo.buttons?.includes('comment')"
+ @tap="onComment(state.orderInfo.id)"
+ 评价
+ formatOrderStatus,
+ formatOrderStatusDescription,
+ handleOrderButtons,
+ merchantTradeNo: '', // 商户订单号
+ comeinType: '', // 进入订单详情的来源类型
+ sheep.$helper.copyText(state.orderInfo.sn);
+ // 去支付
+ function onPay(payOrderId) {
+ sheep.$router.go('/pages/pay/index', {
+ id: payOrderId,
+ // 查看商品
+ function onGoodsDetail(id) {
+ id,
+ // 取消订单
+ async function onCancel(orderId) {
+ content: '确定要取消订单吗?',
+ success: async function (res) {
+ const { code } = await OrderApi.cancelOrder(orderId);
+ await getOrderDetail(orderId);
+ // 查看物流
+ async function onExpress(id) {
+ sheep.$router.go('/pages/order/express/log', {
+ // 确认收货 TODO 芋艿:待测试
+ async function onConfirm(orderId, ignore = false) {
+ // 需开启确认收货组件
+ // todo: 芋艿:待接入微信
+ // 1.怎么检测是否开启了发货组件功能?如果没有开启的话就不能在这里return出去
+ // 2.如果开启了走mpConfirm方法,需要在App.vue的show方法中拿到确认收货结果
+ let isOpenBusinessView = true;
+ sheep.$platform.name === 'WechatMiniProgram' &&
+ !isEmpty(state.orderInfo.wechat_extra_data) &&
+ isOpenBusinessView &&
+ !ignore
+ mpConfirm(orderId);
+ // 正常的确认收货流程
+ const { code } = await OrderApi.receiveOrder(orderId);
+ // 小程序确认收货组件
+ function mpConfirm(orderId) {
+ if (!wx.openBusinessView) {
+ sheep.$helper.toast(`请升级微信版本`);
+ wx.openBusinessView({
+ businessType: 'weappOrderConfirm',
+ extraData: {
+ merchant_trade_no: state.orderInfo.wechat_extra_data.merchant_trade_no,
+ transaction_id: state.orderInfo.wechat_extra_data.transaction_id,
+ success(response) {
+ console.log('success:', response);
+ if (response.errMsg === 'openBusinessView:ok') {
+ if (response.extraData.status === 'success') {
+ onConfirm(orderId, true);
+ fail(error) {
+ complete(result) {
+ console.log('result:', result);
+ // 评价
+ function onComment(id) {
+ sheep.$router.go('/pages/goods/comment/add', {
+ id
+ async function getOrderDetail(id) {
+ // 对详情数据进行适配
+ let res;
+ if (state.comeinType === 'wechat') {
+ // TODO 芋艿:微信场景下
+ res = await OrderApi.getOrder(id, {
+ merchant_trade_no: state.merchantTradeNo,
+ res = await OrderApi.getOrder(id);
+ if (res.code === 0) {
+ state.orderInfo = res.data;
+ handleOrderButtons(state.orderInfo);
+ let id = 0;
+ if (options.id) {
+ id = options.id;
+ // TODO 芋艿:下面两个变量,后续接入
+ state.comeinType = options.comein_type;
+ state.merchantTradeNo = options.merchant_trade_no;
+ await getOrderDetail(id);
+ .apply-btn {
+ border: 2rpx solid #dcdcdc;
+ .state-box {
+ .state-img {
+ .order-address-box {
+ margin: -50rpx 20rpx 16rpx 20rpx;
+ padding: 44rpx 34rpx 42rpx 20rpx;
+ .address-username {
+ .address-detail {
+ .detail-goods {
+ .order-list {
+ .order-card {
+ padding: 20rpx 0;
+ .order-sku {
+ width: 450rpx;
+ .order-num {
+ .tag-btn {
+ border: 2rpx solid var(--ui-BG-Main);
+ // 订单信息。
+ .notice-box__head {
+ border-bottom: 1rpx solid #dfdfdf;
+ .notice-box__content {
+ .self-pickup-box {
+ .self-pickup--img {
+ margin: 40rpx 0;
+ .notice-item,
+ .notice-item--center {
+ .detail {
+ // 订单价格信息
+ .order-price-box {
+ .notice-item {
+ .all-rpice-item {
+ .all-price {
+ // 底部
+ padding-right: 20rpx;
@@ -0,0 +1,162 @@
+<!-- 物流追踪 -->
+ <s-layout title="物流追踪">
+ <view class="log-wrap">
+ <view class="log-card ss-flex ss-m-20 ss-r-10" v-if="goodsImages.length > 0">
+ <uni-swiper-dot :info="goodsImages" :current="state.current" mode="round">
+ <swiper class="swiper-box">
+ <swiper-item v-for="(item, index) in goodsImages" :key="index">
+ <image class="log-card-img" :src="sheep.$url.static(item.image)" />
+ </uni-swiper-dot>
+ <view class="log-card-msg">
+ <!-- TODO 芋艿:优化点:展示状态 -->
+<!-- <view class="ss-flex ss-m-b-8">-->
+<!-- <view>物流状态:</view>-->
+<!-- <view class="warning-color">{{ state.info.status_text }}</view>-->
+<!-- </view>-->
+ <view class="ss-m-b-8">快递单号:{{ state.info.logisticsNo }}</view>
+ <view>快递公司:{{ state.info.logisticsName }}</view>
+ <!-- 物流轨迹 -->
+ <view class="log-content ss-m-20 ss-r-10">
+ class="log-content-box ss-flex"
+ v-for="(item, index) in state.tracks"
+ :key="item.title"
+ <text class="cicon-title" />
+ <view v-if="state.tracks.length - 1 !== index" class="line" />
+ <view class="log-content-msg">
+<!-- <view class="log-msg-title ss-m-b-20">-->
+<!-- {{ item.status_text }}-->
+ <view class="log-msg-desc ss-m-b-16">{{ item.content }}</view>
+ <view class="log-msg-date ss-m-b-40">
+ {{ sheep.$helper.timeFormat(item.time, 'yyyy-mm-dd hh:MM:ss') }}
+ info: [],
+ tracks: [],
+ const goodsImages = computed(() => {
+ let array = [];
+ if (state.info.items) {
+ state.info.items.forEach((item) => {
+ array.push({
+ image: item.picUrl,
+ return array;
+ async function getExpressDetail(id) {
+ const { data } = await OrderApi.getOrderExpressTrackList(id);
+ state.tracks = data.reverse();
+ const { data } = await OrderApi.getOrder(id)
+ getExpressDetail(options.id);
+ getOrderDetail(options.id);
+ .log-card {
+ border-top: 2rpx solid rgba(#dfdfdf, 0.5);
+ .log-card-img {
+ .log-card-msg {
+ width: 490rpx;
+ .log-content {
+ padding: 34rpx 20rpx 0rpx 20rpx;
+ .log-content-box {
+ color: #ccc;
+ color: #f0c785;
+ background: #d8d8d8;
+ .log-content-msg {
+ .log-msg-title {
+ .log-msg-desc {
+ .log-msg-date {
@@ -0,0 +1,468 @@
+<!-- 订单列表 -->
+ <s-layout title="我的订单">
+ <view class="bg-white order-list-card-box ss-r-10 ss-m-t-14 ss-m-20" v-for="order in state.pagination.list"
+ :key="order.id" @tap="onOrderDetail(order.id)">
+ <view class="order-no">订单号:{{ order.no }}</view>
+ <view class="order-state ss-font-26" :class="formatOrderColor(order)">
+ {{ formatOrderStatus(order) }}
+ <view class="border-bottom" v-for="item in order.items" :key="item.id">
+ <s-goods-item :img="item.picUrl" :title="item.spuName"
+ :skuText="item.properties.map((property) => property.valueName).join(' ')" :price="item.price"
+ :num="item.count" />
+ <view class="pay-box ss-m-t-30 ss-flex ss-row-right ss-p-r-20">
+ <view class="discounts-title pay-color">共 {{ order.productCount }} 件商品,总金额:</view>
+ <view class="discounts-money pay-color">
+ ¥{{ fen2yuan(order.payPrice) }}
+ <view class="order-card-footer ss-flex ss-col-center ss-p-x-20"
+ :class="order.buttons.length > 3 ? 'ss-row-between' : 'ss-row-right'">
+ <button v-if="order.buttons.includes('combination')" class="tool-btn ss-reset-button"
+ @tap.stop="onOrderGroupon(order)">
+ <button v-if="order.buttons.length === 0" class="tool-btn ss-reset-button"
+ @tap.stop="onOrderDetail(order.id)">
+ 查看详情
+ <button v-if="order.buttons.includes('confirm')" class="tool-btn ss-reset-button"
+ @tap.stop="onConfirm(order)">
+ <button v-if="order.buttons.includes('express')" class="tool-btn ss-reset-button"
+ @tap.stop="onExpress(order.id)">
+ <button v-if="order.buttons.includes('cancel')" class="tool-btn ss-reset-button"
+ @tap.stop="onCancel(order.id)">
+ <button v-if="order.buttons.includes('comment')" class="tool-btn ss-reset-button"
+ @tap.stop="onComment(order.id)">
+ <button v-if="order.buttons.includes('delete')" class="delete-btn ss-reset-button"
+ @tap.stop="onDelete(order.id)">
+ 删除订单
+ <button v-if="order.buttons.includes('pay')"
+ class="tool-btn ss-reset-button ui-BG-Main-Gradient" @tap.stop="onPay(order.payOrderId)">
+ onReachBottom,
+ formatOrderColor,
+ resetPagination
+ loadStatus: ''
+ name: '全部'
+ name: '待付款',
+ name: '待发货',
+ value: 10,
+ name: '待收货',
+ value: 20,
+ name: '待评价',
+ value: 30,
+ if (state.currentTab === e.index) {
+ // 重头加载代码
+ function onOrderDetail(id) {
+ // 分享拼团 TODO 芋艿:待测试
+ function onOrderGroupon(order) {
+ id: order.ext.groupon_id,
+ // 继续支付
+ async function onConfirm(order, ignore = false) {
+ // todo: 芋艿:需要后续接入微信收货组件
+ !isEmpty(order.wechat_extra_data) &&
+ mpConfirm(order);
+ } = await OrderApi.receiveOrder(order.id);
+ // 小程序确认收货组件 TODO 芋艿:后续再接入
+ function mpConfirm(order) {
+ merchant_id: '1481069012',
+ merchant_trade_no: order.wechat_extra_data.merchant_trade_no,
+ transaction_id: order.wechat_extra_data.transaction_id,
+ onConfirm(order, true);
+ } = await OrderApi.cancelOrder(orderId);
+ // 修改数据的状态
+ let index = state.pagination.list.findIndex((order) => order.id === orderId);
+ const orderInfo = state.pagination.list[index];
+ orderInfo.status = 40;
+ handleOrderButtons(orderInfo);
+ // 删除订单
+ function onDelete(orderId) {
+ content: '确定要删除订单吗?',
+ } = await OrderApi.deleteOrder(orderId);
+ // 删除数据
+ state.pagination.list.splice(index, 1);
+ let {
+ } = await OrderApi.getOrderPage({
+ commentStatus: tabMaps[state.currentTab].value === 30 ? false : null
+ data.list.forEach(order => handleOrderButtons(order));
+ .delete-btn {
+ background: #fee;
+ border-radius: 28rpx;
+ .order-state {}
+ .pay-box {
+ .discounts-money {
+ .pay-color {
+ width: 154rpx;
+ :deep(.uni-tooltip-popup) {
@@ -0,0 +1,288 @@
+<!-- 收银台 -->
+ <s-layout title="收银台">
+ <view class="bg-white ss-modal-box ss-flex-col">
+ <view class="modal-header ss-flex-col ss-col-center ss-row-center">
+ <view class="money-box ss-m-b-20">
+ <text class="money-text">{{ fen2yuan(state.orderInfo.price) }}</text>
+ <view class="time-text">
+ <text>{{ payDescText }}</text>
+ <!-- 支付方式 -->
+ <view class="modal-content ss-flex-1">
+ <view class="pay-title ss-p-l-30 ss-m-y-30">选择支付方式</view>
+ <radio-group @change="onTapPay">
+ <label class="pay-type-item" v-for="item in state.payMethods" :key="item.title">
+ class="pay-item ss-flex ss-col-center ss-row-between ss-p-x-30 border-bottom"
+ :class="{ 'disabled-pay-item': item.disabled }"
+ class="pay-icon"
+ v-if="item.disabled"
+ :src="sheep.$url.static('/static/img/shop/pay/cod_disabled.png')"
+ :src="sheep.$url.static(item.icon)"
+ <text class="pay-title">{{ item.title }}</text>
+ <view class="check-box ss-flex ss-col-center ss-p-l-10">
+ <view class="userInfo-money ss-m-r-10" v-if="item.value === 'wallet'">
+ 余额: {{ fen2yuan(userWallet.balance) }}元
+ :disabled="item.disabled"
+ :checked="state.payment === item.value"
+ <!-- 工具 -->
+ <view class="modal-footer ss-flex ss-row-center ss-col-center ss-m-t-80 ss-m-b-40">
+ <button v-if="state.payStatus === 0" class="ss-reset-button past-due-btn">
+ 检测支付环境中
+ <button v-else-if="state.payStatus === -1" class="ss-reset-button past-due-btn" disabled>
+ 支付已过期
+ class="ss-reset-button save-btn"
+ @tap="onPay"
+ :disabled="state.payStatus !== 1"
+ :class="{ 'disabled-btn': state.payStatus !== 1 }"
+ 立即支付
+ import { fen2yuan, useDurationTime } from '@/sheep/hooks/useGoods';
+ import PayOrderApi from '@/sheep/api/pay/order';
+ import PayChannelApi from '@/sheep/api/pay/channel';
+ import { getPayMethods } from '@/sheep/platform/pay';
+ const userWallet = computed(() => sheep.$store('user').userWallet);
+ // 检测支付环境
+ orderType: 'goods', // 订单类型; goods - 商品订单, recharge - 充值订单
+ orderInfo: {}, // 支付单信息
+ payStatus: 0, // 0=检测支付环境, -2=未查询到支付单信息, -1=支付已过期, 1=待支付,2=订单已支付
+ payMethods: [], // 可选的支付方式
+ payment: '', // 选中的支付方式
+ const onPay = () => {
+ if (state.payment === '') {
+ sheep.$helper.toast('请选择支付方式');
+ if (state.payment === 'wallet') {
+ content: '确定要支付吗?',
+ sheep.$platform.pay(state.payment, state.orderType, state.orderInfo.id);
+ // 支付文案提示
+ const payDescText = computed(() => {
+ if (state.payStatus === 2) {
+ return '该订单已支付';
+ if (state.payStatus === 1) {
+ const time = useDurationTime(state.orderInfo.expireTime);
+ if (time.ms <= 0) {
+ state.payStatus = -1;
+ return `剩余支付时间 ${time.h}:${time.m}:${time.s} `;
+ if (state.payStatus === -2) {
+ return '未查询到支付单信息';
+ // 状态转换:payOrder.status => payStatus
+ function checkPayStatus() {
+ if (state.orderInfo.status === 10
+ || state.orderInfo.status === 20 ) { // 支付成功
+ state.payStatus = 2;
+ if (state.orderInfo.status === 30) { // 支付关闭
+ state.payStatus = 1; // 待支付
+ // 切换支付方式
+ function onTapPay(e) {
+ state.payment = e.detail.value;
+ // 设置支付订单信息
+ async function setOrder(id) {
+ // 获得支付订单信息
+ const { data, code } = await PayOrderApi.getOrder(id);
+ if (code !== 0 || !data) {
+ state.payStatus = -2;
+ // 获得支付方式
+ await setPayMethods();
+ // 设置支付状态
+ checkPayStatus();
+ async function setPayMethods() {
+ const { data, code } = await PayChannelApi.getEnableChannelCodeList(state.orderInfo.appId)
+ state.payMethods = getPayMethods(data)
+ if (sheep.$platform.name === 'WechatOfficialAccount'
+ && sheep.$platform.os === 'ios'
+ && !sheep.$platform.landingPage.includes('pages/pay/index')) {
+ location.reload();
+ let id = options.id;
+ if (options.orderType) {
+ state.orderType = options.orderType;
+ setOrder(id);
+ // 刷新钱包的缓存
+ sheep.$store('user').getWallet();
+ .pay-icon {
+ margin-right: 26rpx;
+ // max-height: 1000rpx;
+ padding: 60rpx 20rpx 40rpx;
+ .money-text {
+ color: $gray-b;
+ .pay-title {
+ .pay-tip {
+ .pay-item {
+ .disabled-pay-item {
+ .userInfo-money {
+ .past-due-btn {
+ background-color: #999;