diff --git a/miniprogram/tcb-shop/.eslintrc.js b/miniprogram/tcb-shop/.eslintrc.js
new file mode 100644
index 0000000..21d54f4
--- /dev/null
+++ b/miniprogram/tcb-shop/.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'],
+ },
+};
diff --git a/miniprogram/tcb-shop/.gitignore b/miniprogram/tcb-shop/.gitignore
new file mode 100644
index 0000000..68a200f
--- /dev/null
+++ b/miniprogram/tcb-shop/.gitignore
@@ -0,0 +1,16 @@
+node_modules/
+yarn-error.log
+miniprogram/
+miniprogram_npm/
+miniprogram_dist/
+.DS_Store
+$node_modules/
+.history/
+**/dist
+components/**/*.lock
+components/**/package-lock.json
+package-lock.json
+yarn.lock
+project.private.config.json
+.eslintcache
+cloudfunctions/
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/.npmrc b/miniprogram/tcb-shop/.npmrc
new file mode 100644
index 0000000..21b91a6
--- /dev/null
+++ b/miniprogram/tcb-shop/.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/
diff --git a/miniprogram/tcb-shop/.prettierignore b/miniprogram/tcb-shop/.prettierignore
new file mode 100644
index 0000000..9ff2448
--- /dev/null
+++ b/miniprogram/tcb-shop/.prettierignore
@@ -0,0 +1,3 @@
+miniprogram_npm
+package.json
+project.config.json
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/.prettierrc.yml b/miniprogram/tcb-shop/.prettierrc.yml
new file mode 100644
index 0000000..a13251c
--- /dev/null
+++ b/miniprogram/tcb-shop/.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
diff --git a/miniprogram/tcb-shop/LICENSE b/miniprogram/tcb-shop/LICENSE
new file mode 100644
index 0000000..789cbde
--- /dev/null
+++ b/miniprogram/tcb-shop/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.
diff --git a/miniprogram/tcb-shop/README.md b/miniprogram/tcb-shop/README.md
new file mode 100644
index 0000000..bcfae25
--- /dev/null
+++ b/miniprogram/tcb-shop/README.md
@@ -0,0 +1,302 @@
+
+
+
+
+
+
云开发电商模板
+
+
+ 一键创建零售商城
+
+
+
+
+## 说明
+
+云开发电商模板提供了一键创建零售商城小程序的能力。
+
+## 快速上手
+
+1. 安装本模版
+2. 导入云开发电商模板小程序
+
+![](https://qcloudimg.tencent-cloud.cn/raw/5f1962e709c28af3252c0acb583e989b.png)
+
+3. 填入开通了云开发环境的小程序的 appId
+
+![](https://qcloudimg.tencent-cloud.cn/raw/800f05945779cb940ef6851d96419f6e.png)
+
+4. 在 `app.js` 中,填入云开发环境 id
+
+![](https://qcloudimg.tencent-cloud.cn/raw/1da510c60d9d552119ed7aa79c7a6826.png)
+
+5. 前往[小程序微信支付模版](https://tcb.cloud.tencent.com/cloud-admin#/cloud-template/detail?appName=wx-pay-v2&solutionId=solution-1sbaF7cyIqcgRj&appType=basic),填入该模版所需参数
+
+![]( https://qcloudimg.tencent-cloud.cn/raw/9a11a564b8985883194f19cdd11c3f2b.png)
+
+6. 安装依赖
+ 6.1 在命令行执行 `npm install`
+ 6.2 点击菜单栏中的「工具 -> 构建 npm」
+ 6.3. 详情可参考[npm 支持 | 微信开放文档](https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html#%E4%BD%BF%E7%94%A8-npm-%E5%8C%85)
+7. 编译启动小程序,开始体验
+8. 正式发布前,移除云开发引导弹窗
+ 8.1 删除 `components/cloud-template-dialog/` 文件夹
+
+ ![]( https://qcloudimg.tencent-cloud.cn/raw/fcc15824e38fbfacc1afee040c648099.png)
+
+ 8.2 删除 `pages/home/home.wxml` 中对云开发引导弹窗的引用
+
+ ![]( https://qcloudimg.tencent-cloud.cn/raw/cb2a20e0f840eaee236a964fac734f28.png)
+
+ 8.3 删除 `pages/home/home.json` 中对云开发引导弹窗的引用
+
+ ![](https://qcloudimg.tencent-cloud.cn/raw/7c0c32bbb494bc3b0877891d302c096c.png)
+
+## 页面介绍
+
+### 首页
+
+![](https://qcloudimg.tencent-cloud.cn/raw/89cf8dedbd3edfdbda31920447bddd51.png)
+
+首页由三部分组成:
+
+● 搜索栏
+
+● 轮播图
+
+● 商品列表
+
+#### 搜索栏
+
+点击搜索栏,可以输入搜索商品,并展示搜索到的商品列表:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/94e2ee9666aed0f8147affa180c879c7.png)
+
+#### 轮播图
+
+轮播图可循环展示配置好的图片。
+
+![](https://qcloudimg.tencent-cloud.cn/raw/b42bc12d0fa4fe350b83154ace0a613c.png)
+
+#### 商品列表
+
+首页商品列表会列出所有的商品,点击单个商品后,会跳转至对应的商品详情页。
+
+首页商品列表:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/98fca5271899639e1393753e52523fce.png)
+
+点击商品后跳转商品详情页:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/54c50176b1af344b23fb61e22e611265.png)
+
+### 分类页
+
+分类页会以侧边栏展示商品的一级分类,主内容展示商品的二级分类。
+
+![](https://qcloudimg.tencent-cloud.cn/raw/1ff658bb467a4e8febf34be13ecf80da.png)
+
+点击二级分类后,会展示当前分类下的商品列表。同样,在此商品列表点击商品后,会跳转至对应商品详情页。
+
+水果分类下的商品列表:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/927b12f16467f1def96e877c50b1a7e8.png)
+
+
+### 商品详情页
+
+![](https://qcloudimg.tencent-cloud.cn/raw/ef651d54d5e57d2aacaa50bc283ccfb5.png)
+
+商品详情页由多个部分组成:
+
+● 商品轮播图
+
+● 商品信息
+
+● 规格选择栏
+
+● 评价预览
+
+● 商品详情
+
+● 底部动作栏
+
+#### 商品轮播图
+
+商品轮播图可循环展示该商品的多个图片:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/5b59639efcc112ce592b72f6cd77fcb8.png)
+
+#### 商品信息
+
+商品信息会展示商品的名称与最低价格:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/5d23000c19ccb652ce9eab0afd1fd2e7.png)
+
+#### 规格选择栏
+
+点击规格选择栏后,可在跳出的弹窗中选择商品对应的规格,并选择购买数量。确定商品规格和购买数量后,可加入购物车或选择立即购买:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/f7aecbc84c8d691a8e6c97070b4af71b.png)
+
+#### 评价预览
+
+当商品存在评价时,会展示评价预览,包括评论数、好评率,以及一条评价:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/dd5e089f0be8b10dd900d65566bb2778.png)
+
+#### 商品详情
+
+商品详情用于展示商品的详细信息,这部分的内容以富文本的形式呈现:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/81271f5c219fb5c475493af8e8751d99.png)
+
+#### 底部动作栏
+
+底部动作栏提供了四个按钮,包括首页和购物车页的跳转按钮,点击后会跳转至相应页面;还有加入购物车与立即购买按钮,点击后能唤出规格选择弹窗,选择完商品后就能够执行对应的操作:
+
+底部动作栏:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/862684258b666755e5ea1dae45bdecfe.png)
+
+点击后唤出规格选择弹窗:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/de44ca5df5e297415d1863eb6cac7c1e.png)
+
+### 购物车页
+
+![](https://qcloudimg.tencent-cloud.cn/raw/eec6aa7cbdf1aefae293a68e867f8501.png)
+
+购物车页可以查看购物车项。对于每个购物车项,可以进行删除、修改数量的操作。选中购物车项后,可以点击「去结算」按钮前往订单确认页进行订单创建并购买。
+
+左滑购物车项进行删除:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/21311abac70b47bc352734e4e6da0485.png)
+
+选中购物车项后可进行结算:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/a2af456542cd7afae411cffaaac9d501.png)
+
+点击「去结算」按钮后跳转至订单确认页:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/c5e01f1c2cb4ef6eae6bd0fce0574a95.png)
+
+### 订单确认页
+
+![](https://qcloudimg.tencent-cloud.cn/raw/8789c377b15e0a222475b5253cd45c0a.png)
+
+订单确认页提供以下功能:
+
+● 地址选择栏
+
+● 订单信息展示
+
+● 提交订单并支付
+
+#### 地址选择栏
+
+地址选择栏展示当前订单的配送地址。如果未选择地址,将无法提交订单。点击地址选择栏后可跳转至地址列表,对地址进行增加、删除、修改和选择的操作。选择完地址后,会返回至此页,届时可继续进行订单提交。
+
+地址选择栏:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/febf20b5ae98eacd2b69a93ecbba684e.png)
+
+地址列表:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/c4d74f2749a9029c7a731071165fdd9f.png)
+
+选择完地址后,跳回订单确认页:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/4faedcef881787e25926e69f50ec18cd.png)
+
+#### 订单信息展示
+
+在订单确认页会展示当前创建的订单的详细内容,包括订单项、总价等信息。
+
+![](https://qcloudimg.tencent-cloud.cn/raw/268fe7c66225fe45d8ace3fd933e6b31.png)
+
+#### 提交订单并支付
+
+在选择完地址后,点击提交订单,即可创建订单,并弹出支付弹窗。若支付流程失败,可在订单列表中查询到待支付的订单,重新支付。
+
+![](https://qcloudimg.tencent-cloud.cn/raw/e81466d3fe35a15a1add1467e3b9114a.png)
+
+### 用户中心页
+
+用户中心页展示用户信息、订单列表和地址列表。
+
+![](https://qcloudimg.tencent-cloud.cn/raw/f7e8527c81b88c69bb800f3afbaa9e62.png)
+
+## 数据模型
+
+云开发电商模板附带了一系列的数据模型。对于数据模型的能力说明,请查看[云开发数据模型](https://docs.cloudbase.net/model/introduce)。
+
+本模版附带的数据模型有:
+
+|名称|标识|描述|
+|---|---|---|
+|电商SPU|shop_spu|一件商品,对应电商概念中的 SPU,即 Standard Product Unit。|
+|电商SPU分类|shop_spu_cate|商品分类,每个商品分类都可以有自己的子分类、父分类和对应的商品。|
+|电商SPU属性名|shop_attr_name|商品的属性名,如数量。|
+|电商SPU属性值|shop_attr_value|商品的属性值,如数量对应的值可能为「1 件」、「10 件」。|
+|电商SPU评价|shop_comment|用户对商品做出的评价。|
+|电商SKU|shop_sku|一件有具体属性的商品销售单位。每个 SPU 都可以对应多个 SKU。举例来说,A 品牌的平板电脑为 SPU,其对应的 SKU 为 A 品牌的 16GB 内存、白色的平板电脑。|
+|电商购物车项|shop_cart_item||
+|电商订单|shop_order||
+|电商订单项|shop_order_item||
+|电商收货信息|shop_delivery_info||
+|电商首页轮播图|shop_home_swiper_image||
+
+## 应用配置说明
+
+### 配置商品
+
+#### 配置SPU
+
+在「电商SPU」数据模型中,填入 SPU 数据。这里我们添加一行「荔枝」的 SPU 数据:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/adda72dbcf9f96d41236c128cb11522e.png)
+
+#### 配置SKU
+
+添加完 SPU 后,我们还需要为其添加 SKU 数据。SKU 可以理解为具有具体规格/属性的 SPU,比如荔枝的属性可以有数量、大小等等。不同数量、不同大小的荔枝会有不同的价格、不同的库存。
+
+首先,我们为荔枝配置属性选项。
+
+在「电商SPU属性名」数据模型中,我们添加一行「大小」属性:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/dd45aedca3c2235dce20c749faf6a890.png)
+
+添加完属性名后,我们还需要为此属性名配置属性值。
+
+我们在「电商SPU属性值」配置两个属性值,分别是大、小:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/e42f566f0622d4825da0d56acc085e25.png)
+
+![](https://qcloudimg.tencent-cloud.cn/raw/5a7b0b51e9062366f5412ef851b2d606.png)
+
+至此,我们配置好了「大小」这个属性名,这个属性名下有两个可选的属性值,分别为「大」和「小」。
+
+现在,我们可以为「荔枝」SPU 添加 SKU 了,分别是「大荔枝」和「小荔枝」,他们的价格户不相同:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/a829aae86242a37c5130a0d1e0568d8d.png)
+
+![](https://qcloudimg.tencent-cloud.cn/raw/a5a6975a715e92f3abf9296cc53a279b.png)
+
+#### 配置商品分类
+
+每个 SPU 可以有多个分类,分类下也可以有子分类。现在我们来为荔枝添加二级分类,分别是「美食」->「水果」。
+
+我们在「电商SPU分类」数据模型中添加「美食」分类:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/b511ddf9b3069097f5c95f19779392f4.png)
+
+然后再添加「水果」分类,添加时把父分类选为「美食」,并在 SPU 中添加「荔枝」:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/2a3c6522f9af616740e57a7db80ef94f.png)
+
+### 配置首页轮播图
+
+首页轮播图会展示「电商首页轮播图」数据模型的第一行数据中的图片:
+
+![](https://qcloudimg.tencent-cloud.cn/raw/73b6b4c119ea526506f683a4cf1f8c13.png)
diff --git a/miniprogram/tcb-shop/app.js b/miniprogram/tcb-shop/app.js
new file mode 100644
index 0000000..2f1800e
--- /dev/null
+++ b/miniprogram/tcb-shop/app.js
@@ -0,0 +1,18 @@
+import updateManager from './common/updateManager';
+import { init } from '@cloudbase/wx-cloud-client-sdk';
+
+wx.cloud.init({
+ env: 'your-env-id', // 指定云开发环境 ID
+});
+
+const client = init(wx.cloud);
+const models = client.models;
+globalThis.dataModel = models;
+// 接下来就可以调用 models 上的数据模型增删改查等方法了
+
+App({
+ onLaunch: function () {},
+ onShow: function () {
+ updateManager();
+ },
+});
diff --git a/miniprogram/tcb-shop/app.json b/miniprogram/tcb-shop/app.json
new file mode 100644
index 0000000..b4cea02
--- /dev/null
+++ b/miniprogram/tcb-shop/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": "你的位置信息将用于小程序位置接口的效果展示"
+ }
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/app.wxss b/miniprogram/tcb-shop/app.wxss
new file mode 100644
index 0000000..9ff2554
--- /dev/null
+++ b/miniprogram/tcb-shop/app.wxss
@@ -0,0 +1,3 @@
+@import 'style/iconfont.wxss';
+
+@import 'style/theme.wxss';
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/commitlint.config.js b/miniprogram/tcb-shop/commitlint.config.js
new file mode 100644
index 0000000..422b194
--- /dev/null
+++ b/miniprogram/tcb-shop/commitlint.config.js
@@ -0,0 +1 @@
+module.exports = { extends: ['@commitlint/config-conventional'] };
diff --git a/miniprogram/tcb-shop/common/updateManager.js b/miniprogram/tcb-shop/common/updateManager.js
new file mode 100644
index 0000000..c45de6d
--- /dev/null
+++ b/miniprogram/tcb-shop/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 () {
+ // 新版本下载失败
+ });
+};
diff --git a/miniprogram/tcb-shop/components/cloud-template-dialog/index.js b/miniprogram/tcb-shop/components/cloud-template-dialog/index.js
new file mode 100644
index 0000000..cb0422b
--- /dev/null
+++ b/miniprogram/tcb-shop/components/cloud-template-dialog/index.js
@@ -0,0 +1,41 @@
+const NEVER_SHOW_KEY = 'never_show_cloud_template_init_dialog';
+
+Component({
+ properties: {
+ show: {
+ type: Boolean,
+ value: true,
+ },
+ url: {
+ type: String,
+ value: '',
+ },
+ },
+
+ data: { checked: false },
+
+ methods: {
+ close() {
+ if (this.data.checked) {
+ wx.setStorageSync(NEVER_SHOW_KEY, 'true');
+ }
+ },
+ copy() {
+ wx.setClipboardData({
+ data: this.data.url,
+ });
+ },
+ neverShowChanged({ detail: { checked } }) {
+ this.setData({ checked });
+ },
+ },
+
+ lifetimes: {
+ attached: function () {
+ const ifNever = wx.getStorageSync(NEVER_SHOW_KEY);
+ this.setData({
+ show: ifNever !== 'true',
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/components/cloud-template-dialog/index.json b/miniprogram/tcb-shop/components/cloud-template-dialog/index.json
new file mode 100644
index 0000000..677ffc2
--- /dev/null
+++ b/miniprogram/tcb-shop/components/cloud-template-dialog/index.json
@@ -0,0 +1,9 @@
+{
+ "component": true,
+ "styleIsolation": "shared",
+ "usingComponents": {
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-radio": "tdesign-miniprogram/radio/radio"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/cloud-template-dialog/index.wxml b/miniprogram/tcb-shop/components/cloud-template-dialog/index.wxml
new file mode 100644
index 0000000..9e4a11e
--- /dev/null
+++ b/miniprogram/tcb-shop/components/cloud-template-dialog/index.wxml
@@ -0,0 +1,13 @@
+
+
+
+
+ {{url}}
+
+
+
+ 复制
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/cloud-template-dialog/index.wxss b/miniprogram/tcb-shop/components/cloud-template-dialog/index.wxss
new file mode 100644
index 0000000..9089da6
--- /dev/null
+++ b/miniprogram/tcb-shop/components/cloud-template-dialog/index.wxss
@@ -0,0 +1,27 @@
+.url-container text {
+ word-break: break-all;
+}
+
+.btns {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 10px;
+}
+
+.template-btn {
+ flex-grow: 1;
+}
+
+.template-dialog .t-dialog {
+ margin-bottom: 20px;
+}
+
+
+.template-dialog .t-dialog__footer {
+ display: none;
+}
+
+.template-dialog .t-radio--block {
+ padding-left: 0;
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/filter-popup/index.js b/miniprogram/tcb-shop/components/filter-popup/index.js
new file mode 100644
index 0000000..14c69fd
--- /dev/null
+++ b/miniprogram/tcb-shop/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 });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/components/filter-popup/index.json b/miniprogram/tcb-shop/components/filter-popup/index.json
new file mode 100644
index 0000000..f5d3702
--- /dev/null
+++ b/miniprogram/tcb-shop/components/filter-popup/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-popup": "tdesign-miniprogram/popup/popup"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/filter-popup/index.wxml b/miniprogram/tcb-shop/components/filter-popup/index.wxml
new file mode 100644
index 0000000..2932624
--- /dev/null
+++ b/miniprogram/tcb-shop/components/filter-popup/index.wxml
@@ -0,0 +1,18 @@
+
+
+
+
+ 重置
+
+ 确定
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/components/filter-popup/index.wxss b/miniprogram/tcb-shop/components/filter-popup/index.wxss
new file mode 100644
index 0000000..a206a82
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/components/filter/index.js b/miniprogram/tcb-shop/components/filter/index.js
new file mode 100644
index 0000000..dc99b95
--- /dev/null
+++ b/miniprogram/tcb-shop/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,
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/components/filter/index.json b/miniprogram/tcb-shop/components/filter/index.json
new file mode 100644
index 0000000..7464ae6
--- /dev/null
+++ b/miniprogram/tcb-shop/components/filter/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-icon": "tdesign-miniprogram/icon/icon"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/filter/index.wxml b/miniprogram/tcb-shop/components/filter/index.wxml
new file mode 100644
index 0000000..9048bfd
--- /dev/null
+++ b/miniprogram/tcb-shop/components/filter/index.wxml
@@ -0,0 +1,37 @@
+
+
+
+
+ 综合
+
+
+ 价格
+
+
+
+
+
+
+ 筛选
+
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/components/filter/index.wxss b/miniprogram/tcb-shop/components/filter/index.wxss
new file mode 100644
index 0000000..97728f9
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/components/goods-card/index.js b/miniprogram/tcb-shop/components/goods-card/index.js
new file mode 100644
index 0000000..d1d63cf
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+ }
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/components/goods-card/index.json b/miniprogram/tcb-shop/components/goods-card/index.json
new file mode 100644
index 0000000..f199647
--- /dev/null
+++ b/miniprogram/tcb-shop/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"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/goods-card/index.wxml b/miniprogram/tcb-shop/components/goods-card/index.wxml
new file mode 100644
index 0000000..b28d772
--- /dev/null
+++ b/miniprogram/tcb-shop/components/goods-card/index.wxml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ {{ goods.name }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/goods-card/index.wxss b/miniprogram/tcb-shop/components/goods-card/index.wxss
new file mode 100644
index 0000000..6bf6879
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/goods-list/index.js b/miniprogram/tcb-shop/components/goods-list/index.js
new file mode 100644
index 0000000..5184903
--- /dev/null
+++ b/miniprogram/tcb-shop/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)}`,
+ });
+ }
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/components/goods-list/index.json b/miniprogram/tcb-shop/components/goods-list/index.json
new file mode 100644
index 0000000..bdaa23d
--- /dev/null
+++ b/miniprogram/tcb-shop/components/goods-list/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "goods-card": "/components/goods-card/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/goods-list/index.wxml b/miniprogram/tcb-shop/components/goods-list/index.wxml
new file mode 100644
index 0000000..5a9a804
--- /dev/null
+++ b/miniprogram/tcb-shop/components/goods-list/index.wxml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/components/goods-list/index.wxss b/miniprogram/tcb-shop/components/goods-list/index.wxss
new file mode 100644
index 0000000..7262a4d
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/components/load-more/index.js b/miniprogram/tcb-shop/components/load-more/index.js
new file mode 100644
index 0000000..b71a839
--- /dev/null
+++ b/miniprogram/tcb-shop/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');
+ }
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/components/load-more/index.json b/miniprogram/tcb-shop/components/load-more/index.json
new file mode 100644
index 0000000..94e107c
--- /dev/null
+++ b/miniprogram/tcb-shop/components/load-more/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-loading": "tdesign-miniprogram/loading/loading",
+ "t-divider": "tdesign-miniprogram/divider/divider"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/load-more/index.wxml b/miniprogram/tcb-shop/components/load-more/index.wxml
new file mode 100644
index 0000000..4b69c86
--- /dev/null
+++ b/miniprogram/tcb-shop/components/load-more/index.wxml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
+ {{noMoreText}}
+
+
+
+
+ 加载失败
+ 刷新
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/components/load-more/index.wxss b/miniprogram/tcb-shop/components/load-more/index.wxss
new file mode 100644
index 0000000..bbaf636
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/components/loading-content/index.js b/miniprogram/tcb-shop/components/loading-content/index.js
new file mode 100644
index 0000000..4c6b925
--- /dev/null
+++ b/miniprogram/tcb-shop/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)',
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/components/loading-content/index.json b/miniprogram/tcb-shop/components/loading-content/index.json
new file mode 100644
index 0000000..c08b2ef
--- /dev/null
+++ b/miniprogram/tcb-shop/components/loading-content/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-loading": "tdesign-miniprogram/loading/loading"
+ }
+}
diff --git a/miniprogram/tcb-shop/components/loading-content/index.wxml b/miniprogram/tcb-shop/components/loading-content/index.wxml
new file mode 100644
index 0000000..add589c
--- /dev/null
+++ b/miniprogram/tcb-shop/components/loading-content/index.wxml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/components/loading-content/index.wxss b/miniprogram/tcb-shop/components/loading-content/index.wxss
new file mode 100644
index 0000000..d3c112b
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/components/loading-dialog/index.js b/miniprogram/tcb-shop/components/loading-dialog/index.js
new file mode 100644
index 0000000..7c4a2b9
--- /dev/null
+++ b/miniprogram/tcb-shop/components/loading-dialog/index.js
@@ -0,0 +1,8 @@
+Component({
+ properties: {
+ show: {
+ type: Boolean,
+ value: false,
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/components/loading-dialog/index.json b/miniprogram/tcb-shop/components/loading-dialog/index.json
new file mode 100644
index 0000000..95170eb
--- /dev/null
+++ b/miniprogram/tcb-shop/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"
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/loading-dialog/index.wxml b/miniprogram/tcb-shop/components/loading-dialog/index.wxml
new file mode 100644
index 0000000..44fd109
--- /dev/null
+++ b/miniprogram/tcb-shop/components/loading-dialog/index.wxml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/loading-dialog/index.wxss b/miniprogram/tcb-shop/components/loading-dialog/index.wxss
new file mode 100644
index 0000000..9748562
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/price/index.js b/miniprogram/tcb-shop/components/price/index.js
new file mode 100644
index 0000000..c0218f7
--- /dev/null
+++ b/miniprogram/tcb-shop/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 });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/components/price/index.json b/miniprogram/tcb-shop/components/price/index.json
new file mode 100644
index 0000000..a89ef4d
--- /dev/null
+++ b/miniprogram/tcb-shop/components/price/index.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+}
diff --git a/miniprogram/tcb-shop/components/price/index.wxml b/miniprogram/tcb-shop/components/price/index.wxml
new file mode 100644
index 0000000..7f4f9d9
--- /dev/null
+++ b/miniprogram/tcb-shop/components/price/index.wxml
@@ -0,0 +1,21 @@
+
+ var REGEXP = getRegExp('^\d+(\.\d+)?$');
+ function addUnit(value) {
+ if (value == null) {
+ return '';
+ }
+ return REGEXP.test('' + value) ? value + 'rpx' : value;
+ }
+ module.exports = {
+ addUnit: addUnit
+ };
+
+
+
+ {{symbol}}
+
+ {{pArr[0]}}
+ .{{pArr[1]}}
+
+
+
diff --git a/miniprogram/tcb-shop/components/price/index.wxss b/miniprogram/tcb-shop/components/price/index.wxss
new file mode 100644
index 0000000..45da266
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/components/swipeout/index.js b/miniprogram/tcb-shop/components/swipeout/index.js
new file mode 100644
index 0000000..3db6b79
--- /dev/null
+++ b/miniprogram/tcb-shop/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();
+ }
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/components/swipeout/index.json b/miniprogram/tcb-shop/components/swipeout/index.json
new file mode 100644
index 0000000..a89ef4d
--- /dev/null
+++ b/miniprogram/tcb-shop/components/swipeout/index.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+}
diff --git a/miniprogram/tcb-shop/components/swipeout/index.wxml b/miniprogram/tcb-shop/components/swipeout/index.wxml
new file mode 100644
index 0000000..353bf72
--- /dev/null
+++ b/miniprogram/tcb-shop/components/swipeout/index.wxml
@@ -0,0 +1,174 @@
+
+ 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
+ };
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/components/swipeout/index.wxss b/miniprogram/tcb-shop/components/swipeout/index.wxss
new file mode 100644
index 0000000..1aa2943
--- /dev/null
+++ b/miniprogram/tcb-shop/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);
+}
diff --git a/miniprogram/tcb-shop/components/webp-image/index.js b/miniprogram/tcb-shop/components/webp-image/index.js
new file mode 100644
index 0000000..1706049
--- /dev/null
+++ b/miniprogram/tcb-shop/components/webp-image/index.js
@@ -0,0 +1,86 @@
+/*
+ * @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
+ */
+const systemInfo = wx.getSystemInfoSync();
+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);
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/components/webp-image/index.json b/miniprogram/tcb-shop/components/webp-image/index.json
new file mode 100644
index 0000000..6ffda42
--- /dev/null
+++ b/miniprogram/tcb-shop/components/webp-image/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-image": "tdesign-miniprogram/image/image"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/components/webp-image/index.wxml b/miniprogram/tcb-shop/components/webp-image/index.wxml
new file mode 100644
index 0000000..1a917b2
--- /dev/null
+++ b/miniprogram/tcb-shop/components/webp-image/index.wxml
@@ -0,0 +1,14 @@
+
+
diff --git a/miniprogram/tcb-shop/components/webp-image/index.wxss b/miniprogram/tcb-shop/components/webp-image/index.wxss
new file mode 100644
index 0000000..e69de29
diff --git a/miniprogram/tcb-shop/components/webp-image/utils.wxs b/miniprogram/tcb-shop/components/webp-image/utils.wxs
new file mode 100644
index 0000000..4f6e5d1
--- /dev/null
+++ b/miniprogram/tcb-shop/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,
+};
diff --git a/miniprogram/tcb-shop/config/eslintCheck.js b/miniprogram/tcb-shop/config/eslintCheck.js
new file mode 100644
index 0000000..9294baf
--- /dev/null
+++ b/miniprogram/tcb-shop/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}`);
+ }
+ },
+);
diff --git a/miniprogram/tcb-shop/config/index.js b/miniprogram/tcb-shop/config/index.js
new file mode 100644
index 0000000..26ca6ee
--- /dev/null
+++ b/miniprogram/tcb-shop/config/index.js
@@ -0,0 +1,6 @@
+export const config = {
+ /** 是否使用mock代替api返回 */
+ useMock: true,
+};
+
+export const cdnBase = 'https://we-retail-static-1300977798.cos.ap-guangzhou.myqcloud.com/retail-mp';
diff --git a/miniprogram/tcb-shop/config/model.js b/miniprogram/tcb-shop/config/model.js
new file mode 100644
index 0000000..0e7f6b4
--- /dev/null
+++ b/miniprogram/tcb-shop/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',
+};
diff --git a/miniprogram/tcb-shop/custom-tab-bar/data.js b/miniprogram/tcb-shop/custom-tab-bar/data.js
new file mode 100644
index 0000000..ff11d64
--- /dev/null
+++ b/miniprogram/tcb-shop/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',
+ },
+];
diff --git a/miniprogram/tcb-shop/custom-tab-bar/index.js b/miniprogram/tcb-shop/custom-tab-bar/index.js
new file mode 100644
index 0000000..90c58df
--- /dev/null
+++ b/miniprogram/tcb-shop/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 });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/custom-tab-bar/index.json b/miniprogram/tcb-shop/custom-tab-bar/index.json
new file mode 100644
index 0000000..c8ba0bd
--- /dev/null
+++ b/miniprogram/tcb-shop/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"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/custom-tab-bar/index.wxml b/miniprogram/tcb-shop/custom-tab-bar/index.wxml
new file mode 100644
index 0000000..384fd2f
--- /dev/null
+++ b/miniprogram/tcb-shop/custom-tab-bar/index.wxml
@@ -0,0 +1,18 @@
+
+
+
+
+ {{ item.text }}
+
+
+
+
diff --git a/miniprogram/tcb-shop/custom-tab-bar/index.wxss b/miniprogram/tcb-shop/custom-tab-bar/index.wxss
new file mode 100644
index 0000000..3b855f5
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/jsconfig.json b/miniprogram/tcb-shop/jsconfig.json
new file mode 100644
index 0000000..36aa1a4
--- /dev/null
+++ b/miniprogram/tcb-shop/jsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "baseUrl": "."
+ }
+}
diff --git a/miniprogram/tcb-shop/model/activities.js b/miniprogram/tcb-shop/model/activities.js
new file mode 100644
index 0000000..31bc3b8
--- /dev/null
+++ b/miniprogram/tcb-shop/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();
diff --git a/miniprogram/tcb-shop/model/activity.js b/miniprogram/tcb-shop/model/activity.js
new file mode 100644
index 0000000..34abb07
--- /dev/null
+++ b/miniprogram/tcb-shop/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元' }],
+ };
+}
diff --git a/miniprogram/tcb-shop/model/address.js b/miniprogram/tcb-shop/model/address.js
new file mode 100644
index 0000000..c7ae8dc
--- /dev/null
+++ b/miniprogram/tcb-shop/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));
+}
diff --git a/miniprogram/tcb-shop/model/cart.js b/miniprogram/tcb-shop/model/cart.js
new file mode 100644
index 0000000..d0ad6c5
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/model/category.js b/miniprogram/tcb-shop/model/category.js
new file mode 100644
index 0000000..81b7502
--- /dev/null
+++ b/miniprogram/tcb-shop/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',
+ },
+ ],
+ },
+ ],
+ },
+ ];
+}
diff --git a/miniprogram/tcb-shop/model/comments.js b/miniprogram/tcb-shop/model/comments.js
new file mode 100644
index 0000000..78dd61d
--- /dev/null
+++ b/miniprogram/tcb-shop/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',
+ };
+}
diff --git a/miniprogram/tcb-shop/model/comments/queryDetail.js b/miniprogram/tcb-shop/model/comments/queryDetail.js
new file mode 100644
index 0000000..3d78780
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/model/coupon.js b/miniprogram/tcb-shop/model/coupon.js
new file mode 100644
index 0000000..a41ff8b
--- /dev/null
+++ b/miniprogram/tcb-shop/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));
+}
diff --git a/miniprogram/tcb-shop/model/detailsComments.js b/miniprogram/tcb-shop/model/detailsComments.js
new file mode 100644
index 0000000..82fc887
--- /dev/null
+++ b/miniprogram/tcb-shop/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',
+ };
+}
diff --git a/miniprogram/tcb-shop/model/good.js b/miniprogram/tcb-shop/model/good.js
new file mode 100644
index 0000000..84b5ecd
--- /dev/null
+++ b/miniprogram/tcb-shop/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],
+ };
+}
diff --git a/miniprogram/tcb-shop/model/goods.js b/miniprogram/tcb-shop/model/goods.js
new file mode 100644
index 0000000..850e0ba
--- /dev/null
+++ b/miniprogram/tcb-shop/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();
diff --git a/miniprogram/tcb-shop/model/order/applyService.js b/miniprogram/tcb-shop/model/order/applyService.js
new file mode 100644
index 0000000..e3a2fdf
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/model/order/orderConfirm.js b/miniprogram/tcb-shop/model/order/orderConfirm.js
new file mode 100644
index 0000000..7170492
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/model/order/orderDetail.js b/miniprogram/tcb-shop/model/order/orderDetail.js
new file mode 100644
index 0000000..0dcd233
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/model/order/orderList.js b/miniprogram/tcb-shop/model/order/orderList.js
new file mode 100644
index 0000000..c042dec
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/model/promotion.js b/miniprogram/tcb-shop/model/promotion.js
new file mode 100644
index 0000000..0aaee61
--- /dev/null
+++ b/miniprogram/tcb-shop/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',
+ };
+}
diff --git a/miniprogram/tcb-shop/model/search.js b/miniprogram/tcb-shop/model/search.js
new file mode 100644
index 0000000..2d2e68d
--- /dev/null
+++ b/miniprogram/tcb-shop/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,
+ };
+}
diff --git a/miniprogram/tcb-shop/model/submitComment.js b/miniprogram/tcb-shop/model/submitComment.js
new file mode 100644
index 0000000..e620b4f
--- /dev/null
+++ b/miniprogram/tcb-shop/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,
+ },
+ };
+}
diff --git a/miniprogram/tcb-shop/model/swiper.js b/miniprogram/tcb-shop/model/swiper.js
new file mode 100644
index 0000000..dd5702c
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/model/usercenter.js b/miniprogram/tcb-shop/model/usercenter.js
new file mode 100644
index 0000000..d143a04
--- /dev/null
+++ b/miniprogram/tcb-shop/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,
+});
diff --git a/miniprogram/tcb-shop/package.json b/miniprogram/tcb-shop/package.json
new file mode 100644
index 0000000..b290b6b
--- /dev/null
+++ b/miniprogram/tcb-shop/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"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-bar/index.js b/miniprogram/tcb-shop/pages/cart/components/cart-bar/index.js
new file mode 100644
index 0000000..fb4f99f
--- /dev/null
+++ b/miniprogram/tcb-shop/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');
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-bar/index.json b/miniprogram/tcb-shop/pages/cart/components/cart-bar/index.json
new file mode 100644
index 0000000..c6c4351
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/cart-bar/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "price": "/components/price/index",
+ "t-icon": "tdesign-miniprogram/icon/icon"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-bar/index.wxml b/miniprogram/tcb-shop/pages/cart/components/cart-bar/index.wxml
new file mode 100644
index 0000000..5205e9c
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/cart-bar/index.wxml
@@ -0,0 +1,14 @@
+
+
+
+ 全选
+
+
+ 总计
+
+
+
+
+ 去结算({{totalGoodsNum}})
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-bar/index.wxss b/miniprogram/tcb-shop/pages/cart/components/cart-bar/index.wxss
new file mode 100644
index 0000000..0e63f60
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.js b/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.js
new file mode 100644
index 0000000..01cf9c3
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.js
@@ -0,0 +1,23 @@
+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: '去首页',
+ },
+ },
+ data: {},
+ methods: {
+ handleClick() {
+ this.triggerEvent('handleClick');
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.json b/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.json
new file mode 100644
index 0000000..b659310
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-image": "/components/webp-image/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.wxml b/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.wxml
new file mode 100644
index 0000000..d0cdd43
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.wxml
@@ -0,0 +1,6 @@
+
+
+ {{tip}}
+ {{btnText}}
+
+
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.wxss b/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.wxss
new file mode 100644
index 0000000..d074bc3
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/cart-empty/index.wxss
@@ -0,0 +1,33 @@
+.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;
+}
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-group/index.js b/miniprogram/tcb-shop/pages/cart/components/cart-group/index.js
new file mode 100644
index 0000000..5389941
--- /dev/null
+++ b/miniprogram/tcb-shop/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' });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-group/index.json b/miniprogram/tcb-shop/pages/cart/components/cart-group/index.json
new file mode 100644
index 0000000..9b8003d
--- /dev/null
+++ b/miniprogram/tcb-shop/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"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-group/index.wxml b/miniprogram/tcb-shop/pages/cart/components/cart-group/index.wxml
new file mode 100644
index 0000000..9e2102a
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/cart-group/index.wxml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 仅剩{{goods.sku.count}}件
+
+
+
+
+
+
+
+
+ 删除
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-group/index.wxs b/miniprogram/tcb-shop/pages/cart/components/cart-group/index.wxs
new file mode 100644
index 0000000..39f1e0b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/cart-group/index.wxs
@@ -0,0 +1,5 @@
+var hasPromotion = function (code) {
+ return code && code !== 'EMPTY_PROMOTION';
+};
+
+module.exports.hasPromotion = hasPromotion;
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-group/index.wxss b/miniprogram/tcb-shop/pages/cart/components/cart-group/index.wxss
new file mode 100644
index 0000000..641101f
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+}
diff --git a/miniprogram/tcb-shop/pages/cart/components/cart-group/utils.wxs b/miniprogram/tcb-shop/pages/cart/components/cart-group/utils.wxs
new file mode 100644
index 0000000..f887eba
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+};
diff --git a/miniprogram/tcb-shop/pages/cart/components/goods-card/index.js b/miniprogram/tcb-shop/pages/cart/components/goods-card/index.js
new file mode 100644
index 0000000..ccb37e4
--- /dev/null
+++ b/miniprogram/tcb-shop/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;
+ }
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/cart/components/goods-card/index.json b/miniprogram/tcb-shop/pages/cart/components/goods-card/index.json
new file mode 100644
index 0000000..d76303b
--- /dev/null
+++ b/miniprogram/tcb-shop/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"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/components/goods-card/index.wxml b/miniprogram/tcb-shop/pages/cart/components/goods-card/index.wxml
new file mode 100644
index 0000000..ff5d792
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/goods-card/index.wxml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ goods.sku.spu.name }}
+
+
+
+
+ {{ attr_str }}
+
+
+
+ 库存不足
+
+
+
+
+
+ {{ pricePrefix }}
+
+
+
+
+
+
+
+
+ 请重新选择商品规格
+ 重选
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/components/goods-card/index.wxss b/miniprogram/tcb-shop/pages/cart/components/goods-card/index.wxss
new file mode 100644
index 0000000..6a7a4da
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/goods-card/index.wxss
@@ -0,0 +1,260 @@
+.wr-goods-card {
+ box-sizing: border-box;
+ font-size: 24rpx;
+}
+/* */
+.wr-goods-card__main {
+ position: relative;
+ display: flex;
+ padding: 0;
+ background: transparent;
+}
+
+.wr-goods-card.center .wr-goods-card__main {
+ align-items: flex-start;
+ justify-content: center;
+}
+
+.wr-goods-card__thumb {
+ flex-shrink: 0;
+ position: relative;
+ width: 140rpx;
+ height: 140rpx;
+}
+
+.wr-goods-card__thumb-com {
+ width: 192rpx;
+ height: 192rpx;
+ border-radius: 8rpx;
+ overflow: hidden;
+}
+.wr-goods-card__thumb:empty {
+ display: none;
+ margin: 0;
+}
+
+.wr-goods-card__body {
+ display: flex;
+ margin: 0 0 0 20rpx;
+ flex-direction: row;
+ flex: 1 1 auto;
+ min-height: 192rpx;
+}
+
+.wr-goods-card__long_content {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ flex: 1 1 auto;
+}
+.wr-goods-card__long_content .goods_tips {
+ width: 100%;
+ margin-top: 16rpx;
+ text-align: right;
+ color: #fa4126;
+ font-size: 24rpx;
+ line-height: 32rpx;
+ font-weight: bold;
+}
+.wr-goods-card__title {
+ flex-shrink: 0;
+ font-size: 28rpx;
+ color: #333;
+ line-height: 40rpx;
+ font-weight: 400;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ word-break: break-word;
+}
+.wr-goods-card__title__prefix-tags {
+ display: inline-flex;
+}
+.wr-goods-card__title__prefix-tags .prefix-tag {
+ margin: 0 8rpx 0 0;
+}
+.wr-goods-card__desc {
+ font-size: 24rpx;
+ color: #f5f5f5;
+ line-height: 40rpx;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+}
+.wr-goods-card__specs__desc,
+.wr-goods-card__specs__text {
+ font-size: 24rpx;
+ height: 32rpx;
+ line-height: 32rpx;
+ color: #999999;
+ margin: 8rpx 0;
+}
+.wr-goods-card__specs__desc {
+ display: flex;
+ align-self: flex-start;
+ flex-direction: row;
+ background: #f5f5f5;
+ border-radius: 8rpx;
+ padding: 4rpx 8rpx;
+}
+.wr-goods-card__specs__desc-text {
+ height: 100%;
+ max-width: 380rpx;
+ word-break: break-all;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 1;
+}
+.wr-goods-card__specs__desc-icon {
+ line-height: inherit;
+ margin-left: 8rpx;
+ font-size: 24rpx;
+ color: #bbb;
+}
+.wr-goods-card__specs__text {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: break-all;
+}
+.wr-goods-card__tags {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ margin: 16rpx 0 0 0;
+}
+.wr-goods-card__tag {
+ color: #fa550f;
+ background: transparent;
+ font-size: 20rpx;
+ border: 1rpx solid #fa550f;
+ padding: 0 8rpx;
+ height: 30rpx;
+ line-height: 30rpx;
+ margin: 0 8rpx 8rpx 0;
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ word-break: keep-all;
+ text-overflow: ellipsis;
+}
+.wr-goods-card__short_content {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-end;
+ margin: 0 0 0 46rpx;
+}
+.wr-goods-card__price__prefix {
+ order: 0;
+ color: #666;
+ margin: 0;
+}
+.wr-goods-card__price {
+ white-space: nowrap;
+ font-weight: bold;
+ order: 1;
+ color: #fa4126;
+ font-size: 36rpx;
+ margin: 0;
+ line-height: 48rpx;
+}
+.wr-goods-card__origin-price {
+ white-space: nowrap;
+ font-weight: normal;
+ order: 2;
+ color: #aaaaaa;
+ font-size: 24rpx;
+ margin: 0;
+}
+.wr-goods-card__num {
+ white-space: nowrap;
+ order: 4;
+ font-size: 24rpx;
+ color: #999;
+ margin: 20rpx 0 0 auto;
+}
+.wr-goods-card__num__prefix {
+ color: inherit;
+}
+.wr-goods-card__add-cart {
+ order: 3;
+ margin: auto 0 0 auto;
+}
+.wr-goods-card.horizontal-wrap .wr-goods-card__thumb {
+ width: 192rpx;
+ height: 192rpx;
+ border-radius: 8rpx;
+ overflow: hidden;
+}
+.wr-goods-card.horizontal-wrap .wr-goods-card__body {
+ flex-direction: column;
+}
+.wr-goods-card.horizontal-wrap .wr-goods-card__short_content {
+ flex-direction: row;
+ align-items: center;
+ margin: 16rpx 0 0 0;
+}
+
+.wr-goods-card.horizontal-wrap .wr-goods-card__num {
+ margin: 0 0 0 auto;
+}
+.wr-goods-card.vertical .wr-goods-card__main {
+ padding: 0 0 22rpx 0;
+ flex-direction: column;
+}
+.wr-goods-card.vertical .wr-goods-card__thumb {
+ width: 340rpx;
+ height: 340rpx;
+}
+.wr-goods-card.vertical .wr-goods-card__body {
+ margin: 20rpx 20rpx 0 20rpx;
+ flex-direction: column;
+}
+.wr-goods-card.vertical .wr-goods-card__long_content {
+ overflow: hidden;
+}
+.wr-goods-card.vertical .wr-goods-card__title {
+ line-height: 36rpx;
+}
+.wr-goods-card.vertical .wr-goods-card__short_content {
+ margin: 20rpx 0 0 0;
+}
+.wr-goods-card.vertical .wr-goods-card__price {
+ order: 2;
+ color: #fa4126;
+ margin: 20rpx 0 0 0;
+}
+.wr-goods-card.vertical .wr-goods-card__origin-price {
+ order: 1;
+}
+.wr-goods-card.vertical .wr-goods-card__add-cart {
+ position: absolute;
+ bottom: 20rpx;
+ right: 20rpx;
+}
+
+.wr-goods-card__short_content .no_storage {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 40rpx;
+ color: #333;
+ font-size: 24rpx;
+ line-height: 32rpx;
+ width: 100%;
+}
+
+.no_storage .no_storage__right {
+ width: 80rpx;
+ height: 40rpx;
+ border-radius: 20rpx;
+ border: 2rpx solid #fa4126;
+ line-height: 40rpx;
+ text-align: center;
+ color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.js b/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.js
new file mode 100644
index 0000000..5b71038
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.js
@@ -0,0 +1,48 @@
+Component({
+ options: {
+ addGlobalClass: true,
+ multipleSlots: true, // 在组件定义时的选项中启用多slot支持
+ },
+
+ properties: {
+ goods: {
+ type: Object,
+ value: null,
+ observer(goods) {
+ if (!goods) {
+ return;
+ }
+ this.setData({
+ goodsWithoutAttrValue: {
+ ...goods,
+ sku: {
+ ...goods.sku,
+ attr_value: [],
+ },
+ },
+ });
+ },
+ },
+ show: {
+ type: Boolean,
+ value: false,
+ },
+ thumbMode: {
+ type: String,
+ value: 'aspectFit',
+ },
+ },
+
+ data: {
+ goodsWithoutAttrValue: null,
+ },
+ methods: {
+ onClose() {
+ this.triggerEvent('close');
+ },
+
+ onCloseOver() {
+ this.triggerEvent('closeover');
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.json b/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.json
new file mode 100644
index 0000000..d59fc6d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-popup": "tdesign-miniprogram/popup/popup",
+ "goods-card": "../../components/goods-card/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.wxml b/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.wxml
new file mode 100644
index 0000000..8833670
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.wxml
@@ -0,0 +1,16 @@
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.wxss b/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.wxss
new file mode 100644
index 0000000..359c185
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/components/specs-popup/index.wxss
@@ -0,0 +1,68 @@
+.specs-popup {
+ width: 100vw;
+ box-sizing: border-box;
+ padding: 32rpx 32rpx calc(20rpx + env(safe-area-inset-bottom)) 32rpx;
+ max-height: 80vh;
+ display: flex;
+ flex-direction: column;
+ background-color: white;
+ border-radius: 20rpx 20rpx 0 0;
+}
+.specs-popup .section {
+ margin-top: 44rpx;
+ flex: auto;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ -webkit-overflow-scrolling: touch;
+}
+.specs-popup .section .title {
+ font-size: 26rpx;
+ color: #4f5356;
+}
+.specs-popup .section .options {
+ color: #333333;
+ font-size: 24rpx;
+ margin-right: -26rpx;
+}
+.specs-popup .section .options .option {
+ display: inline-block;
+ margin-top: 24rpx;
+ height: 56rpx;
+ line-height: 56rpx;
+ padding: 0 16rpx;
+ border-radius: 8rpx;
+ background-color: #f5f5f5;
+ max-width: 100%;
+ box-sizing: border-box;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.specs-popup .section .options .option:not(:last-child) {
+ margin-right: 26rpx;
+}
+.specs-popup .bottom-btn {
+ margin-top: 42rpx;
+ position: relative;
+ height: 80rpx;
+ line-height: 80rpx;
+ text-align: center;
+ background-color: white;
+ color: #fa4126;
+}
+.specs-popup .bottom-btn--active {
+ opacity: 0.5;
+}
+.specs-popup .bottom-btn::after {
+ display: block;
+ content: ' ';
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 200%;
+ height: 200%;
+ border: 1px solid #fa4126;
+ border-radius: 80rpx;
+ transform: scale(0.5);
+ transform-origin: left top;
+}
diff --git a/miniprogram/tcb-shop/pages/cart/index.js b/miniprogram/tcb-shop/pages/cart/index.js
new file mode 100644
index 0000000..8e50e3d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/index.js
@@ -0,0 +1,310 @@
+import Dialog from 'tdesign-miniprogram/dialog/index';
+import { fetchCartGroupData, fetchCartItems, deleteCartItem, updateCartItemCount } from '../../services/cart/cart';
+import { getSkuDetail } from '../../services/sku/sku';
+import { objectToParamString } from '../../utils/util';
+import { getSingleCloudImageTempUrl } from '../../utils/cloudImageHandler';
+
+let updateCartItemCountTimeout = null;
+
+Component({
+ data: {
+ cartGroupData: null,
+ cartItems: [],
+ selectedCartItemNum: 0,
+ allSelected: false,
+ totalAmount: 0,
+ loading: false,
+ inited: false,
+ },
+
+ observers: {
+ cartItems: function (cartItems) {
+ const selectedCartItems = cartItems.filter((x) => x.selected === true);
+ const selectedCartItemNum = selectedCartItems.length;
+ const totalAmount = selectedCartItems.reduce((acc, cur) => acc + cur.count * cur.sku.price, 0);
+ const allSelected = selectedCartItemNum === cartItems.length;
+
+ this.setData({
+ selectedCartItemNum,
+ allSelected,
+ totalAmount,
+ });
+ },
+ },
+
+ lifetimes: {
+ attached: async function () {
+ // console.log('called attached');
+ // // 调用自定义tabbar的init函数,使页面与tabbar激活状态保持一致
+ // this.getTabBar().init();
+ // await this.setLoading();
+ // await this.setDataPromise({ inited: true });
+ // try {
+ // await this.init();
+ // } finally {
+ // await this.unsetLoading();
+ // }
+ },
+ },
+
+ pageLifetimes: {
+ show: async function () {
+ // 调用自定义tabbar的init函数,使页面与tabbar激活状态保持一致
+ this.getTabBar().init();
+
+ await this.setLoading();
+ try {
+ await this.init();
+ } finally {
+ await this.unsetLoading();
+ }
+ },
+ },
+
+ methods: {
+ async init() {
+ const cartItems = (await fetchCartItems()).map((x) =>
+ Object.assign(x, {
+ selected: false,
+ }),
+ );
+ await Promise.all(
+ cartItems.map(async (cartItem) => {
+ const skuId = cartItem.sku._id;
+ const sku = await getSkuDetail(skuId);
+ if (sku.image) {
+ sku.image = await getSingleCloudImageTempUrl(sku.image);
+ }
+ cartItem.sku = sku;
+ }),
+ );
+ await this.setDataPromise({
+ cartItems,
+ });
+ },
+
+ findGoods(spuId, skuId) {
+ let currentStore;
+ let currentActivity;
+ let currentGoods;
+ const { storeGoods } = this.data.cartGroupData;
+ for (const store of storeGoods) {
+ for (const activity of store.promotionGoodsList) {
+ for (const goods of activity.goodsPromotionList) {
+ if (goods.spuId === spuId && goods.skuId === skuId) {
+ currentStore = store;
+ currentActivity = currentActivity;
+ currentGoods = goods;
+ return {
+ currentStore,
+ currentActivity,
+ currentGoods,
+ };
+ }
+ }
+ }
+ }
+ return {
+ currentStore,
+ currentActivity,
+ currentGoods,
+ };
+ },
+
+ // 注:实际场景时应该调用接口获取购物车数据
+ getCartGroupData() {
+ const { cartGroupData } = this.data;
+ if (!cartGroupData) {
+ return fetchCartGroupData();
+ }
+ return Promise.resolve({ data: cartGroupData });
+ },
+
+ // 选择单个商品
+ // 注:实际场景时应该调用接口更改选中状态
+ selectGoodsService({ spuId, skuId, isSelected }) {
+ this.findGoods(spuId, skuId).currentGoods.isSelected = isSelected;
+ return Promise.resolve();
+ },
+
+ // 全选门店
+ // 注:实际场景时应该调用接口更改选中状态
+ selectStoreService({ storeId, isSelected }) {
+ const currentStore = this.data.cartGroupData.storeGoods.find((s) => s.storeId === storeId);
+ currentStore.isSelected = isSelected;
+ currentStore.promotionGoodsList.forEach((activity) => {
+ activity.goodsPromotionList.forEach((goods) => {
+ goods.isSelected = isSelected;
+ });
+ });
+ return Promise.resolve();
+ },
+
+ // 加购数量变更
+ // 注:实际场景时应该调用接口
+ changeQuantityService({ spuId, skuId, quantity }) {
+ this.findGoods(spuId, skuId).currentGoods.quantity = quantity;
+ return Promise.resolve();
+ },
+
+ // 删除加购商品
+ // 注:实际场景时应该调用接口
+ deleteGoodsService({ spuId, skuId }) {
+ function deleteGoods(group) {
+ for (const gindex in group) {
+ const goods = group[gindex];
+ if (goods.spuId === spuId && goods.skuId === skuId) {
+ group.splice(gindex, 1);
+ return gindex;
+ }
+ }
+ return -1;
+ }
+ const { storeGoods, invalidGoodItems } = this.data.cartGroupData;
+ for (const store of storeGoods) {
+ for (const activity of store.promotionGoodsList) {
+ if (deleteGoods(activity.goodsPromotionList) > -1) {
+ return Promise.resolve();
+ }
+ }
+ if (deleteGoods(store.shortageGoodsList) > -1) {
+ return Promise.resolve();
+ }
+ }
+ if (deleteGoods(invalidGoodItems) > -1) {
+ return Promise.resolve();
+ }
+ return Promise.reject();
+ },
+
+ onGoodsSelect({ detail: { goods } }) {
+ const { cartItems } = this.data;
+ const item = cartItems.find((x) => x._id === goods._id);
+
+ if (item == null) {
+ console.warn('Cart item not found!');
+ return;
+ }
+
+ item.selected = !item.selected;
+ this.setData({ cartItems });
+ },
+
+ onQuantityChange({ detail: { cartItemId, count } }) {
+ const { cartItems } = this.data;
+ const item = cartItems.find((x) => x._id === cartItemId);
+ if (item == null) {
+ console.warn('Cart item not found');
+ return;
+ }
+ item.count = count;
+ this.setData({ cartItems });
+ this.debouncedUpdateCartItemCount({ cartItemId, count });
+ },
+
+ debouncedUpdateCartItemCount({ cartItemId, count }) {
+ clearTimeout(updateCartItemCountTimeout);
+ updateCartItemCountTimeout = setTimeout(async () => {
+ this.setLoading();
+ try {
+ await updateCartItemCount({ cartItemId, count });
+ } finally {
+ this.unsetLoading();
+ }
+ }, 500);
+ },
+
+ goCollect() {
+ /** 活动肯定有一个活动ID,用来获取活动banner,活动商品列表等 */
+ const promotionID = '123';
+ wx.navigateTo({
+ url: `/pages/promotion-detail/index?promotion_id=${promotionID}`,
+ });
+ },
+
+ goGoodsDetail({
+ detail: {
+ goods: {
+ sku: {
+ spu: { _id },
+ },
+ },
+ },
+ }) {
+ wx.navigateTo({
+ url: `/pages/goods/details/index?spuId=${_id}`,
+ });
+ },
+
+ async onGoodsDelete({
+ detail: {
+ goods: { _id },
+ },
+ }) {
+ try {
+ await Dialog.confirm({
+ context: this,
+ closeOnOverlayClick: true,
+ title: '确认删除该商品吗?',
+ confirmBtn: '确定',
+ cancelBtn: '取消',
+ });
+ this.setLoading();
+ try {
+ await deleteCartItem({ cartItemId: _id });
+ const { cartItems } = this.data;
+ this.setData({
+ cartItems: cartItems.filter((x) => x._id !== _id),
+ });
+ } finally {
+ this.unsetLoading();
+ }
+ } catch {
+ console.warn('deletion is cancelled');
+ }
+ },
+
+ onSelectAll() {
+ const { cartItems, allSelected } = this.data;
+ cartItems.forEach((x) => (x.selected = !allSelected));
+ this.setData({ cartItems });
+ },
+
+ onToSettle() {
+ wx.navigateTo({
+ url: `/pages/order/order-confirm/index?${objectToParamString({
+ type: 'cart',
+ cartIds: this.data.cartItems
+ .filter((x) => x.selected === true)
+ .map((x) => x._id)
+ .join(','),
+ })}`,
+ });
+ },
+ onGotoHome() {
+ wx.switchTab({ url: '/pages/home/home' });
+ },
+
+ setLoading() {
+ return this.setDataPromise({
+ loading: true,
+ });
+ },
+ unsetLoading() {
+ return this.setDataPromise({
+ loading: false,
+ });
+ },
+ toggleLoading() {
+ this.setData({
+ loading: !this.data.loading,
+ });
+ },
+
+ setDataPromise(data) {
+ return new Promise((res) => {
+ this.setData(data, () => res());
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/cart/index.json b/miniprogram/tcb-shop/pages/cart/index.json
new file mode 100644
index 0000000..b78f95a
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/index.json
@@ -0,0 +1,11 @@
+{
+ "navigationBarTitleText": "购物车",
+ "usingComponents": {
+ "cart-group": "./components/cart-group/index",
+ "cart-empty": "./components/cart-empty/index",
+ "cart-bar": "./components/cart-bar/index",
+ "loading-dialog": "../../components/loading-dialog/index",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/index.wxml b/miniprogram/tcb-shop/pages/cart/index.wxml
new file mode 100644
index 0000000..affa744
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/index.wxml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/cart/index.wxss b/miniprogram/tcb-shop/pages/cart/index.wxss
new file mode 100644
index 0000000..ea19962
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/cart/index.wxss
@@ -0,0 +1,13 @@
+:host {
+ padding-bottom: 100rpx;
+}
+
+.gap {
+ height: 100rpx;
+ width: 100%;
+}
+
+.t-button {
+ --td-button-default-color: #000;
+ --td-button-primary-text-color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.js b/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.js
new file mode 100644
index 0000000..07785fe
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.js
@@ -0,0 +1,57 @@
+const statusMap = {
+ default: { text: '去使用', theme: 'primary' },
+ useless: { text: '已使用', theme: 'default' },
+ disabled: { text: '已过期', theme: 'default' },
+};
+Component({
+ options: {
+ addGlobalClass: true,
+ multipleSlots: true, // 在组件定义时的选项中启用多slot支持
+ },
+
+ externalClasses: ['coupon-class'],
+
+ properties: {
+ couponDTO: {
+ type: Object,
+ value: {}, // 优惠券数据
+ },
+ },
+
+ data: {
+ btnText: '',
+ btnTheme: '',
+ },
+
+ observers: {
+ couponDTO: function (couponDTO) {
+ if (!couponDTO) {
+ return;
+ }
+ const statusInfo = statusMap[couponDTO.status];
+
+ this.setData({
+ btnText: statusInfo.text,
+ btnTheme: statusInfo.theme,
+ });
+ },
+ },
+
+ attached() {},
+
+ methods: {
+ // 跳转到详情页
+ gotoDetail() {
+ wx.navigateTo({
+ url: `/pages/coupon/coupon-detail/index?id=${this.data.couponDTO.key}`,
+ });
+ },
+
+ // 跳转到商品列表
+ gotoGoodsList() {
+ wx.navigateTo({
+ url: `/pages/coupon/coupon-activity-goods/index?id=${this.data.couponDTO.key}`,
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.json b/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.json
new file mode 100644
index 0000000..2d851d1
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "ui-coupon-card": "../ui-coupon-card/index",
+ "t-button": "tdesign-miniprogram/button/button"
+ }
+}
diff --git a/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.wxml b/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.wxml
new file mode 100644
index 0000000..4dd56e1
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.wxml
@@ -0,0 +1,23 @@
+
+
+ {{btnText}}
+
+
+
diff --git a/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.wxss b/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.wxss
new file mode 100644
index 0000000..905dc40
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/coupon-card/index.wxss
@@ -0,0 +1,9 @@
+.coupon-btn-default {
+ display: none;
+}
+
+.coupon-btn-primary {
+ --td-button-extra-small-padding-horizontal: 26rpx;
+ --td-button-primary-outline-color: #fa4126;
+ --td-button-primary-outline-border-color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.js b/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.js
new file mode 100644
index 0000000..f29bcad
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.js
@@ -0,0 +1,17 @@
+Component({
+ data: { icon: 'cart' },
+
+ properties: {
+ count: {
+ type: Number,
+ },
+ },
+
+ methods: {
+ goToCart() {
+ wx.switchTab({
+ url: '/pages/cart/index',
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.json b/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.json
new file mode 100644
index 0000000..fa2d209
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-icon": "tdesign-miniprogram/icon/icon"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.wxml b/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.wxml
new file mode 100644
index 0000000..7f1a9e7
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.wxml
@@ -0,0 +1,14 @@
+
+
+
+
+
+ {{count}}
+
+
+
diff --git a/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.wxss b/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.wxss
new file mode 100644
index 0000000..faad943
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/floating-button/index.wxss
@@ -0,0 +1,30 @@
+.floating-button {
+ position: fixed;
+ right: 20rpx;
+ bottom: 108rpx;
+}
+
+.floating-button .floating-inner-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 96rpx;
+ width: 96rpx;
+ background-color: rgba(0, 0, 0, 0.8);
+ opacity: 0.7;
+ border-radius: 48rpx;
+}
+
+.floating-button .floating-right-top {
+ position: absolute;
+ right: 0rpx;
+ top: 0rpx;
+ height: 28rpx;
+ background: #fa4126;
+ border-radius: 64rpx;
+ font-weight: bold;
+ font-size: 22rpx;
+ line-height: 28rpx;
+ color: #fff;
+ padding: 0 8rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.js b/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.js
new file mode 100644
index 0000000..9101be8
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.js
@@ -0,0 +1,87 @@
+Component({
+ options: {
+ addGlobalClass: true,
+ multipleSlots: true,
+ },
+
+ externalClasses: ['coupon-class'],
+
+ properties: {
+ mask: {
+ type: Boolean,
+ value: false, // 是否添加遮罩
+ },
+ superposable: {
+ type: Boolean,
+ value: false, // 是否可叠加
+ },
+ type: {
+ type: String,
+ value: '', // 优惠券类型:CouponType
+ },
+ value: {
+ type: String,
+ value: '', // 优惠金额
+ },
+ tag: {
+ type: String,
+ value: '', // 优惠标签,优惠券名字标签,img
+ },
+ desc: {
+ type: String,
+ value: '', // 优惠金额描述,金额下方
+ },
+ title: {
+ type: String, // 优惠券名称
+ value: '',
+ },
+ timeLimit: {
+ type: String, // 优惠券时限
+ value: '',
+ },
+ ruleDesc: {
+ type: String, // 优惠券适用规则描述
+ value: '',
+ },
+ currency: {
+ type: String,
+ value: '¥', // 优惠货币
+ },
+ status: {
+ type: String,
+ value: 'default',
+ },
+ image: {
+ type: String,
+ value: '',
+ },
+ },
+
+ data: {
+ CouponType: {
+ MJ_COUPON: 1,
+ ZK_COUPON: 2,
+ MJF_COUPON: 3,
+ GIFT_COUPON: 4,
+ },
+ theme: 'primary',
+ },
+
+ observers: {
+ status: function (value) {
+ let theme = 'primary';
+ // 已过期或已使用的券 颜色置灰
+ if (value === 'useless' || value === 'disabled') {
+ theme = 'weak';
+ }
+
+ this.setData({ theme });
+ },
+ },
+
+ attached() {
+ this.setData({
+ color: `color${this.properties.colorStyle}`,
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.json b/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.json
new file mode 100644
index 0000000..dd874c8
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-image": "/components/webp-image/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.wxml b/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.wxml
new file mode 100644
index 0000000..ec76acb
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.wxml
@@ -0,0 +1,55 @@
+
+ function isBigValue(value) {
+ var values = (value + '').split('.');
+ if (values[1] && values[0].length >= 3) return true;
+ else return false
+ }
+
+ function getBigValues(value) {
+ return value.split('.');
+ }
+
+ module.exports = { isBigValue: isBigValue, getBigValues: getBigValues };
+
+
+
+
+ {{value}}
+ 折
+ {{desc}}
+
+
+
+ {{tools.getBigValues(value)[0]}}
+ .{{tools.getBigValues(value)[1]}}
+
+ {{value / 100}}
+ 元
+ {{desc}}
+
+
+
+ 免邮
+
+ {{desc}}
+
+
+
+
+
+
+
+ {{title}}
+ {{timeLimit}}
+
+ {{ruleDesc}}
+
+
+
+
+
+
+
+
+ 可叠加
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.wxss b/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.wxss
new file mode 100644
index 0000000..0ef9648
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/components/ui-coupon-card/index.wxss
@@ -0,0 +1,147 @@
+.wr-coupon {
+ display: flex;
+ background-image: url('https://cdn-we-retail.ym.tencent.com/miniapp/coupon/coupon-bg-nocorners.png');
+ background-size: 100% 100%;
+ background-repeat: no-repeat;
+ position: relative;
+ margin-bottom: 24rpx;
+ overflow: hidden;
+}
+.theme-weak.wr-coupon {
+ background-image: url('https://cdn-we-retail.ym.tencent.com/miniapp/coupon/coupon-bg-grey2.png');
+}
+
+.wr-coupon__left {
+ width: 200rpx;
+ height: 180rpx;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ text-align: center;
+ color: #fa4126;
+ overflow: hidden;
+ position: relative;
+}
+.theme-weak .wr-coupon__left {
+ color: #333;
+}
+
+.wr-coupon__left--value {
+ font-size: 64rpx;
+ line-height: 88rpx;
+ font-weight: bold;
+ font-family: 'DIN Alternate', cursive;
+}
+.wr-coupon__left--value-int {
+ font-size: 48rpx;
+ line-height: 88rpx;
+}
+.wr-coupon__left--value-decimal {
+ font-size: 36rpx;
+ line-height: 48rpx;
+}
+.wr-coupon__left--image {
+ width: 128rpx;
+ height: 128rpx;
+ border-radius: 8px;
+ margin-top: 30rpx;
+}
+.wr-coupon__left--unit {
+ font-size: 24rpx;
+ line-height: 32rpx;
+}
+.wr-coupon__left--desc {
+ font-size: 24rpx;
+ line-height: 32rpx;
+ color: #fa4126;
+}
+
+.theme-weak .wr-coupon__left--desc {
+ color: #333;
+}
+
+.wr-coupon__right {
+ flex-grow: 1;
+ padding: 0 20rpx;
+ height: 180rpx;
+ box-sizing: border-box;
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+}
+.wr-coupon__right--title {
+ display: flex;
+ -webkit-display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ color: #999999;
+ font-size: 24rpx;
+ flex: 1;
+}
+.wr-coupon__right--title .coupon-title {
+ max-width: 320rpx;
+ color: #333333;
+ font-size: 28rpx;
+ line-height: 40rpx;
+ font-weight: bold;
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ white-space: normal;
+}
+.wr-coupon__right--title .coupon-time {
+ margin-top: 16rpx;
+ /* // letter-spacing: -0.05em; */
+}
+.wr-coupon__right--title .coupon-desc {
+ margin-top: 8rpx;
+}
+.wr-coupon__right--title .coupon-arrow {
+ font-size: 22rpx;
+}
+.wr-coupon__right--oper {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+.wr-coupon__mask {
+ width: 702rpx;
+ height: 182rpx;
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: #ffffff;
+ opacity: 0.5;
+}
+.wr-coupon__tag {
+ position: absolute;
+ top: 8px;
+ right: -24rpx;
+ text-align: center;
+ width: 106rpx;
+ height: 28rpx;
+ opacity: 0.9;
+ font-size: 20rpx;
+ line-height: 28rpx;
+ color: #fa4126;
+ border: 0.5px solid #fa4126;
+ box-sizing: border-box;
+ transform: rotate(45deg);
+}
+.wr-coupon__seal {
+ width: 128rpx;
+ height: 128rpx;
+ position: absolute;
+ top: 0;
+ right: 0;
+ background-size: 100% 100%;
+}
+
+.wr-coupon__seal.seal-useless {
+ background-image: url('https://cdn-we-retail.ym.tencent.com/miniapp/coupon/seal-used.png');
+}
+
+.wr-coupon__seal.seal-disabled {
+ background-image: url('https://cdn-we-retail.ym.tencent.com/miniapp/coupon/coupon-expired.png');
+}
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.js b/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.js
new file mode 100644
index 0000000..8010400
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.js
@@ -0,0 +1,75 @@
+import { fetchCouponDetail } from '../../../services/coupon/index';
+import Toast from 'tdesign-miniprogram/toast/index';
+
+Page({
+ data: {
+ goods: [],
+ detail: {},
+ couponTypeDesc: '',
+ showStoreInfoList: false,
+ cartNum: 2,
+ },
+
+ id: '',
+
+ onLoad(query) {
+ const id = parseInt(query.id);
+ this.id = id;
+
+ this.getCouponDetail(id);
+ this.getGoodsList(id);
+ },
+
+ getCouponDetail(id) {
+ fetchCouponDetail(id).then(({ detail }) => {
+ this.setData({ detail });
+ if (detail.type === 2) {
+ if (detail.base > 0) {
+ this.setData({
+ couponTypeDesc: `满${detail.base / 100}元${detail.value}折`,
+ });
+ } else {
+ this.setData({ couponTypeDesc: `${detail.value}折` });
+ }
+ } else if (detail.type === 1) {
+ if (detail.base > 0) {
+ this.setData({
+ couponTypeDesc: `满${detail.base / 100}元减${detail.value / 100}元`,
+ });
+ } else {
+ this.setData({ couponTypeDesc: `减${detail.value / 100}元` });
+ }
+ }
+ });
+ },
+
+ getGoodsList() {
+ throw new Error('unimplemented');
+ },
+
+ openStoreList() {
+ this.setData({
+ showStoreInfoList: true,
+ });
+ },
+
+ closeStoreList() {
+ this.setData({
+ showStoreInfoList: false,
+ });
+ },
+
+ goodClickHandle(e) {
+ const { index } = e.detail;
+ const { spuId } = this.data.goods[index];
+ wx.navigateTo({ url: `/pages/goods/details/index?spuId=${spuId}` });
+ },
+
+ cartClickHandle() {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '点击加入购物车',
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.json b/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.json
new file mode 100644
index 0000000..31cf9c3
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.json
@@ -0,0 +1,10 @@
+{
+ "navigationBarTitleText": "活动商品",
+ "usingComponents": {
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-popup": "tdesign-miniprogram/popup/popup",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "goods-list": "/components/goods-list/index",
+ "floating-button": "../components/floating-button/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.wxml b/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.wxml
new file mode 100644
index 0000000..1c55b7f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.wxml
@@ -0,0 +1,40 @@
+
+
+
+ 以下商品可使用
+ {{couponTypeDesc}}
+ 优惠券
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.wxss b/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.wxss
new file mode 100644
index 0000000..3217203
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-activity-goods/index.wxss
@@ -0,0 +1,69 @@
+page {
+ background-color: #f5f5f5;
+}
+
+.coupon-page-container .notice-bar-content {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding: 8rpx 0;
+}
+
+.coupon-page-container .notice-bar-text {
+ font-size: 26rpx;
+ line-height: 36rpx;
+ font-weight: 400;
+ color: #666666;
+ margin-left: 24rpx;
+ margin-right: 12rpx;
+}
+
+.coupon-page-container .notice-bar-text .height-light {
+ color: #fa550f;
+}
+
+.coupon-page-container .popup-content-wrap {
+ background-color: #fff;
+ border-top-left-radius: 20rpx;
+ border-top-right-radius: 20rpx;
+}
+
+.coupon-page-container .popup-content-title {
+ font-size: 32rpx;
+ color: #333;
+ text-align: center;
+ height: 104rpx;
+ line-height: 104rpx;
+ position: relative;
+}
+
+.coupon-page-container .desc-group-wrap {
+ padding-bottom: env(safe-area-inset-bottom);
+}
+
+.coupon-page-container .desc-group-wrap .item-wrap {
+ margin: 0 30rpx 30rpx;
+}
+
+.coupon-page-container .desc-group-wrap .item-title {
+ font-size: 26rpx;
+ color: #333;
+ font-weight: 500;
+}
+
+.coupon-page-container .desc-group-wrap .item-label {
+ font-size: 24rpx;
+ color: #666;
+ margin-top: 12rpx;
+ white-space: pre-line;
+ word-break: break-all;
+ line-height: 34rpx;
+}
+
+.coupon-page-container .goods-list-container {
+ margin: 0 24rpx 24rpx;
+}
+
+.coupon-page-container .goods-list-wrap {
+ background: #f5f5f5 !important;
+}
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.js b/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.js
new file mode 100644
index 0000000..71ff5d6
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.js
@@ -0,0 +1,32 @@
+import { fetchCouponDetail } from '../../../services/coupon/index';
+
+Page({
+ data: {
+ detail: null,
+ storeInfoList: [],
+ storeInfoStr: '',
+ showStoreInfoList: false,
+ },
+
+ id: '',
+
+ onLoad(query) {
+ const id = parseInt(query.id);
+ this.id = id;
+ this.getGoodsList(id);
+ },
+
+ getGoodsList(id) {
+ fetchCouponDetail(id).then(({ detail }) => {
+ this.setData({
+ detail,
+ });
+ });
+ },
+
+ navGoodListHandle() {
+ wx.navigateTo({
+ url: `/pages/coupon/coupon-activity-goods/index?id=${this.id}`,
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.json b/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.json
new file mode 100644
index 0000000..d419a0b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.json
@@ -0,0 +1,10 @@
+{
+ "navigationBarTitleText": "优惠券详情",
+ "usingComponents": {
+ "coupon-card": "../components/coupon-card/index",
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-icon": "tdesign-miniprogram/icon/icon"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.wxml b/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.wxml
new file mode 100644
index 0000000..8c447c2
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.wxml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 查看可用商品
+
+
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.wxss b/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.wxss
new file mode 100644
index 0000000..8fb1fe7
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-detail/index.wxss
@@ -0,0 +1,91 @@
+page {
+ background-color: #f5f5f5;
+}
+
+.coupon-card-wrap {
+ background-color: #fff;
+ padding: 32rpx 32rpx 1rpx;
+}
+.desc-wrap {
+ margin-top: 24rpx;
+}
+.desc-wrap .button-wrap {
+ margin: 50rpx 32rpx 0;
+}
+
+.desc-group-wrap .t-class-cell {
+ align-items: flex-start;
+}
+
+.desc-group-wrap .t-class-title {
+ font-size: 26rpx;
+ width: 140rpx;
+ flex: none;
+ color: #888;
+}
+
+.desc-group-wrap .t-class-note {
+ font-size: 26rpx;
+ word-break: break-all;
+ white-space: pre-line;
+ justify-content: flex-start;
+ color: #333;
+}
+
+.desc-group-wrap {
+ border-radius: 8rpx;
+ overflow: hidden;
+
+ --cell-label-font-size: 26rpx;
+ --cell-label-line-height: 36rpx;
+ --cell-label-color: #999;
+}
+
+.desc-group-wrap.in-popup {
+ border-radius: 0;
+ overflow: auto;
+ max-height: 828rpx;
+}
+
+.desc-group-wrap .wr-cell__title {
+ color: #333;
+ font-size: 28rpx;
+}
+
+/* .desc-group-wrap .max-width-cell {
+ overflow: hidden;
+} */
+
+/* .desc-group-wrap .signal-line-label {
+ word-break: keep-all;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+.desc-group-wrap .multi-line-label {
+ word-break: break-all;
+ white-space: pre-line;
+} */
+
+.popup-content-wrap {
+ background-color: #fff;
+ border-top-left-radius: 20rpx;
+ border-top-right-radius: 20rpx;
+}
+
+.popup-content-title {
+ font-size: 32rpx;
+ color: #333;
+
+ text-align: center;
+ height: 104rpx;
+ line-height: 104rpx;
+
+ position: relative;
+}
+
+.popup-content-title .close-icon {
+ position: absolute;
+ top: 24rpx;
+ right: 24rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-list/index.js b/miniprogram/tcb-shop/pages/coupon/coupon-list/index.js
new file mode 100644
index 0000000..f3b8675
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-list/index.js
@@ -0,0 +1,77 @@
+import { fetchCouponList } from '../../../services/coupon/index';
+
+Page({
+ data: {
+ status: 0,
+ list: [
+ {
+ text: '可使用',
+ key: 0,
+ },
+ {
+ text: '已使用',
+ key: 1,
+ },
+ {
+ text: '已失效',
+ key: 2,
+ },
+ ],
+
+ couponList: [],
+ },
+
+ onLoad() {
+ this.init();
+ },
+
+ init() {
+ this.fetchList();
+ },
+
+ fetchList(status = this.data.status) {
+ let statusInFetch = '';
+ switch (Number(status)) {
+ case 0: {
+ statusInFetch = 'default';
+ break;
+ }
+ case 1: {
+ statusInFetch = 'useless';
+ break;
+ }
+ case 2: {
+ statusInFetch = 'disabled';
+ break;
+ }
+ default: {
+ throw new Error(`unknown fetchStatus: ${statusInFetch}`);
+ }
+ }
+ fetchCouponList(statusInFetch).then((couponList) => {
+ this.setData({ couponList });
+ });
+ },
+
+ tabChange(e) {
+ const { value } = e.detail;
+
+ this.setData({ status: value });
+ this.fetchList(value);
+ },
+
+ goCouponCenterHandle() {
+ wx.showToast({ title: '去领券中心', icon: 'none' });
+ },
+
+ onPullDownRefresh_() {
+ this.setData(
+ {
+ couponList: [],
+ },
+ () => {
+ this.fetchList();
+ },
+ );
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-list/index.json b/miniprogram/tcb-shop/pages/coupon/coupon-list/index.json
new file mode 100644
index 0000000..64b7e4b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-list/index.json
@@ -0,0 +1,10 @@
+{
+ "navigationBarTitleText": "优惠券",
+ "usingComponents": {
+ "t-pull-down-refresh": "tdesign-miniprogram/pull-down-refresh/pull-down-refresh",
+ "t-tabs": "tdesign-miniprogram/tabs/tabs",
+ "t-tab-panel": "tdesign-miniprogram/tab-panel/tab-panel",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "coupon-card": "../components/coupon-card/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-list/index.wxml b/miniprogram/tcb-shop/pages/coupon/coupon-list/index.wxml
new file mode 100644
index 0000000..391fe7b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-list/index.wxml
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+ 领券中心
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/coupon/coupon-list/index.wxss b/miniprogram/tcb-shop/pages/coupon/coupon-list/index.wxss
new file mode 100644
index 0000000..4e28302
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/coupon/coupon-list/index.wxss
@@ -0,0 +1,78 @@
+page {
+ height: 100%;
+}
+
+.tabs-external__inner {
+ height: 88rpx;
+ width: 100%;
+ line-height: 88rpx;
+ z-index: 100;
+}
+.tabs-external__inner {
+ font-size: 26rpx;
+ color: #333333;
+ position: fixed;
+ width: 100vw;
+ top: 0;
+ left: 0;
+}
+
+.tabs-external__inner .tabs-external__track {
+ background: #fa4126 !important;
+}
+
+.tabs-external__inner .tabs-external__item {
+ color: #666;
+}
+
+.tabs-external__inner .tabs-external__active {
+ font-size: 28rpx;
+ color: #fa4126 !important;
+}
+
+.tabs-external__inner.order-nav .order-nav-item .bottom-line {
+ bottom: 12rpx;
+}
+
+.coupon-list-wrap {
+ margin-top: 32rpx;
+ margin-left: 32rpx;
+ margin-right: 32rpx;
+ overflow-y: auto;
+ padding-bottom: 100rpx;
+ padding-bottom: calc(constant(safe-area-inset-top) + 100rpx);
+ padding-bottom: calc(env(safe-area-inset-bottom) + 100rpx);
+ -webkit-overflow-scrolling: touch;
+}
+
+.center-entry {
+ box-sizing: content-box;
+ border-top: 1rpx solid #dce0e4;
+ background-color: #fff;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ height: 100rpx;
+ padding-bottom: 0;
+ padding-bottom: constant(safe-area-inset-top);
+ padding-bottom: env(safe-area-inset-bottom);
+}
+
+.center-entry-btn {
+ color: #fa4126;
+ font-size: 28rpx;
+ text-align: center;
+ line-height: 100rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ height: 100rpx;
+}
+
+.coupon-list-wrap .t-pull-down-refresh__bar {
+ background: #fff !important;
+}
+.t-class-indicator {
+ color: #b9b9b9 !important;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/README.md b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/README.md
new file mode 100644
index 0000000..88d7b0b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/README.md
@@ -0,0 +1,95 @@
+# Sidebar 侧边导航
+
+### 引入
+
+全局引入,在miniprogram根目录下的`app.json`中配置,局部引入,在需要引入的页面或组件的`index.json`中配置。
+
+```json
+// app.json 或 index.json
+"usingComponents": {
+ "wr-sidebar": "path/to/components/goods-category/wr-sidebar/index",
+ "wr-sidebar-item": "path/to/component/goods-category/wr-sidebar/wr-sidebar-item/index"
+}
+```
+
+## 代码演示
+
+### 基础用法
+
+通过在`wr-sidebar`上设置`activeKey`属性来控制选中项
+
+```html
+
+
+
+
+
+```
+
+``` javascript
+Page({
+ data: {
+ activeKey: 0
+ },
+
+ onChange(event) {
+ wx.showToast({
+ icon: 'none',
+ title: `切换至第${event.detail}项`
+ });
+ }
+});
+```
+
+### 提示气泡(暂未实现)
+
+设置`dot`属性后,会在右上角展示一个小红点。设置`info`属性后,会在右上角展示相应的徽标
+
+```html
+
+
+
+
+
+```
+
+## API
+
+### Sidebar Props
+
+| 参数 | 说明 | 类型 | 默认值 | 版本 |
+|-----------|-----------|-----------|-------------|-------------|
+| activeKey | 选中项的索引 | *string \| number* | `0` | - |
+
+### Sidebar Event
+
+| 事件名 | 说明 | 参数 |
+|------|------|------|
+| change | 切换选项时触发 | 当前选中选项的索引 |
+
+### Sidebar 外部样式类
+
+| 类名 | 说明 |
+|-----------|-----------|
+| custom-class | 根节点样式类 |
+
+### SidebarItem Props
+
+| 参数 | 说明 | 类型 | 默认值 | 版本 |
+|-----------|-----------|-----------|-------------|-------------|
+| title | 内容 | *string* | `''` | - |
+| disabled | 是否禁用 | | *boolean* | `false` | - |
+| dot | 是否显示右上角小红点 | *boolean* | `false` | - |
+| info | 提示消息 | *string \| number* | `''` | - |
+
+### SidebarItem Event
+
+| 事件名 | 说明 | 参数 |
+|------|------|------|
+| click | 点击徽章时触发 | 当前徽章的索引 |
+
+### SidebarItem 外部样式类
+
+| 类名 | 说明 |
+|-----------|-----------|
+| custom-class | 根节点样式类 |
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.js b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.js
new file mode 100644
index 0000000..810d5e5
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.js
@@ -0,0 +1,51 @@
+Component({
+ relations: {
+ '../../c-sidebar/index': {
+ type: 'ancestor',
+ linked(target) {
+ this.parent = target;
+ },
+ },
+ },
+
+ externalClasses: ['custom-class'],
+ properties: {
+ title: String,
+ disabled: Boolean,
+ },
+
+ data: {
+ topRightRadius: false,
+ bottomRightRadius: false,
+ },
+
+ methods: {
+ setActive(selected) {
+ return this.setData({ selected });
+ },
+ onClick() {
+ const { parent } = this;
+
+ if (!parent || this.properties.disabled) {
+ return;
+ }
+
+ const index = parent.children.indexOf(this);
+
+ parent.setActive(index).then(() => {
+ this.triggerEvent('click', index);
+ parent.triggerEvent('change', { index });
+ });
+ },
+ setTopRightRadius(val) {
+ return this.setData({
+ topRightRadius: val,
+ });
+ },
+ setBottomRightRadius(val) {
+ return this.setData({
+ bottomRightRadius: val,
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.json b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.json
new file mode 100644
index 0000000..e8cfaaf
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.wxml b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.wxml
new file mode 100644
index 0000000..435f1e9
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.wxml
@@ -0,0 +1,10 @@
+
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.wxss b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.wxss
new file mode 100644
index 0000000..28d50dc
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/c-sidebar-item/index.wxss
@@ -0,0 +1,60 @@
+.c-sidebar-item {
+ display: flex;
+ justify-content: center;
+ text-align: center;
+ background-color: #f5f5f5;
+ color: #222427;
+ padding: 20rpx 0;
+ font-size: 26rpx;
+}
+
+.c-sidebar-item.active {
+ position: relative;
+ background: white;
+}
+
+.c-sidebar-item.active::before {
+ content: '';
+ position: absolute;
+ width: 6rpx;
+ height: 48rpx;
+ background-color: #fa4126;
+ left: 0;
+ top: 50%;
+ transform: translate(0, -50%);
+ border-radius: 64rpx;
+}
+
+.c-sidebar-item__text {
+ width: 136rpx;
+ height: 36rpx;
+ padding: 8rpx 0;
+ line-height: 36rpx;
+ text-align: center;
+ font-size: 28rpx;
+ color: #666666;
+}
+
+.c-sidebar-item.active .c-sidebar-item__text {
+ background-color: white;
+ border-radius: 36rpx;
+ color: #fa4126;
+}
+
+.text-overflow {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.top-right-radius {
+ border-top-right-radius: 16rpx;
+}
+
+.bottom-right-radius {
+ border-bottom-right-radius: 16rpx;
+}
+
+.c-sidebar-item-container {
+ background-color: white;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.js b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.js
new file mode 100644
index 0000000..b057ad1
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.js
@@ -0,0 +1,106 @@
+Component({
+ relations: {
+ './c-sidebar-item/index': {
+ type: 'descendant',
+ linked(target) {
+ this.children.push(target);
+ this.setActive(this.properties.activeKey, true);
+ },
+ unlinked(target) {
+ this.children = this.children.filter((item) => item !== target);
+ this.setActive(this.properties.activeKey, true);
+ },
+ },
+ },
+
+ externalClasses: ['custom-class'],
+
+ properties: {
+ activeKey: {
+ type: Number,
+ value: 0,
+ },
+ },
+ observers: {
+ activeKey(newVal) {
+ this.setActive(newVal);
+ },
+ },
+
+ created() {
+ this.children = [];
+ this.currentActive = -1;
+ this.topRightRadiusItemIndexs = [];
+ this.bottomRightRadiusItemIndexs = [];
+ },
+
+ methods: {
+ setActive(activeKey, isChildrenChange) {
+ const {
+ children,
+ currentActive,
+ topRightRadiusItemIndexs: preTopRightRadiusItemIndexs,
+ bottomRightRadiusItemIndexs: preBottomRightRadiusItemIndexs,
+ } = this;
+
+ if (!children.length) {
+ return Promise.resolve();
+ }
+
+ if (activeKey === currentActive && !isChildrenChange) {
+ return Promise.resolve();
+ }
+
+ this.currentActive = activeKey;
+ this.topRightRadiusItemIndexs = this.getTopRightRadiusItemIndexs(
+ activeKey,
+ children,
+ );
+ this.bottomRightRadiusItemIndexs = this.getBottomRightRadiusItemIndexs(
+ activeKey,
+ children,
+ );
+
+ const stack = []; // 任务列表,存放调用子组件的setActive后返回的一堆promise
+
+ // 将旧的选中项改为false
+ if (currentActive !== activeKey && children[currentActive]) {
+ stack.push(children[currentActive].setActive(false));
+ }
+
+ // 将新的选中项改为true
+ if (children[activeKey]) {
+ stack.push(children[activeKey].setActive(true));
+ }
+
+ preTopRightRadiusItemIndexs.forEach((item) => {
+ stack.push(children[item].setTopRightRadius(false));
+ });
+
+ preBottomRightRadiusItemIndexs.forEach((item) => {
+ stack.push(children[item].setBottomRightRadius(false));
+ });
+
+ this.topRightRadiusItemIndexs.forEach((item) => {
+ stack.push(children[item].setTopRightRadius(true));
+ });
+
+ this.bottomRightRadiusItemIndexs.forEach((item) => {
+ stack.push(children[item].setBottomRightRadius(true));
+ });
+
+ return Promise.all(stack);
+ },
+ getTopRightRadiusItemIndexs(activeKey, children) {
+ const { length } = children;
+ if (activeKey !== 0 && activeKey < length - 1) return [0, activeKey + 1];
+ if (activeKey !== 0) return [0];
+ if (activeKey < length - 1) return [activeKey + 1];
+ return [];
+ },
+ getBottomRightRadiusItemIndexs(activeKey) {
+ if (activeKey !== 0) return [activeKey - 1];
+ return [];
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.json b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.json
new file mode 100644
index 0000000..84ff738
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.json
@@ -0,0 +1,4 @@
+{
+ "component": true
+}
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.wxml b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.wxml
new file mode 100644
index 0000000..a1fe026
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.wxml
@@ -0,0 +1,3 @@
+
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.wxss b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.wxss
new file mode 100644
index 0000000..95e59ff
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-sidebar/index.wxss
@@ -0,0 +1,9 @@
+.c-sidebar {
+ width: 176rpx;
+ height: 100vh;
+}
+.c-sidebar::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+ color: transparent;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.js b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.js
new file mode 100644
index 0000000..2b6f95d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.js
@@ -0,0 +1,37 @@
+Component({
+ externalClasses: ['custom-class'],
+
+ properties: {
+ tabList: Array,
+ },
+
+ data: {
+ unfolded: false,
+ boardMaxHeight: null,
+ },
+ attached() {
+ wx.createSelectorQuery()
+ .in(this)
+ .select('.c-tabbar-more')
+ .boundingClientRect((rect) => {
+ this.setData({ boardMaxHeight: rect.height });
+ })
+ .exec();
+ },
+
+ methods: {
+ changeFold() {
+ this.setData({
+ unfolded: !this.data.unfolded,
+ });
+ const { unfolded } = this.data;
+ this.triggerEvent('change', { unfolded });
+ },
+
+ onSelect(event) {
+ const activeKey = event.currentTarget.dataset.index;
+ this.triggerEvent('select', activeKey);
+ this.changeFold();
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.json b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.json
new file mode 100644
index 0000000..a89ef4d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+}
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.wxml b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.wxml
new file mode 100644
index 0000000..0ea9ad4
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.wxml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.wxss b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.wxss
new file mode 100644
index 0000000..d0d08b3
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/c-tabbar-more/index.wxss
@@ -0,0 +1,63 @@
+.c-tabbar-more {
+ width: 100%;
+ height: calc(100% - var(--tabbar-height, 100rpx));
+ position: absolute;
+ top: var(--tabbar-height, 100rpx);
+}
+.c-tabbar-more__btn {
+ position: absolute;
+ top: calc(0rpx - var(--tabbar-height, 100rpx));
+ right: 0;
+ width: 80rpx;
+ height: var(--tabbar-height, 100rpx);
+ line-height: var(--tabbar-height, 100rpx);
+ background-color: var(--tabbar-background-color, white);
+ box-shadow: -20rpx 0 20rpx -10rpx var(--tabbar-background-color, white);
+ text-align: center;
+}
+.c-tabbar-more__btn .market {
+ font-size: 20rpx;
+}
+.t-tabbar-more__boardwrapper {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+}
+.t-tabbar-more__mask {
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+}
+.c-tabbar-more__board {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ max-height: 100%;
+}
+.c-tabbar-more__boardinner {
+ padding: 20rpx 0 20rpx 20rpx;
+ background-color: var(--tabbar-background-color, white);
+ display: flex;
+ flex-flow: row wrap;
+}
+.c-tabbar-more__item {
+ margin: 0 20rpx 20rpx 0;
+ flex: 0 0 calc((100% - 60rpx) / 3);
+ box-sizing: border-box;
+ padding: 0 10rpx;
+ border-radius: 30rpx;
+ height: 60rpx;
+ line-height: 60rpx;
+ text-align: center;
+ font-size: 22rpx;
+ color: #5d5d5d;
+ background-color: #eee;
+}
+.text-overflow {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.js b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.js
new file mode 100644
index 0000000..ddf6367
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.js
@@ -0,0 +1,68 @@
+Component({
+ externalClasses: ['custom-class'],
+
+ properties: {
+ activeKey: {
+ type: Number,
+ value: 0,
+ },
+ tabList: {
+ type: Array,
+ value: [],
+ },
+ showMore: Boolean, // 是否需要下拉功能
+ },
+ observers: {
+ activeKey(newVal) {
+ if (this.properties.tabList && newVal) {
+ this.setActive(newVal).catch((e) => {
+ console.error(e);
+ });
+ }
+ },
+ },
+
+ data: {
+ currentActive: -1,
+ },
+ attached() {
+ this.setActive(this.properties.activeKey).catch((e) => {
+ console.error(e);
+ });
+ },
+
+ methods: {
+ setActive(activeKey) {
+ if (
+ !this.properties.tabList[activeKey] ||
+ this.properties.tabList[activeKey].disabled
+ ) {
+ return Promise.reject('数据异常或不可操作');
+ }
+ return new Promise((resolve) => {
+ this.setData(
+ {
+ currentActive: activeKey,
+ },
+ () => resolve(),
+ );
+ });
+ },
+ onClick(event) {
+ let activeKey;
+ if (event.type === 'select') {
+ activeKey = event.detail;
+ } else {
+ activeKey = event.currentTarget.dataset.index;
+ }
+ this.setActive(activeKey)
+ .then(() => {
+ const { currentActive } = this.data;
+ this.triggerEvent('change', { index: currentActive });
+ })
+ .catch((e) => {
+ console.error(e);
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.json b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.json
new file mode 100644
index 0000000..644e632
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "c-tabbar-more": "./c-tabbar-more/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.wxml b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.wxml
new file mode 100644
index 0000000..1227f88
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.wxml
@@ -0,0 +1,29 @@
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.wxss b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.wxss
new file mode 100644
index 0000000..d89475f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/components/c-tabbar/index.wxss
@@ -0,0 +1,53 @@
+.c-tabbar {
+ width: 100%;
+ height: 100%;
+ position: relative;
+ --tabbar-height: 100rpx;
+ --tabbar-fontsize: 28rpx;
+ --tabbar-background-color: white;
+}
+.c-tabbar__inner {
+ display: flex;
+ flex-flow: row nowrap;
+}
+.c-tabbar__scroll {
+ position: relative;
+}
+.c-tabbar__scroll::after {
+ content: '';
+ display: block;
+ position: absolute;
+ width: 100%;
+ left: 0;
+ bottom: -1px;
+ height: 1px;
+ background-color: #eee;
+ z-index: 1;
+}
+.c-tabbar__inner.c-tabbar__inner_more::after {
+ content: '';
+ display: block;
+ width: 100rpx;
+ height: 100rpx;
+ flex: none;
+}
+.c-tabbar-item {
+ flex: none;
+ height: 100rpx;
+ color: #282828;
+ font-size: 28rpx;
+ padding: 0 20rpx;
+}
+.c-tabbar-item.active:not(.disabled) {
+ color: #0071ce;
+ position: relative;
+}
+.c-tabbar-item.disabled {
+ color: #ccc;
+}
+.c-tabbar-item__text {
+ width: 100%;
+ text-align: center;
+ height: 100rpx;
+ line-height: 100rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.js b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.js
new file mode 100644
index 0000000..8ea282f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.js
@@ -0,0 +1,69 @@
+Component({
+ externalClasses: ['custom-class'],
+
+ properties: {
+ category: {
+ type: Array,
+ },
+ initActive: {
+ type: Array,
+ value: [],
+ observer(newVal, oldVal) {
+ if (newVal[0] !== oldVal[0]) {
+ this.setActiveKey(newVal[0], 0);
+ }
+ },
+ },
+ isSlotRight: {
+ type: Boolean,
+ value: false,
+ },
+ level: {
+ type: Number,
+ value: 2,
+ },
+ },
+ data: {
+ activeKey: 0,
+ subActiveKey: 0,
+ },
+ attached() {
+ if (this.properties.initActive && this.properties.initActive.length > 0) {
+ this.setData({
+ activeKey: this.properties.initActive[0],
+ subActiveKey: this.properties.initActive[1] || 0,
+ });
+ }
+ },
+ methods: {
+ onParentChange(event) {
+ this.setActiveKey(event.detail.index, 0).then(() => {
+ this.triggerEvent('change', [this.data.activeKey, this.data.subActiveKey]);
+ });
+ },
+ onChildChange(event) {
+ this.setActiveKey(this.data.activeKey, event.detail.index).then(() => {
+ this.triggerEvent('change', [this.data.activeKey, this.data.subActiveKey]);
+ });
+ },
+ changCategory(event) {
+ const { item } = event.currentTarget.dataset;
+ this.triggerEvent('changeCategory', {
+ item,
+ });
+ },
+ setActiveKey(key, subKey) {
+ return new Promise((resolve) => {
+ this.setData(
+ {
+ activeKey: key,
+ subActiveKey: subKey,
+ },
+ () => {
+ resolve();
+ },
+ );
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.json b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.json
new file mode 100644
index 0000000..8ca3d3c
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.json
@@ -0,0 +1,9 @@
+{
+ "component": true,
+ "usingComponents": {
+ "c-tabbar": "./components/c-tabbar/index",
+ "c-sidebar": "./components/c-sidebar/index",
+ "c-sidebar-item": "./components/c-sidebar/c-sidebar-item/index",
+ "t-image": "/components/webp-image/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.wxml b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.wxml
new file mode 100644
index 0000000..c183ff9
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.wxml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.name}}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.wxss b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.wxss
new file mode 100644
index 0000000..fbf4569
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/components/goods-category/index.wxss
@@ -0,0 +1,102 @@
+.goods-category {
+ display: flex;
+}
+
+.custom-sidebar {
+ height: 100%;
+}
+
+.goods-category__right {
+ height: 100%;
+ flex: auto;
+ width: 0;
+ position: relative;
+ overflow: scroll;
+ -webkit-overflow-scrolling: touch;
+ background-color: white;
+}
+
+.flex {
+ display: flex;
+}
+
+.goods-category-normal {
+ margin: 28rpx 34rpx 0rpx 32rpx;
+}
+
+.goods-category-normal-item-title {
+ font-size: 28rpx;
+ font-weight: 500;
+}
+
+.goods-category-normal-item-container {
+ background-color: #fff;
+ border-radius: 8rpx;
+ padding-top: 28rpx;
+ margin-top: -24rpx;
+ margin-bottom: 30rpx;
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.goods-category-normal-item-container-item {
+ height: 196rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin-top: 24rpx;
+ width: 33.3%;
+}
+
+.goods-category-normal-item-container-item .image {
+ width: 144rpx;
+ height: 144rpx;
+}
+
+.goods-category-normal-item-container-item-title {
+ justify-content: center;
+ font-size: 24rpx;
+ color: #666666;
+ margin-top: 20rpx;
+}
+
+.goods-category .custom-sidebar {
+ background-color: #f5f5f5;
+}
+
+.custom-sidebar {
+ width: 180rpx;
+ height: 100vh;
+}
+
+.custom-sidebar::-webkit-scrollbar {
+ width: 0;
+ height: 0;
+ color: transparent;
+}
+
+.goods-category-normal-item-second-container {
+ background-color: #fff;
+ border-radius: 8rpx;
+ margin-top: 8rpx;
+ margin-bottom: 30rpx;
+ display: grid;
+ grid-template-columns: 33.33% 33.33% 33.33%;
+}
+
+.goods-category-normal-item-second-container-item {
+ height: 200rpx;
+ text-align: center;
+ margin-top: 20rpx;
+}
+
+.goods-category-normal-item-second-container-item .image {
+ width: 144rpx;
+ height: 144rpx;
+}
+
+.goods-category-normal-item-second-container-item-title {
+ justify-content: center;
+ font-size: 24rpx;
+ color: #222427;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/category/index.js b/miniprogram/tcb-shop/pages/goods/category/index.js
new file mode 100644
index 0000000..b7867d7
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/index.js
@@ -0,0 +1,36 @@
+import Toast from 'tdesign-miniprogram/toast/index';
+import { getCates } from '../../../services/cate/cate';
+
+Page({
+ data: {
+ cates: [],
+ },
+ async init() {
+ try {
+ const cates = await getCates();
+ this.setData({ cates });
+ } catch (e) {
+ console.error('获取商品分类列表失败', e);
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '获取商品分类列表失败',
+ duration: 1000,
+ icon: '',
+ });
+ }
+ },
+
+ onShow() {
+ this.getTabBar().init();
+ },
+ onChange(e) {
+ const cateId = e?.detail?.item?._id;
+ wx.navigateTo({
+ url: `/pages/goods/list/index?cateId=${cateId}`,
+ });
+ },
+ onLoad() {
+ this.init(true);
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/category/index.json b/miniprogram/tcb-shop/pages/goods/category/index.json
new file mode 100644
index 0000000..0bb1263
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/index.json
@@ -0,0 +1,7 @@
+{
+ "navigationBarTitleText": "分类",
+ "usingComponents": {
+ "goods-category": "./components/goods-category/index",
+ "t-toast": "tdesign-miniprogram/toast/toast"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/category/index.wxml b/miniprogram/tcb-shop/pages/goods/category/index.wxml
new file mode 100644
index 0000000..cdfd458
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/index.wxml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/category/index.wxss b/miniprogram/tcb-shop/pages/goods/category/index.wxss
new file mode 100644
index 0000000..ac2e0a7
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/category/index.wxss
@@ -0,0 +1,23 @@
+.tabbar-position {
+ position: fixed !important;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+}
+
+.wrap {
+ height: 100vh;
+ overflow: hidden;
+}
+.goods-category-class {
+ background-color: #f6f6f6 !important;
+ height: 100%;
+}
+.goods-category-class .goods-category-normal-item-container-item {
+ margin-top: 20rpx;
+}
+
+page {
+ min-height: none;
+ padding-bottom: 0;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/assets/play.png b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/assets/play.png
new file mode 100644
index 0000000..d816ef7
Binary files /dev/null and b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/assets/play.png differ
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.js b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.js
new file mode 100644
index 0000000..a0a3c48
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.js
@@ -0,0 +1,36 @@
+// pages/goods/comments/components/comments-card/images-videos/index.js
+Component({
+ /**
+ * 组件的属性列表
+ */
+ properties: {
+ resources: {
+ type: Array,
+ value: [],
+ },
+ },
+
+ /**
+ * 组件的初始数据
+ */
+ data: {
+ classType: 'single',
+ },
+
+ observers: {
+ resources: function (newVal) {
+ if (newVal.length <= 1) {
+ this.setData({ classType: 'single' });
+ } else if (newVal.length === 2) {
+ this.setData({ classType: 'double' });
+ } else {
+ this.setData({ classType: 'multiple' });
+ }
+ },
+ },
+
+ /**
+ * 组件的方法列表
+ */
+ methods: {},
+});
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.json b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.json
new file mode 100644
index 0000000..83597fd
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "my-video": "../my-video/index",
+ "t-image": "/components/webp-image/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.wxml b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.wxml
new file mode 100644
index 0000000..2515909
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.wxml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.wxss b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.wxss
new file mode 100644
index 0000000..e7944be
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/images-videos/index.wxss
@@ -0,0 +1,68 @@
+.resource-item-single {
+ width: 360rpx;
+ height: 360rpx;
+ border-radius: 8rpx;
+}
+
+.resource-item-double {
+ width: 334rpx;
+ height: 334rpx;
+ border-radius: 8rpx;
+}
+
+.resource-item-multiple {
+ width: 218rpx;
+ height: 218rpx;
+ border-radius: 8rpx;
+}
+
+.resource-container-single {
+ padding-left: 0;
+ padding-top: 0;
+}
+
+.resource-container-double {
+ padding-left: 18rpx;
+ padding-top: 18rpx;
+}
+
+.resource-container-multiple {
+ padding-left: 16rpx;
+ padding-top: 16rpx;
+}
+
+.container-single {
+ margin-left: 0;
+}
+
+.container-double {
+ margin-left: -18rpx;
+ margin-top: -18rpx;
+}
+
+.container-multiple {
+ margin-left: -16rpx;
+ margin-top: -16rpx;
+}
+
+.resource-container {
+ display: flex;
+}
+
+.play-icon {
+ width: 96rpx;
+ height: 96rpx;
+}
+
+.images-videos-container {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.image {
+ border-radius: 8rpx;
+}
+
+.cover-img-container {
+ background-color: white;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.js b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.js
new file mode 100644
index 0000000..987cf18
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.js
@@ -0,0 +1,55 @@
+Component({
+ externalClasses: ['my-video', 'my-cover-img', 'my-play-icon'],
+ properties: {
+ videoSrc: { type: String },
+ },
+ data: {
+ isShow: true,
+ },
+
+ options: {
+ multipleSlots: true, // 在组件定义时的选项中启用多slot支持
+ },
+
+ attached() {
+ this.videoContext = wx.createVideoContext('myVideo', this);
+ },
+
+ fullScreen: false,
+
+ methods: {
+ // 点击封面自定义播放按钮时触发
+ bindplay(e) {
+ this.setData({
+ isShow: false,
+ });
+ this.videoContext.play();
+ this.triggerEvent('play', e);
+ },
+
+ bindplayByVideo(e) {
+ this.setData({
+ isShow: false,
+ });
+ this.triggerEvent('play', e);
+ },
+
+ // 监听播放到末尾时触发
+ bindended(e) {
+ if (!this.fullScreen) {
+ this.setData({
+ isShow: true,
+ });
+ }
+ this.triggerEvent('ended', e);
+ },
+ // 监听暂停播放时触发
+ bindpause(e) {
+ this.triggerEvent('pause', e);
+ },
+ bindfullscreenchange(e) {
+ const fullScreen = e?.detail?.fullScreen;
+ this.fullScreen = fullScreen;
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.json b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.json
new file mode 100644
index 0000000..e8cfaaf
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.json
@@ -0,0 +1,4 @@
+{
+ "component": true,
+ "usingComponents": {}
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.wxml b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.wxml
new file mode 100644
index 0000000..af38919
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.wxml
@@ -0,0 +1,26 @@
+
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.wxss b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.wxss
new file mode 100644
index 0000000..63b962b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/components/my-video/index.wxss
@@ -0,0 +1,21 @@
+.video .video_cover {
+ width: 100%;
+ height: 100%;
+ position: relative;
+}
+
+.video .video_play_icon {
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ transform: translate(-50%, -50%);
+ z-index: 5;
+}
+
+.video .video_txt {
+ margin: 10rpx auto;
+}
+
+.video {
+ display: flex;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.js b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.js
new file mode 100644
index 0000000..53f6103
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.js
@@ -0,0 +1,51 @@
+Component({
+ externalClasses: ['wr-class'],
+ options: {
+ multipleSlots: true,
+ },
+ properties: {
+ goodsDetailInfo: {
+ type: String,
+ value: '',
+ },
+ sellerReply: {
+ type: String,
+ value: '',
+ },
+ userHeadUrl: {
+ type: String,
+ value: '',
+ },
+ userName: {
+ type: String,
+ default: '',
+ },
+ commentContent: {
+ type: String,
+ value: '',
+ },
+ commentScore: {
+ type: Number,
+ value: 0,
+ },
+ commentTime: {
+ type: String,
+ value: '',
+ },
+ commentResources: {
+ type: Array,
+ value: [],
+ },
+ },
+
+ data: {
+ showMoreStatus: false,
+ showContent: false,
+ hideText: false,
+ eleHeight: null,
+ overText: false,
+ isDisabled: true,
+ startColors: ['#FFC51C', '#DDDDDD'],
+ },
+ methods: {},
+});
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.json b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.json
new file mode 100644
index 0000000..1130655
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.json
@@ -0,0 +1,8 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-rate": "tdesign-miniprogram/rate/rate",
+ "images-videos": "./components/images-videos",
+ "t-image": "/components/webp-image/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.wxml b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.wxml
new file mode 100644
index 0000000..af86507
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.wxml
@@ -0,0 +1,27 @@
+
diff --git a/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.wxss b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.wxss
new file mode 100644
index 0000000..c5e0564
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/components/comments-card/index.wxss
@@ -0,0 +1,172 @@
+@import '../../../../../style/theme.wxss';
+
+.comments-card-item {
+ padding: 32rpx;
+ display: flex;
+ background-color: #fff;
+ position: relative;
+}
+
+.comments-card-item::after {
+ content: '';
+ position: absolute;
+ bottom: 0rpx;
+ width: 686rpx;
+ height: 2rpx;
+ background-color: #f5f5f5;
+}
+
+.comments-card-item-userImg {
+ display: flex;
+}
+
+.comments-card-item-userImg .userImg {
+ width: 64rpx;
+ height: 64rpx;
+ border-radius: 50%;
+}
+
+.comments-card-item-container {
+ width: 100%;
+}
+
+.comments-card-item-container-name {
+ display: flex;
+ font-size: 28rpx;
+ color: #333;
+ font-weight: 600;
+ align-items: center;
+}
+
+.comments-card-item-container-name .userName {
+ margin-right: 12rpx;
+}
+
+.comments-card-item-container-date {
+ font-size: 22rpx;
+ color: #999;
+ margin-top: 4rpx;
+ display: flex;
+}
+
+.comments-card-item-container-content {
+ margin-top: 16rpx;
+ position: relative;
+}
+
+.comments-card-item-container-content .content-text {
+ font-size: 28rpx;
+ white-space: normal;
+ word-break: break-all;
+ font-weight: normal;
+}
+
+.comments-card-item-container-content .hide-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -webkit-line-clamp: 5;
+ text-align: justify;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+}
+
+.comments-card-item-container-content .showMore {
+ position: absolute;
+ width: 112rpx;
+ height: 36rpx;
+ bottom: 0;
+ right: 0;
+ background: linear-gradient(
+ to right,
+ rgba(255, 255, 255, 0.2) 0,
+ rgba(255, 255, 255, 0.45) 20%,
+ rgba(255, 255, 255, 0.7) 25%,
+ rgba(255, 255, 255, 0.9) 30%,
+ rgba(255, 255, 255, 0.95) 35%,
+ #ffffff 50%,
+ #fff 100%
+ );
+ font-size: 26rpx;
+ color: #fa550f;
+ line-height: 36rpx;
+ text-align: right;
+}
+
+.comments-card-item-container-image {
+ margin-top: 24rpx;
+ display: flex;
+ justify-content: space-between;
+ flex-wrap: wrap;
+}
+
+.comments-card-item-container-image .commentImg {
+ border-radius: 8rpx;
+ margin-top: 12rpx;
+}
+
+.comments-card-item-container-image .commentImg3 {
+ width: 196rpx;
+ height: 196rpx;
+}
+
+.comments-card-item-container-image .commentImg2 {
+ width: 300rpx;
+ height: 300rpx;
+}
+
+.comments-card-item-container-image .commentImg1 {
+ width: 404rpx;
+ height: 404rpx;
+}
+
+.comments-card-item-container .comments-title {
+ display: flex;
+ align-items: center;
+ position: relative;
+}
+
+.comments-title .userName {
+ font-size: 26rpx;
+ color: #333333;
+ margin-left: 24rpx;
+}
+
+.comments-title .commentTime {
+ font-size: 24rpx;
+ color: #999999;
+ position: absolute;
+ right: 0;
+}
+
+.comments-info {
+ display: flex;
+ align-items: center;
+ margin-top: 18rpx;
+}
+
+.comments-info .rate {
+ margin-right: 24rpx;
+}
+
+.comments-info .goods-info-text {
+ font-size: 24rpx;
+
+ color: #999999;
+}
+
+.comments-card-item-container .comments-card-reply {
+ background-color: #f5f5f5;
+ padding: 24rpx 16rpx;
+ margin-top: 24rpx;
+}
+
+.comments-card-item-container .comments-card-reply .prefix {
+ font-size: 26rpx;
+ font-weight: bold;
+ color: #666666;
+}
+
+.comments-card-item-container .comments-card-reply .content {
+ font-size: 26rpx;
+ color: #666666;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/comments/create-list/index.js b/miniprogram/tcb-shop/pages/goods/comments/create-list/index.js
new file mode 100644
index 0000000..922ec2c
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/create-list/index.js
@@ -0,0 +1,56 @@
+import Toast from 'tdesign-miniprogram/toast/index';
+import { getOrder } from '../../../../services/order/order';
+import { getAllOrderItemsOfAnOrder } from '../../../../services/order/orderItem';
+import { getCloudImageTempUrl } from '../../../../utils/cloudImageHandler';
+
+Page({
+ data: {
+ order: null,
+ },
+
+ async onLoad(options) {
+ const orderId = options?.orderId;
+ if (typeof orderId !== 'string') {
+ this.failedAndBack('获取订单详情失败');
+ return;
+ }
+
+ try {
+ const [order, items] = await Promise.all([getOrder(orderId), getAllOrderItemsOfAnOrder({ orderId })]);
+
+ const images = items.map((x) => x.sku.image ?? '');
+ try {
+ const handleImages = await getCloudImageTempUrl(images);
+ handleImages.forEach((image, index) => (items[index].sku.image = image));
+ } catch (e) {
+ console.error('处理商品图片失败', e);
+ }
+ order.orderItems = items;
+ this.setData({ order });
+ } catch (e) {
+ this.failedAndBack('获取订单详情失败', e);
+ }
+ },
+
+ failedAndBack(message, e) {
+ e && console.error(e);
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message,
+ });
+ setTimeout(() => wx.navigateBack(), 1000);
+ },
+
+ toComment(e) {
+ const orderItemId = e?.target?.dataset?.orderItem?._id;
+ if (typeof orderItemId !== 'string') {
+ this.failedAndBack('获取订单详情失败');
+ return;
+ }
+
+ wx.navigateTo({
+ url: `/pages/goods/comments/create/index?orderItemId=${orderItemId}`,
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/comments/create-list/index.json b/miniprogram/tcb-shop/pages/goods/comments/create-list/index.json
new file mode 100644
index 0000000..b594baf
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/create-list/index.json
@@ -0,0 +1,9 @@
+{
+ "navigationBarTitleText": "选择评价商品",
+ "usingComponents": {
+ "order-card": "../../../order/components/order-card",
+ "specs-goods-card": "../../../order/components/specs-goods-card/index",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-toast": "tdesign-miniprogram/toast/toast"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/comments/create-list/index.wxml b/miniprogram/tcb-shop/pages/goods/comments/create-list/index.wxml
new file mode 100644
index 0000000..c2e2a2a
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/create-list/index.wxml
@@ -0,0 +1,22 @@
+
+
+
+
+ 订单号
+ {{order._id}}
+
+
+
+
+
+
+ 评价
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/comments/create-list/index.wxss b/miniprogram/tcb-shop/pages/goods/comments/create-list/index.wxss
new file mode 100644
index 0000000..dc90d1f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/create-list/index.wxss
@@ -0,0 +1,54 @@
+:host {
+ width: 100%;
+}
+.btn-bar {
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ line-height: 1;
+}
+.btn-bar .order-btn {
+ line-height: 1;
+ /* border-radius: unset; */
+ /* min-width: 160rpx; */
+}
+
+.btn-bar .right {
+ display: flex;
+ align-items: center;
+}
+.btn-bar .t-button {
+ width: 160rpx;
+ font-weight: 400;
+ margin-left: 24rpx;
+}
+.btn-bar .t-button--max {
+ width: 176rpx;
+ margin-left: 24rpx;
+
+ --td-button-extra-small-height: 72rpx;
+}
+
+.btn-bar .left .delete-btn {
+ font-size: 22rpx;
+}
+.btn-bar .left .delete-btn::after {
+ display: none;
+}
+
+.btn-bar .right .normal {
+ --td-button-default-color: #333333;
+ --td-button-default-border-color: #dddddd;
+}
+
+.btn-bar .right .primary {
+ --td-button-default-color: #fff;
+ --td-button-default-bg-color: #fa4126;
+ --td-button-default-border-color: #fa4126;
+ --td-button-default-active-bg-color: #fa42269c;
+}
+
+.t-button {
+ --td-button-default-color: #000;
+ --td-button-primary-text-color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/comments/create/index.js b/miniprogram/tcb-shop/pages/goods/comments/create/index.js
new file mode 100644
index 0000000..7c3e081
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/create/index.js
@@ -0,0 +1,114 @@
+import Toast from 'tdesign-miniprogram/toast/index';
+import { getOrderItem } from '../../../../services/order/orderItem';
+import { getSkuDetail } from '../../../../services/sku/sku';
+import { getCloudImageTempUrl } from '../../../../utils/cloudImageHandler';
+import { createComment } from '../../../../services/comments/comments';
+
+Page({
+ data: {
+ sku: null,
+ },
+
+ toast(message) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message,
+ });
+ },
+
+ failedAndBack(message, e) {
+ e && console.error(e);
+ this.toast(message);
+ setTimeout(() => {
+ wx.navigateBack();
+ }, 1000);
+ },
+
+ async onLoad(options) {
+ const orderItemId = options.orderItemId;
+ if (typeof orderItemId !== 'string') return this.failedAndBack('获取订单失败');
+
+ const orderItem = await getOrderItem(orderItemId);
+
+ const comment = orderItem?.comment;
+ if (comment != null) return this.failedAndBack('已经评价过了');
+
+ let sku = orderItem?.sku;
+ if (sku == null) return this.failedAndBack('获取商品信息失败');
+
+ try {
+ sku = await getSkuDetail(sku._id);
+ sku.image = (await getCloudImageTempUrl([sku.image ?? '']))[0];
+ sku.spec = sku.attr_value.map((x) => x.value).join(',');
+ this.setData({ sku, orderItemId });
+ } catch (e) {
+ return this.failedAndBack('获取商品信息失败', e);
+ }
+ },
+
+ onRateChange(e) {
+ const { value } = e?.detail;
+ const item = e?.currentTarget?.dataset?.item;
+ this.setData({ [item]: value }, () => {
+ this.updateButtonStatus();
+ });
+ },
+
+ onAnonymousChange(e) {
+ const status = !!e?.detail?.checked;
+ this.setData({ isAnonymous: status });
+ },
+
+ handleSuccess(e) {
+ const { files } = e.detail;
+
+ this.setData({
+ uploadFiles: files,
+ });
+ },
+
+ handleRemove(e) {
+ const { index } = e.detail;
+ const { uploadFiles } = this.data;
+ uploadFiles.splice(index, 1);
+ this.setData({
+ uploadFiles,
+ });
+ },
+
+ onTextAreaChange(e) {
+ const value = e?.detail?.value;
+ this.textAreaValue = value;
+ this.updateButtonStatus();
+ },
+
+ updateButtonStatus() {
+ const { goodRateValue, isAllowedSubmit } = this.data;
+ const { textAreaValue } = this;
+ const temp = Boolean(goodRateValue) && Boolean(textAreaValue);
+ if (temp !== isAllowedSubmit) this.setData({ isAllowedSubmit: temp });
+ },
+
+ async onSubmitBtnClick() {
+ const { goodRateValue, isAllowedSubmit, orderItemId, sku } = this.data;
+ const { textAreaValue } = this;
+
+ if (!isAllowedSubmit) return;
+
+ try {
+ await createComment({ orderItemId, content: textAreaValue, rating: goodRateValue, spuId: sku.spu._id });
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '评价提交成功',
+ icon: 'check-circle',
+ });
+ setTimeout(() => {
+ wx.navigateBack();
+ }, 1000);
+ } catch (e) {
+ return this.failedAndBack('创建评论失败', e);
+ }
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/comments/create/index.json b/miniprogram/tcb-shop/pages/goods/comments/create/index.json
new file mode 100644
index 0000000..7c6d3eb
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/create/index.json
@@ -0,0 +1,13 @@
+{
+ "navigationBarTitleText": "评价商品",
+ "usingComponents": {
+ "t-image": "/components/webp-image/index",
+ "t-rate": "tdesign-miniprogram/rate/rate",
+ "t-textarea": "tdesign-miniprogram/textarea/textarea",
+ "t-checkbox": "tdesign-miniprogram/checkbox/checkbox",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-upload": "tdesign-miniprogram/upload/upload",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-toast": "tdesign-miniprogram/toast/toast"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/comments/create/index.wxml b/miniprogram/tcb-shop/pages/goods/comments/create/index.wxml
new file mode 100644
index 0000000..2bd1cc8
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/create/index.wxml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/comments/create/index.wxss b/miniprogram/tcb-shop/pages/goods/comments/create/index.wxss
new file mode 100644
index 0000000..abf20e4
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/create/index.wxss
@@ -0,0 +1,168 @@
+page {
+ background-color: #f5f5f5;
+}
+
+.page-container .comment-card {
+ padding: 24rpx 32rpx 28rpx;
+ background-color: #ffffff;
+}
+
+.comment-card .goods-info-container .goods-image {
+ width: 112rpx;
+ height: 112rpx;
+ border-radius: 8rpx;
+}
+
+.comment-card .goods-info-container {
+ display: flex;
+ align-items: center;
+}
+
+.comment-card .goods-info-container .goods-title-container {
+ padding-left: 24rpx;
+}
+
+.comment-card .goods-info-container .goods-title {
+ font-size: 28rpx;
+ font-weight: normal;
+}
+
+.comment-card .goods-info-container .goods-detail {
+ font-size: 24rpx;
+ font-weight: normal;
+ color: #999999;
+ margin-top: 16rpx;
+}
+
+.comment-card .rate-container {
+ display: flex;
+ align-items: center;
+ margin-top: 22rpx;
+}
+
+.comment-card .rate-container .rate-title {
+ font-size: 28rpx;
+ font-weight: bold;
+ margin-right: 12rpx;
+}
+
+.comment-card .textarea-container {
+ margin-top: 22rpx;
+}
+
+.comment-card .textarea-container .textarea {
+ height: 294rpx;
+ background-color: #f5f5f5;
+ border-radius: 16rpx;
+ font-size: 28rpx;
+ font-weight: normal;
+}
+
+.page-container .t-checkbox__bordered {
+ display: none;
+}
+
+.page-container .anonymous-box {
+ display: flex;
+ align-items: center;
+ padding-top: 52rpx;
+}
+
+.page-container .anonymous-box .name {
+ font-size: 28rpx;
+ font-weight: normal;
+ color: #999999;
+ padding-left: 28rpx;
+}
+
+.page-container .t-checkbox {
+ padding: 0rpx !important;
+}
+
+.page-container .t-checkbox__content {
+ display: none;
+}
+
+.comment-card .convey-comment-title {
+ font-size: 28rpx;
+ font-weight: bold;
+}
+
+.convey-card {
+ background-color: #ffffff;
+ margin-top: 24rpx;
+ padding: 32rpx;
+ padding-bottom: calc(env(safe-area-inset-bottom) + 140rpx);
+}
+
+.convey-card .rate-container .rate-title {
+ font-weight: normal;
+}
+
+.page-container .t-checkbox__icon-left {
+ margin-right: 0rpx !important;
+}
+
+.submit-button-container {
+ padding: 12rpx 32rpx;
+ display: flex;
+ width: 100vw;
+ box-sizing: border-box;
+ justify-content: center;
+ position: fixed;
+ bottom: 0;
+ padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
+ background-color: #ffffff;
+ z-index: 99;
+}
+
+.submit-button-container .submit-button {
+ --td-button-default-color: #fff;
+ --td-button-default-bg-color: #fa4126;
+ --td-button-default-border-color: #fa4126;
+ --td-button-default-active-bg-color: #fa42269c;
+}
+
+.submit-button-container .submit-button-disabled {
+ --td-button-default-color: #fff;
+ --td-button-default-bg-color: #ccc;
+ --td-button-default-border-color: #ccc;
+ --td-button-default-active-bg-color: rgba(204, 204, 204, 0.789);
+}
+
+.page-container .upload-container {
+ margin-top: 24rpx;
+}
+
+.page-container .t-upload__wrapper {
+ border-radius: 8rpx;
+ overflow: hidden;
+}
+
+.page-container .submmit-bar {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 12;
+ padding: 12rpx 32rpx;
+ padding-bottom: env(safe-area-inset-bottom);
+ background-color: #fff;
+ height: 112rpx;
+}
+
+.page-container .submmit-bar-button {
+ border-radius: 48rpx !important;
+ padding: 0 !important;
+}
+
+.page-container .t-upload__close-btn {
+ background-color: rgba(0, 0, 0, 0.4);
+ border-bottom-left-radius: 8rpx;
+ width: 36rpx;
+ height: 36rpx;
+}
+
+.upload-container .upload-addcontent-slot {
+ font-size: 26rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/comments/index.js b/miniprogram/tcb-shop/pages/goods/comments/index.js
new file mode 100644
index 0000000..4171e0a
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/index.js
@@ -0,0 +1,71 @@
+import { getSkuDetail } from '../../../services/sku/sku';
+import { getOrderItem } from '../../../services/order/orderItem';
+import { getCommentsOfSpu } from '../../../services/comments/comments';
+import { LIST_LOADING_STATUS } from '../../../utils/listLoading';
+import dayjs from 'dayjs';
+
+const layoutMap = {
+ 0: 'vertical',
+};
+Page({
+ data: {
+ commentList: [],
+ layoutText: layoutMap[0],
+ loadMoreStatus: 0,
+ spuId: null,
+ },
+
+ pageNumber: 1,
+ pageSize: 10,
+
+ async loadComments(refresh = false) {
+ refresh && (this.pageNumber = 1);
+
+ this.setData({ loadMoreStatus: LIST_LOADING_STATUS.LOADING });
+
+ const { spuId } = this.data;
+ const { pageNumber, pageSize } = this;
+ try {
+ const { records: newComments, total } = await getCommentsOfSpu({ spuId, pageNumber, pageSize });
+ await Promise.all(
+ newComments.map(async (x) => {
+ const orderItemId = x.order_item._id;
+ const {
+ sku: { _id: skuId },
+ } = await getOrderItem(orderItemId);
+ const sku = await getSkuDetail(skuId);
+ x.desc = sku.attr_value.map((x) => x.value).join(',');
+ x.createdTimeString = dayjs(new Date(x.createdAt)).format('YYYY-MM-DD HH:mm:ss');
+ x.user = x.createBy.substring(0, 10);
+ }),
+ );
+
+ this.pageNumber++;
+ const commentList = refresh ? newComments : this.data.commentList.concat(newComments);
+ if (commentList.length >= total) {
+ this.setData;
+ }
+ this.setData({
+ commentList,
+ loadMoreStatus: commentList.length >= total ? LIST_LOADING_STATUS.NO_MORE : LIST_LOADING_STATUS.READY,
+ });
+ } catch (e) {
+ console.error(e);
+ this.setData({ loadMoreStatus: LIST_LOADING_STATUS.FAILED });
+ }
+ },
+
+ async onLoad(options) {
+ const spuId = options?.spuId;
+ if (typeof spuId !== 'string') {
+ this.setData({ loadMoreStatus: LIST_LOADING_STATUS.FAILED });
+ } else {
+ this.setData({ spuId });
+ }
+
+ this.loadComments(true);
+ },
+ onReachBottom() {
+ if (this.data.loadMoreStatus == LIST_LOADING_STATUS.READY) this.loadComments(false);
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/comments/index.json b/miniprogram/tcb-shop/pages/goods/comments/index.json
new file mode 100644
index 0000000..a6418c9
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/index.json
@@ -0,0 +1,8 @@
+{
+ "navigationBarTitleText": "全部评价",
+ "usingComponents": {
+ "t-tag": "tdesign-miniprogram/tag/tag",
+ "comments-card": "./components/comments-card/index",
+ "t-load-more": "/components/load-more/index"
+ }
+}
diff --git a/miniprogram/tcb-shop/pages/goods/comments/index.wxml b/miniprogram/tcb-shop/pages/goods/comments/index.wxml
new file mode 100644
index 0000000..66a4514
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/index.wxml
@@ -0,0 +1,6 @@
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/comments/index.wxss b/miniprogram/tcb-shop/pages/goods/comments/index.wxss
new file mode 100644
index 0000000..b98f36b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/comments/index.wxss
@@ -0,0 +1,49 @@
+/* 层级定义
+@z-index-0: 1;
+@z-index-1: 100;
+@z-index-2: 200;
+@z-index-5: 500;
+@z-index-component: 1000; // 通用组件级别
+@z-index-dropdown: @z-index-component;
+@z-index-sticky: @z-index-component + 20;
+@z-index-fixed: @z-index-component + 30;
+@z-index-modal-backdrop:@z-index-component + 40;
+@z-index-modal:@z-index-component + 50;
+@z-index-popover:@z-index-component + 60;
+@z-index-tooltip:@z-index-component + 70;
+*/
+/* var() css变量适配*/
+page {
+ background-color: #FFFFFF;
+}
+
+.comments-header {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 32rpx 32rpx 0rpx;
+ background-color: #fff;
+ margin-top: -24rpx;
+ margin-left: -24rpx;
+}
+
+.comments-header-tag {
+ margin-top: 24rpx;
+ margin-left: 24rpx;
+ height: 56rpx !important;
+ font-size: 24rpx !important;
+ justify-content: center;
+ background-color: #F5F5F5 !important;
+ border-radius: 8rpx !important;
+ border: 1px solid #F5F5F5 !important;
+}
+
+.comments-header-active {
+ background-color: #FFECE9 !important;
+ color: #FA4126 !important;
+ border: 1px solid #FA4126 !important;
+}
+
+.no-more {
+ padding-left: 20rpx;
+ padding-right: 20rpx;
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.js b/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.js
new file mode 100644
index 0000000..ae274e2
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.js
@@ -0,0 +1,66 @@
+Component({
+ externalClasses: ['wr-sold-out', 'wr-class'],
+
+ options: { multipleSlots: true },
+
+ properties: {
+ soldout: {
+ // 商品是否下架
+ type: Boolean,
+ value: false,
+ },
+ jumpArray: {
+ type: Array,
+ value: [],
+ },
+ isStock: {
+ type: Boolean,
+ value: true,
+ }, // 是否有库存
+ isSlotButton: {
+ type: Boolean,
+ value: false,
+ }, // 是否开启按钮插槽
+ shopCartNum: {
+ type: Number, // 购物车气泡数量
+ },
+ buttonType: {
+ type: Number,
+ value: 0,
+ },
+ minDiscountPrice: {
+ type: String,
+ value: '',
+ },
+ minSalePrice: {
+ type: String,
+ value: '',
+ },
+ },
+
+ data: {
+ fillPrice: false,
+ },
+
+ methods: {
+ toAddCart() {
+ const { isStock } = this.properties;
+ if (!isStock) return;
+ this.triggerEvent('toAddCart');
+ },
+
+ toBuyNow(e) {
+ const { isStock } = this.properties;
+ if (!isStock) return;
+ this.triggerEvent('toBuyNow', e);
+ },
+
+ toNav(e) {
+ const { url } = e.currentTarget.dataset;
+ return this.triggerEvent('toNav', {
+ e,
+ url,
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.json b/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.json
new file mode 100644
index 0000000..7464ae6
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-icon": "tdesign-miniprogram/icon/icon"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.wxml b/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.wxml
new file mode 100644
index 0000000..604afb4
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.wxml
@@ -0,0 +1,38 @@
+
+ {{soldout ? '商品已下架' : '商品已售馨'}}
+
+
+
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.wxss b/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.wxss
new file mode 100644
index 0000000..44e8ad0
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/buy-bar/index.wxss
@@ -0,0 +1,107 @@
+.footer-cont {
+ background-color: #fff;
+ padding: 16rpx;
+}
+
+.icon-warp {
+ width: 110rpx;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+}
+
+.operate-wrap {
+ position: relative;
+}
+
+.bottom-operate-left {
+ width: 100%;
+}
+
+.bottom-operate-left .icon-warp {
+ width: 50%;
+}
+
+.tag-cart-num {
+ display: inline-block;
+ position: absolute;
+ left: 50rpx;
+ right: auto;
+ top: 6rpx;
+ color: #fff;
+ line-height: 24rpx;
+ text-align: center;
+ z-index: 99;
+ white-space: nowrap;
+ min-width: 28rpx;
+ border-radius: 14rpx;
+ background-color: #fa550f !important;
+ font-size: 20rpx;
+ font-weight: 400;
+ padding: 2rpx 6rpx;
+}
+
+.operate-text {
+ color: #666;
+ font-size: 20rpx;
+}
+
+.soldout {
+ height: 80rpx;
+ background: rgba(170, 170, 170, 1);
+ width: 100%;
+ color: #fff;
+}
+
+.addCart-disabled,
+.bar-addCart-disabled {
+ background: rgba(221, 221, 221, 1) !important;
+ color: #fff !important;
+ font-size: 28rpx;
+}
+
+.buyNow-disabled,
+.bar-buyNow-disabled {
+ background: rgba(198, 198, 198, 1) !important;
+ color: #fff !important;
+ font-size: 28rpx;
+}
+
+.bar-separately,
+.bar-buy {
+ width: 254rpx;
+ height: 80rpx;
+ color: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.bar-separately {
+ background: #ffece9;
+ color: #fa4126;
+ border-radius: 40rpx 0 0 40rpx;
+}
+
+.bar-buy {
+ background-color: #fa4126;
+ border-radius: 0rpx 40rpx 40rpx 0rpx;
+}
+
+.flex {
+ display: flex;
+ display: -webkit-flex;
+}
+
+.flex-center {
+ justify-content: center;
+ -webkit-justify-content: center;
+ align-items: center;
+ -webkit-align-items: center;
+}
+
+.flex-between {
+ justify-content: space-between;
+ -webkit-justify-content: space-between;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.js b/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.js
new file mode 100644
index 0000000..e43fac2
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.js
@@ -0,0 +1,303 @@
+const ATTR_VALUE_STATUS = {
+ PICKED: 'picked',
+ UNPICKED: 'unpicked',
+ DISABLED: 'disabled',
+};
+
+Component({
+ options: {
+ multipleSlots: true,
+ addGlobalClass: true,
+ },
+
+ properties: {
+ title: String,
+ show: {
+ type: Boolean,
+ value: false,
+ },
+ initProps: {
+ type: Object,
+ observer(initProps) {
+ if (!initProps) {
+ return;
+ }
+ const { skus, spu } = initProps;
+
+ const attrValues = collectAttrValueSet(skus);
+ const attrList = initAttrList(attrValues);
+
+ this.calculateValue(attrList, skus);
+
+ this.setData({
+ skus,
+ attrList,
+ spu,
+ });
+ },
+ },
+ outOperateStatus: {
+ type: String,
+ value: 'no',
+ },
+ },
+
+ data: {
+ price: 0,
+ imgSrc: '',
+ max: 1,
+
+ skus: [],
+ attrList: [],
+ spu: null,
+ pickedSku: null,
+ value: 1,
+ },
+
+ observers: {
+ 'spu, pickedSku': function (spu, pickedSku) {
+ const imgSrc = pickedSku?.image ? pickedSku.image : spu.cover_image;
+ this.setData({
+ imgSrc,
+ });
+ },
+ 'skus, pickedSku': function (skus, pickedSku) {
+ let max;
+ let price;
+ if (pickedSku != null) {
+ max = pickedSku.count;
+ price = pickedSku.price;
+ } else {
+ price = skus.reduce((acc, cur) => Math.min(acc, cur.price), Infinity);
+ max = 1;
+ }
+ this.setData({ price, max });
+ },
+ max: function (max) {
+ const { value } = this.data;
+ if (value > max) {
+ this.setData({
+ value: max,
+ });
+ }
+ },
+ 'attrList, skus': function (attrList, skus) {
+ this.calculateValue(attrList, skus);
+ const sku = this.pickOnlySku(attrList);
+ sku && this.triggerEvent('picked');
+ this.setData({ pickedSku: sku });
+ },
+ },
+
+ methods: {
+ clickAttrValue({
+ target: {
+ dataset: { attrValueIndex, attrNameIndex },
+ },
+ }) {
+ const setAttrListWithCalculation = (attrList) => {
+ const { skus } = this.data;
+ this.calculateValue(attrList, skus);
+ const sku = this.pickOnlySku(attrList);
+ this.setData({ attrList, pickedSku: sku });
+ };
+ const { attrList } = this.data;
+
+ const attrName = attrList[attrNameIndex];
+ const attrValue = attrName.values[attrValueIndex];
+
+ switch (attrValue.status) {
+ case ATTR_VALUE_STATUS.UNPICKED:
+ // pick it, and set others unpicked
+ attrName.values.forEach((value) => {
+ value.status = value === attrValue ? ATTR_VALUE_STATUS.PICKED : ATTR_VALUE_STATUS.UNPICKED;
+ });
+ setAttrListWithCalculation(attrList);
+ break;
+ case ATTR_VALUE_STATUS.DISABLED:
+ // do nothing
+ break;
+ case ATTR_VALUE_STATUS.PICKED:
+ // unpick it
+ attrValue.status = ATTR_VALUE_STATUS.UNPICKED;
+ setAttrListWithCalculation(attrList);
+ break;
+ default:
+ // invalid status, skip
+ return;
+ }
+ },
+
+ /**
+ *
+ * @param {[]} attrList
+ * @param {[]} skus
+ */
+ calculateValue(attrList, skus) {
+ // Any row of attr values, should be filter out by picked attr values from rest of the rows.
+ attrList.forEach((attrName, index) => {
+ const restPickedValues = attrList
+ .filter((_, i) => i !== index) // exclude current line
+ .map((x) => x.values.find((y) => y.status === ATTR_VALUE_STATUS.PICKED))
+ .filter((x) => x != null);
+
+ const filteredSkus = skus.filter(
+ (sku) =>
+ sku.count > 0 &&
+ contains({
+ container: sku.attrValues,
+ arr: restPickedValues,
+ eq: (a, b) => a._id === b._id,
+ }),
+ );
+ const filteredAttrValues = collectAttrValueSet(filteredSkus);
+
+ attrName.values.forEach((value) => {
+ if (filteredAttrValues.find((x) => x._id === value._id) == null) {
+ value.status = ATTR_VALUE_STATUS.DISABLED;
+ } else {
+ if (value.status === ATTR_VALUE_STATUS.DISABLED) {
+ value.status = ATTR_VALUE_STATUS.UNPICKED;
+ }
+ }
+ });
+ });
+ },
+
+ pickOnlySku(attrList) {
+ const pickedAttrValues = attrList
+ .map((x) => x.values.find((x) => x.status === ATTR_VALUE_STATUS.PICKED))
+ .filter((x) => x != null);
+ if (pickedAttrValues.length !== attrList.length) {
+ // should pick more
+ return null;
+ }
+
+ const { skus } = this.data;
+ const pickedSku = skus.find(
+ (sku) =>
+ sku.attrValues.length === pickedAttrValues.length && // a and b have the same size
+ sku.attrValues.every((x) => pickedAttrValues.find((y) => y._id === x._id) != null), // every item in a can be found in b
+ );
+ if (pickedSku == null) {
+ console.error(
+ 'Something went wrong! With enough picked attr values, an sku should be picked! Picked attr values:',
+ pickedAttrValues,
+ );
+ return null;
+ }
+ return pickedSku;
+ },
+
+ specsConfirm() {
+ if (this.data.outOperateStatus === 'cart') {
+ this.addCart();
+ } else if (this.data.outOperateStatus === 'buy') {
+ this.buyNow();
+ }
+ },
+
+ handlePopupHide() {
+ this.triggerEvent('closeSpecsPopup', {
+ show: false,
+ });
+ },
+
+ addCart() {
+ const { pickedSku, max, value } = this.properties;
+ if (pickedSku == null) {
+ return;
+ }
+ if (value > max) {
+ return;
+ }
+ if (value < 1) {
+ return;
+ }
+ this.triggerEvent('addCart', { pickedSku, count: value });
+ },
+
+ buyNow() {
+ const { pickedSku, max, value } = this.properties;
+ if (pickedSku == null) {
+ return;
+ }
+ if (value > max) {
+ return;
+ }
+ if (value < 1) {
+ return;
+ }
+ this.triggerEvent('buyNow', { pickedSku, count: value });
+ },
+
+ handleBuyNumChange({ detail: { value } }) {
+ this.setData({ value });
+ },
+ },
+ lifetimes: {},
+});
+
+/**
+ *
+ * @param {Array} attrValues
+ * @returns {Array}
+ */
+function initAttrList(attrValues) {
+ const list = attrValues.reduce((acc, cur) => {
+ const item = acc.find((x) => x._id === cur.attr_name._id);
+ if (item != null) {
+ // already has this attr name, push value to this item
+ if (item.values.find((x) => x._id === cur._id) != null) {
+ // already has this attr value, do nothing
+ } else {
+ item.values.push({
+ value: cur.value,
+ _id: cur._id,
+ status: ATTR_VALUE_STATUS.UNPICKED,
+ });
+ }
+ } else {
+ // not added attr kind, make a new one
+ acc.push({
+ ...cur.attr_name,
+ values: [
+ {
+ value: cur.value,
+ _id: cur._id,
+ status: ATTR_VALUE_STATUS.UNPICKED,
+ },
+ ],
+ });
+ }
+ return acc;
+ }, []);
+ return list;
+}
+
+/**
+ *
+ * @param {{container: Array, arr: Array, eq: (a, b) => boolean}} param0
+ * @returns
+ */
+function contains({ container, arr, eq }) {
+ return arr.every((itemInArr) => container.findIndex((x) => eq(x, itemInArr)) !== -1);
+}
+
+/**
+ *
+ * @param {Array} skus
+ */
+function collectAttrValueSet(skus) {
+ const attrValues = skus.reduce((acc, sku) => {
+ sku.attrValues.forEach((value) => {
+ if (acc.find((x) => x._id === value._id) != null) {
+ // exists, skip
+ } else {
+ acc.push(Object.assign({}, value));
+ }
+ });
+ return acc;
+ }, []);
+ return attrValues;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.json b/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.json
new file mode 100644
index 0000000..3d351b0
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.json
@@ -0,0 +1,11 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-popup": "tdesign-miniprogram/popup/popup",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-image": "/components/webp-image/index",
+ "t-stepper": "tdesign-miniprogram/stepper/stepper",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "price": "/components/price/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.wxml b/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.wxml
new file mode 100644
index 0000000..7600498
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.wxml
@@ -0,0 +1,63 @@
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.wxss b/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.wxss
new file mode 100644
index 0000000..41d852d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/goods-specs-popup/index.wxss
@@ -0,0 +1,302 @@
+.popup-container {
+ background-color: #ffffff;
+ position: relative;
+ z-index: 100;
+ border-radius: 16rpx 16rpx 0 0;
+ padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
+}
+
+.popup-container .popup-close {
+ position: absolute;
+ right: 30rpx;
+ top: 30rpx;
+ z-index: 9;
+ color: #999999;
+}
+
+.popup-sku-header {
+ display: flex;
+ padding: 30rpx 28rpx 0 30rpx;
+}
+
+.popup-sku-header .popup-sku-header__img {
+ width: 176rpx;
+ height: 176rpx;
+ border-radius: 8rpx;
+ background: #d8d8d8;
+ margin-right: 24rpx;
+}
+
+.popup-sku-header .popup-sku-header__goods-info {
+ position: relative;
+ width: 500rpx;
+}
+
+.popup-sku-header .popup-sku-header__goods-info .popup-sku__goods-name {
+ font-size: 28rpx;
+ line-height: 40rpx;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ white-space: normal;
+ overflow: hidden;
+ width: 430rpx;
+ text-overflow: ellipsis;
+}
+
+.popup-sku-header .popup-sku-header__goods-info .popup-sku__selected-spec {
+ display: flex;
+ color: #333333;
+ font-size: 26rpx;
+ line-height: 36rpx;
+}
+
+.popup-sku-header
+ .popup-sku-header__goods-info
+ .popup-sku__selected-spec
+ .popup-sku__selected-item {
+ margin-right: 10rpx;
+ margin-left: 10rpx;
+ display: inline-block;
+}
+
+.popup-sku-body {
+ margin: 0 30rpx 40rpx;
+ max-height: 600rpx;
+ overflow-y: scroll;
+ -webkit-overflow-scrolling: touch;
+}
+
+.popup-sku-body .popup-sku-group-container .popup-sku-row {
+ padding: 32rpx 0;
+ border-bottom: 1rpx solid #f5f5f5;
+}
+
+.popup-sku-body
+ .popup-sku-group-container
+ .popup-sku-row
+ .popup-sku-row__title {
+ font-size: 26rpx;
+ color: #333;
+}
+
+.popup-sku-body .popup-sku-group-container .popup-sku-row .popup-sku-row__item {
+ font-size: 24rpx;
+ color: #333;
+ min-width: 128rpx;
+ height: 56rpx;
+ background-color: #f5f5f5;
+ border-radius: 8rpx;
+ border: 2rpx solid #f5f5f5;
+ margin: 19rpx 26rpx 0 0;
+ padding: 0 16rpx;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.popup-sku-body
+ .popup-sku-group-container
+ .popup-sku-row
+ .popup-sku-row__item.popup-sku-row__item--active {
+ border: 2rpx solid #fa4126;
+ color: #fa4126;
+ background: rgba(255, 95, 21, 0.04);
+}
+
+.popup-sku-body
+ .popup-sku-group-container
+ .popup-sku-row
+ .disabled-sku-selected {
+ background: #f5f5f5 !important;
+ color: #cccccc;
+}
+
+.popup-sku-body .popup-sku-stepper-stock .popup-sku-stepper-container {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin: 40rpx 0;
+}
+
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-sku__stepper-title {
+ display: flex;
+ font-size: 26rpx;
+ color: #333;
+}
+
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-sku__stepper-title
+ .limit-text {
+ margin-left: 10rpx;
+ color: #999999;
+}
+
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-stepper {
+ display: flex;
+ flex-flow: row nowrap;
+ align-items: center;
+ font-size: 28px;
+ height: 48rpx;
+ line-height: 62rpx;
+}
+
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-stepper
+ .input-btn,
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-stepper
+ .input-num-wrap {
+ position: relative;
+ height: 100%;
+ text-align: center;
+ background-color: #f5f5f5;
+ border-radius: 4rpx;
+}
+
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-stepper
+ .input-num-wrap {
+ color: #282828;
+ display: flex;
+ max-width: 76rpx;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-stepper
+ .input-num-wrap
+ .input-num {
+ height: 100%;
+ width: auto;
+ font-weight: 600;
+ font-size: 30rpx;
+}
+
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-stepper
+ .input-btn {
+ width: 48rpx;
+}
+
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-stepper
+ .popup-stepper__minus {
+ margin-right: 4rpx;
+ border-radius: 4rpx;
+ color: #9a979b;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-stepper
+ .popup-stepper__plus {
+ margin-left: 4rpx;
+ border-radius: 4rpx;
+ color: #9a979b;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-stepper
+ .popup-stepper__plus::after {
+ width: 24rpx;
+ height: 3rpx;
+ background-color: #999999;
+}
+
+.popup-sku-body
+ .popup-sku-stepper-stock
+ .popup-sku-stepper-container
+ .popup-stepper
+ .popup-stepper__plus::before {
+ width: 3rpx;
+ height: 24rpx;
+ background-color: #999999;
+}
+
+.popup-sku-actions {
+ font-size: 32rpx;
+ height: 80rpx;
+ text-align: center;
+ line-height: 80rpx;
+ padding: 0 20rpx;
+}
+
+.popup-sku-actions .sku-operate {
+ height: 80rpx;
+ width: 50%;
+ color: #fff;
+ border-radius: 48rpx;
+}
+
+.popup-sku-actions .sku-operate .sku-operate-addCart {
+ background-color: #ffece9;
+ color: #fa4126;
+ border-radius: 48rpx 0 0 48rpx;
+}
+
+.popup-sku-actions .sku-operate .sku-operate-addCart.disabled {
+ background: rgb(221, 221, 221);
+ color: #fff;
+}
+
+.popup-sku-actions .sku-operate .sku-operate-buyNow {
+ background-color: #fa4126;
+ border-radius: 0 48rpx 48rpx 0;
+}
+
+.popup-sku-actions .sku-operate .sku-operate-buyNow.disabled {
+ color: #fff;
+ background: rgb(198, 198, 198);
+}
+
+.popup-sku-actions .sku-operate .selected-sku-btn {
+ width: 100%;
+}
+
+.popup-container .single-confirm-btn {
+ border-radius: 48rpx;
+ color: #ffffff;
+ margin: 0 32rpx;
+ font-size: 32rpx;
+ height: 80rpx;
+ text-align: center;
+ line-height: 88rpx;
+ background-color: #fa4126;
+}
+
+.popup-container .single-confirm-btn.disabled {
+ font-size: 32rpx;
+ color: #fff;
+ background-color: #dddddd;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.js b/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.js
new file mode 100644
index 0000000..9743cc1
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.js
@@ -0,0 +1,35 @@
+Component({
+ options: {
+ multipleSlots: true,
+ },
+
+ properties: {
+ list: Array,
+ title: {
+ type: String,
+ value: '促销说明',
+ },
+ show: {
+ type: Boolean,
+ },
+ },
+
+ // data: {
+ // list: [],
+ // },
+
+ methods: {
+ change(e) {
+ const { index } = e.currentTarget.dataset;
+ this.triggerEvent('promotionChange', {
+ index,
+ });
+ },
+
+ closePromotionPopup() {
+ this.triggerEvent('closePromotionPopup', {
+ show: false,
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.json b/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.json
new file mode 100644
index 0000000..a9de77d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-popup": "tdesign-miniprogram/popup/popup",
+ "t-icon": "tdesign-miniprogram/icon/icon"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.wxml b/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.wxml
new file mode 100644
index 0000000..c1cea9d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.wxml
@@ -0,0 +1,34 @@
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.wxss b/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.wxss
new file mode 100644
index 0000000..6e0e167
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/components/promotion-popup/index.wxss
@@ -0,0 +1,131 @@
+.promotion-popup-container {
+ background-color: #ffffff;
+ position: relative;
+ z-index: 100;
+ border-radius: 16rpx 16rpx 0 0;
+}
+
+.promotion-popup-container .promotion-popup-close {
+ position: absolute;
+ right: 30rpx;
+ top: 30rpx;
+ z-index: 9;
+ color: rgba(153, 153, 153, 1);
+}
+
+.promotion-popup-container .promotion-popup-close .market {
+ font-size: 25rpx;
+ color: #999;
+}
+
+.promotion-popup-container .promotion-popup-title {
+ height: 100rpx;
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.promotion-popup-container .promotion-popup-title {
+ font-size: 32rpx;
+ color: #222427;
+ font-weight: 600;
+}
+
+.promotion-popup-container .promotion-popup-content {
+ min-height: 400rpx;
+ max-height: 600rpx;
+ padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
+ overflow-y: scroll;
+ -webkit-overflow-scrolling: touch;
+}
+
+.promotion-popup-container .promotion-popup-content .promotion-detail-list {
+ margin: 0 30rpx;
+}
+
+.promotion-popup-container
+ .promotion-popup-content
+ .promotion-detail-list
+ .list-item:last-child {
+ margin-bottom: env(safe-area-inset-bottom);
+ border-bottom: 0;
+ padding-bottom: calc(28rpx + env(safe-area-inset-bottom));
+}
+
+.promotion-popup-container
+ .promotion-popup-content
+ .promotion-detail-list
+ .list-item {
+ display: flex;
+ justify-content: space-between;
+ padding: 10rpx 0 28rpx;
+ position: relative;
+ font-size: 24rpx;
+ color: #222427;
+}
+
+.promotion-popup-container
+ .promotion-popup-content
+ .promotion-detail-list
+ .list-item
+ .tag {
+ box-sizing: border-box;
+ font-size: 20rpx;
+ line-height: 32rpx;
+ padding: 2rpx 12rpx;
+ background-color: #ffece9;
+ margin-right: 16rpx;
+ display: inline-flex;
+ color: #fa4126;
+ border-radius: 54rpx;
+ flex-shrink: 0;
+ position: relative;
+ top: 2rpx;
+}
+
+.promotion-popup-container
+ .promotion-popup-content
+ .promotion-detail-list
+ .list-item
+ .content {
+ font-size: 28rpx;
+ color: #222427;
+ flex: 1;
+ line-height: 40rpx;
+ display: flex;
+}
+
+.promotion-popup-container
+ .promotion-popup-content
+ .promotion-detail-list
+ .list-item
+ .content
+ .list-content {
+ width: 440rpx;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ display: inline-block;
+}
+
+.promotion-popup-container
+ .promotion-popup-content
+ .promotion-detail-list
+ .list-item
+ .collect-btn {
+ font-size: 24rpx;
+ flex-shrink: 0;
+ margin-left: 20rpx;
+ display: flex;
+ align-items: center;
+}
+
+.promotion-popup-container
+ .promotion-popup-content
+ .promotion-detail-list
+ .list-item
+ .collect-btn
+ .linkText {
+ margin-right: 8rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/details/index.js b/miniprogram/tcb-shop/pages/goods/details/index.js
new file mode 100644
index 0000000..1172eb5
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/index.js
@@ -0,0 +1,421 @@
+// 第三方库
+import Toast from 'tdesign-miniprogram/toast/index';
+
+// 服务层
+import { getAllAttrValues } from '../../../services/attrValue/attrValue';
+import { handleSpuCloudImage, getSpu } from '../../../services/good/spu';
+import { fetchCartItems, createCartItem, updateCartItemCount } from '../../../services/cart/cart';
+import { getGoodsDetailCommentInfo } from '../../../services/comments/comments';
+import { getAllSku } from '../../../services/sku/sku';
+
+// 工具函数
+import { cdnBase } from '../../../config/index';
+import { cartShouldFresh } from '../../../utils/cartFresh';
+import { getCloudImageTempUrl, getSingleCloudImageTempUrl } from '../../../utils/cloudImageHandler';
+import { objectToParamString } from '../../../utils/util';
+
+const imgPrefix = `${cdnBase}/`;
+
+const recLeftImg = `${imgPrefix}common/rec-left.png`;
+const recRightImg = `${imgPrefix}common/rec-right.png`;
+
+async function replaceCloudImageWithTempUrl(text) {
+ let ret = text;
+
+ // 使用正则表达式匹配所有被双引号包裹的链接
+ const regex = /"(cloud:\/\/[^"]*)"/g;
+ let match;
+ // 使用一个循环来处理所有匹配的链接
+ while ((match = regex.exec(ret)) !== null) {
+ const originalLink = match[0];
+ const pureLink = match[1];
+ // 处理链接
+ const processedLink = await getSingleCloudImageTempUrl(pureLink);
+ // 替换文本中的原始链接
+ ret = ret.replace(originalLink, `"${processedLink}"`);
+ }
+
+ return ret;
+}
+
+const OUT_OPERATE_STATUS = {
+ CART: 'cart',
+ BUY: 'buy',
+ NO: 'no',
+};
+
+Page({
+ data: {
+ commentsList: [],
+ commentsStatistics: {
+ badCount: 0,
+ commentCount: 0,
+ goodCount: 0,
+ goodRate: 0,
+ hasImageCount: 0,
+ middleCount: 0,
+ },
+ isShowPromotionPop: false,
+ activityList: [],
+ recLeftImg,
+ recRightImg,
+ details: {},
+ jumpArray: [
+ {
+ title: '首页',
+ url: '/pages/home/home',
+ iconName: 'home',
+ },
+ {
+ title: '购物车',
+ url: '/pages/cart/index',
+ iconName: 'cart',
+ showCartNum: true,
+ },
+ ],
+ isStock: true,
+ cartNum: 0,
+ soldout: false,
+ buttonType: 1,
+ buyNum: 1,
+ selectedAttrStr: '',
+ skuArray: [],
+ primaryImage: '',
+ specImg: '',
+ isSpuSelectPopupShow: false,
+ isAllSelectedSku: false,
+ buyType: 0,
+ outOperateStatus: OUT_OPERATE_STATUS.NO, // 是否外层加入购物车
+ operateType: 0,
+ selectSkuSellsPrice: 0,
+ maxLinePrice: 0,
+ minSalePrice: 0,
+ maxSalePrice: 0,
+ list: [],
+ spuId: '',
+ navigation: { type: 'fraction' },
+ current: 0,
+ autoplay: true,
+ duration: 500,
+ interval: 5000,
+ soldNum: 0, // 已售数量,
+ loading: false,
+ },
+
+ setLoading() {
+ this.setData({ loading: true });
+ },
+ unsetLoading() {
+ this.setData({ loading: false });
+ },
+
+ handlePopupHide() {
+ this.setData({
+ isSpuSelectPopupShow: false,
+ });
+ },
+
+ onSpecSelectTap() {
+ this.showSkuSelectPopup(OUT_OPERATE_STATUS.NO);
+ },
+
+ showSkuSelectPopup(status) {
+ this.setData({
+ outOperateStatus: status,
+ isSpuSelectPopupShow: true,
+ });
+ },
+
+ buyItNow() {
+ this.showSkuSelectPopup(OUT_OPERATE_STATUS.BUY);
+ },
+
+ toAddCart() {
+ this.showSkuSelectPopup(OUT_OPERATE_STATUS.CART);
+ },
+
+ toNav(e) {
+ const { url } = e.detail;
+ wx.switchTab({
+ url: url,
+ });
+ },
+
+ showCurImg(e) {
+ const { index } = e.detail;
+ const { images } = this.data.details;
+ wx.previewImage({
+ current: images[index],
+ urls: images, // 需要预览的图片http链接列表
+ });
+ },
+
+ onPageScroll({ scrollTop }) {
+ const goodsTab = this.selectComponent('#goodsTab');
+ goodsTab && goodsTab.onScroll(scrollTop);
+ },
+
+ chooseSpecItem(e) {
+ const { specList } = this.data.details;
+ const { selectedSku, isAllSelectedSku } = e.detail;
+ if (!isAllSelectedSku) {
+ this.setData({
+ selectSkuSellsPrice: 0,
+ });
+ }
+ this.setData({
+ isAllSelectedSku,
+ });
+ this.getSkuItem(specList, selectedSku);
+ },
+
+ getSkuItem(specList, selectedSku) {
+ const { skuArray, primaryImage } = this.data;
+ const selectedSkuValues = this.getSelectedSkuValues(specList, selectedSku);
+ let selectedAttrStr = ` 件 `;
+ selectedSkuValues.forEach((item) => {
+ selectedAttrStr += `,${item.specValue} `;
+ });
+ // eslint-disable-next-line array-callback-return
+ const skuItem = skuArray.filter((item) => {
+ let status = true;
+ (item.specInfo || []).forEach((subItem) => {
+ if (!selectedSku[subItem.specId] || selectedSku[subItem.specId] !== subItem.specValueId) {
+ status = false;
+ }
+ });
+ if (status) return item;
+ });
+ this.selectSpecsName(selectedSkuValues.length > 0 ? selectedAttrStr : '');
+ if (skuItem) {
+ this.setData({
+ selectItem: skuItem,
+ selectSkuSellsPrice: skuItem.price || 0,
+ });
+ } else {
+ this.setData({
+ selectItem: null,
+ selectSkuSellsPrice: 0,
+ });
+ }
+ this.setData({
+ specImg: skuItem && skuItem.skuImage ? skuItem.skuImage : primaryImage,
+ });
+ },
+
+ // 获取已选择的sku名称
+ getSelectedSkuValues(skuTree, selectedSku) {
+ const normalizedTree = this.normalizeSkuTree(skuTree);
+ return Object.keys(selectedSku).reduce((selectedValues, skuKeyStr) => {
+ const skuValues = normalizedTree[skuKeyStr];
+ const skuValueId = selectedSku[skuKeyStr];
+ if (skuValueId !== '') {
+ const skuValue = skuValues.filter((value) => {
+ return value.specValueId === skuValueId;
+ })[0];
+ skuValue && selectedValues.push(skuValue);
+ }
+ return selectedValues;
+ }, []);
+ },
+
+ normalizeSkuTree(skuTree) {
+ const normalizedTree = {};
+ skuTree.forEach((treeItem) => {
+ normalizedTree[treeItem.specId] = treeItem.specValueList;
+ });
+ return normalizedTree;
+ },
+
+ selectSpecsName(selectSpecsName) {
+ if (selectSpecsName) {
+ this.setData({
+ selectedAttrStr: selectSpecsName,
+ });
+ } else {
+ this.setData({
+ selectedAttrStr: '',
+ });
+ }
+ },
+
+ toast(message) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message,
+ icon: '',
+ duration: 1000,
+ });
+ },
+
+ async addCart({ detail: { count, pickedSku } }) {
+ const overCount = () => this.toast('超过购买上限了');
+ const addCartSucceed = () => {
+ cartShouldFresh();
+ this.handlePopupHide();
+ this.toast('加入购物车成功');
+ };
+ const addCartFail = () => this.toast('加入购物车失败');
+
+ const records = await fetchCartItems();
+
+ const cartItem = records.find((x) => x.sku._id === pickedSku._id);
+ // eslint-disable-next-line eqeqeq
+ if (cartItem == null) {
+ // cart item empty, create
+ if (count <= pickedSku.count) {
+ await createCartItem({ skuId: pickedSku._id, count }).then(addCartSucceed, addCartFail);
+ } else {
+ overCount();
+ }
+ } else {
+ // cart item exists, update count
+ const finalCount = cartItem.count + count;
+ if (finalCount <= pickedSku.count) {
+ await updateCartItemCount({ cartItemId: cartItem._id, count: finalCount }).then(addCartSucceed, addCartFail);
+ } else {
+ overCount();
+ }
+ }
+ },
+
+ onPicked() {
+ this.setData({ isAllSelectedSku: true });
+ },
+
+ gotoBuy(e) {
+ const overCount = () => this.toast('超过购买上限了');
+ const buyCount = e.detail.count;
+ const skuId = e.detail.pickedSku._id;
+ const skuCount = e.detail.pickedSku.count;
+ if (buyCount > skuCount) return overCount();
+
+ wx.navigateTo({
+ url: `/pages/order/order-confirm/index?${objectToParamString({ type: 'direct', count: buyCount, skuId })}`,
+ });
+ },
+
+ specsConfirm() {
+ const { buyType } = this.data;
+ if (buyType === 1) {
+ this.gotoBuy();
+ } else {
+ this.addCart();
+ }
+ },
+
+ changeNum(e) {
+ this.setData({
+ buyNum: e.detail.buyNum,
+ });
+ },
+
+ closePromotionPopup() {
+ this.setData({
+ isShowPromotionPop: false,
+ });
+ },
+
+ promotionChange(e) {
+ const { index } = e.detail;
+ wx.navigateTo({
+ url: `/pages/promotion-detail/index?promotion_id=${index}`,
+ });
+ },
+
+ showPromotionPopup() {
+ this.setData({
+ isShowPromotionPop: true,
+ });
+ },
+
+ async getInfo(spuId) {
+ // pics
+ // min price
+ // spu title
+ // attrs => sku => count + price
+ // spu detail
+ // comment
+ const spu = await getSpu(spuId);
+
+ const loadSkus = async () => {
+ const skus = await getAllSku(spuId);
+ const minPrice = skus.reduce((acc, current) => Math.min(acc, current.price), Infinity) * 100;
+ const loadSkuAttrValues = async () => {
+ return Promise.all(
+ skus.map(async (sku) => {
+ const attrValues = await getAllAttrValues(sku._id);
+ sku.attrValues = attrValues;
+ }),
+ );
+ };
+ const handleSkuImages = async () => {
+ const images = skus.map((s) => s.image ?? '');
+ const handledImages = await getCloudImageTempUrl(images);
+ handledImages.forEach((image, index) => (skus[index].image = image));
+ };
+ await Promise.all([handleSkuImages(), loadSkuAttrValues()]);
+ return { skus, minPrice };
+ };
+
+ const [_x, { skus, minPrice }, commentInfo] = await Promise.all([
+ handleSpuCloudImage(spu),
+ loadSkus(),
+ getGoodsDetailCommentInfo(spu._id),
+ ]);
+
+ const [
+ {
+ data: { records, total: commentCount },
+ },
+ {
+ data: { total: goodCommentCount },
+ },
+ ] = commentInfo;
+
+ records.forEach((x) => (x.userNameString = x.createBy.substring(0, 10)));
+
+ const detail = await replaceCloudImageWithTempUrl(spu.detail);
+
+ this.setData({
+ details: {
+ images: spu.swiper_images,
+ title: spu.name,
+ },
+ minSalePrice: minPrice,
+ detail,
+ descPopUpInitProps: {
+ skus,
+ minPrice,
+ spu,
+ },
+ commentsStatistics: {
+ commentCount,
+ goodRate: (goodCommentCount / commentCount) * 100,
+ },
+ spu,
+ commentsList: records,
+ });
+ },
+
+ /** 跳转到评价列表 */
+ navToCommentsListPage() {
+ wx.navigateTo({
+ url: `/pages/goods/comments/index?spuId=${this.data.spu._id}`,
+ });
+ },
+
+ async onLoad(query) {
+ const { spuId } = query;
+ this.setLoading();
+ try {
+ await this.getInfo(spuId);
+ } catch (e) {
+ console.error(e);
+ this.toast('获取商品详情失败');
+ } finally {
+ this.unsetLoading();
+ }
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/details/index.json b/miniprogram/tcb-shop/pages/goods/details/index.json
new file mode 100644
index 0000000..f8cab8b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/index.json
@@ -0,0 +1,19 @@
+{
+ "navigationBarTitleText": "商品详情",
+ "usingComponents": {
+ "t-image": "/components/webp-image/index",
+ "t-tag": "tdesign-miniprogram/tag/tag",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-rate": "tdesign-miniprogram/rate/rate",
+ "t-swiper": "tdesign-miniprogram/swiper/swiper",
+ "t-swiper-nav": "tdesign-miniprogram/swiper-nav/swiper-nav",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-popup": "tdesign-miniprogram/popup/popup",
+ "price": "/components/price/index",
+ "buy-bar": "./components/buy-bar/index",
+ "promotion-popup": "./components/promotion-popup/index",
+ "goods-specs-popup": "./components/goods-specs-popup/index",
+ "loading-dialog": "../../../components/loading-dialog/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/details/index.wxml b/miniprogram/tcb-shop/pages/goods/details/index.wxml
new file mode 100644
index 0000000..3e438db
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/index.wxml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+ 起
+
+
+
+ {{details.title}}
+
+
+
+ 已选
+
+
+ {{selectedAttrStr ? buyNum : ''}}{{selectedAttrStr || '请选择'}}
+
+
+
+
+
+
+
+
+
+ 详情介绍
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/details/index.wxss b/miniprogram/tcb-shop/pages/goods/details/index.wxss
new file mode 100644
index 0000000..7459f9e
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/details/index.wxss
@@ -0,0 +1,344 @@
+@import '../../../style/global.wxss';
+
+page {
+ width: 100vw;
+}
+
+.goods-detail-page .goods-info {
+ margin: 0 auto;
+ padding: 26rpx 0 28rpx 30rpx;
+ background-color: #fff;
+}
+
+.goods-detail-page .swipe-img {
+ width: 100%;
+ height: 750rpx;
+}
+
+.goods-detail-page .goods-info .goods-price {
+ display: flex;
+ align-items: baseline;
+}
+
+.goods-detail-page .goods-info .goods-price-up {
+ color: #fa4126;
+ font-size: 28rpx;
+ position: relative;
+ bottom: 4rpx;
+ left: 8rpx;
+}
+
+.goods-detail-page .goods-info .goods-price .class-goods-price {
+ font-size: 64rpx;
+ color: #fa4126;
+ font-weight: bold;
+ font-family: DIN Alternate;
+}
+
+.goods-detail-page .goods-info .goods-price .class-goods-symbol {
+ font-size: 36rpx;
+ color: #fa4126;
+}
+
+.goods-detail-page .goods-info .goods-price .class-goods-del {
+ position: relative;
+ font-weight: normal;
+ left: 16rpx;
+ bottom: 2rpx;
+ color: #999999;
+ font-size: 32rpx;
+}
+
+.goods-detail-page .goods-info .goods-number {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.goods-detail-page .goods-info .goods-number .sold-num {
+ font-size: 24rpx;
+ color: #999999;
+ display: flex;
+ align-items: flex-end;
+ margin-right: 32rpx;
+}
+
+.goods-detail-page .goods-info .goods-activity {
+ display: flex;
+ margin-top: 16rpx;
+ justify-content: space-between;
+}
+
+.goods-detail-page .goods-info .goods-activity .tags-container {
+ display: flex;
+}
+
+.goods-detail-page .goods-info .goods-activity .tags-container .goods-activity-tag {
+ background: #ffece9;
+ color: #fa4126;
+ font-size: 24rpx;
+ margin-right: 16rpx;
+ padding: 4rpx 8rpx;
+ border-radius: 4rpx;
+}
+
+.goods-detail-page .goods-info .goods-activity .activity-show {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ color: #fa4126;
+ font-size: 24rpx;
+ padding-right: 32rpx;
+}
+
+.goods-detail-page .goods-info .goods-activity .activity-show-text {
+ line-height: 42rpx;
+}
+
+.goods-detail-page .goods-info .goods-title {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 20rpx;
+}
+
+.goods-detail-page .goods-info .goods-title .goods-name {
+ width: 600rpx;
+ font-weight: 500;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ font-size: 32rpx;
+ word-break: break-all;
+ color: #333333;
+}
+
+.goods-detail-page .goods-info .goods-title .goods-tag {
+ width: 104rpx;
+ margin-left: 26rpx;
+}
+
+.goods-detail-page .goods-info .goods-title .goods-tag .shareBtn {
+ border-radius: 200rpx 0px 0px 200rpx;
+ width: 100rpx;
+ height: 96rpx;
+ border: none;
+ padding-right: 36rpx !important;
+}
+
+.goods-detail-page .goods-info .goods-title .goods-tag .shareBtn::after {
+ border: none;
+}
+
+.goods-detail-page .goods-info .goods-title .goods-tag .btn-icon {
+ font-size: 20rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 96rpx;
+ color: #999;
+}
+
+.goods-detail-page .goods-info .goods-title .goods-tag .btn-icon .share-text {
+ padding-top: 8rpx;
+ font-size: 20rpx;
+ line-height: 24rpx;
+}
+
+.goods-detail-page .goods-info .goods-intro {
+ font-size: 26rpx;
+ color: #888;
+ line-height: 36rpx;
+ word-break: break-all;
+ padding-right: 30rpx;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ white-space: normal;
+ overflow: hidden;
+}
+
+.spu-select {
+ height: 80rpx;
+ background-color: #fff;
+ margin-top: 20rpx;
+ display: flex;
+ align-items: center;
+ padding: 30rpx;
+ font-size: 28rpx;
+}
+
+.spu-select .label {
+ margin-right: 30rpx;
+ text-align: center;
+ flex-shrink: 0;
+ color: #999999;
+ font-weight: normal;
+}
+
+.spu-select .content {
+ display: flex;
+ flex: 1;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.spu-select .content .tintColor {
+ color: #aaa;
+}
+
+.goods-detail-page .desc-content {
+ margin-top: 20rpx;
+ background-color: #fff;
+ /* padding-bottom: 120rpx; */
+ padding-bottom: 220rpx;
+}
+
+.goods-detail-page .desc-content__title {
+ font-size: 28rpx;
+ line-height: 36rpx;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ padding: 30rpx 20rpx;
+}
+
+.goods-detail-page .desc-content__title .img {
+ width: 206rpx;
+ height: 10rpx;
+}
+
+.goods-detail-page .desc-content__title--text {
+ font-size: 26rpx;
+ margin: 0 32rpx;
+ font-weight: 600;
+}
+
+.goods-detail-page .desc-content__img {
+ width: 100%;
+ height: auto;
+}
+
+.goods-bottom-operation {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ background-color: #fff;
+ padding-bottom: env(safe-area-inset-bottom);
+}
+
+.popup-sku-header .popup-sku-header__goods-info .popup-sku__price {
+ display: flex;
+ align-items: baseline;
+ color: #fa4126;
+ margin-top: 48rpx;
+}
+
+.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-num {
+ font-size: 64rpx;
+ color: #fa4126;
+ font-weight: bold;
+ font-family: DIN Alternate;
+}
+
+.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-del {
+ position: relative;
+ font-weight: normal;
+ left: 12rpx;
+ bottom: 2rpx;
+ color: #999999;
+ font-size: 32rpx;
+}
+
+.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-symbol {
+ font-size: 36rpx;
+ color: #fa4126;
+}
+
+.popup-sku-header .popup-sku-header__goods-info .popup-sku__price .popup-sku__price-max-num {
+ font-size: 48rpx;
+}
+
+.goods-detail-page .goods-head {
+ --td-swiper-radius: 0;
+}
+
+.t-toast__content {
+ z-index: 12000 !important;
+}
+
+.comments-wrap {
+ margin-top: 20rpx;
+ padding: 32rpx;
+ background-color: #fff;
+}
+
+.comments-wrap .comments-head {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.comments-wrap .comments-head .comments-title-wrap {
+ display: flex;
+}
+
+.comments-title-label,
+.comments-title-count {
+ color: #333333;
+ font-size: 32rpx;
+ font-weight: 500;
+ line-height: 48rpx;
+}
+
+.comments-rate-wrap {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 24rpx;
+}
+
+.comments-rate-wrap .comments-good-rate {
+ color: #999999;
+ font-size: 26rpx;
+ font-weight: 400;
+ font-style: normal;
+ line-height: 36rpx;
+}
+
+.comment-item-wrap .comment-item-head {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin-top: 32rpx;
+}
+
+.comment-item-wrap .comment-item-head .comment-item-avatar {
+ width: 64rpx;
+ height: 64rpx;
+ border-radius: 64rpx;
+}
+
+.comment-item-wrap .comment-item-head .comment-head-right {
+ margin-left: 24rpx;
+}
+
+.comment-head-right .comment-username {
+ font-size: 26rpx;
+ color: #333333;
+ line-height: 36rpx;
+ font-weight: 400;
+}
+
+.comment-item-wrap .comment-item-content {
+ margin-top: 20rpx;
+ color: #333333;
+ line-height: 40rpx;
+ font-size: 28rpx;
+ font-weight: 400;
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/list/index.js b/miniprogram/tcb-shop/pages/goods/list/index.js
new file mode 100644
index 0000000..d9700a9
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/list/index.js
@@ -0,0 +1,208 @@
+/* eslint-disable no-param-reassign */
+import { getPrice } from '../../../services/good/spu';
+import { getAllSpuOfCate } from '../../../services/cate/cate';
+import { getCloudImageTempUrl } from '../../../utils/cloudImageHandler';
+import { LIST_LOADING_STATUS } from '../../../utils/listLoading';
+import Toast from 'tdesign-miniprogram/toast/index';
+
+const initFilters = {
+ overall: 1,
+ sorts: '',
+ layout: 0,
+};
+
+Page({
+ data: {
+ goodsList: [],
+ layout: 0,
+ sorts: '',
+ overall: 1,
+ show: false,
+ minVal: '',
+ maxVal: '',
+ filter: initFilters,
+ hasLoaded: false,
+ loadMoreStatus: 0,
+ loading: true,
+ goodsListLoadStatus: LIST_LOADING_STATUS.READY,
+ },
+
+ pageNum: 1,
+ pageSize: 30,
+ total: 0,
+
+ handleFilterChange(e) {
+ const { layout, overall, sorts } = e.detail;
+ this.pageNum = 1;
+ this.setData({
+ layout,
+ sorts,
+ overall,
+ loadMoreStatus: 0,
+ });
+ this.init(true);
+ },
+
+ generalQueryData(reset = false) {
+ const { filter, keywords, minVal, maxVal } = this.data;
+ const { pageNum, pageSize } = this;
+ const { sorts, overall } = filter;
+ const params = {
+ sort: 0, // 0 综合,1 价格
+ pageNum: 1,
+ pageSize: 30,
+ keyword: keywords,
+ };
+
+ if (sorts) {
+ params.sort = 1;
+ params.sortType = sorts === 'desc' ? 1 : 0;
+ }
+
+ if (overall) {
+ params.sort = 0;
+ } else {
+ params.sort = 1;
+ }
+ params.minPrice = minVal ? minVal * 100 : 0;
+ params.maxPrice = maxVal ? maxVal * 100 : undefined;
+ if (reset) return params;
+ return {
+ ...params,
+ pageNum: pageNum + 1,
+ pageSize,
+ };
+ },
+
+ async init(reset = true) {
+ this.loadGoodsList(reset);
+ },
+
+ async loadGoodsList() {
+ this.setData({ goodsListLoadStatus: LIST_LOADING_STATUS.LOADING });
+
+ try {
+ const { spu: goodsList } = await getAllSpuOfCate(this.cateId);
+ goodsList.sort((a, b) => (b?.priority ?? 0) - (a?.priority ?? 0));
+ const images = goodsList.map((x) => x.cover_image);
+ const handledImages = await getCloudImageTempUrl(images);
+ handledImages.forEach((image, index) => (goodsList[index].cover_image = image));
+ await Promise.all(goodsList.map(async (spu) => (spu.price = await getPrice(spu._id))));
+
+ this.setData({
+ goodsList,
+ // TODO: add pagination
+ goodsListLoadStatus: LIST_LOADING_STATUS.NO_MORE,
+ });
+ } catch (err) {
+ console.error('error', err);
+ this.setData({ goodsListLoadStatus: LIST_LOADING_STATUS.FAILED });
+ }
+ },
+ cateId: null,
+
+ onLoad(query) {
+ const { cateId } = query;
+ if (typeof cateId !== 'string') return;
+
+ this.cateId = cateId;
+ this.init(true);
+ },
+
+ onReachBottom() {
+ const { goodsList } = this.data;
+ const { total = 0 } = this;
+ if (goodsList.length === total) {
+ this.setData({
+ loadMoreStatus: 2,
+ });
+ return;
+ }
+ this.init(false);
+ },
+
+ handleAddCart() {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '点击加购',
+ });
+ },
+
+ tagClickHandle() {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '点击标签',
+ });
+ },
+
+ gotoGoodsDetail(e) {
+ const spuId = e?.detail?.goods?._id;
+ if (typeof spuId !== 'string') return;
+
+ wx.navigateTo({
+ url: `/pages/goods/details/index?spuId=${spuId}`,
+ });
+ },
+
+ showFilterPopup() {
+ this.setData({
+ show: true,
+ });
+ },
+
+ showFilterPopupClose() {
+ this.setData({
+ show: false,
+ });
+ },
+
+ onMinValAction(e) {
+ const { value } = e.detail;
+ this.setData({ minVal: value });
+ },
+
+ onMaxValAction(e) {
+ const { value } = e.detail;
+ this.setData({ maxVal: value });
+ },
+
+ reset() {
+ this.setData({ minVal: '', maxVal: '' });
+ },
+
+ confirm() {
+ const { minVal, maxVal } = this.data;
+ let message = '';
+ if (minVal && !maxVal) {
+ message = `价格最小是${minVal}`;
+ } else if (!minVal && maxVal) {
+ message = `价格范围是0-${minVal}`;
+ } else if (minVal && maxVal && minVal <= maxVal) {
+ message = `价格范围${minVal}-${this.data.maxVal}`;
+ } else {
+ message = '请输入正确范围';
+ }
+ if (message) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message,
+ });
+ }
+ this.pageNum = 1;
+ this.setData(
+ {
+ show: false,
+ minVal: '',
+ goodsList: [],
+ loadMoreStatus: 0,
+ maxVal: '',
+ },
+ () => {
+ this.init();
+ },
+ );
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/list/index.json b/miniprogram/tcb-shop/pages/goods/list/index.json
new file mode 100644
index 0000000..0811511
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/list/index.json
@@ -0,0 +1,12 @@
+{
+ "navigationBarTitleText": "商品列表",
+ "usingComponents": {
+ "t-input": "tdesign-miniprogram/input/input",
+ "t-empty": "tdesign-miniprogram/empty/empty",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "goods-list": "/components/goods-list/index",
+ "filter": "/components/filter/index",
+ "filter-popup": "/components/filter-popup/index",
+ "load-more": "/components/load-more/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/list/index.wxml b/miniprogram/tcb-shop/pages/goods/list/index.wxml
new file mode 100644
index 0000000..32c4b1f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/list/index.wxml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/list/index.wxss b/miniprogram/tcb-shop/pages/goods/list/index.wxss
new file mode 100644
index 0000000..5073012
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/list/index.wxss
@@ -0,0 +1,99 @@
+page {
+ background-color: #fff;
+}
+
+.goods-list-container {
+ display: block;
+}
+
+.goods-list-container .t-search {
+ padding: 0 30rpx;
+ background-color: #fff;
+}
+
+.goods-list-container .t-class__input-container {
+ height: 64rpx !important;
+ border-radius: 32rpx !important;
+}
+
+.goods-list-container .t-search__left-icon {
+ display: flex;
+ align-items: center;
+}
+
+.goods-list-container .t-search__input {
+ font-size: 28rpx !important;
+ color: rgb(116, 116, 116) !important;
+}
+
+.goods-list-container .category-goods-list {
+ background-color: #f2f2f2;
+ overflow-y: scroll;
+ -webkit-overflow-scrolling: touch;
+ padding: 20rpx 24rpx;
+ -webkit-overflow-scrolling: touch;
+}
+
+.goods-list-container .wr-goods-list {
+ background: #f2f2f2 !important;
+}
+
+.goods-list-container .t-image__mask {
+ display: flex !important;
+}
+
+.goods-list-container .empty-wrap {
+ margin-top: 184rpx;
+ margin-bottom: 120rpx;
+ height: 300rpx;
+}
+
+.goods-list-container .empty-wrap .empty-tips .empty-content .content-text {
+ margin-top: 40rpx;
+}
+
+.goods-list-container .price-container {
+ padding: 32rpx;
+ height: 100vh;
+ max-width: 632rpx;
+ background-color: #fff;
+ border-radius: 30rpx 0 0 30rpx;
+ box-sizing: border-box;
+}
+
+.goods-list-container .price-between {
+ font-size: 26rpx;
+ font-weight: 500;
+ color: rgba(51, 51, 51, 1);
+}
+
+.goods-list-container .price-ipts-wrap {
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: space-around;
+ margin-top: 24rpx;
+
+ --td-input-bg-color: rgba(245, 245, 245, 1);
+ --td-input-vertical-padding: 4rpx;
+ --td-input-border-color: transparent;
+}
+
+.goods-list-container .price-ipts-wrap .price-divided {
+ width: 16rpx;
+ margin: 0 24rpx;
+ color: #333333;
+}
+
+.goods-list-container .price-ipts-wrap .t-input__wrapper {
+ margin: 0 !important;
+}
+
+.goods-list-container .price-ipts-wrap .t-input__content,
+.goods-list-container .price-ipts-wrap .t-input__placeholder {
+ font-size: 24rpx !important;
+}
+
+.goods-list-container .price-ipts-wrap .price-ipt {
+ border-radius: 8rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/result/index.js b/miniprogram/tcb-shop/pages/goods/result/index.js
new file mode 100644
index 0000000..bca4c21
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/result/index.js
@@ -0,0 +1,242 @@
+/* eslint-disable no-param-reassign */
+import Toast from 'tdesign-miniprogram/toast/index';
+import { listGood, getPrice } from '../../../services/good/spu';
+import { getCloudImageTempUrl } from '../../../utils/cloudImageHandler';
+import { LIST_LOADING_STATUS } from '../../../utils/listLoading';
+
+const initFilters = {
+ overall: 1,
+ sorts: '',
+};
+
+Page({
+ data: {
+ goodsList: [],
+ sorts: '',
+ overall: 1,
+ show: false,
+ minVal: '',
+ maxVal: '',
+ minSalePriceFocus: false,
+ maxSalePriceFocus: false,
+ filter: initFilters,
+ hasLoaded: false,
+ keywords: '',
+ loadMoreStatus: 0,
+ loading: true,
+ },
+
+ total: 0,
+ pageNum: 1,
+ pageSize: 30,
+
+ onLoad(options) {
+ const { searchValue = '' } = options || {};
+ this.setData(
+ {
+ keywords: searchValue,
+ },
+ () => {
+ this.init(true);
+ },
+ );
+ },
+
+ generalQueryData(reset = false) {
+ const { filter, keywords, minVal, maxVal } = this.data;
+ const { pageNum, pageSize } = this;
+ const { sorts, overall } = filter;
+ const params = {
+ sort: 0, // 0 综合,1 价格
+ pageNum: 1,
+ pageSize: 30,
+ keyword: keywords,
+ };
+
+ if (sorts) {
+ params.sort = 1;
+ params.sortType = sorts === 'desc' ? 1 : 0;
+ }
+ if (overall) {
+ params.sort = 0;
+ } else {
+ params.sort = 1;
+ }
+ params.minPrice = minVal ? minVal * 100 : 0;
+ params.maxPrice = maxVal ? maxVal * 100 : undefined;
+ if (reset) return params;
+ return {
+ ...params,
+ pageNum: pageNum + 1,
+ pageSize,
+ };
+ },
+
+ async init(reset = true) {
+ return this.loadGoodsList(reset);
+ },
+
+ async loadGoodsList(fresh = false) {
+ this.setData({ loadMoreStatus: LIST_LOADING_STATUS.LOADING });
+
+ const pageSize = this.pageSize;
+ const pageIndex = fresh ? 1 : this.pageNum;
+
+ try {
+ const { records: nextList, total } = await listGood({
+ pageNumber: pageIndex,
+ pageSize,
+ search: this.data.keywords,
+ });
+ const images = nextList.map((x) => x.cover_image);
+ const handledImages = await getCloudImageTempUrl(images);
+ handledImages.forEach((image, index) => (nextList[index].cover_image = image));
+ await Promise.all(nextList.map(async (spu) => (spu.price = await getPrice(spu._id))));
+
+ const goodsList = fresh ? nextList : this.data.goodsList.concat(nextList);
+
+ this.setData({
+ goodsList,
+ loadMoreStatus: goodsList.length >= total ? LIST_LOADING_STATUS.NO_MORE : LIST_LOADING_STATUS.READY,
+ hasLoaded: true,
+ });
+
+ this.pageNum = pageIndex + 1;
+ } catch (err) {
+ console.log('error', err);
+ this.setData({ loadMoreStatus: LIST_LOADING_STATUS.FAILED });
+ }
+ },
+
+ handleCartTap() {
+ wx.switchTab({
+ url: '/pages/cart/index',
+ });
+ },
+
+ handleSubmit(e) {
+ const keywords = e?.detail?.value ?? '';
+ this.setData(
+ {
+ keywords,
+ goodsList: [],
+ loadMoreStatus: 0,
+ hasLoaded: false,
+ },
+ () => {
+ this.init(true);
+ },
+ );
+ },
+
+ onReachBottom() {
+ const { goodsList } = this.data;
+ const { total = 0 } = this;
+ if (goodsList.length === total) {
+ this.setData({
+ loadMoreStatus: 2,
+ });
+ return;
+ }
+ this.init(false);
+ },
+
+ handleAddCart() {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '点击加购',
+ });
+ },
+
+ gotoGoodsDetail(e) {
+ wx.navigateTo({
+ url: `/pages/goods/details/index?spuId=${e.detail.goods._id}`,
+ });
+ },
+
+ handleFilterChange(e) {
+ const { overall, sorts } = e.detail;
+ const { total } = this;
+ const _filter = {
+ sorts,
+ overall,
+ };
+ this.setData({
+ filter: _filter,
+ sorts,
+ overall,
+ });
+
+ this.pageNum = 1;
+ this.setData(
+ {
+ goodsList: [],
+ loadMoreStatus: 0,
+ },
+ () => {
+ total && this.init(true);
+ },
+ );
+ },
+
+ showFilterPopup() {
+ this.setData({
+ show: true,
+ });
+ },
+
+ showFilterPopupClose() {
+ this.setData({
+ show: false,
+ });
+ },
+
+ onMinValAction(e) {
+ const { value } = e.detail;
+ this.setData({ minVal: value });
+ },
+
+ onMaxValAction(e) {
+ const { value } = e.detail;
+ this.setData({ maxVal: value });
+ },
+
+ reset() {
+ this.setData({ minVal: '', maxVal: '' });
+ },
+
+ confirm() {
+ const { minVal, maxVal } = this.data;
+ let message = '';
+ if (minVal && !maxVal) {
+ message = `价格最小是${minVal}`;
+ } else if (!minVal && maxVal) {
+ message = `价格范围是0-${minVal}`;
+ } else if (minVal && maxVal && minVal <= maxVal) {
+ message = `价格范围${minVal}-${this.data.maxVal}`;
+ } else {
+ message = '请输入正确范围';
+ }
+ if (message) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message,
+ });
+ }
+ this.pageNum = 1;
+ this.setData(
+ {
+ show: false,
+ minVal: '',
+ goodsList: [],
+ loadMoreStatus: 0,
+ maxVal: '',
+ },
+ () => {
+ this.init();
+ },
+ );
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/result/index.json b/miniprogram/tcb-shop/pages/goods/result/index.json
new file mode 100644
index 0000000..130b48d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/result/index.json
@@ -0,0 +1,15 @@
+{
+ "navigationBarTitleText": "搜索",
+ "usingComponents": {
+ "t-search": "tdesign-miniprogram/search/search",
+ "t-input": "tdesign-miniprogram/input/input",
+ "t-empty": "tdesign-miniprogram/empty/empty",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "goods-list": "/components/goods-list/index",
+ "filter": "/components/filter/index",
+ "filter-popup": "/components/filter-popup/index",
+ "load-more": "/components/load-more/index",
+ "t-icon": "tdesign-miniprogram/icon/icon"
+ },
+ "onReachBottomDistance": 50
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/result/index.wxml b/miniprogram/tcb-shop/pages/goods/result/index.wxml
new file mode 100644
index 0000000..723ef03
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/result/index.wxml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/result/index.wxss b/miniprogram/tcb-shop/pages/goods/result/index.wxss
new file mode 100644
index 0000000..5fee10e
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/result/index.wxss
@@ -0,0 +1,114 @@
+page {
+ background-color: #fff;
+}
+
+page view {
+ box-sizing: border-box;
+}
+
+.result-container {
+ display: block;
+}
+
+.result-container .t-search {
+ padding: 0 30rpx;
+ background-color: #fff;
+}
+
+.result-container .t-class__input-container {
+ height: 64rpx !important;
+ border-radius: 32rpx !important;
+}
+
+.result-container .t-search__left-icon {
+ display: flex;
+ align-items: center;
+}
+
+.result-container .t-search__input {
+ font-size: 28rpx !important;
+ color: #333 !important;
+}
+
+.result-container .category-goods-list {
+ background-color: #f2f2f2;
+ overflow-y: scroll;
+ padding: 20rpx 24rpx;
+ -webkit-overflow-scrolling: touch;
+}
+
+.result-container .wr-goods-list {
+ background: #f2f2f2 !important;
+}
+
+.result-container .t-image__mask {
+ display: flex !important;
+}
+
+.result-container .empty-wrap {
+ margin-top: 184rpx;
+ margin-bottom: 120rpx;
+ height: 300rpx;
+}
+
+.result-container .empty-wrap .empty-tips .empty-content .content-text {
+ margin-top: 40rpx;
+}
+
+.result-container .price-container {
+ padding: 32rpx;
+ height: 100vh;
+ max-width: 632rpx;
+ background-color: #fff;
+ border-radius: 30rpx 0 0 30rpx;
+}
+
+.result-container .price-between {
+ font-size: 26rpx;
+ font-weight: 500;
+ color: rgba(51, 51, 51, 1);
+}
+
+.result-container .price-ipts-wrap {
+ width: 100%;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ margin-top: 24rpx;
+}
+
+.result-container .price-ipts-wrap .price-divided {
+ position: relative;
+ width: 22rpx;
+ margin: 0 20rpx;
+ color: #222427;
+}
+
+.result-container .price-ipts-wrap .price-ipt {
+ box-sizing: border-box;
+ width: 246rpx;
+ font-size: 24rpx;
+ height: 56rpx;
+ padding: 0 24rpx;
+ text-align: center;
+ border-radius: 8rpx;
+ color: #333;
+ background: rgba(245, 245, 245, 1);
+}
+
+.t-class-input {
+ font-size: 24rpx !important;
+}
+
+.t-search__clear {
+ font-size: 40rpx !important;
+}
+
+.result-container .price-ipts-wrap .price-ipt::after {
+ border: none !important;
+}
+
+.result-container .t-input__control {
+ font-size: 24rpx !important;
+ text-align: center;
+}
diff --git a/miniprogram/tcb-shop/pages/goods/search/index.js b/miniprogram/tcb-shop/pages/goods/search/index.js
new file mode 100644
index 0000000..1adf407
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/search/index.js
@@ -0,0 +1,116 @@
+import { getSearchHistory, getSearchPopular } from '../../../services/good/fetchSearchHistory';
+
+Page({
+ data: {
+ historyWords: [],
+ popularWords: [],
+ searchValue: '',
+ dialog: {
+ title: '确认删除当前历史记录',
+ showCancelButton: true,
+ message: '',
+ },
+ dialogShow: false,
+ },
+
+ deleteType: 0,
+ deleteIndex: '',
+
+ onShow() {
+ this.queryHistory();
+ this.queryPopular();
+ },
+
+ async queryHistory() {
+ try {
+ const data = await getSearchHistory();
+ const code = 'Success';
+ if (String(code).toUpperCase() === 'SUCCESS') {
+ const { historyWords = [] } = data;
+ this.setData({
+ historyWords,
+ });
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ },
+
+ async queryPopular() {
+ try {
+ const data = await getSearchPopular();
+ const code = 'Success';
+ if (String(code).toUpperCase() === 'SUCCESS') {
+ const { popularWords = [] } = data;
+ this.setData({
+ popularWords,
+ });
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ },
+
+ confirm() {
+ const { historyWords } = this.data;
+ const { deleteType, deleteIndex } = this;
+ historyWords.splice(deleteIndex, 1);
+ if (deleteType === 0) {
+ this.setData({
+ historyWords,
+ dialogShow: false,
+ });
+ } else {
+ this.setData({ historyWords: [], dialogShow: false });
+ }
+ },
+
+ close() {
+ this.setData({ dialogShow: false });
+ },
+
+ handleClearHistory() {
+ const { dialog } = this.data;
+ this.deleteType = 1;
+ this.setData({
+ dialog: {
+ ...dialog,
+ message: '确认删除所有历史记录',
+ },
+ dialogShow: true,
+ });
+ },
+
+ deleteCurr(e) {
+ const { index } = e.currentTarget.dataset;
+ const { dialog } = this.data;
+ this.deleteIndex = index;
+ this.setData({
+ dialog: {
+ ...dialog,
+ message: '确认删除当前历史记录',
+ deleteType: 0,
+ },
+ dialogShow: true,
+ });
+ },
+
+ handleHistoryTap(e) {
+ const { historyWords } = this.data;
+ const { dataset } = e.currentTarget;
+ const _searchValue = historyWords[dataset.index || 0] || '';
+ if (_searchValue) {
+ wx.navigateTo({
+ url: `/pages/goods/result/index?searchValue=${_searchValue}`,
+ });
+ }
+ },
+
+ handleSubmit(e) {
+ const value = e?.detail?.value;
+ if (!value) return;
+ wx.navigateTo({
+ url: `/pages/goods/result/index?searchValue=${value}`,
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/goods/search/index.json b/miniprogram/tcb-shop/pages/goods/search/index.json
new file mode 100644
index 0000000..74abdf7
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/search/index.json
@@ -0,0 +1,8 @@
+{
+ "navigationBarTitleText": "搜索",
+ "usingComponents": {
+ "t-search": "tdesign-miniprogram/search/search",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog"
+ }
+}
diff --git a/miniprogram/tcb-shop/pages/goods/search/index.wxml b/miniprogram/tcb-shop/pages/goods/search/index.wxml
new file mode 100644
index 0000000..59dd11f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/search/index.wxml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/goods/search/index.wxss b/miniprogram/tcb-shop/pages/goods/search/index.wxss
new file mode 100644
index 0000000..676603e
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/goods/search/index.wxss
@@ -0,0 +1,79 @@
+.search-page {
+ box-sizing: border-box;
+ width: 100vw;
+ height: 100vh;
+ padding: 0 30rpx;
+}
+
+.search-page .t-class__input-container {
+ height: 64rpx !important;
+ border-radius: 32rpx !important;
+}
+
+.search-page .t-search__input {
+ font-size: 28rpx !important;
+ color: #333 !important;
+}
+
+.search-page .search-wrap {
+ margin-top: 44rpx;
+}
+
+.search-page .history-wrap {
+ margin-bottom: 20px;
+}
+
+.search-page .search-header {
+ display: flex;
+ flex-flow: row nowrap;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search-page .search-title {
+ font-size: 30rpx;
+ font-family: PingFangSC-Semibold, PingFang SC;
+ font-weight: 600;
+ color: rgba(51, 51, 51, 1);
+ line-height: 42rpx;
+}
+
+.search-page .search-clear {
+ font-size: 24rpx;
+ font-family: PingFang SC;
+ line-height: 32rpx;
+ color: #999999;
+ font-weight: normal;
+}
+
+.search-page .search-content {
+ overflow: hidden;
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: flex-start;
+ align-items: flex-start;
+ margin-top: 24rpx;
+}
+
+.search-page .search-item {
+ color: #333333;
+ font-size: 24rpx;
+ line-height: 32rpx;
+ font-weight: normal;
+ margin-right: 24rpx;
+ margin-bottom: 24rpx;
+ background: #f5f5f5;
+ border-radius: 38rpx;
+ padding: 12rpx 24rpx;
+}
+
+.search-page .hover-history-item {
+ position: relative;
+ top: 3rpx;
+ left: 3rpx;
+ box-shadow: 0px 0px 8px rgba(0, 0, 0, 0.1) inset;
+}
+
+.add-notes__confirm {
+ color: #fa4126 !important;
+}
diff --git a/miniprogram/tcb-shop/pages/home/home.js b/miniprogram/tcb-shop/pages/home/home.js
new file mode 100644
index 0000000..f5412dd
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/home/home.js
@@ -0,0 +1,135 @@
+/* eslint-disable no-param-reassign */
+import { getHomeSwiper } from '../../services/home/home';
+import { listGood, getPrice } from '../../services/good/spu';
+import { getCloudImageTempUrl } from '../../utils/cloudImageHandler';
+import { LIST_LOADING_STATUS } from '../../utils/listLoading';
+
+Page({
+ data: {
+ imgSrcs: [],
+ tabList: [],
+ goodsList: [],
+ goodsListLoadStatus: LIST_LOADING_STATUS.READY,
+ pageLoading: false,
+ current: 1,
+ autoplay: true,
+ duration: '500',
+ interval: 5000,
+ navigation: { type: 'dots' },
+ swiperImageProps: { mode: 'scaleToFill' },
+ },
+
+ goodListPagination: {
+ index: 1,
+ num: 20,
+ },
+
+ privateData: {
+ tabIndex: 0,
+ },
+
+ onShow() {
+ this.getTabBar().init();
+ },
+
+ onLoad() {
+ this.init();
+ },
+
+ onReachBottom() {
+ if (this.data.goodsListLoadStatus === LIST_LOADING_STATUS.READY) {
+ this.loadGoodsList();
+ }
+ },
+
+ onPullDownRefresh() {
+ this.init();
+ },
+
+ async init() {
+ wx.stopPullDownRefresh();
+
+ this.setData({
+ pageLoading: false,
+ });
+
+ this.loadGoodsList(true);
+ this.loadHomeSwiper();
+ },
+
+ async loadHomeSwiper() {
+ const { images } = await getHomeSwiper();
+ const imgSrcs = (
+ await wx.cloud.getTempFileURL({
+ fileList: images,
+ })
+ ).fileList.map((x) => x.tempFileURL);
+
+ this.setData({ imgSrcs });
+ },
+
+ onReTry() {
+ this.loadGoodsList();
+ },
+
+ async loadGoodsList(fresh = false) {
+ if (fresh) {
+ wx.pageScrollTo({
+ scrollTop: 0,
+ });
+ }
+
+ this.setData({ goodsListLoadStatus: LIST_LOADING_STATUS.LOADING });
+
+ const pageSize = this.goodListPagination.num;
+ const pageIndex = fresh ? 1 : this.goodListPagination.num;
+
+ try {
+ const { records: nextList, total } = await listGood({ pageNumber: pageIndex, pageSize });
+ const images = nextList.map((x) => x.cover_image);
+ const handledImages = await getCloudImageTempUrl(images);
+ handledImages.forEach((image, index) => (nextList[index].cover_image = image));
+ await Promise.all(nextList.map(async (spu) => (spu.price = await getPrice(spu._id))));
+
+ const goodsList = fresh ? nextList : this.data.goodsList.concat(nextList);
+
+ this.setData({
+ goodsList,
+ goodsListLoadStatus: goodsList.length >= total ? LIST_LOADING_STATUS.NO_MORE : LIST_LOADING_STATUS.READY,
+ });
+
+ this.goodListPagination.index = pageIndex + 1;
+ this.goodListPagination.num = pageSize;
+ } catch (err) {
+ console.error('error', err);
+ this.setData({ goodsListLoadStatus: LIST_LOADING_STATUS.FAILED });
+ }
+ },
+
+ goodListClickHandle(e) {
+ const spuId = e?.detail?.goods?._id;
+ if (typeof spuId !== 'string') return;
+ wx.navigateTo({
+ url: `/pages/goods/details/index?spuId=${spuId}`,
+ });
+ },
+
+ goodListAddCartHandle(e) {
+ const spuId = e?.detail?.goods?._id;
+ if (typeof spuId !== 'string') return;
+ wx.navigateTo({
+ url: `/pages/goods/details/index?spuId=${spuId}`,
+ });
+ },
+
+ navToSearchPage() {
+ wx.navigateTo({ url: '/pages/goods/search/index' });
+ },
+
+ navToActivityDetail({ detail }) {
+ const { index: promotionID = 0 } = detail || {};
+ wx.navigateTo({
+ url: `/pages/promotion-detail/index?promotion_id=${promotionID}`,
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/home/home.json b/miniprogram/tcb-shop/pages/home/home.json
new file mode 100644
index 0000000..8df2da4
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/home/home.json
@@ -0,0 +1,20 @@
+{
+ "navigationBarTitleText": "首页",
+ "onReachBottomDistance": 10,
+ "backgroundTextStyle": "light",
+ "enablePullDownRefresh": true,
+ "usingComponents": {
+ "t-search": "tdesign-miniprogram/search/search",
+ "t-loading": "tdesign-miniprogram/loading/loading",
+ "t-swiper": "tdesign-miniprogram/swiper/swiper",
+ "t-swiper-nav": "tdesign-miniprogram/swiper-nav/swiper-nav",
+ "t-image": "/components/webp-image/index",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-tabs": "tdesign-miniprogram/tabs/tabs",
+ "t-tab-panel": "tdesign-miniprogram/tab-panel/tab-panel",
+ "goods-list": "/components/goods-list/index",
+ "load-more": "/components/load-more/index",
+ "cloud-template-dialog": "/components/cloud-template-dialog/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/home/home.wxml b/miniprogram/tcb-shop/pages/home/home.wxml
new file mode 100644
index 0000000..b2bff61
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/home/home.wxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/home/home.wxss b/miniprogram/tcb-shop/pages/home/home.wxss
new file mode 100644
index 0000000..e0e7d6f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/home/home.wxss
@@ -0,0 +1,107 @@
+page {
+ box-sizing: border-box;
+ padding-bottom: calc(env(safe-area-inset-bottom) + 96rpx);
+}
+
+.t-tabs.t-tabs--top .t-tabs__scroll {
+ border-bottom: none !important;
+}
+
+.home-page-header {
+ background: linear-gradient(#fff, #f5f5f5);
+}
+
+.home-page-container {
+ background: #f5f5f5;
+}
+
+.home-page-container,
+.home-page-header {
+ display: block;
+ padding: 0 24rpx;
+}
+
+.home-page-header .t-search__input-container {
+ border-radius: 32rpx !important;
+ height: 64rpx !important;
+}
+
+.home-page-header .t-search__input {
+ font-size: 28rpx !important;
+ color: rgb(116, 116, 116) !important;
+}
+
+.home-page-header .swiper-wrap {
+ margin-top: 20rpx;
+}
+
+.home-page-header .t-image__swiper {
+ width: 100%;
+ height: 300rpx;
+ border-radius: 10rpx;
+}
+
+.home-page-container .t-tabs {
+ background: #f5f5f5 !important;
+}
+
+.home-page-container .t-tabs .t-tabs-nav {
+ background-color: transparent;
+ line-height: 80rpx;
+ font-size: 28rpx;
+ color: #333;
+}
+
+.home-page-container .t-tabs .t-tabs-scroll {
+ border: none !important;
+}
+
+/* 半个字 */
+.home-page-container .tab.order-nav .order-nav-item.scroll-width {
+ min-width: 165rpx;
+}
+.home-page-container .tab .order-nav-item.active {
+ color: #fa550f !important;
+}
+
+.home-page-container .tab .bottom-line {
+ border-radius: 4rpx;
+}
+
+.home-page-container .tab .order-nav-item.active .bottom-line {
+ background-color: #fa550f !important;
+}
+
+.home-page-container .tabs-external__item {
+ /* color: #666 !important; */
+ font-size: 28rpx;
+}
+
+.home-page-container .tabs-external__active {
+ color: #333333 !important;
+ font-size: 32rpx;
+}
+
+.home-page-container .tabs-external__track {
+ /* background-color: #fa4126 !important; */
+ height: 6rpx !important;
+ border-radius: 4rpx !important;
+ width: 48rpx !important;
+}
+
+.t-tabs.t-tabs--top .t-tabs__item,
+.t-tabs.t-tabs--bottom .t-tabs__item {
+ height: 86rpx !important;
+}
+
+.home-page-container .goods-list-container {
+ background: #f5f5f5 !important;
+ margin-top: 16rpx;
+}
+
+.home-page-tabs {
+ --td-tab-nav-bg-color: transparent;
+ --td-tab-border-color: transparent;
+ --td-tab-item-color: #666;
+ --td-tab-track-color: red;
+}
diff --git a/miniprogram/tcb-shop/pages/home/readme b/miniprogram/tcb-shop/pages/home/readme
new file mode 100644
index 0000000..fcf75d5
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/home/readme
@@ -0,0 +1,8 @@
+首页功能设定
+1. loading入场
+2. 下拉刷新
+3. 搜索栏
+4. 分类切换
+5. 商品列表
+6. 规格弹层
+7. 加载更多
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/after-service-detail/api.js b/miniprogram/tcb-shop/pages/order/after-service-detail/api.js
new file mode 100644
index 0000000..22d62fe
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/after-service-detail/api.js
@@ -0,0 +1,34 @@
+import { resp } from '../after-service-list/api';
+import dayjs from 'dayjs';
+import { mockIp, mockReqId } from '../../../utils/mock';
+
+export const formatTime = (date, template) => dayjs(date).format(template);
+
+export function getRightsDetail({ rightsNo }) {
+ const _resq = {
+ data: {},
+ code: 'Success',
+ msg: null,
+ requestId: mockReqId(),
+ clientIp: mockIp(),
+ rt: 79,
+ success: true,
+ };
+ _resq.data =
+ resp.data.dataList.filter((item) => item.rights.rightsNo === rightsNo) ||
+ {};
+ return Promise.resolve(_resq);
+}
+
+export function cancelRights() {
+ const _resq = {
+ data: {},
+ code: 'Success',
+ msg: null,
+ requestId: mockReqId(),
+ clientIp: mockIp(),
+ rt: 79,
+ success: true,
+ };
+ return Promise.resolve(_resq);
+}
diff --git a/miniprogram/tcb-shop/pages/order/after-service-detail/index.js b/miniprogram/tcb-shop/pages/order/after-service-detail/index.js
new file mode 100644
index 0000000..d90dba4
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/after-service-detail/index.js
@@ -0,0 +1,205 @@
+import Toast from 'tdesign-miniprogram/toast/index';
+import { ServiceType, ServiceTypeDesc, ServiceStatus } from '../config';
+import { formatTime, getRightsDetail } from './api';
+
+const TitleConfig = {
+ [ServiceType.ORDER_CANCEL]: '退款详情',
+ [ServiceType.ONLY_REFUND]: '退款详情',
+ [ServiceType.RETURN_GOODS]: '退货退款详情',
+};
+
+Page({
+ data: {
+ pageLoading: true,
+ serviceRaw: {},
+ service: {},
+ deliveryButton: {},
+ gallery: {
+ current: 0,
+ show: false,
+ proofs: [],
+ },
+ showProofs: false,
+ backRefresh: false,
+ },
+
+ onLoad(query) {
+ this.rightsNo = query.rightsNo;
+ this.inputDialog = this.selectComponent('#input-dialog');
+ this.init();
+ },
+
+ onShow() {
+ // 当从其他页面返回,并且 backRefresh 被置为 true 时,刷新数据
+ if (!this.data.backRefresh) return;
+ this.init();
+ this.setData({ backRefresh: false });
+ },
+
+ // 页面刷新,展示下拉刷新
+ onPullDownRefresh_(e) {
+ const { callback } = e.detail;
+ return this.getService().then(() => callback && callback());
+ },
+
+ init() {
+ this.setData({ pageLoading: true });
+ this.getService().then(() => {
+ this.setData({ pageLoading: false });
+ });
+ },
+
+ getService() {
+ const params = { rightsNo: this.rightsNo };
+ return getRightsDetail(params).then((res) => {
+ const serviceRaw = res.data[0];
+ // 滤掉填写运单号、修改运单号按钮,这两个按钮特殊处理,不在底部按钮栏展示
+ if (!serviceRaw.buttonVOs) serviceRaw.buttonVOs = [];
+ const deliveryButton = {};
+ const service = {
+ id: serviceRaw.rights.rightsNo,
+ serviceNo: serviceRaw.rights.rightsNo,
+ storeName: serviceRaw.rights.storeName,
+ type: serviceRaw.rights.rightsType,
+ typeDesc: ServiceTypeDesc[serviceRaw.rights.rightsType],
+ status: serviceRaw.rights.rightsStatus,
+ statusIcon: this.genStatusIcon(serviceRaw.rights),
+ statusName: serviceRaw.rights.userRightsStatusName,
+ statusDesc: serviceRaw.rights.userRightsStatusDesc,
+ amount: serviceRaw.rights.refundRequestAmount,
+ goodsList: (serviceRaw.rightsItem || []).map((item, i) => ({
+ id: i,
+ thumb: item.goodsPictureUrl,
+ title: item.goodsName,
+ specs: (item.specInfo || []).map((s) => s.specValues || ''),
+ itemRefundAmount: item.itemRefundAmount,
+ rightsQuantity: item.rightsQuantity,
+ })),
+ orderNo: serviceRaw.rights.orderNo, // 订单编号
+ rightsNo: serviceRaw.rights.rightsNo, // 售后服务单号
+ rightsReasonDesc: serviceRaw.rights.rightsReasonDesc, // 申请售后原因
+ isRefunded: serviceRaw.rights.userRightsStatus === ServiceStatus.REFUNDED, // 是否已退款
+ refundMethodList: (serviceRaw.refundMethodList || []).map((m) => ({
+ name: m.refundMethodName,
+ amount: m.refundMethodAmount,
+ })), // 退款明细
+ refundRequestAmount: serviceRaw.rights.refundRequestAmount, // 申请退款金额
+ payTraceNo: serviceRaw.rightsRefund.traceNo, // 交易流水号
+ createTime: formatTime(parseFloat(`${serviceRaw.rights.createTime}`), 'YYYY-MM-DD HH:mm'), // 申请时间
+ logisticsNo: serviceRaw.logisticsVO.logisticsNo, // 退货物流单号
+ logisticsCompanyName: serviceRaw.logisticsVO.logisticsCompanyName, // 退货物流公司
+ logisticsCompanyCode: serviceRaw.logisticsVO.logisticsCompanyCode, // 退货物流公司
+ remark: serviceRaw.logisticsVO.remark, // 退货备注
+ receiverName: serviceRaw.logisticsVO.receiverName, // 收货人
+ receiverPhone: serviceRaw.logisticsVO.receiverPhone, // 收货人电话
+ receiverAddress: this.composeAddress(serviceRaw), // 收货人地址
+ applyRemark: serviceRaw.rightsRefund.refundDesc, // 申请退款时的填写的说明
+ buttons: serviceRaw.buttonVOs || [],
+ logistics: serviceRaw.logisticsVO,
+ };
+ const proofs = serviceRaw.rights.rightsImageUrls || [];
+ this.setData({
+ serviceRaw,
+ service,
+ deliveryButton,
+ 'gallery.proofs': proofs,
+ showProofs:
+ serviceRaw.rights.userRightsStatus === ServiceStatus.PENDING_VERIFY &&
+ (service.applyRemark || proofs.length > 0),
+ });
+ wx.setNavigationBarTitle({
+ title: TitleConfig[service.type],
+ });
+ });
+ },
+
+ composeAddress(service) {
+ return [
+ service.logisticsVO.receiverProvince,
+ service.logisticsVO.receiverCity,
+ service.logisticsVO.receiverCountry,
+ service.logisticsVO.receiverArea,
+ service.logisticsVO.receiverAddress,
+ ]
+ .filter((item) => !!item)
+ .join(' ');
+ },
+
+ onRefresh() {
+ this.init();
+ },
+
+ editLogistices() {
+ this.setData({
+ inputDialogVisible: true,
+ });
+ this.inputDialog.setData({
+ cancelBtn: '取消',
+ confirmBtn: '确定',
+ });
+ this.inputDialog._onConfirm = () => {
+ Toast({
+ message: '确定填写物流单号',
+ });
+ };
+ },
+
+ onProofTap(e) {
+ if (this.data.gallery.show) {
+ this.setData({
+ 'gallery.show': false,
+ });
+ return;
+ }
+ const { index } = e.currentTarget.dataset;
+ this.setData({
+ 'gallery.show': true,
+ 'gallery.current': index,
+ });
+ },
+
+ onGoodsCardTap(e) {
+ const { index } = e.currentTarget.dataset;
+ const goods = this.data.serviceRaw.rightsItem[index];
+ wx.navigateTo({ url: `/pages/goods/details/index?skuId=${goods.skuId}` });
+ },
+
+ onServiceNoCopy() {
+ wx.setClipboardData({
+ data: this.data.service.serviceNo,
+ });
+ },
+
+ onAddressCopy() {
+ wx.setClipboardData({
+ data: `${this.data.service.receiverName} ${this.data.service.receiverPhone}\n${this.data.service.receiverAddress}`,
+ });
+ },
+
+ /** 获取状态ICON */
+ genStatusIcon(item) {
+ const { userRightsStatus, afterSaleRequireType } = item;
+ switch (userRightsStatus) {
+ // 退款成功
+ case ServiceStatus.REFUNDED: {
+ return 'succeed';
+ }
+ // 已取消、已关闭
+ case ServiceStatus.CLOSED: {
+ return 'indent_close';
+ }
+ default: {
+ switch (afterSaleRequireType) {
+ case 'REFUND_MONEY': {
+ return 'goods_refund';
+ }
+ case 'REFUND_GOODS_MONEY':
+ return 'goods_return';
+ default: {
+ return 'goods_return';
+ }
+ }
+ }
+ }
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/after-service-detail/index.json b/miniprogram/tcb-shop/pages/order/after-service-detail/index.json
new file mode 100644
index 0000000..28e72ee
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/after-service-detail/index.json
@@ -0,0 +1,21 @@
+{
+ "navigationBarTitleText": "",
+ "usingComponents": {
+ "wr-loading-content": "/components/loading-content/index",
+ "wr-price": "/components/price/index",
+ "wr-service-goods-card": "../components/order-goods-card/index",
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
+ "t-pull-down-refresh": "tdesign-miniprogram/pull-down-refresh/pull-down-refresh",
+ "t-grid": "tdesign-miniprogram/grid/grid",
+ "t-grid-item": "tdesign-miniprogram/grid-item/grid-item",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "t-input": "tdesign-miniprogram/input/input",
+ "t-swiper": "tdesign-miniprogram/swiper/swiper",
+ "t-swiper-nav": "tdesign-miniprogram/swiper-nav/swiper-nav",
+ "wr-after-service-button-bar": "../components/after-service-button-bar/index",
+ "t-image": "/components/webp-image/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/after-service-detail/index.wxml b/miniprogram/tcb-shop/pages/order/after-service-detail/index.wxml
new file mode 100644
index 0000000..9ad5f79
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/after-service-detail/index.wxml
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 复制
+
+
+ {{service.receiverAddress}}
+ 收货人:{{service.receiverName}}
+ 收货人手机:{{service.receiverName}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 复制
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 物流单号
+
+ {{amountTip}}
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/after-service-detail/index.wxss b/miniprogram/tcb-shop/pages/order/after-service-detail/index.wxss
new file mode 100644
index 0000000..8431507
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/after-service-detail/index.wxss
@@ -0,0 +1,435 @@
+:host {
+ background-color: #f5f5f5;
+}
+
+.service-detail {
+ position: relative;
+}
+
+.service-detail wr-service-goods-card .wr-goods-card__body {
+ margin-left: 50rpx;
+}
+
+.order-goods-card-footer {
+ display: flex;
+ width: calc(100% - 190rpx);
+ justify-content: space-between;
+ position: absolute;
+ bottom: 20rpx;
+ left: 190rpx;
+}
+
+.order-goods-card-footer-num {
+ color: #999;
+ line-height: 40rpx;
+}
+
+.service-detail .order-goods-card-footer .order-goods-card-footer-price-class {
+ font-size: 36rpx;
+ color: #333;
+ font-family: DIN Alternate;
+}
+
+.service-detail .order-goods-card-footer .order-goods-card-footer-price-decimal {
+ font-size: 28rpx;
+ color: #333;
+ font-family: DIN Alternate;
+}
+
+.service-detail .order-goods-card-footer .order-goods-card-footer-price-symbol {
+ color: #333;
+ font-size: 24rpx;
+ font-family: DIN Alternate;
+}
+
+.service-detail .service-detail__header {
+ padding: 60rpx 0 48rpx 40rpx;
+ box-sizing: border-box;
+ height: 220rpx;
+ background-color: #fff;
+}
+.service-detail .service-detail__header .title,
+.service-detail .service-detail__header .desc {
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+}
+
+.service-detail .service-detail__header .title {
+ -webkit-line-clamp: 1;
+ font-size: 48rpx;
+ font-weight: bold;
+ color: #333;
+ display: flex;
+}
+
+.service-detail .service-detail__header .desc {
+ -webkit-line-clamp: 2;
+ margin-top: 10rpx;
+ font-size: 28rpx;
+ color: #999;
+}
+
+.service-detail .service-detail__header .desc .count-down {
+ color: #fff185;
+ display: inline;
+}
+
+.service-detail .service-section {
+ margin: 20rpx 0 20rpx 0;
+ /* padding: 30rpx 32rpx; */
+ width: auto;
+ border-radius: 8rpx;
+ background-color: white;
+ overflow: hidden;
+}
+.service-section__pay {
+ margin: 0 0 20rpx 0;
+ width: auto;
+ border-radius: 8rpx;
+ background-color: white;
+ overflow: hidden;
+}
+.service-detail .service-section__title {
+ color: #333333;
+ margin-bottom: 10rpx;
+ padding-bottom: 18rpx;
+ height: 224rpx;
+ position: relative;
+}
+.service-detail .service-section__title .icon {
+ margin-right: 16rpx;
+ font-size: 40rpx !important;
+}
+.service-detail .service-section__title .right {
+ flex: none;
+ font-weight: normal;
+ font-size: 26rpx;
+}
+.service-detail .section-content {
+ margin: 16rpx 0 0 52rpx;
+}
+
+.service-detail .main {
+ font-size: 28rpx;
+ color: #222427;
+ font-weight: bold;
+}
+
+.service-detail .main .phone-num {
+ margin-left: 16rpx;
+ display: inline;
+}
+.service-detail .label {
+ color: #999999;
+ font-size: 26rpx;
+}
+
+.service-detail .custom-remark {
+ font-size: 26rpx;
+ line-height: 36rpx;
+ color: #333333;
+ word-wrap: break-word;
+}
+.service-detail .proofs {
+ margin-top: 20rpx;
+}
+
+.service-detail .proofs .proof {
+ width: 100%;
+ height: 100%;
+ background-color: #f9f9f9;
+}
+
+.service-detail .pay-result .t-cell-title,
+.service-detail .pay-result .t-cell-value {
+ color: #666666;
+ font-size: 28rpx;
+}
+
+.t-class-wrapper {
+ padding: 10rpx 24rpx !important;
+}
+
+.t-class-wrapper-first-child {
+ padding: 24rpx !important;
+}
+
+.service-detail .pay-result .wr-cell__value {
+ font-weight: bold;
+}
+.service-detail .right {
+ font-size: 36rpx;
+ color: #fa550f;
+ font-weight: bold;
+}
+
+.service-detail .title {
+ font-weight: bold;
+}
+
+.service-detail .pay-result .service-section__title .right.integer {
+ font-size: 48rpx;
+}
+.service-detail .pay-result .split-line {
+ position: relative;
+}
+
+.service-detail .pay-result .split-line::after {
+ position: absolute;
+ display: block;
+ content: ' ';
+ height: 1px;
+ left: -50%;
+ right: -50%;
+ transform: scale(0.5);
+ background-color: #e6e6e6;
+}
+
+.service-detail .pay-result .section-content {
+ margin-left: 0;
+}
+
+.service-detail .pay-result .section-content .label {
+ color: #999999;
+ font-size: 24rpx;
+}
+
+.service-detail .pay-result .wr-cell::after {
+ left: 0;
+}
+
+.service-detail .footer-bar-wrapper {
+ height: 100rpx;
+}
+
+.service-detail .footer-bar-wrapper .footer-bar {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ height: 100rpx;
+ width: 100vw;
+ box-sizing: border-box;
+ padding: 0 20rpx;
+ background-color: white;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.service-detail .text-btn {
+ display: inline;
+ box-sizing: border-box;
+ color: #333;
+ border: 2rpx solid #ddd;
+ border-radius: 32rpx;
+ margin-left: 10rpx;
+ padding: 0 16rpx;
+ font-weight: normal;
+ font-size: 24rpx;
+ line-height: 32rpx;
+}
+.service-detail .text-btn--active {
+ opacity: 0.5;
+}
+
+.service-detail .specs-popup .bottom-btn {
+ color: #fa550f;
+}
+.service-detail .specs-popup .bottom-btn::after {
+ color: #fa550f;
+}
+
+.dialog .dialog__button-confirm {
+ color: #fa550f;
+}
+
+.page-container .order-goods-card > wr-goods-card .wr-goods-card__bottom .price {
+ top: 100rpx;
+ left: 10rpx;
+ position: absolute;
+ color: #333;
+}
+
+.page-container .order-goods-card > wr-goods-card .wr-goods-card__num {
+ top: 100rpx;
+ right: 0;
+ position: absolute;
+}
+
+.page-container .order-goods-card > wr-goods-card .wr-goods-card__bottom .price::before {
+ display: inline;
+ content: '退款金额:';
+ margin-right: 1em;
+ font-size: 24rpx;
+ color: #333333;
+ font-weight: normal;
+}
+
+.page-container .wr-goods-card__specs {
+ margin: 14rpx 20rpx 0 0;
+}
+
+.page-container .order-goods-card > wr-goods-card .wr-goods-card__title {
+ margin-right: 0;
+ -webkit-line-clamp: 1;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ width: 80%;
+}
+
+.page-container .order-card .header .store-name {
+ -webkit-line-clamp: 1;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ width: 80%;
+}
+
+.page-container .status-desc {
+ box-sizing: border-box;
+ padding: 22rpx 20rpx;
+ font-size: 26rpx;
+ line-height: 1.3;
+ text-align: left;
+ color: #333333;
+ background-color: #f5f5f5;
+ border-radius: 8rpx;
+ word-wrap: break-word;
+ margin-top: 40rpx;
+ margin-bottom: 20rpx;
+}
+
+.page-container .header__right {
+ font-size: 24rpx;
+ color: #333333;
+ display: flex;
+ align-items: center;
+}
+
+.page-container .header__right__icon {
+ color: #d05b27;
+ font-size: 16px !important;
+ margin-right: 10rpx;
+}
+
+.page-container .wr-goods-card__thumb {
+ width: 140rpx;
+}
+.page-container .page-background {
+ position: absolute;
+ z-index: -1;
+ top: 0;
+ left: 0;
+ width: 100vw;
+ color: #fff;
+ overflow: hidden;
+}
+.page-container .page-background-img {
+ width: 100%;
+ height: 320rpx !important;
+}
+.page-container .navbar-bg .nav-back,
+.page-container .navbar-bg .page-background {
+ background: linear-gradient(to right, rgba(250, 85, 15, 1) 0%, rgba(250, 85, 15, 0.6) 100%) !important;
+}
+
+.page-container .navigation-bar__btn {
+ font-size: 40rpx !important;
+ font-weight: bold !important;
+ color: #333;
+}
+
+.t-class-title {
+ color: #000;
+}
+
+.refresh-bar {
+ background: linear-gradient(90deg, #ff6b44 0%, #ed3427 100%) !important;
+}
+
+.page-container .navigation-bar__inner .navigation-bar__left {
+ padding-left: 16rpx;
+}
+
+.t-refund-info {
+ font-size: 26rpx;
+ color: #666;
+}
+
+.t-refund-grid-image {
+ width: 212rpx !important;
+ height: 212rpx !important;
+}
+
+.t-refund-info-img {
+ width: 100%;
+ height: 100%;
+}
+
+.t-refund-wrapper {
+ padding-top: 18rpx !important;
+ padding-bottom: 18rpx !important;
+}
+
+.t-refund-title {
+ font-size: 28rpx;
+ color: #333;
+ font-weight: bold;
+}
+
+.t-refund-note {
+ font-size: 26rpx;
+ color: #333 !important;
+}
+
+.service-detail .logistics {
+ padding-top: 0;
+ padding-bottom: 0;
+ padding-right: 0;
+}
+
+.service-section__title__header {
+ display: flex;
+ align-items: center;
+ color: #333;
+ font-weight: normal;
+ font-size: 32rpx;
+}
+
+.safe-bottom {
+ padding-bottom: env(safe-area-inset-bottom);
+}
+
+.service-section-logistics {
+ display: flex;
+ justify-content: center;
+ color: #fa4126;
+ align-items: center;
+ margin-top: 24rpx;
+}
+
+.t-class-indicator {
+ color: #b9b9b9 !important;
+}
+
+.service-detail .goods-refund-address {
+ padding-top: 0;
+ padding-bottom: 0;
+}
+
+.service-detail .goods-refund-address .goods-refund-address-copy-btn {
+ position: absolute;
+ top: 22rpx;
+ right: 32rpx;
+}
+
+.service-detail .service-goods-card-wrap {
+ padding: 0 32rpx;
+}
+
+.t-button {
+ --td-button-default-color: #000;
+ --td-button-primary-text-color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/order/after-service-list/api.js b/miniprogram/tcb-shop/pages/order/after-service-list/api.js
new file mode 100644
index 0000000..5350229
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/after-service-list/api.js
@@ -0,0 +1,32 @@
+/* eslint-disable */
+import { mockIp, mockReqId } from '../../../utils/mock';
+
+export const resp = {
+ data: {
+ pageNum: 1,
+ pageSize: 10,
+ totalCount: 51,
+ states: {
+ audit: 1,
+ approved: 6,
+ complete: 2,
+ closed: 1,
+ },
+ dataList: [],
+ },
+ code: 'Success',
+ msg: null,
+ requestId: mockReqId(),
+ clientIp: mockIp(),
+ rt: 79,
+ success: true,
+};
+
+export function getRightsList({ parameter: { afterServiceStatus, pageNum } }) {
+ const _resq = JSON.parse(JSON.stringify(resp));
+ if (pageNum > 3) _resq.data.dataList = [];
+ if (afterServiceStatus > -1) {
+ _resq.data.dataList = _resq.data.dataList.filter((item) => item.rights.rightsStatus === afterServiceStatus);
+ }
+ return Promise.resolve(_resq);
+}
diff --git a/miniprogram/tcb-shop/pages/order/after-service-list/index.js b/miniprogram/tcb-shop/pages/order/after-service-list/index.js
new file mode 100644
index 0000000..7cd0cd5
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/after-service-list/index.js
@@ -0,0 +1,220 @@
+import { getRightsList } from './api';
+import { AfterServiceStatus, ServiceType, ServiceTypeDesc } from '../config';
+
+Page({
+ page: {
+ size: 10,
+ num: 1,
+ },
+
+ data: {
+ tabs: [
+ {
+ key: -1,
+ text: '全部',
+ },
+ {
+ key: AfterServiceStatus.TO_AUDIT,
+ text: '待审核',
+ },
+ {
+ key: AfterServiceStatus.THE_APPROVED,
+ text: '已审核',
+ },
+ {
+ key: AfterServiceStatus.COMPLETE,
+ text: '已完成',
+ },
+ {
+ key: AfterServiceStatus.CLOSED,
+ text: '已关闭',
+ },
+ ],
+ curTab: -1,
+ dataList: [],
+ listLoading: 0, // 0-未加载,1-加载中,2-已全部加载
+ pullDownRefreshing: false, // 下拉刷新时不显示load-more
+ emptyImg:
+ 'https://cdn-we-retail.ym.tencent.com/miniapp/order/empty-order-list.png',
+ backRefresh: false,
+ },
+
+ onLoad(query) {
+ let status = parseInt(query.status);
+ status = this.data.tabs.map((t) => t.key).includes(status) ? status : -1;
+ this.init(status);
+ this.pullDownRefresh = this.selectComponent('#wr-pull-down-refresh');
+ },
+
+ onShow() {
+ // 当从其他页面返回,并且 backRefresh 被置为 true 时,刷新数据
+ if (!this.data.backRefresh) return;
+ this.onRefresh();
+ this.setData({
+ backRefresh: false,
+ });
+ },
+
+ onReachBottom() {
+ if (this.data.listLoading === 0) {
+ this.getAfterServiceList(this.data.curTab);
+ }
+ },
+
+ onPageScroll(e) {
+ this.pullDownRefresh && this.pullDownRefresh.onPageScroll(e);
+ },
+
+ onPullDownRefresh_(e) {
+ const { callback } = e.detail;
+ this.setData({
+ pullDownRefreshing: true,
+ }); // 下拉刷新时不显示load-more
+ this.refreshList(this.data.curTab)
+ .then(() => {
+ this.setData({
+ pullDownRefreshing: false,
+ });
+ callback && callback();
+ })
+ .catch((err) => {
+ this.setData({
+ pullDownRefreshing: false,
+ });
+ Promise.reject(err);
+ });
+ },
+
+ init(status) {
+ status = status !== undefined ? status : this.data.curTab;
+ this.refreshList(status);
+ },
+
+ getAfterServiceList(statusCode = -1, reset = false) {
+ const params = {
+ parameter: {
+ pageSize: this.page.size,
+ pageNum: this.page.num,
+ },
+ };
+ if (statusCode !== -1) params.parameter.afterServiceStatus = statusCode;
+ this.setData({
+ listLoading: 1,
+ });
+ return getRightsList(params)
+ .then((res) => {
+ this.page.num++;
+ let dataList = [];
+ let { tabs } = this.data;
+ if (res && res.data && res.data.states) {
+ tabs = this.data.tabs.map((item) => {
+ switch (item.key) {
+ case AfterServiceStatus.TO_AUDIT:
+ item.info = res.data.states.audit;
+ break;
+ case AfterServiceStatus.THE_APPROVED:
+ item.info = res.data.states.approved;
+ break;
+ case AfterServiceStatus.COMPLETE:
+ item.info = res.data.states.complete;
+ break;
+ case AfterServiceStatus.CLOSED:
+ item.info = res.data.states.closed;
+ break;
+ }
+ return item;
+ });
+ }
+ if (res && res.data && res.data.dataList) {
+ dataList = (res.data.dataList || []).map((_data) => {
+ return {
+ id: _data.rights.rightsNo,
+ serviceNo: _data.rights.rightsNo,
+ storeName: _data.rights.storeName,
+ type: _data.rights.rightsType,
+ typeDesc: ServiceTypeDesc[_data.rights.rightsType],
+ typeDescIcon:
+ _data.rightsType === ServiceType.ONLY_REFUND
+ ? 'money-circle'
+ : 'return-goods-1',
+ status: _data.rights.rightsStatus,
+ statusName: _data.rights.userRightsStatusName,
+ statusDesc: _data.rights.userRightsStatusDesc,
+ amount: _data.rights.refundAmount,
+ goodsList: _data.rightsItem.map((item, i) => ({
+ id: i,
+ thumb: item.goodsPictureUrl,
+ title: item.goodsName,
+ specs: (item.specInfo || []).map((s) => s.specValues || ''),
+ itemRefundAmount: item.itemRefundAmount,
+ rightsQuantity: item.itemRefundAmount,
+ })),
+ storeId: _data.storeId,
+ buttons: _data.buttonVOs || [],
+ logisticsNo: _data.logisticsVO.logisticsNo, // 退货物流单号
+ logisticsCompanyName: _data.logisticsVO.logisticsCompanyName, // 退货物流公司
+ logisticsCompanyCode: _data.logisticsVO.logisticsCompanyCode, // 退货物流公司
+ remark: _data.logisticsVO.remark, // 退货备注
+ logisticsVO: _data.logisticsVO,
+ };
+ });
+ }
+ return new Promise((resolve) => {
+ if (reset) {
+ this.setData(
+ {
+ dataList: [],
+ },
+ () => resolve(),
+ );
+ } else resolve();
+ }).then(() => {
+ this.setData({
+ tabs,
+ dataList: this.data.dataList.concat(dataList),
+ listLoading: dataList.length > 0 ? 0 : 2,
+ });
+ });
+ })
+ .catch((err) => {
+ this.setData({
+ listLoading: 3,
+ });
+ return Promise.reject(err);
+ });
+ },
+
+ onReTryLoad() {
+ this.getAfterServiceList(this.data.curTab);
+ },
+
+ onTabChange(e) {
+ const { value } = e.detail;
+ const tab = this.data.tabs.find((v) => v.key === value);
+ if (!tab) return;
+ this.refreshList(value);
+ },
+
+ refreshList(status = -1) {
+ this.page = {
+ size: 10,
+ num: 1,
+ };
+ this.setData({
+ curTab: status,
+ dataList: [],
+ });
+ return this.getAfterServiceList(status, true);
+ },
+
+ onRefresh() {
+ this.refreshList(this.data.curTab);
+ },
+
+ // 点击订单卡片
+ onAfterServiceCardTap(e) {
+ wx.navigateTo({
+ url: `/pages/order/after-service-detail/index?rightsNo=${e.currentTarget.dataset.order.id}`,
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/after-service-list/index.json b/miniprogram/tcb-shop/pages/order/after-service-list/index.json
new file mode 100644
index 0000000..46b5d09
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/after-service-list/index.json
@@ -0,0 +1,15 @@
+{
+ "navigationBarTitleText": "退款/售后",
+ "usingComponents": {
+ "wr-load-more": "/components/load-more/index",
+ "wr-after-service-button-bar": "../components/after-service-button-bar/index",
+ "wr-price": "/components/price/index",
+ "wr-order-card": "../components/order-card/index",
+ "wr-goods-card": "../components/goods-card/index",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "t-empty": "tdesign-miniprogram/empty/empty",
+ "t-pull-down-refresh": "tdesign-miniprogram/pull-down-refresh/pull-down-refresh"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/after-service-list/index.wxml b/miniprogram/tcb-shop/pages/order/after-service-list/index.wxml
new file mode 100644
index 0000000..7b4f649
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/after-service-list/index.wxml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+ {{order.statusDesc}}
+
+
+
+
+
+
+
+
+ 暂无退款或售后申请记录
+
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/after-service-list/index.wxss b/miniprogram/tcb-shop/pages/order/after-service-list/index.wxss
new file mode 100644
index 0000000..8a784bf
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/after-service-list/index.wxss
@@ -0,0 +1,104 @@
+:host {
+ background-color: #f5f5f5;
+}
+
+.list-loading {
+ height: 100rpx;
+}
+
+.empty-wrapper {
+ height: calc(100vh - 88rpx);
+}
+
+.page-container .order-goods-card-footer {
+ display: flex;
+ width: calc(100% - 190rpx);
+ justify-content: space-between;
+ position: absolute;
+ bottom: 20rpx;
+ left: 190rpx;
+}
+
+.page-container .order-goods-card-footer .order-goods-card-footer-num {
+ color: #999;
+ line-height: 40rpx;
+}
+
+.page-container .order-goods-card-footer .order-goods-card-footer-price-class {
+ font-size: 36rpx;
+ color: #333;
+ font-family: DIN Alternate;
+}
+
+.page-container .order-goods-card-footer .order-goods-card-footer-price-decimal {
+ font-size: 28rpx;
+ color: #333;
+ font-family: DIN Alternate;
+}
+
+.page-container .order-goods-card-footer .order-goods-card-footer-price-symbol {
+ color: #333;
+ font-size: 24rpx;
+ font-family: DIN Alternate;
+}
+
+.page-container .wr-goods-card__specs {
+ margin: 14rpx 20rpx 0 0;
+}
+
+.page-container .order-goods-card > wr-goods-card .wr-goods-card__title {
+ margin-right: 0;
+ -webkit-line-clamp: 1;
+}
+
+.page-container .order-card .header .store-name {
+ width: 80%;
+ -webkit-line-clamp: 1;
+}
+
+.page-container .order-card .header .store-name > view {
+ overflow: hidden;
+ width: 100%;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+
+.page-container .status-desc {
+ box-sizing: border-box;
+ padding: 22rpx 20rpx;
+ font-size: 26rpx;
+ line-height: 1.3;
+ text-align: left;
+ color: #333333;
+ background-color: #f5f5f5;
+ border-radius: 8rpx;
+ word-wrap: break-word;
+ margin-top: 24rpx;
+ margin-bottom: 20rpx;
+}
+
+.page-container .header__right {
+ font-size: 24rpx;
+ color: #fa4126;
+ display: flex;
+ align-items: center;
+}
+
+.page-container .header__right__icon {
+ color: #d05b27;
+ font-size: 16px !important;
+ margin-right: 10rpx;
+}
+
+.t-class-indicator {
+ color: #b9b9b9 !important;
+}
+
+.page-container .header-class {
+ margin-bottom: 5rpx !important;
+}
+
+.t-button {
+ --td-button-default-color: #000;
+ --td-button-primary-text-color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/order/apply-service/index.js b/miniprogram/tcb-shop/pages/order/apply-service/index.js
new file mode 100644
index 0000000..0642e69
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/apply-service/index.js
@@ -0,0 +1,441 @@
+import Dialog from 'tdesign-miniprogram/dialog/index';
+import Toast from 'tdesign-miniprogram/toast/index';
+import { priceFormat } from '../../../utils/util';
+import { OrderStatus, ServiceType, ServiceReceiptStatus } from '../config';
+import reasonSheet from '../components/reason-sheet/reasonSheet';
+import {
+ fetchRightsPreview,
+ dispatchConfirmReceived,
+ fetchApplyReasonList,
+ dispatchApplyService,
+} from '../../../services/order/applyService';
+
+Page({
+ query: {},
+ data: {
+ uploading: false, // 凭证上传状态
+ canApplyReturn: true, // 是否可退货
+ goodsInfo: {},
+ receiptStatusList: [
+ { desc: '未收到货', status: ServiceReceiptStatus.NOT_RECEIPTED },
+ { desc: '已收到货', status: ServiceReceiptStatus.RECEIPTED },
+ ],
+ applyReasons: [],
+ serviceType: null, // 20-仅退款,10-退货退款
+ serviceFrom: {
+ returnNum: 1,
+ receiptStatus: { desc: '请选择', status: null },
+ applyReason: { desc: '请选择', type: null },
+ // max-填写上限(单位分),current-当前值(单位分),temp输入框中的值(单位元)
+ amount: { max: 0, current: 0, temp: 0, focus: false },
+ remark: '',
+ rightsImageUrls: [],
+ },
+ maxApplyNum: 2, // 最大可申请售后的商品数
+ amountTip: '',
+ showReceiptStatusDialog: false,
+ validateRes: {
+ valid: false,
+ msg: '',
+ },
+ submitting: false,
+ inputDialogVisible: false,
+ uploadGridConfig: {
+ column: 3,
+ width: 212,
+ height: 212,
+ },
+ serviceRequireType: '',
+ },
+
+ setWatcher(key, callback) {
+ let lastData = this.data;
+ const keys = key.split('.');
+ keys.slice(0, -1).forEach((k) => {
+ lastData = lastData[k];
+ });
+ const lastKey = keys[keys.length - 1];
+ this.observe(lastData, lastKey, callback);
+ },
+
+ observe(data, k, callback) {
+ let val = data[k];
+ Object.defineProperty(data, k, {
+ configurable: true,
+ enumerable: true,
+ set: (value) => {
+ val = value;
+ callback();
+ },
+ get: () => {
+ return val;
+ },
+ });
+ },
+
+ validate() {
+ let valid = true;
+ let msg = '';
+ // 检查必填项
+ if (!this.data.serviceFrom.applyReason.type) {
+ valid = false;
+ msg = '请填写退款原因';
+ } else if (!this.data.serviceFrom.amount.current) {
+ valid = false;
+ msg = '请填写退款金额';
+ }
+ if (this.data.serviceFrom.amount.current <= 0) {
+ valid = false;
+ msg = '退款金额必须大于0';
+ }
+ this.setData({ validateRes: { valid, msg } });
+ },
+
+ onLoad(query) {
+ this.query = query;
+ if (!this.checkQuery()) return;
+ this.setData({
+ canApplyReturn: query.canApplyReturn === 'true',
+ });
+ this.init();
+ this.inputDialog = this.selectComponent('#input-dialog');
+ this.setWatcher('serviceFrom.returnNum', this.validate.bind(this));
+ this.setWatcher('serviceFrom.applyReason', this.validate.bind(this));
+ this.setWatcher('serviceFrom.amount', this.validate.bind(this));
+ this.setWatcher('serviceFrom.rightsImageUrls', this.validate.bind(this));
+ },
+
+ async init() {
+ try {
+ await this.refresh();
+ } catch (e) {}
+ },
+
+ checkQuery() {
+ const { orderNo, skuId } = this.query;
+ if (!orderNo) {
+ Dialog.alert({
+ content: '请先选择订单',
+ }).then(() => {
+ wx.redirectTo({ url: 'pages/order/order-list/index' });
+ });
+ return false;
+ }
+ if (!skuId) {
+ Dialog.alert({
+ content: '请先选择商品',
+ }).then(() => {
+ wx.redirectTo(`pages/order/order-detail/index?orderNo=${orderNo}`);
+ });
+ return false;
+ }
+ return true;
+ },
+
+ async refresh() {
+ wx.showLoading({ title: 'loading' });
+ try {
+ const res = await this.getRightsPreview();
+ wx.hideLoading();
+ const goodsInfo = {
+ id: res.data.skuId,
+ thumb: res.data.goodsInfo && res.data.goodsInfo.skuImage,
+ title: res.data.goodsInfo && res.data.goodsInfo.goodsName,
+ spuId: res.data.spuId,
+ skuId: res.data.skuId,
+ specs: ((res.data.goodsInfo && res.data.goodsInfo.specInfo) || []).map((s) => s.specValue),
+ paidAmountEach: res.data.paidAmountEach,
+ boughtQuantity: res.data.boughtQuantity,
+ };
+ this.setData({
+ goodsInfo,
+ 'serviceFrom.amount': {
+ max: res.data.refundableAmount,
+ current: res.data.refundableAmount,
+ },
+ 'serviceFrom.returnNum': res.data.numOfSku,
+ amountTip: `最多可申请退款¥ ${priceFormat(res.data.refundableAmount, 2)},含发货运费¥ ${priceFormat(
+ res.data.shippingFeeIncluded,
+ 2,
+ )}`,
+ maxApplyNum: res.data.numOfSkuAvailable,
+ });
+ } catch (err) {
+ wx.hideLoading();
+ throw err;
+ }
+ },
+
+ async getRightsPreview() {
+ const { orderNo, skuId, spuId } = this.query;
+ const params = {
+ orderNo,
+ skuId,
+ spuId,
+ numOfSku: this.data.serviceFrom.returnNum,
+ };
+ const res = await fetchRightsPreview(params);
+ return res;
+ },
+
+ onApplyOnlyRefund() {
+ wx.setNavigationBarTitle({ title: '申请退款' });
+ this.setData({ serviceRequireType: 'REFUND_MONEY' });
+ this.switchReceiptStatus(0);
+ },
+
+ onApplyReturnGoods() {
+ wx.setNavigationBarTitle({ title: '申请退货退款' });
+ this.setData({ serviceRequireType: 'REFUND_GOODS' });
+ const orderStatus = parseInt(this.query.orderStatus);
+ Promise.resolve()
+ .then(() => {
+ if (orderStatus === OrderStatus.PENDING_RECEIPT) {
+ return Dialog.confirm({
+ title: '订单商品是否已经收到货',
+ content: '',
+ confirmBtn: '确认收货,并申请退货',
+ cancelBtn: '未收到货',
+ }).then(() => {
+ return dispatchConfirmReceived({
+ parameter: {
+ logisticsNo: this.query.logisticsNo,
+ orderNo: this.query.orderNo,
+ },
+ });
+ });
+ }
+ return;
+ })
+ .then(() => {
+ this.setData({ serviceType: ServiceType.RETURN_GOODS });
+ this.switchReceiptStatus(1);
+ });
+ },
+
+ onApplyReturnGoodsStatus() {
+ reasonSheet({
+ show: true,
+ title: '选择退款原因',
+ options: this.data.applyReasons.map((r) => ({
+ title: r.desc,
+ })),
+ showConfirmButton: true,
+ showCancelButton: true,
+ emptyTip: '请选择退款原因',
+ }).then((indexes) => {
+ this.setData({
+ 'serviceFrom.applyReason': this.data.applyReasons[indexes[0]],
+ });
+ });
+ },
+
+ onChangeReturnNum(e) {
+ const { value } = e.detail;
+ this.setData({
+ 'serviceFrom.returnNum': value,
+ });
+ },
+
+ onApplyGoodsStatus() {
+ reasonSheet({
+ show: true,
+ title: '请选择收货状态',
+ options: this.data.receiptStatusList.map((r) => ({
+ title: r.desc,
+ })),
+ showConfirmButton: true,
+ emptyTip: '请选择收货状态',
+ }).then((indexes) => {
+ this.setData({
+ 'serviceFrom.receiptStatus': this.data.receiptStatusList[indexes[0]],
+ });
+ });
+ },
+
+ switchReceiptStatus(index) {
+ const statusItem = this.data.receiptStatusList[index];
+ // 没有找到对应的状态,则清空/初始化
+ if (!statusItem) {
+ this.setData({
+ showReceiptStatusDialog: false,
+ 'serviceFrom.receiptStatus': { desc: '请选择', status: null },
+ 'serviceFrom.applyReason': { desc: '请选择', type: null }, // 收货状态改变时,初始化申请原因
+ applyReasons: [],
+ });
+ return;
+ }
+ // 仅选中项与当前项不一致时,才切换申请原因列表applyReasons
+ if (!statusItem || statusItem.status === this.data.serviceFrom.receiptStatus.status) {
+ this.setData({ showReceiptStatusDialog: false });
+ return;
+ }
+ this.getApplyReasons(statusItem.status).then((reasons) => {
+ this.setData({
+ showReceiptStatusDialog: false,
+ 'serviceFrom.receiptStatus': statusItem,
+ 'serviceFrom.applyReason': { desc: '请选择', type: null }, // 收货状态改变时,重置申请原因
+ applyReasons: reasons,
+ });
+ });
+ },
+
+ getApplyReasons(receiptStatus) {
+ const params = { rightsReasonType: receiptStatus };
+ return fetchApplyReasonList(params)
+ .then((res) => {
+ return res.data.rightsReasonList.map((reason) => ({
+ type: reason.id,
+ desc: reason.desc,
+ }));
+ })
+ .catch(() => {
+ return [];
+ });
+ },
+
+ onReceiptStatusDialogConfirm(e) {
+ const { index } = e.currentTarget.dataset;
+ this.switchReceiptStatus(index);
+ },
+
+ onAmountTap() {
+ this.setData({
+ 'serviceFrom.amount.temp': priceFormat(this.data.serviceFrom.amount.current),
+ 'serviceFrom.amount.focus': true,
+ inputDialogVisible: true,
+ });
+ this.inputDialog.setData({
+ cancelBtn: '取消',
+ confirmBtn: '确定',
+ });
+ this.inputDialog._onConfirm = () => {
+ this.setData({
+ 'serviceFrom.amount.current': this.data.serviceFrom.amount.temp * 100,
+ });
+ };
+ this.inputDialog._onCancel = () => {};
+ },
+
+ // 对输入的值进行过滤
+ onAmountInput(e) {
+ let { value } = e.detail;
+ const regRes = value.match(/\d+(\.?\d*)?/); // 输入中,允许末尾为小数点
+ value = regRes ? regRes[0] : '';
+ this.setData({ 'serviceFrom.amount.temp': value });
+ },
+
+ // 失去焦点时,更严格的过滤并转化为float
+ onAmountBlur(e) {
+ let { value } = e.detail;
+ const regRes = value.match(/\d+(\.?\d+)?/); // 失去焦点时,不允许末尾为小数点
+ value = regRes ? regRes[0] : '0';
+ value = parseFloat(value) * 100;
+ if (value > this.data.serviceFrom.amount.max) {
+ value = this.data.serviceFrom.amount.max;
+ }
+ this.setData({
+ 'serviceFrom.amount.temp': priceFormat(value),
+ 'serviceFrom.amount.focus': false,
+ });
+ },
+
+ onAmountFocus() {
+ this.setData({ 'serviceFrom.amount.focus': true });
+ },
+
+ onRemarkChange(e) {
+ const { value } = e.detail;
+ this.setData({
+ 'serviceFrom.remark': value,
+ });
+ },
+
+ // 发起申请售后请求
+ onSubmit() {
+ this.submitCheck().then(() => {
+ const params = {
+ rights: {
+ orderNo: this.query.orderNo,
+ refundRequestAmount: this.data.serviceFrom.amount.current,
+ rightsImageUrls: this.data.serviceFrom.rightsImageUrls,
+ rightsReasonDesc: this.data.serviceFrom.applyReason.desc,
+ rightsReasonType: this.data.serviceFrom.receiptStatus.status,
+ rightsType: this.data.serviceType,
+ },
+ rightsItem: [
+ {
+ itemTotalAmount: this.data.goodsInfo.price * this.data.serviceFrom.returnNum,
+ rightsQuantity: this.data.serviceFrom.returnNum,
+ skuId: this.query.skuId,
+ spuId: this.query.spuId,
+ },
+ ],
+ refundMemo: this.data.serviceFrom.remark.current,
+ };
+ this.setData({ submitting: true });
+ // 发起申请售后请求
+ dispatchApplyService(params)
+ .then((res) => {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '申请成功',
+ icon: '',
+ });
+
+ wx.redirectTo({
+ url: `/pages/order/after-service-detail/index?rightsNo=${res.data.rightsNo}`,
+ });
+ })
+ .then(() => this.setData({ submitting: false }))
+ .catch(() => this.setData({ submitting: false }));
+ });
+ },
+
+ submitCheck() {
+ return new Promise((resolve) => {
+ const { msg, valid } = this.data.validateRes;
+ if (!valid) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: msg,
+ icon: '',
+ });
+ return;
+ }
+ resolve();
+ });
+ },
+
+ handleSuccess(e) {
+ const { files } = e.detail;
+ this.setData({
+ 'sessionFrom.rightsImageUrls': files,
+ });
+ },
+
+ handleRemove(e) {
+ const { index } = e.detail;
+ const {
+ sessionFrom: { rightsImageUrls },
+ } = this.data;
+ rightsImageUrls.splice(index, 1);
+ this.setData({
+ 'sessionFrom.rightsImageUrls': rightsImageUrls,
+ });
+ },
+
+ handleComplete() {
+ this.setData({
+ uploading: false,
+ });
+ },
+
+ handleSelectChange() {
+ this.setData({
+ uploading: true,
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/apply-service/index.json b/miniprogram/tcb-shop/pages/order/apply-service/index.json
new file mode 100644
index 0000000..b5c4f62
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/apply-service/index.json
@@ -0,0 +1,19 @@
+{
+ "navigationBarTitleText": "选择售后类型",
+ "usingComponents": {
+ "wr-price": "/components/price/index",
+ "wr-order-goods-card": "../components/order-goods-card/index",
+ "wr-reason-sheet": "../components/reason-sheet/index",
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
+ "t-stepper": "tdesign-miniprogram/stepper/stepper",
+ "t-popup": "tdesign-miniprogram/popup/popup",
+ "t-textarea": "tdesign-miniprogram/textarea/textarea",
+ "t-input": "tdesign-miniprogram/input/input",
+ "t-upload": "tdesign-miniprogram/upload/upload"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/apply-service/index.wxml b/miniprogram/tcb-shop/pages/order/apply-service/index.wxml
new file mode 100644
index 0000000..b6471ea
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/apply-service/index.wxml
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 修改
+
+
+
+
+
+
+
+ 退款说明
+
+
+
+
+
+
+
+ 上传凭证
+ (最多3张)
+
+
+
+
+
+
+ 提交
+
+
+
+
+
+
+
+
+
+ {{item.desc}}
+
+
+ 取消
+
+
+
+
+
+
+ 退款金额
+
+
+ {{amountTip}}
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/apply-service/index.wxss b/miniprogram/tcb-shop/pages/order/apply-service/index.wxss
new file mode 100644
index 0000000..86fb12f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/apply-service/index.wxss
@@ -0,0 +1,308 @@
+:host {
+ background-color: #f5f5f5;
+}
+.select-service .service-form .service-from-group {
+ margin-top: 20rpx;
+}
+.select-service .service-form {
+ padding-bottom: calc(env(safe-area-inset-bottom) + 80rpx);
+}
+
+.order-goods-card-footer {
+ display: flex;
+ width: calc(100% - 190rpx);
+ justify-content: space-between;
+ position: absolute;
+ bottom: 0;
+ left: 190rpx;
+}
+
+.order-goods-card-footer-num {
+ color: #999;
+}
+
+.select-service .order-goods-card-footer .order-goods-card-footer-price-class {
+ font-size: 36rpx;
+ color: #333;
+ font-family: DIN Alternate;
+}
+.select-service .order-goods-card-footer .order-goods-card-footer-price-decimal {
+ font-size: 28rpx;
+ color: #333;
+ font-family: DIN Alternate;
+}
+.select-service .order-goods-card-footer .order-goods-card-footer-price-symbol {
+ color: #333;
+ font-size: 24rpx;
+ font-family: DIN Alternate;
+}
+
+.select-service .remark {
+ min-height: 110rpx;
+ border-radius: 10rpx;
+ margin-top: 20rpx;
+ background-color: #f5f5f5;
+}
+.select-service .remark::after {
+ border: none;
+}
+
+.select-service .special-cell .special-cell-note {
+ display: flex;
+ flex-direction: column;
+}
+
+.select-service .special-cell .wr-cell__title {
+ margin-right: 100rpx;
+}
+
+.select-service .special-cell .special-cell-note-price-class {
+ font-size: 36rpx;
+ color: #fa4126;
+ font-family: DIN Alternate;
+}
+.select-service .special-cell .special-cell-note-price-decimal {
+ font-size: 28rpx;
+ color: #fa4126;
+ font-family: DIN Alternate;
+}
+.select-service .special-cell .special-cell-note-price-symbol {
+ color: #fa4126;
+ font-size: 24rpx;
+ font-family: DIN Alternate;
+}
+
+.select-service .bottom-bar__btn {
+ width: 686rpx;
+ background-color: #fa4126;
+ color: white;
+ font-size: 32rpx;
+ border-radius: 48rpx;
+ position: absolute;
+ left: 50%;
+ top: 20rpx;
+ transform: translateX(-50%);
+}
+.select-service .bottom-bar__btn::after {
+ border: none;
+}
+.select-service .bottom-bar__btn.disabled {
+ background-color: #c6c6c6;
+ --td-button-default-active-bg-color: #c6c6c6;
+ --td-button-default-border-bg-color: #c6c6c6;
+}
+.select-service .bottom-bar__btn.disabled::after {
+ border: none;
+}
+.select-service .order-goods-card .wr-goods-card {
+ padding: 0 30rpx;
+}
+
+.order-goods-card-footer {
+ display: flex;
+ width: calc(100% - 190rpx);
+ justify-content: space-between;
+ position: absolute;
+ bottom: 20rpx;
+ left: 190rpx;
+}
+
+.order-goods-card-footer-num {
+ color: #999;
+ line-height: 40rpx;
+}
+
+.order-goods-card-title-class {
+ width: 10rpx !important;
+}
+
+.input-dialog__content .input-dialog__input {
+ font-size: 72rpx !important;
+ height: 64rpx;
+}
+
+.t-input__label {
+ margin-right: 0 !important;
+}
+
+.input-dialog__label {
+ font-size: 48rpx;
+ color: #333;
+}
+
+.input-dialog__content .input-dialog__input,
+.input-dialog__label {
+ height: 64rpx;
+ line-height: 64rpx !important;
+}
+
+.input-dialog__content .input {
+ font-size: 48rpx;
+ padding-left: 0;
+ padding-right: 0;
+ --td-input-border-left-space: 0;
+}
+
+.input-dialog__content .tips {
+ margin-top: 24rpx;
+ font-size: 24rpx;
+ color: #999999;
+}
+
+.t-input__name {
+ width: 10rpx !important;
+}
+
+.input-dialog__title {
+ color: #333;
+ font-size: 32rpx;
+ font-weight: normal;
+}
+
+.dialog--service-status {
+ background-color: #f3f4f5;
+ overflow: hidden;
+}
+.dialog--service-status .options .option {
+ color: #333333;
+ font-size: 30rpx;
+ text-align: center;
+ height: 100rpx;
+ line-height: 100rpx;
+ background-color: white;
+}
+.dialog--service-status .options .option:not(:last-child) {
+ border-bottom: 1rpx solid #e6e6e6;
+}
+.dialog--service-status .options .option--active {
+ opacity: 0.5;
+}
+.dialog--service-status .options .option.main {
+ color: #fa4126;
+}
+.dialog--service-status .cancel {
+ color: #333333;
+ font-size: 30rpx;
+ text-align: center;
+ height: 100rpx;
+ line-height: 100rpx;
+ background-color: white;
+ margin-top: 20rpx;
+}
+.dialog--service-status .cancel--active {
+ opacity: 0.5;
+}
+.amount-dialog--focus .popup__content--center,
+.remark-dialog--focus .popup__content--center {
+ top: 100rpx;
+ transform: translate(-50%, 0);
+}
+.dialog .dialog__button-confirm {
+ color: #fa4126;
+ color: var(--color-primary, #fa4126);
+}
+.select-service .bottom-bar {
+ background-color: #fff;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 158rpx;
+ z-index: 3;
+}
+.order-goods-card {
+ background: #fff;
+ margin-bottom: 24rpx;
+}
+
+.service-from-group__wrapper {
+ display: flex;
+ flex-direction: column;
+ font-family: DIN Alternate;
+ font-weight: bold;
+ font-size: 36rpx;
+ text-align: right;
+ color: #fa4126;
+}
+.service-from-group__price {
+ display: flex;
+ align-items: center;
+ color: #bbb;
+ font-size: 24rpx;
+ position: relative;
+ left: 30rpx;
+}
+.textarea--label {
+}
+.service-from-group__textarea {
+ margin-top: 20rpx;
+ background-color: #fff;
+ padding: 32rpx 32rpx 24rpx;
+}
+
+.textarea--content {
+ margin-top: 32rpx;
+ background: #f5f5f5 !important;
+ border-radius: 16rpx;
+}
+.service-from-group__textarea .t-textarea__wrapper .t-textarea__wrapper-textarea {
+ height: 136rpx;
+ box-sizing: border-box;
+}
+.service-from-group__grid {
+ padding: 0 32rpx 48rpx;
+ background: #fff;
+ margin-bottom: 148rpx;
+}
+
+.upload-addcontent-slot {
+ background-color: #f5f5f5;
+ height: inherit;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.refund-money__description {
+ font-size: 24rpx !important;
+}
+
+.upload-desc {
+ text-align: center;
+ display: flex;
+ flex-direction: column;
+ font-size: 24rpx;
+ color: #999;
+}
+
+.t-cell__left__icon {
+ position: relative;
+ top: -24rpx;
+ margin-right: 18rpx;
+}
+
+.service-choice .t-cell__title-text {
+ color: #333;
+ font-weight: bold;
+}
+
+.service-form .service-from-group .service-from-group__wrapper .refund-money-price-class {
+ font-size: 36rpx;
+ font-family: DIN Alternate;
+}
+
+.service-form .service-from-group .service-from-group__wrapper .refund-money-price-decimal {
+ font-size: 28rpx;
+ font-family: DIN Alternate;
+}
+
+.service-form .service-from-group .service-from-group__wrapper .refund-money-price-symbol {
+ font-size: 24rpx;
+ font-family: DIN Alternate;
+}
+
+.t-button {
+ --td-button-default-color: #000;
+ --td-button-primary-text-color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.js b/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.js
new file mode 100644
index 0000000..c1f813d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.js
@@ -0,0 +1,95 @@
+import Dialog from 'tdesign-miniprogram/dialog/index';
+import Toast from 'tdesign-miniprogram/toast/index';
+
+import { cancelRights } from '../../after-service-detail/api';
+import { ServiceButtonTypes } from '../../config';
+
+Component({
+ properties: {
+ service: {
+ type: Object,
+ observer(service) {
+ const buttonsRight = service.buttons || service.buttonVOs || [];
+ this.setData({
+ buttons: {
+ left: [],
+ right: buttonsRight,
+ },
+ });
+ },
+ },
+ },
+
+ data: {
+ service: {},
+ buttons: {
+ left: [],
+ right: [],
+ },
+ },
+
+ methods: {
+ // 点击【订单操作】按钮,根据按钮类型分发
+ onServiceBtnTap(e) {
+ const { type } = e.currentTarget.dataset;
+ switch (type) {
+ case ServiceButtonTypes.REVOKE:
+ this.onConfirm(this.data.service);
+ break;
+ case ServiceButtonTypes.FILL_TRACKING_NO:
+ this.onFillTrackingNo(this.data.service);
+ break;
+ case ServiceButtonTypes.CHANGE_TRACKING_NO:
+ this.onChangeTrackingNo(this.data.service);
+ break;
+ case ServiceButtonTypes.VIEW_DELIVERY:
+ this.viewDelivery(this.data.service);
+ break;
+ }
+ },
+
+ onFillTrackingNo(service) {
+ wx.navigateTo({
+ url: `/pages/order/fill-tracking-no/index?rightsNo=${service.id}`,
+ });
+ },
+
+ viewDelivery(service) {
+ wx.navigateTo({
+ url: `/pages/order/delivery-detail/index?data=${JSON.stringify(
+ service.logistics || service.logisticsVO,
+ )}&source=2`,
+ });
+ },
+
+ onChangeTrackingNo(service) {
+ wx.navigateTo({
+ url: `/pages/order/fill-tracking-no/index?rightsNo=${
+ service.id
+ }&logisticsNo=${service.logisticsNo}&logisticsCompanyName=${
+ service.logisticsCompanyName
+ }&logisticsCompanyCode=${service.logisticsCompanyCode}&remark=${
+ service.remark || ''
+ }`,
+ });
+ },
+
+ onConfirm() {
+ Dialog.confirm({
+ title: '是否撤销退货申请?',
+ content: '',
+ confirmBtn: '撤销申请',
+ cancelBtn: '不撤销',
+ }).then(() => {
+ const params = { rightsNo: this.data.service.id };
+ return cancelRights(params).then(() => {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '你确认撤销申请',
+ });
+ });
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.json b/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.json
new file mode 100644
index 0000000..75d1e24
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-button": "tdesign-miniprogram/button/button"
+ }
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.wxml b/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.wxml
new file mode 100644
index 0000000..c19c824
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.wxml
@@ -0,0 +1,33 @@
+
+
+
+ {{leftBtn.name}}
+
+
+
+
+ {{rightBtn.name}}
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.wxss b/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.wxss
new file mode 100644
index 0000000..e83597f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/after-service-button-bar/index.wxss
@@ -0,0 +1,43 @@
+:host {
+ width: 100%;
+}
+.btn-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ line-height: 1;
+}
+.btn-bar .order-btn {
+ background-color: inherit;
+ font-size: 26rpx;
+ padding: 16rpx 28rpx;
+ line-height: 1;
+ border-radius: unset;
+ min-width: 160rpx;
+ border-radius: 32rpx;
+ height: 60rpx;
+ margin-right: 10rpx;
+}
+
+.btn-bar .left .order-btn:not(:first-child),
+.btn-bar .right .order-btn:not(:first-child) {
+ margin-left: 20rpx;
+}
+.btn-bar .left .delete-btn {
+ font-size: 22rpx;
+}
+.btn-bar .left .delete-btn::after {
+ display: none;
+}
+
+.btn-bar .right .normal {
+ --td-button-default-color: #333333;
+ --td-button-default-border-color: #dddddd;
+}
+
+.btn-bar .right .primary {
+ --td-button-default-color: #fff;
+ --td-button-default-bg-color: #fa4126;
+ --td-button-default-border-color: #fa4126;
+ --td-button-default-active-bg-color: #fa42269c;
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/customer-service/index.js b/miniprogram/tcb-shop/pages/order/components/customer-service/index.js
new file mode 100644
index 0000000..419b97f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/customer-service/index.js
@@ -0,0 +1,38 @@
+Component({
+ externalClasses: ['wr-class'],
+
+ properties: {
+ phoneNumber: String,
+ desc: String,
+ },
+
+ data: {
+ show: false,
+ },
+
+ methods: {
+ onBtnTap() {
+ this.setData({
+ show: true,
+ });
+ },
+
+ onDialogClose() {
+ this.setData({
+ show: false,
+ });
+ },
+
+ onCall() {
+ const { phoneNumber } = this.properties;
+ wx.makePhoneCall({
+ phoneNumber,
+ });
+ },
+ onCallOnlineService() {
+ wx.showToast({
+ title: '你点击了在线客服',
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/components/customer-service/index.json b/miniprogram/tcb-shop/pages/order/components/customer-service/index.json
new file mode 100644
index 0000000..6e4c04e
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/customer-service/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-popup": "tdesign-miniprogram/popup/popup"
+ }
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/customer-service/index.wxml b/miniprogram/tcb-shop/pages/order/components/customer-service/index.wxml
new file mode 100644
index 0000000..f5413a4
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/customer-service/index.wxml
@@ -0,0 +1,23 @@
+
+联系客服
+
+
+
+
+ 服务时间:
+ {{desc}}
+
+
+ 呼叫 {{phoneNumber}}
+
+ 在线客服
+ 取消
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/components/customer-service/index.wxss b/miniprogram/tcb-shop/pages/order/components/customer-service/index.wxss
new file mode 100644
index 0000000..75f0363
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/customer-service/index.wxss
@@ -0,0 +1,48 @@
+.text-btn {
+ display: inline;
+ color: #333;
+ font-size: 24rpx;
+}
+.text-btn--active {
+ opacity: 0.5;
+}
+.dialog--customer-service {
+ background-color: #f3f4f5;
+ overflow: hidden;
+}
+.dialog--customer-service .content {
+ font-size: 26rpx;
+ margin: 32rpx 30rpx;
+ text-align: center;
+}
+.dialog--customer-service .content .title {
+ display: inline;
+ color: #999999;
+ font-weight: bold;
+}
+.dialog--customer-service .content .subtitle {
+ display: inline;
+ color: #999999;
+}
+.dialog--customer-service .options .option {
+ color: #333333;
+ font-size: 30rpx;
+ text-align: center;
+ height: 100rpx;
+ line-height: 100rpx;
+ background-color: white;
+}
+.dialog--customer-service .options .option:not(:last-child) {
+ margin-bottom: 20rpx;
+}
+.dialog--customer-service .options .option--active {
+ opacity: 0.5;
+}
+.dialog--customer-service .options .option.main {
+ color: #333;
+}
+.dialog--customer-service .options .option.online {
+ position: relative;
+ top: -17rpx;
+ margin-bottom: 2rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/goods-card/index.js b/miniprogram/tcb-shop/pages/order/components/goods-card/index.js
new file mode 100644
index 0000000..293f3f0
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/goods-card/index.js
@@ -0,0 +1,251 @@
+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',
+ ],
+
+ relations: {
+ '../order-card/index': {
+ type: 'ancestor',
+ linked(target) {
+ this.parent = target;
+ },
+ },
+ },
+
+ 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) {
+ return;
+ }
+
+ // 敲定换行数量默认值
+ if (goods.lineClamp === undefined || goods.lineClamp <= 0) {
+ // tag数组长度 大于0 且 可见
+ // 指定换行为1行
+ if ((goods.tags?.length || 0) > 0 && !goods.hideKey?.tags) {
+ goods.lineClamp = 1;
+ } else {
+ goods.lineClamp = 2;
+ }
+ }
+ goods.specs = goods.sku.attr_value.map((v) => v.value).join(',');
+
+ this.setData({ goods });
+ },
+ },
+ layout: {
+ type: String,
+ value: 'horizontal',
+ },
+ thumbMode: {
+ type: String,
+ value: 'aspectFill',
+ },
+ thumbWidth: Number,
+ thumbHeight: Number,
+ priceFill: {
+ type: Boolean,
+ value: true,
+ },
+ currency: {
+ type: String,
+ value: '¥',
+ },
+ lazyLoad: {
+ type: Boolean,
+ value: false,
+ },
+ centered: {
+ type: Boolean,
+ value: false,
+ },
+ showCart: {
+ type: Boolean,
+ value: false,
+ },
+ pricePrefix: {
+ type: String,
+ value: '',
+ },
+ cartSize: {
+ type: Number,
+ value: 48,
+ },
+ cartColor: {
+ type: String,
+ value: '#FA550F',
+ },
+ /** 元素可见监控阈值, 数组长度大于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 });
+ },
+ clickTagHandle(evt) {
+ const { index } = evt.currentTarget.dataset;
+ this.triggerEvent('tag', { goods: this.data.goods, index });
+ },
+ // 加入购物车
+ 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 {
+ // `goods-card-88888888`
+ 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;
+ }
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/components/goods-card/index.json b/miniprogram/tcb-shop/pages/order/components/goods-card/index.json
new file mode 100644
index 0000000..0dbb3d8
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/goods-card/index.json
@@ -0,0 +1,8 @@
+{
+ "component": true,
+ "usingComponents": {
+ "price": "/components/price/index",
+ "t-image": "/components/webp-image/index",
+ "t-icon": "tdesign-miniprogram/icon/icon"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/components/goods-card/index.wxml b/miniprogram/tcb-shop/pages/order/components/goods-card/index.wxml
new file mode 100644
index 0000000..fab9cce
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/goods-card/index.wxml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{ goods.sku.spu.name }}
+
+
+
+
+ {{ goods.specs }}
+
+
+
+
+
+ {{ pricePrefix }}
+
+
+
+
+
+
+ x
+ {{ goods.count }}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/components/goods-card/index.wxss b/miniprogram/tcb-shop/pages/order/components/goods-card/index.wxss
new file mode 100644
index 0000000..7bcb096
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/goods-card/index.wxss
@@ -0,0 +1,254 @@
+.wr-goods-card {
+ box-sizing: border-box;
+ font-size: 24rpx;
+}
+.wr-goods-card__main {
+ position: relative;
+ display: flex;
+ line-height: 1;
+ flex-direction: row;
+ background: transparent;
+ padding: 16rpx 0rpx;
+}
+.wr-goods-card.center .wr-goods-card__main {
+ align-items: center;
+ justify-content: center;
+}
+.wr-goods-card__thumb {
+ flex-shrink: 0;
+ position: relative;
+ width: 176rpx;
+ height: 176rpx;
+}
+.wr-goods-card__thumb-com {
+ width: 176rpx;
+ height: 176rpx;
+ border-radius: 8rpx;
+ overflow: hidden;
+}
+.wr-goods-card__thumb:empty {
+ display: none;
+ margin: 0;
+}
+
+.wr-goods-card__body {
+ display: flex;
+ margin: 0 0 0 16rpx;
+ flex-direction: row;
+ flex: 1 1 auto;
+ min-height: 176rpx;
+}
+.wr-goods-card__long_content {
+ display: flex;
+ flex-direction: column;
+ overflow: hidden;
+ flex: 1 1 auto;
+}
+.wr-goods-card__long_content .goods_tips {
+ width: 100%;
+ margin-top: 16rpx;
+ text-align: right;
+ color: #fa4126;
+ font-size: 24rpx;
+ line-height: 32rpx;
+ font-weight: bold;
+}
+.wr-goods-card__title {
+ flex-shrink: 0;
+ font-size: 28rpx;
+ color: #333;
+ line-height: 40rpx;
+ font-weight: 400;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ word-break: break-word;
+}
+.wr-goods-card__title__prefix-tags {
+ display: inline-flex;
+}
+.wr-goods-card__title__prefix-tags .prefix-tag {
+ margin: 0 8rpx 0 0;
+}
+.wr-goods-card__desc {
+ font-size: 24rpx;
+ color: #f5f5f5;
+ line-height: 40rpx;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+}
+.wr-goods-card__specs__desc,
+.wr-goods-card__specs__text {
+ font-size: 24rpx;
+ height: 32rpx;
+ line-height: 32rpx;
+ color: #999999;
+ margin: 8rpx 0;
+}
+.wr-goods-card__specs__desc {
+ display: flex;
+ align-self: flex-start;
+ flex-direction: row;
+}
+.wr-goods-card__specs__desc-text {
+ height: 100%;
+ max-width: 380rpx;
+ word-break: break-all;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 1;
+}
+.wr-goods-card__specs__desc-icon {
+ line-height: inherit;
+ margin-left: 8rpx;
+ font-size: 24rpx;
+ color: #bbb;
+}
+.wr-goods-card__specs__text {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: break-all;
+}
+.wr-goods-card__tags {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ margin: 16rpx 0 0 0;
+}
+.wr-goods-card__tag {
+ color: #fa550f;
+ background: transparent;
+ font-size: 20rpx;
+ border: 1rpx solid #fa550f;
+ padding: 0 8rpx;
+ height: 30rpx;
+ line-height: 30rpx;
+ margin: 0 8rpx 8rpx 0;
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ word-break: keep-all;
+ text-overflow: ellipsis;
+}
+.wr-goods-card__short_content {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-end;
+ margin: 0 0 0 46rpx;
+}
+.wr-goods-card__price__prefix {
+ order: 0;
+ color: #666;
+ margin: 0;
+}
+.wr-goods-card__price {
+ white-space: nowrap;
+ font-weight: bold;
+ order: 1;
+ color: #fa4126;
+ font-size: 36rpx;
+ margin: 0;
+ line-height: 48rpx;
+}
+.wr-goods-card__origin-price {
+ white-space: nowrap;
+ font-weight: normal;
+ order: 2;
+ color: #aaaaaa;
+ font-size: 24rpx;
+ margin: 0;
+}
+.wr-goods-card__num {
+ white-space: nowrap;
+ order: 4;
+ font-size: 24rpx;
+ color: #999;
+ margin: 20rpx 0 0 auto;
+}
+.wr-goods-card__num__prefix {
+ color: inherit;
+}
+.wr-goods-card__add-cart {
+ order: 3;
+ margin: auto 0 0 auto;
+}
+.wr-goods-card.horizontal-wrap .wr-goods-card__thumb {
+ width: 192rpx;
+ height: 192rpx;
+ border-radius: 8rpx;
+ overflow: hidden;
+}
+.wr-goods-card.horizontal-wrap .wr-goods-card__body {
+ flex-direction: column;
+}
+.wr-goods-card.horizontal-wrap .wr-goods-card__short_content {
+ flex-direction: row;
+ align-items: center;
+ margin: 16rpx 0 0 0;
+}
+
+.wr-goods-card.horizontal-wrap .wr-goods-card__num {
+ margin: 0 0 0 auto;
+}
+.wr-goods-card.vertical .wr-goods-card__main {
+ padding: 0 0 22rpx 0;
+ flex-direction: column;
+}
+.wr-goods-card.vertical .wr-goods-card__thumb {
+ width: 340rpx;
+ height: 340rpx;
+}
+.wr-goods-card.vertical .wr-goods-card__body {
+ margin: 20rpx 20rpx 0 20rpx;
+ flex-direction: column;
+}
+.wr-goods-card.vertical .wr-goods-card__long_content {
+ overflow: hidden;
+}
+.wr-goods-card.vertical .wr-goods-card__title {
+ line-height: 36rpx;
+}
+.wr-goods-card.vertical .wr-goods-card__short_content {
+ margin: 20rpx 0 0 0;
+}
+.wr-goods-card.vertical .wr-goods-card__price {
+ order: 2;
+ color: #fa4126;
+ margin: 20rpx 0 0 0;
+}
+.wr-goods-card.vertical .wr-goods-card__origin-price {
+ order: 1;
+}
+.wr-goods-card.vertical .wr-goods-card__add-cart {
+ position: absolute;
+ bottom: 20rpx;
+ right: 20rpx;
+}
+
+.wr-goods-card__short_content .no_storage {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ height: 40rpx;
+ color: #333;
+ font-size: 24rpx;
+ line-height: 32rpx;
+ width: 100%;
+}
+
+.no_storage .no_storage__right {
+ width: 80rpx;
+ height: 40rpx;
+ border-radius: 20rpx;
+ border: 2rpx solid #fa4126;
+ line-height: 40rpx;
+ text-align: center;
+ color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/noGoods/noGood.wxs b/miniprogram/tcb-shop/pages/order/components/noGoods/noGood.wxs
new file mode 100644
index 0000000..f9b13d0
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/noGoods/noGood.wxs
@@ -0,0 +1,17 @@
+var isOnlyBack = function (data) {
+ return data.limitGoodsList || (data.inValidGoodsList && !data.storeGoodsList);
+};
+
+var isShowChangeAddress = function (data) {
+ return data.abnormalDeliveryGoodsList;
+};
+
+var isShowKeepPay = function (data) {
+ return data.outOfStockGoodsList || (data.storeGoodsList && data.inValidGoodsList);
+};
+
+module.exports = {
+ isOnlyBack: isOnlyBack,
+ isShowChangeAddress: isShowChangeAddress,
+ isShowKeepPay: isShowKeepPay,
+};
diff --git a/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.js b/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.js
new file mode 100644
index 0000000..53b6f86
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.js
@@ -0,0 +1,57 @@
+Component({
+ properties: {
+ settleDetailData: {
+ type: Object,
+ value: {},
+ observer(settleDetailData) {
+ const {
+ outOfStockGoodsList,
+ abnormalDeliveryGoodsList,
+ inValidGoodsList,
+ limitGoodsList,
+ } = settleDetailData;
+ // 弹窗逻辑 限购 超出配送范围 失效 库存不足;
+ const tempList =
+ limitGoodsList ||
+ abnormalDeliveryGoodsList ||
+ inValidGoodsList ||
+ outOfStockGoodsList ||
+ [];
+
+ tempList.forEach((goods, index) => {
+ goods.id = index;
+ goods.unSettlementGoods &&
+ goods.unSettlementGoods.forEach((ele) => {
+ ele.name = ele.goodsName;
+ ele.price = ele.payPrice;
+ ele.imgUrl = ele.image;
+ });
+ });
+ this.setData({
+ // settleDetailData,
+ goodsList: tempList,
+ });
+ },
+ },
+ },
+
+ data: {
+ goodList: [],
+ },
+ methods: {
+ onCard(e) {
+ const { item } = e.currentTarget.dataset;
+ if (item === 'cart') {
+ // 购物车
+ Navigator.gotoPage('/cart');
+ } else if (item === 'orderSure') {
+ // 结算页
+ this.triggerEvent('change', undefined);
+ }
+ },
+ onDelive() {
+ // 修改配送地址
+ Navigator.gotoPage('/address', { type: 'orderSure' });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.json b/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.json
new file mode 100644
index 0000000..31f62da
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.json
@@ -0,0 +1,8 @@
+{
+ "component": true,
+ "usingComponents": {
+ "wr-order-card": "/pages/order/components/order-card/index",
+ "wr-goods-card": "/components/goods-card/index",
+ "wr-order-goods-card": "/pages/order/components/order-goods-card/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.wxml b/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.wxml
new file mode 100644
index 0000000..04cc10a
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.wxml
@@ -0,0 +1,53 @@
+
+
+
+
+ 限购商品信息
+ 以下商品限购数量,建议您修改商品数量
+
+
+ 不支持配送
+ 以下店铺的商品不支持配送,请更改地址或去掉对应店铺商品再进行结算
+
+
+ 部分商品库存不足或失效
+ 请返回购物车重新选择商品,如果继续结算将自动忽略库存不足或失效的商品。
+
+
+ 全部商品库存不足或失效
+ 请返回购物车重新选择商品
+
+
+
+
+
+
+
+
+
+
+ 返回购物车
+
+
+ 修改配送地址
+
+
+ 继续结算
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.wxss b/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.wxss
new file mode 100644
index 0000000..0331d40
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/noGoods/noGoods.wxss
@@ -0,0 +1,68 @@
+/* 层级定义
+@z-index-0: 1;
+@z-index-1: 100;
+@z-index-2: 200;
+@z-index-5: 500;
+@z-index-component: 1000; // 通用组件级别
+@z-index-dropdown: @z-index-component;
+@z-index-sticky: @z-index-component + 20;
+@z-index-fixed: @z-index-component + 30;
+@z-index-modal-backdrop:@z-index-component + 40;
+@z-index-modal:@z-index-component + 50;
+@z-index-popover:@z-index-component + 60;
+@z-index-tooltip:@z-index-component + 70;
+*/
+/* var() css变量适配*/
+.goods-fail {
+ display: block;
+ background: #fff;
+ font-size: 30rpx;
+ border-radius: 20rpx 20rpx 0 0;
+}
+.goods-fail .title {
+ display: inline-block;
+ width: 100%;
+ text-align: center;
+ margin-top: 30rpx;
+ line-height: 42rpx;
+ font-weight: bold;
+ font-size: 32rpx;
+}
+.goods-fail .info {
+ display: block;
+ font-size: 26rpx;
+ font-weight: 400;
+ line-height: 36rpx;
+ margin: 20rpx auto 10rpx;
+ text-align: center;
+ width: 560rpx;
+ color: #999;
+}
+.goods-fail .goods-fail-btn {
+ display: flex;
+ padding: 30rpx;
+ justify-content: space-between;
+ align-items: center;
+ font-size: 30rpx;
+}
+.goods-fail .goods-fail-btn .btn {
+ width: 330rpx;
+ height: 80rpx;
+ line-height: 80rpx;
+ border-radius: 8rpx;
+ text-align: center;
+ border: 1rpx solid #999;
+ background: #fff;
+ font-size: 32rpx;
+ color: #666;
+}
+.goods-fail .goods-fail-btn .btn.origin,
+.goods-fail .goods-fail-btn .btn.limit {
+ color: #fa550f;
+ color: var(--color-primary, #fa550f);
+ border: 1rpx solid #fa550f;
+ border: 1rpx solid var(--color-primary, #fa550f);
+}
+.goods-fail .goods-fail-btn .btn.limit {
+ flex-grow: 1;
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.js b/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.js
new file mode 100644
index 0000000..0ea93cd
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.js
@@ -0,0 +1,274 @@
+import Dialog from 'tdesign-miniprogram/dialog/index';
+import { ORDER_STATUS, updateOrderStatus } from '../../../../services/order/order';
+import { pay, refund } from '../../../../services/pay/pay';
+import { OrderButtonTypes } from '../../config';
+import { objectToParamString } from '../../../../utils/util';
+import { OPERATION_TYPE } from '../../../../utils/orderOperation';
+
+const OPERATION_DONE_EVENT = 'operation';
+
+Component({
+ options: {
+ addGlobalClass: true,
+ },
+ properties: {
+ order: {
+ type: Object,
+ observer(order) {
+ this.init(order);
+ },
+ },
+ goodsIndex: {
+ type: Number,
+ value: null,
+ },
+ isBtnMax: {
+ type: Boolean,
+ value: false,
+ },
+ },
+
+ data: {
+ order: {},
+ buttons: {
+ left: [],
+ right: [],
+ },
+ },
+
+ methods: {
+ // 点击【订单操作】按钮,根据按钮类型分发
+ onOrderBtnTap(e) {
+ const { type } = e.currentTarget.dataset;
+ switch (type) {
+ case OrderButtonTypes.CANCEL:
+ this.onCancel(this.data.order);
+ break;
+ case OrderButtonTypes.CONFIRM:
+ this.onConfirm(this.data.order);
+ break;
+ case OrderButtonTypes.PAY:
+ this.onPay(this.data.order);
+ break;
+ case OrderButtonTypes.APPLY_REFUND:
+ this.onApplyRefund(this.data.order);
+ break;
+ case OrderButtonTypes.COMMENT:
+ this.onAddComment(this.data.order);
+ break;
+ }
+ },
+
+ checkOrder(order, operationType) {
+ if (order != null) {
+ return true;
+ }
+ this.triggerEvent(OPERATION_DONE_EVENT, {
+ type: operationType,
+ message: 'no order',
+ success: false,
+ });
+ return false;
+ },
+
+ async onCancel(order) {
+ if (!this.checkOrder(order, OPERATION_TYPE.CANCEL)) return;
+
+ // if order is paid, we should first refund
+ if (order.status !== ORDER_STATUS.TO_PAY) {
+ try {
+ await refund(order._id);
+ } catch (e) {
+ this.triggerEvent(OPERATION_DONE_EVENT, {
+ type: OPERATION_TYPE.CANCEL,
+ message: 'refund failed',
+ success: false,
+ detail: e,
+ });
+ return;
+ }
+ }
+
+ try {
+ await updateOrderStatus({ orderId: order._id, status: ORDER_STATUS.CANCELED });
+ } catch (e) {
+ this.triggerEvent(OPERATION_DONE_EVENT, {
+ type: OPERATION_TYPE.CANCEL,
+ message: 'update order status failed',
+ success: false,
+ detail: e,
+ });
+ return;
+ }
+
+ this.triggerEvent(OPERATION_DONE_EVENT, {
+ type: OPERATION_TYPE.CANCEL,
+ success: true,
+ });
+ },
+
+ async onConfirm(order) {
+ if (!this.checkOrder(order, OPERATION_TYPE.CONFIRM)) return;
+
+ try {
+ await Dialog.confirm({
+ title: '确认是否已经收到货?',
+ content: '',
+ confirmBtn: '确认收货',
+ cancelBtn: '取消',
+ });
+ } catch (e) {
+ this.triggerEvent(OPERATION_DONE_EVENT, {
+ type: OPERATION_TYPE.CONFIRM,
+ message: 'confirm dialog failed',
+ success: false,
+ detail: e,
+ });
+ return;
+ }
+
+ try {
+ await updateOrderStatus({ orderId: order._id, status: ORDER_STATUS.FINISHED });
+ } catch (e) {
+ this.triggerEvent(OPERATION_DONE_EVENT, {
+ type: OPERATION_TYPE.CONFIRM,
+ message: 'update order status failed',
+ success: false,
+ detail: e,
+ });
+ }
+
+ this.triggerEvent(OPERATION_DONE_EVENT, {
+ type: OPERATION_TYPE.CONFIRM,
+ success: true,
+ });
+ },
+
+ async onPay(order) {
+ if (!this.checkOrder(order, OPERATION_TYPE.PAY)) return;
+
+ try {
+ await pay({ id: order._id, totalPrice: order.totalPrice });
+ } catch (e) {
+ this.triggerEvent(OPERATION_DONE_EVENT, {
+ type: OPERATION_TYPE.PAY,
+ message: 'pay failed',
+ success: false,
+ detail: e,
+ });
+ return;
+ }
+
+ this.triggerEvent(OPERATION_DONE_EVENT, {
+ type: OPERATION_TYPE.PAY,
+ success: true,
+ });
+ },
+
+ onApplyRefund(order) {
+ wx.navigateTo({ url: `/pages/order/apply-service/index?${objectToParamString({ orderId: order._id })}` });
+ },
+
+ /** 添加订单评论 */
+ onAddComment(order) {
+ wx.navigateTo({
+ url: `/pages/goods/comments/create-list/index?${objectToParamString({ orderId: order._id })}`,
+ });
+ },
+
+ init(order) {
+ if (order == null) return;
+
+ if (order.status === ORDER_STATUS.TO_PAY) {
+ this.setData({
+ buttons: {
+ left: [],
+ right: [
+ { type: OrderButtonTypes.CANCEL, name: '取消订单' },
+ { type: OrderButtonTypes.PAY, name: '付款', primary: true },
+ ],
+ },
+ });
+ return;
+ }
+ if (order.status === ORDER_STATUS.TO_SEND) {
+ this.setData({
+ buttons: {
+ left: [],
+ right: [{ type: OrderButtonTypes.CANCEL, name: '取消订单' }],
+ },
+ });
+ return;
+ }
+ if (order.status === ORDER_STATUS.TO_RECEIVE) {
+ this.setData({
+ buttons: {
+ left: [],
+ right: [{ type: OrderButtonTypes.CONFIRM, name: '确认收货', primary: true }],
+ },
+ });
+ return;
+ }
+ if (order.status === ORDER_STATUS.FINISHED) {
+ this.setData({
+ buttons: {
+ left: [],
+ right: [{ type: OrderButtonTypes.COMMENT, name: '评价', primary: true }],
+ },
+ });
+ return;
+ }
+ if (order.status === ORDER_STATUS.CANCELED) {
+ this.setData({
+ buttons: {
+ left: [],
+ right: [],
+ },
+ });
+ return;
+ }
+ if (order.status === ORDER_STATUS.RETURN_APPLIED) {
+ this.setData({
+ buttons: {
+ left: [],
+ right: [],
+ },
+ });
+ return;
+ }
+ if (order.status === ORDER_STATUS.RETURN_REFUSED) {
+ this.setData({
+ buttons: {
+ left: [],
+ right: [],
+ },
+ });
+ return;
+ }
+ if (order.status === ORDER_STATUS.RETURN_FINISH) {
+ this.setData({
+ buttons: {
+ left: [],
+ right: [],
+ },
+ });
+ return;
+ }
+ if (order.status === ORDER_STATUS.RETURN_MONEY_REFUSED) {
+ this.setData({
+ buttons: {
+ left: [],
+ right: [],
+ },
+ });
+ return;
+ }
+ },
+ },
+
+ lifetimes: {
+ attached() {
+ this.init(this.data.order);
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.json b/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.json
new file mode 100644
index 0000000..3084bb3
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.json
@@ -0,0 +1,8 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.wxml b/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.wxml
new file mode 100644
index 0000000..492b87a
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.wxml
@@ -0,0 +1,37 @@
+
+
+
+ {{leftBtn.name}}
+
+
+
+
+ {{rightBtn.name}}
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.wxss b/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.wxss
new file mode 100644
index 0000000..73385a2
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/order-button-bar/index.wxss
@@ -0,0 +1,54 @@
+:host {
+ width: 100%;
+}
+.btn-bar {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ line-height: 1;
+}
+.btn-bar .order-btn {
+ line-height: 1;
+ /* border-radius: unset; */
+ /* min-width: 160rpx; */
+}
+
+.btn-bar .right {
+ display: flex;
+ align-items: center;
+}
+.btn-bar .t-button {
+ width: 160rpx;
+ font-weight: 400;
+ margin-left: 24rpx;
+}
+.btn-bar .t-button--max {
+ width: 176rpx;
+ margin-left: 24rpx;
+
+ --td-button-extra-small-height: 72rpx;
+}
+
+.btn-bar .left .delete-btn {
+ font-size: 22rpx;
+}
+.btn-bar .left .delete-btn::after {
+ display: none;
+}
+
+.btn-bar .right .normal {
+ --td-button-default-color: #333333;
+ --td-button-default-border-color: #dddddd;
+}
+
+.btn-bar .right .primary {
+ --td-button-default-color: #fff;
+ --td-button-default-bg-color: #fa4126;
+ --td-button-default-border-color: #fa4126;
+ --td-button-default-active-bg-color: #fa42269c;
+}
+
+.t-button {
+ --td-button-default-color: #000;
+ --td-button-primary-text-color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/order-card/index.js b/miniprogram/tcb-shop/pages/order/components/order-card/index.js
new file mode 100644
index 0000000..9b615d2
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/order-card/index.js
@@ -0,0 +1,88 @@
+import { orderStatusToName } from '../../../../services/order/order';
+
+Component({
+ externalClasses: ['wr-class', 'header-class', 'title-class'],
+
+ options: {
+ multipleSlots: true,
+ },
+
+ relations: {
+ '../order-goods-card/index': {
+ type: 'descendant',
+ linked(target) {
+ this.children.push(target);
+ this.setHidden();
+ },
+ unlinked(target) {
+ this.children = this.children.filter((item) => item !== target);
+ },
+ },
+ '../goods-card/index': {
+ type: 'descendant',
+ linked(target) {
+ this.children.push(target);
+ this.setHidden();
+ },
+ unlinked(target) {
+ this.children = this.children.filter((item) => item !== target);
+ },
+ },
+ '../specs-goods-card/index': {
+ type: 'descendant',
+ linked(target) {
+ this.children.push(target);
+ this.setHidden();
+ },
+ unlinked(target) {
+ this.children = this.children.filter((item) => item !== target);
+ },
+ },
+ },
+
+ created() {
+ this.children = [];
+ },
+
+ properties: {
+ order: {
+ type: Object,
+ observer(order) {
+ const goodsCount = order?.orderItems?.length;
+ if (typeof goodsCount !== 'number') return;
+
+ this.setData({
+ goodsCount,
+ });
+ },
+ },
+ useTopLeftSlot: Boolean,
+ useTopRightSlot: Boolean,
+ // 初始显示的商品数量,超出部分会隐藏。
+ defaultShowNum: {
+ type: null,
+ value: 10,
+ },
+ },
+
+ data: {
+ showAll: true, // 是否展示所有商品,设置为false,可以使用展开更多功能
+ goodsCount: 0,
+ },
+
+ methods: {
+ setHidden() {
+ const isHidden = !this.data.showAll;
+ this.children.forEach((c, i) => i >= this.properties.defaultShowNum && c.setHidden(isHidden));
+ },
+
+ onOrderCardTap() {
+ this.triggerEvent('cardtap');
+ },
+
+ onShowMoreTap() {
+ this.setData({ showAll: true }, () => this.setHidden());
+ this.triggerEvent('showall');
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/components/order-card/index.json b/miniprogram/tcb-shop/pages/order/components/order-card/index.json
new file mode 100644
index 0000000..8c3cde6
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/order-card/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-image": "/components/webp-image/index",
+ "t-icon": "tdesign-miniprogram/icon/icon"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/components/order-card/index.wxml b/miniprogram/tcb-shop/pages/order/components/order-card/index.wxml
new file mode 100644
index 0000000..83258ee
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/order-card/index.wxml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+ 展开商品信息(共 {{goodsCount}} 个)
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/components/order-card/index.wxss b/miniprogram/tcb-shop/pages/order/components/order-card/index.wxss
new file mode 100644
index 0000000..d241796
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/order-card/index.wxss
@@ -0,0 +1,45 @@
+.order-card {
+ margin: 24rpx 0;
+ padding: 24rpx 32rpx 24rpx;
+ background-color: white;
+ border-radius: 8rpx;
+}
+.order-card .header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 24rpx;
+}
+.order-card .header .store-name {
+ font-size: 28rpx;
+ font-weight: normal;
+ color: #333333;
+ display: flex;
+ align-items: center;
+ line-height: 40rpx;
+}
+.order-card .header .store-name__logo {
+ margin-right: 16rpx;
+ font-size: 40rpx;
+ width: 48rpx;
+ height: 48rpx;
+}
+.order-card .header .store-name__label {
+ max-width: 500rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: break-all;
+ white-space: nowrap;
+}
+.order-card .header .order-status {
+ font-size: 26rpx;
+ line-height: 40rpx;
+ color: #fa4126;
+}
+.order-card .more-mask {
+ padding: 20rpx 0;
+ text-align: center;
+ background-color: white;
+ color: #fa4126;
+ font-size: 24rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/order-goods-card/index.js b/miniprogram/tcb-shop/pages/order/components/order-goods-card/index.js
new file mode 100644
index 0000000..edaf299
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/order-goods-card/index.js
@@ -0,0 +1,43 @@
+Component({
+ options: {
+ addGlobalClass: true,
+ multipleSlots: true, // 在组件定义时的选项中启用多slot支持
+ },
+
+ relations: {
+ '../order-card/index': {
+ type: 'ancestor',
+ linked(target) {
+ this.parent = target;
+ },
+ },
+ },
+
+ properties: {
+ goods: Object,
+ thumbWidth: Number,
+ thumbHeight: Number,
+ thumbWidthInPopup: Number,
+ thumbHeightInPopup: Number,
+ noTopLine: Boolean,
+ step: Boolean,
+ stepDisabled: Boolean,
+ },
+
+ data: {
+ goods: {},
+ hidden: false,
+ },
+
+ methods: {
+ setHidden(hidden) {
+ if (this.data.hidden === hidden) return;
+ this.setData({ hidden });
+ },
+
+ onNumChange(e) {
+ const { value } = e.detail;
+ this.triggerEvent('num-change', { value });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/components/order-goods-card/index.json b/miniprogram/tcb-shop/pages/order/components/order-goods-card/index.json
new file mode 100644
index 0000000..7743bba
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/order-goods-card/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-stepper": "tdesign-miniprogram/stepper/stepper",
+ "goods-card": "../specs-goods-card/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/components/order-goods-card/index.wxml b/miniprogram/tcb-shop/pages/order/components/order-goods-card/index.wxml
new file mode 100644
index 0000000..59778e8
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/order-goods-card/index.wxml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.js b/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.js
new file mode 100644
index 0000000..e5741e6
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.js
@@ -0,0 +1,114 @@
+Component({
+ properties: {
+ show: Boolean,
+ title: String,
+ options: {
+ type: Object,
+ observer() {
+ this.init();
+ },
+ },
+ multiple: {
+ type: Boolean,
+ observer() {
+ this.init();
+ },
+ },
+ showConfirmButton: Boolean,
+ showCloseButton: Boolean,
+ confirmButtonText: {
+ type: String,
+ value: '确定',
+ },
+ cancelButtonText: {
+ type: String,
+ value: '取消',
+ },
+ emptyTip: {
+ type: String,
+ value: '请选择',
+ },
+ },
+
+ data: {
+ _options: [],
+ checkedIndexes: [],
+ },
+
+ methods: {
+ attached() {
+ this.toast = this.selectComponent('#t-toast');
+ },
+
+ init() {
+ const checkedIndexes = [];
+ const _options = this.properties.options.map((opt, i) => {
+ const checked = !!opt.checked;
+ if (checked) {
+ if (this.properties.multiple) checkedIndexes[0] = i;
+ else checkedIndexes.push(i);
+ }
+ return {
+ title: opt.title,
+ checked,
+ };
+ });
+ this.setData({ checkedIndexes, _options });
+ },
+
+ onOptionTap(e) {
+ const { index } = e.currentTarget.dataset;
+ const { checkedIndexes } = this.data;
+ let data = {};
+ if (this.properties.multiple) {
+ if (checkedIndexes.includes(index)) {
+ checkedIndexes.splice(index, 1);
+ data = { checkedIndexes, [`_options[${index}].checked`]: false };
+ } else {
+ checkedIndexes.push(index);
+ data = { checkedIndexes, [`_options[${index}].checked`]: true };
+ }
+ } else {
+ if (checkedIndexes[0] === index) {
+ // 单选不可取消选择
+ return;
+ }
+ data = {
+ [`_options[${index}].checked`]: true,
+ checkedIndexes: [index],
+ };
+ if (checkedIndexes[0] !== undefined) {
+ data[`_options[${checkedIndexes[0]}].checked`] = false;
+ }
+ }
+ this.setData(data);
+ this.triggerEvent('select', { index });
+ this._onOptionTap && this._onOptionTap(index);
+ if (!this.properties.showConfirmButton && !this.properties.multiple) {
+ // 没有确认按钮且是单选的情况下,选择选项则自动确定
+ this._onConfirm && this._onConfirm([index]);
+ this.setData({ show: false });
+ }
+ },
+
+ onCancel() {
+ this.triggerEvent('cancel');
+ this._onCancel && this._onCancel();
+ this.setData({ show: false });
+ },
+
+ onConfirm() {
+ if (this.data.checkedIndexes.length === 0) {
+ this.toast.show({
+ icon: '',
+ text: this.properties.emptyTip,
+ });
+ return;
+ }
+ const indexed = this.data.checkedIndexes;
+ this.triggerEvent('confirm', { indexed });
+ this._onConfirm && this._onConfirm(indexed);
+ this.setData({ show: false });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.json b/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.json
new file mode 100644
index 0000000..a22be6b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.json
@@ -0,0 +1,10 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-popup": "tdesign-miniprogram/popup/popup",
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-button": "tdesign-miniprogram/button/button"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.wxml b/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.wxml
new file mode 100644
index 0000000..35a5df2
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.wxml
@@ -0,0 +1,50 @@
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.wxss b/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.wxss
new file mode 100644
index 0000000..ed92d44
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/reason-sheet/index.wxss
@@ -0,0 +1,47 @@
+page view {
+ box-sizing: border-box;
+}
+.popup-content {
+ background-color: white;
+ color: #222427;
+ border-radius: 20rpx 20rpx 0 0;
+ overflow: hidden;
+}
+.popup-content .header {
+ height: 100rpx;
+ line-height: 100rpx;
+ text-align: center;
+ vertical-align: middle;
+ font-size: 32rpx;
+ font-weight: bold;
+ position: relative;
+}
+.popup-content .options {
+ max-height: 60vh;
+ overflow-y: scroll;
+ -webkit-overflow-scrolling: touch;
+}
+.popup-content .options .cell {
+ height: 100rpx;
+ align-items: center;
+ font-size: 30rpx;
+ color: #333333;
+}
+.popup-content .button-bar {
+ width: 100%;
+ padding: 20rpx 30rpx;
+ display: flex;
+ flex-wrap: nowrap;
+ align-items: center;
+ justify-content: space-between;
+ padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+}
+.popup-content .button-bar .btn {
+ width: 100%;
+ background: #fa4126;
+ color: #fff;
+ border-radius: 48rpx;
+}
+.button-bar .btnWrapper {
+ width: 100%;
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/reason-sheet/reasonSheet.js b/miniprogram/tcb-shop/pages/order/components/reason-sheet/reasonSheet.js
new file mode 100644
index 0000000..384f68f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/reason-sheet/reasonSheet.js
@@ -0,0 +1,25 @@
+function getInstance(context, selector = '#wr-reason-sheet') {
+ if (!context) {
+ const pages = getCurrentPages();
+ const page = pages[pages.length - 1];
+ context = page;
+ }
+ const instance = context && context.selectComponent(selector);
+ if (!instance) {
+ console.warn(`未找到reason-sheet组件,请检查selector是否正确`);
+ return null;
+ }
+ return instance;
+}
+
+export default function (options) {
+ const { context, selector, ..._options } = options;
+ return new Promise((resolve, reject) => {
+ const instance = getInstance(context, selector);
+ if (instance) {
+ instance.setData(Object.assign({}, _options));
+ instance._onCancel = () => reject();
+ instance._onConfirm = (indexes) => resolve(indexes);
+ }
+ });
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/selectCoupons/mock.js b/miniprogram/tcb-shop/pages/order/components/selectCoupons/mock.js
new file mode 100644
index 0000000..6e67895
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/selectCoupons/mock.js
@@ -0,0 +1,22 @@
+export const couponsData = {
+ couponResultList: [
+ {
+ couponVO: {
+ condition: '满200元可用',
+ couponId: 11,
+ endTime: 1584530282686,
+ name: '折扣券',
+ profit: '5.5折',
+ promotionCode: 90,
+ promotionSubCode: 1,
+ scopeText: '部分商品可用',
+ startTime: 1584530282686,
+ storeId: 90,
+ value: 550,
+ type: 2,
+ },
+ status: 0, // 0:未勾选。1:勾选。-1:置灰
+ },
+ ],
+ reduce: 1000,
+};
diff --git a/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupon.wxs b/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupon.wxs
new file mode 100644
index 0000000..8c4ce2e
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupon.wxs
@@ -0,0 +1,16 @@
+function formatDays(value) {
+ if (value < 10) {
+ return '0' + value;
+ }
+ return value;
+}
+var dateFormat = function (d) {
+ var date = getDate(+d);
+ return (
+ date.getFullYear() +
+ '-' +
+ formatDays(date.getMonth() + 1) +
+ formatDays(date.getDate())
+ );
+};
+module.exports.dateFormat = dateFormat;
diff --git a/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.js b/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.js
new file mode 100644
index 0000000..235ffba
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.js
@@ -0,0 +1,160 @@
+import dayjs from 'dayjs';
+import { couponsData } from './mock';
+
+const emptyCouponImg = `https://cdn-we-retail.ym.tencent.com/miniapp/coupon/ordersure-coupon-newempty.png`;
+
+Component({
+ properties: {
+ storeId: String,
+ promotionGoodsList: {
+ type: Array,
+ value: [],
+ },
+ orderSureCouponList: {
+ type: Array,
+ value: [],
+ },
+ couponsShow: {
+ type: Boolean,
+ value: false,
+ observer(couponsShow) {
+ if (couponsShow) {
+ const { promotionGoodsList, orderSureCouponList, storeId } =
+ this.data;
+ const products =
+ promotionGoodsList &&
+ promotionGoodsList.map((goods) => {
+ this.storeId = goods.storeId;
+ return {
+ skuId: goods.skuId,
+ spuId: goods.spuId,
+ storeId: goods.storeId,
+ selected: true,
+ quantity: goods.num,
+ prices: {
+ sale: goods.settlePrice,
+ },
+ };
+ });
+ const selectedCoupons =
+ orderSureCouponList &&
+ orderSureCouponList.map((ele) => {
+ return {
+ promotionId: ele.promotionId,
+ storeId: ele.storeId,
+ couponId: ele.couponId,
+ };
+ });
+ this.setData({
+ products,
+ });
+ this.coupons({
+ products,
+ selectedCoupons,
+ storeId,
+ }).then((res) => {
+ this.initData(res);
+ });
+ }
+ },
+ },
+ },
+ data: {
+ emptyCouponImg,
+ goodsList: [],
+ selectedList: [],
+ couponsList: [],
+ orderSureCouponList: [],
+ promotionGoodsList: [],
+ },
+ methods: {
+ initData(data = {}) {
+ const { couponResultList = [], reduce = 0 } = data;
+ const selectedList = [];
+ let selectedNum = 0;
+ const couponsList =
+ couponResultList &&
+ couponResultList.map((coupon) => {
+ const { status, couponVO } = coupon;
+ const {
+ couponId,
+ condition = '',
+ endTime = 0,
+ name = '',
+ startTime = 0,
+ value,
+ type,
+ } = couponVO;
+ if (status === 1) {
+ selectedNum++;
+ selectedList.push({
+ couponId,
+ promotionId: ruleId,
+ storeId: this.storeId,
+ });
+ }
+ const val = type === 2 ? value / 100 : value / 10;
+ return {
+ key: couponId,
+ title: name,
+ isSelected: false,
+ timeLimit: `${dayjs(+startTime).format('YYYY-MM-DD')}-${dayjs(
+ +endTime,
+ ).format('YYYY-MM-DD')}`,
+ value: val,
+ status: status === -1 ? 'useless' : 'default',
+ desc: condition,
+ type,
+ tag: '',
+ };
+ });
+ this.setData({
+ selectedList,
+ couponsList,
+ reduce,
+ selectedNum,
+ });
+ },
+ selectCoupon(e) {
+ const { key } = e.currentTarget.dataset;
+ const { couponsList, selectedList } = this.data;
+ couponsList.forEach((coupon) => {
+ if (coupon.key === key) {
+ coupon.isSelected = !coupon.isSelected;
+ }
+ });
+
+ const couponSelected = couponsList.filter(
+ (coupon) => coupon.isSelected === true,
+ );
+
+ this.setData({
+ selectedList: [...selectedList, ...couponSelected],
+ couponsList: [...couponsList],
+ });
+
+ this.triggerEvent('sure', {
+ selectedList: [...selectedList, ...couponSelected],
+ });
+ },
+ hide() {
+ this.setData({
+ couponsShow: false,
+ });
+ },
+ coupons(coupon = {}) {
+ return new Promise((resolve, reject) => {
+ if (coupon?.selectedCoupons) {
+ resolve({
+ couponResultList: couponsData.couponResultList,
+ reduce: couponsData.reduce,
+ });
+ }
+ return reject({
+ couponResultList: [],
+ reduce: undefined,
+ });
+ });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.json b/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.json
new file mode 100644
index 0000000..c46dc55
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.json
@@ -0,0 +1,10 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-popup": "tdesign-miniprogram/popup/popup",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-image": "/components/webp-image/index",
+ "wr-price": "/components/price/index",
+ "coupon-card": "/pages/coupon/components/ui-coupon-card/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.wxml b/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.wxml
new file mode 100644
index 0000000..10b8898
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.wxml
@@ -0,0 +1,43 @@
+
+
+
+
+
+ 选择优惠券
+
+
+ 你有{{couponsList.length}}张可用优惠券
+
+ 已选中{{selectedNum}}张推荐优惠券, 共抵扣
+
+
+
+
+
+
+
+
+
+
+
+ 此优惠券不能和已勾选的优惠券叠加使用
+
+
+
+
+
+
+ 暂无优惠券
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.wxss b/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.wxss
new file mode 100644
index 0000000..a8c795f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/selectCoupons/selectCoupons.wxss
@@ -0,0 +1,104 @@
+.select-coupons {
+ background: #fff;
+ width: 100%;
+ position: relative;
+ border-radius: 20rpx 20rpx 0 0;
+ padding-top: 28rpx;
+ padding-bottom: env(safe-area-inset-bottom);
+}
+.select-coupons .title {
+ width: 100%;
+ text-align: center;
+ font-size: 32rpx;
+ color: #333;
+ font-weight: 600;
+ line-height: 44rpx;
+}
+.select-coupons .info {
+ width: 100%;
+ height: 34rpx;
+ font-size: 24rpx;
+ color: #999;
+ line-height: 34rpx;
+ margin: 20rpx 0;
+ padding: 0 20rpx;
+}
+.select-coupons .info .price {
+ color: #fa4126;
+}
+.select-coupons .coupons-list {
+ max-height: 500rpx;
+}
+.select-coupons .coupons-list .coupons-wrap {
+ padding: 0rpx 20rpx;
+}
+.select-coupons .coupons-list .disable {
+ font-size: 24rpx;
+ color: #ff2525;
+ padding-top: 20rpx;
+}
+.select-coupons .coupons-list .slot-radio {
+ position: absolute;
+ right: 22rpx;
+ top: 50%;
+ transform: translateY(-50%);
+ display: inline-block;
+}
+.select-coupons .coupons-list .slot-radio .wr-check-filled {
+ font-size: 36rpx;
+}
+.select-coupons .coupons-list .slot-radio .check {
+ width: 36rpx;
+}
+.select-coupons .coupons-list .slot-radio .text-primary {
+ color: #fa4126;
+}
+.select-coupons .coupons-list .slot-radio .wr-check {
+ font-size: 36rpx;
+}
+.select-coupons .coupons-list .slot-radio .wr-uncheck {
+ font-size: 36rpx;
+ color: #999;
+}
+.select-coupons .couponp-empty-wrap {
+ padding: 40rpx;
+}
+.select-coupons .couponp-empty-wrap .couponp-empty-img {
+ display: block;
+ width: 240rpx;
+ height: 240rpx;
+ margin: 0 auto;
+}
+.select-coupons .couponp-empty-wrap .couponp-empty-title {
+ font-size: 28rpx;
+ color: #999;
+ text-align: center;
+ line-height: 40rpx;
+ margin-top: 40rpx;
+}
+.select-coupons .coupons-cover {
+ height: 112rpx;
+ width: 100%;
+ box-sizing: border-box;
+ margin-top: 30rpx;
+ padding: 12rpx 32rpx;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.select-coupons .coupons-cover .btn {
+ width: 332rpx;
+ height: 88rpx;
+ text-align: center;
+ line-height: 88rpx;
+ font-size: 32rpx;
+ border-radius: 44rpx;
+ box-sizing: border-box;
+ border: 2rpx solid #dddddd;
+ color: #333333;
+}
+.select-coupons .coupons-cover .red {
+ border-color: #fa4126;
+ background-color: #fa4126;
+ color: #ffffff;
+}
diff --git a/miniprogram/tcb-shop/pages/order/components/specs-goods-card/index.js b/miniprogram/tcb-shop/pages/order/components/specs-goods-card/index.js
new file mode 100644
index 0000000..d5cd284
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/specs-goods-card/index.js
@@ -0,0 +1,132 @@
+Component({
+ options: {
+ addGlobalClass: true,
+ multipleSlots: true, // 在组件定义时的选项中启用多slot支持
+ },
+
+ externalClasses: [
+ 'title-class',
+ 'desc-class',
+ 'num-class',
+ 'thumb-class',
+ 'specs-class',
+ 'price-class',
+ 'origin-price-class',
+ 'price-prefix-class',
+ ],
+
+ relations: {
+ '../order-card/index': {
+ type: 'ancestor',
+ linked(target) {
+ this.parent = target;
+ },
+ },
+ },
+
+ properties: {
+ id: String,
+ hidden: {
+ // 设置为null代表不做类型转换
+ type: null,
+ observer(hidden) {
+ // null就是代表没有设置,没有设置的话不setData,防止祖先组件触发的setHidden操作被覆盖
+ if (hidden !== null) {
+ this.setHidden(!!hidden);
+ }
+ },
+ },
+ data: Object,
+ layout: {
+ type: String,
+ value: 'horizontal',
+ },
+ thumbMode: {
+ type: String,
+ value: 'aspectFill',
+ },
+ thumbWidth: Number,
+ thumbHeight: Number,
+ thumbWidthInPopup: Number,
+ thumbHeightInPopup: Number,
+ priceFill: {
+ type: Boolean,
+ value: true,
+ },
+ currency: {
+ type: String,
+ value: '¥',
+ },
+ lazyLoad: Boolean,
+ centered: Boolean,
+ showCart: Boolean,
+ pricePrefix: String,
+ cartSize: {
+ type: Number,
+ value: 48,
+ },
+ cartColor: {
+ type: String,
+ value: '#FA550F',
+ },
+ disablePopup: Boolean,
+ },
+
+ data: {
+ hiddenInData: false,
+ specsPopup: {
+ insert: false,
+ show: false,
+ },
+ },
+
+ currentInTapSpecs: false,
+
+ lifetimes: {
+ ready() {
+ const { hidden } = this.properties;
+ if (hidden !== null) {
+ this.setHidden(!!hidden);
+ }
+ },
+ },
+
+ methods: {
+ closeSpecsPopup() {
+ this.setData({
+ 'specsPopup.show': false,
+ });
+ this.triggerEvent('specsclose', { good: this.properties.data });
+ },
+
+ removeSpecsPopup() {
+ this.setData({
+ 'specsPopup.insert': false,
+ });
+ },
+
+ onClick(e) {
+ if (this.currentInTapSpecs) {
+ this.currentInTapSpecs = false;
+ return;
+ }
+ this.triggerEvent('click', e.detail);
+ },
+
+ onClickThumb(e) {
+ this.triggerEvent('thumb', e.detail);
+ },
+
+ onClickTag(e) {
+ this.triggerEvent('tag', e.detail);
+ },
+
+ onClickCart(e) {
+ this.triggerEvent('add-cart', e.detail);
+ },
+
+ setHidden(hidden) {
+ this.setData({ hiddenInData: !!hidden });
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/components/specs-goods-card/index.json b/miniprogram/tcb-shop/pages/order/components/specs-goods-card/index.json
new file mode 100644
index 0000000..ccbce72
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/specs-goods-card/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "goods-card": "../goods-card/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/components/specs-goods-card/index.wxml b/miniprogram/tcb-shop/pages/order/components/specs-goods-card/index.wxml
new file mode 100644
index 0000000..f6f2a38
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/components/specs-goods-card/index.wxml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/components/specs-goods-card/index.wxss b/miniprogram/tcb-shop/pages/order/components/specs-goods-card/index.wxss
new file mode 100644
index 0000000..e69de29
diff --git a/miniprogram/tcb-shop/pages/order/config.js b/miniprogram/tcb-shop/pages/order/config.js
new file mode 100644
index 0000000..20a3317
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/config.js
@@ -0,0 +1,94 @@
+export const OrderStatus = {
+ PENDING_PAYMENT: 5, // 待支付
+ PENDING_DELIVERY: 10, // 待发货
+ PENDING_RECEIPT: 40, // 待收货
+ COMPLETE: 50, // 已完成/待评价
+ PAYMENT_TIMEOUT: 80, // 已取消,支付超时
+ CANCELED_NOT_PAYMENT: 80, // 已取消,未支付主动取消
+ CANCELED_PAYMENT: 80, // 已取消,已支付主动取消
+ CANCELED_REJECTION: 80, // 已取消,拒收
+};
+
+// 售后状态 10:待审核,20:已审核,30:已收货,40:收货异常,50:已完成,60:已关闭;
+export const AfterServiceStatus = {
+ TO_AUDIT: 10, // 待审核
+ THE_APPROVED: 20, // 已审核
+ HAVE_THE_GOODS: 30, // 已收货
+ ABNORMAL_RECEIVING: 40, // 收货异常
+ COMPLETE: 50, // 已完成
+ CLOSED: 60, // 已关闭
+};
+
+// 售后类型
+export const ServiceType = {
+ RETURN_GOODS: 10, // 退货退款
+ ONLY_REFUND: 20, // 仅退款
+ ORDER_CANCEL: 30, // 支付后取消
+};
+
+export const ServiceTypeDesc = {
+ [ServiceType.RETURN_GOODS]: '退货',
+ [ServiceType.ONLY_REFUND]: '退款',
+ [ServiceType.ORDER_CANCEL]: '支付后取消',
+};
+
+// 订单按钮类型
+export const OrderButtonTypes = {
+ PAY: 1, // 付款
+ CANCEL: 2, // 取消订单
+ CONFIRM: 3, // 确认收货
+ APPLY_REFUND: 4, // 申请售后
+ VIEW_REFUND: 5, // 查看退款
+ COMMENT: 6, // 评价
+ DELETE: 7, // 删除订单
+ DELIVERY: 8, // 查看物流
+ REBUY: 9, // 再次购买
+ INVITE_GROUPON: 11, //邀请好友拼团
+};
+
+// 售后服务按钮类型
+export const ServiceButtonTypes = {
+ REVOKE: 2, // 撤销
+ FILL_TRACKING_NO: 3, // 填写运单号
+ CHANGE_TRACKING_NO: 4, // 修改运单号
+ VIEW_DELIVERY: 5, // 查看物流
+};
+
+// 售后状态
+export const ServiceStatus = {
+ PENDING_VERIFY: 100, //待审核
+ VERIFIED: 110, // 已审核待寄回商品
+ PENDING_DELIVERY: 120, // 等待买家寄回商品
+ PENDING_RECEIPT: 130, // 已寄回商品,待收货
+ RECEIVED: 140, // 已收货
+ EXCEPTION: 150, // 收货异常
+ REFUNDED: 160, // 已退款
+ CLOSED: 170, // 已关闭
+};
+
+// 售后收货状态
+export const ServiceReceiptStatus = {
+ RECEIPTED: 1, // 已收到货
+ NOT_RECEIPTED: 2, // 未收到货
+};
+
+// 物流节点
+export const LogisticsNodeTypes = {
+ SUBMITTED: 200001, // 已提交订单
+ PAYMENTED: 200002, // 已付款/已下单
+ SHIPPED: 200003, // 已发货
+ CANCELED: 200004, // 已取消
+ RECEIVED: 200005, // 已签收
+ ADDRESS_CHANGED: 200006, // 已修改地址
+ IN_TRANSIT: 200007, // 运输中
+};
+
+export const LogisticsIconMap = {
+ [LogisticsNodeTypes.SUBMITTED]: '',
+ [LogisticsNodeTypes.PAYMENTED]: 'credit_card',
+ [LogisticsNodeTypes.SHIPPED]: 'deliver',
+ [LogisticsNodeTypes.CANCELED]: '',
+ [LogisticsNodeTypes.RECEIVED]: 'check',
+ [LogisticsNodeTypes.ADDRESS_CHANGED]: '',
+ [LogisticsNodeTypes.IN_TRANSIT]: 'yunshuzhong',
+};
diff --git a/miniprogram/tcb-shop/pages/order/delivery-detail/index.js b/miniprogram/tcb-shop/pages/order/delivery-detail/index.js
new file mode 100644
index 0000000..f10ff02
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/delivery-detail/index.js
@@ -0,0 +1,43 @@
+Page({
+ data: {
+ logisticsData: {
+ logisticsNo: '',
+ nodes: [],
+ company: '',
+ phoneNumber: '',
+ },
+ active: 0,
+ },
+
+ onLoad(query) {
+ let data;
+ try {
+ data = JSON.parse(decodeURIComponent(query.data || '{}'));
+ } catch (e) {
+ console.warn('物流节点数据解析失败', e);
+ }
+ if (Number(query.source) === 2) {
+ const service = {
+ company: data.logisticsCompanyName,
+ logisticsNo: data.logisticsNo,
+ nodes: data.nodes,
+ };
+ this.setData({
+ logisticsData: service,
+ });
+ } else if (data) {
+ this.setData({ logisticsData: data });
+ }
+ },
+
+ onLogisticsNoCopy() {
+ wx.setClipboardData({ data: this.data.logisticsData.logisticsNo });
+ },
+
+ onCall() {
+ const { phoneNumber } = this.data.logisticsData;
+ wx.makePhoneCall({
+ phoneNumber,
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/delivery-detail/index.json b/miniprogram/tcb-shop/pages/order/delivery-detail/index.json
new file mode 100644
index 0000000..2ba9163
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/delivery-detail/index.json
@@ -0,0 +1,11 @@
+{
+ "navigationBarTitleText": "物流信息",
+ "usingComponents": {
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
+ "t-image": "/components/webp-image/index",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-steps": "tdesign-miniprogram/steps/steps",
+ "t-step": "tdesign-miniprogram/step-item/step-item"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/delivery-detail/index.wxml b/miniprogram/tcb-shop/pages/order/delivery-detail/index.wxml
new file mode 100644
index 0000000..b5a8bf5
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/delivery-detail/index.wxml
@@ -0,0 +1,91 @@
+
+ var isUrl = function(item) {
+ return item.indexOf('http') > -1;
+ }
+ module.exports = {
+ isUrl: isUrl,
+ }
+
+
+
+
+
+ {{logisticsData.logisticsNo}}
+ 复制
+
+
+
+
+ 拨打
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{item.desc}}
+ {{item.date}}
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/delivery-detail/index.wxss b/miniprogram/tcb-shop/pages/order/delivery-detail/index.wxss
new file mode 100644
index 0000000..6a2524c
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/delivery-detail/index.wxss
@@ -0,0 +1,99 @@
+page {
+ background-color: #f5f5f5;
+}
+.page-section {
+ margin-top: 24rpx;
+ background-color: white;
+}
+.page-section .order-group__left {
+ margin-right: 0 !important;
+}
+.cell-steps {
+ padding: 8rpx;
+}
+.wr-cell__title {
+ flex: none;
+ font-size: 28rpx;
+ color: #666;
+}
+.wr-cell__value {
+ flex: auto;
+ margin-left: 30rpx;
+ font-size: 28rpx;
+ color: #333 !important;
+}
+.logistics-no {
+ display: inline-block;
+ text-align: left;
+ word-break: break-all;
+ color: #333;
+}
+.text-btn {
+ margin-left: 20rpx;
+ display: inline;
+ font-size: 24rpx;
+ padding: 0 15rpx;
+ border: 1rpx solid #ddd;
+ border-radius: 28rpx;
+ color: #333;
+}
+.text-btn--active {
+ opacity: 0.5;
+}
+.steps .step-title {
+ font-weight: bold;
+ color: #333 !important;
+ font-size: 30rpx;
+}
+.steps .step-desc {
+ color: #333333;
+ font-size: 28rpx;
+}
+.steps .step-date {
+ color: #999999;
+ font-size: 24rpx;
+}
+
+.cell-steps__img,
+.cell-steps__imgWrapper {
+ width: 48rpx;
+ height: 48rpx;
+}
+
+.steps
+ .t-step--vertical.t-step--default-anchor
+ .t-steps-item--process
+ .t-steps-item__icon-number {
+ background: #ffece9 !important;
+ color: white !important;
+ border: none;
+}
+
+.steps
+ .t-step--vertical.t-step--default-anchor
+ .t-steps-item--default
+ .t-steps-item__icon-number {
+ color: white !important;
+ background: #f5f5f5 !important;
+ border: none;
+}
+
+.steps
+ .t-step--vertical.t-step--default-anchor.t-step--not-last-child
+ .t-steps-item__inner::after {
+ top: 48rpx;
+ height: calc(100% - 44rpx - 4rpx);
+}
+
+.steps
+ .t-step--vertical.t-step--default-anchor.t-step--not-last-child
+ .t-steps-item__inner::after,
+.steps
+ .t-step--vertical.t-step--default-anchor.t-step--not-last-child
+ .t-steps-item--default
+ .t-steps-item__inner:after {
+ background: #f5f5f5 !important;
+}
+.page-section__steps {
+ padding: 24rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/order/fill-tracking-no/api.js b/miniprogram/tcb-shop/pages/order/fill-tracking-no/api.js
new file mode 100644
index 0000000..33f41de
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/fill-tracking-no/api.js
@@ -0,0 +1,71 @@
+import { mockIp, mockReqId } from '../../../utils/mock';
+
+export function create() {
+ const _resq = {
+ data: null,
+ code: 'Success',
+ msg: null,
+ requestId: mockReqId(),
+ clientIp: mockIp(),
+ rt: 79,
+ success: true,
+ };
+ return Promise.resolve(_resq);
+}
+
+export function update() {
+ const _resq = {
+ data: null,
+ code: 'Success',
+ msg: null,
+ requestId: mockReqId(),
+ clientIp: mockIp(),
+ rt: 79,
+ success: true,
+ };
+ return Promise.resolve(_resq);
+}
+
+export function getDeliverCompanyList() {
+ const _resq = {
+ data: [
+ {
+ name: '中通快递',
+ code: '0001',
+ },
+ {
+ name: '申通快递',
+ code: '0002',
+ },
+ {
+ name: '圆通快递',
+ code: '0003',
+ },
+ {
+ name: '顺丰快递',
+ code: '0004',
+ },
+ {
+ name: '百世快递',
+ code: '0005',
+ },
+ {
+ name: '韵达快递',
+ code: '0006',
+ },
+ {
+ name: '邮政快递',
+ code: '0007',
+ },
+ {
+ name: '丰网快递',
+ code: '0008',
+ },
+ {
+ name: '顺丰直邮',
+ code: '0009',
+ },
+ ],
+ };
+ return Promise.resolve(_resq);
+}
diff --git a/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.js b/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.js
new file mode 100644
index 0000000..dd6ff3b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.js
@@ -0,0 +1,190 @@
+import Dialog from 'tdesign-miniprogram/dialog/index';
+import Toast from 'tdesign-miniprogram/toast/index';
+import reasonSheet from '../components/reason-sheet/reasonSheet';
+import { getDeliverCompanyList, create, update } from './api';
+
+Page({
+ deliveryCompanyList: [],
+
+ data: {
+ trackingNo: '',
+ remark: '',
+ deliveryCompany: null,
+ submitActived: false,
+ submitting: false,
+ },
+ onLoad(query) {
+ const {
+ rightsNo = '',
+ logisticsNo = '',
+ logisticsCompanyName = '',
+ logisticsCompanyCode = '',
+ remark = '',
+ } = query;
+
+ if (!rightsNo) {
+ Dialog.confirm({
+ title: '请选择售后单?',
+ content: '',
+ confirmBtn: '确认',
+ }).then(() => {
+ wx.navigateBack({ backRefresh: true });
+ });
+ }
+ this.rightsNo = rightsNo;
+ if (logisticsNo) {
+ wx.setNavigationBarTitle({
+ title: '修改运单号',
+ fail() {},
+ });
+ this.isChange = true;
+ this.setData({
+ deliveryCompany: {
+ name: logisticsCompanyName,
+ code: logisticsCompanyCode,
+ },
+ trackingNo: logisticsNo,
+ remark,
+ submitActived: true,
+ });
+ }
+ this.setWatcher('trackingNo', this.checkParams.bind(this));
+ this.setWatcher('deliveryCompany', this.checkParams.bind(this));
+ },
+
+ setWatcher(key, callback) {
+ let lastData = this.data;
+ const keys = key.split('.');
+ keys.slice(0, -1).forEach((k) => {
+ lastData = lastData[k];
+ });
+ const lastKey = keys[keys.length - 1];
+ this.observe(lastData, lastKey, callback);
+ },
+
+ observe(data, k, callback) {
+ let val = data[k];
+ Object.defineProperty(data, k, {
+ configurable: true,
+ enumerable: true,
+ set: (value) => {
+ val = value;
+ callback();
+ },
+ get: () => {
+ return val;
+ },
+ });
+ },
+
+ getDeliveryCompanyList() {
+ if (this.deliveryCompanyList.length > 0) {
+ return Promise.resolve(this.deliveryCompanyList);
+ }
+ return getDeliverCompanyList().then((res) => {
+ this.deliveryCompanyList = res.data || [];
+ return this.deliveryCompanyList;
+ });
+ },
+
+ onInput(e) {
+ const { key } = e.currentTarget.dataset;
+ const { value } = e.detail;
+ this.setData({ [key]: value });
+ },
+
+ onCompanyTap() {
+ this.getDeliveryCompanyList().then((deliveryCompanyList) => {
+ reasonSheet({
+ show: true,
+ title: '选择物流公司',
+ options: deliveryCompanyList.map((company) => ({
+ title: company.name,
+ checked: this.data.deliveryCompany
+ ? company.code === this.data.deliveryCompany.code
+ : false,
+ })),
+ showConfirmButton: true,
+ showCancelButton: true,
+ emptyTip: '请选择物流公司',
+ }).then((indexes) => {
+ this.setData({
+ deliveryCompany: deliveryCompanyList[indexes[0]],
+ });
+ });
+ });
+ },
+
+ checkParams() {
+ const res = { errMsg: '', require: false };
+
+ if (!this.data.trackingNo) {
+ res.errMsg = '请填写运单号';
+ res.require = true;
+ } else if (!this.data.deliveryCompany) {
+ res.errMsg = '请选择物流公司';
+ res.require = true;
+ }
+ this.setData({ submitActived: !res.require });
+ return res;
+ },
+
+ onSubmit() {
+ const checkRes = this.checkParams();
+ if (checkRes.errMsg) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: checkRes.errMsg,
+ icon: '',
+ });
+ return;
+ }
+
+ const {
+ trackingNo,
+ remark,
+ deliveryCompany: { code, name },
+ } = this.data;
+
+ const params = {
+ rightsNo: this.rightsNo,
+ logisticsCompanyCode: code,
+ logisticsCompanyName: name,
+ logisticsNo: trackingNo,
+ remark,
+ };
+ const api = this.isChange ? create : update;
+ this.setData({ submitting: true });
+ api(params)
+ .then(() => {
+ this.setData({ submitting: false });
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '保存成功',
+ icon: '',
+ });
+ setTimeout(() => wx.navigateBack({ backRefresh: true }), 1000);
+ })
+ .catch(() => {
+ this.setData({ submitting: false });
+ });
+ },
+
+ onScanTap() {
+ wx.scanCode({
+ scanType: ['barCode'],
+ success: (res) => {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '扫码成功',
+ icon: '',
+ });
+ this.setData({ trackingNo: res.result });
+ },
+ fail: () => {},
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.json b/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.json
new file mode 100644
index 0000000..bd1bb20
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.json
@@ -0,0 +1,14 @@
+{
+ "navigationBarTitleText": "填写运单号",
+ "usingComponents": {
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
+ "t-textarea": "tdesign-miniprogram/textarea/textarea",
+ "t-input": "tdesign-miniprogram/input/input",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "t-button": "tdesign-miniprogram/button/button",
+ "ui-reason-sheet": "../components/reason-sheet/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.wxml b/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.wxml
new file mode 100644
index 0000000..7949a0e
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.wxml
@@ -0,0 +1,51 @@
+
+ 请填写正确的退货包裹运单信息,以免影响退款进度
+
+
+
+
+
+
+
+
+
+
+ 备注信息
+
+
+
+
+
+ 保存
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.wxss b/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.wxss
new file mode 100644
index 0000000..726ab89
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/fill-tracking-no/index.wxss
@@ -0,0 +1,103 @@
+@import '../../../style/theme.wxss';
+
+:host {
+ background-color: #f5f5f5;
+}
+
+.notice-bar {
+ padding: 24rpx 30rpx;
+ text-align: center;
+ font-size: 26rpx;
+ color: #e17349;
+ background: #fefcef;
+}
+
+.fill-tracking-no__form {
+ margin-top: 20rpx;
+}
+
+.fill-tracking-no__form .t-cell__note {
+ justify-content: flex-start;
+}
+
+.fill-tracking-no__form .t-cell__value {
+ color: #333 !important;
+ font-size: 30rpx;
+ text-align: left;
+ padding: 0;
+}
+
+.fill-tracking-no__form .t-cell__value::after {
+ border: none !important;
+}
+
+.fill-tracking-no__form .t-cell__value .t-textarea__wrapper {
+ padding: 0;
+}
+
+.fill-tracking-no__form .t-input__control,
+.fill-tracking-no__form .t-textarea__placeholder,
+.fill-tracking-no__form .t-cell__placeholder {
+ font-size: 30rpx !important;
+}
+
+.fill-tracking-no__form .t-textarea__placeholder,
+.fill-tracking-no__form .t-cell__placeholder {
+ color: #bbbbbb !important;
+}
+
+.t-textarea__note {
+ width: 100%;
+}
+
+.fill-tracking-no__button-bar {
+ margin: 38rpx 30rpx 0;
+}
+
+.fill-tracking-no__button-bar .btn {
+ background-color: transparent;
+ font-size: 32rpx;
+ width: 100%;
+ border-radius: 48rpx;
+}
+
+.fill-tracking-no__button-bar .btn:first-child {
+ margin-bottom: 20rpx;
+}
+
+.fill-tracking-no__button-bar .btn.confirmBtn {
+ background: #fa4126;
+ color: #fff;
+}
+
+.fill-tracking-no__button-bar .btn.disabled {
+ background-color: #c6c6c6;
+ color: #fff;
+}
+
+.t-cell-title-width {
+ width: 160rpx;
+ flex: none !important;
+}
+.textarea-wrapper {
+ background: #fff;
+ display: flex;
+ align-items: flex-start;
+ padding: 24rpx 32rpx 0 32rpx;
+}
+.t-textarea-wrapper {
+ box-sizing: border-box;
+}
+
+.fill-tracking-no__form .t-input__wrapper {
+ margin: 0 !important;
+}
+
+.fill-tracking-no__form {
+ --td-input-vertical-padding: 0;
+}
+
+.t-button {
+ --td-button-default-color: #aeb3b7;
+ --td-button-primary-text-color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/order/invoice/index.js b/miniprogram/tcb-shop/pages/order/invoice/index.js
new file mode 100644
index 0000000..5333802
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/invoice/index.js
@@ -0,0 +1,38 @@
+import { fetchOrderDetail } from '../../../services/order/orderDetail';
+
+Page({
+ data: {
+ invoice: {},
+ },
+ onLoad({ orderNo }) {
+ this.orderNo = orderNo;
+ this.init();
+ },
+ init() {
+ this.getDetail();
+ },
+ getDetail() {
+ const params = {
+ parameter: this.orderNo,
+ };
+ return fetchOrderDetail(params).then((res) => {
+ const order = res.data;
+
+ const invoice = {
+ buyerName: order?.invoiceVO?.buyerName, //个人或公司名称
+ buyerTaxNo: order?.invoiceVO?.buyerTaxNo, //税号
+ buyerPhone: order?.invoiceVO?.buyerPhone, //手机
+ email: order?.invoiceVO?.email, //邮箱
+ titleType: order?.invoiceVO?.titleType === 1 ? '个人' : '公司', //发票抬头 1-个人 2-公司
+ ontentType: order?.invoiceVO?.ontentType === 1 ? '商品明细' : '2类别', //发票内容 1-明细 2类别
+ invoiceType:
+ order?.invoiceVO?.invoiceType === 5 ? '电子普通发票' : '不开发票', //是否开票 0-不开 5-电子发票
+ isInvoice: order?.invoiceVO?.buyerName ? '已开票' : '未开票',
+ money: order?.invoiceVO?.money,
+ };
+ this.setData({
+ invoice,
+ });
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/invoice/index.json b/miniprogram/tcb-shop/pages/order/invoice/index.json
new file mode 100644
index 0000000..ea83b65
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/invoice/index.json
@@ -0,0 +1,8 @@
+{
+ "navigationBarTitleText": "发票详情",
+ "usingComponents": {
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-cell-group": "tdesign-miniprogram/cell-group/cell-group"
+ }
+}
diff --git a/miniprogram/tcb-shop/pages/order/invoice/index.wxml b/miniprogram/tcb-shop/pages/order/invoice/index.wxml
new file mode 100644
index 0000000..b3bc794
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/invoice/index.wxml
@@ -0,0 +1,40 @@
+
+
+ 发票详情
+
+ 发票类型
+ {{invoice.invoiceType}}
+
+
+ 发票抬头
+ {{invoice.buyerName}}
+
+
+ 纳税人识别号
+ {{invoice.buyerTaxNo}}
+
+
+ 发票内容
+ {{invoice.ontentType}}
+
+
+ 发票金额
+ {{invoice.money}}
+
+
+
+ 收票人信息
+
+ 邮箱
+ {{invoice.email}}
+
+
+ 手机号
+ {{invoice.buyerPhone}}
+
+
+ 开票状态
+ {{invoice.isInvoice}}
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/invoice/index.wxss b/miniprogram/tcb-shop/pages/order/invoice/index.wxss
new file mode 100644
index 0000000..0a0379b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/invoice/index.wxss
@@ -0,0 +1,31 @@
+:host {
+ background-color: #f5f5f5;
+}
+
+.invoice-detail .invoice-detail-box {
+ background-color: #fff;
+ padding: 24rpx 32rpx;
+ margin-top: 24rpx;
+}
+
+.invoice-detail-title {
+ font-size: 14px;
+ font-weight: 600;
+}
+
+.invoice-detail-box-row {
+ display: flex;
+ margin-top: 44rpx;
+}
+
+.invoice-detail-box-title {
+ font-size: 13px;
+ color: #666666;
+ width: 156rpx;
+ margin-right: 32rpx;
+}
+
+.invoice-detail-box-value {
+ font-size: 13px;
+ color: #333333;
+}
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.js b/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.js
new file mode 100644
index 0000000..96cf8ef
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.js
@@ -0,0 +1,25 @@
+/*
+ * @Author: rileycai
+ * @Date: 2022-03-05 16:47:16
+ * @LastEditTime: 2022-03-05 16:48:32
+ * @LastEditors: rileycai
+ * @Description:
+ * @FilePath: /tdesign-miniprogram-starter/pages/order/order-confirm/components/address-card/index.js
+ */
+Component({
+ externalClasses: ['wr-class'],
+ properties: {
+ addressData: {
+ type: Object,
+ value: {},
+ },
+ },
+ methods: {
+ onAddressTap() {
+ this.triggerEvent('addressclick');
+ },
+ onAddTap() {
+ this.triggerEvent('addclick');
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.json b/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.json
new file mode 100644
index 0000000..08ecc96
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-icon": "tdesign-miniprogram/icon/icon"
+ }
+}
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.wxml b/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.wxml
new file mode 100644
index 0000000..a6294aa
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.wxml
@@ -0,0 +1,25 @@
+
+ var hidePhoneNum = function(array) {
+ if (!array) return;
+ var mphone = array.substring(0, 3) + '****' + array.substring(7);
+ return mphone;
+ }
+ module.exports = {
+ hidePhoneNum:hidePhoneNum
+ }
+
+
+
+
+
+ {{addressData.detailAddress}}
+ {{addressData.name}} {{utils.hidePhoneNum(addressData.phone)}}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.wxss b/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.wxss
new file mode 100644
index 0000000..aaff4bd
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/components/address-card/index.wxss
@@ -0,0 +1,66 @@
+.address-card {
+ background: #fff;
+ margin: 0rpx 0rpx 24rpx;
+}
+.address-card .wr-cell__title {
+ color: #999;
+ margin-left: 6rpx;
+}
+.address-card .order-address {
+ display: flex;
+ width: 100%;
+}
+.address-card .order-address .address-content {
+ flex: 1;
+}
+.order-address .address__right {
+ align-self: center;
+}
+.address-card .order-address .title {
+ display: flex;
+ align-items: center;
+ height: 40rpx;
+ font-size: 28rpx;
+ font-weight: normal;
+ color: #999999;
+ line-height: 40rpx;
+}
+.address-card .order-address .title .address-tag {
+ width: 52rpx;
+ height: 29rpx;
+ border: 1rpx solid #0091ff;
+ background-color: rgba(122, 167, 251, 0.1);
+ text-align: center;
+ line-height: 29rpx;
+ border-radius: 8rpx;
+ color: #0091ff;
+ font-size: 20rpx;
+ margin-right: 12rpx;
+}
+.address-card .order-address .detail {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ font-size: 36rpx;
+ font-weight: bold;
+ color: #333333;
+ line-height: 48rpx;
+ margin: 8rpx 0;
+}
+.address-card .order-address .info {
+ height: 40rpx;
+ font-size: 28rpx;
+ font-weight: normal;
+ color: #333333;
+ line-height: 40rpx;
+}
+.address-card .top-line {
+ width: 100%;
+ height: 6rpx;
+ background-color: #fff;
+ background-image: url(https://cdn-we-retail.ym.tencent.com/miniapp/order/stripe.png);
+ background-repeat: repeat-x;
+ display: block;
+}
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/getNotes.wxs b/miniprogram/tcb-shop/pages/order/order-confirm/getNotes.wxs
new file mode 100644
index 0000000..935962c
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/getNotes.wxs
@@ -0,0 +1,11 @@
+var getNotes = function (storeInfoList, storeIndex) {
+ if (!storeInfoList) {
+ return '';
+ }
+ var storeInfo = storeInfoList[storeIndex];
+ if (!storeInfo) {
+ return '';
+ }
+ return storeInfoList[storeIndex].remark;
+};
+module.exports = getNotes;
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/handleInvoice.wxs b/miniprogram/tcb-shop/pages/order/order-confirm/handleInvoice.wxs
new file mode 100644
index 0000000..30e50f9
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/handleInvoice.wxs
@@ -0,0 +1,11 @@
+var handleInvoice = function (invoiceData) {
+ if (!invoiceData || invoiceData.invoiceType == 0) {
+ return '暂不开发票';
+ }
+ var title = invoiceData.titleType == 2 ? '公司' : '个人';
+ var content = invoiceData.contentType == 2 ? '商品类别' : '商品明细';
+ return invoiceData.email
+ ? '电子普通发票 (' + content + ' - ' + title + ')'
+ : '暂不开发票';
+};
+module.exports = handleInvoice;
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/index.js b/miniprogram/tcb-shop/pages/order/order-confirm/index.js
new file mode 100644
index 0000000..f2b3cf8
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/index.js
@@ -0,0 +1,308 @@
+import Toast from 'tdesign-miniprogram/toast/index';
+import { createOrderItem } from '../../../services/order/orderItem';
+import { createOrder, ORDER_STATUS, updateOrderStatus } from '../../../services/order/order';
+import { getCartItem, deleteCartItem } from '../../../services/cart/cart';
+import { getSkuDetail, updateSku } from '../../../services/sku/sku';
+import { getAddressPromise } from '../../usercenter/address/list/util';
+import { getSingleCloudImageTempUrl } from '../../../utils/cloudImageHandler';
+import { cartShouldFresh } from '../../../utils/cartFresh';
+import { pay } from '../../../services/pay/pay';
+
+const stripeImg = `https://cdn-we-retail.ym.tencent.com/miniapp/order/stripe.png`;
+
+async function createOrderItemFromSku({ count, orderId, skuId }) {
+ const latestSku = await getSkuDetail(skuId);
+ const finalCount = latestSku.count - count;
+
+ // check if sku is enough
+ if (finalCount < 0) {
+ return Promise.reject({ reason: 'SKU_NOT_ENOUGH' });
+ }
+
+ try {
+ // decrease sku's count
+ await updateSku({
+ skuId,
+ data: {
+ count: finalCount,
+ },
+ });
+ try {
+ // create order item
+ await createOrderItem({ count, orderId, skuId });
+ } catch (e) {
+ console.error(e);
+ return Promise.reject({ reason: 'CREATE_ORDER_ITEM_FAILED' });
+ }
+ } catch (e) {
+ console.error(e);
+ return Promise.reject({ reason: 'SKU_DECREASE_FAILED' });
+ }
+}
+
+/**
+ *
+ * @param {Object} cartItem
+ * @param {String} orderId
+ */
+function createOrderItemFromCartItem(cartItem, orderId) {
+ return createOrderItemFromSku({ count: cartItem.count, orderId, skuId: cartItem.sku._id });
+}
+
+/**
+ *
+ * @param {Array} cartItems
+ */
+function cartItemToGoodList(cartItems) {
+ return cartItems.map((item) => ({
+ thumb: item.sku.image,
+ title: item.sku.spu.name,
+ specs: item.sku.attr_value.map((v) => v.value).join(','),
+ price: item.sku.price,
+ num: item.count,
+ }));
+}
+
+Page({
+ data: {
+ placeholder: '备注信息',
+ stripeImg,
+ loading: true,
+ orderCardList: [], // 仅用于商品卡片展示
+ goodsRequestList: [],
+ userAddressReq: null,
+ storeInfoList: [],
+ promotionGoodsList: [], //当前门店商品列表(优惠券)
+ currentStoreId: null, //当前优惠券storeId
+ userAddress: null,
+ goodsList: [],
+ cartItems: [],
+ totalSalePrice: 0,
+ directSku: null,
+ },
+
+ payLock: false,
+
+ type: null,
+
+ async onLoadFromCart(cartIds) {
+ if (typeof cartIds !== 'string') {
+ console.error('invalid cart item ids', cartIds);
+ this.failedAndBack('获取购物车信息失败');
+ return;
+ }
+ const ids = cartIds.split(',');
+ try {
+ const cartItems = await Promise.all(
+ ids.map(async (id) => {
+ const cartItem = (await getCartItem({ id })).data;
+ cartItem.sku = { ...cartItem.sku, ...(await getSkuDetail(cartItem.sku._id)) };
+ cartItem.sku.image = await getSingleCloudImageTempUrl(cartItem.sku.image);
+ return cartItem;
+ }),
+ );
+ const goodsList = cartItemToGoodList(cartItems);
+ const totalSalePrice = goodsList.reduce((acc, cur) => acc + cur.price * cur.num, 0);
+ this.setData({
+ goodsList,
+ totalSalePrice,
+ cartItems,
+ });
+ } catch (e) {
+ this.failedAndBack('获取购物车信息失败', e);
+ }
+ },
+ async onLoadFromDirect(countStr, skuId) {
+ const count = parseInt(countStr);
+ if (typeof count !== 'number' || isNaN(count) || typeof skuId !== 'string') {
+ console.error('invalid cunt or skiId', count, skuId);
+ this.failedAndBack('初始化信息有误');
+ return;
+ }
+
+ try {
+ const sku = await getSkuDetail(skuId);
+ sku.image = await getSingleCloudImageTempUrl(sku.image);
+
+ const goodsList = [
+ {
+ thumb: sku.image,
+ title: sku.spu.name,
+ specs: sku.attr_value.map((v) => v.value).join(','),
+ price: sku.price,
+ num: count,
+ },
+ ];
+
+ const totalSalePrice = goodsList.reduce((acc, cur) => acc + cur.price * cur.num, 0);
+ this.setData({
+ goodsList,
+ totalSalePrice,
+ directSku: sku,
+ });
+ } catch (e) {
+ this.failedAndBack('获取商品信息失败', e);
+ }
+ },
+
+ async onLoad(options) {
+ this.type = options?.type;
+ if (this.type === 'cart') {
+ await this.onLoadFromCart(options?.cartIds);
+ } else if (this.type === 'direct') {
+ await this.onLoadFromDirect(options?.count, options?.skuId);
+ } else {
+ this.failedAndBack('初始化信息有误', 'invalid type');
+ }
+
+ this.setData({
+ loading: false,
+ });
+ },
+
+ init() {
+ this.setData({
+ loading: false,
+ });
+ const { goodsRequestList } = this;
+ this.handleOptionsParams({ goodsRequestList });
+ },
+
+ toast(message) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message,
+ duration: 1000,
+ icon: '',
+ });
+ },
+
+ onGotoAddress() {
+ /** 获取一个Promise */
+ getAddressPromise()
+ .then((address) => {
+ this.setData({
+ userAddress: {
+ ...address,
+ detailAddress: address.address,
+ },
+ });
+ })
+ .catch(() => {});
+
+ wx.navigateTo({
+ url: `/pages/usercenter/address/list/index?selectMode=true`,
+ });
+ },
+ onTap() {
+ this.setData({
+ placeholder: '',
+ });
+ },
+
+ async payImpl(totalPrice, orderId) {
+ try {
+ await pay({ _id: orderId, totalPrice });
+ try {
+ await updateOrderStatus({ orderId, status: ORDER_STATUS.TO_SEND });
+ this.toast('支付成功');
+ } catch (e) {
+ console.error(e);
+ this.toast('支付成功,但订单状态更新失败');
+ } finally {
+ setTimeout(() => {
+ wx.navigateBack();
+ }, 1000);
+ }
+ } catch (e) {
+ this.failedAndBack('支付失败', e);
+ }
+ },
+
+ async submitOrderFromCart() {
+ /**
+ * 1.创建订单
+ * 2.创建订单项
+ * 3.删除购物车项
+ */
+
+ const { cartItems, userAddress } = this.data;
+ const { id: orderId } = await createOrder({ status: ORDER_STATUS.TO_PAY, addressId: userAddress._id });
+
+ try {
+ await cartItems.map(async (cartItem) => {
+ await createOrderItemFromCartItem(cartItem, orderId);
+ });
+
+ try {
+ await cartItems.map(async (cartItem) => {
+ await deleteCartItem({ cartItemId: cartItem._id });
+ // any deletion should notify cart to fresh
+ cartShouldFresh();
+ });
+ } catch (e) {
+ console.error(e);
+ this.toast('删除购物车失败,请手动删除');
+ // do not return, continue to pay
+ }
+
+ const totalPrice = cartItems.reduce((acc, cur) => acc + cur.count * cur.sku.price, 0);
+ await this.payImpl(totalPrice, orderId);
+ } catch (e) {
+ this.failedAndBack('创建订单失败', e);
+ }
+ },
+
+ async submitOrderFromDirect() {
+ /**
+ * 1.创建订单
+ * 2.创建订单项
+ */
+
+ const { directSku, userAddress, goodsList } = this.data;
+ const goods = goodsList[0];
+ const { id: orderId } = await createOrder({ status: ORDER_STATUS.TO_PAY, addressId: userAddress._id });
+
+ try {
+ await createOrderItemFromSku({ count: goods.num, orderId, skuId: directSku._id });
+ const totalPrice = goods.price * goods.num;
+
+ await this.payImpl(totalPrice, orderId);
+ } catch (e) {
+ this.failedAndBack('创建订单失败', e);
+ }
+ },
+
+ failedAndBack(message, e) {
+ e && console.error(e);
+ this.toast(message);
+ setTimeout(() => {
+ wx.navigateBack();
+ }, 1000);
+ },
+
+ // 提交订单
+ async submitOrder() {
+ const { userAddress } = this.data;
+ if (!userAddress) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '请添加收货地址',
+ duration: 2000,
+ icon: 'help-circle',
+ });
+ return;
+ }
+
+ if (this.type === 'cart') {
+ this.submitOrderFromCart();
+ } else if (this.type === 'direct') {
+ this.submitOrderFromDirect();
+ } else {
+ console.error('invalid type', this.type);
+ this.failedAndBack('初始化信息有误');
+ }
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/index.json b/miniprogram/tcb-shop/pages/order/order-confirm/index.json
new file mode 100644
index 0000000..11f25ac
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/index.json
@@ -0,0 +1,16 @@
+{
+ "navigationBarTitleText": "订单确认",
+ "usingComponents": {
+ "t-popup": "tdesign-miniprogram/popup/popup",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "t-textarea": "tdesign-miniprogram/textarea/textarea",
+ "price": "/components/price/index",
+ "select-coupons": "../components/selectCoupons/selectCoupons",
+ "no-goods": "../components/noGoods/noGoods",
+ "t-image": "/components/webp-image/index",
+ "address-card": "./components/address-card/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/index.wxml b/miniprogram/tcb-shop/pages/order/order-confirm/index.wxml
new file mode 100644
index 0000000..b01ce44
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/index.wxml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+ {{goods.title}}
+ {{goods.specs}}
+
+
+
+ x{{goods.num}}
+
+
+
+
+
+ 商品总额
+
+
+
+
+
+ 共{{goodsList.length}}件
+ 小计
+
+
+
+
+
+
+
+ 提交订单
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/index.wxss b/miniprogram/tcb-shop/pages/order/order-confirm/index.wxss
new file mode 100644
index 0000000..8b201c8
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/index.wxss
@@ -0,0 +1,221 @@
+.order-sure {
+ box-sizing: border-box;
+ background: #f6f6f6;
+ padding: 24rpx 0 calc(env(safe-area-inset-bottom) + 136rpx);
+ min-height: 100vh;
+}
+
+.order-sure .wx-pay-cover {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ z-index: 10;
+ background: #fff;
+ height: 112rpx;
+ padding-bottom: env(safe-area-inset-bottom);
+}
+.order-sure .wx-pay-cover .wx-pay {
+ width: 100%;
+ height: 100rpx;
+ box-sizing: border-box;
+ padding: 0rpx 32rpx;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.order-sure .wx-pay-cover .wx-pay .price {
+ color: #fa4126;
+ font-weight: bold;
+ font-size: 63rpx;
+ line-height: 88rpx;
+}
+
+.order-sure .wx-pay-cover .wx-pay .submit-btn {
+ height: 80rpx;
+ width: 240rpx;
+ border-radius: 40rpx;
+ background-color: #fa4126;
+ color: #ffffff;
+ line-height: 80rpx;
+ font-weight: bold;
+ font-size: 28rpx;
+ text-align: center;
+}
+.order-sure .wx-pay-cover .wx-pay .btn-gray {
+ background: #cccccc;
+}
+
+.order-wrapper .store-wrapper {
+ width: 100%;
+ height: 96rpx;
+ box-sizing: border-box;
+ padding: 0 32rpx;
+ display: flex;
+ align-items: center;
+ font-size: 28rpx;
+ line-height: 40rpx;
+ color: #333333;
+ background-color: #ffffff;
+}
+.order-wrapper .store-wrapper .store-logo {
+ margin-right: 16rpx;
+}
+.order-wrapper .goods-wrapper {
+ width: 100%;
+ box-sizing: border-box;
+ padding: 16rpx 32rpx;
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ font-size: 24rpx;
+ line-height: 32rpx;
+ color: #999999;
+ background-color: #ffffff;
+}
+.goods-wrapper .goods-image {
+ width: 176rpx;
+ height: 176rpx;
+ border-radius: 8rpx;
+ overflow: hidden;
+ margin-right: 16rpx;
+}
+.goods-wrapper .goods-content {
+ flex: 1;
+}
+
+.goods-wrapper .goods-content .goods-title {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 28rpx;
+ line-height: 40rpx;
+ margin-bottom: 12rpx;
+ color: #333333;
+ margin-right: 16rpx;
+}
+
+.goods-wrapper .goods-right {
+ min-width: 128rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+}
+
+.goods-right .goods-price {
+ color: #333333;
+ font-size: 32rpx;
+ line-height: 48rpx;
+ font-weight: bold;
+ margin-bottom: 16rpx;
+}
+
+.goods-right .goods-num {
+ text-align: right;
+}
+
+.order-sure .pay-detail {
+ background-color: #ffffff;
+ padding: 16rpx 32rpx;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.order-sure .pay-detail .pay-item {
+ width: 100%;
+ height: 72rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 26rpx;
+ line-height: 36rpx;
+ color: #666666;
+}
+.order-sure .pay-detail .pay-item .pay-item__right {
+ color: #333333;
+ font-size: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ max-width: 400rpx;
+}
+.order-sure .pay-detail .pay-item .pay-item__right .pay-remark {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ max-width: 400rpx;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.order-sure .pay-detail .pay-item .font-bold {
+ font-weight: bold;
+}
+.order-sure .pay-detail .pay-item .primary {
+ color: #fa4126;
+}
+
+.add-notes .add-notes__content {
+ --td-textarea-background-color: #f5f5f5;
+}
+
+.add-notes .t-textarea__placeholder {
+ color: #aeb3b7;
+}
+
+.add-notes .add-notes__textarea__font {
+ font-size: 26rpx;
+}
+.add-notes .add-notes__textarea {
+ margin-top: 32rpx;
+}
+
+.order-sure .add-notes .dialog__message {
+ border-radius: 8rpx;
+}
+
+.order-sure .add-notes .dialog__button-cancel::after {
+ border-right: 0;
+}
+
+.order-sure .amount-wrapper {
+ width: 100%;
+ box-sizing: border-box;
+ background-color: #ffffff;
+ padding: 0rpx 32rpx;
+ height: 96rpx;
+}
+
+.order-sure .pay-amount {
+ width: 100%;
+ height: 96rpx;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ font-size: 28rpx;
+ color: #333333;
+ position: relative;
+}
+.order-sure .pay-amount::after {
+ position: absolute;
+ content: ' ';
+ top: 0;
+ left: 0;
+ width: 200%;
+ height: 200%;
+ transform: scale(0.5);
+ transform-origin: 0 0;
+ border-top: 2rpx solid #f5f5f5;
+}
+.order-sure .pay-amount .order-num {
+ color: #999999;
+ padding-right: 8rpx;
+}
+
+.order-sure .pay-amount .total-price {
+ font-size: 36rpx;
+ color: #fa4126;
+ font-weight: bold;
+ padding-left: 8rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/order.wxs b/miniprogram/tcb-shop/pages/order/order-confirm/order.wxs
new file mode 100644
index 0000000..42f3de1
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/order.wxs
@@ -0,0 +1,8 @@
+var toHide = function (array) {
+ if (!array) return;
+ var mphone = array.substring(0, 3) + '****' + array.substring(7);
+ return mphone;
+};
+module.exports = {
+ toHide: toHide,
+};
diff --git a/miniprogram/tcb-shop/pages/order/order-confirm/pay.js b/miniprogram/tcb-shop/pages/order/order-confirm/pay.js
new file mode 100644
index 0000000..0c27c3c
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-confirm/pay.js
@@ -0,0 +1,115 @@
+import Dialog from 'tdesign-miniprogram/dialog/index';
+import Toast from 'tdesign-miniprogram/toast/index';
+
+import { dispatchCommitPay } from '../../../services/order/orderConfirm';
+
+// 真实的提交支付
+export const commitPay = (params) => {
+ return dispatchCommitPay({
+ goodsRequestList: params.goodsRequestList, // 待结算的商品集合
+ invoiceRequest: params.invoiceRequest, // 发票信息
+ // isIgnore: params.isIgnore || false, // 删掉 是否忽视库存不足和商品失效,继续结算,true=继续结算 购物车请赋值false
+ userAddressReq: params.userAddressReq, // 地址信息(用户在购物选择更换地址)
+ currency: params.currency || 'CNY', // 支付货币: 人民币=CNY,美元=USD
+ logisticsType: params.logisticsType || 1, // 配送方式 0=无需配送 1=快递 2=商家 3=同城 4=自提
+ // orderMark: params.orderMark, // 下单备注
+ orderType: params.orderType || 0, // 订单类型 0=普通订单 1=虚拟订单
+ payType: params.payType || 1, // 支付类型(0=线上、1=线下)
+ totalAmount: params.totalAmount, // 新增字段"totalAmount"总的支付金额
+ userName: params.userName, // 用户名
+ payWay: 1,
+ authorizationCode: '', //loginCode, // 登录凭证
+ storeInfoList: params.storeInfoList, //备注信息列表
+ couponList: params.couponList,
+ groupInfo: params.groupInfo,
+ });
+};
+
+export const paySuccess = (payOrderInfo) => {
+ const { payAmt, tradeNo, groupId, promotionId } = payOrderInfo;
+ // 支付成功
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '支付成功',
+ duration: 2000,
+ icon: 'check-circle',
+ });
+
+ const params = {
+ totalPaid: payAmt,
+ orderNo: tradeNo,
+ };
+ if (groupId) {
+ params.groupId = groupId;
+ }
+ if (promotionId) {
+ params.promotionId = promotionId;
+ }
+ const paramsStr = Object.keys(params)
+ .map((k) => `${k}=${params[k]}`)
+ .join('&');
+ // 跳转支付结果页面
+ wx.redirectTo({ url: `/pages/order/pay-result/index?${paramsStr}` });
+};
+
+export const payFail = (payOrderInfo, resultMsg) => {
+ if (resultMsg === 'requestPayment:fail cancel') {
+ if (payOrderInfo.dialogOnCancel) {
+ //结算页,取消付款,dialog提示
+ Dialog.confirm({
+ title: '是否放弃付款',
+ content: '商品可能很快就会被抢空哦,是否放弃付款?',
+ confirmBtn: '放弃',
+ cancelBtn: '继续付款',
+ }).then(() => {
+ wx.redirectTo({ url: '/pages/order/order-list/index' });
+ });
+ } else {
+ //订单列表页,订单详情页,取消付款,toast提示
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '支付取消',
+ duration: 2000,
+ icon: 'close-circle',
+ });
+ }
+ } else {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: `支付失败:${resultMsg}`,
+ duration: 2000,
+ icon: 'close-circle',
+ });
+ setTimeout(() => {
+ wx.redirectTo({ url: '/pages/order/order-list/index' });
+ }, 2000);
+ }
+};
+
+// 微信支付方式
+export const wechatPayOrder = (payOrderInfo) => {
+ // const payInfo = JSON.parse(payOrderInfo.payInfo);
+ // const { timeStamp, nonceStr, signType, paySign } = payInfo;
+ return new Promise((resolve) => {
+ // demo 中直接走支付成功
+ paySuccess(payOrderInfo);
+ resolve();
+ /* wx.requestPayment({
+ timeStamp,
+ nonceStr,
+ package: payInfo.package,
+ signType,
+ paySign,
+ success: function () {
+ paySuccess(payOrderInfo);
+ resolve();
+ },
+ fail: function (err) {
+ payFail(payOrderInfo, err.errMsg);
+ },
+ }); */
+ });
+};
diff --git a/miniprogram/tcb-shop/pages/order/order-detail/index.js b/miniprogram/tcb-shop/pages/order/order-detail/index.js
new file mode 100644
index 0000000..5ca9433
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-detail/index.js
@@ -0,0 +1,315 @@
+import Toast from 'tdesign-miniprogram/toast/index';
+import dayjs from 'dayjs';
+import { orderListShouldFresh } from '../../../utils/orderListFresh';
+import { OrderStatus } from '../config';
+import { getAllOrderItemsOfAnOrder } from '../../../services/order/orderItem';
+import { getOrder, orderStatusToName, ORDER_STATUS, updateOrderDeliveryInfo } from '../../../services/order/order';
+import { fetchBusinessTime } from '../../../services/order/orderDetail';
+import { getAddressPromise } from '../../usercenter/address/list/util';
+import { OPERATION_TYPE } from '../../../utils/orderOperation';
+
+Page({
+ data: {
+ pageLoading: true,
+ order: {}, // 后台返回的原始数据
+ _order: {}, // 内部使用和提供给 order-card 的数据
+ storeDetail: {},
+ countDownTime: null,
+ addressEditable: false,
+ backRefresh: false, // 用于接收其他页面back时的状态
+ formatCreateTime: '', //格式化订单创建时间
+ logisticsNodes: [],
+ /** 订单评论状态 */
+ orderHasCommented: true,
+ },
+
+ toast(message) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message,
+ });
+ },
+
+ onLoad({ orderId }) {
+ if (orderId == null) {
+ toast('异常订单号');
+ setTimeout(() => {
+ wx.navigateBack();
+ }, 1000);
+ }
+ this.orderId = orderId;
+ this.init();
+ this.navbar = this.selectComponent('#navbar');
+ this.pullDownRefresh = this.selectComponent('#wr-pull-down-refresh');
+ },
+
+ onShow() {
+ // 当从其他页面返回,并且 backRefresh 被置为 true 时,刷新数据
+ if (!this.data.backRefresh) return;
+ this.onRefresh();
+ this.setData({ backRefresh: false });
+ },
+
+ onPageScroll(e) {
+ this.pullDownRefresh && this.pullDownRefresh.onPageScroll(e);
+ },
+
+ onImgError(e) {
+ if (e.detail) {
+ console.error('img 加载失败');
+ }
+ },
+
+ // 页面初始化,会展示pageLoading
+ init() {
+ this.setData({ pageLoading: true });
+ this.getStoreDetail();
+ this.getDetail()
+ .then(() => {
+ this.setData({ pageLoading: false });
+ })
+ .catch((e) => {
+ console.error(e);
+ });
+ },
+
+ // 页面刷新,展示下拉刷新
+ onRefresh() {
+ this.init();
+ // 如果上一页为订单列表,通知其刷新数据
+ const pages = getCurrentPages();
+ const lastPage = pages[pages.length - 2];
+ if (lastPage) {
+ lastPage.data.backRefresh = true;
+ }
+ },
+
+ // 页面刷新,展示下拉刷新
+ onPullDownRefresh_(e) {
+ const { callback } = e.detail;
+ return this.getDetail().then(() => callback && callback());
+ },
+
+ async getDetail() {
+ const orderId = this.orderId;
+ const [order, orderItems] = await Promise.all([getOrder(orderId), getAllOrderItemsOfAnOrder({ orderId })]);
+ order.orderItems = orderItems;
+ order.totalPrice = orderItems.reduce((acc, cur) => acc + cur.count * cur.sku.price, 0);
+ order.statusDesc = orderStatusToName(order.status);
+ order.isPaid = order.status !== ORDER_STATUS.TO_PAY;
+ order.createdTimeString = dayjs(new Date(order.createdAt)).format('YYYY-MM-DD HH:mm:ss');
+
+ this.setData({ order, addressEditable: order.statusDesc === '待付款' });
+ // console.log('ha', order);
+ // return fetchOrderDetail(params).then((res) => {
+ // const order = res.data;
+ // const _order = {
+ // id: order.orderId,
+ // orderNo: order.orderNo,
+ // parentOrderNo: order.parentOrderNo,
+ // storeId: order.storeId,
+ // storeName: order.storeName,
+ // status: order.orderStatus,
+ // statusDesc: order.orderStatusName,
+ // amount: order.paymentAmount,
+ // totalAmount: order.goodsAmountApp,
+ // logisticsNo: order.logisticsVO.logisticsNo,
+ // goodsList: (order.orderItemVOs || []).map((goods) =>
+ // Object.assign({}, goods, {
+ // id: goods.id,
+ // thumb: goods.goodsPictureUrl,
+ // title: goods.goodsName,
+ // skuId: goods.skuId,
+ // spuId: goods.spuId,
+ // specs: (goods.specifications || []).map((s) => s.specValue),
+ // price: goods.tagPrice ? goods.tagPrice : goods.actualPrice, // 商品销售单价, 优先取限时活动价
+ // num: goods.buyQuantity,
+ // titlePrefixTags: goods.tagText ? [{ text: goods.tagText }] : [],
+ // buttons: goods.buttonVOs || [],
+ // }),
+ // ),
+ // buttons: order.buttonVOs || [],
+ // createTime: order.createTime,
+ // receiverAddress: this.composeAddress(order),
+ // groupInfoVo: order.groupInfoVo,
+ // };
+ // this.setData({
+ // order,
+ // _order,
+ // formatCreateTime: formatTime(parseFloat(`${order.createTime}`), 'YYYY-MM-DD HH:mm'), // 格式化订单创建时间
+ // countDownTime: this.computeCountDownTime(order),
+ // addressEditable:
+ // [OrderStatus.PENDING_PAYMENT, OrderStatus.PENDING_DELIVERY].includes(order.orderStatus) &&
+ // order.orderSubStatus !== -1, // 订单正在取消审核时不允许修改地址(但是返回的状态码与待发货一致)
+ // isPaid: !!order.paymentVO.paySuccessTime,
+ // invoiceStatus: this.datermineInvoiceStatus(order),
+ // invoiceDesc: order.invoiceDesc,
+ // invoiceType: order.invoiceVO?.invoiceType === 5 ? '电子普通发票' : '不开发票', //是否开票 0-不开 5-电子发票
+ // logisticsNodes: this.flattenNodes(order.trajectoryVos || []),
+ // });
+ // });
+ },
+
+ datermineInvoiceStatus(order) {
+ // 1-已开票
+ // 2-未开票(可补开)
+ // 3-未开票
+ // 4-门店不支持开票
+ return order.invoiceStatus;
+ },
+
+ // 拼接省市区
+ composeAddress(order) {
+ return [
+ //order.logisticsVO.receiverProvince,
+ order.logisticsVO.receiverCity,
+ order.logisticsVO.receiverCountry,
+ order.logisticsVO.receiverArea,
+ order.logisticsVO.receiverAddress,
+ ]
+ .filter((s) => !!s)
+ .join(' ');
+ },
+
+ getStoreDetail() {
+ fetchBusinessTime().then((res) => {
+ const storeDetail = {
+ storeTel: res.data.telphone,
+ storeBusiness: res.data.businessTime.join('\n'),
+ };
+ this.setData({ storeDetail });
+ });
+ },
+
+ // 仅对待支付状态计算付款倒计时
+ // 返回时间若是大于2020.01.01,说明返回的是关闭时间,否则说明返回的直接就是剩余时间
+ computeCountDownTime(order) {
+ if (order.orderStatus !== OrderStatus.PENDING_PAYMENT) return null;
+ return order.autoCancelTime > 1577808000000 ? order.autoCancelTime - Date.now() : order.autoCancelTime;
+ },
+
+ onCountDownFinish() {
+ //this.setData({ countDownTime: -1 });
+ const { countDownTime, order } = this.data;
+ if (countDownTime > 0 || (order && order.groupInfoVo && order.groupInfoVo.residueTime > 0)) {
+ this.onRefresh();
+ }
+ },
+
+ onGoodsCardTap(e) {
+ const { index } = e.currentTarget.dataset;
+ const goods = this.data.order.orderItemVOs[index];
+ wx.navigateTo({ url: `/pages/goods/details/index?spuId=${goods.spuId}` });
+ },
+
+ async onEditAddressTap() {
+ const deliveryInfoPromise = getAddressPromise();
+ // TODO: check url param
+ wx.navigateTo({
+ url: `/pages/usercenter/address/list/index?selectMode=true`,
+ });
+ try {
+ const deliveryInfo = await deliveryInfoPromise;
+ try {
+ await updateOrderDeliveryInfo({ orderId: this.data.order._id, deliveryInfoId: deliveryInfo._id });
+ this.setData({
+ 'order.delivery_info._id': deliveryInfo._id,
+ 'order.delivery_info.phone': deliveryInfo.phone,
+ 'order.delivery_info.address': deliveryInfo.address,
+ 'order.delivery_info.name': deliveryInfo.name,
+ });
+ } catch (e) {
+ console.error(e);
+ this.toast('更新地址失败');
+ }
+ } catch {}
+ },
+
+ onOrderNumCopy() {
+ wx.setClipboardData({
+ data: this.data.order.orderNo,
+ });
+ },
+
+ onDeliveryNumCopy() {
+ wx.setClipboardData({
+ data: this.data.order.logisticsVO.logisticsNo,
+ });
+ },
+
+ onToInvoice() {
+ wx.navigateTo({
+ url: `/pages/order/invoice/index?orderNo=${this.data._order.orderNo}`,
+ });
+ },
+
+ onSuppleMentInvoice() {
+ wx.navigateTo({
+ url: `/pages/order/receipt/index?orderNo=${this.data._order.orderNo}`,
+ });
+ },
+
+ onDeliveryClick() {
+ const logisticsData = {
+ nodes: this.data.logisticsNodes,
+ company: this.data.order.logisticsVO.logisticsCompanyName,
+ logisticsNo: this.data.order.logisticsVO.logisticsNo,
+ phoneNumber: this.data.order.logisticsVO.logisticsCompanyTel,
+ };
+ wx.navigateTo({
+ url: `/pages/order/delivery-detail/index?data=${encodeURIComponent(JSON.stringify(logisticsData))}`,
+ });
+ },
+
+ /** 跳转订单评价 */
+ navToCommentCreate() {
+ wx.navigateTo({
+ url: `/pages/order/createComment/index?orderNo=${this.orderNo}`,
+ });
+ },
+
+ /** 跳转拼团详情/分享页*/
+ toGrouponDetail() {
+ wx.showToast({ title: '点击了拼团' });
+ },
+
+ clickService() {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '您点击了联系客服',
+ });
+ },
+
+ onOrderInvoiceView() {
+ wx.navigateTo({
+ url: `/pages/order/invoice/index?orderNo=${this.orderNo}`,
+ });
+ },
+
+ onOperation(e) {
+ const type = e?.detail?.type;
+ const success = e?.detail?.success;
+
+ if (type == null) return;
+
+ const resultMessage = success ? '成功' : '失败';
+
+ let operationMessage;
+
+ if (type === OPERATION_TYPE.CANCEL) {
+ operationMessage = '取消订单';
+ } else if (type === OPERATION_TYPE.CONFIRM) {
+ operationMessage = '确认收货';
+ } else {
+ operationMessage = '支付';
+ }
+
+ this.toast(`${operationMessage}${resultMessage}`);
+ orderListShouldFresh();
+ setTimeout(() => {
+ wx.navigateBack();
+ }, 1000);
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/order-detail/index.json b/miniprogram/tcb-shop/pages/order/order-detail/index.json
new file mode 100644
index 0000000..b8bbaec
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-detail/index.json
@@ -0,0 +1,17 @@
+{
+ "navigationBarTitleText": "订单详情",
+ "usingComponents": {
+ "t-pull-down-refresh": "tdesign-miniprogram/pull-down-refresh/pull-down-refresh",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-image": "/components/webp-image/index",
+ "t-count-down": "tdesign-miniprogram/count-down/count-down",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "price": "/components/price/index",
+ "order-card": "../components/order-card/index",
+ "order-goods-card": "../components/order-goods-card/index",
+ "order-button-bar": "../components/order-button-bar/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/order-detail/index.wxml b/miniprogram/tcb-shop/pages/order/order-detail/index.wxml
new file mode 100644
index 0000000..67f1d57
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-detail/index.wxml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+ 商品总额
+
+
+
+ {{order.isPaid ? '实付' : '应付'}}
+
+
+
+
+
+
+ 订单编号
+
+ {{order._id}}
+ 复制
+
+
+
+ 下单时间
+
+ {{order.createdTimeString}}
+
+
+
+
+
+ 联系客服
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/order-detail/index.wxss b/miniprogram/tcb-shop/pages/order/order-detail/index.wxss
new file mode 100644
index 0000000..6bae005
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-detail/index.wxss
@@ -0,0 +1,245 @@
+:host {
+ background-color: #f8f8f8;
+}
+
+.order-detail {
+ width: 100%;
+ box-sizing: border-box;
+ padding: 0rpx 0rpx calc(env(safe-area-inset-bottom) + 144rpx);
+}
+
+.order-detail .count-down {
+ color: #ffffff;
+}
+.order-detail .header {
+ width: 100%;
+ background-color: #ffffff;
+}
+.order-detail .order-detail__header {
+ width: 700rpx;
+ height: 200rpx;
+ border-radius: 24rpx;
+ margin: 0 auto;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background-image: url('https://cdn-we-retail.ym.tencent.com/miniapp/template/order-bg.png');
+ background-repeat: no-repeat;
+ background-size: contain;
+}
+.order-detail .order-detail__header .title,
+.order-detail .order-detail__header .desc {
+ color: #ffffff;
+ overflow: hidden;
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+}
+.order-detail .order-detail__header .title {
+ -webkit-line-clamp: 1;
+ font-size: 44rpx;
+ line-height: 64rpx;
+ margin-bottom: 8rpx;
+ font-weight: bold;
+}
+.order-detail .order-detail__header .desc {
+ -webkit-line-clamp: 2;
+ font-size: 24rpx;
+ line-height: 32rpx;
+}
+.order-detail .order-detail__header .desc .count-down {
+ display: inline;
+}
+.order-detail .order-logistics {
+ box-sizing: border-box;
+ padding: 32rpx;
+ width: 100%;
+ background-color: #ffffff;
+ overflow: hidden;
+ color: #333333;
+ font-size: 32rpx;
+ line-height: 48rpx;
+ display: flex;
+ position: relative;
+}
+
+.order-logistics .logistics-icon {
+ width: 40rpx;
+ height: 40rpx;
+ margin-right: 16rpx;
+ margin-top: 4rpx;
+}
+
+.order-logistics .logistics-content {
+ flex: 1;
+}
+
+.order-logistics .logistics-content .logistics-time {
+ font-size: 28rpx;
+ line-height: 40rpx;
+ color: #999999;
+ margin-top: 12rpx;
+}
+
+.order-logistics .logistics-back {
+ color: #999999;
+ align-self: center;
+}
+
+.order-logistics .edit-text {
+ color: #fa4126;
+ font-size: 26rpx;
+ line-height: 36rpx;
+}
+
+.order-detail .border-bottom {
+ margin: 0 auto;
+ width: 686rpx;
+ scale: 1 0.5;
+ height: 2rpx;
+ background-color: #e5e5e5;
+}
+
+.order-detail .border-bottom-margin {
+ margin: 16rpx auto;
+}
+
+.order-detail .pay-detail {
+ background-color: #ffffff;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.order-detail .padding-inline {
+ padding: 16rpx 32rpx;
+}
+
+.order-detail .pay-detail .pay-item {
+ width: 100%;
+ height: 72rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ font-size: 26rpx;
+ line-height: 36rpx;
+ color: #666666;
+ background-color: #ffffff;
+}
+.order-detail .pay-detail .pay-item .pay-item__right {
+ color: #333333;
+ font-size: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+ max-width: 400rpx;
+}
+.order-detail .pay-detail .pay-item .pay-item__right .pay-remark {
+ display: -webkit-box;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ max-width: 400rpx;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+.order-detail .pay-detail .pay-item .font-bold {
+ font-weight: bold;
+}
+.order-detail .pay-detail .pay-item .primary {
+ color: #fa4126;
+}
+.order-detail .pay-detail .pay-item .max-size {
+ font-size: 36rpx;
+ line-height: 48rpx;
+}
+
+.pay-item .pay-item__right .pay-item__right__copy {
+ width: 80rpx;
+ height: 40rpx;
+ text-align: center;
+ font-size: 24rpx;
+ line-height: 40rpx;
+ color: #333333;
+ position: relative;
+}
+
+.pay-item .pay-item__right .pay-item__right__copy::before {
+ position: absolute;
+ content: '';
+ width: 200%;
+ height: 200%;
+ border-radius: 40rpx;
+ border: 2rpx solid #dddddd;
+ transform: scale(0.5);
+ left: 0;
+ top: 0;
+ transform-origin: left top;
+}
+
+.pay-item .pay-item__right .order-no {
+ color: #333333;
+ font-size: 26rpx;
+ line-height: 40rpx;
+ padding-right: 16rpx;
+}
+
+.pay-item .pay-item__right .normal-color {
+ color: #333333;
+}
+
+.order-detail .pay-detail .pay-service {
+ width: 100%;
+ height: 72rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 32rpx;
+ line-height: 36rpx;
+ color: #333333;
+ background-color: #ffffff;
+}
+
+.bottom-bar {
+ position: fixed;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ z-index: 10;
+ background: #fff;
+ height: 112rpx;
+ width: 686rpx;
+ padding: 0rpx 32rpx env(safe-area-inset-bottom);
+ display: flex;
+ align-items: center;
+}
+
+.bottom-bar::before {
+ position: absolute;
+ content: '';
+ width: 200%;
+ height: 200%;
+ border-top: 2rpx solid #dddddd;
+ transform: scale(0.5);
+ left: 0;
+ top: 0;
+ transform-origin: left top;
+}
+
+.goods-button-bar {
+ height: 112rpx;
+ width: 686rpx;
+ margin-bottom: 16rpx;
+}
+
+.t-class-indicator {
+ color: #b9b9b9 !important;
+}
+
+.add-notes__confirm {
+ color: #fa4126 !important;
+}
+
+.t-button {
+ --td-button-default-color: #000;
+ --td-button-primary-text-color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/order/order-list/index.js b/miniprogram/tcb-shop/pages/order/order-list/index.js
new file mode 100644
index 0000000..31cbb1c
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-list/index.js
@@ -0,0 +1,186 @@
+import Toast from 'tdesign-miniprogram/toast/index';
+import { ORDER_STATUS, listOrder, orderStatusToName } from '../../../services/order/order';
+import { getAllOrderItemsOfAnOrder } from '../../../services/order/orderItem';
+import { LIST_LOADING_STATUS } from '../../../utils/listLoading';
+import { getCloudImageTempUrl } from '../../../utils/cloudImageHandler';
+import { OPERATION_TYPE } from '../../../utils/orderOperation';
+import { shouldFresh, orderListFinishFresh } from '../../../utils/orderListFresh';
+
+const ORDER_STATUS_ALL = '0';
+
+Page({
+ page: {
+ size: 5,
+ num: 1,
+ },
+
+ data: {
+ tabs: [
+ { key: ORDER_STATUS_ALL, text: '全部', total: 0 },
+ { key: ORDER_STATUS.TO_PAY, text: '待付款', total: 0 },
+ { key: ORDER_STATUS.TO_SEND, text: '待发货', total: 0 },
+ { key: ORDER_STATUS.TO_RECEIVE, text: '待收货', total: 0 },
+ { key: ORDER_STATUS.FINISHED, text: '已完成', total: 0 },
+ ],
+ curTab: ORDER_STATUS_ALL,
+ orderList: [],
+ listLoading: LIST_LOADING_STATUS.READY,
+ emptyImg: 'https://cdn-we-retail.ym.tencent.com/miniapp/order/empty-order-list.png',
+ backRefresh: false,
+ status: ORDER_STATUS_ALL,
+ pullDownRefreshing: false,
+ loadingProps: {
+ theme: 'circular',
+ size: '40rpx',
+ },
+ },
+
+ errorToast(message, e) {
+ console.error(message, e);
+ this.toast(message);
+ },
+
+ toast(message) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message,
+ duration: 1000,
+ icon: '',
+ });
+ },
+
+ onLoad(query) {
+ const status = this.data.tabs.find((x) => x.key === query.status)?.key ?? ORDER_STATUS_ALL;
+ this.setData({
+ status,
+ });
+ this.refreshList(status);
+ },
+
+ async pullRefresh() {
+ this.setData({ pullDownRefreshing: true });
+ try {
+ await this.onRefresh();
+ orderListFinishFresh();
+ } catch (e) {
+ this.errorToast('获取订单列表失败', e);
+ } finally {
+ this.setData({ pullDownRefreshing: false });
+ }
+ },
+
+ async onShow() {
+ if (!shouldFresh) return;
+ try {
+ await this.onRefresh();
+ orderListFinishFresh();
+ } catch (e) {
+ this.errorToast('获取订单列表失败', e);
+ }
+ },
+
+ onReachBottom() {
+ if (this.data.listLoading === LIST_LOADING_STATUS.READY) {
+ this.getOrderList(this.data.curTab);
+ }
+ },
+
+ async getOrderItems(order) {
+ const orderId = order._id;
+ try {
+ const orderItems = await getAllOrderItemsOfAnOrder({ orderId });
+
+ const images = orderItems.map((x) => x.sku.image ?? '');
+ (await getCloudImageTempUrl(images)).forEach((image, index) => (orderItems[index].sku.image = image));
+
+ order.orderItems = orderItems;
+ order.totalPrice = orderItems.reduce((acc, cur) => acc + cur.sku.price * cur.count, 0);
+ } catch (e) {
+ this.errorToast('获取订单详情失败', e);
+ }
+ },
+
+ async getOrderList(statusCode = ORDER_STATUS_ALL, reset = false) {
+ this.setData({
+ listLoading: LIST_LOADING_STATUS.LOADING,
+ });
+ try {
+ const { records, total } = await listOrder({
+ pageSize: this.page.size,
+ pageNumber: this.page.num,
+ status: statusCode !== ORDER_STATUS_ALL ? statusCode : undefined,
+ });
+
+ records.forEach((order) => (order.statusDesc = orderStatusToName(order.status)));
+
+ // async get items for each order
+ await Promise.all(records.map((order) => this.getOrderItems(order)));
+
+ const orderList = reset ? records : this.data.orderList.concat(records);
+ const listLoading = orderList.length >= total ? LIST_LOADING_STATUS.NO_MORE : LIST_LOADING_STATUS.READY; // TODO: maybe we should notify user when `length > total`?
+
+ this.setData({ listLoading, orderList });
+ const currentNum = reset ? 1 : this.page.num;
+ this.page.num = currentNum + 1;
+ } catch (e) {
+ console.error('获取订单列表失败', e);
+ this.setData({ listLoading: LIST_LOADING_STATUS.FAILED });
+ }
+ },
+
+ onReTryLoad() {
+ this.getOrderList(this.data.curTab);
+ },
+
+ onTabChange(e) {
+ const { value } = e.detail;
+ this.setData({
+ status: value,
+ });
+ this.refreshList(value);
+ },
+
+ refreshList(status = ORDER_STATUS_ALL) {
+ this.page = {
+ size: this.page.size,
+ num: 1,
+ };
+ this.setData({ curTab: status, orderList: [] });
+
+ return this.getOrderList(status, true);
+ },
+
+ onRefresh() {
+ return this.refreshList(this.data.curTab);
+ },
+
+ onOrderCardTap(e) {
+ const { order } = e.currentTarget.dataset;
+ wx.navigateTo({
+ url: `/pages/order/order-detail/index?orderId=${order._id}`,
+ });
+ },
+
+ onOperation(e) {
+ const type = e?.detail?.type;
+ const success = e?.detail?.success;
+
+ if (type == null) return;
+
+ const resultMessage = success ? '成功' : '失败';
+
+ let operationMessage;
+
+ if (type === OPERATION_TYPE.CANCEL) {
+ operationMessage = '取消订单';
+ } else if (type === OPERATION_TYPE.CONFIRM) {
+ operationMessage = '确认收货';
+ } else {
+ operationMessage = '支付';
+ }
+
+ this.toast(`${operationMessage}${resultMessage}`);
+ this.onRefresh();
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/order-list/index.json b/miniprogram/tcb-shop/pages/order/order-list/index.json
new file mode 100644
index 0000000..eaa089e
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-list/index.json
@@ -0,0 +1,17 @@
+{
+ "navigationBarTitleText": "我的订单",
+ "usingComponents": {
+ "t-tabs": "tdesign-miniprogram/tabs/tabs",
+ "t-tab-panel": "tdesign-miniprogram/tab-panel/tab-panel",
+ "t-empty": "tdesign-miniprogram/empty/empty",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "t-loading": "tdesign-miniprogram/loading/loading",
+ "t-pull-down-refresh": "tdesign-miniprogram/pull-down-refresh/pull-down-refresh",
+ "load-more": "/components/load-more/index",
+ "order-button-bar": "../components/order-button-bar/index",
+ "price": "/components/price/index",
+ "order-card": "../components/order-card/index",
+ "specs-goods-card": "../components/specs-goods-card/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/order-list/index.wxml b/miniprogram/tcb-shop/pages/order/order-list/index.wxml
new file mode 100644
index 0000000..9cd0d09
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-list/index.wxml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+ 订单号
+ {{order._id}}
+
+
+
+
+
+
+ 总价
+
+
+
+
+
+
+
+
+
+
+ 暂无相关订单
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/order-list/index.wxss b/miniprogram/tcb-shop/pages/order/order-list/index.wxss
new file mode 100644
index 0000000..c675b3d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/order-list/index.wxss
@@ -0,0 +1,117 @@
+:host {
+ background-color: #f5f5f5;
+}
+.page-container .tab-bar__placeholder,
+.page-container .tab-bar__inner {
+ height: 88rpx;
+ line-height: 88rpx;
+ background: #fff;
+}
+.page-container .tab-bar__inner {
+ font-size: 26rpx;
+ color: #333333;
+ position: fixed;
+ width: 100vw;
+ top: 0;
+ left: 0;
+}
+.page-container .tab-bar__inner.order-nav .order-nav-item .bottom-line {
+ bottom: 12rpx;
+}
+.tab-bar__inner .t-tabs-is-active {
+ color: #fa4126 !important;
+}
+
+.tab-bar__inner .t-tabs-track {
+ background: #fa4126 !important;
+}
+
+.page-container .tab-bar__active {
+ font-size: 28rpx;
+}
+.page-container .specs-popup .bottom-btn {
+ color: #fa4126;
+ color: var(--color-primary, #fa4126);
+}
+.page-container .specs-popup .bottom-btn::after {
+ border-color: #fa4126;
+ border-color: var(--color-primary, #fa4126);
+}
+.dialog .dialog__button-confirm {
+ color: #fa4126;
+ color: var(--color-primary, #fa4126);
+}
+.list-loading {
+ height: 100rpx;
+}
+.empty-wrapper {
+ height: calc(100vh - 88rpx);
+}
+.btn-bar {
+ margin-top: 20rpx;
+}
+.load-more {
+ margin: 0 24rpx;
+}
+wr-order-goods-card:not(:first-child) .wr-goods-card {
+ margin-top: 40rpx;
+}
+
+.price-total {
+ font-size: 24rpx;
+ line-height: 32rpx;
+ color: #999999;
+ padding-top: 10rpx;
+ width: 100%;
+ display: flex;
+ align-items: baseline;
+ justify-content: flex-end;
+}
+.price-total .bold-price {
+ color: #333333;
+ font-size: 28rpx;
+ line-height: 40rpx;
+ color: #333333;
+}
+.price-total .real-pay {
+ font-size: 36rpx;
+ line-height: 48rpx;
+ color: #fa4126;
+ font-weight: bold;
+}
+
+.t-tabs.t-tabs--top .t-tabs-scroll {
+ border: none !important;
+}
+.t-empty-text {
+ font-size: 28rpx;
+ color: #999;
+}
+
+.page-container .order-number {
+ color: #666666;
+ font-size: 28rpx;
+}
+.t-class-indicator {
+ color: #b9b9b9 !important;
+}
+.tab-bar .tab-bar__active {
+ color: #333333 !important;
+}
+
+.tab-bar .t-tabs-track {
+ background: #333333 !important;
+}
+
+.t-button {
+ --td-button-default-color: #000;
+ --td-button-primary-text-color: #fa4126;
+}
+
+.order-card-loading-container {
+ text-align: center;
+}
+
+.t-loading__spinner--circular .t-loading__circular {
+ color: #b9b9b9 !important;
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/pay-result/index.js b/miniprogram/tcb-shop/pages/order/pay-result/index.js
new file mode 100644
index 0000000..bd6dad6
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/pay-result/index.js
@@ -0,0 +1,47 @@
+/*
+ * @Author: rileycai
+ * @Date: 2022-03-14 21:18:07
+ * @LastEditTime: 2022-03-22 21:17:16
+ * @LastEditors: rileycai
+ * @Description:
+ * @FilePath: /tdesign-miniprogram-starter/pages/order/pay-result/index.js
+ */
+Page({
+ data: {
+ totalPaid: 0,
+ orderNo: '',
+ groupId: '',
+ groupon: null,
+ spu: null,
+ adUrl: '',
+ },
+
+ onLoad(options) {
+ const { totalPaid = 0, orderNo = '', groupId = '' } = options;
+ this.setData({
+ totalPaid,
+ orderNo,
+ groupId,
+ });
+ },
+
+ onTapReturn(e) {
+ const target = e.currentTarget.dataset.type;
+ const { orderNo } = this.data;
+ if (target === 'home') {
+ wx.switchTab({ url: '/pages/home/home' });
+ } else if (target === 'orderList') {
+ wx.navigateTo({
+ url: `/pages/order/order-list/index?orderNo=${orderNo}`,
+ });
+ } else if (target === 'order') {
+ wx.navigateTo({
+ url: `/pages/order/order-detail/index?orderNo=${orderNo}`,
+ });
+ }
+ },
+
+ navBackHandle() {
+ wx.navigateBack();
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/pay-result/index.json b/miniprogram/tcb-shop/pages/order/pay-result/index.json
new file mode 100644
index 0000000..f572818
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/pay-result/index.json
@@ -0,0 +1,9 @@
+{
+ "navigationBarTitleText": "支付结果",
+ "navigationStyle": "custom",
+ "usingComponents": {
+ "t-navbar": "tdesign-miniprogram/navbar/navbar",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "price": "/components/price/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/order/pay-result/index.wxml b/miniprogram/tcb-shop/pages/order/pay-result/index.wxml
new file mode 100644
index 0000000..3153fe6
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/pay-result/index.wxml
@@ -0,0 +1,22 @@
+
+
+
+
+ 支付成功
+
+
+ 微信支付:
+
+
+
+ 查看订单
+ 返回首页
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/pay-result/index.wxss b/miniprogram/tcb-shop/pages/order/pay-result/index.wxss
new file mode 100644
index 0000000..abe1b25
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/pay-result/index.wxss
@@ -0,0 +1,54 @@
+.pay-result {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 100%;
+}
+
+.pay-result .pay-status {
+ margin-top: 100rpx;
+ font-size: 48rpx;
+ line-height: 72rpx;
+ font-weight: bold;
+ color: #333333;
+ display: flex;
+ align-items: center;
+}
+.pay-result .pay-status text {
+ padding-left: 12rpx;
+}
+.pay-result .pay-money {
+ color: #666666;
+ font-size: 28rpx;
+ line-height: 48rpx;
+ margin-top: 28rpx;
+ display: flex;
+ align-items: baseline;
+}
+
+.pay-result .pay-money .pay-money__price {
+ font-size: 36rpx;
+ line-height: 48rpx;
+ color: #fa4126;
+}
+.pay-result .btn-wrapper {
+ margin-top: 48rpx;
+ padding: 12rpx 32rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.pay-result .btn-wrapper .status-btn {
+ height: 88rpx;
+ width: 334rpx;
+ border-radius: 44rpx;
+ border: 2rpx solid #fa4126;
+ color: #fa4126;
+ font-size: 28rpx;
+ font-weight: bold;
+ line-height: 88rpx;
+ text-align: center;
+}
diff --git a/miniprogram/tcb-shop/pages/order/receipt/index.js b/miniprogram/tcb-shop/pages/order/receipt/index.js
new file mode 100644
index 0000000..3d03225
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/receipt/index.js
@@ -0,0 +1,182 @@
+/* eslint-disable no-nested-ternary */
+import Dialog from 'tdesign-miniprogram/dialog/index';
+import Toast from 'tdesign-miniprogram/toast/index';
+import { dispatchSupplementInvoice } from '../../../services/order/orderConfirm';
+
+const invoiceJson = {
+ info: [
+ '1.根据当地税务局的要求,开具有效的企业发票需填写税务局登记证号。开具个人发票不需要填写纳税人识别码。 ',
+ '2.电子普通发票: 电子普通发票是税局认可的有效首付款凭证,其法律效力、基本用途及使用规定同纸质发票,如需纸质发票可自行下载打印。 ',
+ '3.增值税专用发票: 增值税发票暂时不可开,可查看《开局增值税发票》或致电400-633-6868。',
+ ],
+ codeTitle: [
+ '1.什么是纳税人识别号/统一社会信用代码? 纳税人识别号,一律由15位、17位、18或者20位码(字符型)组成,其中:企业、事业单位等组织机构纳税人,以国家质量监督检验检疫总局编制的9位码(其中区分主码位与校检位之间的“—”符省略不打印)并在其“纳税人识别号”。国家税务总局下达的纳税人代码为15位,其中:1—2位为省、市代码,3—6位为地区代码,7—8位为经济性质代码,9—10位行业代码,11—15位为各地区自设的顺序码。',
+ '2.入户获取/知晓纳税人识别号/统一社会信用代码? 纳税人识别号是税务登记证上的号码,通常简称为“税号”,每个企业的纳税人识别号都是唯一的。这个属于每个人自己且终身不变的数字代码很可能成为我们的第二张“身份证”。 ',
+ ],
+};
+
+Page({
+ orderNo: '',
+ data: {
+ receiptIndex: 0,
+ addressTagsIndex: 0,
+ goodsClassesIndex: 0,
+ dialogShow: false,
+ codeShow: false,
+ receipts: [
+ { title: '不开发票', id: 0, name: 'receipt' },
+ { title: '电子发票', id: 1, name: 'receipt' },
+ ],
+ addressTags: [
+ { title: '个人', id: 0, name: 'addressTags', type: 1 },
+ { title: '公司', id: 1, name: 'addressTags', type: 2 },
+ ],
+ goodsClasses: [
+ { title: '商品明细', id: 0, name: 'goodsClasses' },
+ { title: '商品类别', id: 1, name: 'goodsClasses' },
+ ],
+ name: '',
+ componentName: '',
+ code: '',
+ phone: '',
+ email: '',
+ invoiceInfo: invoiceJson,
+ },
+ onLoad(query) {
+ const { orderNo, invoiceData } = query;
+ const tempData = JSON.parse(invoiceData || '{}');
+ const invoice = {
+ receiptIndex: tempData.invoiceType === 5 ? 1 : 0,
+ name: tempData.buyerName || '',
+ email: tempData.email || '',
+ phone: tempData.buyerPhone || '',
+ addressTagsIndex: tempData.titleType === 2 ? 1 : 0,
+ goodsClassesIndex: tempData.contentType === 2 ? 1 : 0,
+ code: tempData.buyerTaxNo || '',
+ componentName: tempData.titleType === 2 ? tempData.buyerName : '',
+ };
+ this.orderNo = orderNo;
+ this.setData({ ...invoice });
+ },
+ onLabels(e) {
+ const { item } = e.currentTarget.dataset;
+ const nameIndex = `${item.name}Index`;
+ this.setData({ [nameIndex]: item.id });
+ },
+ onInput(e) {
+ const { addressTagsIndex } = this.data;
+ const { item } = e.currentTarget.dataset;
+ const { value } = e.detail;
+ const key =
+ item === 'name'
+ ? addressTagsIndex === 0
+ ? 'name'
+ : 'componentName'
+ : item === 'code'
+ ? addressTagsIndex === 0
+ ? 'phone'
+ : 'code'
+ : 'email';
+ this.setData({ [key]: value });
+ },
+ onSure() {
+ const result = this.checkSure();
+ if (!result) {
+ Dialog.alert({
+ title: '请填写发票信息',
+ content: '',
+ confirmBtn: '确认',
+ });
+ return;
+ }
+ const {
+ receiptIndex,
+ addressTagsIndex,
+ receipts,
+ addressTags,
+ name,
+ componentName,
+ code,
+ phone,
+ email,
+ goodsClassesIndex,
+ } = this.data;
+
+ const data = {
+ buyerName: addressTagsIndex === 0 ? name : componentName,
+ buyerTaxNo: code,
+ buyerPhone: phone,
+ email,
+ titleType: addressTags[addressTagsIndex].type,
+ contentType: goodsClassesIndex === 0 ? 1 : 2,
+ invoiceType: receiptIndex === 1 ? 5 : 0,
+ };
+ if (this.orderNo) {
+ if (this.submitting) return;
+ const params = {
+ parameter: {
+ orderNo: this.orderNo,
+ invoiceVO: data,
+ },
+ };
+ this.submitting = true;
+ dispatchSupplementInvoice(params)
+ .then(() => {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '保存成功',
+ duration: 2000,
+ icon: '',
+ });
+ setTimeout(() => {
+ this.submitting = false;
+ wx.navigateBack({ delta: 1 });
+ }, 1000);
+ })
+ .catch((err) => {
+ this.submitting = false;
+ console.error(err);
+ });
+ } else {
+ Object.assign(data, {
+ receipts: receipts[receiptIndex],
+ addressTags: addressTags[addressTagsIndex],
+ });
+ wx.setStorageSync('invoiceData', data);
+ wx.navigateBack({ delta: 1 });
+ }
+ },
+ checkSure() {
+ const { name, componentName, code, phone, email, addressTagsIndex, receiptIndex } = this.data;
+ if (receiptIndex === 0) {
+ return true;
+ }
+ if (addressTagsIndex === 0) {
+ if (!name.length || !phone.length) {
+ return false;
+ }
+ } else if (addressTagsIndex === 1) {
+ if (!componentName.length || !code.length) {
+ return false;
+ }
+ }
+ if (!email.length) {
+ return false;
+ }
+ return true;
+ },
+ onDialogTap() {
+ const { dialogShow } = this.data;
+ this.setData({
+ dialogShow: !dialogShow,
+ codeShow: false,
+ });
+ },
+ onKnoeCode() {
+ this.setData({
+ dialogShow: !this.data.dialogShow,
+ codeShow: true,
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/order/receipt/index.json b/miniprogram/tcb-shop/pages/order/receipt/index.json
new file mode 100644
index 0000000..5996047
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/receipt/index.json
@@ -0,0 +1,11 @@
+{
+ "navigationBarTitleText": "发票",
+ "usingComponents": {
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-input": "tdesign-miniprogram/input/input",
+ "t-button": "tdesign-miniprogram/button/button"
+ }
+}
diff --git a/miniprogram/tcb-shop/pages/order/receipt/index.wxml b/miniprogram/tcb-shop/pages/order/receipt/index.wxml
new file mode 100644
index 0000000..a79397b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/receipt/index.wxml
@@ -0,0 +1,135 @@
+
+
+
+
+
+ {{item.title}}
+
+
+
+
+
+
+
+
+ {{tag.title}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{good.title}}
+
+
+
+ 发票内容将显示详细商品名称与价格信息,发票金额为实际支付金额,不包含优惠等扣减金额
+
+
+ 发票须知
+
+
+
+
+
+
+
+ {{item}}
+
+
+ {{item}}
+
+
+
+
+
+
+
+
+ 确定
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/order/receipt/index.wxss b/miniprogram/tcb-shop/pages/order/receipt/index.wxss
new file mode 100644
index 0000000..c12d654
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/order/receipt/index.wxss
@@ -0,0 +1,220 @@
+@import '../../../style/theme.wxss';
+
+.receipt {
+ height: 100vh;
+ background: #f5f5f5;
+ position: relative;
+ padding-top: 20rpx;
+
+ --td-input-vertical-padding: 0;
+}
+
+.receipt-cell .t-cell__title {
+ width: 144rpx;
+ padding-right: 32rpx;
+ flex: none !important;
+}
+
+.receipt .t-input__wrapper {
+ margin: 0 !important;
+}
+.srcoll-view-wrap {
+ margin-top: 20rpx;
+}
+.receipt .flex {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+.receipt .head-title {
+ color: #333;
+ font-size: 30rpx;
+ font-weight: bold;
+}
+.receipt .btn-wrap {
+ display: flex;
+}
+.receipt .btn-wrap .btn {
+ width: 128rpx;
+ background: #f5f5f5;
+ font-size: 24rpx;
+ color: #333;
+ margin-right: 22rpx;
+ text-align: center;
+ border-radius: 8rpx;
+ position: relative;
+ border: 2rpx solid #f5f5f5;
+}
+.receipt .btn-wrap .active-btn {
+ background-color: transparent;
+ border-color: #fa4126;
+ color: #fa4126;
+}
+.receipt .title {
+ width: 100%;
+ background-color: #fff;
+ margin-bottom: 20rpx;
+}
+
+.receipt .receipt-label {
+ display: flex;
+}
+.receipt .receipt-label .btn {
+ width: 128rpx;
+ background: #f5f5f5;
+ font-size: 24rpx;
+ color: #333;
+ margin-left: 22rpx;
+ text-align: center;
+ border-radius: 8rpx;
+ border: 2rpx solid #f5f5f5;
+}
+.receipt .receipt-label .active-btn {
+ background-color: transparent;
+ border-color: #fa4126;
+ color: #fa4126;
+}
+.receipt .receipt-label .wr-cell__title {
+ font-size: 30rpx;
+ color: #333;
+ font-weight: bold;
+}
+.receipt .receipt-content {
+ background: #fff;
+ margin-top: 20rpx;
+}
+.receipt .receipt-content .addressTags {
+ padding: 0 30rpx;
+ height: 100rpx;
+}
+.receipt .receipt-content .addressTags .btn-wrap {
+ display: flex;
+}
+.receipt .receipt-content .line {
+ width: 720rpx;
+ margin-left: 30rpx;
+ background-color: #e6e6e6;
+ height: 1rpx;
+}
+.receipt .receipt-content .receipt-input {
+ display: flex;
+ padding: 0 30rpx;
+ align-items: center;
+ height: 100rpx;
+ color: #666;
+}
+.receipt .receipt-content .receipt-input .title {
+ color: #333;
+ display: inline-block;
+ width: 140rpx;
+ margin-right: 30rpx;
+ font-size: 30rpx;
+ font-weight: bold;
+}
+.input-com {
+ display: inline-block;
+ flex: 1;
+ font-size: 30rpx;
+ font-weight: 400;
+ line-height: 30rpx;
+ padding: 0 !important;
+ color: #666;
+}
+.input-com::after {
+ border: none !important;
+}
+
+.receipt .receipt-content .receipt-input .wr-icon {
+ font-size: 28rpx !important;
+ margin-left: 20rpx;
+}
+.receipt .receipt-info {
+ background: #fff;
+ margin-top: 20rpx;
+}
+.receipt .receipt-info .info-con {
+ padding: 0 30rpx;
+ height: 100rpx;
+}
+.receipt .receipt-info .title {
+ font-size: 24rpx;
+ color: #999999;
+ line-height: 36rpx;
+ padding: 0 30rpx 20rpx;
+ box-sizing: border-box;
+}
+.receipt .receipt-know {
+ display: flex;
+ align-items: center;
+ font-size: 26rpx;
+ font-weight: 400;
+ color: #999999;
+ padding: 20rpx 30rpx;
+ line-height: 26rpx;
+}
+.receipt .receipt-know .icon {
+ margin-left: 16rpx;
+ font-size: 26rpx;
+}
+.receipt .dialog-receipt .dialog__message {
+ padding: 0;
+}
+.receipt .dialog-receipt .dialog-info {
+ max-height: 622rpx;
+}
+.receipt .dialog-receipt .info-wrap {
+ padding: 0 18rpx;
+}
+.receipt .dialog-receipt .info .title {
+ display: inline-block;
+ font-size: 28rpx;
+ font-weight: 400;
+ color: #999;
+ line-height: 40rpx;
+ margin-bottom: 40rpx;
+ text-align: left;
+}
+.receipt .receipt-btn {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 100;
+ background: #fff;
+ width: 100%;
+ padding: 0 20rpx;
+ box-sizing: border-box;
+ padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+}
+.receipt .receipt-btn .receipt-btn-con {
+ margin-top: 20rpx;
+ display: inline-block;
+ width: 100%;
+ line-height: 80rpx;
+ background: #fa4126;
+ text-align: center;
+ color: #fff;
+ border-radius: 48rpx;
+}
+
+.cell-left {
+ margin-right: 0 !important;
+}
+
+.cell-right {
+ display: flex;
+ justify-content: flex-start;
+ width: 480rpx;
+}
+
+.addressTagsIndex-cell {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+}
+
+.t-button {
+ --td-button-default-color: #000;
+ --td-button-primary-text-color: #fa4126;
+}
diff --git a/miniprogram/tcb-shop/pages/promotion-detail/index.js b/miniprogram/tcb-shop/pages/promotion-detail/index.js
new file mode 100644
index 0000000..dd6614e
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/promotion-detail/index.js
@@ -0,0 +1,57 @@
+import Toast from 'tdesign-miniprogram/toast/index';
+import { fetchPromotion } from '../../services/promotion/detail';
+
+Page({
+ data: {
+ list: [],
+ banner: '',
+ time: 0,
+ showBannerDesc: false,
+ statusTag: '',
+ },
+
+ onLoad(query) {
+ const promotionID = parseInt(query.promotion_id);
+ this.getGoodsList(promotionID);
+ },
+
+ getGoodsList(promotionID) {
+ fetchPromotion(promotionID).then(
+ ({ list, banner, time, showBannerDesc, statusTag }) => {
+ const goods = list.map((item) => ({
+ ...item,
+ tags: item.tags.map((v) => v.title),
+ }));
+ this.setData({
+ list: goods,
+ banner,
+ time,
+ showBannerDesc,
+ statusTag,
+ });
+ },
+ );
+ },
+
+ goodClickHandle(e) {
+ const { index } = e.detail;
+ const { spuId } = this.data.list[index];
+ wx.navigateTo({ url: `/pages/goods/details/index?spuId=${spuId}` });
+ },
+
+ cardClickHandle() {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '点击加购',
+ });
+ },
+
+ bannerClickHandle() {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '点击规则详情',
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/promotion-detail/index.json b/miniprogram/tcb-shop/pages/promotion-detail/index.json
new file mode 100644
index 0000000..2eb4145
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/promotion-detail/index.json
@@ -0,0 +1,10 @@
+{
+ "navigationBarTitleText": "营销详情",
+ "usingComponents": {
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-image": "/components/webp-image/index",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "count-down": "tdesign-miniprogram/count-down/count-down",
+ "goods-list": "/components/goods-list/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/promotion-detail/index.wxml b/miniprogram/tcb-shop/pages/promotion-detail/index.wxml
new file mode 100644
index 0000000..be5b5c8
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/promotion-detail/index.wxml
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+ 已结束
+ 活动已结束
+
+
+
+ 未开始
+
+ 距结束仅剩
+
+
+
+ 规则详情
+
+
+
+
+
+
+
+
+
+
+ 已结束
+ 活动已结束
+
+
+
+ 未开始
+
+ 距结束仅剩
+
+
+
+ 规则详情
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/promotion-detail/index.wxss b/miniprogram/tcb-shop/pages/promotion-detail/index.wxss
new file mode 100644
index 0000000..2151b7a
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/promotion-detail/index.wxss
@@ -0,0 +1,111 @@
+.promotion-detail-container .wrap {
+ display: block;
+ padding: 0 24rpx;
+ background: linear-gradient(#fff, #f5f5f5);
+}
+
+.promotion-detail-container .t-class-promotion-head {
+ width: 702rpx;
+ height: 160rpx;
+ border-radius: 8rpx;
+}
+
+.promotion-detail-container .wrap .count-down-wrap {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ align-items: baseline;
+ line-height: 34rpx;
+}
+
+.promotion-detail-container .wrap .count-down-wrap.in-banner-count-down-wrap {
+ position: absolute;
+ bottom: 32rpx;
+ left: 32rpx;
+ right: 32rpx;
+}
+
+.promotion-detail-container .wrap .count-down-wrap .status-tag {
+ height: 32rpx;
+ line-height: 32rpx;
+ font-size: 20rpx;
+ margin-right: 12rpx;
+ border-radius: 16rpx;
+ padding: 0 12rpx;
+}
+
+.promotion-detail-container .wrap .count-down-wrap .status-tag.before {
+ color: #fff;
+ background-color: #ff9853;
+}
+
+.promotion-detail-container .wrap .count-down-wrap .status-tag.finish {
+ color: #fff;
+ background-color: #ccc;
+}
+
+.promotion-detail-container .wrap .count-down-wrap .count-down-label {
+ color: #666;
+ font-size: 24rpx;
+ margin-right: 0.5em;
+}
+
+.promotion-detail-container .wrap .count-down-wrap .detail-entry {
+ margin-left: auto;
+ height: 40rpx;
+}
+
+.promotion-detail-container .wrap .count-down-wrap .detail-entry-label {
+ color: #fff;
+ font-size: 24rpx;
+ margin-right: 12rpx;
+}
+
+.promotion-detail-container
+ .wrap
+ .count-down-wrap.after-banner-count-down-wrap {
+ padding: 10rpx;
+}
+
+.promotion-detail-container
+ .wrap
+ .count-down-wrap.after-banner-count-down-wrap
+ .detail-entry {
+ display: flex;
+ align-items: center;
+}
+
+.promotion-detail-container
+ .wrap
+ .count-down-wrap.after-banner-count-down-wrap
+ .detail-entry-label {
+ color: #999;
+ margin-right: 0;
+}
+
+.promotion-detail-container .wrap .gl-empty-wrap {
+ margin-top: 180rpx;
+}
+
+.promotion-detail-container .wrap .gl-empty-img {
+ width: 240rpx;
+ height: 240rpx;
+ display: block;
+ margin: 0 auto;
+}
+
+.promotion-detail-container .wrap .gl-empty-label {
+ font-size: 28rpx;
+ color: #999;
+ margin-top: 40rpx;
+ text-align: center;
+}
+
+.promotion-detail-container .goods-list-container {
+ background: #f5f5f5 !important;
+}
+
+.promotion-detail-container .promotion-goods-list {
+ padding: 20rpx 24rpx;
+ background-color: #f5f5f5;
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/address/edit/index.js b/miniprogram/tcb-shop/pages/usercenter/address/edit/index.js
new file mode 100644
index 0000000..0e0acfc
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/address/edit/index.js
@@ -0,0 +1,141 @@
+import Toast from 'tdesign-miniprogram/toast/index';
+import { createAddress, updateAddress } from '../../../../services/address/address';
+import { addressListShouldFresh } from '../../../../utils/addressListFresh';
+
+const innerPhoneReg = '^1(?:3\\d|4[4-9]|5[0-35-9]|6[67]|7[0-8]|8\\d|9\\d)\\d{8}$';
+const innerNameReg = '^[a-zA-Z\\d\\u4e00-\\u9fa5]+$';
+
+Page({
+ options: {
+ multipleSlots: true,
+ },
+ externalClasses: ['theme-wrapper-class'],
+ data: {
+ detailAddress: '',
+ name: '',
+ phone: '',
+ addressId: null,
+
+ loading: false,
+ },
+ setLoading() {
+ this.setData({ loading: true });
+ },
+ unsetLoading() {
+ this.setData({ loading: false });
+ },
+ onLoad(options) {
+ const { name, address, _id, phone } = options;
+ if (![name, address, _id, phone].every((x) => typeof x === 'string')) return;
+
+ this.setData({
+ name,
+ detailAddress: address,
+ addressId: _id,
+ phone,
+ });
+ },
+ onInputValue(event) {
+ const {
+ detail: { value },
+ target: {
+ dataset: { item },
+ },
+ } = event;
+ this.setData({ [item]: value });
+ },
+ onCheckDefaultAddress({ detail }) {
+ const { value } = detail;
+ this.setData({
+ 'locationState.isDefault': value,
+ });
+ },
+ onVerifyInputLegal() {
+ const { name, phone, detailAddress } = this.data;
+ const prefixPhoneReg = String(this.properties.phoneReg || innerPhoneReg);
+ const prefixNameReg = String(this.properties.nameReg || innerNameReg);
+ const nameRegExp = new RegExp(prefixNameReg);
+ const phoneRegExp = new RegExp(prefixPhoneReg);
+
+ if (!name || !name.trim()) {
+ return {
+ isLegal: false,
+ tips: '请填写收货人',
+ };
+ }
+ if (!nameRegExp.test(name)) {
+ return {
+ isLegal: false,
+ tips: '收货人仅支持输入中文、英文(区分大小写)、数字',
+ };
+ }
+ if (!phone || !phone.trim()) {
+ return {
+ isLegal: false,
+ tips: '请填写手机号',
+ };
+ }
+ if (!phoneRegExp.test(phone)) {
+ return {
+ isLegal: false,
+ tips: '请填写正确的手机号',
+ };
+ }
+ if (!detailAddress || !detailAddress.trim()) {
+ return {
+ isLegal: false,
+ tips: '请完善详细地址',
+ };
+ }
+ if (detailAddress && detailAddress.trim().length > 50) {
+ return {
+ isLegal: false,
+ tips: '详细地址不能超过50个字符',
+ };
+ }
+ return {
+ isLegal: true,
+ tips: '添加成功',
+ };
+ },
+ toast(message) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message,
+ icon: '',
+ duration: 1000,
+ });
+ },
+ async formSubmit() {
+ const { isLegal, tips } = this.onVerifyInputLegal();
+
+ if (isLegal) {
+ const { detailAddress, name, phone, addressId } = this.data;
+ this.setLoading();
+
+ let action, failedMessage;
+ if (typeof addressId === 'string') {
+ console.log('to update');
+ action = () => updateAddress({ name, address: detailAddress, phone, _id: addressId });
+ failedMessage = '修改地址失败,请稍候重试';
+ } else {
+ console.log('to create');
+ action = () => createAddress({ name, phone, address: detailAddress });
+ failedMessage = '添加地址失败,请稍候重试';
+ }
+
+ try {
+ await action();
+ addressListShouldFresh();
+ wx.navigateBack({ delta: 1 });
+ } catch {
+ this.toast(failedMessage);
+ } finally {
+ this.unsetLoading();
+ }
+ } else {
+ this.toast(tips);
+ }
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/usercenter/address/edit/index.json b/miniprogram/tcb-shop/pages/usercenter/address/edit/index.json
new file mode 100644
index 0000000..e4691da
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/address/edit/index.json
@@ -0,0 +1,16 @@
+{
+ "navigationBarTitleText": "添加新地址",
+ "usingComponents": {
+ "t-textarea": "tdesign-miniprogram/textarea/textarea",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-input": "tdesign-miniprogram/input/input",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "t-switch": "tdesign-miniprogram/switch/switch",
+ "t-cascader": "tdesign-miniprogram/cascader/cascader",
+ "loading-dialog": "../../../../components/loading-dialog/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/usercenter/address/edit/index.wxml b/miniprogram/tcb-shop/pages/usercenter/address/edit/index.wxml
new file mode 100644
index 0000000..f9a38b2
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/address/edit/index.wxml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/usercenter/address/edit/index.wxss b/miniprogram/tcb-shop/pages/usercenter/address/edit/index.wxss
new file mode 100644
index 0000000..f89e06f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/address/edit/index.wxss
@@ -0,0 +1,95 @@
+page {
+ background-color: #f5f5f5;
+}
+page .divider-line {
+ width: 100%;
+ height: 20rpx;
+ background-color: #f5f5f5;
+}
+.address-flex-box {
+ display: flex;
+ flex-wrap: wrap;
+}
+.address-detail {
+ font-size: 30rpx;
+}
+.address-detail-wx-location {
+ background: #fff;
+ padding: 24rpx 32rpx;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+.address-detail-wx-arrow {
+ align-items: flex-end;
+}
+
+.form-cell .t-cell__title {
+ width: 144rpx;
+ padding-right: 32rpx;
+ flex: none !important;
+}
+
+.textarea__wrapper {
+ width: 100%;
+}
+
+.textarea__wrapper .t-textarea {
+ padding: 0 !important;
+}
+
+.form-address .map {
+ font-size: 48rpx !important;
+ margin-left: 20rpx;
+ color: #9d9d9f;
+}
+
+.address__tag {
+ justify-content: flex-start !important;
+}
+
+.form-address .label-list {
+ background: #f5f5f5;
+ color: #333;
+ min-width: 100rpx;
+ margin-right: 32rpx;
+ font-size: 26rpx;
+ border: 2rpx solid transparent;
+ width: auto;
+}
+.form-address .label-list::after {
+ content: none;
+}
+.form-address .active-btn {
+ color: #fa4126;
+ border: 2rpx solid #fa4126;
+ background: rgba(255, 95, 21, 0.04);
+}
+.form-address .active-btn::after {
+ border: 4rpx solid #ff5f15;
+}
+
+.submit {
+ box-sizing: border-box;
+ padding: 64rpx 30rpx 88rpx 30rpx;
+}
+.submit .btn-submit-address {
+ background: #fa4126 !important;
+ color: #fff !important;
+}
+
+.dialog__button-confirm {
+ color: #fa4126 !important;
+}
+
+.form-address .form-content {
+ --td-input-vertical-padding: 0;
+}
+
+.dialog__input {
+ margin-top: 32rpx;
+ border-radius: 8rpx;
+ box-sizing: border-box;
+ --td-input-vertical-padding: 12px;
+ --td-input-bg-color: #f3f3f3;
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/address/edit/util.js b/miniprogram/tcb-shop/pages/usercenter/address/edit/util.js
new file mode 100644
index 0000000..17d619a
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/address/edit/util.js
@@ -0,0 +1,33 @@
+let addressPromise = [];
+
+/** 地址编辑Promise */
+export const getAddressPromise = () => {
+ let resolver;
+ let rejecter;
+ const nextPromise = new Promise((resolve, reject) => {
+ resolver = resolve;
+ rejecter = reject;
+ });
+
+ addressPromise.push({ resolver, rejecter });
+
+ return nextPromise;
+};
+
+/** 用户保存了一个地址 */
+export const resolveAddress = (address) => {
+ const allAddress = [...addressPromise];
+ addressPromise = [];
+
+ console.info('用户保存了一个地址', address);
+
+ allAddress.forEach(({ resolver }) => resolver(address));
+};
+
+/** 取消编辑 */
+export const rejectAddress = () => {
+ const allAddress = [...addressPromise];
+ addressPromise = [];
+
+ allAddress.forEach(({ rejecter }) => rejecter(new Error('cancel')));
+};
diff --git a/miniprogram/tcb-shop/pages/usercenter/address/list/index.js b/miniprogram/tcb-shop/pages/usercenter/address/list/index.js
new file mode 100644
index 0000000..6eaf6f9
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/address/list/index.js
@@ -0,0 +1,102 @@
+/* eslint-disable no-param-reassign */
+import { getAllAddress, deleteAddress } from '../../../../services/address/address';
+import Toast from 'tdesign-miniprogram/toast/index';
+import { resolveAddress, rejectAddress } from './util';
+import { shouldFresh, addressListFinishFresh } from '../../../../utils/addressListFresh';
+import { objectToParamString } from '../../../../utils/util';
+
+Page({
+ data: {
+ addressList: [],
+ deleteID: '',
+ showDeleteConfirm: false,
+ loading: false,
+ },
+
+ setLoading() {
+ this.setData({ loading: true });
+ },
+ unsetLoading() {
+ this.setData({ loading: false });
+ },
+
+ /**
+ * 如果是 true 的话,点击后会选中并返回上一页;否则点击后会进入编辑页
+ */
+ selectMode: false,
+ /** 是否已经选择地址,不置为 true 的话页面离开时会触发取消选择行为 */
+ hasSelect: false,
+
+ onLoad(query) {
+ const { selectMode, id = '' } = query;
+ this.setData({
+ id,
+ });
+ this.selectMode = selectMode === 'true';
+ this.init();
+ },
+
+ onShow() {
+ shouldFresh && this.fresh();
+ },
+
+ init() {
+ this.fresh();
+ },
+ onUnload() {
+ if (this.selectMode && !this.hasSelect) {
+ rejectAddress();
+ }
+ },
+ async fresh() {
+ this.setLoading();
+ try {
+ await this.getAddressList();
+ addressListFinishFresh();
+ } catch {
+ this.toast('拉取地址失败,请稍后再试');
+ } finally {
+ this.unsetLoading();
+ }
+ },
+ async getAddressList() {
+ const addressList = await getAllAddress();
+ this.setData({ addressList });
+ },
+ toast(message) {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message,
+ icon: '',
+ duration: 1000,
+ });
+ },
+ async deleteAddressHandle({ detail: { _id } }) {
+ try {
+ this.setLoading();
+ await deleteAddress({ id: _id });
+ const { addressList } = this.data;
+ this.setData({ addressList: addressList.filter((x) => x._id !== _id) });
+ } catch {
+ this.toast('删除地址失败,请稍后再试');
+ } finally {
+ this.unsetLoading();
+ }
+ },
+ editAddressHandle({ detail }) {
+ wx.navigateTo({ url: `/pages/usercenter/address/edit/index?${objectToParamString(detail)}` });
+ },
+ selectHandle({ detail }) {
+ if (this.selectMode) {
+ this.hasSelect = true;
+ resolveAddress(detail);
+ wx.navigateBack({ delta: 1 });
+ } else {
+ this.editAddressHandle({ detail });
+ }
+ },
+ createHandle() {
+ wx.navigateTo({ url: '/pages/usercenter/address/edit/index' });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/usercenter/address/list/index.json b/miniprogram/tcb-shop/pages/usercenter/address/list/index.json
new file mode 100644
index 0000000..0a51b22
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/address/list/index.json
@@ -0,0 +1,11 @@
+{
+ "navigationBarTitleText": "收货地址",
+ "usingComponents": {
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-image": "/components/webp-image/index",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-address-item": "../../components/ui-address-item/index",
+ "t-empty": "tdesign-miniprogram/empty/empty",
+ "loading-dialog": "../../../../components/loading-dialog/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/usercenter/address/list/index.wxml b/miniprogram/tcb-shop/pages/usercenter/address/list/index.wxml
new file mode 100644
index 0000000..77b3b98
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/address/list/index.wxml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 新建收货地址
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/usercenter/address/list/index.wxss b/miniprogram/tcb-shop/pages/usercenter/address/list/index.wxss
new file mode 100644
index 0000000..286227b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/address/list/index.wxss
@@ -0,0 +1,109 @@
+page {
+ background: #f5f5f5;
+ height: 100%;
+}
+.address-container {
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+ padding-bottom: calc(env(safe-area-inset-bottom) + 172rpx);
+}
+.address-container .address-list {
+ font-size: 24rpx;
+ background-color: #ffffff;
+ -webkit-overflow-scrolling: touch;
+}
+.address-list .no-address {
+ width: 750rpx;
+ padding-top: 30vh;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+}
+.address-list .no-address__icon {
+ width: 224rpx;
+ height: 224rpx;
+}
+.address-list .no-address__text {
+ font-size: 28rpx;
+ line-height: 40rpx;
+ color: #999999;
+ margin-top: 24rpx;
+}
+.address-container .bottom-fixed {
+ border-top: 1rpx solid #e5e5e5;
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ z-index: 1000;
+ background: #fff;
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ align-items: center;
+ padding: 12rpx 32rpx calc(env(safe-area-inset-bottom) + 12rpx) 32rpx;
+}
+.address-container .btn-wrap {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-size: 32rpx;
+ font-weight: bold;
+}
+.address-container .btn-wrap .location-btn {
+ width: 332rpx;
+ height: 88rpx;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: #ffffff;
+ color: #333;
+ position: relative;
+}
+.address-container .btn-wrap .location-btn::after {
+ content: '';
+ position: absolute; /* 把父视图设置为relative,方便定位*/
+ top: 0;
+ left: 0;
+ width: 200%;
+ height: 200%;
+ transform: scale(0.5);
+ transform-origin: 0 0;
+ box-sizing: border-box;
+ border-radius: 88rpx;
+ border: #dddddd 2rpx solid;
+}
+.address-container .btn-wrap .address-btn {
+ flex-grow: 1;
+ height: 88rpx;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: #fa4126;
+ border-radius: 44rpx;
+ color: #fff;
+}
+.address-container .btn-wrap .btn-default {
+ background: #c6c6c6;
+}
+.address-container .bottom-fixed .footer {
+ margin-top: 10rpx;
+ display: inline-block;
+ width: 100%;
+ text-align: center;
+ font-size: 24rpx;
+ font-weight: 400;
+ color: #ff2525;
+ line-height: 60rpx;
+ height: 60rpx;
+}
+.address-container .message {
+ margin-top: 48rpx;
+}
+.address-container .custom-class {
+ margin-right: 12rpx;
+ font-weight: normal;
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/address/list/util.js b/miniprogram/tcb-shop/pages/usercenter/address/list/util.js
new file mode 100644
index 0000000..953f8d0
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/address/list/util.js
@@ -0,0 +1,31 @@
+let addressPromise = [];
+
+/** 获取一个地址选择Promise */
+export const getAddressPromise = () => {
+ let resolver;
+ let rejecter;
+ const nextPromise = new Promise((resolve, reject) => {
+ resolver = resolve;
+ rejecter = reject;
+ });
+
+ addressPromise.push({ resolver, rejecter });
+
+ return nextPromise;
+};
+
+/** 用户选择了一个地址 */
+export const resolveAddress = (address) => {
+ const allAddress = [...addressPromise];
+ addressPromise = [];
+
+ allAddress.forEach(({ resolver }) => resolver(address));
+};
+
+/** 用户没有选择任何地址只是返回上一页了 */
+export const rejectAddress = () => {
+ const allAddress = [...addressPromise];
+ addressPromise = [];
+
+ allAddress.forEach(({ rejecter }) => rejecter(new Error('cancel')));
+};
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.js b/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.js
new file mode 100644
index 0000000..5bfe0f6
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.js
@@ -0,0 +1,37 @@
+Component({
+ externalClasses: ['title-class', 'icon-class', 'number-class'],
+ options: {
+ multipleSlots: true,
+ },
+ properties: {
+ orderTagInfos: {
+ type: Array,
+ value: [],
+ },
+ title: {
+ type: String,
+ value: '我的订单',
+ },
+ desc: {
+ type: String,
+ value: '全部订单',
+ },
+ isTop: {
+ type: Boolean,
+ value: true,
+ },
+ classPrefix: {
+ type: String,
+ value: 'wr',
+ },
+ },
+ methods: {
+ onClickItem(e) {
+ this.triggerEvent('onClickItem', e.currentTarget.dataset.item);
+ },
+
+ onClickTop() {
+ this.triggerEvent('onClickTop', {});
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.json b/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.json
new file mode 100644
index 0000000..c22feda
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.json
@@ -0,0 +1,9 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
+ "t-badge": "tdesign-miniprogram/badge/badge",
+ "t-icon": "tdesign-miniprogram/icon/icon"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.wxml b/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.wxml
new file mode 100644
index 0000000..de56e28
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.wxml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+ {{item.title}}
+
+
+
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.wxss b/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.wxss
new file mode 100644
index 0000000..59b9aa1
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/order-group/index.wxss
@@ -0,0 +1,56 @@
+.order-group {
+ margin-bottom: 24rpx;
+ background-color: #ffffff;
+ border-radius: 16rpx 16rpx 0 0;
+}
+.order-group .order-group__top {
+ padding: 24rpx 18rpx 24rpx 32rpx;
+ border-radius: 16rpx 16rpx 0 0;
+}
+.order-group__top___title {
+ font-size: 32rpx;
+ line-height: 48rpx;
+ font-weight: bold;
+}
+.order-group__top__note {
+ font-size: 28rpx;
+}
+.order-group__content {
+ overflow: hidden;
+ width: 100%;
+ height: 164rpx;
+ display: flex;
+ background-color: #fff;
+ border-radius: 0 0 16rpx 16rpx;
+}
+.order-group__item {
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ flex: 1;
+}
+.order-group__item:first-child {
+ border-radius: 0 0 0 16rpx;
+}
+.order-group__item:last-child {
+ border-radius: 0 0 16rpx 0;
+}
+.order-group__item__title {
+ font-size: 24rpx;
+ color: #666;
+ line-height: 32rpx;
+}
+.order-group__item__icon {
+ margin-bottom: 20rpx;
+ width: 56rpx;
+ height: 56rpx;
+ position: relative;
+}
+.order-group__top__title {
+ font-weight: bold;
+}
+.order-group .order-group__left {
+ margin-right: 0;
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.js b/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.js
new file mode 100644
index 0000000..467bbf7
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.js
@@ -0,0 +1,46 @@
+Component({
+ options: {
+ addGlobalClass: true,
+ multipleSlots: true,
+ },
+ properties: {
+ address: {
+ type: Object,
+ value: {},
+ },
+ customIcon: {
+ type: String,
+ value: 'edit-1',
+ },
+ extraSpace: {
+ type: Boolean,
+ value: true,
+ },
+ isDrawLine: {
+ type: Boolean,
+ value: true,
+ },
+ },
+ externalClasses: [
+ 'item-wrapper-class',
+ 'title-class',
+ 'default-tag-class',
+ 'normal-tag-class',
+ 'address-info-class',
+ 'delete-class',
+ ],
+ methods: {
+ onDelete(e) {
+ const { item } = e.currentTarget.dataset;
+ this.triggerEvent('onDelete', item);
+ },
+ onSelect(e) {
+ const { item } = e.currentTarget.dataset;
+ this.triggerEvent('onSelect', item);
+ },
+ onEdit(e) {
+ const { item } = e.currentTarget.dataset;
+ this.triggerEvent('onEdit', item);
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.json b/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.json
new file mode 100644
index 0000000..0f9fe0f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.json
@@ -0,0 +1,8 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-tag": "tdesign-miniprogram/tag/tag",
+ "t-swipe-cell": "tdesign-miniprogram/swipe-cell/swipe-cell"
+ }
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.wxml b/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.wxml
new file mode 100644
index 0000000..9cc4bba
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.wxml
@@ -0,0 +1,30 @@
+
+ var toHide = function(array) { var mphone = array.substring(0, 3) + '****' + array.substring(7); return mphone; }
+ module.exports.toHide = toHide;
+
+
+
+
+
+
+
+
+
+ {{address.name}}
+ {{phoneReg.toHide(address.phone || '')}}
+
+
+
+ {{address.address}}
+
+
+
+
+
+
+
+
+ 删除
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.wxss b/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.wxss
new file mode 100644
index 0000000..5ce1ea9
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/ui-address-item/index.wxss
@@ -0,0 +1,103 @@
+.address-item-wrapper {
+ overflow: hidden;
+}
+.address-item-wrapper .swipe-out .wr-swiper-cell {
+ margin-top: 20rpx;
+}
+.address-item-wrapper .swipe-out .swipe-right-del {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 144rpx;
+ height: 100%;
+ background-color: #fa4126;
+ color: #fff;
+ font-size: 28rpx;
+ line-height: 40rpx;
+}
+.address-item-wrapper .draw-line {
+ position: relative;
+}
+.address-item-wrapper .draw-line::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: 32rpx;
+ width: 200%;
+ height: 2rpx;
+ transform: scale(0.5);
+ transform-origin: 0 0;
+ box-sizing: border-box;
+ border-bottom: #e5e5e5 2rpx solid;
+}
+.address-item-wrapper .address {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 32rpx;
+ background-color: #fff;
+}
+.address-item-wrapper .address .address-edit {
+ padding: 20rpx 0 20rpx 46rpx;
+}
+.address-item-wrapper .address .address-left {
+ width: 80rpx;
+ display: flex;
+ justify-content: center;
+}
+.address-item-wrapper .address .address-content {
+ display: flex;
+ flex-direction: column;
+ flex: 1;
+}
+.address-item-wrapper .address .address-content .title {
+ font-size: 32rpx;
+ line-height: 48rpx;
+ margin-bottom: 16rpx;
+ color: #333333;
+ font-weight: bold;
+ display: flex;
+}
+.address-item-wrapper .address .address-content .title .text-style {
+ margin-right: 8rpx;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ max-width: 280rpx;
+}
+.address-item-wrapper .address .address-content .label-adds {
+ display: flex;
+}
+.address-item-wrapper .address .address-content .label-adds .adds {
+ display: -webkit-box;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ color: #999999;
+}
+.address-item-wrapper .address .address-content .label-adds .tag {
+ display: inline-block;
+ padding: 0rpx 8rpx;
+ min-width: 40rpx;
+ height: 32rpx;
+ border-radius: 18rpx;
+ font-size: 20rpx;
+ line-height: 32rpx;
+ text-align: center;
+ margin-right: 8rpx;
+ vertical-align: text-top;
+}
+.address-item-wrapper .address .address-content .label-adds .tag-default {
+ background: #ffece9;
+ color: #fa4126;
+}
+.address-item-wrapper .address .address-content .label-adds .tag-primary {
+ background: #f0f1ff;
+ color: #5a66ff;
+}
+.address-item-wrapper .address .address-content .label-adds .address-text {
+ font-size: 28rpx;
+ line-height: 40rpx;
+ color: #999999;
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.js b/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.js
new file mode 100644
index 0000000..fff68cc
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.js
@@ -0,0 +1,73 @@
+Component({
+ properties: {
+ show: {
+ type: Boolean,
+ observer(show) {
+ if (!show) return;
+ this.updateDivisions();
+ },
+ },
+ title: {
+ type: String,
+ value: '',
+ },
+ value: {
+ type: String,
+ value: '',
+ observer() {
+ if (!this.data.show) return;
+ this.updateDivisions();
+ },
+ },
+ pickerOptions: {
+ type: Array,
+ value: [],
+ observer() {
+ if (!this.data.show) return;
+ this.updateDivisions();
+ },
+ },
+ headerVisible: {
+ type: Boolean,
+ value: true,
+ },
+ },
+ data: {
+ pickerValue: [],
+ },
+ methods: {
+ updateDivisions() {
+ const { pickerOptions, value } = this.data;
+ const index = (pickerOptions || []).findIndex(
+ (item) => item.code === value,
+ );
+
+ setTimeout(() => {
+ this.setData({ pickerValue: index >= 0 ? [index] : [0] });
+ }, 0);
+ },
+
+ getAreaByIndex(indexes) {
+ const { pickerOptions } = this.data;
+ return pickerOptions[indexes.toString()];
+ },
+
+ onChange(e) {
+ const currentValue = e.detail.value;
+ const target = this.getAreaByIndex(currentValue);
+ if (target === null) return;
+
+ this.setData({ pickerValue: currentValue });
+ this.triggerEvent('change', { value: target.code, target: target });
+ },
+
+ onConfirm() {
+ const target = this.getAreaByIndex(this.data.pickerValue);
+ this.triggerEvent('confirm', { value: target?.code, target });
+ },
+
+ onClose() {
+ this.triggerEvent('close');
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.json b/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.json
new file mode 100644
index 0000000..6e4c04e
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.json
@@ -0,0 +1,6 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-popup": "tdesign-miniprogram/popup/popup"
+ }
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.wxml b/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.wxml
new file mode 100644
index 0000000..6e3cddc
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.wxml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ {{ item.name }}
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.wxss b/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.wxss
new file mode 100644
index 0000000..3e977be
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/ui-select-picker/index.wxss
@@ -0,0 +1,102 @@
+.city-picker-container {
+ opacity: 0;
+ position: fixed;
+ top: 100vh;
+ left: 0;
+ right: 0;
+ height: 100vh;
+ z-index: 100;
+}
+.city-picker-container.show {
+ top: 0;
+ opacity: 1;
+}
+.city-picker-container.show .city-picker-box {
+ bottom: 0;
+}
+.city-picker-shadow {
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.65);
+}
+.city-picker-header {
+ height: 100rpx;
+ line-height: 100rpx;
+ text-align: center;
+ font-size: 32rpx;
+ color: #333333;
+}
+.city-picker-more {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.city-picker-footer {
+ height: 100rpx;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+.city-picker-footer .btn {
+ width: 330rpx;
+ height: 80rpx;
+ line-height: 80rpx;
+ text-align: center;
+ color: #666666;
+ font-size: 32rpx;
+ position: relative;
+}
+.city-picker-footer .btn__active {
+ opacity: 0.5;
+}
+.city-picker-footer .btn::after {
+ display: block;
+ content: ' ';
+ position: absolute;
+ left: -50%;
+ right: -50%;
+ top: -50%;
+ bottom: -50%;
+ transform: scale(0.5);
+ border: 1rpx solid #999999;
+ border-radius: 16rpx;
+}
+.city-picker-footer .btn.primary {
+ color: #fa550f;
+}
+.city-picker-footer .btn.primary::after {
+ border-color: #fa550f;
+}
+.picker-column:not(:first-child) {
+ margin-left: 40rpx;
+}
+.city-picker-box {
+ position: absolute;
+ bottom: -100%;
+ transition: 0.3s bottom ease-in-out;
+ left: 0;
+ right: 0;
+ z-index: 100;
+ background-color: #fff;
+ padding: 0 30rpx;
+ color: #333333;
+ font-size: 34rpx;
+ border-radius: 20rpx 20rpx 0 0;
+ padding-bottom: env(safe-area-inset-bottom);
+}
+.show .city-picker-shadow {
+ display: block;
+}
+.picker {
+ height: 300rpx;
+ margin: 50rpx 0;
+ line-height: 88rpx;
+ text-align: center;
+}
+/* 似乎小程序picker-view的bug,indicator-class仅height生效,其他诸如line-height、text-align等放到父class中设置 */
+.picker-center-row {
+ height: 88rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.js b/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.js
new file mode 100644
index 0000000..47593c4
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.js
@@ -0,0 +1,35 @@
+const AuthStepType = {
+ ONE: 1,
+ TWO: 2,
+ THREE: 3,
+};
+
+Component({
+ options: {
+ multipleSlots: true,
+ },
+ properties: {
+ currAuthStep: {
+ type: Number,
+ value: AuthStepType.ONE,
+ },
+ userInfo: {
+ type: Object,
+ value: {},
+ },
+ isNeedGetUserInfo: {
+ type: Boolean,
+ value: false,
+ },
+ },
+ data: {
+ defaultAvatarUrl:
+ 'https://cdn-we-retail.ym.tencent.com/miniapp/usercenter/icon-user-center-avatar@2x.png',
+ AuthStepType,
+ },
+ methods: {
+ gotoUserEditPage() {
+ this.triggerEvent('gotoUserEditPage');
+ },
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.json b/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.json
new file mode 100644
index 0000000..e169f6f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.json
@@ -0,0 +1,7 @@
+{
+ "component": true,
+ "usingComponents": {
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-avatar": "tdesign-miniprogram/avatar/avatar"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.wxml b/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.wxml
new file mode 100644
index 0000000..ad11e7d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.wxml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.wxss b/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.wxss
new file mode 100644
index 0000000..2b488c1
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/components/user-center-card/index.wxss
@@ -0,0 +1,48 @@
+.user-center-card {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 480rpx;
+ background-image: url('https://cdn-we-retail.ym.tencent.com/miniapp/template/user-center-bg-v1.png');
+ background-size: cover;
+ background-repeat: no-repeat;
+ padding: 0 24rpx;
+}
+.user-center-card__header {
+ margin-top: 192rpx;
+ margin-bottom: 48rpx;
+ height: 96rpx;
+ line-height: 48rpx;
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ color: #333;
+ position: relative;
+}
+.user-center-card__header__avatar {
+ width: 96rpx;
+ height: 96rpx;
+ border-radius: 48rpx;
+ overflow: hidden;
+}
+
+.user-center-card__header__name {
+ font-size: 36rpx;
+ line-height: 48rpx;
+ color: #333;
+ font-weight: bold;
+ margin-left: 24rpx;
+ margin-right: 16rpx;
+}
+.user-center-card__header__transparent {
+ position: absolute;
+ left: 0;
+ top: 0;
+ background-color: transparent;
+ height: 100%;
+ width: 100%;
+}
+.user-center-card__icon {
+ line-height: 96rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/index.js b/miniprogram/tcb-shop/pages/usercenter/index.js
new file mode 100644
index 0000000..cf90eed
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/index.js
@@ -0,0 +1,220 @@
+import { fetchUserCenter } from '../../services/usercenter/fetchUsercenter';
+import { getToPayOrderCount, getToSendOrderCount, getToReceiveOrderCount } from '../../services/order/order';
+import { ORDER_STATUS } from '../../services/order/order';
+import Toast from 'tdesign-miniprogram/toast/index';
+
+const menuData = [
+ [
+ {
+ title: '收货地址',
+ tit: '',
+ url: '',
+ type: 'address',
+ },
+ ],
+];
+
+const orderTagInfos = [
+ {
+ title: '待付款',
+ iconName: 'wallet',
+ orderNum: 0,
+ tabType: ORDER_STATUS.TO_PAY,
+ status: 1,
+ },
+ {
+ title: '待发货',
+ iconName: 'deliver',
+ orderNum: 0,
+ tabType: ORDER_STATUS.TO_SEND,
+ status: 1,
+ },
+ {
+ title: '待收货',
+ iconName: 'package',
+ orderNum: 0,
+ tabType: ORDER_STATUS.TO_RECEIVE,
+ status: 1,
+ },
+ {
+ title: '待评价',
+ iconName: 'comment',
+ orderNum: 0,
+ tabType: ORDER_STATUS.FINISHED,
+ status: 1,
+ },
+ // {
+ // title: '退款/售后',
+ // iconName: 'exchang',
+ // orderNum: 0,
+ // tabType: 0,
+ // status: 1,
+ // },
+];
+
+const getDefaultData = () => ({
+ showMakePhone: false,
+ userInfo: {
+ avatarUrl: '',
+ nickName: '正在登录...',
+ phoneNumber: '',
+ },
+ menuData,
+ orderTagInfos,
+ customerServiceInfo: {},
+ currAuthStep: 1,
+ showKefu: true,
+ versionNo: '',
+ toPayOrderCount: 0,
+ toSendOrderCount: 0,
+ toReceiveOrderCount: 0,
+});
+
+Page({
+ data: getDefaultData(),
+
+ onLoad() {
+ this.getVersionInfo();
+ },
+
+ onShow() {
+ this.getTabBar().init();
+ this.init();
+ },
+ onPullDownRefresh() {
+ this.init();
+ },
+
+ init() {
+ this.fetUseriInfoHandle();
+ this.initOrderCount();
+ },
+
+ async initOrderCount() {
+ const [pay, send, receive] = await Promise.all([
+ getToPayOrderCount(),
+ getToSendOrderCount(),
+ getToReceiveOrderCount(),
+ ]);
+ this.setData({
+ 'orderTagInfos[0].orderNum': pay,
+ 'orderTagInfos[1].orderNum': send,
+ 'orderTagInfos[2].orderNum': receive,
+ });
+ },
+
+ fetUseriInfoHandle() {
+ fetchUserCenter().then(({ userInfo, countsData, customerServiceInfo }) => {
+ // eslint-disable-next-line no-unused-expressions
+ menuData?.[0].forEach((v) => {
+ countsData.forEach((counts) => {
+ if (counts.type === v.type) {
+ // eslint-disable-next-line no-param-reassign
+ v.tit = counts.num;
+ }
+ });
+ });
+ this.setData({
+ userInfo,
+ menuData,
+ customerServiceInfo,
+ currAuthStep: 2,
+ });
+ wx.stopPullDownRefresh();
+ });
+ },
+
+ onClickCell({ currentTarget }) {
+ const { type } = currentTarget.dataset;
+
+ switch (type) {
+ case 'address': {
+ wx.navigateTo({ url: '/pages/usercenter/address/list/index' });
+ break;
+ }
+ case 'service': {
+ this.openMakePhone();
+ break;
+ }
+ case 'help-center': {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '你点击了帮助中心',
+ icon: '',
+ duration: 1000,
+ });
+ break;
+ }
+ case 'point': {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '你点击了积分菜单',
+ icon: '',
+ duration: 1000,
+ });
+ break;
+ }
+ case 'coupon': {
+ wx.navigateTo({ url: '/pages/coupon/coupon-list/index' });
+ break;
+ }
+ default: {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '未知跳转',
+ icon: '',
+ duration: 1000,
+ });
+ break;
+ }
+ }
+ },
+
+ jumpNav(e) {
+ const status = e.detail.tabType;
+
+ if (status === 0) {
+ wx.navigateTo({ url: '/pages/order/after-service-list/index' });
+ } else {
+ wx.navigateTo({ url: `/pages/order/order-list/index?status=${status}` });
+ }
+ },
+
+ jumpAllOrder() {
+ wx.navigateTo({ url: '/pages/order/order-list/index' });
+ },
+
+ openMakePhone() {
+ this.setData({ showMakePhone: true });
+ },
+
+ closeMakePhone() {
+ this.setData({ showMakePhone: false });
+ },
+
+ call() {
+ wx.makePhoneCall({
+ phoneNumber: this.data.customerServiceInfo.servicePhone,
+ });
+ },
+
+ gotoUserEditPage() {
+ const { currAuthStep } = this.data;
+ if (currAuthStep === 2) {
+ wx.navigateTo({ url: '/pages/usercenter/person-info/index' });
+ } else {
+ this.fetUseriInfoHandle();
+ }
+ },
+
+ getVersionInfo() {
+ const versionInfo = wx.getAccountInfoSync();
+ const { version, envVersion = __wxConfig } = versionInfo.miniProgram;
+ this.setData({
+ versionNo: envVersion === 'release' ? version : envVersion,
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/usercenter/index.json b/miniprogram/tcb-shop/pages/usercenter/index.json
new file mode 100644
index 0000000..398a70b
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/index.json
@@ -0,0 +1,14 @@
+{
+ "navigationBarTitleText": "个人中心",
+ "navigationStyle": "custom",
+ "usingComponents": {
+ "t-popup": "tdesign-miniprogram/popup/popup",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-user-center-card": "./components/user-center-card/index",
+ "t-order-group": "./components/order-group/index",
+ "t-toast": "tdesign-miniprogram/toast/toast"
+ },
+ "enablePullDownRefresh": true
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/usercenter/index.wxml b/miniprogram/tcb-shop/pages/usercenter/index.wxml
new file mode 100644
index 0000000..e42a865
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/index.wxml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/usercenter/index.wxss b/miniprogram/tcb-shop/pages/usercenter/index.wxss
new file mode 100644
index 0000000..483e6f2
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/index.wxss
@@ -0,0 +1,146 @@
+page {
+ background-color: #f5f5f5;
+}
+
+.content-wrapper {
+ margin-top: 340rpx;
+ position: relative;
+ padding: 0 30rpx;
+}
+
+.main-content {
+ height: 500rpx;
+}
+
+.order-group-wrapper {
+ margin-bottom: 16rpx;
+}
+
+.order-group-note {
+ font-size: 28rpx;
+}
+
+.cell-box {
+ border-radius: 10rpx;
+ overflow: hidden;
+ margin-bottom: 20rpx;
+}
+.icon-color {
+ color: #aaa;
+}
+.cell-class {
+ height: 100rpx;
+ display: flex;
+ align-items: center;
+}
+
+.order-content {
+ overflow: hidden;
+ width: 100%;
+ display: flex;
+ background-color: #fff;
+ border-radius: 16rpx;
+}
+
+.order-item {
+ flex: 1;
+ height: 180rpx;
+ overflow: hidden;
+ position: relative;
+ text-align: center;
+}
+
+.order-content-box {
+ margin: auto;
+ position: absolute;
+ width: 100%;
+ top: 50%;
+ left: 50%;
+ -webkit-transform: translate(-50%, -50%);
+ -ms-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+}
+.order-content-t {
+ margin-top: 10rpx;
+ font-size: 24rpx;
+ color: #333;
+ letter-spacing: 0;
+ text-align: center;
+}
+
+.popup-content {
+ background: #f5f5f5;
+ margin-bottom: env(safe-area-inset-bottom);
+ border-radius: 16rpx 16rpx 0 0;
+}
+.popup-content .popup-title {
+ background: #fff;
+ text-align: center;
+ font-size: 24rpx;
+ color: #999;
+ height: 112rpx;
+ text-align: center;
+ line-height: 112rpx;
+ border-radius: 16rpx 16rpx 0 0;
+}
+
+.border-bottom-1px {
+ position: relative;
+}
+
+.border-bottom-1px::after {
+ position: absolute;
+ display: block;
+ content: '';
+ box-sizing: border-box;
+ top: 0;
+ left: 0;
+ width: 200%;
+ height: 200%;
+ transform: scale(0.5);
+ transform-origin: left top;
+ border-bottom: 2rpx solid #e5e5e5;
+}
+.popup-content .popup-phone,
+.popup-content .popup-close {
+ background: #fff;
+ height: 100rpx;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+ font-size: 30rpx;
+ font-family: PingFangSC-Regular, PingFang SC;
+ font-weight: 400;
+ color: #333;
+}
+.popup-content .popup-phone.online {
+ margin-bottom: 20rpx;
+}
+.popup-content .popup-phone.online::after {
+ content: none;
+}
+.popup-content .popup-close {
+ color: #333;
+ border: 0;
+ margin-top: 16rpx;
+}
+
+.my-order {
+ border-radius: 10rpx;
+}
+
+.footer__version {
+ text-align: center;
+ margin-top: 50rpx;
+ color: #999;
+ margin-bottom: 4rpx;
+ font-size: 24rpx;
+ line-height: 32rpx;
+}
+.cell-box .order-group__left {
+ margin-right: 0;
+}
+.cell-box .t-cell-padding {
+ padding: 24rpx 18rpx 24rpx 32rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/name-edit/index.js b/miniprogram/tcb-shop/pages/usercenter/name-edit/index.js
new file mode 100644
index 0000000..57e4e3d
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/name-edit/index.js
@@ -0,0 +1,19 @@
+Page({
+ data: {
+ nameValue: '',
+ },
+ onLoad(options) {
+ const { name } = options;
+ this.setData({
+ nameValue: name,
+ });
+ },
+ onSubmit() {
+ wx.navigateBack({ backRefresh: true });
+ },
+ clearContent() {
+ this.setData({
+ nameValue: '',
+ });
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/usercenter/name-edit/index.json b/miniprogram/tcb-shop/pages/usercenter/name-edit/index.json
new file mode 100644
index 0000000..efc7e5f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/name-edit/index.json
@@ -0,0 +1,8 @@
+{
+ "navigationBarTitleText": "昵称",
+ "usingComponents": {
+ "t-input": "tdesign-miniprogram/input/input",
+ "t-icon": "tdesign-miniprogram/icon/icon",
+ "t-button": "tdesign-miniprogram/button/button"
+ }
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/name-edit/index.wxml b/miniprogram/tcb-shop/pages/usercenter/name-edit/index.wxml
new file mode 100644
index 0000000..2ceb518
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/name-edit/index.wxml
@@ -0,0 +1,14 @@
+
+
+ 最多可输入15个字
+
+ 保存
+
+
diff --git a/miniprogram/tcb-shop/pages/usercenter/name-edit/index.wxss b/miniprogram/tcb-shop/pages/usercenter/name-edit/index.wxss
new file mode 100644
index 0000000..61674b3
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/name-edit/index.wxss
@@ -0,0 +1,18 @@
+page {
+ background-color: #f5f5f5;
+}
+page view {
+ box-sizing: border-box;
+}
+.name-edit {
+ padding-top: 20rpx;
+}
+.name-edit .name-edit__input--desc {
+ font-size: 26rpx;
+ padding: 16rpx 32rpx;
+ color: #999;
+ margin-bottom: 200rpx;
+}
+.name-edit .name-edit__wrapper {
+ margin: 0 32rpx;
+}
diff --git a/miniprogram/tcb-shop/pages/usercenter/person-info/index.js b/miniprogram/tcb-shop/pages/usercenter/person-info/index.js
new file mode 100644
index 0000000..fb5db98
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/person-info/index.js
@@ -0,0 +1,122 @@
+import { fetchPerson } from '../../../services/usercenter/fetchPerson';
+import { phoneEncryption } from '../../../utils/util';
+import Toast from 'tdesign-miniprogram/toast/index';
+
+Page({
+ data: {
+ personInfo: {
+ avatarUrl: '',
+ nickName: '',
+ gender: 0,
+ phoneNumber: '',
+ },
+ showUnbindConfirm: false,
+ pickerOptions: [
+ {
+ name: '男',
+ code: '1',
+ },
+ {
+ name: '女',
+ code: '2',
+ },
+ ],
+ typeVisible: false,
+ genderMap: ['', '男', '女'],
+ },
+ onLoad() {
+ this.init();
+ },
+ init() {
+ this.fetchData();
+ },
+ fetchData() {
+ fetchPerson().then((personInfo) => {
+ this.setData({
+ personInfo,
+ 'personInfo.phoneNumber': phoneEncryption(personInfo.phoneNumber),
+ });
+ });
+ },
+ onClickCell({ currentTarget }) {
+ const { dataset } = currentTarget;
+ const { nickName } = this.data.personInfo;
+
+ switch (dataset.type) {
+ case 'gender':
+ this.setData({
+ typeVisible: true,
+ });
+ break;
+ case 'name':
+ wx.navigateTo({
+ url: `/pages/usercenter/name-edit/index?name=${nickName}`,
+ });
+ break;
+ case 'avatarUrl':
+ this.toModifyAvatar();
+ break;
+ default: {
+ break;
+ }
+ }
+ },
+ onClose() {
+ this.setData({
+ typeVisible: false,
+ });
+ },
+ onConfirm(e) {
+ const { value } = e.detail;
+ this.setData(
+ {
+ typeVisible: false,
+ 'personInfo.gender': value,
+ },
+ () => {
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: '设置成功',
+ theme: 'success',
+ });
+ },
+ );
+ },
+ async toModifyAvatar() {
+ try {
+ const tempFilePath = await new Promise((resolve, reject) => {
+ wx.chooseImage({
+ count: 1,
+ sizeType: ['compressed'],
+ sourceType: ['album', 'camera'],
+ success: (res) => {
+ const { path, size } = res.tempFiles[0];
+ if (size <= 10485760) {
+ resolve(path);
+ } else {
+ reject({ errMsg: '图片大小超出限制,请重新上传' });
+ }
+ },
+ fail: (err) => reject(err),
+ });
+ });
+ const tempUrlArr = tempFilePath.split('/');
+ const tempFileName = tempUrlArr[tempUrlArr.length - 1];
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: `已选择图片-${tempFileName}`,
+ theme: 'success',
+ });
+ } catch (error) {
+ if (error.errMsg === 'chooseImage:fail cancel') return;
+ Toast({
+ context: this,
+ selector: '#t-toast',
+ message: error.errMsg || error.msg || '修改头像出错了',
+ theme: 'error',
+ });
+ }
+ },
+});
diff --git a/miniprogram/tcb-shop/pages/usercenter/person-info/index.json b/miniprogram/tcb-shop/pages/usercenter/person-info/index.json
new file mode 100644
index 0000000..e63dcc5
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/person-info/index.json
@@ -0,0 +1,12 @@
+{
+ "navigationBarTitleText": "个人资料",
+ "usingComponents": {
+ "t-cell-group": "tdesign-miniprogram/cell-group/cell-group",
+ "t-cell": "tdesign-miniprogram/cell/cell",
+ "t-button": "tdesign-miniprogram/button/button",
+ "t-image": "/components/webp-image/index",
+ "t-dialog": "tdesign-miniprogram/dialog/dialog",
+ "t-toast": "tdesign-miniprogram/toast/toast",
+ "t-select-picker": "../components/ui-select-picker/index"
+ }
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/usercenter/person-info/index.wxml b/miniprogram/tcb-shop/pages/usercenter/person-info/index.wxml
new file mode 100644
index 0000000..21b8c6f
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/person-info/index.wxml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/pages/usercenter/person-info/index.wxss b/miniprogram/tcb-shop/pages/usercenter/person-info/index.wxss
new file mode 100644
index 0000000..cb4eb70
--- /dev/null
+++ b/miniprogram/tcb-shop/pages/usercenter/person-info/index.wxss
@@ -0,0 +1,45 @@
+:host {
+ background-color: #f5f5f5;
+}
+page view {
+ box-sizing: border-box;
+}
+.person-info {
+ padding-top: 20rpx;
+}
+
+.person-info__btn {
+ width: 100%;
+ border: 2rpx solid #ddd;
+ border-radius: 48rpx;
+ padding: 18rpx 0;
+ display: flex;
+ align-self: center;
+ justify-content: center;
+}
+.person-info__wrapper {
+ width: 100%;
+ padding: 0 32rpx;
+ padding-bottom: calc(env(safe-area-inset-bottom) + 20rpx);
+ position: absolute;
+ bottom: 0;
+ left: 0;
+}
+
+.avatarUrl {
+ width: 80rpx;
+ height: 80rpx;
+ border-radius: 50% !important;
+ overflow: hidden;
+}
+
+.t-class-confirm {
+ color: #fa550f !important;
+}
+
+.person-info .order-group__left {
+ margin-right: 0;
+}
+.person-info .t-cell-class {
+ height: 112rpx;
+}
diff --git a/miniprogram/tcb-shop/project.config.json b/miniprogram/tcb-shop/project.config.json
new file mode 100644
index 0000000..2a3b8cb
--- /dev/null
+++ b/miniprogram/tcb-shop/project.config.json
@@ -0,0 +1,150 @@
+{
+ "description": "项目配置文件,详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html",
+ "packOptions": {
+ "ignore": [],
+ "include": []
+ },
+ "setting": {
+ "urlCheck": true,
+ "es6": true,
+ "enhance": true,
+ "postcss": true,
+ "preloadBackgroundData": false,
+ "minified": true,
+ "newFeature": false,
+ "coverView": true,
+ "nodeModules": true,
+ "autoAudits": false,
+ "showShadowRootInWxmlPanel": true,
+ "scopeDataCheck": false,
+ "uglifyFileName": false,
+ "checkInvalidKey": true,
+ "checkSiteMap": true,
+ "uploadWithSourceMap": true,
+ "compileHotReLoad": false,
+ "lazyloadPlaceholderEnable": false,
+ "useMultiFrameRuntime": true,
+ "useApiHook": true,
+ "useApiHostProcess": true,
+ "ignoreDevUnusedFiles": false,
+ "babelSetting": {
+ "ignore": [],
+ "disablePlugins": [],
+ "outputPath": ""
+ },
+ "enableEngineNative": false,
+ "useIsolateContext": true,
+ "userConfirmedBundleSwitch": false,
+ "packNpmManually": false,
+ "packNpmRelationList": [],
+ "minifyWXSS": true,
+ "disableUseStrict": false,
+ "minifyWXML": true,
+ "showES6CompileOption": false,
+ "useCompilerPlugins": false,
+ "ignoreUploadUnusedFiles": true,
+ "useStaticServer": true
+ },
+ "compileType": "miniprogram",
+ "libVersion": "2.23.1",
+ "appid": "",
+ "projectname": "tcb-shop",
+ "simulatorType": "wechat",
+ "simulatorPluginLibVersion": {},
+ "condition": {
+ "miniprogram": {
+ "list": [
+ {
+ "name": "首页入口",
+ "pathName": "pages/home/home",
+ "query": "",
+ "scene": null
+ },
+ {
+ "name": "示例页-商品分类",
+ "pathName": "pages/goods/category/index",
+ "query": "",
+ "scene": null
+ },
+ {
+ "name": "示例页-个人中心",
+ "pathName": "pages/usercenter/index",
+ "query": "",
+ "scene": null
+ },
+ {
+ "name": "示例页-商品列表",
+ "pathName": "pages/goods/list/index",
+ "query": "",
+ "scene": null
+ },
+ {
+ "name": "示例页-商品详情",
+ "pathName": "pages/goods/details/index",
+ "query": "",
+ "scene": null
+ },
+ {
+ "name": "示例页-商品评论",
+ "pathName": "pages/goods/comments/index",
+ "query": "",
+ "scene": null
+ },
+ {
+ "name": "示例页-售后列表",
+ "pathName": "pages/order/after-service-list/index",
+ "query": "",
+ "scene": null
+ },
+ {
+ "name": "示例页-售后详情",
+ "pathName": "pages/order/after-service-detail/index",
+ "query": "rightsNo=123123423",
+ "scene": null
+ },
+ {
+ "name": "示例页-搜索页",
+ "pathName": "pages/goods/search/index",
+ "query": "",
+ "scene": null
+ },
+ {
+ "name": "示例页-搜索结果",
+ "pathName": "pages/goods/result/index",
+ "query": "",
+ "scene": null
+ },
+ {
+ "name": "示例页-商品评价",
+ "pathName": "pages/goods/comments/create/index",
+ "query": "",
+ "scene": null
+ },
+ {
+ "name": "示例页-选择商品评价",
+ "pathName": "pages/goods/comments/create-list/index",
+ "query": "",
+ "scene": null
+ },
+ {
+ "name": "示例页-申请售后",
+ "pathName": "pages/order/apply-service/index",
+ "query": "orderNo=132222623132329291&skuId=135691625",
+ "scene": null
+ },
+ {
+ "name": "示例页-发票详情",
+ "pathName": "pages/order/invoice/index",
+ "query": "orderNo=132381532610540875",
+ "scene": null
+ }
+ ]
+ }
+ },
+ "editorSetting": {
+ "tabIndent": "insertSpaces",
+ "tabSize": 2
+ },
+ "cloudfunctionTemplateRoot": "cloudfunctionTemplate/",
+ "cloudfunctionRoot": "cloudfunctions/"
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/services/_utils/delay.js b/miniprogram/tcb-shop/services/_utils/delay.js
new file mode 100644
index 0000000..da4ad62
--- /dev/null
+++ b/miniprogram/tcb-shop/services/_utils/delay.js
@@ -0,0 +1,3 @@
+export function delay(ms = 200) {
+ return new Promise((resolve) => setTimeout(resolve, ms));
+}
diff --git a/miniprogram/tcb-shop/services/_utils/model.js b/miniprogram/tcb-shop/services/_utils/model.js
new file mode 100644
index 0000000..46329e9
--- /dev/null
+++ b/miniprogram/tcb-shop/services/_utils/model.js
@@ -0,0 +1,34 @@
+export function model() {
+ return globalThis.dataModel;
+}
+
+export async function getAll({ filter, select, name }) {
+ const addSelect = (prop) => (select ? { ...prop, select } : prop);
+ const pageSize = 200;
+ const first = await model()[name].list(
+ addSelect({
+ pageNumber: 1,
+ pageSize,
+ getCount: true,
+ filter,
+ }),
+ );
+ const {
+ data: { total },
+ } = first;
+ const totalPage = Math.ceil(total / 200);
+ const lists = await Promise.all(
+ Array.from({ length: totalPage - 1 }, (_, index) => index + 2).map((pageNumber) =>
+ model()[name].list(
+ addSelect({
+ pageNumber,
+ pageSize,
+ filter,
+ }),
+ ),
+ ),
+ );
+
+ const ret = lists.reduce((acc, current) => acc.concat(current.data.records), first.data.records);
+ return ret;
+}
diff --git a/miniprogram/tcb-shop/services/_utils/timeout.js b/miniprogram/tcb-shop/services/_utils/timeout.js
new file mode 100644
index 0000000..870c2bf
--- /dev/null
+++ b/miniprogram/tcb-shop/services/_utils/timeout.js
@@ -0,0 +1,3 @@
+export function timeout(ms = 1000) {
+ return new Promise((_, reject) => setTimeout(reject, ms));
+}
diff --git a/miniprogram/tcb-shop/services/activity/fetchActivity.js b/miniprogram/tcb-shop/services/activity/fetchActivity.js
new file mode 100644
index 0000000..e9aefa6
--- /dev/null
+++ b/miniprogram/tcb-shop/services/activity/fetchActivity.js
@@ -0,0 +1,20 @@
+import { config } from '../../config/index';
+
+/** 获取活动列表 */
+function mockFetchActivity(ID = 0) {
+ const { delay } = require('../_utils/delay');
+ const { getActivity } = require('../../model/activity');
+
+ return delay().then(() => getActivity(ID));
+}
+
+/** 获取活动列表 */
+export function fetchActivity(ID = 0) {
+ if (config.useMock) {
+ return mockFetchActivity(ID);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/activity/fetchActivityList.js b/miniprogram/tcb-shop/services/activity/fetchActivityList.js
new file mode 100644
index 0000000..2522aff
--- /dev/null
+++ b/miniprogram/tcb-shop/services/activity/fetchActivityList.js
@@ -0,0 +1,20 @@
+import { config } from '../../config/index';
+
+/** 获取活动列表 */
+function mockFetchActivityList(pageIndex = 1, pageSize = 20) {
+ const { delay } = require('../_utils/delay');
+ const { getActivityList } = require('../../model/activities');
+
+ return delay().then(() => getActivityList(pageIndex, pageSize));
+}
+
+/** 获取活动列表 */
+export function fetchActivityList(pageIndex = 1, pageSize = 20) {
+ if (config.useMock) {
+ return mockFetchActivityList(pageIndex, pageSize);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/address/address.js b/miniprogram/tcb-shop/services/address/address.js
new file mode 100644
index 0000000..d0e285e
--- /dev/null
+++ b/miniprogram/tcb-shop/services/address/address.js
@@ -0,0 +1,85 @@
+import { DATA_MODEL_KEY } from '../../config/model';
+import { getAll, model } from '../_utils/model';
+
+const DELIVERY_INFO_MODEL_KEY = DATA_MODEL_KEY.DELIVERY_INFO;
+
+export function getAllAddress() {
+ return getAll({
+ name: DELIVERY_INFO_MODEL_KEY,
+ select: {
+ _id: true,
+ phone: true,
+ address: true,
+ name: true,
+ },
+ });
+}
+
+/**
+ *
+ * @param {{
+ * name: String,
+ * address: String,
+ * phone: String,
+ * }} param0
+ * @returns
+ */
+export function createAddress({ name, address, phone }) {
+ return model()[DELIVERY_INFO_MODEL_KEY].create({
+ data: {
+ name,
+ address,
+ phone,
+ },
+ });
+}
+
+/**
+ *
+ * @param {{
+ * name,
+ * address,
+ * phone,
+ * _id
+ * }} param0
+ */
+export function updateAddress({ name, address, phone, _id }) {
+ return model()[DELIVERY_INFO_MODEL_KEY].update({
+ data: {
+ name,
+ address,
+ phone,
+ },
+ filter: {
+ where: {
+ _id: { $eq: _id },
+ },
+ },
+ });
+}
+
+export function deleteAddress({ id }) {
+ return model()[DELIVERY_INFO_MODEL_KEY].delete({
+ filter: {
+ where: {
+ _id: {
+ $eq: id,
+ },
+ },
+ },
+ });
+}
+
+export async function getAddress({ id }) {
+ return (
+ await model()[DELIVERY_INFO_MODEL_KEY].get({
+ filter: {
+ where: {
+ _id: {
+ $eq: id,
+ },
+ },
+ },
+ })
+ ).data;
+}
diff --git a/miniprogram/tcb-shop/services/attrValue/attrValue.js b/miniprogram/tcb-shop/services/attrValue/attrValue.js
new file mode 100644
index 0000000..915e460
--- /dev/null
+++ b/miniprogram/tcb-shop/services/attrValue/attrValue.js
@@ -0,0 +1,30 @@
+import { getAll } from '../_utils/model';
+import { DATA_MODEL_KEY } from '../../config/model';
+
+const ATTR_VALUE_MODEL_KEY = DATA_MODEL_KEY.ATTR_VALUE;
+
+export async function getAllAttrValues(skuId) {
+ const res = await getAll({
+ name: ATTR_VALUE_MODEL_KEY,
+ filter: {
+ relateWhere: {
+ sku: {
+ where: {
+ _id: {
+ $eq: skuId,
+ },
+ },
+ },
+ },
+ },
+ select: {
+ _id: true,
+ value: true,
+ attr_name: {
+ _id: true,
+ name: true,
+ },
+ },
+ });
+ return res;
+}
diff --git a/miniprogram/tcb-shop/services/cart/cart.js b/miniprogram/tcb-shop/services/cart/cart.js
new file mode 100644
index 0000000..fb69231
--- /dev/null
+++ b/miniprogram/tcb-shop/services/cart/cart.js
@@ -0,0 +1,120 @@
+import { model, getAll } from '../../services/_utils/model';
+import { config } from '../../config/index';
+import { DATA_MODEL_KEY } from '../../config/model';
+
+const CATE_ITEM_MODEL_KEY = DATA_MODEL_KEY.CART_ITEM;
+
+/** 获取购物车mock数据 */
+function mockFetchCartGroupData(params) {
+ const { delay } = require('../_utils/delay');
+ const { genCartGroupData } = require('../../model/cart');
+
+ return delay().then(() => genCartGroupData(params));
+}
+
+/**
+ *
+ * @param {{id: string}} param0
+ * @returns
+ */
+export async function getCartItem({ id }) {
+ return model()[CATE_ITEM_MODEL_KEY].get({
+ filter: {
+ where: {
+ _id: {
+ $eq: id,
+ },
+ },
+ },
+ select: {
+ _id: true,
+ count: true,
+ sku: {
+ _id: true,
+ count: true,
+ description: true,
+ },
+ },
+ });
+}
+
+export async function fetchCartItems() {
+ return getAll({
+ name: CATE_ITEM_MODEL_KEY,
+ select: {
+ _id: true,
+ count: true,
+ sku: {
+ _id: true,
+ count: true,
+ description: true,
+ },
+ },
+ });
+}
+
+/**
+ *
+ * @param {{
+ * skuId: String,
+ * count: Number
+ * }} param0
+ */
+export async function createCartItem({ skuId, count }) {
+ return await model()[CATE_ITEM_MODEL_KEY].create({
+ data: {
+ count,
+ sku: { _id: skuId },
+ },
+ });
+}
+
+/**
+ *
+ * @param {{cartItemId: string}} param0
+ */
+export async function deleteCartItem({ cartItemId }) {
+ return await model()[CATE_ITEM_MODEL_KEY].delete({
+ filter: {
+ where: {
+ _id: {
+ $eq: cartItemId,
+ },
+ },
+ },
+ });
+}
+
+/**
+ *
+ * @param {{
+ * cartItemId: String,
+ * count: Number
+ * }} param0
+ * @returns
+ */
+export async function updateCartItemCount({ cartItemId, count }) {
+ return await model()[CATE_ITEM_MODEL_KEY].update({
+ data: {
+ count,
+ },
+ filter: {
+ where: {
+ _id: {
+ $eq: cartItemId,
+ },
+ },
+ },
+ });
+}
+
+/** 获取购物车数据 */
+export function fetchCartGroupData(params) {
+ if (config.useMock) {
+ return mockFetchCartGroupData(params);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/cate/cate.js b/miniprogram/tcb-shop/services/cate/cate.js
new file mode 100644
index 0000000..331e1c9
--- /dev/null
+++ b/miniprogram/tcb-shop/services/cate/cate.js
@@ -0,0 +1,60 @@
+/* eslint-disable eqeqeq */
+import { model, getAll } from '../_utils/model';
+import { getCloudImageTempUrl } from '../../utils/cloudImageHandler';
+import { SPU_SELLING_STATUS } from '../../utils/spuStatus';
+import { DATA_MODEL_KEY } from '../../config/model';
+
+const CATE_MODEL_KEY = DATA_MODEL_KEY.CATE;
+
+// TODO: we should do pagination
+export async function getAllSpuOfCate(cateId) {
+ return (
+ await model()[CATE_MODEL_KEY].get({
+ filter: {
+ where: {
+ _id: {
+ $eq: cateId,
+ },
+ },
+ relateWhere: {
+ spu: {
+ where: {
+ status: {
+ $eq: SPU_SELLING_STATUS,
+ },
+ },
+ },
+ },
+ },
+ select: {
+ spu: {
+ $master: true,
+ },
+ },
+ })
+ ).data;
+}
+
+export async function getCates() {
+ const cateSelect = {
+ _id: true,
+ name: true,
+ image: true,
+ };
+
+ const allCates = (
+ await getAll({
+ name: CATE_MODEL_KEY,
+ select: {
+ ...cateSelect,
+ child_cate: cateSelect,
+ },
+ })
+ ).filter((c) => c.child_cate.length !== 0);
+
+ const childCates = allCates.flatMap((c) => c.child_cate);
+ const res = await getCloudImageTempUrl(childCates.map((x) => x.image));
+ res.forEach((image, index) => (childCates[index].image = image));
+
+ return allCates;
+}
diff --git a/miniprogram/tcb-shop/services/comments/comments.js b/miniprogram/tcb-shop/services/comments/comments.js
new file mode 100644
index 0000000..a4647fd
--- /dev/null
+++ b/miniprogram/tcb-shop/services/comments/comments.js
@@ -0,0 +1,109 @@
+import { model } from '../_utils/model';
+import { DATA_MODEL_KEY } from '../../config/model';
+
+const COMMENT_MODEL_KEY = DATA_MODEL_KEY.COMMENT;
+
+export async function getGoodsDetailCommentInfo(spuId) {
+ const firstAndCount = () =>
+ model()[COMMENT_MODEL_KEY].list({
+ filter: {
+ relateWhere: {
+ spu: {
+ where: {
+ _id: {
+ $eq: spuId,
+ },
+ },
+ },
+ },
+ },
+ select: {
+ $master: true,
+ spu: {
+ _id: true,
+ },
+ },
+ orderBy: [{ rating: 'desc' }],
+ pageNumber: 1,
+ pageSize: 1,
+ getCount: true,
+ });
+ const goodCount = () =>
+ model()[COMMENT_MODEL_KEY].list({
+ filter: {
+ relateWhere: {
+ spu: {
+ where: {
+ _id: {
+ $eq: spuId,
+ },
+ },
+ },
+ },
+ where: {
+ rating: {
+ $gt: 3,
+ },
+ },
+ },
+ select: {
+ $master: true,
+ },
+ pageNumber: 1,
+ pageSize: 1,
+ getCount: true,
+ });
+
+ return Promise.all([firstAndCount(), goodCount()]);
+}
+
+export async function getCommentsOfSpu({ spuId, pageNumber, pageSize }) {
+ return (
+ await model()[COMMENT_MODEL_KEY].list({
+ select: {
+ $master: true,
+ order_item: {
+ _id: true,
+ },
+ },
+ filter: {
+ relateWhere: {
+ spu: {
+ where: {
+ _id: {
+ $eq: spuId,
+ },
+ },
+ },
+ },
+ },
+ pageSize,
+ pageNumber,
+ getCount: true,
+ })
+ ).data;
+}
+
+/**
+ *
+ * @param {{
+ * orderItemId: string,
+ * content: string,
+ * rating: number,
+ * spuId: string
+ * }} param0
+ */
+export function createComment({ orderItemId, content, rating, spuId }) {
+ return model()[COMMENT_MODEL_KEY].create({
+ data: {
+ content,
+ rating,
+ order_item: {
+ _id: orderItemId,
+ },
+ spu: {
+ _id: spuId,
+ },
+ },
+ });
+}
diff --git a/miniprogram/tcb-shop/services/comments/fetchComments.js b/miniprogram/tcb-shop/services/comments/fetchComments.js
new file mode 100644
index 0000000..9bd1155
--- /dev/null
+++ b/miniprogram/tcb-shop/services/comments/fetchComments.js
@@ -0,0 +1,18 @@
+import { config } from '../../config/index';
+
+/** 获取商品评论 */
+function mockFetchComments(parmas) {
+ const { delay } = require('../_utils/delay');
+ const { getGoodsAllComments } = require('../../model/comments');
+ return delay().then(() => getGoodsAllComments(parmas));
+}
+
+/** 获取商品评论 */
+export function fetchComments(parmas) {
+ if (config.useMock) {
+ return mockFetchComments(parmas);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/comments/fetchCommentsCount.js b/miniprogram/tcb-shop/services/comments/fetchCommentsCount.js
new file mode 100644
index 0000000..cbb09d0
--- /dev/null
+++ b/miniprogram/tcb-shop/services/comments/fetchCommentsCount.js
@@ -0,0 +1,18 @@
+import { config } from '../../config/index';
+
+/** 获取商品评论数 */
+function mockFetchCommentsCount(ID = 0) {
+ const { delay } = require('../_utils/delay');
+ const { getGoodsCommentsCount } = require('../../model/comments');
+ return delay().then(() => getGoodsCommentsCount(ID));
+}
+
+/** 获取商品评论数 */
+export function fetchCommentsCount(ID = 0) {
+ if (config.useMock) {
+ return mockFetchCommentsCount(ID);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/coupon/index.js b/miniprogram/tcb-shop/services/coupon/index.js
new file mode 100644
index 0000000..a0010e0
--- /dev/null
+++ b/miniprogram/tcb-shop/services/coupon/index.js
@@ -0,0 +1,65 @@
+import { config } from '../../config/index';
+
+/** 获取优惠券列表 */
+function mockFetchCoupon(status) {
+ const { delay } = require('../_utils/delay');
+ const { getCouponList } = require('../../model/coupon');
+ return delay().then(() => getCouponList(status));
+}
+
+/** 获取优惠券列表 */
+export function fetchCouponList(status = 'default') {
+ if (config.useMock) {
+ return mockFetchCoupon(status);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
+
+/** 获取优惠券 详情 */
+function mockFetchCouponDetail(id, status) {
+ const { delay } = require('../_utils/delay');
+ const { getCoupon } = require('../../model/coupon');
+ const { genAddressList } = require('../../model/address');
+
+ return delay().then(() => {
+ const result = {
+ detail: getCoupon(id, status),
+ storeInfoList: genAddressList(),
+ };
+
+ result.detail.useNotes = `1个订单限用1张,除运费券外,不能与其它类型的优惠券叠加使用(运费券除外)\n2.仅适用于各区域正常售卖商品,不支持团购、抢购、预售类商品`;
+ result.detail.storeAdapt = `商城通用`;
+
+ if (result.detail.type === 'price') {
+ result.detail.desc = `减免 ${result.detail.value / 100} 元`;
+
+ if (result.detail.base) {
+ result.detail.desc += `,满${result.detail.base / 100}元可用`;
+ }
+
+ result.detail.desc += '。';
+ } else if (result.detail.type === 'discount') {
+ result.detail.desc = `${result.detail.value}折`;
+
+ if (result.detail.base) {
+ result.detail.desc += `,满${result.detail.base / 100}元可用`;
+ }
+
+ result.detail.desc += '。';
+ }
+
+ return result;
+ });
+}
+
+/** 获取优惠券 详情 */
+export function fetchCouponDetail(id, status = 'default') {
+ if (config.useMock) {
+ return mockFetchCouponDetail(id, status);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/good/comments/fetchCommentDetail.js b/miniprogram/tcb-shop/services/good/comments/fetchCommentDetail.js
new file mode 100644
index 0000000..fce338d
--- /dev/null
+++ b/miniprogram/tcb-shop/services/good/comments/fetchCommentDetail.js
@@ -0,0 +1,20 @@
+import { config } from '../../../config/index';
+import { queryCommentDetail } from '../../../model/comments/queryDetail';
+/** 获取商品评价数据 */
+function mockQueryCommentDetail(params) {
+ const { delay } = require('../../_utils/delay');
+ const data = queryCommentDetail(params);
+ return delay().then(() => {
+ return data;
+ });
+}
+
+/** 获取评价详情 */
+export function getCommentDetail(params) {
+ if (config.useMock) {
+ return mockQueryCommentDetail(params);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/good/fetchCategoryList.js b/miniprogram/tcb-shop/services/good/fetchCategoryList.js
new file mode 100644
index 0000000..adf6ba4
--- /dev/null
+++ b/miniprogram/tcb-shop/services/good/fetchCategoryList.js
@@ -0,0 +1,18 @@
+import { config } from '../../config/index';
+
+/** 获取商品列表 */
+function mockFetchGoodCategory() {
+ const { delay } = require('../_utils/delay');
+ const { getCategoryList } = require('../../model/category');
+ return delay().then(() => getCategoryList());
+}
+
+/** 获取商品列表 */
+export function getCategoryList() {
+ if (config.useMock) {
+ return mockFetchGoodCategory();
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/good/fetchGood.js b/miniprogram/tcb-shop/services/good/fetchGood.js
new file mode 100644
index 0000000..577801d
--- /dev/null
+++ b/miniprogram/tcb-shop/services/good/fetchGood.js
@@ -0,0 +1,24 @@
+import { config } from '../../config/index';
+
+/** 获取商品列表 */
+function mockFetchGood(ID = 0) {
+
+ const { delay } = require('../_utils/delay');
+ const { genGood } = require('../../model/good');
+
+ return delay().then(() => {
+ const res = genGood(ID);
+ console.log("mock fetch goood return res", res);
+ return res;
+ });
+}
+
+/** 获取商品列表 */
+export function fetchGood(ID = 0) {
+ if (config.useMock) {
+ return mockFetchGood(ID);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/good/fetchGoodsDetailsComments.js b/miniprogram/tcb-shop/services/good/fetchGoodsDetailsComments.js
new file mode 100644
index 0000000..95a11c4
--- /dev/null
+++ b/miniprogram/tcb-shop/services/good/fetchGoodsDetailsComments.js
@@ -0,0 +1,37 @@
+import { config } from '../../config/index';
+
+/** 获取商品详情页评论数 */
+function mockFetchGoodDetailsCommentsCount(spuId = 0) {
+ const { delay } = require('../_utils/delay');
+ const {
+ getGoodsDetailsCommentsCount,
+ } = require('../../model/detailsComments');
+ return delay().then(() => getGoodsDetailsCommentsCount(spuId));
+}
+
+/** 获取商品详情页评论数 */
+export function getGoodsDetailsCommentsCount(spuId = 0) {
+ if (config.useMock) {
+ return mockFetchGoodDetailsCommentsCount(spuId);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
+
+/** 获取商品详情页评论 */
+function mockFetchGoodDetailsCommentList(spuId = 0) {
+ const { delay } = require('../_utils/delay');
+ const { getGoodsDetailsComments } = require('../../model/detailsComments');
+ return delay().then(() => getGoodsDetailsComments(spuId));
+}
+
+/** 获取商品详情页评论 */
+export function getGoodsDetailsCommentList(spuId = 0) {
+ if (config.useMock) {
+ return mockFetchGoodDetailsCommentList(spuId);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/good/fetchGoodsList.js b/miniprogram/tcb-shop/services/good/fetchGoodsList.js
new file mode 100644
index 0000000..1650b9b
--- /dev/null
+++ b/miniprogram/tcb-shop/services/good/fetchGoodsList.js
@@ -0,0 +1,39 @@
+/* eslint-disable no-param-reassign */
+import { config } from '../../config/index';
+
+/** 获取商品列表 */
+function mockFetchGoodsList(params) {
+ const { delay } = require('../_utils/delay');
+ const { getSearchResult } = require('../../model/search');
+
+ const data = getSearchResult(params);
+
+ if (data.spuList.length) {
+ data.spuList.forEach((item) => {
+ item.spuId = item.spuId;
+ item.thumb = item.primaryImage;
+ item.title = item.title;
+ item.price = item.minSalePrice;
+ item.originPrice = item.maxLinePrice;
+ item.desc = '';
+ if (item.spuTagList) {
+ item.tags = item.spuTagList.map((tag) => tag.title);
+ } else {
+ item.tags = [];
+ }
+ });
+ }
+ return delay().then(() => {
+ return data;
+ });
+}
+
+/** 获取商品列表 */
+export function fetchGoodsList(params) {
+ if (config.useMock) {
+ return mockFetchGoodsList(params);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/good/fetchSearchHistory.js b/miniprogram/tcb-shop/services/good/fetchSearchHistory.js
new file mode 100644
index 0000000..3a73963
--- /dev/null
+++ b/miniprogram/tcb-shop/services/good/fetchSearchHistory.js
@@ -0,0 +1,35 @@
+import { config } from '../../config/index';
+
+/** 获取搜索历史 */
+function mockSearchHistory() {
+ const { delay } = require('../_utils/delay');
+ const { getSearchHistory } = require('../../model/search');
+ return delay().then(() => getSearchHistory());
+}
+
+/** 获取搜索历史 */
+export function getSearchHistory() {
+ if (config.useMock) {
+ return mockSearchHistory();
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
+
+/** 获取搜索历史 */
+function mockSearchPopular() {
+ const { delay } = require('../_utils/delay');
+ const { getSearchPopular } = require('../../model/search');
+ return delay().then(() => getSearchPopular());
+}
+
+/** 获取搜索历史 */
+export function getSearchPopular() {
+ if (config.useMock) {
+ return mockSearchPopular();
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/good/fetchSearchResult.js b/miniprogram/tcb-shop/services/good/fetchSearchResult.js
new file mode 100644
index 0000000..5b42851
--- /dev/null
+++ b/miniprogram/tcb-shop/services/good/fetchSearchResult.js
@@ -0,0 +1,38 @@
+/* eslint-disable no-param-reassign */
+import { config } from '../../config/index';
+
+/** 获取搜索历史 */
+function mockSearchResult(params) {
+ const { delay } = require('../_utils/delay');
+ const { getSearchResult } = require('../../model/search');
+
+ const data = getSearchResult(params);
+
+ if (data.spuList.length) {
+ data.spuList.forEach((item) => {
+ item.spuId = item.spuId;
+ item.thumb = item.primaryImage;
+ item.title = item.title;
+ item.price = item.minSalePrice;
+ item.originPrice = item.maxLinePrice;
+ if (item.spuTagList) {
+ item.tags = item.spuTagList.map((tag) => ({ title: tag.title }));
+ } else {
+ item.tags = [];
+ }
+ });
+ }
+ return delay().then(() => {
+ return data;
+ });
+}
+
+/** 获取搜索历史 */
+export function getSearchResult(params) {
+ if (config.useMock) {
+ return mockSearchResult(params);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/good/spu.js b/miniprogram/tcb-shop/services/good/spu.js
new file mode 100644
index 0000000..05ae792
--- /dev/null
+++ b/miniprogram/tcb-shop/services/good/spu.js
@@ -0,0 +1,77 @@
+import { model } from '../_utils/model';
+import { getCloudImageTempUrl } from '../../utils/cloudImageHandler';
+import { SPU_SELLING_STATUS } from '../../utils/spuStatus';
+import { DATA_MODEL_KEY } from '../../config/model';
+
+const SPU_MODEL_KEY = DATA_MODEL_KEY.SPU;
+const SKU_MODEL_KEY = DATA_MODEL_KEY.SKU;
+
+/**
+ *
+ * @param {{
+ * pageSize: Number,
+ * pageNumber: Number,
+ * cateId: String,
+ * search: String
+ * }}} param0
+ * @returns
+ */
+export async function listGood({ pageSize, pageNumber, search }) {
+ const filter = {
+ where: {
+ status: { $eq: SPU_SELLING_STATUS },
+ },
+ };
+ if (search) {
+ filter.where.name = { $search: search };
+ }
+
+ return (
+ await model()[SPU_MODEL_KEY].list({
+ filter,
+ pageSize,
+ pageNumber,
+ getCount: true,
+ orderBy: [{ priority: 'desc' }],
+ })
+ ).data;
+}
+
+export async function getPrice(spuId) {
+ const {
+ data: { records },
+ } = await model()[SKU_MODEL_KEY].list({
+ filter: {
+ where: {
+ spu: {
+ $eq: spuId,
+ },
+ },
+ },
+ });
+ return records[0].price;
+}
+
+export async function handleSpuCloudImage(spu) {
+ if (spu == null) {
+ return;
+ }
+ const handledImages = await getCloudImageTempUrl([spu.cover_image, ...spu.swiper_images]);
+ handledImages.forEach((image, index) => {
+ if (index === 0) {
+ spu.cover_image = image;
+ return;
+ }
+ spu.swiper_images[index - 1] = image;
+ });
+}
+
+export async function getSpu(spuId) {
+ return (
+ await model()[SPU_MODEL_KEY].get({
+ filter: {
+ where: { _id: { $eq: spuId } },
+ },
+ })
+ ).data;
+}
diff --git a/miniprogram/tcb-shop/services/home/home.js b/miniprogram/tcb-shop/services/home/home.js
new file mode 100644
index 0000000..ac3483d
--- /dev/null
+++ b/miniprogram/tcb-shop/services/home/home.js
@@ -0,0 +1,8 @@
+import { model } from '../_utils/model';
+import { DATA_MODEL_KEY } from '../../config/model';
+
+const HOME_SWIPER_MODEL_KEY = DATA_MODEL_KEY.HOME_SWIPER;
+
+export async function getHomeSwiper() {
+ return (await model()[HOME_SWIPER_MODEL_KEY].list({ select: { images: true } })).data.records[0];
+}
diff --git a/miniprogram/tcb-shop/services/order/applyService.js b/miniprogram/tcb-shop/services/order/applyService.js
new file mode 100644
index 0000000..c66930a
--- /dev/null
+++ b/miniprogram/tcb-shop/services/order/applyService.js
@@ -0,0 +1,70 @@
+import { config } from '../../config/index';
+
+/** 获取售后单mock数据 */
+function mockFetchRightsPreview(params) {
+ const { delay } = require('../_utils/delay');
+ const { genRightsPreview } = require('../../model/order/applyService');
+
+ return delay().then(() => genRightsPreview(params));
+}
+
+/** 获取售后单数据 */
+export function fetchRightsPreview(params) {
+ if (config.useMock) {
+ return mockFetchRightsPreview(params);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
+
+/** 确认收货 */
+export function dispatchConfirmReceived() {
+ if (config.useMock) {
+ const { delay } = require('../_utils/delay');
+ return delay();
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
+
+/** 获取可选的mock售后原因列表 */
+function mockFetchApplyReasonList(params) {
+ const { delay } = require('../_utils/delay');
+ const { genApplyReasonList } = require('../../model/order/applyService');
+
+ return delay().then(() => genApplyReasonList(params));
+}
+
+/** 获取可选的售后原因列表 */
+export function fetchApplyReasonList(params) {
+ if (config.useMock) {
+ return mockFetchApplyReasonList(params);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
+
+/** 发起mock售后申请 */
+function mockDispatchApplyService(params) {
+ const { delay } = require('../_utils/delay');
+ const { applyService } = require('../../model/order/applyService');
+
+ return delay().then(() => applyService(params));
+}
+
+/** 发起售后申请 */
+export function dispatchApplyService(params) {
+ if (config.useMock) {
+ return mockDispatchApplyService(params);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/order/order.js b/miniprogram/tcb-shop/services/order/order.js
new file mode 100644
index 0000000..265d882
--- /dev/null
+++ b/miniprogram/tcb-shop/services/order/order.js
@@ -0,0 +1,171 @@
+import { model, getAll } from '../../services/_utils/model';
+import { DATA_MODEL_KEY } from '../../config/model';
+
+const ORDER_MODEL_KEY = DATA_MODEL_KEY.ORDER;
+
+const ORDER_STATUS_INFO = {
+ TO_PAY: { value: 'TO_PAY', label: '待付款' },
+ TO_SEND: { value: 'TO_SEND', label: '待发货' },
+ TO_RECEIVE: { value: 'TO_RECEIVE', label: '待收货' },
+ FINISHED: { value: 'FINISHED', label: '已完成' },
+ CANCELED: { value: 'CANCELED', label: '已取消' },
+ RETURN_APPLIED: { value: 'RETURN_APPLIED', label: '申请退货' },
+ RETURN_REFUSED: { value: 'RETURN_REFUSED', label: '拒绝退货申请' },
+ RETURN_FINISH: { value: 'RETURN_FINISH', label: '退货完成' },
+ RETURN_MONEY_REFUSED: { value: 'RETURN_MONEY_REFUSED', label: '拒绝退款' },
+};
+
+export const ORDER_STATUS = new Proxy(ORDER_STATUS_INFO, {
+ get(target, prop) {
+ return target[prop]?.value;
+ },
+});
+
+export const orderStatusToName = (status) => Object.values(ORDER_STATUS_INFO).find((x) => x.value === status)?.label;
+
+/**
+ *
+ * @param {{
+ * status: String,
+ * addressId: String
+ * }} param0
+ * @returns
+ */
+export async function createOrder({ status, addressId }) {
+ return (
+ await model()[ORDER_MODEL_KEY].create({
+ data: {
+ status,
+ delivery_info: {
+ _id: addressId,
+ },
+ },
+ })
+ ).data;
+}
+
+export function getAllOrder() {
+ return getAll({
+ name: ORDER_MODEL_KEY,
+ });
+}
+
+/**
+ *
+ * @param {{
+ * pageSize: Number,
+ * pageNumber: Number,
+ * status?: String
+ * }}} param0
+ * @returns
+ */
+export async function listOrder({ pageSize, pageNumber, status }) {
+ if (status != null) {
+ return (
+ await model()[ORDER_MODEL_KEY].list({
+ filter: {
+ where: {
+ status: {
+ $eq: status,
+ },
+ },
+ },
+ pageSize,
+ pageNumber,
+ getCount: true,
+ })
+ ).data;
+ }
+ return (
+ await model()[ORDER_MODEL_KEY].list({
+ filter: {},
+ pageSize,
+ pageNumber,
+ getCount: true,
+ })
+ ).data;
+}
+
+async function getOrderCountOfStatus(status) {
+ return (
+ await model()[ORDER_MODEL_KEY].list({
+ filter: { where: { status: { $eq: status } } },
+ select: { _id: true },
+ getCount: true,
+ })
+ ).data.total;
+}
+
+export async function getToPayOrderCount() {
+ return getOrderCountOfStatus(ORDER_STATUS.TO_PAY);
+}
+
+export async function getToSendOrderCount() {
+ return getOrderCountOfStatus(ORDER_STATUS.TO_SEND);
+}
+
+export async function getToReceiveOrderCount() {
+ return getOrderCountOfStatus(ORDER_STATUS.TO_RECEIVE);
+}
+
+/**
+ *
+ * @param {String} orderId
+ */
+export async function getOrder(orderId) {
+ return (
+ await model()[ORDER_MODEL_KEY].get({
+ filter: {
+ where: {
+ _id: { $eq: orderId },
+ },
+ },
+ select: {
+ $master: true,
+ delivery_info: {
+ _id: true,
+ phone: true,
+ address: true,
+ name: true,
+ },
+ },
+ })
+ ).data;
+}
+
+export async function updateOrderDeliveryInfo({ orderId, deliveryInfoId }) {
+ return model()[ORDER_MODEL_KEY].update({
+ data: {
+ delivery_info: {
+ _id: deliveryInfoId,
+ },
+ },
+ filter: {
+ where: {
+ _id: {
+ $eq: orderId,
+ },
+ },
+ },
+ });
+}
+
+/**
+ *
+ * @param {{orderId: String, status: String}}} param0
+ * @returns
+ */
+export async function updateOrderStatus({ orderId, status }) {
+ return await model()[ORDER_MODEL_KEY].update({
+ data: {
+ status,
+ },
+ filter: {
+ where: {
+ _id: {
+ $eq: orderId,
+ },
+ },
+ },
+ });
+}
diff --git a/miniprogram/tcb-shop/services/order/orderConfirm.js b/miniprogram/tcb-shop/services/order/orderConfirm.js
new file mode 100644
index 0000000..4d34744
--- /dev/null
+++ b/miniprogram/tcb-shop/services/order/orderConfirm.js
@@ -0,0 +1,69 @@
+import { config } from '../../config/index';
+import { mockIp, mockReqId } from '../../utils/mock';
+
+/** 获取结算mock数据 */
+function mockFetchSettleDetail(params) {
+ const { delay } = require('../_utils/delay');
+ const { genSettleDetail } = require('../../model/order/orderConfirm');
+
+ return delay().then(() => genSettleDetail(params));
+}
+
+/** 提交mock订单 */
+function mockDispatchCommitPay() {
+ const { delay } = require('../_utils/delay');
+
+ return delay().then(() => ({
+ data: {
+ isSuccess: true,
+ tradeNo: '350930961469409099',
+ payInfo: '{}',
+ code: null,
+ transactionId: 'E-200915180100299000',
+ msg: null,
+ interactId: '15145',
+ channel: 'wechat',
+ limitGoodsList: null,
+ },
+ code: 'Success',
+ msg: null,
+ requestId: mockReqId(),
+ clientIp: mockIp(),
+ rt: 891,
+ success: true,
+ }));
+}
+
+/** 获取结算数据 */
+export function fetchSettleDetail(params) {
+ if (config.useMock) {
+ return mockFetchSettleDetail(params);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
+
+/* 提交订单 */
+export function dispatchCommitPay(params) {
+ if (config.useMock) {
+ return mockDispatchCommitPay(params);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
+
+/** 开发票 */
+export function dispatchSupplementInvoice() {
+ if (config.useMock) {
+ const { delay } = require('../_utils/delay');
+ return delay();
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/order/orderDetail.js b/miniprogram/tcb-shop/services/order/orderDetail.js
new file mode 100644
index 0000000..3e7d797
--- /dev/null
+++ b/miniprogram/tcb-shop/services/order/orderDetail.js
@@ -0,0 +1,39 @@
+import { config } from '../../config/index';
+
+/** 获取订单详情mock数据 */
+function mockFetchOrderDetail(params) {
+ const { delay } = require('../_utils/delay');
+ const { genOrderDetail } = require('../../model/order/orderDetail');
+
+ return delay().then(() => genOrderDetail(params));
+}
+
+/** 获取订单详情数据 */
+export function fetchOrderDetail(params) {
+ if (config.useMock) {
+ return mockFetchOrderDetail(params);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
+
+/** 获取客服mock数据 */
+function mockFetchBusinessTime(params) {
+ const { delay } = require('../_utils/delay');
+ const { genBusinessTime } = require('../../model/order/orderDetail');
+
+ return delay().then(() => genBusinessTime(params));
+}
+
+/** 获取客服数据 */
+export function fetchBusinessTime(params) {
+ if (config.useMock) {
+ return mockFetchBusinessTime(params);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/order/orderItem.js b/miniprogram/tcb-shop/services/order/orderItem.js
new file mode 100644
index 0000000..6587585
--- /dev/null
+++ b/miniprogram/tcb-shop/services/order/orderItem.js
@@ -0,0 +1,77 @@
+import { model, getAll } from '../../services/_utils/model';
+import { getSkuDetail } from '../sku/sku';
+import { DATA_MODEL_KEY } from '../../config/model';
+
+const ORDER_ITEM_MODEL_KEY = DATA_MODEL_KEY.ORDER_ITEM;
+
+export async function getOrderItem(id) {
+ return (
+ await model()[ORDER_ITEM_MODEL_KEY].get({
+ filter: {
+ where: {
+ _id: {
+ $eq: id,
+ },
+ },
+ },
+ select: {
+ sku: {
+ _id: true,
+ },
+ comment: {
+ _id: true,
+ },
+ },
+ })
+ ).data;
+}
+
+export async function createOrderItem({ count, skuId, orderId }) {
+ return model()[ORDER_ITEM_MODEL_KEY].create({
+ data: {
+ count,
+ sku: {
+ _id: skuId,
+ },
+ order: {
+ _id: orderId,
+ },
+ },
+ });
+}
+
+export function getAllOrderItems() {
+ return getAll({ name: ORDER_ITEM_MODEL_KEY });
+}
+
+/**
+ *
+ * @param {{orderId: String}} param0
+ */
+export async function getAllOrderItemsOfAnOrder({ orderId }) {
+ const orderItems = await getAll({
+ name: ORDER_ITEM_MODEL_KEY,
+ filter: {
+ where: {
+ order: {
+ $eq: orderId,
+ },
+ },
+ },
+ select: {
+ _id: true,
+ count: true,
+ sku: {
+ _id: true,
+ },
+ },
+ });
+ await Promise.all(
+ orderItems.map(async (orderItem) => {
+ const skuId = orderItem.sku._id;
+ const sku = await getSkuDetail(skuId);
+ orderItem.sku = sku;
+ }),
+ );
+ return orderItems;
+}
diff --git a/miniprogram/tcb-shop/services/order/orderList.js b/miniprogram/tcb-shop/services/order/orderList.js
new file mode 100644
index 0000000..f4a9e7a
--- /dev/null
+++ b/miniprogram/tcb-shop/services/order/orderList.js
@@ -0,0 +1,39 @@
+import { config } from '../../config/index';
+
+/** 获取订单列表mock数据 */
+function mockFetchOrders(params) {
+ const { delay } = require('../_utils/delay');
+ const { genOrders } = require('../../model/order/orderList');
+
+ return delay(200).then(() => genOrders(params));
+}
+
+/** 获取订单列表数据 */
+export function fetchOrders(params) {
+ if (config.useMock) {
+ return mockFetchOrders(params);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
+
+/** 获取订单列表mock数据 */
+function mockFetchOrdersCount(params) {
+ const { delay } = require('../_utils/delay');
+ const { genOrdersCount } = require('../../model/order/orderList');
+
+ return delay().then(() => genOrdersCount(params));
+}
+
+/** 获取订单列表统计 */
+export function fetchOrdersCount(params) {
+ if (config.useMock) {
+ return mockFetchOrdersCount(params);
+ }
+
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/order/orderSubmitComment.js b/miniprogram/tcb-shop/services/order/orderSubmitComment.js
new file mode 100644
index 0000000..5e3ea36
--- /dev/null
+++ b/miniprogram/tcb-shop/services/order/orderSubmitComment.js
@@ -0,0 +1,22 @@
+import { config } from '../../config/index';
+
+/** 获取评价商品 */
+function mockGetGoods(parameter) {
+ const { delay } = require('../_utils/delay');
+ const { getGoods } = require('../../model/submitComment');
+ const data = getGoods(parameter);
+
+ return delay().then(() => {
+ return data;
+ });
+}
+
+/** 获取评价商品 */
+export function getGoods(parameter) {
+ if (config.useMock) {
+ return mockGetGoods(parameter);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/pay/pay.js b/miniprogram/tcb-shop/services/pay/pay.js
new file mode 100644
index 0000000..bcc9552
--- /dev/null
+++ b/miniprogram/tcb-shop/services/pay/pay.js
@@ -0,0 +1,44 @@
+/**
+ *
+ * @param {{_id: String, totalPrice: Number}} order
+ * @returns
+ */
+export async function pay(order) {
+ try {
+ const res = await wx.cloud.callFunction({
+ // 云函数名称
+ name: 'wxpayFunctions',
+ data: {
+ type: 'wxpay_order',
+ order,
+ },
+ });
+ const paymentData = res.result?.data;
+ // 唤起微信支付组件,完成支付
+ try {
+ await wx.requestPayment({
+ timeStamp: paymentData?.timeStamp,
+ nonceStr: paymentData?.nonceStr,
+ package: paymentData?.packageVal,
+ paySign: paymentData?.paySign,
+ signType: 'RSA', // 该参数为固定值
+ });
+ } catch (e) {
+ return Promise.reject(e);
+ }
+ } catch (e) {
+ return Promise.reject(e);
+ }
+}
+
+export async function refund(orderId) {
+ return wx.cloud.callFunction({
+ // 云函数名称
+ name: 'wxpayFunctions',
+ data: {
+ // 调用云函数中的申请退款方法
+ type: 'wxpay_refund',
+ orderId,
+ },
+ });
+}
diff --git a/miniprogram/tcb-shop/services/promotion/detail.js b/miniprogram/tcb-shop/services/promotion/detail.js
new file mode 100644
index 0000000..841f723
--- /dev/null
+++ b/miniprogram/tcb-shop/services/promotion/detail.js
@@ -0,0 +1,18 @@
+import { config } from '../../config/index';
+
+/** 获取商品列表 */
+function mockFetchPromotion(ID = 0) {
+ const { delay } = require('../_utils/delay');
+ const { getPromotion } = require('../../model/promotion');
+ return delay().then(() => getPromotion(ID));
+}
+
+/** 获取商品列表 */
+export function fetchPromotion(ID = 0) {
+ if (config.useMock) {
+ return mockFetchPromotion(ID);
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/sku/sku.js b/miniprogram/tcb-shop/services/sku/sku.js
new file mode 100644
index 0000000..60d550d
--- /dev/null
+++ b/miniprogram/tcb-shop/services/sku/sku.js
@@ -0,0 +1,57 @@
+import { model, getAll } from '../_utils/model';
+import { DATA_MODEL_KEY } from '../../config/model';
+
+const SKU_MODEL_KEY = DATA_MODEL_KEY.SKU;
+
+/**
+ *
+ * @param {String} skuId
+ */
+export async function getSkuDetail(skuId) {
+ const { data } = await model()[SKU_MODEL_KEY].get({
+ filter: {
+ where: {
+ _id: { $eq: skuId },
+ },
+ },
+ select: {
+ _id: true,
+ count: true,
+ price: true,
+ image: true,
+ attr_value: {
+ value: true,
+ _id: true,
+ },
+ spu: {
+ name: true,
+ },
+ },
+ });
+ return data;
+}
+
+export async function updateSku({ skuId, data }) {
+ return wx.cloud.callFunction({
+ // 云函数名称
+ name: 'shop_update_sku',
+ // 传给云函数的参数
+ data: {
+ skuId,
+ data,
+ },
+ });
+}
+
+export async function getAllSku(spuId) {
+ return getAll({
+ name: SKU_MODEL_KEY,
+ filter: {
+ where: {
+ spu: {
+ $eq: spuId,
+ },
+ },
+ },
+ });
+}
diff --git a/miniprogram/tcb-shop/services/usercenter/fetchPerson.js b/miniprogram/tcb-shop/services/usercenter/fetchPerson.js
new file mode 100644
index 0000000..bd31178
--- /dev/null
+++ b/miniprogram/tcb-shop/services/usercenter/fetchPerson.js
@@ -0,0 +1,28 @@
+import { config } from '../../config/index';
+
+/** 获取个人中心信息 */
+function mockFetchPerson() {
+ const { delay } = require('../_utils/delay');
+ const { genSimpleUserInfo } = require('../../model/usercenter');
+ const { genAddress } = require('../../model/address');
+ const address = genAddress();
+ return delay().then(() => ({
+ ...genSimpleUserInfo(),
+ address: {
+ provinceName: address.provinceName,
+ provinceCode: address.provinceCode,
+ cityName: address.cityName,
+ cityCode: address.cityCode,
+ },
+ }));
+}
+
+/** 获取个人中心信息 */
+export function fetchPerson() {
+ if (config.useMock) {
+ return mockFetchPerson();
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/services/usercenter/fetchUsercenter.js b/miniprogram/tcb-shop/services/usercenter/fetchUsercenter.js
new file mode 100644
index 0000000..b187644
--- /dev/null
+++ b/miniprogram/tcb-shop/services/usercenter/fetchUsercenter.js
@@ -0,0 +1,18 @@
+import { config } from '../../config/index';
+
+/** 获取个人中心信息 */
+function mockFetchUserCenter() {
+ const { delay } = require('../_utils/delay');
+ const { genUsercenter } = require('../../model/usercenter');
+ return delay(200).then(() => genUsercenter());
+}
+
+/** 获取个人中心信息 */
+export function fetchUserCenter() {
+ if (config.useMock) {
+ return mockFetchUserCenter();
+ }
+ return new Promise((resolve) => {
+ resolve('real api');
+ });
+}
diff --git a/miniprogram/tcb-shop/sitemap.json b/miniprogram/tcb-shop/sitemap.json
new file mode 100644
index 0000000..ca02add
--- /dev/null
+++ b/miniprogram/tcb-shop/sitemap.json
@@ -0,0 +1,7 @@
+{
+ "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+ "rules": [{
+ "action": "allow",
+ "page": "*"
+ }]
+}
\ No newline at end of file
diff --git a/miniprogram/tcb-shop/style/cart-group.wxss b/miniprogram/tcb-shop/style/cart-group.wxss
new file mode 100644
index 0000000..ad585a5
--- /dev/null
+++ b/miniprogram/tcb-shop/style/cart-group.wxss
@@ -0,0 +1,83 @@
+/* var() css变量适配*/
+.wr-swiper-cell__right {
+ margin: 20rpx 0;
+}
+.wr-swiper-cell__right .swiper-right-del {
+ height: calc(100% - 40rpx);
+ width: 60px;
+ background-color: #ff2525;
+ 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;
+}
+.invalid-card .invalid-private-mask .wr-goods-card {
+ background-color: #fff;
+ padding: 18rpx 32rpx 24rpx 80rpx;
+}
+.text-primary .wr-icon {
+ color: #fa550f;
+ color: var(--color-primary, #fa550f);
+}
diff --git a/miniprogram/tcb-shop/style/global.wxss b/miniprogram/tcb-shop/style/global.wxss
new file mode 100644
index 0000000..0194db1
--- /dev/null
+++ b/miniprogram/tcb-shop/style/global.wxss
@@ -0,0 +1,960 @@
+/*
+ * @Author: oliverppeng
+ * @LastEditors: Please set LastEditors
+ * @Date: 2021-12-01 17:33:43
+ * @LastEditTime: 2021-12-03 15:31:17
+ * @Description:
+ * @FilePath: /retail-mp/style/global.wxss
+ */
+.text-primary {
+ color: #fa550f;
+}
+.text-success {
+ color: #5fb446;
+}
+.text-warn {
+ color: #ec8131;
+}
+.text-danger {
+ color: #de1c24;
+}
+.text-title {
+ color: #282828;
+}
+.text-normal {
+ color: #5d5d5d;
+}
+.text-small {
+ color: #9b9b9b;
+}
+.text-minor {
+ color: #ececec;
+}
+.text-border {
+ color: #eeeeee;
+}
+.text-white {
+ color: #fff;
+}
+.bg-primary {
+ background-color: #fa550f;
+ color: #fff;
+}
+.bg-success {
+ background-color: #5fb446;
+ color: #fff;
+}
+.bg-warn {
+ background-color: #ec8131;
+ color: #fff;
+}
+.bg-danger {
+ background-color: #de1c24;
+ color: #fff;
+}
+.bg-title {
+ background-color: #282828;
+ color: #fff;
+}
+.bg-normal {
+ background-color: #5d5d5d;
+ color: #282828;
+}
+.bg-small {
+ background-color: #9b9b9b;
+ color: #5d5d5d;
+}
+.bg-minor {
+ background-color: #ececec;
+ color: #5d5d5d;
+}
+.bg-border {
+ background-color: #eeeeee;
+ color: #5d5d5d;
+}
+.bd-primary {
+ color: #fa550f;
+}
+.bd-success {
+ color: #5fb446;
+}
+.bd-warn {
+ color: #ec8131;
+}
+.bd-danger {
+ color: #de1c24;
+}
+.bd-title {
+ color: #282828;
+}
+.bd-normal {
+ color: #5d5d5d;
+}
+.bd-small {
+ color: #9b9b9b;
+}
+.bd-minor {
+ color: #ececec;
+}
+.bd-border {
+ color: #eeeeee;
+}
+.ft-super {
+ font-size: 40rpx;
+}
+.ft-main {
+ font-size: 36rpx;
+}
+.ft-normal {
+ font-size: 32rpx;
+}
+.ft-assist {
+ font-size: 28rpx;
+}
+.ft-minor {
+ font-size: 24rpx;
+}
+.ft-mini {
+ font-size: 20rpx;
+}
+.fw-super {
+ font-weight: 800;
+}
+.fw-main {
+ font-weight: 600;
+}
+.fw-normal {
+ font-weight: 400;
+}
+.fw-minor {
+ font-weight: 300;
+}
+.mo-border-1rpx {
+ position: relative;
+ z-index: 0;
+}
+.mo-border-1rpx::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 200%;
+ height: 200%;
+ border-width: 2rpx;
+ border-style: solid;
+ transform: scale(0.5);
+ transform-origin: 0 0;
+ z-index: -1;
+ box-sizing: border-box;
+}
+/* 层级定义
+ @z-index-0: 1;
+ @z-index-1: 100;
+ @z-index-2: 200;
+ @z-index-5: 500;
+ @z-index-component: 1000; // 通用组件级别
+ @z-index-dropdown: @z-index-component;
+ @z-index-sticky: @z-index-component + 20;
+ @z-index-fixed: @z-index-component + 30;
+ @z-index-modal-backdrop:@z-index-component + 40;
+ @z-index-modal:@z-index-component + 50;
+ @z-index-popover:@z-index-component + 60;
+ @z-index-tooltip:@z-index-component + 70;
+ */
+page {
+ height: 100%;
+ font-family: 'Microsoft YaHei', '微软雅黑', 'MicrosoftJhengHei', '华文细黑', Helvetica, Arial, 'sans-serif';
+ font-size: 26rpx;
+ background-color: #f8f8f8;
+ font-weight: 400;
+}
+view,
+image,
+icon,
+scroll-view,
+text,
+button,
+checkbox,
+form,
+input,
+label,
+navigator,
+audio,
+video,
+canvas {
+ box-sizing: border-box;
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+}
+scroll-view {
+ height: 100%;
+}
+form,
+image {
+ display: block;
+}
+button {
+ padding: 0;
+ margin: 0;
+ border-radius: 0;
+ height: 100%;
+ display: block;
+ line-height: inherit;
+ font-size: inherit;
+ color: inherit;
+ background: none;
+ -webkit-appearance: none;
+ border: none;
+}
+button::after {
+ content: none;
+}
+input,
+textarea {
+ font-family: 'Microsoft YaHei', '微软雅黑', 'MicrosoftJhengHei', '华文细黑', Helvetica, Arial, 'sans-serif';
+ font-size: 26rpx;
+ z-index: 0;
+}
+.price {
+ color: #ec8131;
+ font-size: 32rpx;
+ font-weight: 600;
+}
+.price-del {
+ color: #9b9b9b;
+ font-size: 24rpx;
+ font-weight: 400;
+}
+.page {
+ background: #fff;
+}
+.color-price {
+ color: #ec8131;
+}
+.bg-cart {
+ background-color: #ffc220;
+ color: #fff;
+}
+.market-addcart {
+ color: #ec8131;
+ font-size: 42rpx;
+}
+.ovh {
+ overflow: hidden;
+}
+.hidden {
+ display: none;
+}
+.show {
+ display: block;
+}
+.text {
+ display: inline-block;
+}
+.inline {
+ display: inline;
+}
+.minHeight {
+ min-height: 101%;
+}
+.imgCover {
+ width: 100%;
+ padding-bottom: 100%;
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ display: block;
+ position: relative;
+}
+.imgCover-list {
+ width: 195rpx;
+ height: 260rpx;
+ padding-bottom: 0;
+ background-size: cover;
+ background-position: center;
+ background-repeat: no-repeat;
+ display: block;
+ position: relative;
+}
+.circular {
+ border-radius: 50%;
+}
+.text-line1 {
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ word-break: break-all;
+}
+.text-line-1 {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 1;
+ -webkit-box-orient: vertical;
+ word-break: break-all;
+}
+.text-line2 {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ word-break: break-all;
+}
+.text-line3 {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ word-break: break-all;
+}
+.flex,
+.box {
+ display: flex;
+ display: -webkit-flex;
+}
+.flex-v-center {
+ align-items: center;
+ -webkit-align-items: center;
+}
+.flex-center {
+ justify-content: center;
+ -webkit-justify-content: center;
+ align-items: center;
+ -webkit-align-items: center;
+}
+.flex-between {
+ justify-content: space-between;
+ -webkit-justify-content: space-between;
+}
+.flex-v-between {
+ align-content: space-between;
+ -webkit-align-content: space-between;
+}
+.flex-end {
+ justify-content: flex-end;
+ -webkit-justify-content: flex-end;
+}
+.flex-col {
+ flex-direction: column;
+ -webkit-flex-direction: column;
+}
+.flex1 {
+ flex: 1;
+ -webkit-flex: 1;
+}
+.flex0 {
+ flex: none;
+ -webkit-flex: none;
+}
+.flex-start {
+ justify-content: flex-start;
+ -webkit-justify-content: flex-start;
+}
+.border-around,
+.border-bottom-1px,
+.border-left-1px,
+.border-right-1px,
+.border-top-1px {
+ position: relative;
+ border: 1rpx solid #e9e9e9;
+}
+.border-top-1px {
+ border-width: 1rpx 0 0 0;
+}
+.border-right-1px {
+ border-width: 0 1rpx 0 0;
+}
+.border-bottom-1px {
+ border-width: 0 0 1rpx 0;
+}
+.border-left-1px {
+ border-width: 0 0 0 1rpx;
+}
+.border-t-1px,
+.border-r-1px,
+.border-b-1px,
+.border-l-1px {
+ position: relative;
+}
+.border-t-1px::after,
+.border-r-1px::after,
+.border-b-1px::after,
+.border-l-1px::after {
+ content: '';
+ position: absolute;
+ border-width: 2rpx;
+ border-color: #efefef;
+ border-style: solid;
+}
+.border-t-1px::after {
+ left: 0;
+ top: 0;
+ width: 100%;
+ transform: scaleY(0.5);
+}
+.border-b-1px::after {
+ left: 0;
+ bottom: 0;
+ width: 100%;
+ transform: scaleY(0.5);
+}
+.border-l-1px::after {
+ left: 0;
+ top: 0;
+ height: 100%;
+ transform: scaleX(0.5);
+}
+.border-r-1px::after {
+ right: 0;
+ top: 0;
+ height: 100%;
+ transform: scaleX(0.5);
+}
+.arrows {
+ position: relative;
+}
+.arrows::after {
+ content: '';
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ border: 2px solid #c3c3c3;
+ border-width: 2px 2px 0 0;
+ position: absolute;
+ top: 50%;
+ right: 26rpx;
+ margin-top: -3px;
+ -webkit-transform: rotate(45deg);
+ transform: rotate(45deg);
+}
+.arrows-inline::after {
+ position: relative;
+ left: auto;
+ top: auto;
+ right: auto;
+ bottom: auto;
+ margin-top: -4px;
+ margin-left: 5px;
+}
+.pt-16,
+.pt-8 {
+ padding-top: 16rpx;
+}
+.pb-16,
+.pb-8 {
+ padding-bottom: 16rpx;
+}
+.pl-16,
+.pl-8 {
+ padding-left: 16rpx;
+}
+.pr-16,
+.pr-8 {
+ padding-right: 16rpx;
+}
+.pl-20,
+.pl-10 {
+ padding-left: 20rpx;
+}
+.pr-20,
+.pr-10 {
+ padding-right: 20rpx;
+}
+.pl-30 {
+ padding-left: 30rpx;
+}
+.pr-30 {
+ padding-right: 30rpx;
+}
+.pl-32,
+.pl-15 {
+ padding-left: 32rpx;
+}
+.pr-32,
+.pr-15 {
+ padding-right: 32rpx;
+}
+.pb360 {
+ padding-bottom: 360rpx;
+}
+.PriceSwitch {
+ line-height: 21px;
+ font-size: 24rpx;
+ padding: 0 8rpx;
+}
+.PriceSwitch .i {
+ font-size: 30rpx;
+}
+.Original {
+ font-style: normal;
+ font-size: 24rpx;
+ color: #9a9a9a;
+ text-decoration: line-through;
+ margin-left: 16rpx;
+ display: inline-block;
+}
+.color1,
+h1,
+h2,
+h3,
+h4,
+h5,
+h6 {
+ font-weight: normal;
+ color: #1a1a1a;
+ margin: 0;
+ padding: 0;
+}
+.color-3 {
+ color: #333;
+}
+.color-6 {
+ color: #666;
+}
+.color-9 {
+ color: #999;
+}
+.color-e9 {
+ color: #e9e9e9;
+}
+.color-64 {
+ color: #646464;
+}
+.color-b4 {
+ color: #b4b4b4;
+}
+.color-97 {
+ color: #979797;
+}
+.color-9b {
+ color: #9b9b9b;
+}
+.color-white {
+ color: #fff;
+}
+.color-black {
+ color: #000;
+}
+.color-c {
+ color: #ccc;
+}
+.bkg-white {
+ background-color: #fff;
+}
+.font10 {
+ font-size: 20rpx;
+}
+.font11 {
+ font-size: 22rpx;
+}
+.font12 {
+ font-size: 24rpx;
+}
+.font13 {
+ font-size: 26rpx;
+}
+.font14 {
+ font-size: 28rpx;
+}
+.font15 {
+ font-size: 30rpx;
+}
+.font16 {
+ font-size: 32rpx;
+}
+.font17 {
+ font-size: 34rpx;
+}
+.font18 {
+ font-size: 36rpx;
+}
+.font19 {
+ font-size: 38rpx;
+}
+.font20 {
+ font-size: 20rpx;
+}
+.font22 {
+ font-size: 22rpx;
+}
+.font24 {
+ font-size: 24rpx;
+}
+.font26 {
+ font-size: 26rpx;
+}
+.font28 {
+ font-size: 28rpx;
+}
+.font30 {
+ font-size: 30rpx;
+}
+.font32 {
+ font-size: 32rpx;
+}
+.font34 {
+ font-size: 34rpx;
+}
+.font36 {
+ font-size: 36rpx;
+}
+.font38 {
+ font-size: 38rpx;
+}
+.font40 {
+ font-size: 40rpx;
+}
+.font46 {
+ font-size: 46rpx;
+}
+.font50 {
+ font-size: 50rpx;
+}
+.font56 {
+ font-size: 56rpx;
+}
+.font82 {
+ font-size: 82rpx;
+}
+.bkg-white {
+ background-color: #fff;
+}
+.fontWeight-l {
+ font-weight: 500;
+}
+.fontWeight-n {
+ font-weight: 300;
+}
+.fontWeight-nor {
+ font-weight: normal;
+}
+.line-height1 {
+ line-height: 1;
+}
+.btn-active {
+ width: 40rpx;
+ display: inline-block;
+ text-align: center;
+ height: 40rpx;
+ background: #ff2e45;
+ border-radius: 8rpx;
+ color: #fff;
+ line-height: 40rpx;
+ font-size: 24rpx;
+ font-weight: normal;
+ font-style: normal;
+ overflow: hidden;
+}
+.btn-auto {
+ display: inline-block;
+ font-size: 20rpx;
+ border: 1px solid #ff2e45;
+ border-radius: 8rpx;
+ height: 36rpx;
+ line-height: 36rpx;
+ color: #ff2e45;
+ padding: 0 12rpx;
+ margin-right: 16rpx;
+}
+.btn-lg,
+.btn-md,
+.btn-sm,
+.btn-xs {
+ text-align: center;
+ width: 100%;
+ border-radius: 8rpx;
+ color: #fff;
+ line-height: 88rpx;
+ font-size: 30rpx;
+}
+.btn-lg,
+.btn-md {
+ background-color: #ff2e45;
+}
+.btn-sm {
+ border: 1px solid #999;
+ color: #666;
+ height: 60rpx;
+ line-height: 60rpx;
+}
+.btn-xs {
+ width: 100%;
+ height: 68rpx;
+ line-height: 68rpx;
+ border: 1px solid #e2e2e2;
+ color: #666;
+}
+.btn-dashed,
+.btn-md-dashed {
+ width: 100%;
+ height: 80rpx;
+ line-height: 80rpx;
+ text-align: center;
+ color: #ff2e45;
+ border-radius: 8rpx;
+ border: 1px solid #ff2e45;
+ font-size: 26rpx;
+}
+.btn-md-dashed {
+ height: 50rpx;
+ line-height: 50rpx;
+ border-radius: 8rpx;
+}
+.btn-bj1 {
+ background-color: #ff2e45;
+}
+.btn-bj2 {
+ background-color: #ff8522;
+}
+.btn-dis {
+ background-color: #999;
+ color: #bbb;
+}
+.btn-dashed-dis {
+ border: 1px solid #c8c8c8;
+ color: #bbbbbb;
+ border-radius: 8rpx;
+}
+.titleTag {
+ line-height: 28rpx;
+ height: 28rpx;
+ margin-right: 8rpx;
+ color: #fff;
+ padding: 0 8rpx;
+ font-size: 18rpx;
+ border-radius: 4rpx;
+ font-weight: bold;
+ display: inline-block;
+}
+.titleTag.memberTag {
+ margin-top: 2rpx;
+}
+.amounts {
+ font-style: normal;
+ display: inline-block;
+ height: 24rpx;
+ min-width: 24rpx;
+ padding: 0 6rpx;
+ border-radius: 24rpx;
+ background-color: #fd1d45 !important;
+ color: #fff;
+ text-align: center;
+ font-size: 20rpx;
+ font-weight: 600;
+ position: absolute;
+ top: 6rpx;
+ line-height: 24rpx;
+ box-sizing: border-box;
+}
+.amounts:empty,
+.titleTag:empty {
+ display: none;
+}
+.loadMore {
+ font-size: 26rpx;
+ color: #c1c1c1;
+ text-align: center;
+ height: 80rpx;
+ line-height: 58rpx;
+ margin-top: 20rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+.loadings {
+ margin-right: 10rpx;
+ width: 40rpx;
+ height: 40rpx;
+ display: inline-block;
+ vertical-align: sub;
+ background: url(https://cdn.ghsmpwalmart.com/saas/market/image/loadings.png) no-repeat;
+ background-size: 240rpx 40rpx;
+ background-position: 0 0;
+ animation: circle-loadings 1s steps(6) infinite;
+}
+@keyframes circle-loadings {
+ from {
+ background-position: 0 0;
+ }
+ to {
+ background-position: -240rpx 0;
+ }
+}
+.page-loading {
+ display: inline-block;
+ width: 72rpx;
+ height: 75rpx;
+ background: url('https://cdn.ghsmpwalmart.com/saas/market/image/page-loading-bc.png') no-repeat;
+ background-position: 0 0;
+ background-size: 72rpx 75rpx;
+}
+.page-loading::before {
+ content: ' ';
+ display: inline-block;
+ width: 72rpx;
+ height: 75rpx;
+ background: url('https://cdn.ghsmpwalmart.com/saas/market/image/page-loading-spin.png') no-repeat;
+ background-position: 0 0;
+ background-size: 720rpx 75rpx;
+ animation: animate-page-loading 0.4s steps(10) infinite;
+}
+@keyframes animate-page-loading {
+ from {
+ background-position-x: 0rpx;
+ }
+ to {
+ background-position-x: -720rpx;
+ }
+}
+.page-loading-wrap {
+ width: 100%;
+ height: 1026rpx;
+ display: flex;
+ display: -webkit-flex;
+ justify-content: center;
+ -webkit-justify-content: center;
+ padding-top: 46vh;
+}
+.grooms {
+ margin: 0 16rpx;
+ overflow: hidden;
+ text-align: center;
+ height: 52rpx;
+}
+.grooms label {
+ display: inline-block;
+ padding: 0 20rpx;
+ height: 52rpx;
+ line-height: 52rpx;
+ position: relative;
+ color: #3e3e3e;
+ font-size: 26rpx;
+}
+.grooms label > p {
+ overflow: hidden;
+ max-width: 400rpx;
+ min-width: 120rpx;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+.grooms label > p::before {
+ content: '';
+ display: inline-block;
+ position: absolute;
+ width: 1000rpx;
+ margin-left: -1000rpx;
+ height: 0;
+ left: 0;
+ top: 50%;
+ border: 1px solid #e9e9e9;
+ border-width: 1px 0 0 0;
+}
+.grooms label > p::after {
+ content: '';
+ display: inline-block;
+ position: absolute;
+ width: 1000rpx;
+ margin-left: -1000rpx;
+ height: 0;
+ left: 0;
+ left: inherit;
+ top: 50%;
+ border: 1px solid #e9e9e9;
+ border-width: 0 0 1px 0;
+ right: 0;
+ margin-right: -1000rpx;
+}
+.navHeight {
+ height: 100rpx;
+}
+.widget-mask,
+.widget_mask {
+ position: fixed;
+ left: 0;
+ top: 0px;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ z-index: 200;
+}
+.theme-color {
+ color: #f12d22;
+}
+.theme-bgc {
+ background-color: #f12d22;
+}
+.theme-bdc {
+ border-color: #f12d22;
+}
+.theme-bgcart {
+ background-color: #ffb56c;
+}
+.IphoneX {
+ padding-bottom: 68rpx !important;
+}
+.fixIphonex {
+ bottom: 68rpx !important;
+}
+.fixIphonex::after {
+ content: ' ';
+ position: fixed;
+ bottom: 0 !important;
+ height: 68rpx !important;
+ width: 100%;
+ background: #ffffff;
+ left: 0;
+}
+.iphonexMenus {
+ bottom: 166rpx !important;
+}
+.imgCover-oblong-sm .imgCover {
+ width: 195rpx;
+ height: 260rpx;
+}
+.imgCover-oblong-lg .imgCover {
+ padding-bottom: 133%;
+}
+.fw-Light {
+ font-weight: 300;
+}
+.fw-Regular {
+ font-weight: 400;
+}
+.fw-Medium {
+ font-weight: 600;
+}
+.fw-Semibold {
+ font-weight: 800;
+}
+.icon-xingouwuche {
+ font-size: 42rpx;
+}
+.color777 {
+ color: #777777;
+}
+.popup-header {
+ color: #000;
+ font-size: 28rpx;
+ text-align: center;
+ height: 100rpx;
+ line-height: 100rpx;
+ position: relative;
+ border-bottom: 1rpx solid #dbdbdb;
+ font-weight: 400;
+}
+.popup-header .wr-close {
+ position: absolute;
+ left: 0;
+ font-size: 26rpx;
+ color: #5d5d5d;
+ width: 60rpx;
+ height: 60rpx;
+ text-align: center;
+ line-height: 60rpx;
+ top: 20rpx;
+ left: 10rpx;
+}
diff --git a/miniprogram/tcb-shop/style/goodsList.wxss b/miniprogram/tcb-shop/style/goodsList.wxss
new file mode 100644
index 0000000..be0fcac
--- /dev/null
+++ b/miniprogram/tcb-shop/style/goodsList.wxss
@@ -0,0 +1,169 @@
+/* 层级定义
+@z-index-0: 1;
+@z-index-1: 100;
+@z-index-2: 200;
+@z-index-5: 500;
+@z-index-component: 1000; // 通用组件级别
+@z-index-dropdown: @z-index-component;
+@z-index-sticky: @z-index-component + 20;
+@z-index-fixed: @z-index-component + 30;
+@z-index-modal-backdrop:@z-index-component + 40;
+@z-index-modal:@z-index-component + 50;
+@z-index-popover:@z-index-component + 60;
+@z-index-tooltip:@z-index-component + 70;
+*/
+/* var() css变量适配*/
+.goods-list-wrap {
+ padding-left: 24rpx;
+ background-color: #fff;
+}
+.goods-list-wrap .wr-goods-card {
+ padding: 24rpx 24rpx 24rpx 0;
+ border-bottom: 1rpx solid #e6e6e6;
+ background-color: #fff;
+}
+.goods-list-wrap .wr-goods-card.no-border {
+ border-bottom: none;
+}
+.goods-list-wrap .wr-goods-card .wr-goods-card__thumb {
+ width: 200rpx;
+ height: 200rpx;
+ margin-right: 24rpx;
+ border-radius: 8rpx;
+ overflow: hidden;
+}
+.goods-list-wrap .wr-goods-card .wr-goods-card__content {
+ position: relative;
+ display: flex;
+ flex-flow: column nowrap;
+ justify-content: space-between;
+ width: 478rpx;
+ height: 200rpx;
+}
+.goods-list-wrap .wr-goods-card .wr-goods-card__content .wr-goods-card__title {
+ overflow: hidden;
+ margin-bottom: 24rpx;
+ text-overflow: ellipsis;
+ -webkit-box-orient: vertical;
+ -webkit-line-clamp: 2;
+ line-clamp: 2;
+ display: -webkit-box;
+ font-size: 26rpx;
+ line-height: 36rpx;
+ font-weight: 400;
+}
+.goods-list-wrap .wr-goods-card .wr-goods-card__content .card-index--wr-goods-card__price {
+ font-size: 32rpx;
+}
+.goods-list-wrap .wr-goods-card .wr-goods-card__content .card-index--wr-goods-card__price .symbol {
+ font-size: 24rpx;
+}
+.goods-list-wrap .wr-goods-card .wr-goods-card__tags {
+ flex-grow: 2;
+}
+.goods-list-wrap .wr-goods-card .goods-card-tags-wrap {
+ color: #fa550f;
+ color: var(--color-primary, #fa550f);
+ display: flex;
+ height: 30rpx;
+ flex-flow: row wrap;
+ text-align: center;
+ width: 100%;
+ flex-shrink: 0;
+}
+.goods-list-wrap .wr-goods-card .goods-card-tags-wrap .tag {
+ box-sizing: border-box;
+ font-size: 20rpx;
+ border-radius: 4rpx;
+ flex-shrink: 0;
+ vertical-align: middle;
+ margin-right: 8rpx;
+ background-color: #fff;
+}
+.goods-list-wrap .wr-goods-card .goods-card-tags-wrap .tag::after {
+ border-radius: 4rpx;
+ border: 2rpx solid #fa550f;
+ border: 2rpx solid var(--color-primary, #fa550f);
+}
+.goods-list-wrap .wr-goods-card .goods-add-cart {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ width: 48rpx;
+}
+.goods-list-wrap .wr-goods-card .goods-add-cart .goods-add-cart {
+ line-height: 48rpx;
+ height: 48rpx;
+}
+.goods-list-wrap.vertical {
+ padding: 20rpx 24rpx;
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: space-between;
+ background-color: transparent;
+}
+.goods-list-wrap.vertical .wr-goods-card {
+ width: 340rpx;
+ height: 574rpx;
+ overflow: hidden;
+ padding: 0;
+ border-bottom: none;
+ display: flex;
+ flex-flow: column nowrap;
+ border-radius: 8px;
+ margin-bottom: 24rpx;
+}
+.goods-list-wrap.vertical .wr-goods-card .wr-goods-card__thumb {
+ width: 100%;
+ height: 340rpx;
+}
+.goods-list-wrap.vertical .wr-goods-card .wr-goods-card__content {
+ width: 100%;
+ padding: 20rpx;
+ overflow: hidden;
+ height: 234rpx;
+ box-sizing: border-box;
+}
+.goods-list-wrap.vertical .wr-goods-card .wr-goods-card__content .wr-goods-card__title {
+ -webkit-box-orient: horizontal;
+ -webkit-line-clamp: 1;
+ line-clamp: 1;
+ height: 36rpx;
+ color: #333;
+ white-space: nowrap;
+ display: block;
+}
+.goods-list-wrap.vertical .wr-goods-card .wr-goods-card__content .card-index--wr-goods-card__origin-price {
+ position: absolute;
+ left: 20rpx;
+ bottom: 72rpx;
+ margin-left: 0;
+ font-size: 24rpx;
+ color: #aaaaaa;
+}
+.goods-list-wrap.vertical .wr-goods-card .wr-goods-card__content .wr-goods-card__groupon-price {
+ position: absolute;
+ left: 20rpx;
+ bottom: 72rpx;
+ margin-left: 0;
+ font-size: 24rpx;
+ color: #aaaaaa;
+}
+.goods-list-wrap.vertical .wr-goods-card .goods-add-cart {
+ right: 20rpx;
+ bottom: 20rpx;
+}
+.goods-list-wrap.vertical .grouponPrice {
+ margin-bottom: 50rpx;
+}
+.goods-list-wrap .wr-goods-card__twoLine .wr-goods-card__title {
+ -webkit-line-clamp: 2 !important;
+ line-clamp: 2 !important;
+ display: -webkit-box !important;
+ white-space: normal !important;
+ height: auto !important;
+ -webkit-box-orient: vertical !important;
+}
diff --git a/miniprogram/tcb-shop/style/iconfont.wxss b/miniprogram/tcb-shop/style/iconfont.wxss
new file mode 100644
index 0000000..23df272
--- /dev/null
+++ b/miniprogram/tcb-shop/style/iconfont.wxss
@@ -0,0 +1,306 @@
+@font-face {
+ font-family: 'wr';
+ src: url('https://cdn3.codesign.qq.com/icons/gqxWyZ1yMJZmVXk/Yyg5Zp2LG8292lK/iconfont.woff?t=cfc62dd36011e60805f5c3ad1a20b642')
+ format('woff2');
+}
+
+.wr {
+ font-family: 'wr' !important;
+ font-size: 32rpx;
+ font-style: normal;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+.wr-deliver:before {
+ content: '\e033';
+}
+.wr-indent_close:before {
+ content: '\e041';
+}
+.wr-edit:before {
+ content: '\e002';
+}
+.wr-succeed:before {
+ content: '\e00d';
+}
+.wr-goods_return:before {
+ content: '\e03c';
+}
+.wr-wallet:before {
+ content: '\e051';
+}
+.wr-package:before {
+ content: '\e047';
+}
+.wr-comment:before {
+ content: '\e037';
+}
+.wr-exchang:before {
+ content: '\e03e';
+}
+.wr-credit_card:before {
+ content: '\e035';
+}
+.wr-service:before {
+ content: '\e04a';
+}
+.wr-shop_bag:before {
+ content: '\e02a';
+}
+.wr-goods_refund:before {
+ content: '\e03d';
+}
+.wr-check:before {
+ content: '\e053';
+}
+.wr-wechat:before {
+ content: '\e065';
+}
+.wr-cartAdd:before {
+ content: '\e05d';
+}
+.wr-home:before {
+ content: '\e020';
+}
+.wr-person:before {
+ content: '\e02c';
+}
+.wr-cart:before {
+ content: '\e023';
+}
+.wr-location:before {
+ content: '\e016';
+}
+.wr-arrow_forward:before {
+ content: '\e012';
+}
+.wr-close:before {
+ content: '\e021';
+}
+.wr-search:before {
+ content: '\e011';
+}
+.wr-clear_filled:before {
+ content: '\e027';
+}
+.wr-arrow_drop_up:before {
+ content: '\e071';
+}
+.wr-arrow_drop_down:before {
+ content: '\e070';
+}
+.wr-filter:before {
+ content: '\e038';
+}
+.wr-copy:before {
+ content: '\e001';
+}
+.wr-arrow_back:before {
+ content: '\e003';
+}
+.wr-add_circle:before {
+ content: '\e004';
+}
+.wr-Download:before {
+ content: '\e006';
+}
+.wr-map:before {
+ content: '\e007';
+}
+.wr-store:before {
+ content: '\e008';
+}
+.wr-movie:before {
+ content: '\e00a';
+}
+.wr-done:before {
+ content: '\e00b';
+}
+.wr-minus:before {
+ content: '\e00c';
+}
+.wr-list:before {
+ content: '\e00e';
+}
+.wr-expand_less:before {
+ content: '\e00f';
+}
+.wr-person_add:before {
+ content: '\e010';
+}
+.wr-Photo:before {
+ content: '\e013';
+}
+.wr-preview:before {
+ content: '\e014';
+}
+.wr-remind:before {
+ content: '\e015';
+}
+
+.wr-info:before {
+ content: '\e017';
+}
+.wr-expand_less_s:before {
+ content: '\e018';
+}
+.wr-arrow_forward_s:before {
+ content: '\e019';
+}
+.wr-expand_more_s:before {
+ content: '\e01a';
+}
+.wr-share:before {
+ content: '\e01d';
+}
+.wr-notify:before {
+ content: '\e01e';
+}
+.wr-add:before {
+ content: '\e01f';
+}
+.wr-Home:before {
+ content: '\e020';
+}
+.wr-delete:before {
+ content: '\e022';
+}
+.wr-error:before {
+ content: '\e025';
+}
+.wr-sort:before {
+ content: '\e028';
+}
+.wr-sort_filled:before {
+ content: '\e029';
+}
+.wr-shop_bag_filled:before {
+ content: '\e02b';
+}
+
+.wr-person_filled:before {
+ content: '\e02d';
+}
+.wr-cart_filled:before {
+ content: '\e02e';
+}
+.wr-home_filled:before {
+ content: '\e02f';
+}
+.wr-add_outline:before {
+ content: '\e030';
+}
+
+.wr-compass:before {
+ content: '\e034';
+}
+.wr-goods_exchange:before {
+ content: '\e03a';
+}
+.wr-group_buy:before {
+ content: '\e03b';
+}
+.wr-group:before {
+ content: '\e03f';
+}
+.wr-indent_goods:before {
+ content: '\e040';
+}
+.wr-help:before {
+ content: '\e042';
+}
+.wr-group_takeout:before {
+ content: '\e043';
+}
+.wr-label:before {
+ content: '\e044';
+}
+.wr-indent_wating:before {
+ content: '\e045';
+}
+.wr-member:before {
+ content: '\e046';
+}
+
+.wr-scanning:before {
+ content: '\e04b';
+}
+.wr-tv:before {
+ content: '\e04d';
+}
+.wr-to_top:before {
+ content: '\e04f';
+}
+.wr-visibility_off:before {
+ content: '\e050';
+}
+.wr-error-1:before {
+ content: '\e052';
+}
+
+.wr-arrow_right:before {
+ content: '\e054';
+}
+.wr-arrow_left:before {
+ content: '\e056';
+}
+.wr-picture_filled:before {
+ content: '\e057';
+}
+.wr-navigation:before {
+ content: '\e058';
+}
+.wr-telephone:before {
+ content: '\e059';
+}
+.wr-indent_time:before {
+ content: '\e05c';
+}
+.wr-cart_add:before {
+ content: '\e05d';
+}
+.wr-classify:before {
+ content: '\e060';
+}
+.wr-place:before {
+ content: '\e063';
+}
+.wr-wechat_pay:before {
+ content: '\e064';
+}
+.wr-security:before {
+ content: '\e066';
+}
+.wr-alarm:before {
+ content: '\e067';
+}
+.wr-person-1:before {
+ content: '\e068';
+}
+.wr-open_in_new:before {
+ content: '\e069';
+}
+.wr-uncheck:before {
+ content: '\e06b';
+}
+.wr-thumb_up:before {
+ content: '\e06c';
+}
+.wr-thumb_up_filled:before {
+ content: '\e06d';
+}
+.wr-star:before {
+ content: '\e06e';
+}
+.wr-star_filled:before {
+ content: '\e06f';
+}
+.wr-cards:before {
+ content: '\e072';
+}
+.wr-picture_error_filled:before {
+ content: '\e076';
+}
+.wr-discount:before {
+ content: '\e077';
+}
diff --git a/miniprogram/tcb-shop/style/theme.wxss b/miniprogram/tcb-shop/style/theme.wxss
new file mode 100644
index 0000000..bb1249e
--- /dev/null
+++ b/miniprogram/tcb-shop/style/theme.wxss
@@ -0,0 +1,47 @@
+/* 主题定制 */
+.t-input {
+ --td-input-placeholder-text-color: #bbbbbb;
+ --td-input-text-color: #333333;
+}
+
+.t-tab-bar {
+ --td-tab-bar-color: #bbb;
+ --td-tab-bar-active-color: #333;
+}
+
+.t-cascader {
+ --td-cascader-active-color: #fa4126;
+}
+
+.t-switch {
+ --td-switch-checked-color: #34c759;
+}
+
+.t-button {
+ --td-button-font-weight: 500;
+ --td-button-medium-font-size: 32rpx;
+ --td-button-default-color: #fff;
+ --td-button-default-bg-color: #fa4126;
+ --td-button-default-border-color: #fa4126;
+ --td-button-default-disabled-color: #fff;
+ --td-button-default-disabled-bg: #cccccc;
+ --td-button-default-disabled-border-color: #cccccc;
+ --td-button-default-active-bg-color: #fa4126;
+ --td-button-default-active-border-color: #fa4126;
+}
+
+.t-textarea {
+ --td-textarea-placeholder-color: #bbb;
+}
+
+.t-checkbox {
+ --td-checkbox-icon-checked-color: #fa4126;
+}
+
+.dialog__button-confirm {
+ color: #fa4126 !important;
+}
+
+.dialog__button-cancel {
+ color: #aeb3b7 !important;
+}
diff --git a/miniprogram/tcb-shop/utils/addressListFresh.js b/miniprogram/tcb-shop/utils/addressListFresh.js
new file mode 100644
index 0000000..b643493
--- /dev/null
+++ b/miniprogram/tcb-shop/utils/addressListFresh.js
@@ -0,0 +1,3 @@
+export let shouldFresh = false;
+export const addressListShouldFresh = () => (shouldFresh = true);
+export const addressListFinishFresh = () => (shouldFresh = false);
diff --git a/miniprogram/tcb-shop/utils/cartFresh.js b/miniprogram/tcb-shop/utils/cartFresh.js
new file mode 100644
index 0000000..68e13a4
--- /dev/null
+++ b/miniprogram/tcb-shop/utils/cartFresh.js
@@ -0,0 +1,3 @@
+export let shouldFresh = false;
+export const cartShouldFresh = () => (shouldFresh = true);
+export const cartFinishFresh = () => (shouldFresh = false);
diff --git a/miniprogram/tcb-shop/utils/cloudImageHandler.js b/miniprogram/tcb-shop/utils/cloudImageHandler.js
new file mode 100644
index 0000000..94b2f95
--- /dev/null
+++ b/miniprogram/tcb-shop/utils/cloudImageHandler.js
@@ -0,0 +1,30 @@
+export async function getCloudImageTempUrl(images) {
+ const handledIndexToOriginIndex = new Map();
+ const shouldBeHandledImages = [];
+ images.forEach((x, index) => {
+ if (!x.startsWith('cloud')) return;
+
+ const handleIndex = shouldBeHandledImages.length;
+ shouldBeHandledImages.push(x);
+ handledIndexToOriginIndex.set(handleIndex, index);
+ });
+
+ const handledImages = (
+ await wx.cloud.getTempFileURL({
+ fileList: shouldBeHandledImages,
+ })
+ ).fileList.map((x) => x.tempFileURL);
+
+ const ret = [...images];
+ handledImages.forEach((image, handleIndex) => (ret[handledIndexToOriginIndex.get(handleIndex)] = image));
+
+ return ret;
+}
+
+export async function getSingleCloudImageTempUrl(image) {
+ return (
+ await wx.cloud.getTempFileURL({
+ fileList: [image],
+ })
+ ).fileList[0].tempFileURL;
+}
diff --git a/miniprogram/tcb-shop/utils/getPermission.js b/miniprogram/tcb-shop/utils/getPermission.js
new file mode 100644
index 0000000..e6a64cb
--- /dev/null
+++ b/miniprogram/tcb-shop/utils/getPermission.js
@@ -0,0 +1,45 @@
+const getPermission = ({ code, name }) => {
+ return new Promise((resolve, reject) => {
+ wx.getSetting({
+ success: (res) => {
+ if (res.authSetting[code] === false) {
+ wx.showModal({
+ title: `获取${name}失败`,
+ content: `获取${name}失败,请在【右上角】-小程序【设置】项中,将【${name}】开启。`,
+ confirmText: '去设置',
+ confirmColor: '#FA550F',
+ cancelColor: '取消',
+ success(res) {
+ if (res.confirm) {
+ wx.openSetting({
+ success(settinRes) {
+ if (settinRes.authSetting[code] === true) {
+ resolve();
+ } else {
+ console.warn('用户未打开权限', name, code);
+ reject();
+ }
+ },
+ });
+ } else {
+ reject();
+ }
+ },
+ fail() {
+ reject();
+ },
+ });
+ } else {
+ resolve();
+ }
+ },
+ fail() {
+ reject();
+ },
+ });
+ });
+};
+
+module.exports = {
+ getPermission,
+};
diff --git a/miniprogram/tcb-shop/utils/listLoading.js b/miniprogram/tcb-shop/utils/listLoading.js
new file mode 100644
index 0000000..10d16e6
--- /dev/null
+++ b/miniprogram/tcb-shop/utils/listLoading.js
@@ -0,0 +1,9 @@
+/**
+ * Should be used with `components/load-more`.
+ */
+export const LIST_LOADING_STATUS = {
+ READY: 0,
+ LOADING: 1,
+ NO_MORE: 2,
+ FAILED: 3,
+};
diff --git a/miniprogram/tcb-shop/utils/mock.js b/miniprogram/tcb-shop/utils/mock.js
new file mode 100644
index 0000000..27f13e8
--- /dev/null
+++ b/miniprogram/tcb-shop/utils/mock.js
@@ -0,0 +1,51 @@
+/**
+ * 随机打散字符串
+ * @param {number} n 长度
+ * @param {string} str 字符串
+ * @returns
+ */
+function generateMixed(n, str) {
+ var res = '';
+ for (var i = 0; i < n; i++) {
+ var id = Math.ceil(Math.random() * 35);
+ res += str[id];
+ }
+ return res;
+}
+
+/**
+ * 生成随机数
+ * @param {number} min 最小值
+ * @param {number} max 最大值
+ * @returns
+ */
+function getRandomNum(min, max) {
+ var range = max - min;
+ var rand = Math.random();
+ return min + Math.round(rand * range);
+}
+
+/**
+ * 生成随机IP
+ * @returns
+ */
+function mockIp() {
+ return `10.${getRandomNum(1, 254)}.${getRandomNum(1, 254)}.${getRandomNum(
+ 1,
+ 254,
+ )}`;
+}
+
+function mockReqId() {
+ return `${getRandomNum(100000, 999999)}.${new Date().valueOf()}${getRandomNum(
+ 1000,
+ 9999,
+ )}.${getRandomNum(10000000, 99999999)}`;
+}
+
+module.exports = {
+ generateMixed,
+ mockIp,
+ mockReqId,
+ getRandomNum,
+};
diff --git a/miniprogram/tcb-shop/utils/orderListFresh.js b/miniprogram/tcb-shop/utils/orderListFresh.js
new file mode 100644
index 0000000..3195d87
--- /dev/null
+++ b/miniprogram/tcb-shop/utils/orderListFresh.js
@@ -0,0 +1,3 @@
+export let shouldFresh = false;
+export const orderListShouldFresh = () => (shouldFresh = true);
+export const orderListFinishFresh = () => (shouldFresh = false);
diff --git a/miniprogram/tcb-shop/utils/orderOperation.js b/miniprogram/tcb-shop/utils/orderOperation.js
new file mode 100644
index 0000000..1db5146
--- /dev/null
+++ b/miniprogram/tcb-shop/utils/orderOperation.js
@@ -0,0 +1,5 @@
+export const OPERATION_TYPE = {
+ CANCEL: 'cancel',
+ CONFIRM: 'confirm',
+ PAY: 'pay',
+};
diff --git a/miniprogram/tcb-shop/utils/spuStatus.js b/miniprogram/tcb-shop/utils/spuStatus.js
new file mode 100644
index 0000000..40ae401
--- /dev/null
+++ b/miniprogram/tcb-shop/utils/spuStatus.js
@@ -0,0 +1 @@
+export const SPU_SELLING_STATUS = 'ENABLED';
diff --git a/miniprogram/tcb-shop/utils/util.js b/miniprogram/tcb-shop/utils/util.js
new file mode 100644
index 0000000..eb004b6
--- /dev/null
+++ b/miniprogram/tcb-shop/utils/util.js
@@ -0,0 +1,135 @@
+import dayjs from 'dayjs';
+
+const formatTime = (date, template) => dayjs(date).format(template);
+
+/**
+ * 格式化价格数额为字符串
+ * 可对小数部分进行填充,默认不填充
+ * @param price 价格数额,以分为单位!
+ * @param fill 是否填充小数部分 0-不填充 1-填充第一位小数 2-填充两位小数
+ */
+function priceFormat(price, fill = 0) {
+ if (isNaN(price) || price === null || price === Infinity) {
+ return price;
+ }
+
+ let priceFormatValue = Math.round(parseFloat(`${price}`) * 10 ** 8) / 10 ** 8; // 恢复精度丢失
+ priceFormatValue = `${Math.ceil(priceFormatValue) / 100}`; // 向上取整,单位转换为元,转换为字符串
+ if (fill > 0) {
+ // 补充小数位数
+ if (priceFormatValue.indexOf('.') === -1) {
+ priceFormatValue = `${priceFormatValue}.`;
+ }
+ const n = fill - priceFormatValue.split('.')[1]?.length;
+ for (let i = 0; i < n; i++) {
+ priceFormatValue = `${priceFormatValue}0`;
+ }
+ }
+ return priceFormatValue;
+}
+
+/**
+ * 获取cdn裁剪后链接
+ *
+ * @param {string} url 基础链接
+ * @param {number} width 宽度,单位px
+ * @param {number} [height] 可选,高度,不填时与width同值
+ */
+const cosThumb = (url, width, height = width) => {
+ if (url.indexOf('?') > -1) {
+ return url;
+ }
+
+ if (url.indexOf('http://') === 0) {
+ url = url.replace('http://', 'https://');
+ }
+
+ return `${url}?imageMogr2/thumbnail/${~~width}x${~~height}`;
+};
+
+const get = (source, paths, defaultValue) => {
+ if (typeof paths === 'string') {
+ paths = paths.replace(/\[/g, '.').replace(/\]/g, '').split('.').filter(Boolean);
+ }
+ const { length } = paths;
+ let index = 0;
+ while (source != null && index < length) {
+ source = source[paths[index++]];
+ }
+ return source === undefined || index === 0 ? defaultValue : source;
+};
+let systemWidth = 0;
+/** 获取系统宽度,为了减少启动消耗所以在函数里边做初始化 */
+export const loadSystemWidth = () => {
+ if (systemWidth) {
+ return systemWidth;
+ }
+
+ try {
+ ({ screenWidth: systemWidth, pixelRatio } = wx.getSystemInfoSync());
+ } catch (e) {
+ systemWidth = 0;
+ }
+ return systemWidth;
+};
+
+/**
+ * 转换rpx为px
+ *
+ * @description
+ * 什么时候用?
+ * - 布局(width: 172rpx)已经写好, 某些组件只接受px作为style或者prop指定
+ *
+ */
+const rpx2px = (rpx, round = false) => {
+ loadSystemWidth();
+
+ // px / systemWidth = rpx / 750
+ const result = (rpx * systemWidth) / 750;
+
+ if (round) {
+ return Math.floor(result);
+ }
+
+ return result;
+};
+
+/**
+ * 手机号码*加密函数
+ * @param {string} phone 电话号
+ * @returns
+ */
+const phoneEncryption = (phone) => {
+ return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
+};
+
+// 内置手机号正则字符串
+const innerPhoneReg = '^1(?:3\\d|4[4-9]|5[0-35-9]|6[67]|7[0-8]|8\\d|9\\d)\\d{8}$';
+
+/**
+ * 手机号正则校验
+ * @param phone 手机号
+ * @param phoneReg 正则字符串
+ * @returns true - 校验通过 false - 校验失败
+ */
+const phoneRegCheck = (phone) => {
+ const phoneRegExp = new RegExp(innerPhoneReg);
+ return phoneRegExp.test(phone);
+};
+
+function objectToParamString(obj) {
+ return Object.entries(obj)
+ .map(([key, val]) => `${key}=${val}`)
+ .join('&');
+}
+
+module.exports = {
+ formatTime,
+ priceFormat,
+ cosThumb,
+ get,
+ rpx2px,
+ phoneEncryption,
+ phoneRegCheck,
+ objectToParamString,
+};