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
-
-
-
-
- ¥{{ props.price }}
-
-
-
-
-
-
-
- Button
-
-
-
- Button
-
-
-
-
-```
-
-## 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
-
-
-
-
- ¥{{ props.price }}
-
-
-
-
-
-
-
- 积分兑换
-
-
-
- 买买买
-
-
-
-
-```
-
-## 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-image-extra')}
-
- {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 (
-
- );
- }
-
- 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 @@
-
-
-
-
-
-
- {{ t('basicUsage') }}
-
-
-
-
-
-
-
-
- {{ t('customStepper') }}
-
-
-
-
-
-
-
-
- {{ t('hideSoldoutSku') }}
-
-
-
-
-
-
-
-
-
- {{ t('largeImageMode') }}
-
-
-
-
-
-
-
-
-
- ¥
- {{ price }}
-
-
-
-
-
-
-
-
- {{ t('button1') }}
-
-
- {{ t('button2') }}
-
-
-
-
-
- {{ t('customBySlot') }}
-
-
-
-
-
-
-
-
-
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;
-}