Browse Source

添加tcb-shop-demo到仓库

yourname 1 tháng trước cách đây
mục cha
commit
8250097ee5
100 tập tin đã thay đổi với 5089 bổ sung1 xóa
  1. 0 1
      .gitignore
  2. 157 0
      tcb-shop-demo/.eslintrc.js
  3. 0 0
      tcb-shop-demo/.gitignore
  4. 9 0
      tcb-shop-demo/.npmrc
  5. 3 0
      tcb-shop-demo/.prettierignore
  6. 49 0
      tcb-shop-demo/.prettierrc.yml
  7. 9 0
      tcb-shop-demo/LICENSE
  8. 50 0
      tcb-shop-demo/README.md
  9. 18 0
      tcb-shop-demo/app.js
  10. 76 0
      tcb-shop-demo/app.json
  11. 3 0
      tcb-shop-demo/app.wxss
  12. 1 0
      tcb-shop-demo/commitlint.config.js
  13. 29 0
      tcb-shop-demo/common/updateManager.js
  14. 36 0
      tcb-shop-demo/components/filter-popup/index.js
  15. 6 0
      tcb-shop-demo/components/filter-popup/index.json
  16. 18 0
      tcb-shop-demo/components/filter-popup/index.wxml
  17. 39 0
      tcb-shop-demo/components/filter-popup/index.wxss
  18. 84 0
      tcb-shop-demo/components/filter/index.js
  19. 6 0
      tcb-shop-demo/components/filter/index.json
  20. 37 0
      tcb-shop-demo/components/filter/index.wxml
  21. 50 0
      tcb-shop-demo/components/filter/index.wxss
  22. 141 0
      tcb-shop-demo/components/goods-card/index.js
  23. 8 0
      tcb-shop-demo/components/goods-card/index.json
  24. 16 0
      tcb-shop-demo/components/goods-card/index.wxml
  25. 136 0
      tcb-shop-demo/components/goods-card/index.wxss
  26. 62 0
      tcb-shop-demo/components/goods-list/index.js
  27. 6 0
      tcb-shop-demo/components/goods-list/index.json
  28. 16 0
      tcb-shop-demo/components/goods-list/index.wxml
  29. 7 0
      tcb-shop-demo/components/goods-list/index.wxss
  30. 54 0
      tcb-shop-demo/components/load-more/index.js
  31. 7 0
      tcb-shop-demo/components/load-more/index.json
  32. 31 0
      tcb-shop-demo/components/load-more/index.wxml
  33. 35 0
      tcb-shop-demo/components/load-more/index.wxss
  34. 23 0
      tcb-shop-demo/components/loading-content/index.js
  35. 6 0
      tcb-shop-demo/components/loading-content/index.json
  36. 11 0
      tcb-shop-demo/components/loading-content/index.wxml
  37. 23 0
      tcb-shop-demo/components/loading-content/index.wxss
  38. 8 0
      tcb-shop-demo/components/loading-dialog/index.js
  39. 8 0
      tcb-shop-demo/components/loading-dialog/index.json
  40. 5 0
      tcb-shop-demo/components/loading-dialog/index.wxml
  41. 11 0
      tcb-shop-demo/components/loading-dialog/index.wxss
  42. 71 0
      tcb-shop-demo/components/price/index.js
  43. 4 0
      tcb-shop-demo/components/price/index.json
  44. 21 0
      tcb-shop-demo/components/price/index.wxml
  45. 66 0
      tcb-shop-demo/components/price/index.wxss
  46. 79 0
      tcb-shop-demo/components/swipeout/index.js
  47. 4 0
      tcb-shop-demo/components/swipeout/index.json
  48. 174 0
      tcb-shop-demo/components/swipeout/index.wxml
  49. 18 0
      tcb-shop-demo/components/swipeout/index.wxss
  50. 90 0
      tcb-shop-demo/components/webp-image/index.js
  51. 6 0
      tcb-shop-demo/components/webp-image/index.json
  52. 14 0
      tcb-shop-demo/components/webp-image/index.wxml
  53. 0 0
      tcb-shop-demo/components/webp-image/index.wxss
  54. 140 0
      tcb-shop-demo/components/webp-image/utils.wxs
  55. 91 0
      tcb-shop-demo/config/eslintCheck.js
  56. 12 0
      tcb-shop-demo/config/index.js
  57. 12 0
      tcb-shop-demo/config/model.js
  58. 22 0
      tcb-shop-demo/custom-tab-bar/data.js
  59. 29 0
      tcb-shop-demo/custom-tab-bar/index.js
  60. 8 0
      tcb-shop-demo/custom-tab-bar/index.json
  61. 18 0
      tcb-shop-demo/custom-tab-bar/index.wxml
  62. 9 0
      tcb-shop-demo/custom-tab-bar/index.wxss
  63. 5 0
      tcb-shop-demo/jsconfig.json
  64. 7 0
      tcb-shop-demo/model/activities.js
  65. 18 0
      tcb-shop-demo/model/activity.js
  66. 31 0
      tcb-shop-demo/model/address.js
  67. 324 0
      tcb-shop-demo/model/cart.js
  68. 206 0
      tcb-shop-demo/model/category.js
  69. 338 0
      tcb-shop-demo/model/comments.js
  70. 14 0
      tcb-shop-demo/model/comments/queryDetail.js
  71. 39 0
      tcb-shop-demo/model/coupon.js
  72. 30 0
      tcb-shop-demo/model/detailsComments.js
  73. 25 0
      tcb-shop-demo/model/good.js
  74. 7 0
      tcb-shop-demo/model/goods.js
  75. 295 0
      tcb-shop-demo/model/order/applyService.js
  76. 147 0
      tcb-shop-demo/model/order/orderConfirm.js
  77. 26 0
      tcb-shop-demo/model/order/orderDetail.js
  78. 46 0
      tcb-shop-demo/model/order/orderList.js
  79. 21 0
      tcb-shop-demo/model/promotion.js
  80. 60 0
      tcb-shop-demo/model/search.js
  81. 58 0
      tcb-shop-demo/model/submitComment.js
  82. 39 0
      tcb-shop-demo/model/swiper.js
  83. 52 0
      tcb-shop-demo/model/usercenter.js
  84. 46 0
      tcb-shop-demo/package.json
  85. 59 0
      tcb-shop-demo/pages/cart/components/cart-bar/index.js
  86. 7 0
      tcb-shop-demo/pages/cart/components/cart-bar/index.json
  87. 14 0
      tcb-shop-demo/pages/cart/components/cart-bar/index.wxml
  88. 80 0
      tcb-shop-demo/pages/cart/components/cart-bar/index.wxss
  89. 27 0
      tcb-shop-demo/pages/cart/components/cart-empty/index.js
  90. 6 0
      tcb-shop-demo/pages/cart/components/cart-empty/index.json
  91. 11 0
      tcb-shop-demo/pages/cart/components/cart-empty/index.wxml
  92. 54 0
      tcb-shop-demo/pages/cart/components/cart-empty/index.wxss
  93. 168 0
      tcb-shop-demo/pages/cart/components/cart-group/index.js
  94. 11 0
      tcb-shop-demo/pages/cart/components/cart-group/index.json
  95. 35 0
      tcb-shop-demo/pages/cart/components/cart-group/index.wxml
  96. 5 0
      tcb-shop-demo/pages/cart/components/cart-group/index.wxs
  97. 335 0
      tcb-shop-demo/pages/cart/components/cart-group/index.wxss
  98. 20 0
      tcb-shop-demo/pages/cart/components/cart-group/utils.wxs
  99. 232 0
      tcb-shop-demo/pages/cart/components/goods-card/index.js
  100. 9 0
      tcb-shop-demo/pages/cart/components/goods-card/index.json

+ 0 - 1
.gitignore

@@ -56,4 +56,3 @@ loop.txt
 tsconfig.tsbuildinfo
 mini/tests/__snapshots__/*
 
-tcb-shop-demo

+ 157 - 0
tcb-shop-demo/.eslintrc.js

@@ -0,0 +1,157 @@
+module.exports = {
+  env: {
+    browser: true,
+    commonjs: true,
+    es6: true,
+  },
+  parserOptions: {
+    ecmaVersion: 2020,
+    // ECMAScript modules 模式
+    sourceType: 'module',
+  },
+  extends: ['plugin:prettier/recommended', 'prettier'],
+  globals: {
+    wx: true,
+    App: true,
+    Page: true,
+    Component: true,
+    getApp: true,
+    getCurrentPages: true,
+    Behavior: true,
+    global: true,
+    __wxConfig: true,
+  },
+  ignorePatterns: ['*.wxs'],
+  rules: {
+    'prettier/prettier': 'warn',
+    'no-undef': 'off',
+    camelcase: 'off',
+    'class-name-casing': 'off',
+    'no-console': ['warn', { allow: ['warn', 'error'] }],
+    'no-debugger': 'error',
+    'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }],
+    'no-empty-interface': 'off',
+    'no-use-before-define': ['error', { functions: false }],
+    'no-useless-constructor': 'error',
+    'prefer-const': 'error',
+    'prefer-destructuring': [
+      'error',
+      {
+        AssignmentExpression: {
+          array: false,
+          object: false,
+        },
+        VariableDeclarator: {
+          array: false,
+          object: true,
+        },
+      },
+      {
+        enforceForRenamedProperties: false,
+      },
+    ],
+    'no-const-assign': 'error',
+    'no-new-object': 'error',
+    'no-prototype-builtins': 'error',
+    'no-array-constructor': 'error',
+    'array-callback-return': 'warn',
+    'prefer-template': 'error',
+    'no-useless-escape': 'error',
+    'wrap-iife': ['error', 'outside'],
+    'space-before-function-paren': [
+      'warn',
+      {
+        anonymous: 'always',
+        named: 'never',
+        asyncArrow: 'always',
+      },
+    ],
+    'no-param-reassign': [
+      'warn',
+      {
+        props: true,
+        ignorePropertyModificationsFor: [
+          'acc', // for reduce accumulators
+          'accumulator', // for reduce accumulators
+          'e', // for e.returnvalue
+          'ctx', // for Koa routing
+          'req', // for Express requests
+          'request', // for Express requests
+          'res', // for Express responses
+          'response', // for Express responses
+          '$scope', // for Angular 1 scopes
+          'staticContext', // for ReactRouter context
+          'state', // for Vuex
+        ],
+      },
+    ],
+    'no-confusing-arrow': 'warn',
+    'no-dupe-class-members': 'error',
+    'no-iterator': 'warn',
+    'dot-notation': 'warn',
+    'one-var': ['warn', 'never'],
+    'no-multi-assign': 'error',
+    'no-unused-vars': [
+      'error',
+      {
+        args: 'after-used',
+        ignoreRestSiblings: true,
+        argsIgnorePattern: '^_.+',
+        varsIgnorePattern: '^_.+',
+      },
+    ],
+    eqeqeq: ['warn', 'always'],
+    'no-case-declarations': 'error',
+    'no-nested-ternary': 'warn',
+    'no-unneeded-ternary': 'warn',
+    'no-mixed-operators': [
+      'error',
+      {
+        groups: [
+          ['%', '**'],
+          ['%', '+'],
+          ['%', '-'],
+          ['%', '*'],
+          ['%', '/'],
+          ['&', '|', '<<', '>>', '>>>'],
+          ['==', '!=', '===', '!=='],
+          ['&&', '||'],
+        ],
+        allowSamePrecedence: false,
+      },
+    ],
+    'no-else-return': [
+      'warn',
+      {
+        allowElseIf: false,
+      },
+    ],
+    'no-new-wrappers': 'warn',
+    indent: [
+      'warn',
+      2,
+      {
+        SwitchCase: 1,
+        VariableDeclarator: 1,
+        outerIIFEBody: 1,
+        FunctionDeclaration: {
+          parameters: 1,
+          body: 1,
+        },
+        FunctionExpression: {
+          parameters: 1,
+          body: 1,
+        },
+        CallExpression: {
+          arguments: 1,
+        },
+        ArrayExpression: 1,
+        ObjectExpression: 1,
+        ImportDeclaration: 1,
+        flatTernaryExpressions: false,
+        ignoreComments: false,
+      },
+    ],
+    'linebreak-style': ['warn', 'unix'],
+  },
+};

+ 0 - 0
tcb-shop-demo/.gitignore


+ 9 - 0
tcb-shop-demo/.npmrc

@@ -0,0 +1,9 @@
+# 去除注释可以使用代理进行安装
+# proxy=http://127.0.0.1:1080
+# https_proxy=http://127.0.0.1:1080
+
+# 去除注释可以使用淘宝源
+# registry=https://registry.npm.taobao.org
+
+# 去除注释可以使用腾讯源
+#registry=http://mirrors.tencent.com/npm/

+ 3 - 0
tcb-shop-demo/.prettierignore

@@ -0,0 +1,3 @@
+miniprogram_npm
+package.json
+project.config.json

+ 49 - 0
tcb-shop-demo/.prettierrc.yml

@@ -0,0 +1,49 @@
+# 一行最多 100 字符
+printWidth: 120
+# 使用 2 个空格缩进
+tabWidth: 2
+# 不使用缩进符,而使用空格
+useTabs: false
+# 行尾需要分号
+semi: true
+# 使用单引号
+singleQuote: true
+# 对象的 key 仅在必要时用引号
+quoteProps: as-needed
+# jsx 不使用单引号,而使用双引号
+jsxSingleQuote: false
+# 末尾需要逗号
+trailingComma: all
+# 大括号内的首尾需要空格
+bracketSpacing: true
+# jsx 标签的反尖括号需要换行
+jsxBracketSameLine: false
+# 箭头函数,只有一个参数的时候,不需要括号
+arrowParens: always
+# 每个文件格式化的范围是文件的全部内容
+rangeStart: 0
+# 不需要写文件开头的 @prettier
+requirePragma: false
+# 不需要自动在文件开头插入 @prettier
+insertPragma: false
+# 使用默认的折行标准
+proseWrap: preserve
+# 根据显示样式决定 html 要不要折行
+htmlWhitespaceSensitivity: css
+# 换行符使用 lf
+endOfLine: lf
+# 后缀文件名特有规则
+overrides:
+  - files: '*.{wxss,less}'
+    options:
+      parser: less
+  - files: '*.json,.*rc'
+    options:
+      parser: json
+  - files: '*.{wxml,html}'
+    options:
+      parser: html
+      htmlWhitespaceSensitivity: strict
+  - files: '*.wxs'
+    options:
+      parser: babel

+ 9 - 0
tcb-shop-demo/LICENSE

@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2021-present TDesign
+
+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.

+ 50 - 0
tcb-shop-demo/README.md

@@ -0,0 +1,50 @@
+<br />
+<div align="center">
+    <img src="https://qcloudimg.tencent-cloud.cn/raw/f97dc74fbf9af5d7b2b3d8bc0a4e91d4.png" alt="Logo" width="400">
+
+  <h1 align="center">云开发电商模板</h1>
+
+  <p align="center">
+  一键创建零售商城
+    <br />
+  </p>
+</div>
+
+## 说明
+
+本项目为云开发电商模板的小程序,提供首页、购物车、订单、个人中心、商品详情等页面。
+
+本项目默认使用体验数据运行,同时也能够使用真实数据。配置好云开发后端后,即可一键切换至真实数据。
+
+注:体验数据通过本地 Mock 数据实现。
+
+本项目的后端可前往<https://tcb.cloud.tencent.com/cloud-template/detail?appName=electronic-business&from=wxide_tcb_shop>安装。
+
+## 社区
+
+欢迎添加企微群沟通交流:
+
+<div>
+    <img src="https://qcloudimg.tencent-cloud.cn/raw/bbb904f6fd6da01aa677e8a31e37651d.jpg" style="width:30%;">
+</div>
+
+## 安装依赖
+
+1. 安装 npm 依赖
+
+```shell
+npm install
+```
+
+ 如果安装失败,请检查是否有足够权限执行命令,或尝试用更高权限安装依赖:
+
+ ```shell
+ sudo npm install
+ ```
+
+2. 构建 npm
+点击微信开发者工具菜单栏中的「工具」->「构建 npm」
+
+## 运行小程序
+
+在微信开发者工具中导入本项目即可运行,若想配合后端运行完整应用,请前往<https://tcb.cloud.tencent.com/cloud-template/detail?appName=electronic-business&from=wxide_tcb_shop>安装。

+ 18 - 0
tcb-shop-demo/app.js

@@ -0,0 +1,18 @@
+import updateManager from './common/updateManager';
+import { init } from '@cloudbase/wx-cloud-client-sdk';
+
+wx.cloud.init({
+  env: 'cloudbase-1gtdgr1nba7424b9', // 指定云开发环境 ID
+});
+
+const client = init(wx.cloud);
+const models = client.models;
+globalThis.dataModel = models;
+// 接下来就可以调用 models 上的数据模型增删改查等方法了
+
+App({
+  onLaunch: function () {},
+  onShow: function () {
+    updateManager();
+  },
+});

+ 76 - 0
tcb-shop-demo/app.json

@@ -0,0 +1,76 @@
+{
+  "pages": [
+    "pages/home/home",
+    "pages/usercenter/index",
+    "pages/usercenter/person-info/index",
+    "pages/usercenter/address/list/index",
+    "pages/usercenter/address/edit/index",
+    "pages/goods/list/index",
+    "pages/goods/details/index",
+    "pages/goods/category/index",
+    "pages/goods/search/index",
+    "pages/goods/result/index",
+    "pages/cart/index",
+    "pages/order/order-confirm/index",
+    "pages/order/receipt/index",
+    "pages/order/pay-result/index",
+    "pages/order/order-list/index",
+    "pages/order/order-detail/index",
+    "pages/goods/comments/index",
+    "pages/order/apply-service/index",
+    "pages/order/after-service-list/index",
+    "pages/order/after-service-detail/index",
+    "pages/goods/comments/create/index",
+    "pages/goods/comments/create-list/index",
+    "pages/coupon/coupon-list/index",
+    "pages/coupon/coupon-detail/index",
+    "pages/coupon/coupon-activity-goods/index",
+    "pages/promotion-detail/index",
+    "pages/order/fill-tracking-no/index",
+    "pages/order/delivery-detail/index",
+    "pages/order/invoice/index",
+    "pages/usercenter/name-edit/index"
+  ],
+  "tabBar": {
+    "custom": true,
+    "color": "#666666",
+    "selectedColor": "#FF5F15",
+    "backgroundColor": "#ffffff",
+    "borderStyle": "black",
+    "list": [
+      {
+        "pagePath": "pages/home/home",
+        "text": "首页"
+      },
+      {
+        "pagePath": "pages/goods/category/index",
+        "text": "分类"
+      },
+      {
+        "pagePath": "pages/cart/index",
+        "text": "购物车"
+      },
+      {
+        "pagePath": "pages/usercenter/index",
+        "text": "我的"
+      }
+    ]
+  },
+  "requiredPrivateInfos": ["chooseAddress"],
+  "lazyCodeLoading": "requiredComponents",
+  "usingComponents": {
+    
+  },
+  "window": {
+    "backgroundTextStyle": "light",
+    "navigationBarBackgroundColor": "#fff",
+    "navigationBarTitleText": "Weixin",
+    "navigationBarTextStyle": "black"
+  },
+  "sitemapLocation": "sitemap.json",
+  "permission": {
+    "scope.userLocation": {
+      "desc": "你的位置信息将用于小程序位置接口的效果展示"
+    }
+  }
+}

+ 3 - 0
tcb-shop-demo/app.wxss

@@ -0,0 +1,3 @@
+@import 'style/iconfont.wxss';
+
+@import 'style/theme.wxss';

+ 1 - 0
tcb-shop-demo/commitlint.config.js

@@ -0,0 +1 @@
+module.exports = { extends: ['@commitlint/config-conventional'] };

+ 29 - 0
tcb-shop-demo/common/updateManager.js

@@ -0,0 +1,29 @@
+export default () => {
+  if (!wx.canIUse('getUpdateManager')) {
+    return;
+  }
+
+  const updateManager = wx.getUpdateManager();
+
+  updateManager.onCheckForUpdate(function (res) {
+    // 请求完新版本信息的回调
+    console.log('版本信息', res);
+  });
+
+  updateManager.onUpdateReady(function () {
+    wx.showModal({
+      title: '更新提示',
+      content: '新版本已经准备好,是否重启应用?',
+      success(res) {
+        if (res.confirm) {
+          // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
+          updateManager.applyUpdate();
+        }
+      },
+    });
+  });
+
+  updateManager.onUpdateFailed(function () {
+    // 新版本下载失败
+  });
+};

+ 36 - 0
tcb-shop-demo/components/filter-popup/index.js

@@ -0,0 +1,36 @@
+Component({
+  externalClasses: ['wr-class'],
+
+  options: {
+    multipleSlots: true,
+  },
+
+  properties: {
+    show: {
+      type: Boolean,
+      observer(show) {
+        this.setData({ visible: show });
+      },
+    },
+    closeBtn: {
+      type: Boolean,
+      value: false,
+    },
+  },
+
+  data: { visible: false },
+
+  methods: {
+    reset() {
+      this.triggerEvent('reset');
+    },
+    confirm() {
+      this.triggerEvent('confirm');
+    },
+    close() {
+      this.triggerEvent('showFilterPopupClose');
+
+      this.setData({ visible: false });
+    },
+  },
+});

+ 6 - 0
tcb-shop-demo/components/filter-popup/index.json

@@ -0,0 +1,6 @@
+{
+    "component": true,
+    "usingComponents": {
+        "t-popup": "tdesign-miniprogram/popup/popup"
+    }
+}

+ 18 - 0
tcb-shop-demo/components/filter-popup/index.wxml

@@ -0,0 +1,18 @@
+<t-popup
+ visible="{{visible}}"
+ placement="right"
+ bind:visible-change="close"
+ data-index="5"
+ close-btn="{{closeBtn}}"
+>
+	<view class="content">
+		<slot name="filterSlot" />
+		<view class="filter-btns-wrap">
+			<view class="filter-btn btn-reset" bind:tap="reset">重置</view>
+			<view class="filter-btn btn-confirm" bind:tap="confirm" data-index="5">
+				确定
+			</view>
+		</view>
+	</view>
+</t-popup>
+

+ 39 - 0
tcb-shop-demo/components/filter-popup/index.wxss

@@ -0,0 +1,39 @@
+.content .filter-btns-wrap {
+  width: 100%;
+  position: absolute;
+  bottom: calc(20rpx + env(safe-area-inset-bottom));
+  display: flex;
+  flex-direction: row;
+  border-radius: 10rpx 0 0 10rpx;
+  padding: 16rpx 32rpx;
+  border-top: 1rpx solid #e5e5e5;
+  box-sizing: border-box;
+}
+
+.filter-btn {
+  flex: 1;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  font-size: 28rpx;
+  font-weight: 500;
+  height: 80rpx;
+}
+
+.btn-reset {
+  color: #fa4126;
+  background: rgba(255, 255, 255, 1);
+  position: relative;
+  border: 1rpx solid #fa4126;
+  border-radius: 84rpx 0 0 84rpx;
+}
+
+.btn-confirm {
+  border-radius: 0 84rpx 84rpx 0;
+  border: 1rpx solid #fa4126;
+}
+
+.btn-confirm {
+  color: #fff;
+  background: #fa4126;
+}

+ 84 - 0
tcb-shop-demo/components/filter/index.js

@@ -0,0 +1,84 @@
+Component({
+  externalClasses: ['wr-class'],
+
+  options: {
+    multipleSlots: true,
+  },
+
+  properties: {
+    overall: {
+      type: Number,
+      value: 1,
+      observer(overall) {
+        this.setData({
+          overall,
+        });
+      },
+    },
+    layout: {
+      type: Number,
+      value: 1,
+      observer(layout) {
+        this.setData({
+          layout,
+        });
+      },
+    },
+    sorts: {
+      type: String,
+      value: '',
+      observer(sorts) {
+        this.setData({
+          sorts,
+        });
+      },
+    },
+    color: {
+      type: String,
+      value: '#FA550F',
+    },
+  },
+
+  data: {
+    layout: 1,
+    overall: 1,
+    sorts: '',
+  },
+
+  methods: {
+    onChangeShowAction() {
+      const { layout } = this.data;
+      const nextLayout = layout === 1 ? 0 : 1;
+      this.triggerEvent('change', { ...this.properties, layout: nextLayout });
+    },
+
+    handlePriseSort() {
+      const { sorts } = this.data;
+      this.triggerEvent('change', {
+        ...this.properties,
+        overall: 0,
+        sorts: sorts === 'desc' ? 'asc' : 'desc',
+      });
+    },
+
+    open() {
+      this.triggerEvent('showFilterPopup', {
+        show: true,
+      });
+    },
+
+    onOverallAction() {
+      const { overall } = this.data;
+      const nextOverall = overall === 1 ? 0 : 1;
+      const nextData = {
+        sorts: '',
+        prices: [],
+      };
+      this.triggerEvent('change', {
+        ...this.properties,
+        ...nextData,
+        overall: nextOverall,
+      });
+    },
+  },
+});

+ 6 - 0
tcb-shop-demo/components/filter/index.json

@@ -0,0 +1,6 @@
+{
+    "component": true,
+    "usingComponents": {
+        "t-icon": "tdesign-miniprogram/icon/icon"
+    }
+}

+ 37 - 0
tcb-shop-demo/components/filter/index.wxml

@@ -0,0 +1,37 @@
+<!-- 过滤组件 -->
+<view class="wr-class filter-wrap">
+	<view class="filter-left-content">
+		<view class="filter-item {{overall === 1 ? 'filter-active-item' : ''}}" bindtap="onOverallAction">
+			综合
+		</view>
+		<view class="filter-item" bind:tap="handlePriseSort">
+			<text style="color: {{sorts !== '' ? color : '' }}">价格</text>
+			<view class="filter-price">
+				<t-icon
+				  prefix="wr"
+				  name="arrow_drop_up"
+				  size="18rpx"
+				  style="color:{{sorts === 'asc' ? color : '#bbb'}}"
+				/>
+				<t-icon
+				  prefix="wr"
+				  name="arrow_drop_down"
+				  size="18rpx"
+				  style="color:{{sorts === 'desc' ? color : '#bbb'}}"
+				/>
+			</view>
+		</view>
+		<view class="filter-item {{prices.length ? 'filter-active-item' : ''}}" bindtap="open" data-index="5">
+			筛选
+			<t-icon
+			  name="filter"
+			  prefix="wr"
+			  color="#333"
+			  size="32rpx"
+			/>
+		</view>
+	</view>
+</view>
+<!-- 筛选弹框 -->
+<slot name="filterPopup" />
+

+ 50 - 0
tcb-shop-demo/components/filter/index.wxss

@@ -0,0 +1,50 @@
+.filter-wrap {
+  width: 100%;
+  height: 88rpx;
+  display: flex;
+  justify-content: space-between;
+  position: relative;
+  background: #fff;
+}
+
+.filter-right-content {
+  height: 100%;
+  flex-basis: 100rpx;
+  text-align: center;
+  line-height: 88rpx;
+}
+
+.filter-left-content {
+  height: 100%;
+  display: flex;
+  flex-grow: 2;
+  flex-flow: row nowrap;
+  justify-content: space-between;
+}
+
+.filter-left-content .filter-item {
+  flex: 1;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 26rpx;
+  line-height: 36rpx;
+  font-weight: 400;
+  color: rgba(51, 51, 51, 1);
+}
+
+.filter-left-content .filter-item .filter-price {
+  display: flex;
+  flex-direction: column;
+  margin-left: 6rpx;
+  justify-content: space-between;
+}
+
+.filter-left-content .filter-item .wr-filter {
+  margin-left: 8rpx;
+}
+
+.filter-left-content .filter-active-item {
+  color: #fa550f;
+}

+ 141 - 0
tcb-shop-demo/components/goods-card/index.js

@@ -0,0 +1,141 @@
+Component({
+  options: {
+    addGlobalClass: true,
+  },
+
+  properties: {
+    id: {
+      type: String,
+      value: '',
+      observer(id) {
+        this.genIndependentID(id);
+        if (this.properties.thresholds?.length) {
+          this.createIntersectionObserverHandle();
+        }
+      },
+    },
+    data: {
+      type: Object,
+      observer(data) {
+        if (!data) {
+          return;
+        }
+        let isValidityLinePrice = true;
+        if (data.originPrice && data.price && data.originPrice < data.price) {
+          isValidityLinePrice = false;
+        }
+        this.setData({ goods: data, isValidityLinePrice });
+      },
+    },
+    currency: {
+      type: String,
+      value: '¥',
+    },
+
+    thresholds: {
+      type: Array,
+      value: [],
+      observer(thresholds) {
+        if (thresholds && thresholds.length) {
+          this.createIntersectionObserverHandle();
+        } else {
+          this.clearIntersectionObserverHandle();
+        }
+      },
+    },
+  },
+
+  data: {
+    independentID: '',
+    goods: { id: '' },
+    isValidityLinePrice: false,
+  },
+
+  lifetimes: {
+    ready() {
+      this.init();
+    },
+    detached() {
+      this.clear();
+    },
+  },
+
+  pageLifeTimes: {},
+
+  methods: {
+    clickHandle() {
+      this.triggerEvent('click', { goods: this.data.goods });
+    },
+
+    clickThumbHandle() {
+      this.triggerEvent('thumb', { goods: this.data.goods });
+    },
+
+    addCartHandle(e) {
+      const { id } = e.currentTarget;
+      const { id: cardID } = e.currentTarget.dataset;
+      this.triggerEvent('add-cart', {
+        ...e.detail,
+        id,
+        cardID,
+        goods: this.data.goods,
+      });
+    },
+
+    genIndependentID(id) {
+      let independentID;
+      if (id) {
+        independentID = id;
+      } else {
+        independentID = `goods-card-${~~(Math.random() * 10 ** 8)}`;
+      }
+      this.setData({ independentID });
+    },
+
+    init() {
+      const { thresholds, id } = this.properties;
+      this.genIndependentID(id);
+      if (thresholds && thresholds.length) {
+        this.createIntersectionObserverHandle();
+      }
+    },
+
+    clear() {
+      this.clearIntersectionObserverHandle();
+    },
+
+    intersectionObserverContext: null,
+
+    createIntersectionObserverHandle() {
+      if (this.intersectionObserverContext || !this.data.independentID) {
+        return;
+      }
+      this.intersectionObserverContext = this.createIntersectionObserver({
+        thresholds: this.properties.thresholds,
+      }).relativeToViewport();
+
+      this.intersectionObserverContext.observe(
+        `#${this.data.independentID}`,
+        (res) => {
+          this.intersectionObserverCB(res);
+        },
+      );
+    },
+
+    intersectionObserverCB() {
+      this.triggerEvent('ob', {
+        goods: this.data.goods,
+        context: this.intersectionObserverContext,
+      });
+    },
+
+    clearIntersectionObserverHandle() {
+      if (this.intersectionObserverContext) {
+        try {
+          this.intersectionObserverContext.disconnect();
+        } catch (e) { }
+        this.intersectionObserverContext = null;
+      }
+    },
+  },
+});

+ 8 - 0
tcb-shop-demo/components/goods-card/index.json

@@ -0,0 +1,8 @@
+{
+    "component": true,
+    "usingComponents": {
+        "price": "/components/price/index",
+        "t-icon": "tdesign-miniprogram/icon/icon",
+        "t-image": "/components/webp-image/index"
+    }
+}

+ 16 - 0
tcb-shop-demo/components/goods-card/index.wxml

@@ -0,0 +1,16 @@
+<view id="{{independentID}}" class="goods-card" bind:tap="clickHandle" data-goods="{{ goods }}">
+  <view class="goods-card__main">
+    <view class="goods-card__thumb" bind:tap="clickThumbHandle">
+      <t-image wx:if="{{ !!goods.cover_image }}" t-class="goods-card__img" src="{{ goods.cover_image }}" mode="aspectFill" lazy-load />
+    </view>
+    <view class="goods-card__body">
+      <view class="goods-card__upper">
+        <view wx:if="{{ goods.name }}" class="goods-card__title">{{ goods.name }}</view>
+      </view>
+      <view class="goods-card__down">
+        <price wx:if="{{ goods.price }}" wr-class="spec-for-price" symbol-class="spec-for-symbol" symbol="{{currency}}" price="{{goods.price * 100}}" />
+        <t-icon class="goods-card__add-cart" prefix="wr" name="cartAdd" id="{{independentID}}-cart" data-id="{{independentID}}" catchtap="addCartHandle" size="48rpx" color="#FA550F" />
+      </view>
+    </view>
+  </view>
+</view>

+ 136 - 0
tcb-shop-demo/components/goods-card/index.wxss

@@ -0,0 +1,136 @@
+.goods-card {
+  box-sizing: border-box;
+  font-size: 24rpx;
+  border-radius: 0 0 16rpx 16rpx;
+  border-bottom: none;
+}
+
+.goods-card__main {
+  position: relative;
+  display: flex;
+  line-height: 1;
+  padding: 0;
+  background: transparent;
+  width: 342rpx;
+  border-radius: 0 0 16rpx 16rpx;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 16rpx;
+  flex-direction: column;
+}
+
+.goods-card__thumb {
+  flex-shrink: 0;
+  position: relative;
+  width: 340rpx;
+  height: 340rpx;
+}
+
+.goods-card__thumb:empty {
+  display: none;
+  margin: 0;
+}
+
+.goods-card__img {
+  display: block;
+  width: 100%;
+  height: 100%;
+  border-radius: 16rpx 16rpx 0 0;
+  overflow: hidden;
+}
+
+.goods-card__body {
+  display: flex;
+  /* flex-grow: 1; */
+  box-sizing: border-box;
+  width: 100%;
+  flex: 1 1 auto;
+  background: #fff;
+  border-radius: 0 0 16rpx 16rpx;
+  padding: 16rpx 24rpx 18rpx;
+  flex-direction: column;
+}
+
+.goods-card__upper {
+  display: flex;
+  flex-direction: column;
+  overflow: hidden;
+  flex: 1 1 auto;
+}
+
+.goods-card__title {
+  flex-shrink: 0;
+  font-size: 28rpx;
+  color: #333;
+  font-weight: 400;
+  display: -webkit-box;
+  height: 72rpx;
+  -webkit-box-orient: vertical;
+  -webkit-line-clamp: 2;
+  overflow: hidden;
+  word-break: break-word;
+  line-height: 36rpx;
+}
+
+.goods-card__tags {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  margin: 8rpx 0 0 0;
+}
+
+.goods-card__tag {
+  color: #fa4126;
+  background: transparent;
+  font-size: 20rpx;
+  border: 1rpx solid #fa4126;
+  padding: 0 8rpx;
+  border-radius: 16rpx;
+  line-height: 30rpx;
+  margin: 0 8rpx 8rpx 0;
+  display: block;
+  overflow: hidden;
+  white-space: nowrap;
+  word-break: keep-all;
+  text-overflow: ellipsis;
+}
+
+.goods-card__down {
+  display: flex;
+  position: relative;
+  flex-direction: row;
+  justify-content: flex-start;
+  align-items: baseline;
+  line-height: 32rpx;
+  margin: 8rpx 0 0 0;
+}
+
+.goods-card__origin-price {
+  white-space: nowrap;
+  font-weight: 700;
+  order: 2;
+  color: #bbbbbb;
+  font-size: 24rpx;
+  margin: 0 0 0 8rpx;
+}
+
+.goods-card__add-cart {
+  order: 3;
+  margin: auto 0 0 auto;
+  position: absolute;
+  bottom: 0;
+  right: 0;
+}
+
+.spec-for-price {
+  font-size: 36rpx;
+  white-space: nowrap;
+  font-weight: 700;
+  order: 1;
+  color: #fa4126;
+  margin: 0;
+}
+
+.spec-for-symbol {
+  font-size: 24rpx;
+}

+ 62 - 0
tcb-shop-demo/components/goods-list/index.js

@@ -0,0 +1,62 @@
+Component({
+  externalClasses: ['wr-class'],
+
+  properties: {
+    goodsList: {
+      type: Array,
+      value: [],
+    },
+    id: {
+      type: String,
+      value: '',
+      observer: (id) => {
+        this.genIndependentID(id);
+      },
+    },
+    thresholds: {
+      type: Array,
+      value: [],
+    },
+  },
+
+  data: {
+    independentID: '',
+  },
+
+  lifetimes: {
+    ready() {
+      this.init();
+    },
+  },
+
+  methods: {
+    onClickGoods(e) {
+      const { index } = e.currentTarget.dataset;
+      this.triggerEvent('click', { ...e.detail, index });
+    },
+
+    onAddCart(e) {
+      const { index } = e.currentTarget.dataset;
+      this.triggerEvent('addcart', { ...e.detail, index });
+    },
+
+    onClickGoodsThumb(e) {
+      const { index } = e.currentTarget.dataset;
+      this.triggerEvent('thumb', { ...e.detail, index });
+    },
+
+    init() {
+      this.genIndependentID(this.id || '');
+    },
+
+    genIndependentID(id) {
+      if (id) {
+        this.setData({ independentID: id });
+      } else {
+        this.setData({
+          independentID: `goods-list-${~~(Math.random() * 10 ** 8)}`,
+        });
+      }
+    },
+  },
+});

+ 6 - 0
tcb-shop-demo/components/goods-list/index.json

@@ -0,0 +1,6 @@
+{
+    "component": true,
+    "usingComponents": {
+        "goods-card": "/components/goods-card/index"
+    }
+}

+ 16 - 0
tcb-shop-demo/components/goods-list/index.wxml

@@ -0,0 +1,16 @@
+<view class="goods-list-wrap wr-class" id="{{independentID}}">
+	<block wx:for="{{goodsList}}" wx:for-item="item" wx:key="index">
+		<goods-card
+		  id="{{independentID}}-gd-{{index}}"
+		  data="{{item}}"
+		  currency="{{item.currency || '¥'}}"
+		  thresholds="{{thresholds}}"
+		  class="goods-card-inside"
+		  data-index="{{index}}"
+		  bind:thumb="onClickGoodsThumb"
+		  bind:click="onClickGoods"
+		  bind:add-cart="onAddCart"
+		/>
+	</block>
+</view>
+

+ 7 - 0
tcb-shop-demo/components/goods-list/index.wxss

@@ -0,0 +1,7 @@
+.goods-list-wrap {
+  display: flex;
+  flex-flow: row wrap;
+  justify-content: space-between;
+  padding: 0;
+  background: #fff;
+}

+ 54 - 0
tcb-shop-demo/components/load-more/index.js

@@ -0,0 +1,54 @@
+Component({
+  externalClasses: ['wr-class', 'wr-class--no-more'],
+
+  options: { multipleSlots: true },
+
+  properties: {
+    status: {
+      type: Number,
+      value: 0,
+    },
+    loadingText: {
+      type: String,
+      value: '加载中...',
+    },
+    noMoreText: {
+      type: String,
+      value: '没有更多了',
+    },
+    failedText: {
+      type: String,
+      value: '加载失败,点击重试',
+    },
+    color: {
+      type: String,
+      value: '#BBBBBB',
+    },
+    failedColor: {
+      type: String,
+      value: '#FA550F',
+    },
+    size: {
+      type: null,
+      value: '40rpx',
+    },
+    loadingBackgroundColor: {
+      type: String,
+      value: '#F5F5F5',
+    },
+    listIsEmpty: {
+      type: Boolean,
+      value: false,
+    },
+  },
+
+  methods: {
+    /** 点击处理 */
+    tapHandle() {
+      // 失败重试
+      if (this.data.status === 3) {
+        this.triggerEvent('retry');
+      }
+    },
+  },
+});

+ 7 - 0
tcb-shop-demo/components/load-more/index.json

@@ -0,0 +1,7 @@
+{
+    "component": true,
+    "usingComponents": {
+        "t-loading": "tdesign-miniprogram/loading/loading",
+        "t-divider": "tdesign-miniprogram/divider/divider"
+    }
+}

+ 31 - 0
tcb-shop-demo/components/load-more/index.wxml

@@ -0,0 +1,31 @@
+<view
+  class="load-more wr-class"
+  style="{{listIsEmpty && (status === 0 || status === 2) ? 'display: none' : '' }}"
+  bindtap="tapHandle"
+>
+  <!-- 加载中 -->
+
+  <t-loading
+    t-class="t-class-loading"
+    t-class-text="t-class-loading-text"
+    t-class-indicator="t-class-indicator"
+    loading="{{status === 1}}"
+    text="加载中..."
+    theme="circular"
+    size="40rpx"
+  />
+
+  <!-- 已全部加载 -->
+  <t-divider wx:if="{{status === 2}}" t-class="t-class-divider" t-class-content="t-class-divider-content">
+    <text slot="content">{{noMoreText}}</text>
+  </t-divider>
+
+  <!-- 加载失败 -->
+  <view class="load-more__error" wx:if="{{status===3}}">
+    加载失败
+    <text class="load-more__refresh-btn" bind:tap="tapHandle">刷新</text>
+  </view>
+</view>
+
+<!-- 支持通过slot传入页面/列表的空态,load-more来控制空态的显示状态 -->
+<slot wx:if="{{listIsEmpty && (status === 0 || status === 2)}}" name="empty" />

+ 35 - 0
tcb-shop-demo/components/load-more/index.wxss

@@ -0,0 +1,35 @@
+.load-more {
+  font-size: 24rpx;
+  height: 100rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.load-more .t-class-loading {
+  display: flex;
+  justify-content: center;
+
+  --td-loading-color: #fa4126;
+}
+
+.load-more .t-class-loading-text {
+  color: #bbbbbb;
+}
+
+.t-class-divider-content {
+  margin: 0 10rpx;
+  color: #bbbbbb;
+}
+.load-more .t-class-indicator {
+  color: #b9b9b9 !important;
+}
+
+.load-more__error {
+  margin: auto;
+}
+
+.load-more__refresh-btn {
+  margin-left: 16rpx;
+  color: #fa4126;
+}

+ 23 - 0
tcb-shop-demo/components/loading-content/index.js

@@ -0,0 +1,23 @@
+Component({
+  externalClasses: ['wr-class'],
+  properties: {
+    position: {
+      type: String,
+      value: 'static',
+    },
+    noMask: Boolean,
+    type: {
+      type: String,
+      value: 'circular',
+    },
+    vertical: Boolean,
+    size: {
+      type: String,
+      value: '50rpx',
+    },
+    backgroundColor: {
+      type: String,
+      value: 'rgba(0, 0, 0, .6)',
+    },
+  },
+});

+ 6 - 0
tcb-shop-demo/components/loading-content/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "t-loading": "tdesign-miniprogram/loading/loading"
+  }
+}

+ 11 - 0
tcb-shop-demo/components/loading-content/index.wxml

@@ -0,0 +1,11 @@
+<view class="t-class loading-content {{position}}" style="{{(position === 'static' || noMask) ? 'visibility: hidden;' : ''}} background-color: {{backgroundColor}}">
+	<t-loading
+	 t-class="loading"
+	 theme="{{type}}"
+	 layout="{{vertical}}"
+	 size="{{size}}"
+	>
+		<slot/>
+	</t-loading>
+</view>
+

+ 23 - 0
tcb-shop-demo/components/loading-content/index.wxss

@@ -0,0 +1,23 @@
+.loading-content {
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.6);
+  position: relative;
+}
+.loading-content.absolute {
+  position: absolute;
+  z-index: 1;
+  left: 0;
+  top: 0;
+}
+.loading-content.fixed {
+  position: fixed;
+  z-index: 1;
+  left: 0;
+  top: 0;
+}
+.loading-content .loading {
+  width: 100%;
+  height: 100%;
+  visibility: visible;
+}

+ 8 - 0
tcb-shop-demo/components/loading-dialog/index.js

@@ -0,0 +1,8 @@
+Component({
+  properties: {
+    show: {
+      type: Boolean,
+      value: false,
+    },
+  },
+});

+ 8 - 0
tcb-shop-demo/components/loading-dialog/index.json

@@ -0,0 +1,8 @@
+{
+  "component": true,
+  "usingComponents": {
+    "t-loading": "tdesign-miniprogram/loading/loading",
+    "t-dialog": "tdesign-miniprogram/dialog/dialog"
+  },
+  "styleIsolation": "shared"
+}

+ 5 - 0
tcb-shop-demo/components/loading-dialog/index.wxml

@@ -0,0 +1,5 @@
+<view class="loading-dialog">
+  <t-dialog visible="{{show}}">
+    <t-loading slot="content" theme="circular" size="70rpx" class="wrapper" />
+  </t-dialog>
+</view>

+ 11 - 0
tcb-shop-demo/components/loading-dialog/index.wxss

@@ -0,0 +1,11 @@
+.loading-dialog .t-dialog{
+  width: unset;
+}
+
+.loading-dialog .t-dialog__content {
+  padding: 80rpx;
+}
+
+.loading-dialog .t-dialog__footer {
+  display: none;
+}

+ 71 - 0
tcb-shop-demo/components/price/index.js

@@ -0,0 +1,71 @@
+Component({
+  externalClasses: ['wr-class', 'symbol-class', 'decimal-class'],
+  useStore: [],
+  properties: {
+    priceUnit: {
+      type: String,
+      value: 'fen',
+    }, // 价格单位,分 | 元, fen,yuan
+    price: {
+      type: null,
+      value: '',
+      observer(price) {
+        this.format(price);
+      },
+    }, // 价格, 以分为单位
+    type: {
+      type: String,
+      value: '', //
+    }, //  main 粗体, lighter 细体, mini 黑色, del 中划线, delthrough 中划线,包括货币符号
+    symbol: {
+      type: String,
+      value: '¥', // '¥',
+    }, // 货币符号,默认是人民币符号¥
+    fill: Boolean, // 是否自动补齐两位小数
+    decimalSmaller: Boolean, // 小数字号小一点
+    lineThroughWidth: {
+      type: null,
+      value: '0.12em',
+    }, // 划线价线条高度
+  },
+
+  data: {
+    pArr: [],
+  },
+
+  methods: {
+    format(price) {
+      price = parseFloat(`${price}`);
+      const pArr = [];
+      if (!isNaN(price)) {
+        const isMinus = price < 0;
+        if (isMinus) {
+          price = -price;
+        }
+        if (this.properties.priceUnit === 'yuan') {
+          const priceSplit = price.toString().split('.');
+          pArr[0] = priceSplit[0];
+          pArr[1] = !priceSplit[1]
+            ? '00'
+            : priceSplit[1].length === 1
+            ? `${priceSplit[1]}0`
+            : priceSplit[1];
+        } else {
+          price = Math.round(price * 10 ** 8) / 10 ** 8; // 恢复精度丢失
+          price = Math.ceil(price); // 向上取整
+          pArr[0] = price >= 100 ? `${price}`.slice(0, -2) : '0';
+          pArr[1] = `${price + 100}`.slice(-2);
+        }
+        if (!this.properties.fill) {
+          // 如果 fill 为 false, 不显示小数末尾的0
+          if (pArr[1] === '00') pArr[1] = '';
+          else if (pArr[1][1] === '0') pArr[1] = pArr[1][0];
+        }
+        if (isMinus) {
+          pArr[0] = `-${pArr[0]}`;
+        }
+      }
+      this.setData({ pArr });
+    },
+  },
+});

+ 4 - 0
tcb-shop-demo/components/price/index.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 21 - 0
tcb-shop-demo/components/price/index.wxml

@@ -0,0 +1,21 @@
+<wxs module="utils">
+	var REGEXP = getRegExp('^\d+(\.\d+)?$');
+	function addUnit(value) {
+	if (value == null) {
+	return '';
+	}
+	return REGEXP.test('' + value) ? value + 'rpx' : value;
+	}
+	module.exports = {
+	addUnit: addUnit
+	};
+</wxs>
+<view class="price {{type}} wr-class">
+	<view wx:if="{{type === 'delthrough'}}" class="line" style="height:{{utils.addUnit(lineThroughWidth)}};" />
+	<view class="symbol symbol-class">{{symbol}}</view>
+	<view class="pprice">
+		<view class="integer inline">{{pArr[0]}}</view>
+		<view wx:if="{{pArr[1]}}" class="decimal inline {{decimalSmaller ? 'smaller' : ''}} decimal-class">.{{pArr[1]}}</view>
+	</view>
+</view>
+

+ 66 - 0
tcb-shop-demo/components/price/index.wxss

@@ -0,0 +1,66 @@
+:host {
+  display: inline-block;
+  display: inline-block;
+  font-weight: inherit;
+}
+.inline {
+  display: inline;
+  white-space: nowrap;
+}
+.price {
+  display: inline;
+  color: inherit;
+  font-size: inherit;
+  text-decoration: inherit;
+}
+
+.lighter {
+  font-weight: 400;
+  font-size: 32rpx;
+}
+.mini {
+  font-size: 24rpx;
+  color: #5d5d5d;
+  font-weight: 400;
+}
+.del .pprice {
+  font-size: 32rpx;
+  color: #9b9b9b;
+  text-decoration: line-through;
+  font-weight: 400;
+}
+.delthrough {
+  position: relative;
+}
+.delthrough .line {
+  position: absolute;
+  top: 50%;
+  left: 0;
+  right: 0;
+  transform: translateY(-50%);
+  margin: 0;
+  background-color: currentColor;
+}
+
+.symbol {
+  display: inline;
+  color: inherit;
+  font-size: inherit;
+  font-size: 0.8em;
+}
+.pprice {
+  display: inline;
+  margin: 0 0 0 4rpx;
+}
+.integer {
+  color: inherit;
+  font-size: inherit;
+}
+.decimal {
+  color: inherit;
+  font-size: inherit;
+}
+.decimal.smaller {
+  font-size: 0.8em;
+  vertical-align: baseline;
+}

+ 79 - 0
tcb-shop-demo/components/swipeout/index.js

@@ -0,0 +1,79 @@
+let ARRAY = [];
+Component({
+  externalClasses: ['wr-class'],
+
+  options: {
+    multipleSlots: true,
+  },
+  properties: {
+    disabled: Boolean,
+    leftWidth: {
+      type: Number,
+      value: 0,
+    },
+    rightWidth: {
+      type: Number,
+      value: 0,
+    },
+    asyncClose: Boolean,
+  },
+  attached() {
+    ARRAY.push(this);
+  },
+
+  detached() {
+    ARRAY = ARRAY.filter((item) => item !== this);
+  },
+
+  /**
+   * Component initial data
+   */
+  data: {
+    wrapperStyle: '',
+    asyncClose: false,
+    closed: true,
+  },
+
+  /**
+   * Component methods
+   */
+  methods: {
+    open(position) {
+      this.setData({ closed: false });
+      this.triggerEvent('close', {
+        position,
+        instance: this,
+      });
+    },
+
+    close() {
+      this.setData({ closed: true });
+    },
+
+    closeOther() {
+      ARRAY.filter((item) => item !== this).forEach((item) => item.close());
+    },
+
+    noop() {
+      return;
+    },
+
+    onClick(event) {
+      const { key: position = 'outside' } = event.currentTarget.dataset;
+      this.triggerEvent('click', position);
+
+      if (this.data.closed) {
+        return;
+      }
+
+      if (this.data.asyncClose) {
+        this.triggerEvent('close', {
+          position,
+          instance: this,
+        });
+      } else {
+        this.close();
+      }
+    },
+  },
+});

+ 4 - 0
tcb-shop-demo/components/swipeout/index.json

@@ -0,0 +1,4 @@
+{
+  "component": true,
+  "usingComponents": {}
+}

+ 174 - 0
tcb-shop-demo/components/swipeout/index.wxml

@@ -0,0 +1,174 @@
+<wxs module="swipe">
+  var THRESHOLD = 0.3;
+  var MIN_DISTANCE = 10;
+  var owner;
+  var state;
+
+  var getState = function(ownerInstance) {
+    owner = ownerInstance;
+    state = owner.getState();
+    state.leftWidth = state.leftWidth || 0;
+    state.rightWidth = state.rightWidth || 0;
+    state.offset = state.offset || 0;
+    state.startOffset = state.startOffset || 0;
+  };
+
+  var initRightWidth = function(newVal, oldVal, ownerInstance) {
+    getState(ownerInstance);
+    state.rightWidth = newVal;
+    if (state.offset < 0) {
+      swipeMove(-state.rightWidth);
+    }
+  };
+
+  var initLeftWidth = function(newVal, oldVal, ownerInstance) {
+    getState(ownerInstance);
+    state.leftWidth = newVal;
+    if (state.offset > 0) {
+      swipeMove(state.leftWidth);
+    }
+  }
+
+  var resetTouchStatus = function() {
+    state.direction = '';
+    state.deltaX = 0;
+    state.deltaY = 0;
+    state.offsetX = 0;
+    state.offsetY = 0;
+  };
+
+  var touchMove = function(event) {
+    var touchPoint = event.touches[0];
+    state.deltaX = touchPoint.clientX - state.startX;
+    state.deltaY = touchPoint.clientY - state.startY;
+    state.offsetX = Math.abs(state.deltaX);
+    state.offsetY = Math.abs(state.deltaY);
+    state.direction = state.direction || getDirection(state.offsetX, state.offsetY);
+  };
+
+  var getDirection = function(x, y) {
+    if (x > y && x > MIN_DISTANCE) {
+      return 'horizontal';
+    }
+    if (y > x && y > MIN_DISTANCE) {
+      return 'vertical';
+    }
+    return '';
+  };
+
+  var range = function(num, min, max) {
+    return Math.min(Math.max(num, min), max);
+  };
+
+  var swipeMove = function(_offset = 0) {
+    state.offset = range(
+      _offset,
+      -state.rightWidth,
+      +state.leftWidth,
+    );
+
+    var transform = 'translate3d(' + state.offset + 'px, 0, 0)';
+    var transition = state.dragging
+      ? 'none'
+      : 'transform .6s cubic-bezier(0.18, 0.89, 0.32, 1)';
+    owner.selectComponent('#wrapper').setStyle({
+      '-webkit-transform': transform,
+      '-webkit-transition': transition,
+      'transform': transform,
+      'transition': transition
+    });
+  };
+
+  var close = function() {
+    swipeMove(0);
+  };
+
+  var onCloseChange = function(newVal, oldVal, ownerInstance) {
+    getState(ownerInstance);
+    if (newVal === oldVal) return;
+    if (newVal) {
+      close();
+    }
+  };
+
+  var touchStart = function(event) {
+    resetTouchStatus();
+    state.startOffset = state.offset;
+    var touchPoint = event.touches[0];
+    state.startX = touchPoint.clientX;
+    state.startY = touchPoint.clientY;
+    owner.callMethod('closeOther');
+  };
+
+  var startDrag = function(event, ownerInstance) {
+    getState(ownerInstance);
+    touchStart(event);
+  };
+
+  var onDrag = function(event, ownerInstance) {
+    getState(ownerInstance);
+    touchMove(event);
+    if (state.direction !== 'horizontal') {
+      return;
+    }
+    state.dragging = true;
+    swipeMove(state.startOffset + state.deltaX);
+  };
+
+  var open = function(position) {
+    var _offset = position === 'left' ? +state.leftWidth : -state.rightWidth;
+    owner.callMethod('open', { position: position });
+    swipeMove(_offset);
+  };
+
+  var endDrag = function(event, ownerInstance) {
+    getState(ownerInstance);
+    state.dragging = false;
+    // 左/右侧有可滑动区域,且当前不是已open状态,且滑动幅度超过阈值时open左/右侧(滚动到该侧的最边上)
+    if (+state.rightWidth > 0 && -state.startOffset < +state.rightWidth && -state.offset > +state.rightWidth * THRESHOLD) {
+      open('right');
+    } else if (+state.leftWidth > 0 && state.startOffset < +state.leftWidth && state.offset > +state.leftWidth * THRESHOLD) {
+      open('left');
+    } else {
+      // 仅在有发生侧滑的情况下自动关闭(由js控制是否异步关闭)
+      if (state.startOffset !== state.offset) {
+        close();
+      }
+    }
+  };
+
+  module.exports = {
+    initLeftWidth: initLeftWidth,
+    initRightWidth: initRightWidth,
+    startDrag: startDrag,
+    onDrag: onDrag,
+    endDrag: endDrag,
+    onCloseChange: onCloseChange
+  };
+</wxs>
+
+<view
+  class="wr-class wr-swipeout"
+  data-key="cell"
+  capture-bind:tap="onClick"
+  bindtouchstart="{{disabled || swipe.startDrag}}"
+  capture-bind:touchmove="{{disabled || swipe.onDrag}}"
+  bindtouchend="{{disabled || swipe.endDrag}}"
+  bindtouchcancel="{{disabled || swipe.endDrag}}"
+  closed="{{closed}}"
+  change:closed="{{swipe.onCloseChange}}"
+  leftWidth="{{leftWidth}}"
+  rightWidth="{{rightWidth}}"
+  change:leftWidth="{{swipe.initLeftWidth}}"
+  change:rightWidth="{{swipe.initRightWidth}}"
+>
+  <view id="wrapper">
+    <view wx:if="{{ leftWidth }}" class="wr-swipeout__left" data-key="left" catch:tap="onClick">
+      <slot name="left" />
+    </view>
+    <slot />
+    <view wx:if="{{ rightWidth }}" class="wr-swipeout__right" data-key="right" catch:tap="onClick">
+      <slot name="right" />
+    </view>
+  </view>
+</view>

+ 18 - 0
tcb-shop-demo/components/swipeout/index.wxss

@@ -0,0 +1,18 @@
+.wr-swipeout {
+  position: relative;
+  overflow: hidden;
+}
+.wr-swipeout__left,
+.wr-swipeout__right {
+  position: absolute;
+  top: 0;
+  height: 100%;
+}
+.wr-swipeout__left {
+  left: 0;
+  transform: translate3d(-100%, 0, 0);
+}
+.wr-swipeout__right {
+  right: 0;
+  transform: translate3d(100%, 0, 0);
+}

+ 90 - 0
tcb-shop-demo/components/webp-image/index.js

@@ -0,0 +1,90 @@
+/*
+ * @Author: rileycai
+ * @Date: 2022-03-14 14:21:26
+ * @LastEditTime: 2022-03-14 15:23:04
+ * @LastEditors: rileycai
+ * @Description: webp-image组件对t-image包裹了一层,主要实现图片裁剪、webp压缩功能
+ * @FilePath: /tdesign-miniprogram-starter/components/webp-image/index.js
+ */
+// Replace deprecated wx.getSystemInfoSync()
+const systemInfo = {
+  ...wx.getWindowInfo(),
+  ...wx.getDeviceInfo()
+};
+Component({
+  externalClasses: ['t-class', 't-class-load'],
+  properties: {
+    loadFailed: {
+      type: String,
+      value: 'default',
+    },
+    loading: {
+      type: String,
+      value: 'default',
+    },
+    src: {
+      type: String,
+      value: '',
+    },
+    mode: {
+      type: String,
+      value: 'aspectFill',
+    },
+    webp: {
+      type: Boolean,
+      value: true,
+    },
+    lazyLoad: {
+      type: Boolean,
+      value: false,
+    },
+    showMenuByLongpress: {
+      type: Boolean,
+      value: false,
+    },
+  },
+  data: {
+    thumbHeight: 375,
+    thumbWidth: 375,
+    systemInfo,
+  },
+  lifetimes: {
+    ready() {
+      const { mode } = this.properties;
+      // 获取容器的真实宽高,设置图片的裁剪宽度
+      this.getRect('.J-image').then((res) => {
+        if (res) {
+          const { width, height } = res;
+          this.setData(
+            mode === 'heightFix'
+              ? {
+                  thumbHeight: this.px2rpx(height) || 375,
+                }
+              : {
+                  thumbWidth: this.px2rpx(width) || 375,
+                },
+          );
+        }
+      });
+    },
+  },
+  methods: {
+    px2rpx(px) {
+      return (750 / (systemInfo.screenWidth || 375)) * px;
+    },
+    getRect(selector) {
+      return new Promise((resolve) => {
+        if (!this.selectorQuery) {
+          this.selectorQuery = this.createSelectorQuery();
+        }
+        this.selectorQuery.select(selector).boundingClientRect(resolve).exec();
+      });
+    },
+    onLoad(e) {
+      this.triggerEvent('load', e.detail);
+    },
+    onError(e) {
+      this.triggerEvent('error', e.detail);
+    },
+  },
+});

+ 6 - 0
tcb-shop-demo/components/webp-image/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "t-image": "tdesign-miniprogram/image/image"
+  }
+}

+ 14 - 0
tcb-shop-demo/components/webp-image/index.wxml

@@ -0,0 +1,14 @@
+<wxs src="./utils.wxs" module="Utils" />
+<t-image
+  t-class="J-image"
+  src="{{Utils.getSrc({src, thumbWidth: thumbWidth || 0, thumbHeight: thumbHeight || 0, systemInfo, webp, mode})}}"
+  t-class="t-class"
+  t-class-load="t-class-load"
+  mode="{{ mode }}"
+  lazy="{{ lazyLoad }}"
+  show-menu-by-longpress="{{showMenuByLongpress}}"
+  error="{{loadFailed}}"
+  loading="{{loading}}"
+  binderror="onError"
+  bindload="onLoad"
+/>

+ 0 - 0
tcb-shop-demo/components/webp-image/index.wxss


+ 140 - 0
tcb-shop-demo/components/webp-image/utils.wxs

@@ -0,0 +1,140 @@
+var isString = function (value) {
+  return typeof value === 'string';
+};
+
+var isNumber = function (value) {
+  return typeof value === 'number';
+};
+
+var getFileExt = function (src) {
+  var fileUrl = src.split('?')[0];
+  var splitUlr = fileUrl.split('/');
+  var filepath = splitUlr[splitUlr.length - 1];
+  return filepath.split('.')[1] || 'jpg';
+};
+
+function isUrl(url) {
+  // NOCC:ToolNameCheck(非敏感词)
+  var urlReg = getRegExp(
+    '/[(http(s)?)://(www.)?a-zA-Z0-9@:%._+~#=]{2,256}.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/',
+    'ig',
+  );
+
+  return urlReg.test(url);
+}
+
+function rpx2px(rpx, screenWidth) {
+  // px / systemWidth = rpx / 750
+  var result = (rpx * (screenWidth || 375)) / 750;
+
+  return Math.round(result);
+}
+
+function imageMogr(url, options) {
+  if (!isString(url) || !url) return '';
+
+  if (
+    url.indexOf('qlogo.cn') !== -1 ||
+    url.indexOf('wxfile://') === 0 ||
+    url.indexOf('http://tmp/wx') === 0 ||
+    url.indexOf('imageMogr2') !== -1
+  ) {
+    //qlogo.cn域名或者本地图片不做转换
+    return url;
+  } //强制转https
+
+  if (url.indexOf('http://') === 0) {
+    url = url.replace('http://', 'https://');
+  } else if (url.indexOf('//') === 0) {
+    url = 'https:' + url;
+  }
+
+  if (!options) return url;
+
+  var width = Math.ceil(options.width),
+    height = Math.ceil(options.height),
+    format = options.format,
+    _optionsQuality = options.quality,
+    quality = _optionsQuality === undefined ? 70 : _optionsQuality,
+    _optionsStrip = options.strip,
+    strip = _optionsStrip === undefined ? true : _optionsStrip,
+    crop = options.crop;
+  var isValidWidth = isNumber(width) && width > 0;
+  var isValidHeight = isNumber(height) && height > 0;
+  var imageMogrStr = '';
+  var size = '';
+
+  if (isValidWidth && isValidHeight) {
+    size = ''.concat(width, 'x').concat(height);
+  } else if (isValidWidth) {
+    size = ''.concat(width, 'x');
+  } else if (isValidHeight) {
+    size = 'x'.concat(height);
+  }
+
+  if (size) {
+    //缩放或者裁剪
+    imageMogrStr += '/'.concat(crop ? 'crop' : 'thumbnail', '/').concat(size);
+
+    if (crop) {
+      //裁剪目前需求只有以图片中心为基准
+      imageMogrStr += '/gravity/center';
+    }
+  }
+
+  if (isNumber(quality)) {
+    //质量变换
+    imageMogrStr += '/quality/'.concat(quality);
+  }
+
+  if (strip) {
+    //去除元信息
+    imageMogrStr += '/strip';
+  }
+
+  var ext = getFileExt(url);
+
+  // gif 图片不做格式转换,否则会损坏动图
+  if (ext === 'gif') {
+    imageMogrStr += '/cgif/1';
+  } else if (format) {
+    //格式转换
+    imageMogrStr += '/format/'.concat(format);
+  }
+
+  if (format === 'jpg' || (!format && (ext === 'jpg' || ext === 'jpeg'))) {
+    //渐进式 jpg 加载
+    imageMogrStr += '/interlace/1';
+  }
+  if (!imageMogrStr) return url;
+  return ''
+    .concat(url)
+    .concat(url.indexOf('?') !== -1 ? '&' : '?', 'imageMogr2')
+    .concat(imageMogrStr);
+}
+function getSrc(options) {
+  if (!options.src) return '';
+
+  if (options.thumbWidth || options.thumbHeight) {
+    return imageMogr(options.src, {
+      width:
+        options.mode !== 'heightFix'
+          ? rpx2px(options.thumbWidth, options.systemInfo.screenWidth) *
+            options.systemInfo.pixelRatio
+          : null,
+      height:
+        options.mode !== 'widthFix'
+          ? rpx2px(options.thumbHeight, options.systemInfo.screenWidth) *
+            options.systemInfo.pixelRatio
+          : null,
+      format: options.webp ? 'webp' : null,
+    });
+  }
+
+  return '';
+}
+
+module.exports = {
+  imageMogr: imageMogr,
+  getSrc: getSrc,
+};

+ 91 - 0
tcb-shop-demo/config/eslintCheck.js

@@ -0,0 +1,91 @@
+/* eslint-disable prefer-template */
+/**
+ * 工程代码pre-commit 检查工具
+ * @date 2019.9.4
+ * @author 310227663@qq.com
+ */
+const { exec } = require('child_process');
+const chalk = require('chalk');
+const { CLIEngine } = require('eslint');
+const cli = new CLIEngine({});
+const { log } = console;
+
+function getErrorLevel(number) {
+  switch (number) {
+    case 2:
+      return 'error';
+    case 1:
+      return 'warn';
+    default:
+  }
+  return 'undefined';
+}
+let pass = 0;
+exec(
+  'git diff --cached --name-only --diff-filter=ACM | grep -Ei "\\.ts$|\\.js$"',
+  (error, stdout) => {
+    if (stdout.length) {
+      const array = stdout.split('\n');
+      array.pop();
+      const { results } = cli.executeOnFiles(array);
+      let errorCount = 0;
+      let warningCount = 0;
+      results.forEach((result) => {
+        errorCount += result.errorCount;
+        warningCount += result.warningCount;
+        if (result.messages.length > 0) {
+          log('\n');
+          log(result.filePath);
+          result.messages.forEach((obj) => {
+            const level = getErrorLevel(obj.severity);
+            if (level === 'warn')
+              log(
+                ' ' +
+                obj.line +
+                ':' +
+                obj.column +
+                '\t ' +
+                chalk.yellow(level) +
+                ' \0  ' +
+                obj.message +
+                '\t\t' +
+                chalk.grey(obj.ruleId) +
+                '',
+              );
+            if (level === 'error')
+              log(
+                ' ' +
+                obj.line +
+                ':' +
+                obj.column +
+                '\t ' +
+                chalk.red.bold(level) +
+                ' \0  ' +
+                obj.message +
+                '\t\t ' +
+                chalk.grey(obj.ruleId) +
+                '',
+              );
+            if (level === 'error') pass = 1;
+          });
+        }
+      });
+      if (warningCount > 0 || errorCount > 0) {
+        log(
+          '\n' +
+          chalk.bgRed.bold(errorCount + warningCount + ' problems') +
+          ' (' +
+          chalk.red.bold(errorCount) +
+          ' errors, ' +
+          chalk.yellow(warningCount) +
+          ' warnings) \0',
+        );
+      }
+      !pass && log(chalk.green.bold('~~ Done: 代码检验通过,提交成功 ~~'));
+      process.exit(pass);
+    }
+    if (error !== null) {
+      log(`exec error: ${error}`);
+    }
+  },
+);

+ 12 - 0
tcb-shop-demo/config/index.js

@@ -0,0 +1,12 @@
+/** TDesign 使用 */
+export const config = {
+  /** 是否使用mock代替api返回 */
+  useMock: true,
+};
+
+/** 云开发使用 */
+export const cloudbaseTemplateConfig = {
+  useMock: false,
+};
+
+export const cdnBase = 'https://we-retail-static-1300977798.cos.ap-guangzhou.myqcloud.com/retail-mp';

+ 12 - 0
tcb-shop-demo/config/model.js

@@ -0,0 +1,12 @@
+export const DATA_MODEL_KEY = {
+  ATTR_VALUE: 'shop_attr_value',
+  CATE: 'shop_spu_cate',
+  CART_ITEM: 'shop_cart_item',
+  COMMENT: 'shop_comment',
+  DELIVERY_INFO: 'shop_delivery_info',
+  HOME_SWIPER: 'shop_home_swiper_image',
+  ORDER: 'shop_order',
+  ORDER_ITEM: 'shop_order_item',
+  SKU: 'shop_sku',
+  SPU: 'shop_spu',
+};

+ 22 - 0
tcb-shop-demo/custom-tab-bar/data.js

@@ -0,0 +1,22 @@
+export default [
+  {
+    icon: 'home',
+    text: '首页',
+    url: 'pages/home/home',
+  },
+  {
+    icon: 'sort',
+    text: '分类',
+    url: 'pages/goods/category/index',
+  },
+  {
+    icon: 'cart',
+    text: '购物车',
+    url: 'pages/cart/index',
+  },
+  {
+    icon: 'person',
+    text: '个人中心',
+    url: 'pages/usercenter/index',
+  },
+];

+ 29 - 0
tcb-shop-demo/custom-tab-bar/index.js

@@ -0,0 +1,29 @@
+import TabMenu from './data';
+Component({
+  data: {
+    active: 0,
+    list: TabMenu,
+  },
+
+  methods: {
+    onChange(event) {
+      this.setData({ active: event.detail.value });
+      wx.switchTab({
+        url: this.data.list[event.detail.value].url.startsWith('/')
+          ? this.data.list[event.detail.value].url
+          : `/${this.data.list[event.detail.value].url}`,
+      });
+    },
+
+    init() {
+      const page = getCurrentPages().pop();
+      const route = page ? page.route.split('?')[0] : '';
+      const active = this.data.list.findIndex(
+        (item) =>
+          (item.url.startsWith('/') ? item.url.substr(1) : item.url) ===
+          `${route}`,
+      );
+      this.setData({ active });
+    },
+  },
+});

+ 8 - 0
tcb-shop-demo/custom-tab-bar/index.json

@@ -0,0 +1,8 @@
+{
+  "component": true,
+  "usingComponents": {
+    "t-tab-bar": "tdesign-miniprogram/tab-bar/tab-bar",
+    "t-tab-bar-item": "tdesign-miniprogram/tab-bar-item/tab-bar-item",
+    "t-icon": "tdesign-miniprogram/icon/icon"
+  }
+}

+ 18 - 0
tcb-shop-demo/custom-tab-bar/index.wxml

@@ -0,0 +1,18 @@
+<t-tab-bar
+ value="{{active}}"
+ bindchange="onChange"
+ split="{{false}}"
+>
+	<t-tab-bar-item
+	 wx:for="{{list}}"
+	 wx:for-item="item"
+	 wx:for-index="index"
+	 wx:key="index"
+	>
+		<view class="custom-tab-bar-wrapper">
+			<t-icon prefix="wr" name="{{item.icon}}" size="48rpx" />
+			<view class="text">{{ item.text }}</view>
+		</view>
+	</t-tab-bar-item>
+</t-tab-bar>
+

+ 9 - 0
tcb-shop-demo/custom-tab-bar/index.wxss

@@ -0,0 +1,9 @@
+.custom-tab-bar-wrapper {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.custom-tab-bar-wrapper .text {
+  font-size: 20rpx;
+}

+ 5 - 0
tcb-shop-demo/jsconfig.json

@@ -0,0 +1,5 @@
+{
+  "compilerOptions": {
+    "baseUrl": "."
+  }
+}

+ 7 - 0
tcb-shop-demo/model/activities.js

@@ -0,0 +1,7 @@
+import { getActivity } from './activity';
+
+export function getActivityList(baseID = 0, length = 10) {
+  return new Array(length).fill(0).map((_, idx) => getActivity(idx + baseID));
+}
+
+export const activityList = getActivityList();

+ 18 - 0
tcb-shop-demo/model/activity.js

@@ -0,0 +1,18 @@
+/**
+ * @param {string|number} key 唯一值
+ */
+export function getActivity(key) {
+  return {
+    promotionId: `${key}`,
+    title: `满减满折回归${key}`,
+    description: null,
+    promotionCode: 'MERCHANT',
+    promotionSubCode: key % 2 === 0 ? 'MYJ' : 'MYG',
+    tag: '满减',
+    timeType: 1,
+    startTime: '1588737710000',
+    endTime: '1601467070000',
+    teasingStartTime: null,
+    activityLadder: [{ label: '满100元减99.9元' }],
+  };
+}

+ 31 - 0
tcb-shop-demo/model/address.js

@@ -0,0 +1,31 @@
+/** 地址 */
+export function genAddress(id) {
+  return {
+    saasId: '88888888',
+    uid: `8888888820550${id}`,
+    authToken: null,
+    id: `${id}`,
+    addressId: `${id}`,
+    phone: '17612345678',
+    name: `测试用户${id}`,
+    countryName: '中国',
+    countryCode: 'chn',
+    provinceName: '甘肃省',
+    provinceCode: '620000',
+    cityName: '甘南藏族自治州',
+    cityCode: '623000',
+    districtName: '碌曲县',
+    districtCode: '623026',
+    detailAddress: `松日鼎盛大厦${id}层${id}号`,
+    isDefault: `${id}` === '0' ? 1 : 0,
+    addressTag: id === 0 ? '' : '公司',
+    latitude: '34.59103',
+    longitude: '102.48699',
+    storeId: null,
+  };
+}
+
+/** 地址列表 */
+export function genAddressList(len = 10) {
+  return new Array(len).fill(0).map((_, idx) => genAddress(idx));
+}

+ 324 - 0
tcb-shop-demo/model/cart.js

@@ -0,0 +1,324 @@
+import { mockIp, mockReqId } from '../utils/mock';
+
+export function genCartGroupData() {
+  const resp = {
+    data: {
+      isNotEmpty: true,
+      storeGoods: [
+        {
+          storeId: '1000',
+          storeName: '云Mall深圳旗舰店',
+          storeStatus: 1,
+          totalDiscountSalePrice: '9990',
+          promotionGoodsList: [
+            {
+              title: '满减满折回归',
+              promotionCode: 'MERCHANT',
+              promotionSubCode: 'MYJ',
+              promotionId: '159174555838121985',
+              tagText: ['满100元减99.9元'],
+              promotionStatus: 3,
+              tag: '满减',
+              description: '满100元减99.9元,已减99.9元',
+              doorSillRemain: null,
+              isNeedAddOnShop: 0,
+              goodsPromotionList: [
+                {
+                  uid: '88888888205468',
+                  saasId: '88888888',
+                  storeId: '1000',
+                  spuId: '12',
+                  skuId: '135691622',
+                  isSelected: 1,
+                  thumb:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/dz-3a.png',
+                  title:
+                    '腾讯极光盒子4智能网络电视机顶盒6K千兆网络机顶盒4K高分辨率',
+                  primaryImage:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/dz-3a.png',
+                  quantity: 1,
+                  stockStatus: true,
+                  stockQuantity: 3,
+                  price: '9900',
+                  originPrice: '16900',
+                  tagPrice: null,
+                  titlePrefixTags: [{ text: '新品' }, { text: '火爆' }],
+                  roomId: null,
+                  specInfo: [
+                    {
+                      specTitle: '颜色',
+                      specValue: '经典白',
+                    },
+                    {
+                      specTitle: '类型',
+                      specValue: '经典套装',
+                    },
+                  ],
+                  joinCartTime: '2020-06-29T07:55:40.000+0000',
+                  available: 1,
+                  putOnSale: 1,
+                  etitle: null,
+                },
+                {
+                  uid: '88888888205468',
+                  saasId: '88888888',
+                  storeId: '1000',
+                  spuId: '18',
+                  skuId: '135681631',
+                  isSelected: 1,
+                  thumb:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-09a.png',
+                  title:
+                    '白色短袖连衣裙荷叶边裙摆宽松韩版休闲纯白清爽优雅连衣裙',
+                  primaryImage:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-09a.png',
+                  quantity: 1,
+                  stockStatus: true,
+                  stockQuantity: 177,
+                  price: '29800',
+                  originPrice: '40000',
+                  tagPrice: null,
+                  titlePrefixTags: null,
+                  roomId: null,
+                  specInfo: [
+                    {
+                      specTitle: '颜色',
+                      specValue: '米色荷叶边',
+                    },
+                    {
+                      specTitle: '尺码',
+                      specValue: 'M',
+                    },
+                  ],
+                  joinCartTime: '2020-06-29T07:55:27.000+0000',
+                  available: 1,
+                  putOnSale: 1,
+                  etitle: null,
+                },
+                {
+                  uid: '88888888205468',
+                  saasId: '88888888',
+                  storeId: '1000',
+                  spuId: '13',
+                  skuId: '135698362',
+                  isSelected: 1,
+                  thumb:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/muy-3a.png',
+                  title:
+                    '带帽午休毯虎年款多功能加厚加大加绒简约多功能午休毯连帽披肩',
+                  primaryImage:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/muy-3a.png',
+                  quantity: 13,
+                  stockStatus: true,
+                  stockQuantity: 9,
+                  price: '29900',
+                  originPrice: '0',
+                  tagPrice: null,
+                  titlePrefixTags: [{ text: '火爆' }],
+                  roomId: null,
+                  specInfo: [
+                    {
+                      specTitle: '颜色',
+                      specValue: '浅灰色',
+                    },
+                    {
+                      specTitle: '尺码',
+                      specValue: 'M',
+                    },
+                  ],
+                  joinCartTime: '2020-06-29T07:54:43.000+0000',
+                  available: 1,
+                  putOnSale: 1,
+                  etitle: null,
+                },
+                {
+                  uid: '88888888205468',
+                  saasId: '88888888',
+                  storeId: '1000',
+                  spuId: '7',
+                  skuId: '135681625',
+                  isSelected: 1,
+                  thumb:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/gh-2b.png',
+                  title:
+                    '不锈钢刀叉勺套装家用西餐餐具ins简约耐用不锈钢金色银色可选',
+                  primaryImage:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/gh-2b.png',
+                  quantity: 1,
+                  stockStatus: true,
+                  stockQuantity: 0,
+                  price: '29900',
+                  originPrice: '29900',
+                  tagPrice: null,
+                  titlePrefixTags: null,
+                  roomId: null,
+                  specInfo: [
+                    {
+                      specTitle: '颜色',
+                      specValue: '奶黄色',
+                    },
+                    {
+                      specTitle: '数量',
+                      specValue: '六件套',
+                    },
+                  ],
+                  joinCartTime: '2020-06-29T07:55:00.000+0000',
+                  available: 1,
+                  putOnSale: 1,
+                  etitle: null,
+                },
+              ],
+              lastJoinTime: '2020-06-29T07:55:40.000+0000',
+            },
+            {
+              title: null,
+              promotionCode: 'EMPTY_PROMOTION',
+              promotionSubCode: null,
+              promotionId: null,
+              tagText: null,
+              promotionStatus: null,
+              tag: null,
+              description: null,
+              doorSillRemain: null,
+              isNeedAddOnShop: 0,
+              goodsPromotionList: [
+                {
+                  uid: '88888888205468',
+                  saasId: '88888888',
+                  storeId: '1000',
+                  spuId: '11',
+                  skuId: '135691629',
+                  isSelected: 0,
+                  thumb:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-17a.png',
+                  title: '运动连帽拉链卫衣休闲开衫长袖多色运动细绒面料运动上衣',
+                  primaryImage:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-17a.png',
+                  quantity: 1,
+                  stockStatus: false,
+                  stockQuantity: 0,
+                  price: '25900',
+                  originPrice: '39900',
+                  tagPrice: null,
+                  tagText: null,
+                  roomId: null,
+                  specInfo: [
+                    {
+                      specTitle: '颜色',
+                      specValue: '军绿色',
+                    },
+                    {
+                      specTitle: '尺码',
+                      specValue: 'S',
+                    },
+                  ],
+                  joinCartTime: '2020-04-24T06:26:48.000+0000',
+                  available: 1,
+                  putOnSale: 1,
+                  etitle: null,
+                },
+                {
+                  uid: '88888888205468',
+                  saasId: '88888888',
+                  storeId: '1000',
+                  spuId: '5',
+                  skuId: '135691635',
+                  isSelected: 0,
+                  thumb:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/dz-2a.png',
+                  title:
+                    '迷你便携高颜值蓝牙无线耳机立体声只能触控式操作简约立体声耳机',
+                  primaryImage:
+                    'https://cdn-we-retail.ym.tencent.com/tsr/goods/dz-2a.png',
+                  quantity: 1,
+                  stockStatus: true,
+                  stockQuantity: 96,
+                  price: '29000',
+                  originPrice: '29900',
+                  tagPrice: null,
+                  tagText: null,
+                  roomId: null,
+                  specInfo: [
+                    {
+                      specTitle: '颜色',
+                      specValue: '黑色',
+                    },
+                    {
+                      specTitle: '类型',
+                      specValue: '简约款',
+                    },
+                  ],
+                  joinCartTime: '2020-06-29T07:55:17.000+0000',
+                  available: 1,
+                  putOnSale: 1,
+                  etitle: null,
+                },
+              ],
+              lastJoinTime: null,
+            },
+          ],
+          lastJoinTime: '2020-06-29T07:55:40.000+0000',
+          postageFreePromotionVo: {
+            title: null,
+            promotionCode: null,
+            promotionSubCode: null,
+            promotionId: null,
+            tagText: null,
+            promotionStatus: null,
+            tag: null,
+            description: null,
+            doorSillRemain: null,
+            isNeedAddOnShop: 0,
+          },
+        },
+      ],
+      invalidGoodItems: [
+        {
+          uid: '88888888205468',
+          saasId: '88888888',
+          storeId: '1000',
+          spuId: '1',
+          skuId: '135691631',
+          isSelected: 1,
+          thumb: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+          title: '纯色纯棉休闲圆领短袖T恤纯白亲肤厚柔软细腻面料纯白短袖套头T恤',
+          primaryImage:
+            'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+          quantity: 8,
+          stockStatus: true,
+          stockQuantity: 177,
+          price: '26900',
+          originPrice: '31900',
+          tagPrice: null,
+          tagText: null,
+          roomId: null,
+          specInfo: [
+            {
+              specTitle: '颜色',
+              specValue: '白色',
+            },
+            {
+              specTitle: '尺码',
+              specValue: 'S',
+            },
+          ],
+          joinCartTime: '2020-04-28T04:03:59.000+0000',
+          available: 1,
+          putOnSale: 1,
+          etitle: null,
+        },
+      ],
+      isAllSelected: false,
+      selectedGoodsCount: 16,
+      totalAmount: '179997',
+      totalDiscountAmount: '110000',
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 269,
+    success: true,
+  };
+  return resp;
+}

+ 206 - 0
tcb-shop-demo/model/category.js

@@ -0,0 +1,206 @@
+export function getCategoryList() {
+  return [
+    {
+      groupId: '24948',
+      name: '女装',
+      thumbnail:
+        'https://cdn-we-retail.ym.tencent.com/miniapp/category/category-default.png',
+      children: [
+        {
+          groupId: '249481',
+          name: '女装',
+          thumbnail:
+            'https://cdn-we-retail.ym.tencent.com/miniapp/category/category-default.png',
+          children: [
+            {
+              groupId: '249480',
+              name: '卫衣',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-1.png',
+            },
+            {
+              groupId: '249480',
+              name: '毛呢外套',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-2.png',
+            },
+            {
+              groupId: '249480',
+              name: '雪纺衫',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-3.png',
+            },
+            {
+              groupId: '249480',
+              name: '羽绒服',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-4.png',
+            },
+            {
+              groupId: '249480',
+              name: '毛衣',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-5.png',
+            },
+            {
+              groupId: '249480',
+              name: '棉衣',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-6.png',
+            },
+            {
+              groupId: '249480',
+              name: '西装',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-7.png',
+            },
+            {
+              groupId: '249480',
+              name: '马甲',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-8.png',
+            },
+            {
+              groupId: '249480',
+              name: '连衣裙',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-9.png',
+            },
+            {
+              groupId: '249480',
+              name: '半身裙',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-10.png',
+            },
+            {
+              groupId: '249480',
+              name: '裤子',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-11.png',
+            },
+          ],
+        },
+      ],
+    },
+    {
+      groupId: '24948',
+      name: '男装',
+      thumbnail:
+        'https://cdn-we-retail.ym.tencent.com/miniapp/category/category-default.png',
+      children: [
+        {
+          groupId: '249481',
+          name: '男装',
+          thumbnail:
+            'https://cdn-we-retail.ym.tencent.com/miniapp/category/category-default.png',
+          children: [
+            {
+              groupId: '249480',
+              name: '卫衣',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-1.png',
+            },
+            {
+              groupId: '249480',
+              name: '裤子',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-11.png',
+            },
+            {
+              groupId: '249480',
+              name: '西装',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-7.png',
+            },
+            {
+              groupId: '249480',
+              name: '羽绒服',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-4.png',
+            },
+            {
+              groupId: '249480',
+              name: '马甲',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-8.png',
+            },
+          ],
+        },
+      ],
+    },
+    {
+      groupId: '24948',
+      name: '儿童装',
+      thumbnail:
+        'https://cdn-we-retail.ym.tencent.com/miniapp/category/category-default.png',
+      children: [
+        {
+          groupId: '249481',
+          name: '儿童装',
+          thumbnail:
+            'https://cdn-we-retail.ym.tencent.com/miniapp/category/category-default.png',
+          children: [
+            {
+              groupId: '249480',
+              name: '马甲',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-8.png',
+            },
+            {
+              groupId: '249480',
+              name: '裤子',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-11.png',
+            },
+            {
+              groupId: '249480',
+              name: '连衣裙',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/classify/img-9.png',
+            },
+            {
+              groupId: '249480',
+              name: '其他',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/goods/muy-3b.png',
+            },
+          ],
+        },
+      ],
+    },
+    {
+      groupId: '24948',
+      name: '美妆',
+      thumbnail:
+        'https://cdn-we-retail.ym.tencent.com/miniapp/category/category-default.png',
+      children: [
+        {
+          groupId: '249481',
+          name: '美妆',
+          thumbnail:
+            'https://cdn-we-retail.ym.tencent.com/miniapp/category/category-default.png',
+          children: [
+            {
+              groupId: '249480',
+              name: '唇釉',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/goods/mz-20a1.png',
+            },
+            {
+              groupId: '249480',
+              name: '美妆蛋',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/goods/mz-11a1.png',
+            },
+            {
+              groupId: '249480',
+              name: '眼影',
+              thumbnail:
+                'https://cdn-we-retail.ym.tencent.com/tsr/goods/mz-12b.png',
+            },
+          ],
+        },
+      ],
+    },
+  ];
+}

+ 338 - 0
tcb-shop-demo/model/comments.js

@@ -0,0 +1,338 @@
+/**
+ *  * @param {number} spuId
+ * @param {number} pageNum
+ * @param {number} pageSize
+ * @param {number} commentsLevel
+ * @param {boolean} hasImage
+ */
+export function getGoodsAllComments(params) {
+  const { hasImage } = params.queryParameter;
+  if (hasImage) {
+    return {
+      pageNum: 1,
+      pageSize: 10,
+      totalCount: '1',
+      pageList: [
+        {
+          spuId: '1722045',
+          skuId: '0',
+          specInfo: '',
+          commentContent:
+            '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+          commentResources: [
+            {
+              src: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+              type: 'image',
+            },
+            {
+              src: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/comment-video.mp4',
+              type: 'video',
+              coverSrc:
+                'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+            },
+            {
+              src: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/comment-video.mp4',
+              type: 'video',
+              coverSrc:
+                'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+            },
+            {
+              src: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/comment-video.mp4',
+              type: 'video',
+              coverSrc:
+                'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+            },
+          ],
+          commentScore: 4,
+          uid: '88881048075',
+          userName: 'Dean',
+          userHeadUrl:
+            'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+          isAnonymity: false,
+          commentTime: '1591953561000',
+          isAutoComment: false,
+          sellerReply:
+            '亲,你好,我们会联系商家和厂商给您一个满意的答复请一定妥善保管好发票',
+          goodsDetailInfo: '颜色:纯净白  尺码:S码',
+        },
+        {
+          spuId: '1722045',
+          skuId: '0',
+          specInfo: '',
+          commentContent:
+            '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+          commentResources: [
+            {
+              src: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+              type: 'image',
+            },
+          ],
+          commentScore: 4,
+          uid: '88881048075',
+          userName: 'Dean',
+          userHeadUrl:
+            'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+          isAnonymity: false,
+          commentTime: '1591953561000',
+          isAutoComment: false,
+          sellerReply:
+            '亲,你好,我们会联系商家和厂商给您一个满意的答复请一定妥善保管好发票',
+          goodsDetailInfo: '颜色:纯净白  尺码:S码',
+        },
+        {
+          spuId: '1722045',
+          skuId: '0',
+          specInfo: '',
+          commentContent:
+            '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+          commentResources: [
+            {
+              src: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+              type: 'image',
+            },
+            {
+              src: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/comment-video.mp4',
+              type: 'video',
+              coverSrc:
+                'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+            },
+          ],
+          commentScore: 4,
+          uid: '88881048075',
+          userName: 'Dean',
+          userHeadUrl:
+            'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+          isAnonymity: false,
+          commentTime: '1591953561000',
+          isAutoComment: false,
+          sellerReply:
+            '亲,你好,我们会联系商家和厂商给您一个满意的答复请一定妥善保管好发票',
+          goodsDetailInfo: '颜色:纯净白  尺码:S码',
+        },
+        {
+          spuId: '1722045',
+          skuId: '0',
+          specInfo: '',
+          commentContent:
+            '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+          commentResources: [
+            {
+              src: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+              type: 'image',
+            },
+            {
+              src: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/comment-video.mp4',
+              type: 'video',
+              coverSrc:
+                'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+            },
+            {
+              src: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/comment-video.mp4',
+              type: 'video',
+              coverSrc:
+                'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+            },
+          ],
+          commentScore: 4,
+          uid: '88881048075',
+          userName: 'Dean',
+          userHeadUrl:
+            'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+          isAnonymity: false,
+          commentTime: '1591953561000',
+          isAutoComment: false,
+          sellerReply:
+            '亲,你好,我们会联系商家和厂商给您一个满意的答复请一定妥善保管好发票',
+          goodsDetailInfo: '颜色:纯净白  尺码:S码',
+        },
+      ],
+    };
+  }
+  return {
+    pageNum: 1,
+    pageSize: 10,
+    totalCount: '47',
+    pageList: [
+      {
+        spuId: '1722045',
+        skuId: '1697694',
+        specInfo: '很不错',
+        commentContent:
+          '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+        commentImageUrls: null,
+        commentScore: 1,
+        uid: '88881048075',
+        userName: 'Dean',
+        userHeadUrl:
+          'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+        isAnonymity: false,
+        commentTime: '1592224320000',
+        isAutoComment: false,
+        sellerReply:
+          '亲,你好,我们会联系商家和厂商给您一个满意的答复请一定妥善保管好发票',
+        goodsDetailInfo: '颜色:纯净白  尺码:S码',
+      },
+      {
+        spuId: '1722045',
+        skuId: '1697693',
+        specInfo: '很适合',
+        commentContent:
+          '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+        commentImageUrls: null,
+        commentScore: 1,
+        uid: '88881048075',
+        userName: 'Dean',
+        userHeadUrl:
+          'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+        isAnonymity: false,
+        commentTime: '1592224320000',
+        isAutoComment: false,
+        sellerReply:
+          '亲,你好,我们会联系商家和厂商给您一个满意的答复请一定妥善保管好发票',
+        goodsDetailInfo: '颜色:纯净白  尺码:S码',
+      },
+      {
+        spuId: '1722045',
+        skuId: '1697694',
+        specInfo: 'NICE',
+        commentContent:
+          '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+        commentImageUrls: null,
+        commentScore: 5,
+        uid: '88881048075',
+        userName: 'Dean',
+        userHeadUrl:
+          'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+        isAnonymity: false,
+        commentTime: '1592218074000',
+        isAutoComment: true,
+        sellerReply:
+          '亲,你好,我们会联系商家和厂商给您一个满意的答复请一定妥善保管好发票',
+      },
+      {
+        spuId: '1722045',
+        skuId: '0',
+        specInfo: '',
+        commentContent:
+          '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+        commentImageUrls: null,
+        commentScore: 5,
+        uid: '88881048075',
+        userName: 'Dean',
+        userHeadUrl:
+          'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+        isAnonymity: false,
+        commentTime: '1592218074000',
+        isAutoComment: false,
+        goodsDetailInfo: '颜色:纯净白  尺码:S码',
+      },
+      {
+        spuId: '1722045',
+        skuId: '1697694',
+        specInfo: '测试dr超长:dr专用超长;bwtgg01:fff',
+        commentContent:
+          '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+        commentImageUrls: null,
+        commentScore: 5,
+        uid: '88881048075',
+        userName: 'Dean',
+        userHeadUrl:
+          'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+        isAnonymity: false,
+        commentTime: '1592217607000',
+        isAutoComment: false,
+      },
+      {
+        spuId: '1722045',
+        skuId: '1697693',
+        specInfo: '测试dr超长:超长测试超长测试1;bwtgg01:bbb',
+        commentContent:
+          '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+        commentImageUrls: null,
+        commentScore: 4,
+        uid: '88881048075',
+        userName: 'Dean',
+        userHeadUrl:
+          'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+        isAnonymity: false,
+        commentTime: '1592217607000',
+        isAutoComment: false,
+      },
+      {
+        spuId: '1722045',
+        skuId: '1697694',
+        specInfo: '测试dr超长:dr专用超长;bwtgg01:fff',
+        commentContent:
+          '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+        commentImageUrls: null,
+        commentScore: 5,
+        uid: '88881048075',
+        userName: 'Dean',
+        userHeadUrl:
+          'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+        isAnonymity: false,
+        commentTime: '1592205599000',
+        isAutoComment: false,
+      },
+      {
+        spuId: '1722045',
+        skuId: '1697694',
+        specInfo: '测试dr超长:dr专用超长;bwtgg01:fff',
+        commentContent:
+          '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+        commentImageUrls: null,
+        commentScore: 5,
+        uid: '88881048075',
+        userName: 'Dean',
+        userHeadUrl:
+          'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+        isAnonymity: false,
+        commentTime: '1592188822000',
+        isAutoComment: false,
+      },
+      {
+        spuId: '1722045',
+        skuId: '1697694',
+        specInfo: '测试dr超长:dr专用超长;bwtgg01:fff',
+        commentContent:
+          '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+        commentImageUrls: null,
+        commentScore: 5,
+        uid: '88881055835',
+        userName: 'Max',
+        userHeadUrl:
+          'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+        isAnonymity: false,
+        commentTime: '1593792002000',
+        isAutoComment: true,
+      },
+      {
+        spuId: '1722045',
+        skuId: '1697694',
+        specInfo: '测试dr超长:dr专用超长;bwtgg01:fff',
+        commentContent: '',
+        commentImageUrls: null,
+        commentScore: 5,
+        uid: '88881055835',
+        userName: 'Max',
+        userHeadUrl:
+          'https://cdn-we-retail.ym.tencent.com/tsr/avatar/avatar1.png',
+        isAnonymity: false,
+        commentTime: '1593792001000',
+        isAutoComment: true,
+      },
+    ],
+  };
+}
+
+export function getGoodsCommentsCount() {
+  return {
+    commentCount: '47',
+    badCount: '0',
+    middleCount: '2',
+    goodCount: '45',
+    hasImageCount: '1',
+    goodRate: 95.7,
+    uidCount: '0',
+  };
+}

+ 14 - 0
tcb-shop-demo/model/comments/queryDetail.js

@@ -0,0 +1,14 @@
+const queryDetail = {
+  commentInfos: [],
+  logisticsScore: null,
+  serviceScore: null,
+};
+
+/**
+ * @param {string} skuId
+ * @param {string} spuId
+ * @param {string} orderNo
+ */
+export function queryCommentDetail() {
+  return queryDetail;
+}

+ 39 - 0
tcb-shop-demo/model/coupon.js

@@ -0,0 +1,39 @@
+/**
+ * 优惠券
+ *
+ * @typedef {'default'|'useless'|'disabled'} CouponCardStatus
+ * @typedef {'discount'|'price'} CouponCardType
+ *
+ * @param {number} [id]
+ * @param {CouponCardStatus} [status]
+ * @param {CouponCardType} [type]
+ */
+export function getCoupon(id = 0, status = 'default', type = (id % 2) + 1) {
+  return {
+    /** key */
+    key: `${id}`,
+    /** 优惠券状态 */
+    status,
+    /** 优惠券类型 */
+    type,
+    /** 折扣或者满减值 */
+    value: type === 2 ? 5.5 : 1800,
+    /** 标签 */
+    tag: '',
+    /** 描述 */
+    desc: parseInt(id) > 0 ? `满${parseInt(id) * 100}元可用` : '无门槛使用',
+    /** 订单底价,满n元 */
+    base: 10000 * (parseInt(id) || 0),
+    /** 标题 */
+    title: type === 2 ? `生鲜折扣券 - ${id}` : `生鲜满减券 - ${id}`,
+    /** 有效时间限制 */
+    timeLimit: '2019.11.18-2023.12.18',
+    /** 货币符号 */
+    currency: '¥',
+  };
+}
+
+/** 优惠券列表 */
+export function getCouponList(status = 'default', length = 10) {
+  return new Array(length).fill(0).map((_, idx) => getCoupon(idx, status));
+}

+ 30 - 0
tcb-shop-demo/model/detailsComments.js

@@ -0,0 +1,30 @@
+export function getGoodsDetailsComments() {
+  return {
+    homePageComments: [
+      {
+        spuId: '1722045',
+        skuId: null,
+        specInfo: null,
+        commentContent:
+          '收到货了,第一时间试了一下,很漂亮特别喜欢,大爱大爱,颜色也很好看。棒棒!',
+        commentScore: 4,
+        uid: '88881048075',
+        userName: 'Dean',
+        userHeadUrl:
+          'https://wx.qlogo.cn/mmopen/vi_32/5mKrvn3ibyDNaDZSZics3aoKlz1cv0icqn4EruVm6gKjsK0xvZZhC2hkUkRWGxlIzOEc4600JkzKn9icOLE6zjgsxw/132',
+      },
+    ],
+  };
+}
+
+export function getGoodsDetailsCommentsCount() {
+  return {
+    commentCount: '47',
+    badCount: '0',
+    middleCount: '2',
+    goodCount: '45',
+    hasImageCount: '1',
+    goodRate: 95.7,
+    uidCount: '0',
+  };
+}

+ 25 - 0
tcb-shop-demo/model/good.js

@@ -0,0 +1,25 @@
+import { cdnBase } from '../config/index';
+const imgPrefix = cdnBase;
+
+const defaultDesc = [`${imgPrefix}/goods/details-1.png`];
+
+const allGoods = [];
+
+/**
+ * @param {string} id
+ * @param {number} [available] 库存, 默认1
+ */
+export function genGood(id, available = 1) {
+  const specID = ['135681624', '135681628'];
+  if (specID.indexOf(id) > -1) {
+    return allGoods.filter((good) => good.spuId === id)[0];
+  }
+  const item = allGoods[id % allGoods.length];
+  return {
+    ...item,
+    spuId: `${id}`,
+    available: available,
+    desc: item?.desc || defaultDesc,
+    images: item?.images || [item?.primaryImage],
+  };
+}

+ 7 - 0
tcb-shop-demo/model/goods.js

@@ -0,0 +1,7 @@
+import { genGood } from './good';
+
+export function getGoodsList(baseID = 0, length = 10) {
+  return new Array(length).fill(0).map((_, idx) => genGood(idx + baseID));
+}
+
+export const goodsList = getGoodsList();

+ 295 - 0
tcb-shop-demo/model/order/applyService.js

@@ -0,0 +1,295 @@
+import { mockIp, mockReqId } from '../../utils/mock';
+
+const orderResps = [
+  {
+    data: {
+      saasId: '88888888',
+      uid: '88888888205468',
+      storeId: '1000',
+      skuId: '135691625',
+      numOfSku: 1,
+      numOfSkuAvailable: 1,
+      refundableAmount: '26900',
+      refundableDiscountAmount: '0',
+      shippingFeeIncluded: '0',
+      paidAmountEach: '26900',
+      boughtQuantity: 1,
+      orderNo: '132222623132329291',
+      goodsInfo: {
+        goodsName:
+          '迷你便携高颜值蓝牙无线耳机立体声只能触控式操作简约立体声耳机',
+        skuImage: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/dz-2a.png',
+        specInfo: [
+          {
+            specId: '50456',
+            specTitle: '颜色',
+            specValue: '黑色',
+          },
+          {
+            specId: '50459',
+            specTitle: '尺码',
+            specValue: '简约款',
+          },
+        ],
+      },
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 36,
+    success: true,
+  },
+  {
+    data: {
+      saasId: '88888888',
+      uid: '88888888205468',
+      storeId: '1000',
+      skuId: '135676631',
+      numOfSku: 1,
+      numOfSkuAvailable: 1,
+      refundableAmount: '26900',
+      refundableDiscountAmount: '0',
+      shippingFeeIncluded: '0',
+      paidAmountEach: '26900',
+      boughtQuantity: 1,
+      orderNo: '132222623132329291',
+      goodsInfo: {
+        goodsName: '白色短袖连衣裙荷叶边裙摆宽松韩版休闲纯白清爽优雅连衣裙',
+        skuImage: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-09a.png',
+        specInfo: [
+          {
+            specId: '50456',
+            specTitle: '颜色',
+            specValue: '米色荷叶边',
+          },
+          {
+            specId: '50459',
+            specTitle: '尺码',
+            specValue: 'S',
+          },
+        ],
+      },
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 36,
+    success: true,
+  },
+  {
+    data: {
+      saasId: '88888888',
+      uid: '88888888205468',
+      storeId: '1000',
+      skuId: '135691622',
+      numOfSku: 1,
+      numOfSkuAvailable: 1,
+      refundableAmount: '26900',
+      refundableDiscountAmount: '0',
+      shippingFeeIncluded: '0',
+      paidAmountEach: '26900',
+      boughtQuantity: 1,
+      orderNo: '132222623132329291',
+      goodsInfo: {
+        goodsName: '腾讯极光盒子4智能网络电视机顶盒6K千兆网络机顶盒4K高分辨率',
+        skuImage: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/dz-3a.png',
+        specInfo: [
+          {
+            specId: '50456',
+            specTitle: '颜色',
+            specValue: '经典白',
+          },
+          {
+            specId: '50459',
+            specTitle: '类型',
+            specValue: '经典套装',
+          },
+        ],
+      },
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 36,
+    success: true,
+  },
+  {
+    data: {
+      saasId: '88888888',
+      uid: '88888888205468',
+      storeId: '1000',
+      skuId: '135676629',
+      numOfSku: 1,
+      numOfSkuAvailable: 1,
+      refundableAmount: '26900',
+      refundableDiscountAmount: '0',
+      shippingFeeIncluded: '0',
+      paidAmountEach: '26900',
+      boughtQuantity: 1,
+      orderNo: '132222623132329291',
+      goodsInfo: {
+        goodsName: '带帽午休毯虎年款多功能加厚加大加绒简约多功能午休毯连帽披肩',
+        skuImage: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/muy-3a.png',
+        specInfo: [
+          {
+            specId: '50456',
+            specTitle: '颜色',
+            specValue: '浅灰色',
+          },
+          {
+            specId: '50459',
+            specTitle: '尺码',
+            specValue: 'S',
+          },
+        ],
+      },
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 36,
+    success: true,
+  },
+  {
+    data: {
+      saasId: '88888888',
+      uid: '88888888205468',
+      storeId: '1000',
+      skuId: '135686631',
+      numOfSku: 1,
+      numOfSkuAvailable: 1,
+      refundableAmount: '26900',
+      refundableDiscountAmount: '0',
+      shippingFeeIncluded: '0',
+      paidAmountEach: '26900',
+      boughtQuantity: 1,
+      orderNo: '132222623132329291',
+      goodsInfo: {
+        goodsName: '运动连帽拉链卫衣休闲开衫长袖多色运动细绒面料运动上衣',
+        skuImage: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-17a.png',
+        specInfo: [
+          {
+            specId: '50456',
+            specTitle: '颜色',
+            specValue: '军绿色',
+          },
+          {
+            specId: '50459',
+            specTitle: '尺码',
+            specValue: 'XS',
+          },
+        ],
+      },
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 36,
+    success: true,
+  },
+  {
+    data: {
+      saasId: '88888888',
+      uid: '88888888205468',
+      storeId: '1000',
+      skuId: '19384938948343',
+      numOfSku: 1,
+      numOfSkuAvailable: 1,
+      refundableAmount: '26900',
+      refundableDiscountAmount: '0',
+      shippingFeeIncluded: '0',
+      paidAmountEach: '26900',
+      boughtQuantity: 1,
+      orderNo: '130169571554503755',
+      goodsInfo: {
+        goodsName:
+          '纯色纯棉休闲圆领短袖T恤纯白亲肤厚柔软细腻面料纯白短袖套头T恤',
+        skuImage: 'https://cdn-we-retail.ym.tencent.com/tsr/goods/nz-08b.png',
+        specInfo: [
+          {
+            specId: '50456',
+            specTitle: '颜色',
+            specValue: '军绿色',
+          },
+          {
+            specId: '50459',
+            specTitle: '尺码',
+            specValue: 'XS',
+          },
+        ],
+      },
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 36,
+    success: true,
+  },
+];
+
+export function genRightsPreview(params) {
+  const { orderNo, skuId } = params;
+  const resp = orderResps.find(
+    (r) => r.data.orderNo === orderNo && r.data.skuId === skuId,
+  );
+  return resp;
+}
+
+export function genApplyReasonList(params) {
+  const resp = {
+    data: {
+      saasId: '70000001',
+      rightsReasonList: [
+        { id: '1', desc: '实际商品与描述不符' },
+        { id: '2', desc: '质量问题' },
+        { id: '3', desc: '少件/漏发' },
+        { id: '4', desc: '包装/商品/污迹/裂痕/变形' },
+        { id: '5', desc: '发货太慢' },
+        { id: '6', desc: '物流配送太慢' },
+        { id: '7', desc: '商家发错货' },
+        { id: '8', desc: '不喜欢' },
+      ],
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 6,
+    success: true,
+  };
+  // 未收货对应的原因列表
+  if (params.rightsReasonType === 'REFUND_MONEY') {
+    resp.data.rightsReasonList = [
+      { id: '9', desc: '空包裹' },
+      { id: '10', desc: '快递/物流一直未送到' },
+      { id: '11', desc: '货物破损已拒签' },
+      { id: '12', desc: '不喜欢' },
+    ];
+  }
+  return resp;
+}
+
+export function applyService() {
+  const resp = {
+    data: {
+      rightsNo: '123123423',
+      saasId: '70000001',
+      uid: '700000011070005',
+      storeId: '542',
+      result: null,
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 269,
+    success: true,
+  };
+  return resp;
+}

+ 147 - 0
tcb-shop-demo/model/order/orderConfirm.js

@@ -0,0 +1,147 @@
+import { mockIp, mockReqId } from '../../utils/mock';
+
+export const transformGoodsDataToConfirmData = (goodsDataList) => {
+  const list = [];
+
+  goodsDataList.forEach((goodsData) => {
+    list.push({
+      storeId: goodsData.storeId,
+      spuId: goodsData.spuId,
+      skuId: goodsData.skuId,
+      goodsName: goodsData.title,
+      image: goodsData.primaryImage,
+      reminderStock: 119,
+      quantity: goodsData.quantity,
+      payPrice: goodsData.price,
+      totalSkuPrice: goodsData.price,
+      discountSettlePrice: goodsData.price,
+      realSettlePrice: goodsData.price,
+      settlePrice: goodsData.price,
+      oriPrice: goodsData.originPrice,
+      tagPrice: null,
+      tagText: null,
+      skuSpecLst: goodsData.specInfo,
+      promotionIds: null,
+      weight: 0.0,
+      unit: 'KG',
+      volume: null,
+      masterGoodsType: 0,
+      viceGoodsType: 0,
+      roomId: goodsData.roomId,
+      egoodsName: null,
+    });
+  });
+
+  return list;
+};
+
+/** 生成结算数据 */
+export function genSettleDetail(params) {
+  const { userAddressReq, couponList, goodsRequestList } = params;
+
+  const resp = {
+    data: {
+      settleType: 0,
+      userAddress: null,
+      totalGoodsCount: 3,
+      packageCount: 1,
+      totalAmount: '289997',
+      totalPayAmount: '',
+      totalDiscountAmount: '110000',
+      totalPromotionAmount: '1100',
+      totalCouponAmount: '0',
+      totalSalePrice: '289997',
+      totalGoodsAmount: '289997',
+      totalDeliveryFee: '0',
+      invoiceRequest: null,
+      skuImages: null,
+      deliveryFeeList: null,
+      storeGoodsList: [
+        {
+          storeId: '1000',
+          storeName: '云Mall深圳旗舰店',
+          remark: null,
+          goodsCount: 1,
+          deliveryFee: '0',
+          deliveryWords: null,
+          storeTotalAmount: '0',
+          storeTotalPayAmount: '179997',
+          storeTotalDiscountAmount: '110000',
+          storeTotalCouponAmount: '0',
+          skuDetailVos: [],
+          couponList: [
+            {
+              couponId: 11,
+              storeId: '1000',
+            },
+          ],
+        },
+      ],
+      inValidGoodsList: null,
+      outOfStockGoodsList: null,
+      limitGoodsList: null,
+      abnormalDeliveryGoodsList: null,
+      invoiceSupport: 1,
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 244,
+    success: true,
+  };
+
+  const list = transformGoodsDataToConfirmData(goodsRequestList);
+
+  // 获取购物车传递的商品数据
+  resp.data.storeGoodsList[0].skuDetailVos = list;
+
+  // 判断是否携带优惠券数据
+  const discountPrice = [];
+
+  if (couponList && couponList.length > 0) {
+    couponList.forEach((coupon) => {
+      if (coupon.status === 'default') {
+        discountPrice.push({
+          type: coupon.type,
+          value: coupon.value,
+        });
+      }
+    });
+  }
+
+  // 模拟计算场景
+
+  // 计算总价
+  const totalPrice = list.reduce((pre, cur) => {
+    return pre + cur.quantity * Number(cur.settlePrice);
+  }, 0);
+
+  // 计算折扣
+  const totalDiscountPrice =
+    discountPrice.length > 0
+      ? discountPrice.reduce((pre, cur) => {
+          if (cur.type === 1) {
+            return pre + cur.value;
+          }
+          if (cur.type === 2) {
+            return pre + (Number(totalPrice) * cur.value) / 10;
+          }
+
+          return pre + cur;
+        }, 0)
+      : 0;
+
+  resp.data.totalSalePrice = totalPrice;
+
+  resp.data.totalCouponAmount = totalDiscountPrice;
+
+  resp.data.totalPayAmount =
+    totalPrice - totalDiscountPrice - Number(resp.data.totalPromotionAmount);
+
+  if (userAddressReq) {
+    resp.data.settleType = 1;
+    resp.data.userAddress = userAddressReq;
+  }
+  return resp;
+}

+ 26 - 0
tcb-shop-demo/model/order/orderDetail.js

@@ -0,0 +1,26 @@
+import { mockIp, mockReqId } from '../../utils/mock';
+
+const orderResps = [];
+
+export function genOrderDetail(params) {
+  const { parameter } = params;
+  const resp = orderResps.find((r) => r.data.orderNo === parameter);
+  return resp;
+}
+
+export function genBusinessTime() {
+  const resp = {
+    data: {
+      businessTime: ['周一,周二,周三,周四,周五:00:20:00-08:00:00'],
+      telphone: '18565372257',
+      saasId: '88888888',
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 3,
+    success: true,
+  };
+  return resp;
+}

+ 46 - 0
tcb-shop-demo/model/order/orderList.js

@@ -0,0 +1,46 @@
+import { mockIp, mockReqId } from '../../utils/mock';
+
+export function genOrders(params) {
+  const resp = {
+    data: {
+      pageNum: 1,
+      pageSize: 10,
+      totalCount: 7,
+      orders: [],
+    },
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 113,
+    success: true,
+  };
+  const { pageNum, pageSize, orderStatus } = params.parameter;
+  // 实现筛选
+  if (orderStatus > -1) {
+    resp.data.orders = resp.data.orders.filter((order) => order.orderStatus === orderStatus);
+  }
+  // 实现分页
+  resp.data.pageNum = pageNum;
+  resp.data.pageSize = pageSize;
+  resp.data.orders = resp.data.orders.slice((pageNum - 1) * pageSize, pageNum * pageSize);
+  return resp;
+}
+
+export function genOrdersCount() {
+  const resp = {
+    data: [
+      { tabType: 5, orderNum: 1 },
+      { tabType: 10, orderNum: 1 },
+      { tabType: 40, orderNum: 1 },
+      { tabType: 50, orderNum: 2 },
+    ],
+    code: 'Success',
+    msg: null,
+    requestId: mockReqId(),
+    clientIp: mockIp(),
+    rt: 41,
+    success: true,
+  };
+  return resp;
+}

+ 21 - 0
tcb-shop-demo/model/promotion.js

@@ -0,0 +1,21 @@
+import { getGoodsList } from './goods';
+
+export function getPromotion(baseID = 0, length = 10) {
+  return {
+    list: getGoodsList(baseID, length).map((item) => {
+      return {
+        spuId: item.spuId,
+        thumb: item.primaryImage,
+        title: item.title,
+        price: item.minSalePrice,
+        originPrice: item.maxLinePrice,
+        tags: item.spuTagList.map((tag) => ({ title: tag.title })),
+      };
+    }),
+    banner:
+      'https://cdn-we-retail.ym.tencent.com/tsr/promotion/banner-promotion.png',
+    time: 1000 * 60 * 60 * 20,
+    showBannerDesc: true,
+    statusTag: 'running',
+  };
+}

+ 60 - 0
tcb-shop-demo/model/search.js

@@ -0,0 +1,60 @@
+import { getGoodsList } from './goods';
+
+/**
+ * @param {number} sort
+ * @param {number} pageNum
+ * @param {number} pageSize
+ * @param {number} minPrice
+ * @param {number} maxPrice
+ * @param {string} keyword
+ */
+
+export function getSearchHistory() {
+  return {
+    historyWords: [
+      '鸡',
+      '电脑',
+      'iPhone12',
+      '车载手机支架',
+      '自然堂',
+      '小米10',
+      '原浆古井贡酒',
+      '欧米伽',
+      '华为',
+      '针织半身裙',
+      '氢跑鞋',
+      '三盒处理器',
+    ],
+  };
+}
+
+export function getSearchPopular() {
+  return {
+    popularWords: [
+      '鸡',
+      '电脑',
+      'iPhone12',
+      '车载手机支架',
+      '自然堂',
+      '小米10',
+      '原浆古井贡酒',
+      '欧米伽',
+      '华为',
+      '针织半身裙',
+      '氢跑鞋',
+      '三盒处理器',
+    ],
+  };
+}
+
+export function getSearchResult() {
+  return {
+    saasId: null,
+    storeId: null,
+    pageNum: 1,
+    pageSize: 30,
+    totalCount: 1,
+    spuList: getGoodsList(7),
+    algId: 0,
+  };
+}

+ 58 - 0
tcb-shop-demo/model/submitComment.js

@@ -0,0 +1,58 @@
+export function getGoods() {
+  return {
+    goods: [
+      {
+        squid: '1',
+        checkItems: [
+          {
+            name: '匿名评价',
+            value: 'anonymous',
+            checked: false,
+          },
+        ],
+        detail: {
+          image:
+            'https://wx.qlogo.cn/mmopen/vi_32/51VSMNuy1CyHiaAhAjLJ00kMZVqqnCqXeZduCLXHUBr52zFHRGxwL7kGia3fHj8GSNzFcqFDInQmRGM1eWjtQgqA/132',
+          title: '',
+        },
+        goodComment: {
+          /** 商品评价 */
+          rate: 0,
+          /** 评价内容 */
+          label: '123',
+          /** 上传图片 */
+          images: [],
+        },
+      },
+      {
+        squid: '2',
+        checkItems: [
+          {
+            name: '匿名评价',
+            value: 'anonymous',
+            checked: false,
+          },
+        ],
+        detail: {
+          image:
+            'https://wx.qlogo.cn/mmopen/vi_32/51VSMNuy1CyHiaAhAjLJ00kMZVqqnCqXeZduCLXHUBr52zFHRGxwL7kGia3fHj8GSNzFcqFDInQmRGM1eWjtQgqA/132',
+          title: '评价内容 山姆智利进口',
+        },
+        goodComment: {
+          /** 商品评价 */
+          rate: 0,
+          /** 评价内容 */
+          label: '山姆智利进口',
+          /** 上传图片 */
+          images: [],
+        },
+      },
+    ],
+    storeComment: {
+      /** 物流评价 */
+      logisticsRate: 0,
+      /** 服务评价 */
+      servicesRate: 0,
+    },
+  };
+}

+ 39 - 0
tcb-shop-demo/model/swiper.js

@@ -0,0 +1,39 @@
+// const images = [
+//   {
+//     img: 'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner1.png',
+//     text: '1',
+//   },
+//   {
+//     img: 'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner2.png',
+//     text: '2',
+//   },
+//   {
+//     img: 'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner3.png',
+//     text: '3',
+//   },
+//   {
+//     img: 'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner4.png',
+//     text: '4',
+//   },
+//   {
+//     img: 'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner5.png',
+//     text: '5',
+//   },
+//   {
+//     img: 'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner6.png',
+//     text: '6',
+//   },
+// ];
+
+const images = [
+  'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner1.png',
+  'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner2.png',
+  'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner3.png',
+  'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner4.png',
+  'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner5.png',
+  'https://cdn-we-retail.ym.tencent.com/tsr/home/v2/banner6.png',
+];
+
+export function genSwiperImageList() {
+  return images;
+}

+ 52 - 0
tcb-shop-demo/model/usercenter.js

@@ -0,0 +1,52 @@
+const userInfo = {
+  avatarUrl:
+    'https://we-retail-static-1300977798.cos.ap-guangzhou.myqcloud.com/retail-ui/components-exp/avatar/avatar-1.jpg',
+  nickName: '云开发',
+  phoneNumber: '13438358888',
+  gender: 2,
+};
+const countsData = [
+  {
+    num: 2,
+    name: '积分',
+    type: 'point',
+  },
+  {
+    num: 10,
+    name: '优惠券',
+    type: 'coupon',
+  },
+];
+
+const orderTagInfos = [
+  {
+    orderNum: 1,
+    tabType: 5,
+  },
+  {
+    orderNum: 1,
+    tabType: 10,
+  },
+  {
+    orderNum: 1,
+    tabType: 40,
+  },
+  {
+    orderNum: 0,
+    tabType: 0,
+  },
+];
+
+const customerServiceInfo = {
+  servicePhone: '4006336868',
+  serviceTimeDuration: '每周三至周五 9:00-12:00  13:00-15:00',
+};
+
+export const genSimpleUserInfo = () => ({ ...userInfo });
+
+export const genUsercenter = () => ({
+  userInfo,
+  countsData,
+  orderTagInfos,
+  customerServiceInfo,
+});

+ 46 - 0
tcb-shop-demo/package.json

@@ -0,0 +1,46 @@
+{
+  "name": "tcb-cloudbase-shop",
+  "version": "1.0.0",
+  "description": "",
+  "main": "app.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "lint": "eslint --cache --fix --ext .js",
+    "check": "node config/eslintCheck.js",
+    "prepare": "husky install",
+    "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
+  },
+  "author": "",
+  "license": "ISC",
+  "config": {
+    "commitizen": {
+      "path": "./node_modules/cz-conventional-changelog"
+    }
+  },
+  "lint-staged": {
+    "*.{js, ts}": "eslint --cache --fix",
+    "*.{js,ts,wxml,html,json,css,less}": [
+      "prettier --write"
+    ]
+  },
+  "dependencies": {
+    "@cloudbase/wx-cloud-client-sdk": "^1.2.1",
+    "dayjs": "^1.9.3",
+    "tdesign-miniprogram": "^1.6.0",
+    "tslib": "^1.11.1"
+  },
+  "devDependencies": {
+    "@commitlint/cli": "^17.4.2",
+    "@commitlint/config-conventional": "^17.4.2",
+    "commitizen": "^4.3.0",
+    "conventional-changelog-cli": "^2.2.2",
+    "cz-conventional-changelog": "^3.3.0",
+    "eslint": "^6.8.0",
+    "eslint-config-prettier": "^6.10.0",
+    "eslint-plugin-import": "^2.20.1",
+    "eslint-plugin-prettier": "^3.1.2",
+    "husky": "^8.0.3",
+    "lint-staged": "^10.0.8",
+    "prettier": "^2.1.2"
+  }
+}

+ 59 - 0
tcb-shop-demo/pages/cart/components/cart-bar/index.js

@@ -0,0 +1,59 @@
+Component({
+  options: {
+    addGlobalClass: true,
+  },
+  /**
+   * 组件的属性列表
+   */
+  properties: {
+    isAllSelected: {
+      type: Boolean,
+      value: false,
+    },
+    totalAmount: {
+      type: Number,
+      value: 1,
+    },
+    totalGoodsNum: {
+      type: Number,
+      value: 0,
+      observer(num) {
+        const isDisabled = num == 0;
+        setTimeout(() => {
+          this.setData({
+            isDisabled,
+          });
+        });
+      },
+    },
+    totalDiscountAmount: {
+      type: Number,
+      value: 0,
+    },
+    bottomHeight: {
+      type: Number,
+      value: 100,
+    },
+    fixed: Boolean,
+  },
+  data: {
+    isDisabled: true,
+  },
+
+  methods: {
+    handleSelectAll() {
+      const { isAllSelected } = this.data;
+      this.setData({
+        isAllSelected: !isAllSelected,
+      });
+      this.triggerEvent('handleSelectAll', {
+        isAllSelected: isAllSelected,
+      });
+    },
+
+    handleToSettle() {
+      if (this.data.isDisabled) return;
+      this.triggerEvent('handleToSettle');
+    },
+  },
+});

+ 7 - 0
tcb-shop-demo/pages/cart/components/cart-bar/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "usingComponents": {
+    "price": "/components/price/index",
+    "t-icon": "tdesign-miniprogram/icon/icon"
+  }
+}

+ 14 - 0
tcb-shop-demo/pages/cart/components/cart-bar/index.wxml

@@ -0,0 +1,14 @@
+<view class="cart-bar__placeholder" wx:if="{{fixed}}" />
+<view class="cart-bar {{fixed ? 'cart-bar--fixed' : ''}} flex flex-v-center" style="bottom: {{fixed ? 'calc(' + bottomHeight + 'rpx + env(safe-area-inset-bottom))' : ''}};">
+  <t-icon size="40rpx" color="{{isAllSelected ? '#FA4126' : '#BBBBBB'}}" name="{{isAllSelected ? 'check-circle-filled' : 'circle'}}" class="cart-bar__check" catchtap="handleSelectAll" />
+  <text>全选</text>
+  <view class="cart-bar__total flex1">
+    <view>
+      <text class="cart-bar__total--bold text-padding-right">总计</text>
+      <price price="{{totalAmount || '0'}}" fill="{{false}}" decimalSmaller class="cart-bar__total--bold cart-bar__total--price" />
+    </view>
+  </view>
+  <view catchtap="handleToSettle" class="{{!isDisabled ? '' : 'disabled-btn'}} account-btn" hover-class="{{!isDisabled ? '' : 'hover-btn'}}">
+    去结算({{totalGoodsNum}})
+  </view>
+</view>

+ 80 - 0
tcb-shop-demo/pages/cart/components/cart-bar/index.wxss

@@ -0,0 +1,80 @@
+.cart-bar__placeholder {
+  height: 100rpx;
+}
+.flex {
+  display: flex;
+}
+.flex-v-center {
+  align-items: center;
+}
+.flex1 {
+  flex: 1;
+}
+.algin-bottom {
+  text-align: end;
+}
+.cart-bar--fixed {
+  position: fixed;
+  left: 0;
+  right: 0;
+  z-index: 99;
+  bottom: calc(100rpx + env(safe-area-inset-bottom));
+}
+
+.cart-bar {
+  height: 112rpx;
+  background-color: #fff;
+  border-top: 1rpx solid #e5e5e5;
+  padding: 16rpx 32rpx;
+  box-sizing: border-box;
+  font-size: 24rpx;
+  line-height: 36rpx;
+  color: #333;
+}
+
+.cart-bar .cart-bar__check {
+  margin-right: 12rpx;
+}
+
+.cart-bar .cart-bar__total {
+  margin-left: 24rpx;
+}
+
+.cart-bar .account-btn {
+  width: 192rpx;
+  height: 80rpx;
+  border-radius: 40rpx;
+  background-color: #fa4126;
+  font-size: 28rpx;
+  font-weight: bold;
+  line-height: 80rpx;
+  color: #ffffff;
+  text-align: center;
+}
+.cart-bar .disabled-btn {
+  background-color: #cccccc !important;
+}
+.cart-bar .hover-btn {
+  opacity: 0.5;
+}
+
+.cart-bar__total .cart-bar__total--bold {
+  font-size: 28rpx;
+  line-height: 40rpx;
+  color: #333;
+  font-weight: bold;
+}
+.cart-bar__total .cart-bar__total--normal {
+  font-size: 24rpx;
+  line-height: 32rpx;
+  color: #999;
+}
+
+.cart-bar__total .cart-bar__total--price {
+  color: #fa4126;
+  font-weight: bold;
+}
+
+.text-padding-right {
+  padding-right: 4rpx;
+}

+ 27 - 0
tcb-shop-demo/pages/cart/components/cart-empty/index.js

@@ -0,0 +1,27 @@
+Component({
+  properties: {
+    imgUrl: {
+      type: String,
+      value:
+        'https://cdn-we-retail.ym.tencent.com/miniapp/template/empty-cart.png',
+    },
+    tip: {
+      type: String,
+      value: '购物车是空的',
+    },
+    btnText: {
+      type: String,
+      value: '去首页',
+    },
+    advertisementImage: {
+      type: String,
+      value: '',
+    }
+  },
+  data: {},
+  methods: {
+    handleClick() {
+      this.triggerEvent('handleClick');
+    },
+  },
+});

+ 6 - 0
tcb-shop-demo/pages/cart/components/cart-empty/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "t-image": "/components/webp-image/index"
+  }
+}

+ 11 - 0
tcb-shop-demo/pages/cart/components/cart-empty/index.wxml

@@ -0,0 +1,11 @@
+<view class="cart-empty">
+	<t-image t-class="cart-img" src="{{imgUrl}}" />
+	<view class="tip">{{tip}}</view>
+	<view class="btn" bind:tap="handleClick">{{btnText}}</view>
+
+  <view class="cart-advertisement"  >
+    <t-image src="{{advertisementImage}}" t-class="ad-image" mode="aspectFill" />
+  </view>
+
+</view>
+

+ 54 - 0
tcb-shop-demo/pages/cart/components/cart-empty/index.wxss

@@ -0,0 +1,54 @@
+.cart-empty {
+  padding: 64rpx 0rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  box-sizing: border-box;
+  height: calc(100vh - 100rpx);
+  background-color: #f5f5f5;
+}
+.cart-empty .cart-img {
+  width: 160rpx;
+  height: 160rpx;
+  margin-bottom: 24rpx;
+}
+
+.cart-empty .tip {
+  font-size: 28rpx;
+  line-height: 40rpx;
+  color: #999;
+  margin-bottom: 24rpx;
+}
+.cart-empty .btn {
+  width: 240rpx;
+  height: 72rpx;
+  border-radius: 36rpx;
+  text-align: center;
+  line-height: 72rpx;
+  border: 2rpx solid #fa4126;
+  color: #fa4126;
+  background-color: transparent;
+  font-size: 28rpx;
+  font-weight: bold;
+}
+
+.cart-advertisement {
+  position: fixed;
+  bottom: 14%;
+  right: 3%;
+  width: 50%;
+  padding: 20rpx;
+  box-sizing: border-box;
+  z-index: 10;
+  background-color: white;
+  box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.1);
+}
+
+.cart-advertisement .ad-image {
+  width: 100%;
+  height: 550rpx;
+  border-radius: 8rpx;
+  background-color: #f5f5f5;
+  object-fit: cover;
+  display: block;
+}

+ 168 - 0
tcb-shop-demo/pages/cart/components/cart-group/index.js

@@ -0,0 +1,168 @@
+import Toast from 'tdesign-miniprogram/toast/index';
+
+const shortageImg = 'https://cdn-we-retail.ym.tencent.com/miniapp/cart/shortage.png';
+
+Component({
+  isSpecsTap: false, // 标记本次点击事件是否因为点击specs触发(由于底层goods-card组件没有catch specs点击事件,只能在此处加状态来避免点击specs时触发跳转商品详情)
+  externalClasses: ['wr-class'],
+  properties: {
+    storeGoods: {
+      type: Array,
+      observer(storeGoods) {
+        for (const store of storeGoods) {
+          for (const activity of store.promotionGoodsList) {
+            for (const goods of activity.goodsPromotionList) {
+              goods.specs = goods.specInfo.map((item) => item.specValue); // 目前仅展示商品已选规格的值
+            }
+          }
+          for (const goods of store.shortageGoodsList) {
+            goods.specs = goods.specInfo.map((item) => item.specValue); // 目前仅展示商品已选规格的值
+          }
+        }
+
+        this.setData({ _storeGoods: storeGoods });
+      },
+    },
+    invalidGoodItems: {
+      type: Array,
+      observer(invalidGoodItems) {
+        invalidGoodItems.forEach((goods) => {
+          goods.specs = goods.specInfo.map((item) => item.specValue); // 目前仅展示商品已选规格的值
+        });
+        this.setData({ _invalidGoodItems: invalidGoodItems });
+      },
+    },
+    thumbWidth: { type: null },
+    thumbHeight: { type: null },
+    cartItems: { type: Array },
+  },
+
+  data: {
+    shortageImg,
+    isShowSpecs: false,
+    currentGoods: null,
+    isShowToggle: false,
+    _storeGoods: [],
+    _invalidGoodItems: [],
+  },
+
+  methods: {
+    toast(message) {
+      Toast({
+        context: this,
+        selector: '#t-toast',
+        message,
+      });
+    },
+    // 删除商品
+    deleteGoods(e) {
+      const { goods } = e.currentTarget.dataset;
+      this.triggerEvent('delete', { goods });
+    },
+
+    // 清空失效商品
+    clearInvalidGoods() {
+      this.triggerEvent('clearinvalidgoods');
+    },
+
+    // 选中商品
+    selectGoods(e) {
+      const { goods } = e.currentTarget.dataset;
+      this.triggerEvent('selectgoods', {
+        goods,
+      });
+    },
+
+    changeQuantity(count, cartItemId) {
+      this.triggerEvent('changequantity', {
+        count,
+        cartItemId,
+      });
+    },
+    changeStepper({
+      detail: { value: changeToValue },
+      target: {
+        dataset: { goods },
+      },
+    }) {
+      // validation is handled by this.overlimit()
+      this.changeQuantity(changeToValue, goods._id);
+    },
+
+    input(e) {
+      const { value } = e.detail;
+      const { goods } = e.currentTarget.dataset;
+      const num = value;
+      this.changeQuantity(num, goods);
+    },
+
+    overlimit(e) {
+      const text = e.detail.type === 'minus' ? '不能再少了' : '不能再多了';
+      this.toast(text);
+    },
+
+    // 去凑单/再逛逛
+    gotoBuyMore(e) {
+      const { promotion, storeId = '' } = e.currentTarget.dataset;
+      this.triggerEvent('gocollect', { promotion, storeId });
+    },
+
+    // 选中门店
+    selectStore(e) {
+      const { storeIndex } = e.currentTarget.dataset;
+      const store = this.data.storeGoods[storeIndex];
+      const isSelected = !store.isSelected;
+      if (store.storeStockShortage && isSelected) {
+        Toast({
+          context: this,
+          selector: '#t-toast',
+          message: '部分商品库存不足',
+        });
+        return;
+      }
+      this.triggerEvent('selectstore', {
+        store,
+        isSelected,
+      });
+    },
+
+    // 展开/收起切换
+    showToggle() {
+      this.setData({
+        isShowToggle: !this.data.isShowToggle,
+      });
+    },
+
+    // 展示规格popup
+    specsTap({
+      target: {
+        dataset: { goods },
+      },
+    }) {
+      this.isSpecsTap = true;
+      this.setData({
+        isShowSpecs: true,
+        currentGoods: goods,
+      });
+    },
+
+    hideSpecsPopup() {
+      this.setData({
+        isShowSpecs: false,
+      });
+    },
+
+    goGoodsDetail(e) {
+      if (this.isSpecsTap) {
+        this.isSpecsTap = false;
+        return;
+      }
+      const { goods } = e.currentTarget.dataset;
+      this.triggerEvent('goodsclick', { goods });
+    },
+
+    gotoCoupons() {
+      wx.navigateTo({ url: '/pages/coupon/coupon-list/index' });
+    },
+  },
+});

+ 11 - 0
tcb-shop-demo/pages/cart/components/cart-group/index.json

@@ -0,0 +1,11 @@
+{
+  "component": true,
+  "usingComponents": {
+    "t-toast": "tdesign-miniprogram/toast/toast",
+    "t-icon": "tdesign-miniprogram/icon/icon",
+    "t-stepper": "tdesign-miniprogram/stepper/stepper",
+    "swipeout": "/components/swipeout/index",
+    "goods-card": "../../components/goods-card/index",
+    "specs-popup": "../../components/specs-popup/index"
+  }
+}

+ 35 - 0
tcb-shop-demo/pages/cart/components/cart-group/index.wxml

@@ -0,0 +1,35 @@
+<wxs src="./index.wxs" module="handlePromotion" />
+<wxs src="./utils.wxs" module="utils" />
+<view class="cart-group">
+  <view class="goods-wrap">
+    <block wx:if="{{cartItems.length>0}}">
+      <view class="goods-item" wx:for="{{cartItems}}" wx:for-item="goods" wx:for-index="gi" wx:key="_id">
+        <swipeout right-width="{{ 72 }}">
+          <view class="goods-item-info">
+            <view class="check-wrap" catchtap="selectGoods" data-goods="{{goods}}">
+              <t-icon size="40rpx" color="{{goods.selected ? '#FA4126' : '#BBBBBB'}}" name="{{goods.selected ? 'check-circle-filled' : 'circle'}}" class="check" />
+            </view>
+            <view class="goods-sku-info">
+              <goods-card layout="horizontal-wrap" thumb-width="{{thumbWidth}}" thumb-height="{{thumbHeight}}" centered="{{true}}" data="{{goods}}" data-goods="{{goods}}" catchspecs="specsTap" catchclick="goGoodsDetail">
+                <view slot="thumb-cover" class="stock-mask" wx:if="{{goods.sku.count <= 3}}">
+                  仅剩{{goods.sku.count}}件
+                </view>
+                <view slot="append-body" class="goods-stepper">
+                  <t-stepper classname="stepper-info" value="{{goods.count}}" min="{{1}}" max="{{goods.sku.count}}" data-goods="{{goods}}" data-gi="{{gi}}" catchchange="changeStepper" catchblur="input" catchoverlimit="overlimit" theme="filled" />
+                </view>
+              </goods-card>
+            </view>
+          </view>
+          <view slot="right" class="swiper-right-del" bindtap="deleteGoods" data-goods="{{goods}}">
+            删除
+          </view>
+        </swipeout>
+      </view>
+      <view class="promotion-line-wrap" wx:if="{{handlePromotion.hasPromotion(promotion.promotionCode) && promoindex != (store.promotionGoodsList.length - 2)}}">
+        <view class="promotion-line" />
+      </view>
+    </block>
+  </view>
+</view>
+<specs-popup wx:if="{{currentGoods}}" goods="{{currentGoods}}" show="{{isShowSpecs}}" bindclose="hideSpecsPopup" />
+<t-toast id="t-toast" />

+ 5 - 0
tcb-shop-demo/pages/cart/components/cart-group/index.wxs

@@ -0,0 +1,5 @@
+var hasPromotion = function (code) {
+  return code && code !== 'EMPTY_PROMOTION';
+};
+
+module.exports.hasPromotion = hasPromotion;

+ 335 - 0
tcb-shop-demo/pages/cart/components/cart-group/index.wxss

@@ -0,0 +1,335 @@
+.cart-group {
+  border-radius: 8rpx;
+}
+.cart-group .goods-wrap {
+  margin-top: 40rpx;
+  background-color: #fff;
+  border-radius: 8rpx;
+  overflow: hidden;
+}
+.cart-group .goods-wrap:first-of-type {
+  margin-top: 0;
+}
+.cart-group .cart-store {
+  height: 96rpx;
+  background-color: #fff;
+  box-sizing: border-box;
+  display: flex;
+  align-items: center;
+  padding: 0rpx 24rpx 0rpx 36rpx;
+}
+.cart-group .cart-store .cart-store__check {
+  padding: 28rpx 32rpx 28rpx 0rpx;
+}
+.cart-group .cart-store__content {
+  box-sizing: border-box;
+  flex: auto;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+.cart-group .cart-store__content .store-title {
+  flex: auto;
+  font-size: 28rpx;
+  line-height: 40rpx;
+  color: #333333;
+  display: flex;
+  align-items: center;
+  font-weight: bold;
+  overflow: hidden;
+}
+
+.cart-group .cart-store__content .store-title .wr-store {
+  font-size: 32rpx;
+}
+.cart-group .cart-store__content .store-title .store-name {
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  margin-left: 12rpx;
+}
+.cart-group .cart-store__content .get-coupon {
+  width: 112rpx;
+  height: 40rpx;
+  border-radius: 20rpx;
+  background-color: #ffecf9;
+  line-height: 40rpx;
+  text-align: center;
+  font-size: 26rpx;
+  color: #fa4126;
+}
+
+.cart-group .promotion-wrap {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0rpx 24rpx 32rpx 36rpx;
+  background-color: #ffffff;
+  font-size: 24rpx;
+  line-height: 36rpx;
+  color: #222427;
+}
+.cart-group .promotion-wrap .promotion-title {
+  font-weight: bold;
+  flex: auto;
+  overflow: hidden;
+  margin-right: 20rpx;
+  display: flex;
+  align-items: center;
+}
+.cart-group .promotion-wrap .promotion-title .promotion-icon {
+  flex: none;
+  font-weight: normal;
+  display: inline-block;
+  padding: 0 8rpx;
+  color: #ffffff;
+  background: #fa4126;
+  font-size: 20rpx;
+  height: 32rpx;
+  line-height: 32rpx;
+  margin-right: 16rpx;
+  border-radius: 16rpx;
+}
+.cart-group .promotion-wrap .promotion-title .promotion-text {
+  flex: auto;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.cart-group .promotion-wrap .promotion-action {
+  flex: none;
+  color: #333333;
+}
+.cart-group .promotion-line-wrap {
+  background-color: #fff;
+  height: 2rpx;
+  display: flex;
+  justify-content: flex-end;
+}
+.cart-group .promotion-line-wrap .promotion-line {
+  width: 684rpx;
+  height: 2rpx;
+  background-color: #e6e6e6;
+}
+.cart-group .goods-item-info {
+  display: flex;
+  background-color: #fff;
+  align-items: flex-start;
+}
+.cart-group .goods-item-info .check-wrap {
+  margin-top: 56rpx;
+  padding: 20rpx 28rpx 20rpx 36rpx;
+}
+
+.cart-group .goods-item-info .check-wrap .unCheck-icon {
+  box-sizing: border-box;
+  width: 36rpx;
+  height: 36rpx;
+  border-radius: 20rpx;
+  background: #f5f5f5;
+  border: 2rpx solid #bbbbbb;
+}
+
+.cart-group .goods-item-info .goods-sku-info {
+  padding: 0rpx 32rpx 40rpx 0;
+  flex-grow: 1;
+}
+.cart-group .goods-item-info .goods-sku-info .stock-mask {
+  position: absolute;
+  color: #fff;
+  font-size: 24rpx;
+  bottom: 0rpx;
+  background-color: rgba(0, 0, 0, 0.5);
+  width: 100%;
+  height: 40rpx;
+  line-height: 40rpx;
+  text-align: center;
+}
+.cart-group .goods-item-info .goods-sku-info .goods-stepper {
+  position: absolute;
+  right: 0;
+  bottom: 8rpx;
+}
+.cart-group .goods-item-info .goods-sku-info .goods-stepper .stepper-tip {
+  position: absolute;
+  top: -36rpx;
+  right: 0;
+  height: 28rpx;
+  color: #ff2525;
+  font-size: 20rpx;
+  line-height: 28rpx;
+}
+
+.cart-group .shortage-line {
+  width: 662rpx;
+  height: 2rpx;
+  background-color: #e6e6e6;
+  margin: 0 auto;
+}
+.cart-group .shortage-goods-wrap {
+  background-color: #fff;
+}
+.cart-group .shortage-goods-wrap .shortage-tip-title {
+  height: 72rpx;
+  line-height: 72rpx;
+  padding-left: 28rpx;
+  font-size: 24rpx;
+  color: #999;
+}
+.stepper-info {
+  margin-left: auto;
+}
+.invalid-goods-wrap {
+  background-color: #fff;
+  border-radius: 8rpx;
+  margin-top: 40rpx;
+}
+.invalid-goods-wrap .invalid-head {
+  display: flex;
+  justify-content: space-between;
+  padding: 30rpx 20rpx;
+  font-size: 24rpx;
+  border-bottom: 2rpx solid #f6f6f6;
+}
+.invalid-goods-wrap .invalid-head .invalid-title {
+  color: #333;
+  font-size: 28rpx;
+  font-weight: 600;
+}
+.invalid-goods-wrap .invalid-head .invalid-clear {
+  color: #fa4126;
+}
+.invalid-goods-wrap .toggle {
+  display: flex;
+  height: 80rpx;
+  justify-content: center;
+  align-items: center;
+  font-size: 24rpx;
+  color: #fa4126;
+}
+.invalid-goods-wrap .toggle .m-r-6 {
+  margin-right: 6rpx;
+}
+.invalid-goods-wrap .toggle .top-icon {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  border-left: 10rpx solid transparent;
+  border-right: 10rpx solid transparent;
+  border-bottom: 10rpx solid #fa4126;
+}
+.invalid-goods-wrap .toggle .down-icon {
+  display: inline-block;
+  width: 0;
+  height: 0;
+  border-left: 10rpx solid transparent;
+  border-right: 10rpx solid transparent;
+  border-top: 10rpx solid #fa4126;
+}
+.action-btn {
+  display: flex;
+  align-items: center;
+}
+.action-btn .action-btn-arrow {
+  font-size: 20rpx;
+  margin-left: 8rpx;
+}
+.action-btn--active {
+  opacity: 0.5;
+}
+
+.swiper-right-del {
+  height: calc(100% - 40rpx);
+  width: 144rpx;
+  background-color: #fa4126;
+  font-size: 28rpx;
+  color: white;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+.goods-stepper .stepper {
+  border: none;
+  border-radius: 0;
+  height: auto;
+  width: 168rpx;
+  overflow: visible;
+}
+.goods-stepper .stepper .stepper__minus,
+.goods-stepper .stepper .stepper__plus {
+  width: 44rpx;
+  height: 44rpx;
+  background-color: #f5f5f5;
+}
+.goods-stepper .stepper .stepper__minus--hover,
+.goods-stepper .stepper .stepper__plus--hover {
+  background-color: #f5f5f5;
+}
+.goods-stepper .stepper .stepper__minus .wr-icon,
+.goods-stepper .stepper .stepper__plus .wr-icon {
+  font-size: 24rpx;
+}
+.goods-stepper .stepper .stepper__minus {
+  position: relative;
+}
+.goods-stepper .stepper .stepper__minus::after {
+  position: absolute;
+  display: block;
+  content: ' ';
+  left: -20rpx;
+  right: -5rpx;
+  top: -20rpx;
+  bottom: -20rpx;
+  background-color: transparent;
+}
+.goods-stepper .stepper .stepper__plus {
+  position: relative;
+}
+.goods-stepper .stepper .stepper__plus::after {
+  position: absolute;
+  display: block;
+  content: ' ';
+  left: -5rpx;
+  right: -20rpx;
+  top: -20rpx;
+  bottom: -20rpx;
+  background-color: transparent;
+}
+.goods-stepper .stepper .stepper__input {
+  width: 72rpx;
+  height: 44rpx;
+  background-color: #f5f5f5;
+  font-size: 24rpx;
+  color: #222427;
+  font-weight: 600;
+  border-left: none;
+  border-right: none;
+  min-height: 40rpx;
+  margin: 0 4rpx;
+  display: flex;
+  align-items: center;
+}
+
+.goods-sku-info .no-storage-mask {
+  position: absolute;
+  color: #fff;
+  bottom: 0rpx;
+  left: 0rpx;
+  background-color: rgba(0, 0, 0, 0.1);
+  height: 192rpx;
+  width: 192rpx;
+  border-radius: 8rpx;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.no-storage-mask .no-storage-content {
+  width: 128rpx;
+  height: 128rpx;
+  border-radius: 64rpx;
+  background-color: rgba(0, 0, 0, 0.4);
+  text-align: center;
+  line-height: 128rpx;
+  font-size: 28rpx;
+}

+ 20 - 0
tcb-shop-demo/pages/cart/components/cart-group/utils.wxs

@@ -0,0 +1,20 @@
+module.exports.slice = function(arr) {
+  return arr.slice(0, 2);
+};
+module.exports.imgCut = function(url, width, height) {
+  if (url && (url.slice(0, 5) === 'http:' || url.slice(0, 6) === 'https:' || url.slice(0, 2) === '//')) {
+    var argsStr = 'imageMogr2/thumbnail/!' + width +  'x' + height + 'r';
+    if (url.indexOf('?') > -1) {
+      url = url + '&' + argsStr;
+    } else {
+      url = url + '?' + argsStr;
+    }
+    if (url.slice(0, 5) === 'http:') {
+      url = 'https://' + url.slice(5)
+    }
+    if (url.slice(0, 2) === '//') {
+      url = 'https:' + url
+    }
+  }
+  return url;
+};

+ 232 - 0
tcb-shop-demo/pages/cart/components/goods-card/index.js

@@ -0,0 +1,232 @@
+Component({
+  options: {
+    multipleSlots: true, // 在组件定义时的选项中启用多slot支持
+    addGlobalClass: true,
+  },
+  intersectionObserverContext: null,
+
+  externalClasses: [
+    'card-class',
+    'title-class',
+    'desc-class',
+    'num-class',
+    'thumb-class',
+    'specs-class',
+    'price-class',
+    'origin-price-class',
+    'price-prefix-class',
+  ],
+
+  properties: {
+    hidden: {
+      // 设置为null代表不做类型转换
+      type: null,
+      value: false,
+      observer(hidden) {
+        // null就是代表没有设置,没有设置的话不setData,防止祖先组件触发的setHidden操作被覆盖
+        if (hidden !== null) {
+          this.setHidden(!!hidden);
+        }
+      },
+    },
+    id: {
+      type: String,
+      // `goods-card-88888888`
+      // 不能在这里写生成逻辑,如果在这里写,那么假设有多个goods-list时,他们将共享这个值
+      value: '',
+      observer: (id) => {
+        this.genIndependentID(id);
+        if (this.properties.thresholds?.length) {
+          this.createIntersectionObserverHandle();
+        }
+      },
+    },
+    data: {
+      type: Object,
+      observer(goods) {
+        // 有ID的商品才渲染
+        if (!goods?.sku) {
+          return;
+        }
+
+        /** 划线价是否有效 */
+        let isValidityLinePrice = true;
+        // 判断一次划线价格是否合理
+        if (goods.originPrice && goods.price && goods.originPrice < goods.price) {
+          isValidityLinePrice = false;
+        }
+
+        // 敲定换行数量默认值
+        if (goods.lineClamp === undefined || goods.lineClamp <= 0) {
+          // tag数组长度 大于0 且 可见
+          // 指定换行为1行
+          if ((goods.sku.attr_value.length || 0) > 0) {
+            goods.lineClamp = 1;
+          } else {
+            goods.lineClamp = 2;
+          }
+        }
+
+        this.setData({ goods, isValidityLinePrice, attr_str: goods.sku.attr_value.map((x) => x.value).join(',') });
+      },
+    },
+    layout: {
+      type: String,
+      value: 'horizontal',
+    },
+    thumbMode: {
+      type: String,
+      value: 'aspectFill',
+    },
+    priceFill: {
+      type: Boolean,
+      value: true,
+    },
+    currency: {
+      type: String,
+      value: '¥',
+    },
+    lazyLoad: {
+      type: Boolean,
+      value: false,
+    },
+    centered: {
+      type: Boolean,
+      value: false,
+    },
+    pricePrefix: {
+      type: String,
+      value: '',
+    },
+    /** 元素可见监控阈值, 数组长度大于0就创建 */
+    thresholds: {
+      type: Array,
+      value: [],
+      observer(current) {
+        if (current && current.length) {
+          this.createIntersectionObserverHandle();
+        } else {
+          this.clearIntersectionObserverHandle();
+        }
+      },
+    },
+    specsIconClassPrefix: {
+      type: String,
+      value: 'wr',
+    },
+    specsIcon: {
+      type: String,
+      value: 'expand_more',
+    },
+    addCartIconClassPrefix: {
+      type: String,
+      value: 'wr',
+    },
+    addCartIcon: {
+      type: String,
+      value: 'cart',
+    },
+  },
+
+  data: {
+    hiddenInData: false,
+    independentID: '',
+    goods: { id: '' },
+    /** 保证划线价格不小于原价,否则不渲染划线价 */
+    isValidityLinePrice: false,
+  },
+
+  lifetimes: {
+    ready() {
+      this.init();
+    },
+    detached() {
+      this.clear();
+    },
+  },
+
+  methods: {
+    clickHandle() {
+      this.triggerEvent('click', { goods: this.data.goods });
+    },
+    clickThumbHandle() {
+      this.triggerEvent('thumb', { goods: this.data.goods });
+    },
+    clickSpecsHandle() {
+      this.triggerEvent('specs', { goods: this.data.goods });
+    },
+    // 加入购物车
+    addCartHandle(e) {
+      const { id } = e.currentTarget;
+      const { id: cardID } = e.currentTarget.dataset;
+      this.triggerEvent('add-cart', {
+        ...e.detail,
+        id,
+        cardID,
+        goods: this.data.goods,
+      });
+    },
+    genIndependentID(id, cb) {
+      let independentID;
+      if (id) {
+        independentID = id;
+      } else {
+        independentID = `goods-card-${~~(Math.random() * 10 ** 8)}`;
+      }
+      this.setData({ independentID }, cb);
+    },
+
+    init() {
+      const { thresholds, id, hidden } = this.properties;
+      if (hidden !== null) {
+        this.setHidden(!!hidden);
+      }
+
+      this.genIndependentID(id || '', () => {
+        if (thresholds && thresholds.length) {
+          this.createIntersectionObserverHandle();
+        }
+      });
+    },
+
+    clear() {
+      this.clearIntersectionObserverHandle();
+    },
+
+    setHidden(hidden) {
+      this.setData({ hiddenInData: !!hidden });
+    },
+
+    createIntersectionObserverHandle() {
+      if (this.intersectionObserverContext || !this.data.independentID) {
+        return;
+      }
+
+      this.intersectionObserverContext = wx
+        .createIntersectionObserver(this, {
+          thresholds: this.properties.thresholds,
+        })
+        .relativeToViewport();
+
+      this.intersectionObserverContext.observe(`#${this.data.independentID}`, (res) => {
+        this.intersectionObserverCB(res);
+      });
+    },
+    intersectionObserverCB(ob) {
+      this.triggerEvent('ob', {
+        goods: this.data.goods,
+        context: this.intersectionObserverContext,
+        ob,
+      });
+    },
+    clearIntersectionObserverHandle() {
+      if (this.intersectionObserverContext) {
+        try {
+          this.intersectionObserverContext.disconnect();
+        } catch (e) {}
+
+        this.intersectionObserverContext = null;
+      }
+    },
+  },
+});

+ 9 - 0
tcb-shop-demo/pages/cart/components/goods-card/index.json

@@ -0,0 +1,9 @@
+{
+  "component": true,
+  "usingComponents": {
+    "price": "/components/price/index",
+    "t-tag": "tdesign-miniprogram/tag/tag",
+    "t-image": "/components/webp-image/index",
+    "t-icon": "tdesign-miniprogram/icon/icon"
+  }
+}

Một số tệp đã không được hiển thị bởi vì quá nhiều tập tin thay đổi trong này khác