From 1e2f55db5af542662e598a6101debcf3bd8cf76f Mon Sep 17 00:00:00 2001 From: chenjiahan Date: Thu, 20 Aug 2020 22:31:50 +0800 Subject: [PATCH] chore: temporaily remove sku --- src/sku/README.md | 397 --------- src/sku/README.zh-CN.md | 401 --------- src/sku/Sku.js | 807 ------------------- src/sku/components/SkuActions.tsx | 58 -- src/sku/components/SkuDateTimeField.js | 107 --- src/sku/components/SkuHeader.tsx | 110 --- src/sku/components/SkuHeaderItem.tsx | 26 - src/sku/components/SkuImgUploader.js | 73 -- src/sku/components/SkuMessages.js | 184 ----- src/sku/components/SkuRow.js | 122 --- src/sku/components/SkuRowItem.js | 109 --- src/sku/components/SkuRowPropItem.js | 49 -- src/sku/components/SkuStepper.js | 178 ---- src/sku/constants.js | 11 - src/sku/demo/data.ts | 193 ----- src/sku/demo/index.vue | 277 ------- src/sku/index.js | 31 - src/sku/index.less | 373 --------- src/sku/lang.ts | 77 -- src/sku/test/__snapshots__/demo.spec.js.snap | 46 -- src/sku/test/demo.spec.js | 4 - src/sku/test/index.spec.js | 87 -- src/sku/utils/sku-helper.js | 158 ---- src/sku/utils/time-helper.js | 28 - 24 files changed, 3906 deletions(-) delete mode 100644 src/sku/README.md delete mode 100644 src/sku/README.zh-CN.md delete mode 100644 src/sku/Sku.js delete mode 100644 src/sku/components/SkuActions.tsx delete mode 100644 src/sku/components/SkuDateTimeField.js delete mode 100644 src/sku/components/SkuHeader.tsx delete mode 100644 src/sku/components/SkuHeaderItem.tsx delete mode 100644 src/sku/components/SkuImgUploader.js delete mode 100644 src/sku/components/SkuMessages.js delete mode 100644 src/sku/components/SkuRow.js delete mode 100644 src/sku/components/SkuRowItem.js delete mode 100644 src/sku/components/SkuRowPropItem.js delete mode 100644 src/sku/components/SkuStepper.js delete mode 100644 src/sku/constants.js delete mode 100644 src/sku/demo/data.ts delete mode 100644 src/sku/demo/index.vue delete mode 100644 src/sku/index.js delete mode 100644 src/sku/index.less delete mode 100644 src/sku/lang.ts delete mode 100644 src/sku/test/__snapshots__/demo.spec.js.snap delete mode 100644 src/sku/test/demo.spec.js delete mode 100644 src/sku/test/index.spec.js delete mode 100644 src/sku/utils/sku-helper.js delete mode 100644 src/sku/utils/time-helper.js diff --git a/src/sku/README.md b/src/sku/README.md deleted file mode 100644 index f03ac813e..000000000 --- a/src/sku/README.md +++ /dev/null @@ -1,397 +0,0 @@ -# Sku - -### Install - -```js -import Vue from 'vue'; -import { Sku } from 'vant'; - -Vue.use(Sku); -``` - -## Usage - -### Basic Usage - -```html - -``` - -```js -export default { - data() { - return { - show: false, - sku: {}, - goods: {}, - messageConfig: {}, - }; - }, -}; -``` - -### Custom Stepper - -```html - -``` - -### Custom By Slot - -```html - - - - - - - -``` - -## API - -### Props - -| Attribute | Description | Type | Default | -| --- | --- | --- | --- | -| v-model | Whether to show sku | _boolean_ | `false` | -| sku | Sku data | _object_ | - | -| goods | Goods info | _object_ | - | -| goods-id | Goods id | `string | _number_ | - | -| price-tag | Tag behind the price | _string_ | - | -| hide-stock | Whether to hide stock | _boolean_ | `false` | -| hide-quota-text | Whether to hide quota text | _boolean_ | `false` | -| hide-selected-text | Whether to hide selected text | _boolean_ | `false` | -| stock-threshold | stock threshold | _boolean_ | `50` | -| show-add-cart-btn | Whether to show cart button | _boolean_ | `true` | -| buy-text | Buy button text | _string_ | - | - | -| add-cart-text | Add cart button text | _string_ | - | - | -| quota | Quota (0 as no limit) | _number_ | `0` | -| quota-used | Used quota | _number_ | `0` | -| reset-stepper-on-hide | Whether to reset stepper when hide | _boolean_ | `false` | -| reset-selected-sku-on-hide | Whether to reset selected sku when hide | _boolean_ | `false` | -| disable-stepper-input | Whether to disable stepper input | _boolean_ | `false` | -| close-on-click-overlay | Whether to close sku popup when click overlay | _boolean_ | `true` | -| stepper-title | Quantity title | _string_ | `Quantity` | -| custom-stepper-config | Custom stepper related config | _object_ | `{}` | -| message-config | Message related config | _object_ | `{}` | -| get-container | Return the mount node for sku | _string \| () => Element_ | - | -| safe-area-inset-bottom `v2.2.1` | Whether to enable bottom safe area adaptation | _boolean_ | `true` | -| start-sale-num `v2.3.0` | Minimum quantity | _number_ | `1` | -| properties `v2.4.2` | Goods properties | _array_ | - | -| preview-on-click-image `v2.5.2` | Whether to preview image when click goods image | _boolean_ | `true` | -| show-header-image `v2.9.0` | Whether to display header image | _boolean_ | `true` | -| lazy-load | Whether to enable lazy load,should register [Lazyload](#/en-US/lazyload) component | _boolean_ | `false` | - -### Events - -| Event | Description | Arguments | -| --- | --- | --- | -| add-cart | Triggered when click cart button | data: object | -| buy-clicked | Triggered when click buy button | data: object | -| stepper-change | Triggered when stepper value changed | value: number | -| sku-selected | Triggered when select sku | { skuValue, selectedSku, selectedSkuComb } | -| sku-prop-selected | Triggered when select property | { propValue, selectedProp, selectedSkuComb } | -| open-preview | Triggered when open image preview | data: object | -| close-preview | Triggered when close image preview | data: object | -| sku-reset `v2.8.1` | Triggered when reset sku and property | { selectedSku, selectedProp, selectedSkuComb } | - -### Methods - -Use [ref](https://vuejs.org/v2/api/#ref) to get Sku instance and call instance methods - -| Name | Description | Attribute | Return value | -| --- | --- | --- | --- | -| getSkuData | Get current skuData | - | skuData | -| resetSelectedSku `v2.3.0` | Reset selected sku to initial sku | - | - | - -### Slots - -| Name | Description | -| ------------------------------- | --------------------------------- | -| sku-header | Custom header | -| sku-header-price | Custom header price area | -| sku-header-origin-price | Custom header origin price area | -| sku-header-extra | Extra header area | -| sku-header-image-extra `v2.5.2` | Custom header image extra area | -| sku-body-top | Custom content before sku-group | -| sku-group | Custom sku | -| extra-sku-group | Extra custom content | -| sku-stepper | Custom stepper | -| sku-messages | Custom messages | -| sku-actions-top `v2.4.7` | Custom content before sku-actions | -| sku-actions | Custom button actions | - -### Sku Data Structure - -```js -sku: { - tree: [ - { - k: 'Color', - k_s: 's1', - v: [ - { - id: '1', - name: 'Red', - imgUrl: 'https://img.yzcdn.cn/1.jpg', - previewImgUrl: 'https://img.yzcdn.cn/1p.jpg', - }, - { - id: '1', - name: 'Blue', - imgUrl: 'https://img.yzcdn.cn/2.jpg', - previewImgUrl: 'https://img.yzcdn.cn/2p.jpg', - } - ], - largeImageMode: true, // whether to enable large image mode - } - ], - list: [ - { - id: 2259, - s1: '1', - s2: '1', - price: 100, - stock_num: 110 - } - ], - price: '1.00', - stock_num: 227, - collection_id: 2261, - none_sku: false, - messages: [ - { - datetime: '0', - multiple: '0', - name: 'Message', - type: 'text', - required: '1', - placeholder: '' - } - ], - hide_stock: false, - properties: [ - { - k_id: 123, - k: 'More', - is_multiple: true, - v: [ - { - id: 1222, - name: 'Tea', - price: 1, - }, - { - id: 1223, - name: 'Water', - price: 1, - } - ], - } - ] -} -``` - -### properties Data Structure - -```js -[ - { - k_id: 123, - k: 'More', - is_multiple: true, - v: [ - { - id: 1222, - name: 'Tea', - price: 1, - }, - { - id: 1223, - name: 'Water', - price: 1, - }, - ], - }, -]; -``` - -### initialSku Data Structure - -```js -{ - // Key:skuKeyStr - // Value:skuValueId - s1: '30349', - s2: '1193', - selectedNum: 3, - selectedProp: { - 123: [1222] - } -} -``` - -### Goods Data Structure - -```js -goods: { - picture: 'https://img.yzcdn.cn/1.jpg'; -} -``` - -### customStepperConfig Data Structure - -```js -customStepperConfig: { - // custom quota text - quotaText: 'only 5 can buy', - // custom callback when over limit - handleOverLimit: (data) => { - const { action, limitType, quota, quotaUsed, startSaleNum } = data; - - if (action === 'minus') { - Toast(`at least select ${startSaleNum > 1 ? startSaleNum : 'one'}`); - } else if (action === 'plus') { - // const { LIMIT_TYPE } = Sku.skuConstants; - if (limitType === LIMIT_TYPE.QUOTA_LIMIT) { - let msg = `Buy up to ${quota}`; - if (quotaUsed > 0) msg += `,you already buy ${quotaUsed}`; - Toast(msg); - } else { - Toast('not enough stock'); - } - } - }, - // custom callback when stepper value change - handleStepperChange: currentValue => {}, - // stock - stockNum: 1999, - // stock fomatter - stockFormatter: stockNum => {}, -} -``` - -### messageConfig Data Structure - -```js -messageConfig: { - // the upload image callback - uploadImg: () => { - return new Promise((resolve) => { - setTimeout(() => resolve('https://img.yzcdn.cn/upload_files/2017/02/21/FjKTOxjVgnUuPmHJRdunvYky9OHP.jpg!100x100.jpg'), 1000); - }); - }, - // max file size (MB) - uploadMaxSize: 3, - // placeholder config - placeholderMap: { - text: 'xxx', - tel: 'xxx', - ... - }, - // Key:message name - // Value:message value - initialMessages: { - message: 'message value' - } -} -``` - -### Events Params Data Structure - -```js -skuData: { - goodsId: '946755', - messages: { - message_0: '12', - message_1: '' - }, - cartMessages: { - 'Message 1': 'xxxx' - }, - selectedNum: 1, - selectedSkuComb: { - id: 2257, - price: 100, - s1: '30349', - s2: '1193', - s3: '0', - stock_num: 111, - properties: [ - { - k_id: 123, - k: 'More', - is_multiple: true, - v: [ - { - id: 1223, - name: 'Water', - price: 1 - } - ] - } - ], - property_price: 1 - } -} -``` diff --git a/src/sku/README.zh-CN.md b/src/sku/README.zh-CN.md deleted file mode 100644 index 6ec0aa866..000000000 --- a/src/sku/README.zh-CN.md +++ /dev/null @@ -1,401 +0,0 @@ -# Sku 商品规格 - -### 引入 - -```js -import Vue from 'vue'; -import { Sku } from 'vant'; - -Vue.use(Sku); -``` - -## 代码演示 - -### 基础用法 - -```html - -``` - -```js -export default { - data() { - return { - show: false, - sku: { - // 数据结构见下方文档 - }, - goods: { - // 数据结构见下方文档 - }, - messageConfig: { - // 数据结构见下方文档 - }, - }; - }, -}; -``` - -### 自定义步进器 - -```html - -``` - -### 通过插槽定制 - -```html - - - - - - - -``` - -## API - -### Props - -| 参数 | 说明 | 类型 | 默认值 | -| --- | --- | --- | --- | -| v-model | 是否显示商品规格弹窗 | _boolean_ | `false` | -| sku | 商品 sku 数据 | _object_ | - | -| goods | 商品信息 | _object_ | - | -| goods-id | 商品 id | _number \| string_ | - | -| price-tag | 显示在价格后面的标签 | _string_ | - | -| hide-stock | 是否显示商品剩余库存 | _boolean_ | `false` | -| hide-quota-text | 是否显示限购提示 | _boolean_ | `false` | -| hide-selected-text | 是否隐藏已选提示 | _boolean_ | `false` | -| stock-threshold | 库存阈值。低于这个值会把库存数高亮显示 | _boolean_ | `50` | -| show-add-cart-btn | 是否显示加入购物车按钮 | _boolean_ | `true` | -| buy-text | 购买按钮文字 | _string_ | `立即购买` | -| add-cart-text | 加入购物车按钮文字 | _string_ | `加入购物车` | -| quota | 限购数,0 表示不限购 | _number_ | `0` | -| quota-used | 已经购买过的数量 | _number_ | `0` | -| reset-stepper-on-hide | 隐藏时重置选择的商品数量 | _boolean_ | `false` | -| reset-selected-sku-on-hide | 隐藏时重置已选择的 sku | _boolean_ | `false` | -| disable-stepper-input | 是否禁用步进器输入 | _boolean_ | `false` | -| close-on-click-overlay | 是否在点击遮罩层后关闭 | _boolean_ | `true` | -| stepper-title | 数量选择组件左侧文案 | _string_ | `购买数量` | -| custom-stepper-config | 步进器相关自定义配置 | _object_ | `{}` | -| message-config | 留言相关配置 | _object_ | `{}` | -| get-container | 指定挂载的节点,[用法示例](#/zh-CN/popup#zhi-ding-gua-zai-wei-zhi) | _string \| () => Element_ | - | -| initial-sku | 默认选中的 sku,具体参考高级用法 | _object_ | `{}` | -| show-soldout-sku | 是否展示售罄的 sku,默认展示并置灰 | _boolean_ | `true` | -| safe-area-inset-bottom `v2.2.1` | 是否开启[底部安全区适配](#/zh-CN/quickstart#di-bu-an-quan-qu-gua-pei) | _boolean_ | `true` | -| start-sale-num `v2.3.0` | 起售数量 | _number_ | `1` | -| properties `v2.4.2` | 商品属性 | _array_ | - | -| preview-on-click-image `v2.5.2` | 是否在点击商品图片时自动预览 | _boolean_ | `true` | -| show-header-image `v2.9.0` | 是否展示头部图片 | _boolean_ | `true` | -| lazy-load `v2.9.0` | 是否开启图片懒加载,须配合 [Lazyload](#/zh-CN/lazyload) 组件使用 | _boolean_ | `false` | - -### Events - -| 事件名 | 说明 | 回调参数 | -| --- | --- | --- | -| add-cart | 点击添加购物车回调 | skuData: object | -| buy-clicked | 点击购买回调 | skuData: object | -| stepper-change | 购买数量变化时触发 | value: number | -| sku-selected | 切换规格类目时触发 | { skuValue, selectedSku, selectedSkuComb } | -| sku-prop-selected | 切换商品属性时触发 | { propValue, selectedProp, selectedSkuComb } | -| open-preview | 打开商品图片预览时触发 | data: object | -| close-preview | 关闭商品图片预览时触发 | data: object | -| sku-reset `v2.8.1` | 规格和属性被重置时触发 | { selectedSku, selectedProp, selectedSkuComb } | - -### 方法 - -通过 ref 可以获取到 Sku 实例并调用实例方法,详见[组件实例方法](#/zh-CN/quickstart#zu-jian-shi-li-fang-fa) - -| 方法名 | 说明 | 参数 | 返回值 | -| ------------------------- | ---------------------- | ---- | ------- | -| getSkuData | 获取当前 skuData | - | skuData | -| resetSelectedSku `v2.3.0` | 重置选中规格到初始状态 | - | - | - -### Slots - -Sku 组件默认划分好了若干区块,这些区块都定义成了插槽,可以按需进行替换。区块顺序见下表: - -| 名称 | 说明 | -| --- | --- | -| sku-header | 商品信息展示区,包含商品图片、名称、价格等信息 | -| sku-header-price | 自定义 sku 头部价格展示 | -| sku-header-origin-price | 自定义 sku 头部原价展示 | -| sku-header-extra | 额外 sku 头部区域 | -| sku-header-image-extra `v2.5.2` | 自定义 sku 头部图片额外的展示 | -| sku-body-top | sku 展示区上方的内容,无默认展示内容,按需使用 | -| sku-group | 商品 sku 展示区 | -| extra-sku-group | 额外商品 sku 展示区,一般用不到 | -| sku-stepper | 商品数量选择区 | -| sku-messages | 商品留言区 | -| sku-actions-top `v2.4.7` | 操作按钮区顶部内容,无默认展示内容,按需使用 | -| sku-actions | 操作按钮区 | - -### sku 对象结构 - -```js -sku: { - // 所有sku规格类目与其值的从属关系,比如商品有颜色和尺码两大类规格,颜色下面又有红色和蓝色两个规格值。 - // 可以理解为一个商品可以有多个规格类目,一个规格类目下可以有多个规格值。 - tree: [ - { - k: '颜色', // skuKeyName:规格类目名称 - k_s: 's1', // skuKeyStr:sku 组合列表(下方 list)中当前类目对应的 key 值,value 值会是从属于当前类目的一个规格值 id - v: [ - { - id: '1', // skuValueId:规格值 id - name: '红色', // skuValueName:规格值名称 - imgUrl: 'https://img.yzcdn.cn/1.jpg', // 规格类目图片,只有第一个规格类目可以定义图片 - previewImgUrl: 'https://img.yzcdn.cn/1p.jpg', // 用于预览显示的规格类目图片 - }, - { - id: '1', - name: '蓝色', - imgUrl: 'https://img.yzcdn.cn/2.jpg', - previewImgUrl: 'https://img.yzcdn.cn/2p.jpg', - } - ], - largeImageMode: true, // 是否展示大图模式 - } - ], - // 所有 sku 的组合列表,比如红色、M 码为一个 sku 组合,红色、S 码为另一个组合 - list: [ - { - id: 2259, // skuId - s1: '1', // 规格类目 k_s 为 s1 的对应规格值 id - s2: '1', // 规格类目 k_s 为 s2 的对应规格值 id - price: 100, // 价格(单位分) - stock_num: 110 // 当前 sku 组合对应的库存 - } - ], - price: '1.00', // 默认价格(单位元) - stock_num: 227, // 商品总库存 - collection_id: 2261, // 无规格商品 skuId 取 collection_id,否则取所选 sku 组合对应的 id - none_sku: false, // 是否无规格商品 - messages: [ - { - // 商品留言 - datetime: '0', // 留言类型为 time 时,是否含日期。'1' 表示包含 - multiple: '0', // 留言类型为 text 时,是否多行文本。'1' 表示多行 - name: '留言', // 留言名称 - type: 'text', // 留言类型,可选: id_no(身份证), text, tel, date, time, email - required: '1', // 是否必填 '1' 表示必填 - placeholder: '' // 可选值,占位文本 - } - ], - hide_stock: false // 是否隐藏剩余库存 -} -``` - -### properties 对象结构 - -```js -[ - // 商品属性 - { - k_id: 123, // 属性id - k: '加料', // 属性名 - is_multiple: true, // 是否可多选 - v: [ - { - id: 1222, // 属性值id - name: '珍珠', // 属性值名 - price: 1, // 属性值加价 - }, - { - id: 1223, - name: '椰果', - price: 1, - }, - ], - }, -]; -``` - -### initialSku 对象结构 - -```js -{ - // 键:skuKeyStr(sku 组合列表中当前类目对应的 key 值) - // 值:skuValueId(规格值 id) - s1: '1', - s2: '1', - // 初始选中数量 - selectedNum: 3, - // 初始选中的商品属性 - // 键:属性id - // 值:属性值id列表 - selectedProp: { - 123: [1222] - } -} -``` - -### goods 对象结构 - -```js -goods: { - // 默认商品 sku 缩略图 - picture: 'https://img.yzcdn.cn/1.jpg'; -} -``` - -### customStepperConfig 对象结构 - -```js -customStepperConfig: { - // 自定义限购文案 - quotaText: '每次限购xxx件', - // 自定义步进器超过限制时的回调 - handleOverLimit: (data) => { - const { action, limitType, quota, quotaUsed, startSaleNum } = data; - - if (action === 'minus') { - Toast(startSaleNum > 1 ? `${startSaleNum}件起售` : '至少选择一件商品'); - } else if (action === 'plus') { - // const { LIMIT_TYPE } = Sku.skuConstants; - if (limitType === LIMIT_TYPE.QUOTA_LIMIT) { - let msg = `单次限购${quota}件`; - if (quotaUsed > 0) msg += `,你已购买${quotaUsed}`; - Toast(msg); - } else { - Toast('库存不够了'); - } - } - }, - // 步进器变化的回调 - handleStepperChange: currentValue => {}, - // 库存 - stockNum: 1999, - // 格式化库存 - stockFormatter: stockNum => {}, -} -``` - -### messageConfig 对象结构 - -```js -messageConfig: { - // 图片上传回调,需要返回一个promise,promise正确执行的结果需要是一个图片url - uploadImg: () => { - return new Promise((resolve) => { - setTimeout(() => resolve('https://img.yzcdn.cn/upload_files/2017/02/21/FjKTOxjVgnUuPmHJRdunvYky9OHP.jpg!100x100.jpg'), 1000); - }); - }, - // 最大上传体积 (MB) - uploadMaxSize: 3, - // placeholder 配置 - placeholderMap: { - text: 'xxx', - tel: 'xxx', - ... - }, - // 初始留言信息 - // 键:留言 name - // 值:留言内容 - initialMessages: { - 留言: '留言信息' - } -} -``` - -### 添加购物车和点击购买回调函数接收的 skuData 对象结构 - -```js -skuData: { - // 商品 id - goodsId: '946755', - // 留言信息 - messages: { - message_0: '12', - message_1: '' - }, - // 另一种格式的留言,key 不同 - cartMessages: { - '留言1': 'xxxx' - }, - // 选择的商品数量 - selectedNum: 1, - // 选择的 sku 组合 - selectedSkuComb: { - id: 2257, - price: 100, - s1: '30349', - s2: '1193', - s3: '0', - stock_num: 111, - properties: [ - { - k_id: 123, - k: '加料', - is_multiple: true, - v: [ - { - id: 1223, - name: '椰果', - price: 1 - } - ] - } - ], - property_price: 1 - }, -} -``` diff --git a/src/sku/Sku.js b/src/sku/Sku.js deleted file mode 100644 index 230c26191..000000000 --- a/src/sku/Sku.js +++ /dev/null @@ -1,807 +0,0 @@ -import Vue from 'vue'; -import Popup from '../popup'; -import Toast from '../toast'; -import ImagePreview from '../image-preview'; -import SkuHeader from './components/SkuHeader'; -import SkuHeaderItem from './components/SkuHeaderItem'; -import SkuRow from './components/SkuRow'; -import SkuRowItem from './components/SkuRowItem'; -import SkuRowPropItem from './components/SkuRowPropItem'; -import SkuStepper from './components/SkuStepper'; -import SkuMessages from './components/SkuMessages'; -import SkuActions from './components/SkuActions'; -import { createNamespace, isDef } from '../utils'; -import { - isAllSelected, - isSkuChoosable, - getSkuComb, - getSelectedSkuValues, - getSelectedPropValues, - getSelectedProperties, -} from './utils/sku-helper'; -import { LIMIT_TYPE, UNSELECTED_SKU_VALUE_ID } from './constants'; - -const namespace = createNamespace('sku'); -const [createComponent, bem, t] = namespace; -const { QUOTA_LIMIT } = LIMIT_TYPE; - -export default createComponent({ - props: { - sku: Object, - goods: Object, - value: Boolean, - buyText: String, - goodsId: [Number, String], - priceTag: String, - lazyLoad: Boolean, - hideStock: Boolean, - properties: Array, - addCartText: String, - stepperTitle: String, - getContainer: [String, Function], - hideQuotaText: Boolean, - hideSelectedText: Boolean, - resetStepperOnHide: Boolean, - customSkuValidator: Function, - disableStepperInput: Boolean, - resetSelectedSkuOnHide: Boolean, - quota: { - type: Number, - default: 0, - }, - quotaUsed: { - type: Number, - default: 0, - }, - startSaleNum: { - type: Number, - default: 1, - }, - initialSku: { - type: Object, - default: () => ({}), - }, - stockThreshold: { - type: Number, - default: 50, - }, - showSoldoutSku: { - type: Boolean, - default: true, - }, - showAddCartBtn: { - type: Boolean, - default: true, - }, - customStepperConfig: { - type: Object, - default: () => ({}), - }, - showHeaderImage: { - type: Boolean, - default: true, - }, - previewOnClickImage: { - type: Boolean, - default: true, - }, - safeAreaInsetBottom: { - type: Boolean, - default: true, - }, - closeOnClickOverlay: { - type: Boolean, - default: true, - }, - bodyOffsetTop: { - type: Number, - default: 200, - }, - messageConfig: { - type: Object, - default: () => ({ - initialMessages: {}, - placeholderMap: {}, - uploadImg: () => Promise.resolve(), - uploadMaxSize: 5, - }), - }, - }, - - data() { - return { - selectedSku: {}, - selectedProp: {}, - selectedNum: 1, - show: this.value, - }; - }, - - watch: { - show(val) { - this.$emit('input', val); - - if (!val) { - this.$emit('sku-close', { - selectedSkuValues: this.selectedSkuValues, - selectedNum: this.selectedNum, - selectedSkuComb: this.selectedSkuComb, - }); - - if (this.resetStepperOnHide) { - this.resetStepper(); - } - - if (this.resetSelectedSkuOnHide) { - this.resetSelectedSku(); - } - } - }, - - value(val) { - this.show = val; - }, - - skuTree: 'resetSelectedSku', - - initialSku() { - this.resetStepper(); - this.resetSelectedSku(); - }, - }, - - computed: { - skuGroupClass() { - return [ - 'van-sku-group-container', - { - 'van-sku-group-container--hide-soldout': !this.showSoldoutSku, - }, - ]; - }, - - bodyStyle() { - if (this.$isServer) { - return; - } - - const maxHeight = window.innerHeight - this.bodyOffsetTop; - - return { - maxHeight: maxHeight + 'px', - }; - }, - - isSkuCombSelected() { - // SKU 未选完 - if (this.hasSku && !isAllSelected(this.skuTree, this.selectedSku)) { - return false; - } - - // 属性未全选 - return !this.propList.some( - (it) => (this.selectedProp[it.k_id] || []).length < 1 - ); - }, - - isSkuEmpty() { - return Object.keys(this.sku).length === 0; - }, - - hasSku() { - return !this.sku.none_sku; - }, - - hasSkuOrAttr() { - return this.hasSku || this.propList.length > 0; - }, - - selectedSkuComb() { - let skuComb = null; - if (this.isSkuCombSelected) { - if (this.hasSku) { - skuComb = getSkuComb(this.skuList, this.selectedSku); - } else { - skuComb = { - id: this.sku.collection_id, - price: Math.round(this.sku.price * 100), - stock_num: this.sku.stock_num, - }; - } - - if (skuComb) { - skuComb.properties = getSelectedProperties( - this.propList, - this.selectedProp - ); - skuComb.property_price = this.selectedPropValues.reduce( - (acc, cur) => acc + (cur.price || 0), - 0 - ); - } - } - return skuComb; - }, - - selectedSkuValues() { - return getSelectedSkuValues(this.skuTree, this.selectedSku); - }, - - selectedPropValues() { - return getSelectedPropValues(this.propList, this.selectedProp); - }, - - price() { - if (this.selectedSkuComb) { - return ( - (this.selectedSkuComb.price + this.selectedSkuComb.property_price) / - 100 - ).toFixed(2); - } - // sku.price是一个格式化好的价格区间 - return this.sku.price; - }, - - originPrice() { - if (this.selectedSkuComb && this.selectedSkuComb.origin_price) { - return ( - (this.selectedSkuComb.origin_price + - this.selectedSkuComb.property_price) / - 100 - ).toFixed(2); - } - return this.sku.origin_price; - }, - - skuTree() { - return this.sku.tree || []; - }, - - skuList() { - return this.sku.list || []; - }, - - propList() { - return this.properties || []; - }, - - imageList() { - const imageList = [this.goods.picture]; - - if (this.skuTree.length > 0) { - this.skuTree.forEach((treeItem) => { - if (!treeItem.v) { - return; - } - - treeItem.v.forEach((vItem) => { - const imgUrl = vItem.previewImgUrl || vItem.imgUrl || vItem.img_url; - - if (imgUrl && imageList.indexOf(imgUrl) === -1) { - imageList.push(imgUrl); - } - }); - }); - } - - return imageList; - }, - - stock() { - const { stockNum } = this.customStepperConfig; - if (stockNum !== undefined) { - return stockNum; - } - if (this.selectedSkuComb) { - return this.selectedSkuComb.stock_num; - } - return this.sku.stock_num; - }, - - stockText() { - const { stockFormatter } = this.customStepperConfig; - if (stockFormatter) { - return stockFormatter(this.stock); - } - - return [ - `${t('stock')} `, - - {this.stock} - , - ` ${t('stockUnit')}`, - ]; - }, - - selectedText() { - if (this.selectedSkuComb) { - const values = this.selectedSkuValues.concat(this.selectedPropValues); - return `${t('selected')} ${values.map((item) => item.name).join(' ')}`; - } - - const unselectedSku = this.skuTree - .filter( - (item) => this.selectedSku[item.k_s] === UNSELECTED_SKU_VALUE_ID - ) - .map((item) => item.k); - - const unselectedProp = this.propList - .filter((item) => (this.selectedProp[item.k_id] || []).length < 1) - .map((item) => item.k); - - return `${t('select')} ${unselectedSku.concat(unselectedProp).join(' ')}`; - }, - }, - - created() { - const skuEventBus = new Vue(); - this.skuEventBus = skuEventBus; - - skuEventBus.$on('sku:select', this.onSelect); - skuEventBus.$on('sku:propSelect', this.onPropSelect); - skuEventBus.$on('sku:numChange', this.onNumChange); - skuEventBus.$on('sku:previewImage', this.onPreviewImage); - skuEventBus.$on('sku:overLimit', this.onOverLimit); - skuEventBus.$on('sku:stepperState', this.onStepperState); - skuEventBus.$on('sku:addCart', this.onAddCart); - skuEventBus.$on('sku:buy', this.onBuy); - - this.resetStepper(); - this.resetSelectedSku(); - - // 组件初始化后的钩子,抛出skuEventBus - this.$emit('after-sku-create', skuEventBus); - }, - - methods: { - resetStepper() { - const { skuStepper } = this.$refs; - const { selectedNum } = this.initialSku; - const num = isDef(selectedNum) ? selectedNum : this.startSaleNum; - // 用来缓存不合法的情况 - this.stepperError = null; - - if (skuStepper) { - skuStepper.setCurrentNum(num); - } else { - // 当首次加载(skuStepper 为空)时,传入数量如果不合法,可能会存在问题 - this.selectedNum = num; - } - }, - - // @exposed-api - resetSelectedSku() { - this.selectedSku = {}; - - // 重置 selectedSku - this.skuTree.forEach((item) => { - this.selectedSku[item.k_s] = UNSELECTED_SKU_VALUE_ID; - }); - this.skuTree.forEach((item) => { - const key = item.k_s; - // 规格值只有1个时,优先判断 - const valueId = - item.v.length === 1 ? item.v[0].id : this.initialSku[key]; - if ( - valueId && - isSkuChoosable(this.skuList, this.selectedSku, { key, valueId }) - ) { - this.selectedSku[key] = valueId; - } - }); - - const skuValues = this.selectedSkuValues; - - if (skuValues.length > 0) { - this.$nextTick(() => { - this.$emit('sku-selected', { - skuValue: skuValues[skuValues.length - 1], - selectedSku: this.selectedSku, - selectedSkuComb: this.selectedSkuComb, - }); - }); - } - - // 重置商品属性 - this.selectedProp = {}; - const { selectedProp = {} } = this.initialSku; - // 只有一个属性值时,默认选中,且选中外部传入信息 - this.propList.forEach((item) => { - if (item.v && item.v.length === 1) { - this.selectedProp[item.k_id] = [item.v[0].id]; - } else if (selectedProp[item.k_id]) { - this.selectedProp[item.k_id] = selectedProp[item.k_id]; - } - }); - - const propValues = this.selectedPropValues; - if (propValues.length > 0) { - this.$emit('sku-prop-selected', { - propValue: propValues[propValues.length - 1], - selectedProp: this.selectedProp, - selectedSkuComb: this.selectedSkuComb, - }); - } - - // 抛出重置事件 - this.$emit('sku-reset', { - selectedSku: this.selectedSku, - selectedProp: this.selectedProp, - selectedSkuComb: this.selectedSkuComb, - }); - - this.centerInitialSku(); - }, - - getSkuMessages() { - return this.$refs.skuMessages ? this.$refs.skuMessages.getMessages() : {}; - }, - - getSkuCartMessages() { - return this.$refs.skuMessages - ? this.$refs.skuMessages.getCartMessages() - : {}; - }, - - validateSkuMessages() { - return this.$refs.skuMessages - ? this.$refs.skuMessages.validateMessages() - : ''; - }, - - validateSku() { - if (this.selectedNum === 0) { - return t('unavailable'); - } - - if (this.isSkuCombSelected) { - return this.validateSkuMessages(); - } - - // 自定义sku校验 - if (this.customSkuValidator) { - const err = this.customSkuValidator(this); - if (err) return err; - } - - return t('selectSku'); - }, - - onSelect(skuValue) { - // 点击已选中的sku时则取消选中 - this.selectedSku = - this.selectedSku[skuValue.skuKeyStr] === skuValue.id - ? { - ...this.selectedSku, - [skuValue.skuKeyStr]: UNSELECTED_SKU_VALUE_ID, - } - : { ...this.selectedSku, [skuValue.skuKeyStr]: skuValue.id }; - - this.$emit('sku-selected', { - skuValue, - selectedSku: this.selectedSku, - selectedSkuComb: this.selectedSkuComb, - }); - }, - - onPropSelect(propValue) { - const arr = this.selectedProp[propValue.skuKeyStr] || []; - const pos = arr.indexOf(propValue.id); - - if (pos > -1) { - arr.splice(pos, 1); - } else if (propValue.multiple) { - arr.push(propValue.id); - } else { - arr.splice(0, 1, propValue.id); - } - - this.selectedProp = { - ...this.selectedProp, - [propValue.skuKeyStr]: arr, - }; - - this.$emit('sku-prop-selected', { - propValue, - selectedProp: this.selectedProp, - selectedSkuComb: this.selectedSkuComb, - }); - }, - - onNumChange(num) { - this.selectedNum = num; - }, - - onPreviewImage(selectedValue) { - const { imageList } = this; - let index = 0; - let indexImage = imageList[0]; - if (selectedValue && selectedValue.imgUrl) { - this.imageList.some((image, pos) => { - if (image === selectedValue.imgUrl) { - index = pos; - return true; - } - return false; - }); - indexImage = selectedValue.imgUrl; - } - const params = { - ...selectedValue, - index, - imageList: this.imageList, - indexImage, - }; - - this.$emit('open-preview', params); - - if (!this.previewOnClickImage) { - return; - } - - ImagePreview({ - images: this.imageList, - startPosition: index, - onClose: () => { - this.$emit('close-preview', params); - }, - }); - }, - - onOverLimit(data) { - const { action, limitType, quota, quotaUsed } = data; - const { handleOverLimit } = this.customStepperConfig; - - if (handleOverLimit) { - handleOverLimit(data); - return; - } - - if (action === 'minus') { - if (this.startSaleNum > 1) { - Toast(t('minusStartTip', this.startSaleNum)); - } else { - Toast(t('minusTip')); - } - } else if (action === 'plus') { - if (limitType === QUOTA_LIMIT) { - if (quotaUsed > 0) { - Toast(t('quotaUsedTip', quota, quotaUsed)); - } else { - Toast(t('quotaTip', quota)); - } - } else { - Toast(t('soldout')); - } - } - }, - - onStepperState(data) { - this.stepperError = data.valid - ? null - : { - ...data, - action: 'plus', - }; - }, - - onAddCart() { - this.onBuyOrAddCart('add-cart'); - }, - - onBuy() { - this.onBuyOrAddCart('buy-clicked'); - }, - - onBuyOrAddCart(type) { - // sku 不符合购买条件 - if (this.stepperError) { - return this.onOverLimit(this.stepperError); - } - - const error = this.validateSku(); - - if (error) { - Toast(error); - } else { - this.$emit(type, this.getSkuData()); - } - }, - - // @exposed-api - getSkuData() { - return { - goodsId: this.goodsId, - messages: this.getSkuMessages(), - selectedNum: this.selectedNum, - cartMessages: this.getSkuCartMessages(), - selectedSkuComb: this.selectedSkuComb, - }; - }, - - // 当 popup 完全打开后执行 - onOpened() { - this.centerInitialSku(); - }, - - centerInitialSku() { - (this.$refs.skuRows || []).forEach((it) => { - const { k_s } = it.skuRow || {}; - it.centerItem(this.initialSku[k_s]); - }); - }, - }, - - render() { - if (this.isSkuEmpty) { - return; - } - - const { - sku, - skuList, - goods, - price, - lazyLoad, - originPrice, - skuEventBus, - selectedSku, - selectedProp, - selectedNum, - stepperTitle, - selectedSkuComb, - showHeaderImage, - } = this; - - const slotsProps = { - price, - originPrice, - selectedNum, - skuEventBus, - selectedSku, - selectedSkuComb, - }; - - const slots = (name) => this.slots(name, slotsProps); - - const Header = slots('sku-header') || ( - - - {slots('sku-header-price') || ( -
- - {price} - {this.priceTag && ( - {this.priceTag} - )} -
- )} - {slots('sku-header-origin-price') || - (originPrice && ( - - {t('originPrice')} ¥{originPrice} - - ))} - {!this.hideStock && ( - - {this.stockText} - - )} - {this.hasSkuOrAttr && !this.hideSelectedText && ( - {this.selectedText} - )} - {slots('sku-header-extra')} -
- ); - - const Group = - slots('sku-group') || - (this.hasSkuOrAttr && ( -
- {this.skuTree.map((skuTreeItem) => ( - - {skuTreeItem.v.map((skuValue) => ( - - ))} - - ))} - {this.propList.map((skuTreeItem) => ( - - {skuTreeItem.v.map((skuValue) => ( - - ))} - - ))} -
- )); - - const Stepper = slots('sku-stepper') || ( - { - this.$emit('stepper-change', event); - }} - /> - ); - - const Messages = slots('sku-messages') || ( - - ); - - const Actions = slots('sku-actions') || ( - - ); - - return ( - - {Header} -
- {slots('sku-body-top')} - {Group} - {slots('extra-sku-group')} - {Stepper} - {Messages} -
- {slots('sku-actions-top')} - {Actions} -
- ); - }, -}); diff --git a/src/sku/components/SkuActions.tsx b/src/sku/components/SkuActions.tsx deleted file mode 100644 index 0fdd7769c..000000000 --- a/src/sku/components/SkuActions.tsx +++ /dev/null @@ -1,58 +0,0 @@ -// Utils -import { createNamespace } from '../../utils'; -import { inherit } from '../../utils/functional'; - -// Components -import Button from '../../button'; - -// Types -import { DefaultSlots } from '../../utils/types'; -import Vue, { CreateElement, RenderContext } from 'vue/types'; - -export type SkuActionsProps = { - buyText?: string; - skuEventBus: Vue; - addCartText?: string; - showAddCartBtn?: boolean; -}; - -const [createComponent, bem, t] = createNamespace('sku-actions'); - -function SkuActions( - h: CreateElement, - props: SkuActionsProps, - slots: DefaultSlots, - ctx: RenderContext -) { - const createEmitter = (name: string) => () => { - props.skuEventBus.$emit(name); - }; - - return ( -
- {props.showAddCartBtn && ( -
- ); -} - -SkuActions.props = { - buyText: String, - addCartText: String, - skuEventBus: Object, - showAddCartBtn: Boolean, -}; - -export default createComponent(SkuActions); diff --git a/src/sku/components/SkuDateTimeField.js b/src/sku/components/SkuDateTimeField.js deleted file mode 100644 index 879b45b26..000000000 --- a/src/sku/components/SkuDateTimeField.js +++ /dev/null @@ -1,107 +0,0 @@ -// Utils -import { createNamespace } from '../../utils'; -import { stringToDate, dateToString } from '../utils/time-helper'; - -// Components -import Popup from '../../popup'; -import DateTimePicker from '../../datetime-picker'; -import Field from '../../field'; - -const namespace = createNamespace('sku-datetime-field'); -const createComponent = namespace[0]; -const t = namespace[2]; - -export default createComponent({ - props: { - value: String, - label: String, - required: Boolean, - placeholder: String, - type: { - type: String, - default: 'date', - }, - }, - - data() { - return { - showDatePicker: false, - currentDate: this.type === 'time' ? '' : new Date(), - minDate: new Date(new Date().getFullYear() - 60, 0, 1), - }; - }, - - watch: { - value(val) { - switch (this.type) { - case 'time': - this.currentDate = val; - break; - case 'date': - case 'datetime': - this.currentDate = stringToDate(val) || new Date(); - break; - } - }, - }, - - computed: { - title() { - return t(`title.${this.type}`); - }, - }, - - methods: { - onClick() { - this.showDatePicker = true; - }, - onConfirm(val) { - let data = val; - if (this.type !== 'time') { - data = dateToString(val, this.type); - } - this.$emit('input', data); - this.showDatePicker = false; - }, - onCancel() { - this.showDatePicker = false; - }, - formatter(type, val) { - const word = t(`format.${type}`); - return `${val}${word}`; - }, - }, - - render() { - return ( - - - - - - ); - }, -}); diff --git a/src/sku/components/SkuHeader.tsx b/src/sku/components/SkuHeader.tsx deleted file mode 100644 index 79dc9e162..000000000 --- a/src/sku/components/SkuHeader.tsx +++ /dev/null @@ -1,110 +0,0 @@ -// Utils -import { createNamespace } from '../../utils'; -import { inherit } from '../../utils/functional'; -import { BORDER_BOTTOM } from '../../utils/constant'; - -// Components -import Image from '../../image'; - -// Types -import Vue, { CreateElement, RenderContext } from 'vue/types'; -import { DefaultSlots, ScopedSlot } from '../../utils/types'; -import { SkuData, SkuGoodsData, SelectedSkuData } from '../../../types/sku'; - -export type SkuHeaderProps = { - sku: SkuData; - goods: SkuGoodsData; - skuEventBus: Vue; - selectedSku: SelectedSkuData; - showHeaderImage: boolean; -}; - -export type SkuHeaderSlots = DefaultSlots & { - 'sku-header-image-extra'?: ScopedSlot; -}; - -type SelectedValueType = { - ks: string; - imgUrl: string; -}; - -const [createComponent, bem] = createNamespace('sku-header'); - -function getSkuImgValue( - sku: SkuData, - selectedSku: SelectedSkuData -): SelectedValueType | undefined { - let imgValue; - - sku.tree.some((item) => { - const id = selectedSku[item.k_s]; - - if (id && item.v) { - const matchedSku = - item.v.filter((skuValue) => skuValue.id === id)[0] || {}; - - const img = - matchedSku.previewImgUrl || matchedSku.imgUrl || matchedSku.img_url; - if (img) { - imgValue = { - ...matchedSku, - ks: item.k_s, - imgUrl: img, - }; - return true; - } - } - - return false; - }); - - return imgValue; -} - -function SkuHeader( - h: CreateElement, - props: SkuHeaderProps, - slots: SkuHeaderSlots, - ctx: RenderContext -) { - const { - sku, - goods, - skuEventBus, - selectedSku, - showHeaderImage = true, - } = props; - - const selectedValue = getSkuImgValue(sku, selectedSku); - const imgUrl = selectedValue ? selectedValue.imgUrl : goods.picture; - - const previewImage = () => { - skuEventBus.$emit('sku:previewImage', selectedValue); - }; - - return ( -
- {showHeaderImage && ( - - {slots['sku-header-image-extra']?.()} - - )} -
{slots.default?.()}
-
- ); -} - -SkuHeader.props = { - sku: Object, - goods: Object, - skuEventBus: Object, - selectedSku: Object, - showHeaderImage: Boolean, -}; - -export default createComponent(SkuHeader); diff --git a/src/sku/components/SkuHeaderItem.tsx b/src/sku/components/SkuHeaderItem.tsx deleted file mode 100644 index 5f16c29dd..000000000 --- a/src/sku/components/SkuHeaderItem.tsx +++ /dev/null @@ -1,26 +0,0 @@ -// Utils -import { createNamespace } from '../../utils'; -import { inherit } from '../../utils/functional'; - -// Types -import { CreateElement, RenderContext } from 'vue/types'; -import { DefaultSlots } from '../../utils/types'; - -export type SkuHeaderItemProps = {}; - -const [createComponent, bem] = createNamespace('sku-header-item'); - -function SkuHeader( - h: CreateElement, - props: SkuHeaderItemProps, - slots: DefaultSlots, - ctx: RenderContext -) { - return ( -
- {slots.default && slots.default()} -
- ); -} - -export default createComponent(SkuHeader); diff --git a/src/sku/components/SkuImgUploader.js b/src/sku/components/SkuImgUploader.js deleted file mode 100644 index b017c1eba..000000000 --- a/src/sku/components/SkuImgUploader.js +++ /dev/null @@ -1,73 +0,0 @@ -// Utils -import { createNamespace } from '../../utils'; - -// Components -import Uploader from '../../uploader'; - -const namespace = createNamespace('sku-img-uploader'); -const createComponent = namespace[0]; -const t = namespace[2]; - -export default createComponent({ - props: { - value: String, - uploadImg: Function, - maxSize: { - type: Number, - default: 6, - }, - }, - - data() { - return { - fileList: [], - }; - }, - - watch: { - value(val) { - if (val) { - this.fileList = [{ url: val, isImage: true }]; - } else { - this.fileList = []; - } - }, - }, - - methods: { - afterReadFile(file) { - file.status = 'uploading'; - file.message = t('uploading'); - this.uploadImg(file.file, file.content) - .then((img) => { - file.status = 'done'; - this.$emit('input', img); - }) - .catch(() => { - file.status = 'failed'; - file.message = t('fail'); - }); - }, - - onOversize() { - this.$toast(t('oversize', this.maxSize)); - }, - - onDelete() { - this.$emit('input', ''); - }, - }, - - render() { - return ( - - ); - }, -}); diff --git a/src/sku/components/SkuMessages.js b/src/sku/components/SkuMessages.js deleted file mode 100644 index de63f77fd..000000000 --- a/src/sku/components/SkuMessages.js +++ /dev/null @@ -1,184 +0,0 @@ -// Utils -import { createNamespace } from '../../utils'; -import { isEmail } from '../../utils/validate/email'; -import { isNumeric } from '../../utils/validate/number'; - -// Components -import Cell from '../../cell'; -import Field from '../../field'; -import SkuImgUploader from './SkuImgUploader'; -import SkuDateTimeField from './SkuDateTimeField'; - -const [createComponent, bem, t] = createNamespace('sku-messages'); - -export default createComponent({ - props: { - messageConfig: Object, - goodsId: [Number, String], - messages: { - type: Array, - default: () => [], - }, - }, - - data() { - return { - messageValues: this.resetMessageValues(this.messages), - }; - }, - - watch: { - messages(val) { - this.messageValues = this.resetMessageValues(val); - }, - }, - - methods: { - resetMessageValues(messages) { - const { messageConfig } = this; - const { initialMessages = {} } = messageConfig; - return (messages || []).map((message) => ({ - value: initialMessages[message.name] || '', - })); - }, - - getType(message) { - if (+message.multiple === 1) { - return 'textarea'; - } - if (message.type === 'id_no') { - return 'text'; - } - return message.datetime > 0 ? 'datetime' : message.type; - }, - - getMessages() { - const messages = {}; - - this.messageValues.forEach((item, index) => { - messages[`message_${index}`] = item.value; - }); - - return messages; - }, - - getCartMessages() { - const messages = {}; - - this.messageValues.forEach((item, index) => { - const message = this.messages[index]; - messages[message.name] = item.value; - }); - - return messages; - }, - - getPlaceholder(message) { - const type = +message.multiple === 1 ? 'textarea' : message.type; - const map = this.messageConfig.placeholderMap || {}; - return message.placeholder || map[type] || t(`placeholder.${type}`); - }, - - validateMessages() { - const values = this.messageValues; - - for (let i = 0; i < values.length; i++) { - const { value } = values[i]; - const message = this.messages[i]; - - if (value === '') { - // 必填字段的校验 - if (String(message.required) === '1') { - const textType = t(message.type === 'image' ? 'upload' : 'fill'); - return textType + message.name; - } - } else { - if (message.type === 'tel' && !isNumeric(value)) { - return t('invalid.tel'); - } - if (message.type === 'mobile' && !/^\d{6,20}$/.test(value)) { - return t('invalid.mobile'); - } - if (message.type === 'email' && !isEmail(value)) { - return t('invalid.email'); - } - if ( - message.type === 'id_no' && - (value.length < 15 || value.length > 18) - ) { - return t('invalid.id_no'); - } - } - } - }, - /** - * The phone number copied from IOS mobile phone address book - * will add spaces and invisible Unicode characters - * which cannot pass the /^\d+$/ verification - * so keep numbers and dots - */ - getFormatter(message) { - return function formatter(value) { - if (message.type === 'mobile' || message.type === 'tel') { - return value.replace(/[^\d.]/g, ''); - } - - return value; - }; - }, - - genMessage(message, index) { - if (message.type === 'image') { - return ( - - -
{t('imageLabel')}
-
- ); - } - - // 时间和日期使用的vant选择器 - const isDateOrTime = ['date', 'time'].indexOf(message.type) > -1; - if (isDateOrTime) { - return ( - - ); - } - - return ( - - ); - }, - }, - - render() { - return
{this.messages.map(this.genMessage)}
; - }, -}); diff --git a/src/sku/components/SkuRow.js b/src/sku/components/SkuRow.js deleted file mode 100644 index ab8c5056c..000000000 --- a/src/sku/components/SkuRow.js +++ /dev/null @@ -1,122 +0,0 @@ -// Utils -import { createNamespace } from '../../utils'; -import { BORDER_BOTTOM } from '../../utils/constant'; -// Mixins -import { ParentMixin } from '../../mixins/relation'; -import { BindEventMixin } from '../../mixins/bind-event'; - -const [createComponent, bem, t] = createNamespace('sku-row'); - -export { bem }; - -export default createComponent({ - mixins: [ - ParentMixin('vanSkuRows'), - BindEventMixin(function (bind) { - if (this.scrollable && this.$refs.scroller) { - bind(this.$refs.scroller, 'scroll', this.onScroll); - } - }), - ], - - props: { - skuRow: Object, - }, - - data() { - return { - progress: 0, - }; - }, - - computed: { - scrollable() { - return this.skuRow.largeImageMode && this.skuRow.v.length > 6; - }, - }, - - methods: { - onScroll() { - const { scroller, row } = this.$refs; - const distance = row.offsetWidth - scroller.offsetWidth; - this.progress = scroller.scrollLeft / distance; - }, - - genTitle() { - return ( -
- {this.skuRow.k} - {this.skuRow.is_multiple && ( - ({t('multiple')}) - )} -
- ); - }, - - genIndicator() { - if (this.scrollable) { - const style = { - transform: `translate3d(${this.progress * 20}px, 0, 0)`, - }; - - return ( -
-
-
-
-
- ); - } - }, - - genContent() { - const nodes = this.slots(); - - if (this.skuRow.largeImageMode) { - const top = []; - const bottom = []; - - nodes.forEach((node, index) => { - const group = Math.floor(index / 3) % 2 === 0 ? top : bottom; - group.push(node); - }); - - return ( -
-
- {top} -
- {bottom.length ?
{bottom}
: null} -
- ); - } - - return nodes; - }, - - centerItem(selectSkuId) { - if (!this.skuRow.largeImageMode || !selectSkuId) { - return; - } - const { children = [] } = this; - const { scroller, row } = this.$refs; - const child = children.find((it) => +it.skuValue.id === +selectSkuId); - if (scroller && row && child && child.$el) { - const target = child.$el; - const to = - target.offsetLeft - (scroller.offsetWidth - target.offsetWidth) / 2; - scroller.scrollLeft = to; - } - }, - }, - - render() { - return ( -
- {this.genTitle()} - {this.genContent()} - {this.genIndicator()} -
- ); - }, -}); diff --git a/src/sku/components/SkuRowItem.js b/src/sku/components/SkuRowItem.js deleted file mode 100644 index 4e79a0075..000000000 --- a/src/sku/components/SkuRowItem.js +++ /dev/null @@ -1,109 +0,0 @@ -import { bem } from './SkuRow'; -import { createNamespace } from '../../utils'; -import { isSkuChoosable } from '../utils/sku-helper'; -import { ChildrenMixin } from '../../mixins/relation'; -import Image from '../../image'; - -const [createComponent] = createNamespace('sku-row-item'); - -export default createComponent({ - mixins: [ChildrenMixin('vanSkuRows')], - - props: { - lazyLoad: Boolean, - skuValue: Object, - skuKeyStr: String, - skuEventBus: Object, - selectedSku: Object, - largeImageMode: Boolean, - skuList: { - type: Array, - default: () => [], - }, - }, - - computed: { - imgUrl() { - const url = this.skuValue.imgUrl || this.skuValue.img_url; - return this.largeImageMode - ? url || - 'https://img.yzcdn.cn/upload_files/2020/06/24/FmKWDg0bN9rMcTp9ne8MXiQWGtLn.png' - : url; - }, - - choosable() { - return isSkuChoosable(this.skuList, this.selectedSku, { - key: this.skuKeyStr, - valueId: this.skuValue.id, - }); - }, - }, - - methods: { - onSelect() { - if (this.choosable) { - this.skuEventBus.$emit('sku:select', { - ...this.skuValue, - skuKeyStr: this.skuKeyStr, - }); - } - }, - - onPreviewImg(event) { - event.stopPropagation(); - const { skuValue, skuKeyStr } = this; - this.skuEventBus.$emit('sku:previewImage', { - ...skuValue, - ks: skuKeyStr, - imgUrl: skuValue.imgUrl || skuValue.img_url, - }); - }, - - genImage(classPrefix) { - if (this.imgUrl) { - return ( - - ); - } - }, - }, - - render() { - const choosed = this.skuValue.id === this.selectedSku[this.skuKeyStr]; - const classPrefix = this.largeImageMode ? bem('image-item') : bem('item'); - - return ( - - {this.genImage(classPrefix)} -
- {this.largeImageMode ? ( - - {this.skuValue.name} - - ) : ( - this.skuValue.name - )} -
- {this.largeImageMode && ( - - )} -
- ); - }, -}); diff --git a/src/sku/components/SkuRowPropItem.js b/src/sku/components/SkuRowPropItem.js deleted file mode 100644 index 4336f2640..000000000 --- a/src/sku/components/SkuRowPropItem.js +++ /dev/null @@ -1,49 +0,0 @@ -import { createNamespace } from '../../utils'; - -const [createComponent] = createNamespace('sku-row-prop-item'); - -export default createComponent({ - props: { - skuValue: Object, - skuKeyStr: String, - skuEventBus: Object, - selectedProp: Object, - multiple: Boolean, - }, - - computed: { - choosed() { - const { selectedProp, skuKeyStr, skuValue } = this; - - if (selectedProp && selectedProp[skuKeyStr]) { - return selectedProp[skuKeyStr].indexOf(skuValue.id) > -1; - } - - return false; - }, - }, - - methods: { - onSelect() { - this.skuEventBus.$emit('sku:propSelect', { - ...this.skuValue, - skuKeyStr: this.skuKeyStr, - multiple: this.multiple, - }); - }, - }, - - render() { - return ( - - {this.skuValue.name} - - ); - }, -}); diff --git a/src/sku/components/SkuStepper.js b/src/sku/components/SkuStepper.js deleted file mode 100644 index 61a32352e..000000000 --- a/src/sku/components/SkuStepper.js +++ /dev/null @@ -1,178 +0,0 @@ -import { createNamespace } from '../../utils'; -import { LIMIT_TYPE } from '../constants'; -import Stepper from '../../stepper'; - -const namespace = createNamespace('sku-stepper'); -const createComponent = namespace[0]; -const t = namespace[2]; -const { QUOTA_LIMIT, STOCK_LIMIT } = LIMIT_TYPE; - -export default createComponent({ - props: { - stock: Number, - skuEventBus: Object, - skuStockNum: Number, - selectedNum: Number, - stepperTitle: String, - disableStepperInput: Boolean, - customStepperConfig: Object, - hideQuotaText: Boolean, - quota: { - type: Number, - default: 0, - }, - quotaUsed: { - type: Number, - default: 0, - }, - startSaleNum: { - type: Number, - default: 1, - }, - }, - - data() { - return { - currentNum: this.selectedNum, - // 购买限制类型: 限购/库存 - limitType: STOCK_LIMIT, - }; - }, - - watch: { - currentNum(num) { - const intValue = parseInt(num, 10); - if (intValue >= this.stepperMinLimit && intValue <= this.stepperLimit) { - this.skuEventBus.$emit('sku:numChange', intValue); - } - }, - - stepperLimit(limit) { - if (limit < this.currentNum && this.stepperMinLimit <= limit) { - this.currentNum = limit; - } - this.checkState(this.stepperMinLimit, limit); - }, - - stepperMinLimit(start) { - if (start > this.currentNum || start > this.stepperLimit) { - this.currentNum = start; - } - this.checkState(start, this.stepperLimit); - }, - }, - - computed: { - stepperLimit() { - const quotaLimit = this.quota - this.quotaUsed; - let limit; - - // 无限购时直接取库存,有限购时取限购数和库存数中小的那个 - if (this.quota > 0 && quotaLimit <= this.stock) { - // 修正负的limit - limit = quotaLimit < 0 ? 0 : quotaLimit; - this.limitType = QUOTA_LIMIT; - } else { - limit = this.stock; - this.limitType = STOCK_LIMIT; - } - - return limit; - }, - stepperMinLimit() { - return this.startSaleNum < 1 ? 1 : this.startSaleNum; - }, - quotaText() { - const { quotaText, hideQuotaText } = this.customStepperConfig; - if (hideQuotaText) return ''; - - let text = ''; - - if (quotaText) { - text = quotaText; - } else { - const textArr = []; - if (this.startSaleNum > 1) { - textArr.push(t('quotaStart', this.startSaleNum)); - } - if (this.quota > 0) { - textArr.push(t('quotaLimit', this.quota)); - } - text = textArr.join(t('comma')); - } - - return text; - }, - }, - - created() { - this.checkState(this.stepperMinLimit, this.stepperLimit); - }, - - methods: { - setCurrentNum(num) { - this.currentNum = num; - this.checkState(this.stepperMinLimit, this.stepperLimit); - }, - - onOverLimit(action) { - this.skuEventBus.$emit('sku:overLimit', { - action, - limitType: this.limitType, - quota: this.quota, - quotaUsed: this.quotaUsed, - startSaleNum: this.startSaleNum, - }); - }, - - onChange(currentValue) { - const intValue = parseInt(currentValue, 10); - const { handleStepperChange } = this.customStepperConfig; - handleStepperChange && handleStepperChange(intValue); - this.$emit('change', intValue); - }, - - checkState(min, max) { - // 如果选择小于起售,则强制变为起售 - if (this.currentNum < min || min > max) { - this.currentNum = min; - } else if (this.currentNum > max) { - // 当前选择数量大于最大可选时,需要重置已选数量 - this.currentNum = max; - } - - this.skuEventBus.$emit('sku:stepperState', { - valid: min <= max, - min, - max, - limitType: this.limitType, - quota: this.quota, - quotaUsed: this.quotaUsed, - startSaleNum: this.startSaleNum, - }); - }, - }, - - render() { - return ( -
-
- {this.stepperTitle || t('num')} -
- - {!this.hideQuotaText && this.quotaText && ( - ({this.quotaText}) - )} -
- ); - }, -}); diff --git a/src/sku/constants.js b/src/sku/constants.js deleted file mode 100644 index 85eb653a3..000000000 --- a/src/sku/constants.js +++ /dev/null @@ -1,11 +0,0 @@ -export const LIMIT_TYPE = { - QUOTA_LIMIT: 0, - STOCK_LIMIT: 1, -}; - -export const UNSELECTED_SKU_VALUE_ID = ''; - -export default { - LIMIT_TYPE, - UNSELECTED_SKU_VALUE_ID, -}; diff --git a/src/sku/demo/data.ts b/src/sku/demo/data.ts deleted file mode 100644 index 0f000045c..000000000 --- a/src/sku/demo/data.ts +++ /dev/null @@ -1,193 +0,0 @@ -export function getSkuData(largeImageMode = false) { - return { - goods_id: '1', - quota: 5, - quota_used: 0, - start_sale_num: 2, - goods_info: { - price: 1, - title: '测试商品', - picture: 'https://b.yzcdn.cn/vant/sku/shoes-1.png', - }, - sku: { - price: '1.00', - stock_num: 227, - none_sku: false, - hide_stock: false, - collection_id: 2261, - tree: [ - { - k: '颜色', - k_s: 's1', - k_id: '1', - v: [ - { - id: '1', - name: '粉色', - imgUrl: 'https://b.yzcdn.cn/vant/sku/shoes-1.png', - }, - { - id: '2', - name: '黄色', - imgUrl: 'https://b.yzcdn.cn/vant/sku/shoes-2.png', - }, - { - id: '3', - name: '蓝色', - imgUrl: 'https://b.yzcdn.cn/vant/sku/shoes-3.png', - }, - ], - largeImageMode, - }, - { - k: '尺寸', - k_s: 's2', - k_id: '2', - v: [ - { - id: '1', - name: '大', - }, - { - id: '2', - name: '小', - }, - ], - }, - ], - list: [ - { - id: 2259, - s1: '2', - s2: '1', - price: 100, - discount: 100, - stock_num: 110, - }, - { - id: 2260, - s1: '3', - s2: '1', - price: 100, - discount: 100, - stock_num: 99, - }, - { - id: 2257, - s1: '1', - s2: '1', - price: 100, - discount: 100, - stock_num: 111, - }, - { - id: 2258, - s1: '1', - s2: '2', - price: 100, - discount: 100, - stock_num: 6, - }, - ], - messages: [ - { - datetime: '0', - disable: false, - multiple: '0', - name: '留言1', - type: 'text', - required: '1', - }, - { - datetime: '0', - disable: false, - multiple: 0, - name: '留言2', - type: 'id_no', - required: 0, - }, - { - datetime: '0', - disable: false, - multiple: 0, - name: '留言3', - type: 'image', - required: 0, - }, - { - datetime: '0', - disable: false, - multiple: 1, - name: '留言4', - type: 'text', - required: 0, - }, - { - datetime: '0', - disable: false, - name: '数字', - multiple: 0, - type: 'tel', - required: 0, - }, - { - datetime: '0', - disable: false, - name: '邮件', - multiple: 0, - type: 'email', - required: 0, - }, - { - datetime: '0', - disable: false, - name: '日期', - multiple: 0, - type: 'date', - required: 0, - }, - { - datetime: '0', - disable: false, - name: '时间', - multiple: 0, - type: 'time', - required: 0, - }, - ], - }, - properties: [ - { - k: '加料', - k_id: 124, - is_multiple: true, - v: [ - { - id: 1224, - name: '布丁', - price: 3, - }, - { - id: 1225, - name: '波霸', - price: 4, - }, - { - id: 1226, - name: '珍珠', - price: 5, - }, - ], - }, - ], - }; -} - -export const initialSku = { - s1: '1', - s2: '1', - selectedNum: 3, - selectedProp: { - 124: [1225, 1226], - }, -}; diff --git a/src/sku/demo/index.vue b/src/sku/demo/index.vue deleted file mode 100644 index f5ed75ffa..000000000 --- a/src/sku/demo/index.vue +++ /dev/null @@ -1,277 +0,0 @@ - - - - - diff --git a/src/sku/index.js b/src/sku/index.js deleted file mode 100644 index 107514f3f..000000000 --- a/src/sku/index.js +++ /dev/null @@ -1,31 +0,0 @@ -// Utils -import lang from './lang'; -import constants from './constants'; -import skuHelper from './utils/sku-helper'; - -// Components -import Sku from './Sku'; -import Locale from '../locale'; -import SkuActions from './components/SkuActions'; -import SkuHeader from './components/SkuHeader'; -import SkuHeaderItem from './components/SkuHeaderItem'; -import SkuMessages from './components/SkuMessages'; -import SkuStepper from './components/SkuStepper'; -import SkuRow from './components/SkuRow'; -import SkuRowItem from './components/SkuRowItem'; -import SkuRowPropItem from './components/SkuRowPropItem'; - -Locale.add(lang); - -Sku.SkuActions = SkuActions; -Sku.SkuHeader = SkuHeader; -Sku.SkuHeaderItem = SkuHeaderItem; -Sku.SkuMessages = SkuMessages; -Sku.SkuStepper = SkuStepper; -Sku.SkuRow = SkuRow; -Sku.SkuRowItem = SkuRowItem; -Sku.SkuRowPropItem = SkuRowPropItem; -Sku.skuHelper = skuHelper; -Sku.skuConstants = constants; - -export default Sku; diff --git a/src/sku/index.less b/src/sku/index.less deleted file mode 100644 index 301bd431f..000000000 --- a/src/sku/index.less +++ /dev/null @@ -1,373 +0,0 @@ -@import '../style/var'; -@import '../style/mixins/clearfix'; - -.van-sku { - &-container { - display: flex; - flex-direction: column; - align-items: stretch; - min-height: 50%; - max-height: 80%; - overflow-y: visible; - font-size: @font-size-md; - background: @white; - } - - &-body { - flex: 1 1 auto; - min-height: 44px; - overflow-y: scroll; - -webkit-overflow-scrolling: touch; - - &::-webkit-scrollbar { - display: none; - } - } - - &-header { - display: flex; - flex-shrink: 0; - margin: 0 @padding-md; - - &__img-wrap { - flex-shrink: 0; - width: 96px; - height: 96px; - margin: @padding-sm @padding-sm @padding-sm 0; - overflow: hidden; - border-radius: @border-radius-md; - } - - &__goods-info { - display: flex; - flex-direction: column; - justify-content: flex-end; - padding: @padding-sm 20px @padding-sm 0; - } - } - - &-header-item { - margin-top: @padding-xs; - color: @gray-6; - font-size: @font-size-sm; - line-height: 16px; - } - - &__price-symbol { - font-size: @font-size-lg; - vertical-align: bottom; - } - - &__price-num { - font-weight: @font-weight-bold; - font-size: 22px; - vertical-align: bottom; - word-wrap: break-word; - } - - &__goods-price { - // for price align - margin-left: -2px; - color: @red; - } - - &__price-tag { - position: relative; - display: inline-block; - margin-left: @padding-xs; - padding: 0 5px; - overflow: hidden; - color: @red; - font-size: @font-size-sm; - line-height: 16px; - border-radius: 8px; - - &::before { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: currentColor; - opacity: 0.1; - content: ''; - } - } - - &-group-container { - padding-top: @padding-sm; - - &--hide-soldout { - .van-sku-row__item--disabled { - display: none; - } - } - } - - /* sku row */ - &-row { - margin: 0 @padding-md @padding-sm; - - &:last-child { - margin-bottom: 0; - } - - &__item, - &__image-item { - position: relative; - overflow: hidden; - color: @text-color; - border-radius: @border-radius-md; - cursor: pointer; - - &::before { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - background: @sku-item-background-color; - content: ''; - } - - &--active { - color: @red; - - &::before { - background: currentColor; - opacity: 0.1; - } - } - } - - &__item { - display: inline-flex; - align-items: center; - justify-content: center; - min-width: 40px; - margin: 0 @padding-sm @padding-sm 0; - font-size: 13px; - line-height: 16px; - vertical-align: middle; - - &-img { - z-index: 1; - width: 24px; - height: 24px; - margin: 4px 0 4px 4px; - object-fit: cover; - border-radius: @border-radius-sm; - } - - &-name { - z-index: 1; - padding: @padding-xs; - } - - &--disabled { - color: @gray-5; - background: @active-color; - cursor: not-allowed; - - .van-sku-row__item-img { - opacity: 0.3; - } - } - } - - &__image { - margin-right: 0; - - &-item { - display: flex; - flex-direction: column; - width: 110px; - margin: 0 4px 4px 0; - border: 1px solid transparent; - - &:last-child { - margin-right: 0; - } - - &-img { - width: 100%; - height: 110px; - - &-icon { - position: absolute; - top: 0; - right: 0; - z-index: 3; - width: 18px; - height: 18px; - } - } - - &-name { - position: relative; - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; - height: 40px; - padding: @padding-base; - font-size: 12px; - line-height: 16px; - - span { - word-wrap: break-word; - } - } - - &--active { - border-color: currentColor; - } - - &--disabled { - color: @gray-5; - cursor: not-allowed; - - &::before { - z-index: 2; - background: @active-color; - opacity: 0.4; - } - } - } - } - - &__title { - padding-bottom: @padding-sm; - } - - &__title-multiple { - color: @gray-6; - } - - &__scroller { - margin: 0 -@padding-md; - overflow-x: scroll; - overflow-y: hidden; - -webkit-overflow-scrolling: touch; - - &::-webkit-scrollbar { - display: none; - } - } - - &__row { - display: inline-flex; - margin-bottom: 4px; - padding: 0 @padding-md; - } - - &__indicator { - width: 40px; - height: 4px; - background: @gray-3; - border-radius: 2px; - - &-wrapper { - display: flex; - justify-content: center; - padding-bottom: 16px; - } - - &-slider { - width: 50%; - height: 100%; - background-color: @red; - border-radius: 2px; - } - } - } - - &-stepper-stock { - padding: @padding-sm @padding-md; - overflow: hidden; - line-height: 30px; - } - - &__stepper { - float: right; - padding-left: @padding-base; - - &-title { - float: left; - } - - &-quota { - float: right; - color: @red; - font-size: @font-size-sm; - } - } - - &__stock { - display: inline-block; - margin-right: @padding-xs; - color: @gray-6; - font-size: @font-size-sm; - - &-num--highlight { - color: @red; - } - } - - &-messages { - padding-bottom: @padding-xl; - - &__image-cell { - .van-cell__title { - max-width: @field-label-width; - margin-right: @field-label-margin-right; - color: @field-label-color; - text-align: left; - word-wrap: break-word; - } - - .van-cell__value { - overflow: visible; - text-align: left; - } - - &-label { - color: @cell-label-color; - font-size: @cell-label-font-size; - line-height: @cell-label-line-height; - } - } - } - - &-actions { - display: flex; - flex-shrink: 0; - padding: @padding-xs @padding-md; - - .van-button { - height: 40px; - font-weight: @font-weight-bold; - font-size: @font-size-md; - border: none; - border-radius: 0; - - &:first-of-type { - border-top-left-radius: 20px; - border-bottom-left-radius: 20px; - } - - &:last-of-type { - border-top-right-radius: 20px; - border-bottom-right-radius: 20px; - } - - &--warning { - background: @gradient-orange; - } - - &--danger { - background: @gradient-red; - } - } - } -} diff --git a/src/sku/lang.ts b/src/sku/lang.ts deleted file mode 100644 index 22497164c..000000000 --- a/src/sku/lang.ts +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Sku only provide zh-CN lang by default - */ - -export default { - 'zh-CN': { - vanSku: { - select: '请选择', - selected: '已选', - selectSku: '请先选择商品规格', - soldout: '库存不足', - originPrice: '原价', - minusTip: '至少选择一件', - minusStartTip: (start: number) => `${start}件起售`, - unavailable: '商品已经无法购买啦', - stock: '剩余', - stockUnit: '件', - quotaTip: (quota: number) => `每人限购${quota}件`, - quotaUsedTip: (quota: number, count: number) => - `每人限购${quota}件,你已购买${count}件`, - }, - vanSkuActions: { - buy: '立即购买', - addCart: '加入购物车', - }, - vanSkuImgUploader: { - oversize: (maxSize: number) => - `最大可上传图片为${maxSize}MB,请尝试压缩图片尺寸`, - fail: '上传失败', - uploading: '上传中...', - }, - vanSkuStepper: { - quotaLimit: (quota: number) => `限购${quota}件`, - quotaStart: (start: number) => `${start}件起售`, - comma: ',', - num: '购买数量', - }, - vanSkuMessages: { - fill: '请填写', - upload: '请上传', - imageLabel: '仅限一张', - invalid: { - tel: '请填写正确的数字格式留言', - mobile: '手机号长度为6-20位数字', - email: '请填写正确的邮箱', - id_no: '请填写正确的身份证号码', - }, - placeholder: { - id_no: '请填写身份证号', - text: '请填写留言', - tel: '请填写数字', - email: '请填写邮箱', - date: '请选择日期', - time: '请选择时间', - textarea: '请填写留言', - mobile: '请填写手机号', - }, - }, - vanSkuRow: { - multiple: '可多选', - }, - vanSkuDatetimeField: { - title: { - date: '选择年月日', - time: '选择时间', - datetime: '选择日期时间', - }, - format: { - year: '年', - month: '月', - day: '日', - hour: '时', - minute: '分', - }, - }, - }, -}; diff --git a/src/sku/test/__snapshots__/demo.spec.js.snap b/src/sku/test/__snapshots__/demo.spec.js.snap deleted file mode 100644 index c95a77a1a..000000000 --- a/src/sku/test/__snapshots__/demo.spec.js.snap +++ /dev/null @@ -1,46 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders demo correctly 1`] = ` -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`; diff --git a/src/sku/test/demo.spec.js b/src/sku/test/demo.spec.js deleted file mode 100644 index 5c70922b5..000000000 --- a/src/sku/test/demo.spec.js +++ /dev/null @@ -1,4 +0,0 @@ -import Demo from '../demo'; -import { snapshotDemo } from '../../../test/demo'; - -snapshotDemo(Demo); diff --git a/src/sku/test/index.spec.js b/src/sku/test/index.spec.js deleted file mode 100644 index 9822dde0b..000000000 --- a/src/sku/test/index.spec.js +++ /dev/null @@ -1,87 +0,0 @@ -import { mount } from '../../../test'; -import Sku from '..'; -import { getSkuData, initialSku } from '../demo/data'; -import { stringToDate, dateToString } from '../utils/time-helper'; - -const skuData = getSkuData(); - -test('resetSelectedSku method', () => { - skuData.sku.messages = []; - - const wrapper = mount(Sku, { - propsData: { - value: true, - initialSku, - sku: skuData.sku, - quota: skuData.quota, - goods: skuData.goods_info, - goodsId: skuData.goods_id, - quotaUsed: skuData.quota_used, - hideStock: skuData.sku.hide_stock, - startSaleNum: skuData.start_sale_num, - }, - }); - - wrapper.find('.van-button--danger').trigger('click'); - expect(wrapper.emitted('buy-clicked').length).toEqual(1); - - wrapper.setProps({ initialSku: {} }); - wrapper.vm.resetSelectedSku(); - - wrapper.find('.van-button--danger').trigger('click'); - expect(wrapper.emitted('buy-clicked').length).toEqual(1); -}); - -test('message formatter', () => { - const skuData = getSkuData(); - - skuData.sku.messages = skuData.sku.messages.filter( - message => message.type === 'tel' - ); - - const wrapper = mount(Sku, { - propsData: { - value: true, - initialSku, - sku: skuData.sku, - quota: skuData.quota, - goods: skuData.goods_info, - goodsId: skuData.goods_id, - quotaUsed: skuData.quota_used, - hideStock: skuData.sku.hide_stock, - startSaleNum: skuData.start_sale_num, - }, - }); - - const input = wrapper.find('input'); - const correctValue = '15000000000'; - - // \u202c - input.element.value = '15000000000‬'; - input.trigger('input'); - - expect(input.element.value).toEqual(correctValue); - - // \u0020 - input.element.value = '150 0000 0000'; - input.trigger('input'); - - expect(input.element.value).toEqual(correctValue); - - // /[a-zA-z]/ - input.element.value = 'a-zA-z'; - input.trigger('input'); - - expect(input.element.value).toEqual(''); -}); - -test('stringToDate', () => { - expect(dateToString(stringToDate(''))).toEqual(''); - expect(dateToString(stringToDate('2020-07-01'))).toEqual('2020-07-01'); - expect(dateToString(stringToDate('2020-07-01 22:44'), 'datetime')).toEqual( - '2020-07-01 22:44' - ); - expect(dateToString(stringToDate('2020-12-31 23:59'), 'datetime')).toEqual( - '2020-12-31 23:59' - ); -}); diff --git a/src/sku/utils/sku-helper.js b/src/sku/utils/sku-helper.js deleted file mode 100644 index f73465b54..000000000 --- a/src/sku/utils/sku-helper.js +++ /dev/null @@ -1,158 +0,0 @@ -import { UNSELECTED_SKU_VALUE_ID } from '../constants'; - -/* - normalize sku tree - - [ - { - count: 2, - k: "品种", // 规格名称 skuKeyName - k_id: "1200", // skuKeyId - k_s: "s1" // skuKeyStr - v: [ // skuValues - { // skuValue - id: "1201", // skuValueId - name: "萌" // 具体的规格值 skuValueName - }, { - id: "973", - name: "帅" - } - ] - }, - ... - ] - | - v - { - s1: [{ - id: "1201", - name: "萌" - }, { - id: "973", - name: "帅" - }], - ... - } - */ -export const normalizeSkuTree = (skuTree) => { - const normalizedTree = {}; - skuTree.forEach((treeItem) => { - normalizedTree[treeItem.k_s] = treeItem.v; - }); - return normalizedTree; -}; - -export const normalizePropList = (propList) => { - const normalizedProp = {}; - propList.forEach((item) => { - const itemObj = {}; - item.v.forEach((it) => { - itemObj[it.id] = it; - }); - normalizedProp[item.k_id] = itemObj; - }); - return normalizedProp; -}; - -// 判断是否所有的sku都已经选中 -export const isAllSelected = (skuTree, selectedSku) => { - // 筛选selectedSku对象中key值不为空的值 - const selected = Object.keys(selectedSku).filter( - (skuKeyStr) => selectedSku[skuKeyStr] !== UNSELECTED_SKU_VALUE_ID - ); - return skuTree.length === selected.length; -}; - -// 根据已选择的 sku 获取 skuComb -export const getSkuComb = (skuList, selectedSku) => { - const skuComb = skuList.filter((item) => - Object.keys(selectedSku).every( - (skuKeyStr) => String(item[skuKeyStr]) === String(selectedSku[skuKeyStr]) - ) - ); - return skuComb[0]; -}; - -// 获取已选择的sku名称 -export const getSelectedSkuValues = (skuTree, selectedSku) => { - const normalizedTree = normalizeSkuTree(skuTree); - return Object.keys(selectedSku).reduce((selectedValues, skuKeyStr) => { - const skuValues = normalizedTree[skuKeyStr]; - const skuValueId = selectedSku[skuKeyStr]; - - if (skuValueId !== UNSELECTED_SKU_VALUE_ID) { - const skuValue = skuValues.filter((value) => value.id === skuValueId)[0]; - skuValue && selectedValues.push(skuValue); - } - return selectedValues; - }, []); -}; - -// 判断sku是否可选 -export const isSkuChoosable = (skuList, selectedSku, skuToChoose) => { - const { key, valueId } = skuToChoose; - - // 先假设sku已选中,拼入已选中sku对象中 - const matchedSku = { - ...selectedSku, - [key]: valueId, - }; - - // 再判断剩余sku是否全部不可选,若不可选则当前sku不可选中 - const skusToCheck = Object.keys(matchedSku).filter( - (skuKey) => matchedSku[skuKey] !== UNSELECTED_SKU_VALUE_ID - ); - - const filteredSku = skuList.filter((sku) => - skusToCheck.every( - (skuKey) => String(matchedSku[skuKey]) === String(sku[skuKey]) - ) - ); - - const stock = filteredSku.reduce((total, sku) => { - total += sku.stock_num; - return total; - }, 0); - return stock > 0; -}; - -export const getSelectedPropValues = (propList, selectedProp) => { - const normalizeProp = normalizePropList(propList); - return Object.keys(selectedProp).reduce((acc, cur) => { - selectedProp[cur].forEach((it) => { - acc.push({ - ...normalizeProp[cur][it], - }); - }); - return acc; - }, []); -}; - -export const getSelectedProperties = (propList, selectedProp) => { - const list = []; - (propList || []).forEach((prop) => { - if (selectedProp[prop.k_id] && selectedProp[prop.k_id].length > 0) { - const v = []; - prop.v.forEach((it) => { - if (selectedProp[prop.k_id].indexOf(it.id) > -1) { - v.push({ ...it }); - } - }); - list.push({ - ...prop, - v, - }); - } - }); - return list; -}; - -export default { - normalizeSkuTree, - getSkuComb, - getSelectedSkuValues, - isAllSelected, - isSkuChoosable, - getSelectedPropValues, - getSelectedProperties, -}; diff --git a/src/sku/utils/time-helper.js b/src/sku/utils/time-helper.js deleted file mode 100644 index 2010e10b8..000000000 --- a/src/sku/utils/time-helper.js +++ /dev/null @@ -1,28 +0,0 @@ -import { padZero } from '../../utils/format/string'; - -// 字符串转 Date -// 只处理 YYYY-MM-DD 或者 YYYY-MM-DD HH:MM 格式 -export function stringToDate(timeString) { - if (!timeString) { - return null; - } - return new Date(timeString.replace(/-/g, '/')); -} - -// Date 转字符串 -// type: date or datetime -export function dateToString(date, type = 'date') { - if (!date) { - return ''; - } - const year = date.getFullYear(); - const month = date.getMonth() + 1; - const day = date.getDate(); - let timeString = `${year}-${padZero(month)}-${padZero(day)}`; - if (type === 'datetime') { - const hours = date.getHours(); - const minute = date.getMinutes(); - timeString += ` ${padZero(hours)}:${padZero(minute)}`; - } - return timeString; -}