[improvement] rename packages dir to src (#3659)
This commit is contained in:
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-button
|
||||
type="primary"
|
||||
@click="show1 = true"
|
||||
>
|
||||
{{ $t('buttonText') }}
|
||||
</van-button>
|
||||
<van-action-sheet
|
||||
v-model="show1"
|
||||
:actions="simpleActions"
|
||||
safe-area-inset-bottom
|
||||
@select="onSelect"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('status')">
|
||||
<van-button
|
||||
type="primary"
|
||||
@click="show2 = true"
|
||||
>
|
||||
{{ $t('buttonText') }}
|
||||
</van-button>
|
||||
<van-action-sheet
|
||||
v-model="show2"
|
||||
:actions="statusActions"
|
||||
safe-area-inset-bottom
|
||||
@select="onSelect"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title2')">
|
||||
<van-button
|
||||
type="primary"
|
||||
@click="show3 = true"
|
||||
>
|
||||
{{ $t('buttonText') }}
|
||||
</van-button>
|
||||
<van-action-sheet
|
||||
v-model="show3"
|
||||
:actions="simpleActions"
|
||||
:cancel-text="$t('cancel')"
|
||||
safe-area-inset-bottom
|
||||
@cancel="onCancel"
|
||||
@select="onSelect"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title3')">
|
||||
<van-button
|
||||
type="primary"
|
||||
@click="show4 = true"
|
||||
>
|
||||
{{ $t('buttonText') }}
|
||||
</van-button>
|
||||
<van-action-sheet
|
||||
v-model="show4"
|
||||
:title="$t('title')"
|
||||
safe-area-inset-bottom
|
||||
>
|
||||
<p>{{ $t('content') }}</p>
|
||||
</van-action-sheet>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
buttonText: '弹出菜单',
|
||||
title2: '展示取消按钮',
|
||||
title3: '展示标题栏',
|
||||
status: '选项状态',
|
||||
description: '描述信息',
|
||||
disabledOption: '禁用选项'
|
||||
},
|
||||
'en-US': {
|
||||
buttonText: 'Show ActionSheet',
|
||||
title2: 'ActionSheet with cancel button',
|
||||
title3: 'ActionSheet with title',
|
||||
status: 'Status',
|
||||
description: 'Description',
|
||||
disabledOption: 'Disabled Option'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
show1: false,
|
||||
show2: false,
|
||||
show3: false,
|
||||
show4: false
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
simpleActions() {
|
||||
return [
|
||||
{ name: this.$t('option') },
|
||||
{ name: this.$t('option') },
|
||||
{ name: this.$t('option'), subname: this.$t('description') }
|
||||
];
|
||||
},
|
||||
|
||||
statusActions() {
|
||||
return [
|
||||
{ name: this.$t('option') },
|
||||
{ loading: true },
|
||||
{ name: this.$t('disabledOption'), disabled: true }
|
||||
];
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSelect(item) {
|
||||
this.show1 = false;
|
||||
this.show2 = false;
|
||||
this.$toast(item.name);
|
||||
},
|
||||
|
||||
onCancel() {
|
||||
this.$toast('cancel');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import "../../style/var";
|
||||
|
||||
.demo-action-sheet {
|
||||
background-color: @white;
|
||||
|
||||
.van-button {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,128 @@
|
||||
# ActionSheet
|
||||
|
||||
### Install
|
||||
``` javascript
|
||||
import { ActionSheet } from 'vant';
|
||||
|
||||
Vue.use(ActionSheet);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Use `actions` prop to set options of action-sheet.
|
||||
|
||||
```html
|
||||
<van-action-sheet
|
||||
v-model="show"
|
||||
:actions="actions"
|
||||
@select="onSelect"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
actions: [
|
||||
{ name: 'Option' },
|
||||
{ name: 'Option' },
|
||||
{ name: 'Option', subname: 'Description' }
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSelect(item) {
|
||||
this.show = false;
|
||||
Toast(item.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Status
|
||||
|
||||
```html
|
||||
<van-action-sheet
|
||||
v-model="show"
|
||||
:actions="actions"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
actions: [
|
||||
{ name: 'Option' },
|
||||
{ loading: true },
|
||||
{ name: 'Disabled Option', disabled: true }
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ActionSheet with cancel button
|
||||
|
||||
```html
|
||||
<van-action-sheet
|
||||
v-model="show"
|
||||
:actions="actions"
|
||||
cancel-text="Cancel"
|
||||
@select="onSelect"
|
||||
@cancel="onCancel"
|
||||
/>
|
||||
```
|
||||
|
||||
### ActionSheet with title
|
||||
|
||||
```html
|
||||
<van-action-sheet v-model="show" title="Title">
|
||||
<p>Content</p>
|
||||
</van-action-sheet>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| actions | Options | `Array` | `[]` |
|
||||
| title | Title | `String` | - |
|
||||
| cancel-text | Text of cancel button | `String` | - |
|
||||
| overlay | Whether to show overlay | `Boolean` | `true` |
|
||||
| close-on-click-action | Whether to close when click action | `Boolean` | `false` |
|
||||
| close-on-click-overlay | Whether to close when click overlay | `Boolean` | `true` |
|
||||
| lazy-render | Whether to lazy render util appeared | `Boolean` | `true` |
|
||||
| lock-scroll | Whether to lock background scroll | `Boolean` | `true` |
|
||||
| duration | Transition duration, unit second | `Number` | `0.3` |
|
||||
| get-container | Return the mount node for action-sheet | `String | () => HTMLElement` | - |
|
||||
| safe-area-inset-bottom | Whether to enable bottom safe area adaptation, to enable those features use `viewport-fit=cover` in the `viewport` meta tag | `Boolean` | `false` |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
|------|------|------|
|
||||
| select | Triggered when click option | item, index |
|
||||
| cancel | Triggered when cancel click | - |
|
||||
| click-overlay | Triggered when click overlay | - |
|
||||
| open | Triggered when open ActionSheet | - |
|
||||
| opened | Triggered when opened ActionSheet | - |
|
||||
| close | Triggered when close ActionSheet | - |
|
||||
| closed | Triggered when closed ActionSheet | - |
|
||||
|
||||
### Data struct of actions
|
||||
|
||||
| key | Description |
|
||||
|------|------|
|
||||
| name | Title |
|
||||
| subname | Subtitle |
|
||||
| className | className for the option |
|
||||
| loading | Whether to be loading status |
|
||||
| disabled | Whether to be disabled |
|
||||
@@ -0,0 +1,64 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-action-sheet {
|
||||
max-height: @action-sheet-max-height;
|
||||
color: @action-sheet-item-text-color;
|
||||
|
||||
&__item,
|
||||
&__cancel {
|
||||
font-size: @action-sheet-item-font-size;
|
||||
line-height: @action-sheet-item-height;
|
||||
text-align: center;
|
||||
background-color: @action-sheet-item-background;
|
||||
|
||||
&:active {
|
||||
background-color: @active-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__item {
|
||||
height: @action-sheet-item-height;
|
||||
|
||||
&--disabled {
|
||||
color: @gray;
|
||||
|
||||
&:active {
|
||||
background-color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__subname {
|
||||
margin-left: 5px;
|
||||
color: @action-sheet-subname-color;
|
||||
font-size: @action-sheet-subname-font-size;
|
||||
}
|
||||
|
||||
&__cancel::before {
|
||||
display: block;
|
||||
height: 10px;
|
||||
background-color: @background-color;
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
&__header {
|
||||
font-size: @action-sheet-header-font-size;
|
||||
line-height: @action-sheet-header-height;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
padding: 0 15px;
|
||||
color: @action-sheet-close-icon-color;
|
||||
font-size: @action-sheet-close-icon-size;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
&--safe-area-inset-bottom {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { emit, inherit } from '../utils/functional';
|
||||
import { PopupMixin } from '../mixins/popup';
|
||||
import Icon from '../icon';
|
||||
import Popup from '../popup';
|
||||
import Loading from '../loading';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
import { PopupMixinProps } from '../mixins/popup/type';
|
||||
|
||||
export type ActionSheetItem = {
|
||||
name: string;
|
||||
subname?: string;
|
||||
loading?: boolean;
|
||||
disabled?: boolean;
|
||||
className?: string;
|
||||
callback?: (item: ActionSheetItem) => void;
|
||||
};
|
||||
|
||||
export type ActionSheetProps = PopupMixinProps & {
|
||||
title?: string;
|
||||
actions: ActionSheetItem[];
|
||||
duration: number;
|
||||
cancelText?: string;
|
||||
closeOnClickAction?: boolean;
|
||||
safeAreaInsetBottom?: boolean;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('action-sheet');
|
||||
|
||||
function ActionSheet(
|
||||
h: CreateElement,
|
||||
props: ActionSheetProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<ActionSheetProps>
|
||||
) {
|
||||
const { title, cancelText } = props;
|
||||
|
||||
function onCancel() {
|
||||
emit(ctx, 'input', false);
|
||||
emit(ctx, 'cancel');
|
||||
}
|
||||
|
||||
function Header() {
|
||||
if (title) {
|
||||
return (
|
||||
<div class={[bem('header'), 'van-hairline--bottom']}>
|
||||
{title}
|
||||
<Icon name="close" class={bem('close')} onClick={onCancel} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Content() {
|
||||
if (slots.default) {
|
||||
return <div class={bem('content')}>{slots.default()}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Option(item: ActionSheetItem, index: number) {
|
||||
const disabled = item.disabled || item.loading;
|
||||
|
||||
function onClickOption(event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
|
||||
if (item.disabled || item.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.callback) {
|
||||
item.callback(item);
|
||||
}
|
||||
|
||||
emit(ctx, 'select', item, index);
|
||||
|
||||
if (props.closeOnClickAction) {
|
||||
emit(ctx, 'input', false);
|
||||
}
|
||||
}
|
||||
|
||||
function OptionContent() {
|
||||
if (item.loading) {
|
||||
return <Loading size="20px" />;
|
||||
}
|
||||
|
||||
return [
|
||||
<span class={bem('name')}>{item.name}</span>,
|
||||
item.subname && <span class={bem('subname')}>{item.subname}</span>
|
||||
];
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
class={[bem('item', { disabled }), item.className, 'van-hairline--top']}
|
||||
onClick={onClickOption}
|
||||
>
|
||||
{OptionContent()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CancelText() {
|
||||
if (cancelText) {
|
||||
return (
|
||||
<div class={bem('cancel')} onClick={onCancel}>
|
||||
{cancelText}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Popup
|
||||
class={bem({ 'safe-area-inset-bottom': props.safeAreaInsetBottom })}
|
||||
position="bottom"
|
||||
value={props.value}
|
||||
overlay={props.overlay}
|
||||
duration={props.duration}
|
||||
lazyRender={props.lazyRender}
|
||||
lockScroll={props.lockScroll}
|
||||
getContainer={props.getContainer}
|
||||
closeOnClickOverlay={props.closeOnClickOverlay}
|
||||
{...inherit(ctx, true)}
|
||||
>
|
||||
{Header()}
|
||||
{props.actions.map(Option)}
|
||||
{Content()}
|
||||
{CancelText()}
|
||||
</Popup>
|
||||
);
|
||||
}
|
||||
|
||||
ActionSheet.props = {
|
||||
...PopupMixin.props,
|
||||
title: String,
|
||||
actions: Array,
|
||||
cancelText: String,
|
||||
getContainer: [String, Function],
|
||||
closeOnClickAction: Boolean,
|
||||
safeAreaInsetBottom: Boolean,
|
||||
duration: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
overlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
closeOnClickOverlay: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
};
|
||||
|
||||
export default createComponent<ActionSheetProps>(ActionSheet);
|
||||
@@ -0,0 +1,26 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div><button class="van-button van-button--primary van-button--normal"><span class="van-button__text">
|
||||
弹出菜单
|
||||
</span></button>
|
||||
<!---->
|
||||
</div>
|
||||
<div><button class="van-button van-button--primary van-button--normal"><span class="van-button__text">
|
||||
弹出菜单
|
||||
</span></button>
|
||||
<!---->
|
||||
</div>
|
||||
<div><button class="van-button van-button--primary van-button--normal"><span class="van-button__text">
|
||||
弹出菜单
|
||||
</span></button>
|
||||
<!---->
|
||||
</div>
|
||||
<div><button class="van-button van-button--primary van-button--normal"><span class="van-button__text">
|
||||
弹出菜单
|
||||
</span></button>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,23 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`callback events 1`] = `
|
||||
<div class="van-popup van-popup--bottom van-action-sheet" name="van-popup-slide-bottom">
|
||||
<div class="van-action-sheet__item van-hairline--top"><span class="van-action-sheet__name">Option</span></div>
|
||||
<div class="van-action-sheet__item van-action-sheet__item--disabled van-hairline--top"><span class="van-action-sheet__name">Option</span></div>
|
||||
<div class="van-action-sheet__item van-action-sheet__item--disabled van-hairline--top">
|
||||
<div class="van-loading van-loading--circular"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(201, 201, 201); width: 20px; height: 20px;"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
|
||||
</div>
|
||||
<div class="van-action-sheet__item van-hairline--top"><span class="van-action-sheet__name">Option</span><span class="van-action-sheet__subname">Subname</span></div>
|
||||
<div class="van-action-sheet__cancel">Cancel</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`disable lazy-render 1`] = `
|
||||
<div class="van-popup van-popup--bottom van-action-sheet" style="display: none;" name="van-popup-slide-bottom">
|
||||
<div class="van-action-sheet__item van-hairline--top"><span class="van-action-sheet__name">Option</span></div>
|
||||
<div class="van-action-sheet__item van-hairline--top"><span class="van-action-sheet__name">Option</span></div>
|
||||
<div class="van-action-sheet__cancel">Cancel</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render title and default slot 1`] = ``;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,139 @@
|
||||
import { mount } from '../../../test/utils';
|
||||
import ActionSheet from '..';
|
||||
|
||||
test('callback events', () => {
|
||||
const callback = jest.fn();
|
||||
const onInput = jest.fn();
|
||||
const onCancel = jest.fn();
|
||||
const onSelect = jest.fn();
|
||||
|
||||
const actions = [
|
||||
{ name: 'Option', callback },
|
||||
{ name: 'Option', disabled: true },
|
||||
{ name: 'Option', loading: true },
|
||||
{ name: 'Option', subname: 'Subname' },
|
||||
];
|
||||
|
||||
const wrapper = mount(ActionSheet, {
|
||||
propsData: {
|
||||
value: true,
|
||||
actions,
|
||||
cancelText: 'Cancel'
|
||||
},
|
||||
context: {
|
||||
on: {
|
||||
input: onInput,
|
||||
cancel: onCancel,
|
||||
select: onSelect
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const options = wrapper.findAll('.van-action-sheet__item');
|
||||
options.at(0).trigger('click');
|
||||
options.at(1).trigger('click');
|
||||
wrapper.find('.van-action-sheet__cancel').trigger('click');
|
||||
|
||||
expect(callback).toHaveBeenCalled();
|
||||
expect(onCancel).toHaveBeenCalled();
|
||||
expect(onInput).toHaveBeenCalledWith(false);
|
||||
expect(onSelect).toHaveBeenCalledWith(actions[0], 0);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('click overlay and close', () => {
|
||||
const onInput = jest.fn();
|
||||
const onClickOverlay = jest.fn();
|
||||
const div = document.createElement('div');
|
||||
|
||||
mount({
|
||||
template: `
|
||||
<div>
|
||||
<action-sheet
|
||||
:value="true"
|
||||
:get-container="getContainer"
|
||||
@input="onInput"
|
||||
@click-overlay="onClickOverlay"
|
||||
/>
|
||||
</div>
|
||||
`,
|
||||
components: {
|
||||
ActionSheet
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
getContainer: () => div
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
onInput,
|
||||
onClickOverlay
|
||||
}
|
||||
});
|
||||
|
||||
div.querySelector('.van-overlay').click();
|
||||
expect(onInput).toHaveBeenCalledWith(false);
|
||||
expect(onClickOverlay).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('disable lazy-render', () => {
|
||||
const wrapper = mount(ActionSheet, {
|
||||
propsData: {
|
||||
lazyRender: false,
|
||||
actions: [
|
||||
{ name: 'Option' },
|
||||
{ name: 'Option' }
|
||||
],
|
||||
cancelText: 'Cancel'
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render title and default slot', () => {
|
||||
const wrapper = mount(ActionSheet, {
|
||||
propsData: {
|
||||
title: 'Title'
|
||||
},
|
||||
scopedSlots: {
|
||||
default() {
|
||||
return 'Default';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('get container', () => {
|
||||
const wrapper = mount(ActionSheet, {
|
||||
propsData: {
|
||||
value: true,
|
||||
getContainer: 'body'
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.vm.$el.parentNode).toEqual(document.body);
|
||||
});
|
||||
|
||||
test('close-on-click-action prop', () => {
|
||||
const onInput = jest.fn();
|
||||
const wrapper = mount(ActionSheet, {
|
||||
propsData: {
|
||||
value: true,
|
||||
actions: [{ name: 'Option' }],
|
||||
closeOnClickAction: true
|
||||
},
|
||||
context: {
|
||||
on: {
|
||||
input: onInput
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const option = wrapper.find('.van-action-sheet__item');
|
||||
option.trigger('click');
|
||||
|
||||
expect(onInput).toHaveBeenCalledWith(false);
|
||||
});
|
||||
@@ -0,0 +1,137 @@
|
||||
# ActionSheet 上拉菜单
|
||||
|
||||
### 引入
|
||||
``` javascript
|
||||
import { ActionSheet } from 'vant';
|
||||
|
||||
Vue.use(ActionSheet);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
`ActionSheet`通过`actions`数组来定义展示的选项,数组的每一项是一个对象,对象属性见文档下方表格。
|
||||
|
||||
```html
|
||||
<van-action-sheet
|
||||
v-model="show"
|
||||
:actions="actions"
|
||||
@select="onSelect"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
actions: [
|
||||
{ name: '选项' },
|
||||
{ name: '选项' },
|
||||
{ name: '选项', subname: '描述信息' }
|
||||
]
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSelect(item) {
|
||||
// 点击选项时默认不会关闭菜单,可以手动关闭
|
||||
this.show = false;
|
||||
Toast(item.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 选项状态
|
||||
|
||||
选项可以设置为加载状态或禁用状态
|
||||
|
||||
```html
|
||||
<van-action-sheet
|
||||
v-model="show"
|
||||
:actions="actions"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
actions: [
|
||||
{ name: '选项' },
|
||||
{ name: '选项', loading: true },
|
||||
{ name: '禁用选项', disabled: true }
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 展示取消按钮
|
||||
|
||||
设置`cancelText`属性后,会在底部展示取消按钮,点击后关闭当前菜单
|
||||
|
||||
```html
|
||||
<van-action-sheet
|
||||
v-model="show"
|
||||
:actions="actions"
|
||||
cancel-text="取消"
|
||||
@select="onSelect"
|
||||
@cancel="onCancel"
|
||||
/>
|
||||
```
|
||||
|
||||
### 展示标题栏
|
||||
|
||||
通过设置`title`属性展示标题栏,同时可以使用插槽自定义菜单内容
|
||||
|
||||
```html
|
||||
<van-action-sheet v-model="show" title="标题">
|
||||
<p>内容</p>
|
||||
</van-action-sheet>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| actions | 菜单选项 | `Array` | `[]` | - |
|
||||
| title | 标题 | `String` | - | - |
|
||||
| cancel-text | 取消按钮文字 | `String` | - | - |
|
||||
| overlay | 是否显示遮罩层 | `Boolean` | `true` | - |
|
||||
| close-on-click-action | 是否在点击选项后关闭 | `Boolean` | `false` | 2.0.0 |
|
||||
| close-on-click-overlay | 是否在点击遮罩层后关闭 | `Boolean` | `true` | - |
|
||||
| lazy-render | 是否在显示弹层时才渲染节点 | `Boolean` | `true` | 1.1.11 |
|
||||
| lock-scroll | 是否锁定背景滚动 | `Boolean` | `true` | 2.0.0 |
|
||||
| duration | 动画时长,单位秒 | `Number` | `0.3` | 2.0.3 |
|
||||
| get-container | 指定挂载的节点,可以传入选择器,<br>或一个返回节点的函数 | `String | () => HTMLElement` | - | - |
|
||||
| safe-area-inset-bottom | 是否开启 iPhone X 底部安全区适配,需要在 `viewport` meta 标签中设置 `viewport-fit=cover` | `Boolean` | `false` | 1.6.15 |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|------|------|------|
|
||||
| select | 选中选项时触发,禁用或加载状态下不会触发 | item: 选项对应的对象, index: 选择对应的索引 |
|
||||
| cancel | 取消按钮点击时触发 | - |
|
||||
| click-overlay | 点击遮罩层时触发 | - |
|
||||
| open | 打开菜单时触发 | - |
|
||||
| opened | 打开菜单且动画结束后触发 | - |
|
||||
| close | 关闭菜单时触发 | - |
|
||||
| closed | 关闭菜单且动画结束后触发 | - |
|
||||
|
||||
### actions
|
||||
|
||||
`Props`中的`actions`为一个对象数组,数组中的每一个对象配置每一列,每一列有以下`key`:
|
||||
|
||||
| key | 说明 |
|
||||
|------|------|
|
||||
| name | 标题 |
|
||||
| subname | 二级标题 |
|
||||
| className | 为对应列添加额外的 class |
|
||||
| loading | 是否为加载状态 |
|
||||
| disabled | 是否为禁用状态 |
|
||||
@@ -0,0 +1,84 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { isAndroid } from '../utils/validate/system';
|
||||
import Cell from '../cell';
|
||||
import Field from '../field';
|
||||
|
||||
const [createComponent, bem, t] = createNamespace('address-edit-detail');
|
||||
const android = isAndroid();
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
value: String,
|
||||
error: Boolean,
|
||||
focused: Boolean,
|
||||
detailRows: Number,
|
||||
searchResult: Array,
|
||||
showSearchResult: Boolean
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSelect(express) {
|
||||
this.$emit('select-search', express);
|
||||
this.$emit(
|
||||
'input',
|
||||
`${express.address || ''} ${express.name || ''}`.trim()
|
||||
);
|
||||
},
|
||||
|
||||
onFinish() {
|
||||
this.$refs.field.blur();
|
||||
},
|
||||
|
||||
renderFinish() {
|
||||
const show = this.value && this.focused && android;
|
||||
if (show) {
|
||||
return (
|
||||
<div class={bem('finish')} onClick={this.onFinish}>
|
||||
{t('complete')}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
renderSearchResult() {
|
||||
const { searchResult } = this;
|
||||
const show = this.focused && searchResult && this.showSearchResult;
|
||||
if (show) {
|
||||
return searchResult.map(express => (
|
||||
<Cell
|
||||
key={express.name + express.address}
|
||||
title={express.name}
|
||||
label={express.address}
|
||||
icon="location-o"
|
||||
clickable
|
||||
onClick={() => {
|
||||
this.onSelect(express);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
return (
|
||||
<Cell class={bem()}>
|
||||
<Field
|
||||
autosize
|
||||
ref="field"
|
||||
rows={this.detailRows}
|
||||
clearable={!android}
|
||||
type="textarea"
|
||||
maxlength="200"
|
||||
value={this.value}
|
||||
error={this.error}
|
||||
label={t('label')}
|
||||
placeholder={t('placeholder')}
|
||||
scopedSlots={{ icon: this.renderFinish }}
|
||||
{...{ on: this.$listeners }}
|
||||
/>
|
||||
{this.renderSearchResult()}
|
||||
</Cell>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-address-edit
|
||||
:area-list="areaList"
|
||||
show-postal
|
||||
show-delete
|
||||
show-set-default
|
||||
show-search-result
|
||||
:search-result="searchResult"
|
||||
@save="onSave"
|
||||
@delete="onDelete"
|
||||
@change-detail="onChangeDetail"
|
||||
/>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import areaList from '../../area/demo/area';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
searchResult: [{
|
||||
name: '黄龙万科中心',
|
||||
address: '杭州市西湖区'
|
||||
}, {
|
||||
name: '黄龙万科中心G座'
|
||||
}, {
|
||||
name: '黄龙万科中心H座',
|
||||
address: '杭州市西湖区'
|
||||
}]
|
||||
},
|
||||
'en-US': {
|
||||
searchResult: [{
|
||||
name: 'Name1',
|
||||
address: 'Address'
|
||||
}, {
|
||||
name: 'Name2'
|
||||
}, {
|
||||
name: 'Name3',
|
||||
address: 'Address'
|
||||
}]
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
areaList,
|
||||
searchResult: []
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSave() {
|
||||
this.$toast(this.$t('save'));
|
||||
},
|
||||
|
||||
onDelete() {
|
||||
this.$toast(this.$t('delete'));
|
||||
},
|
||||
|
||||
onChangeDetail(val) {
|
||||
this.searchResult = val ? this.$t('searchResult') : [];
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,130 @@
|
||||
# AddressEdit
|
||||
|
||||
### Install
|
||||
|
||||
``` javascript
|
||||
import { AddressEdit } from 'vant';
|
||||
|
||||
Vue.use(AddressEdit);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-address-edit
|
||||
:area-list="areaList"
|
||||
show-postal
|
||||
show-delete
|
||||
show-set-default
|
||||
show-search-result
|
||||
:search-result="searchResult"
|
||||
@save="onSave"
|
||||
@delete="onDelete"
|
||||
@change-detail="onChangeDetail"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
areaList,
|
||||
searchResult: []
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSave() {
|
||||
Toast('save');
|
||||
},
|
||||
onDelete() {
|
||||
Toast('delete');
|
||||
},
|
||||
onChangeDetail(val) {
|
||||
if (val) {
|
||||
this.searchResult = [{
|
||||
name: '黄龙万科中心',
|
||||
address: '杭州市西湖区'
|
||||
}];
|
||||
} else {
|
||||
this.searchResult = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| area-list | Area List | `Object` | - |
|
||||
| address-info | Address Info | `Object` | `{}` |
|
||||
| search-result | Address search result | `Array` | `[]` |
|
||||
| show-postal | Whether to show postal field | `Boolean` | `false` |
|
||||
| show-delete | Whether to show delete button | `Boolean` | `false` |
|
||||
| show-set-default | Whether to show default address switch | `Boolean` | `false` |
|
||||
| show-search-result | Whether to show address search result | `Boolean` | `false` |
|
||||
| save-button-text | Save button text | `String` | `Save` |
|
||||
| delete-button-text | Delete button text | `String` | `Delete` |
|
||||
| is-saving | Whether to show save button loading status | `Boolean` | `false` |
|
||||
| is-deleting | Whether to show delete button loading status | `Boolean` | `false` |
|
||||
| tel-validator | The method to validate tel | `(tel: string) => boolean` | - |
|
||||
| validator | Custom validator | `(key, value) => string` | - | 1.3.9 |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
|------|------|------|
|
||||
| save | Triggered when click save button | content:form content |
|
||||
| focus | Triggered when focus field | key: field name |
|
||||
| delete | Triggered when confirm delete | content:form content |
|
||||
| cancel-delete | Triggered when cancel delete | content:form content |
|
||||
| select-search | Triggered when select search result | value: search content |
|
||||
| change-area | Triggered when change area | values: area values |
|
||||
| change-detail | Triggered when address detail changed | value: address detail |
|
||||
| change-default | Triggered when switch default address | value: checked |
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description |
|
||||
|------|------|
|
||||
| default | Custom content below postal |
|
||||
|
||||
### Methods
|
||||
|
||||
Use ref to get address-edit instance and call instance methods
|
||||
|
||||
| Name | Attribute | Return value | Description |
|
||||
|------|------|------|------|
|
||||
| setAddressDetail | addressDetail: string | - | Set address detail |
|
||||
|
||||
### addressInfo Data Structure
|
||||
|
||||
| key | Description | Type |
|
||||
|------|------|------|
|
||||
| id | Address Id | `String | Number` |
|
||||
| name | Name | `String` |
|
||||
| tel | Phone | `String` |
|
||||
| province | Province | `String` |
|
||||
| city | City | `String` |
|
||||
| county | County | `String` |
|
||||
| addressDetail | Detailed Address | `String` |
|
||||
| areaCode | Area code | `String` |
|
||||
| postalCode | Postal code | `String` |
|
||||
| isDefault | Is default address | `Boolean` |
|
||||
|
||||
### searchResult Data Structure
|
||||
|
||||
| key | Description | Type |
|
||||
|------|------|------|
|
||||
| name | Name | `String` |
|
||||
| address | Address | `String` |
|
||||
|
||||
### Area Data Structure
|
||||
|
||||
Please refer to [Area](#/en-US/area) component。
|
||||
@@ -0,0 +1,335 @@
|
||||
import { createNamespace, isObj } from '../utils';
|
||||
import { isMobile } from '../utils/validate/mobile';
|
||||
import Area from '../area';
|
||||
import Field from '../field';
|
||||
import Popup from '../popup';
|
||||
import Toast from '../toast';
|
||||
import Button from '../button';
|
||||
import Dialog from '../dialog';
|
||||
import Detail from './Detail';
|
||||
import SwitchCell from '../switch-cell';
|
||||
|
||||
const [createComponent, bem, t] = createNamespace('address-edit');
|
||||
|
||||
const defaultData = {
|
||||
name: '',
|
||||
tel: '',
|
||||
country: '',
|
||||
province: '',
|
||||
city: '',
|
||||
county: '',
|
||||
areaCode: '',
|
||||
postalCode: '',
|
||||
addressDetail: '',
|
||||
isDefault: false
|
||||
};
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
areaList: Object,
|
||||
isSaving: Boolean,
|
||||
isDeleting: Boolean,
|
||||
validator: Function,
|
||||
showDelete: Boolean,
|
||||
showPostal: Boolean,
|
||||
searchResult: Array,
|
||||
showSetDefault: Boolean,
|
||||
showSearchResult: Boolean,
|
||||
saveButtonText: String,
|
||||
deleteButtonText: String,
|
||||
showArea: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
showDetail: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
detailRows: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
addressInfo: {
|
||||
type: Object,
|
||||
default: () => ({ ...defaultData })
|
||||
},
|
||||
telValidator: {
|
||||
type: Function,
|
||||
default: isMobile
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
data: {},
|
||||
showAreaPopup: false,
|
||||
detailFocused: false,
|
||||
errorInfo: {
|
||||
tel: false,
|
||||
name: false,
|
||||
postalCode: false,
|
||||
addressDetail: false
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
areaListLoaded() {
|
||||
return isObj(this.areaList) && Object.keys(this.areaList).length;
|
||||
},
|
||||
|
||||
areaText() {
|
||||
const { country, province, city, county, areaCode } = this.data;
|
||||
if (areaCode) {
|
||||
const arr = [country, province, city, county];
|
||||
if (province && province === city) {
|
||||
arr.splice(1, 1);
|
||||
}
|
||||
return arr.filter(text => text).join('/');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
addressInfo: {
|
||||
handler(val) {
|
||||
this.data = {
|
||||
...defaultData,
|
||||
...val
|
||||
};
|
||||
|
||||
this.setAreaCode(val.areaCode);
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
|
||||
areaList() {
|
||||
this.setAreaCode(this.data.areaCode);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onFocus(key) {
|
||||
this.errorInfo[key] = false;
|
||||
this.detailFocused = key === 'addressDetail';
|
||||
this.$emit('focus', key);
|
||||
},
|
||||
|
||||
onChangeDetail(val) {
|
||||
this.data.addressDetail = val;
|
||||
this.$emit('change-detail', val);
|
||||
},
|
||||
|
||||
onAreaConfirm(values) {
|
||||
this.showAreaPopup = false;
|
||||
this.assignAreaValues();
|
||||
this.$emit('change-area', values);
|
||||
},
|
||||
|
||||
assignAreaValues() {
|
||||
const { area } = this.$refs;
|
||||
if (area) {
|
||||
const detail = area.getArea();
|
||||
detail.areaCode = detail.code;
|
||||
delete detail.code;
|
||||
Object.assign(this.data, detail);
|
||||
}
|
||||
},
|
||||
|
||||
onSave() {
|
||||
const items = ['name', 'tel', 'areaCode', 'addressDetail'];
|
||||
|
||||
if (this.showPostal) {
|
||||
items.push('postalCode');
|
||||
}
|
||||
|
||||
const isValid = items.every(item => {
|
||||
const msg = this.getErrorMessage(item);
|
||||
if (msg) {
|
||||
this.errorInfo[item] = true;
|
||||
Toast(msg);
|
||||
}
|
||||
return !msg;
|
||||
});
|
||||
|
||||
if (isValid && !this.isSaving) {
|
||||
this.$emit('save', this.data);
|
||||
}
|
||||
},
|
||||
|
||||
getErrorMessage(key) {
|
||||
const value = String(this.data[key] || '').trim();
|
||||
|
||||
if (this.validator) {
|
||||
const message = this.validator(key, value);
|
||||
if (message) {
|
||||
return message;
|
||||
}
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case 'name':
|
||||
return value ? '' : t('nameEmpty');
|
||||
case 'tel':
|
||||
return this.telValidator(value) ? '' : t('telInvalid');
|
||||
case 'areaCode':
|
||||
return value ? '' : t('areaEmpty');
|
||||
case 'addressDetail':
|
||||
return value ? '' : t('addressEmpty');
|
||||
case 'postalCode':
|
||||
return value && !/^\d{6}$/.test(value) ? t('postalEmpty') : '';
|
||||
}
|
||||
},
|
||||
|
||||
onDelete() {
|
||||
Dialog.confirm({
|
||||
title: t('confirmDelete')
|
||||
})
|
||||
.then(() => {
|
||||
this.$emit('delete', this.data);
|
||||
})
|
||||
.catch(() => {
|
||||
this.$emit('cancel-delete', this.data);
|
||||
});
|
||||
},
|
||||
|
||||
// get values of area component
|
||||
getArea() {
|
||||
return this.$refs.area ? this.$refs.area.getValues() : [];
|
||||
},
|
||||
|
||||
// set area code to area component
|
||||
setAreaCode(code) {
|
||||
this.data.areaCode = code || '';
|
||||
|
||||
if (code) {
|
||||
this.$nextTick(this.assignAreaValues);
|
||||
}
|
||||
},
|
||||
|
||||
setAddressDetail(value) {
|
||||
this.data.addressDetail = value;
|
||||
},
|
||||
|
||||
onDetailBlur() {
|
||||
// await for click search event
|
||||
setTimeout(() => {
|
||||
this.detailFocused = false;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
const { data, errorInfo } = this;
|
||||
const onFocus = name => () => this.onFocus(name);
|
||||
|
||||
// hide bottom field when use search && detail get focused
|
||||
const hideBottomFields = this.searchResult.length && this.detailFocused;
|
||||
|
||||
return (
|
||||
<div class={bem()}>
|
||||
<Field
|
||||
vModel={data.name}
|
||||
clearable
|
||||
label={t('name')}
|
||||
placeholder={t('namePlaceholder')}
|
||||
error={errorInfo.name}
|
||||
onFocus={onFocus('name')}
|
||||
/>
|
||||
<Field
|
||||
vModel={data.tel}
|
||||
clearable
|
||||
type="tel"
|
||||
label={t('tel')}
|
||||
placeholder={t('telPlaceholder')}
|
||||
error={errorInfo.tel}
|
||||
onFocus={onFocus('tel')}
|
||||
/>
|
||||
<Field
|
||||
vShow={this.showArea}
|
||||
readonly
|
||||
label={t('area')}
|
||||
placeholder={t('areaPlaceholder')}
|
||||
value={this.areaText}
|
||||
onClick={() => {
|
||||
this.showAreaPopup = true;
|
||||
}}
|
||||
/>
|
||||
<Detail
|
||||
vShow={this.showDetail}
|
||||
focused={this.detailFocused}
|
||||
value={data.addressDetail}
|
||||
error={errorInfo.addressDetail}
|
||||
detailRows={this.detailRows}
|
||||
searchResult={this.searchResult}
|
||||
showSearchResult={this.showSearchResult}
|
||||
onFocus={onFocus('addressDetail')}
|
||||
onBlur={this.onDetailBlur}
|
||||
onInput={this.onChangeDetail}
|
||||
onSelect-search={event => {
|
||||
this.$emit('select-search', event);
|
||||
}}
|
||||
/>
|
||||
{this.showPostal && (
|
||||
<Field
|
||||
vShow={!hideBottomFields}
|
||||
vModel={data.postalCode}
|
||||
type="tel"
|
||||
maxlength="6"
|
||||
label={t('postal')}
|
||||
placeholder={t('postal')}
|
||||
error={errorInfo.postalCode}
|
||||
onFocus={onFocus('postalCode')}
|
||||
/>
|
||||
)}
|
||||
{this.slots()}
|
||||
{this.showSetDefault && (
|
||||
<SwitchCell
|
||||
vModel={data.isDefault}
|
||||
vShow={!hideBottomFields}
|
||||
title={t('defaultAddress')}
|
||||
onChange={event => {
|
||||
this.$emit('change-default', event);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<div vShow={!hideBottomFields} class={bem('buttons')}>
|
||||
<Button
|
||||
block
|
||||
loading={this.isSaving}
|
||||
type="danger"
|
||||
text={this.saveButtonText || t('save')}
|
||||
onClick={this.onSave}
|
||||
/>
|
||||
{this.showDelete && (
|
||||
<Button
|
||||
block
|
||||
loading={this.isDeleting}
|
||||
text={this.deleteButtonText || t('delete')}
|
||||
onClick={this.onDelete}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<Popup
|
||||
vModel={this.showAreaPopup}
|
||||
position="bottom"
|
||||
lazyRender={false}
|
||||
getContainer="body"
|
||||
>
|
||||
<Area
|
||||
ref="area"
|
||||
loading={!this.areaListLoaded}
|
||||
value={data.areaCode}
|
||||
areaList={this.areaList}
|
||||
onConfirm={this.onAreaConfirm}
|
||||
onCancel={() => {
|
||||
this.showAreaPopup = false;
|
||||
}}
|
||||
/>
|
||||
</Popup>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-address-edit {
|
||||
&__buttons {
|
||||
padding: @address-edit-buttons-padding;
|
||||
|
||||
.van-button {
|
||||
margin-bottom: @address-edit-button-margin-bottom;
|
||||
}
|
||||
}
|
||||
|
||||
&-detail {
|
||||
padding: 0;
|
||||
|
||||
&__finish {
|
||||
color: @address-edit-detail-finish-color;
|
||||
font-size: @address-edit-detail-fnish-font-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-address-edit">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>姓名</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="收货人姓名" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>电话</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="tel" placeholder="收货人手机号" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>地区</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="选择省 / 市 / 区" readonly="readonly" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-address-edit-detail">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>详细地址</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><textarea rows="1" maxlength="200" placeholder="街道门牌、楼层房间号等信息" class="van-field__control" style="height: auto;"></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>邮政编码</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="tel" maxlength="6" placeholder="邮政编码" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--center van-switch-cell">
|
||||
<div class="van-cell__title"><span>设为默认收货地址</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div role="switch" aria-checked="false" class="van-switch" style="font-size: 24px;">
|
||||
<div class="van-switch__node"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-address-edit__buttons"><button class="van-button van-button--danger van-button--normal van-button--block"><span class="van-button__text">保存</span></button><button class="van-button van-button--default van-button--normal van-button--block"><span class="van-button__text">删除</span></button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,129 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`create a AddressEdit 1`] = `
|
||||
<div class="van-address-edit">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>姓名</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="收货人姓名" value="" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>电话</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="tel" placeholder="收货人手机号" value="" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>地区</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="选择省 / 市 / 区" readonly="readonly" value="" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-address-edit-detail">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>详细地址</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><textarea rows="1" maxlength="200" placeholder="街道门牌、楼层房间号等信息" class="van-field__control"></textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-address-edit__buttons"><button class="van-button van-button--danger van-button--normal van-button--block"><span class="van-button__text">保存</span></button></div>
|
||||
<div name="van-popup-slide-bottom" class="van-popup van-popup--bottom" style="display:none;">
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<div class="van-loading van-loading--circular van-picker__loading"><span class="van-loading__spinner van-loading__spinner--circular" style="color:#1989fa;"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
|
||||
<div class="van-picker__columns" style="height:220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size:100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height:44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`create a AddressEdit with props 1`] = `
|
||||
<div class="van-address-edit">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>姓名</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="收货人姓名" value="测试" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>电话</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="tel" placeholder="收货人手机号" value="13000000000" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>地区</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="text" placeholder="选择省 / 市 / 区" readonly="readonly" value="北京市/朝阳区" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-address-edit-detail">
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>详细地址</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><textarea rows="1" maxlength="200" placeholder="街道门牌、楼层房间号等信息" class="van-field__control">详细地址</textarea></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-field">
|
||||
<div class="van-cell__title van-field__label"><span>邮政编码</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div class="van-field__body"><input type="tel" maxlength="6" placeholder="邮政编码" value="10000" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--center van-switch-cell">
|
||||
<div class="van-cell__title"><span>设为默认收货地址</span></div>
|
||||
<div class="van-cell__value">
|
||||
<div role="switch" aria-checked="true" class="van-switch van-switch--on" style="font-size:24px;">
|
||||
<div class="van-switch__node"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-address-edit__buttons"><button class="van-button van-button--danger van-button--normal van-button--block"><span class="van-button__text">保存</span></button></div>
|
||||
<div name="van-popup-slide-bottom" class="van-popup van-popup--bottom" style="display:none;">
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height:220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform:translate3d(0, 88px, 0);transition-duration:0ms;transition-property:none;line-height:44px;"></ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size:100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height:44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,242 @@
|
||||
/* eslint-disable camelcase */
|
||||
import { renderToString } from '@vue/server-test-utils';
|
||||
import AddressEdit from '..';
|
||||
import areaList from '../../area/demo/area.simple';
|
||||
import { mount, later, transitionStub } from '../../../test/utils';
|
||||
|
||||
transitionStub();
|
||||
|
||||
const addressInfo = {
|
||||
name: '测试',
|
||||
tel: '13000000000',
|
||||
province: '北京市',
|
||||
city: '北京市',
|
||||
county: '朝阳区',
|
||||
addressDetail: '详细地址',
|
||||
areaCode: '110101',
|
||||
postalCode: '10000',
|
||||
isDefault: true
|
||||
};
|
||||
|
||||
const createComponent = () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
propsData: {
|
||||
areaList,
|
||||
addressInfo,
|
||||
showPostal: true
|
||||
}
|
||||
});
|
||||
|
||||
const button = wrapper.find('.van-button');
|
||||
const field = wrapper.findAll('.van-field__control');
|
||||
const { errorInfo, data } = wrapper.vm;
|
||||
return {
|
||||
vm: wrapper.vm,
|
||||
data,
|
||||
field,
|
||||
button,
|
||||
wrapper,
|
||||
errorInfo
|
||||
};
|
||||
};
|
||||
|
||||
test('create a AddressEdit', () => {
|
||||
expect(renderToString(AddressEdit)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('create a AddressEdit with props', () => {
|
||||
const wrapper = renderToString(AddressEdit, {
|
||||
propsData: {
|
||||
areaList,
|
||||
addressInfo,
|
||||
showPostal: true,
|
||||
showSetDefault: true,
|
||||
showSearchResult: true
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('valid name', () => {
|
||||
const { data, field, button, errorInfo } = createComponent();
|
||||
|
||||
// name empty
|
||||
data.name = '';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.name).toBeTruthy();
|
||||
field.at(0).trigger('focus');
|
||||
expect(errorInfo.name).toBeFalsy();
|
||||
});
|
||||
|
||||
it('valid tel', () => {
|
||||
const { data, field, button, errorInfo } = createComponent();
|
||||
data.tel = '';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.tel).toBeTruthy();
|
||||
field.at(1).trigger('focus');
|
||||
expect(errorInfo.tel).toBeFalsy();
|
||||
});
|
||||
|
||||
it('valid areaCode', () => {
|
||||
const { data, button, errorInfo } = createComponent();
|
||||
// areaCode empty
|
||||
data.areaCode = '';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.areaCode).toBeTruthy();
|
||||
|
||||
// areaCode invalid
|
||||
data.areaCode = '-1';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.areaCode).toBeTruthy();
|
||||
});
|
||||
|
||||
it('valid addressDetail', () => {
|
||||
const { data, field, button, errorInfo } = createComponent();
|
||||
data.addressDetail = '';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.addressDetail).toBeTruthy();
|
||||
field.at(3).trigger('focus');
|
||||
expect(errorInfo.addressDetail).toBeFalsy();
|
||||
});
|
||||
|
||||
test('valid postal code', () => {
|
||||
const { vm, data, field, button, errorInfo } = createComponent();
|
||||
|
||||
// postalCode invalid
|
||||
data.postalCode = '123';
|
||||
button.trigger('click');
|
||||
expect(errorInfo.postalCode).toBeTruthy();
|
||||
field.at(4).trigger('focus');
|
||||
expect(errorInfo.postalCode).toBeFalsy();
|
||||
|
||||
// valid result
|
||||
data.postalCode = '123456';
|
||||
button.trigger('click');
|
||||
|
||||
// not show postalCode
|
||||
data.postalCode = '156';
|
||||
vm.showPostal = false;
|
||||
button.trigger('click');
|
||||
expect(errorInfo.postalCode).toBeFalsy();
|
||||
});
|
||||
|
||||
test('on change detail', () => {
|
||||
const wrapper = mount(AddressEdit);
|
||||
const field = wrapper.findAll('.van-field__control').at(3);
|
||||
|
||||
field.element.value = '123';
|
||||
field.trigger('input');
|
||||
expect(wrapper.emitted('change-detail')[0][0]).toEqual('123');
|
||||
});
|
||||
|
||||
test('watch address info', () => {
|
||||
const wrapper = mount(AddressEdit);
|
||||
wrapper.setProps({ addressInfo: { name: '123' } });
|
||||
expect(wrapper.vm.data.name).toEqual('123');
|
||||
});
|
||||
|
||||
test('set/get area code', async () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
propsData: { areaList }
|
||||
});
|
||||
|
||||
expect(wrapper.vm.getArea()).toEqual([
|
||||
{ code: '110000', name: '北京市' },
|
||||
{ code: '110100', name: '北京市' },
|
||||
{ code: '110101', name: '东城区' }
|
||||
]);
|
||||
|
||||
wrapper.vm.setAreaCode('110102');
|
||||
|
||||
await later(50);
|
||||
expect(wrapper.vm.data.areaCode).toEqual('110102');
|
||||
expect(wrapper.vm.getArea()).toEqual([
|
||||
{ code: '110000', name: '北京市' },
|
||||
{ code: '110100', name: '北京市' },
|
||||
{ code: '110102', name: '西城区' }
|
||||
]);
|
||||
|
||||
wrapper.vm.$refs = [];
|
||||
wrapper.vm.setAreaCode('110102');
|
||||
expect(wrapper.vm.getArea()).toEqual([]);
|
||||
});
|
||||
|
||||
test('watch area code', async () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
propsData: {
|
||||
areaList: {},
|
||||
addressInfo: {
|
||||
areaCode: '110101'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.vm.data.city).toEqual('');
|
||||
wrapper.vm.areaList = areaList;
|
||||
|
||||
await later(50);
|
||||
expect(wrapper.vm.data.city).toEqual('北京市');
|
||||
});
|
||||
|
||||
test('show search result', async () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
propsData: {
|
||||
showSearchResult: true,
|
||||
searchResult: [
|
||||
{ name: 'name1', address: 'address1' },
|
||||
{ name: 'name2' },
|
||||
{ address: 'address2' }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const field = wrapper.findAll('.van-field__control').at(3);
|
||||
const input = field.element;
|
||||
field.trigger('focus');
|
||||
|
||||
const items = wrapper.findAll('.van-icon-location-o');
|
||||
items.at(0).element.parentNode.click();
|
||||
expect(input.value).toEqual('address1 name1');
|
||||
items.at(1).element.parentNode.click();
|
||||
expect(input.value).toEqual('name2');
|
||||
items.at(2).element.parentNode.click();
|
||||
expect(input.value).toEqual('address2');
|
||||
|
||||
field.trigger('blur');
|
||||
await later(150);
|
||||
expect(wrapper.vm.detailFocused).toBeFalsy();
|
||||
});
|
||||
|
||||
test('delete address', async () => {
|
||||
const wrapper = mount(AddressEdit, {
|
||||
attachToDocument: true,
|
||||
propsData: {
|
||||
showDelete: true
|
||||
}
|
||||
});
|
||||
|
||||
const deleteButton = wrapper.findAll('.van-button').at(1);
|
||||
deleteButton.trigger('click');
|
||||
|
||||
await later();
|
||||
document.querySelector('.van-dialog__cancel').click();
|
||||
deleteButton.trigger('click');
|
||||
document.querySelector('.van-dialog__confirm').click();
|
||||
|
||||
await later();
|
||||
expect(wrapper.emitted('delete')).toBeTruthy();
|
||||
expect(wrapper.emitted('cancel-delete')).toBeTruthy();
|
||||
});
|
||||
|
||||
test('setAddressDetail method', () => {
|
||||
const { vm, data } = createComponent();
|
||||
vm.setAddressDetail('test');
|
||||
expect(data.addressDetail).toEqual('test');
|
||||
});
|
||||
|
||||
test('select area', () => {
|
||||
const { wrapper, data } = createComponent();
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
expect(data.areaCode).toEqual('110101');
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
# AddressEdit 地址编辑
|
||||
|
||||
### 引入
|
||||
``` javascript
|
||||
import { AddressEdit } from 'vant';
|
||||
|
||||
Vue.use(AddressEdit);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
```html
|
||||
<van-address-edit
|
||||
:area-list="areaList"
|
||||
show-postal
|
||||
show-delete
|
||||
show-set-default
|
||||
show-search-result
|
||||
:search-result="searchResult"
|
||||
@save="onSave"
|
||||
@delete="onDelete"
|
||||
@change-detail="onChangeDetail"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
areaList,
|
||||
searchResult: []
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSave() {
|
||||
Toast('save');
|
||||
},
|
||||
onDelete() {
|
||||
Toast('delete');
|
||||
},
|
||||
onChangeDetail(val) {
|
||||
if (val) {
|
||||
this.searchResult = [{
|
||||
name: '黄龙万科中心',
|
||||
address: '杭州市西湖区'
|
||||
}];
|
||||
} else {
|
||||
this.searchResult = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| area-list | 地区列表 | `Object` | - | - |
|
||||
| address-info | 收货人信息初始值 | `Object` | `{}` | - |
|
||||
| search-result | 详细地址搜索结果 | `Array` | `[]` | - |
|
||||
| show-postal | 是否显示邮政编码 | `Boolean` | `false` | - |
|
||||
| show-delete | 是否显示删除按钮 | `Boolean` | `false` | 1.0.0 |
|
||||
| show-set-default | 是否显示默认地址栏 | `Boolean` | `false` | - |
|
||||
| show-search-result | 是否显示搜索结果 | `Boolean` | `false` | - |
|
||||
| save-button-text | 保存按钮文字 | `String` | `保存` | - |
|
||||
| delete-button-text | 删除按钮文字 | `String` | `删除` | - |
|
||||
| is-saving | 是否显示保存按钮加载动画 | `Boolean` | `false` | - |
|
||||
| is-deleting | 是否显示删除按钮加载动画 | `Boolean` | `false` | - |
|
||||
| tel-validator | 手机号格式校验函数 | `string => boolean` | - | - |
|
||||
| validator | 自定义校验函数 | `(key, value) => string` | - | 1.3.9 |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|------|------|------|
|
||||
| save | 点击保存按钮时触发 | content:表单内容 |
|
||||
| focus | 输入框聚焦时触发 | key: 聚焦的输入框对应的 key |
|
||||
| delete | 确认删除地址时触发 | content:表单内容 |
|
||||
| cancel-delete | 取消删除地址时触发 | content:表单内容 |
|
||||
| select-search | 选中搜索结果时触发 | value: 搜索结果 |
|
||||
| change-area | 修改收件地区时触发 | values: 地区信息 |
|
||||
| change-detail | 修改详细地址时触发 | value: 详细地址内容 |
|
||||
| change-default | 切换是否使用默认地址时触发 | value: 是否选中 |
|
||||
|
||||
### Slots
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| default | 在邮政编码下方插入内容 |
|
||||
|
||||
### 方法
|
||||
|
||||
通过 ref 可以获取到 address-edit 实例并调用实例方法
|
||||
|
||||
| 方法名 | 参数 | 返回值 | 介绍 |
|
||||
|------|------|------|------|
|
||||
| setAddressDetail | addressDetail: string | - | 设置详细地址 |
|
||||
|
||||
### addressInfo 数据格式
|
||||
|
||||
注意:addressInfo 仅作为初始值传入,表单最终内容可以在 save 事件中获取
|
||||
|
||||
| key | 说明 | 类型 |
|
||||
|------|------|------|
|
||||
| id | 每条地址的唯一标识 | `String | Number` |
|
||||
| name | 收货人姓名 | `String` |
|
||||
| tel | 收货人手机号 | `String` |
|
||||
| province | 省份 | `String` |
|
||||
| city | 城市 | `String` |
|
||||
| county | 区县 | `String` |
|
||||
| addressDetail | 详细地址 | `String` |
|
||||
| areaCode | 地区编码,通过`省市区选择`获取(必填) | `String` |
|
||||
| postalCode | 邮政编码 | `String` |
|
||||
| isDefault | 是否为默认地址 | `Boolean` |
|
||||
|
||||
### searchResult 数据格式
|
||||
|
||||
| key | 说明 | 类型 |
|
||||
|------|------|------|
|
||||
| name | 地名 | `String` |
|
||||
| address | 详细地址 | `String` |
|
||||
|
||||
### 省市县列表数据格式
|
||||
|
||||
请参考 [Area](#/zh-CN/area) 组件。
|
||||
@@ -0,0 +1,94 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { RED } from '../utils/color';
|
||||
import { emit, inherit } from '../utils/functional';
|
||||
import Icon from '../icon';
|
||||
import Cell from '../cell';
|
||||
import Radio from '../radio';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
|
||||
export type AddressItemData = {
|
||||
id: string | number;
|
||||
tel: string | number;
|
||||
name: string;
|
||||
address: string;
|
||||
};
|
||||
|
||||
export type AddressItemProps = {
|
||||
data: AddressItemData;
|
||||
disabled?: boolean;
|
||||
switchable?: boolean;
|
||||
};
|
||||
|
||||
export type AddressItemEvents = {
|
||||
onEdit(): void;
|
||||
onSelect(): void;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('address-item');
|
||||
|
||||
function AddressItem(
|
||||
h: CreateElement,
|
||||
props: AddressItemProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<AddressItemProps>
|
||||
) {
|
||||
const { disabled, switchable } = props;
|
||||
|
||||
function onSelect() {
|
||||
if (switchable) {
|
||||
emit(ctx, 'select');
|
||||
}
|
||||
}
|
||||
|
||||
const renderRightIcon = () => (
|
||||
<Icon
|
||||
name="edit"
|
||||
class={bem('edit')}
|
||||
onClick={(event: Event) => {
|
||||
event.stopPropagation();
|
||||
emit(ctx, 'edit');
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
const renderContent = () => {
|
||||
const { data } = props;
|
||||
const Info = [
|
||||
<div class={bem('name')}>{`${data.name},${data.tel}`}</div>,
|
||||
<div class={bem('address')}>{data.address}</div>
|
||||
];
|
||||
|
||||
return switchable && !disabled ? (
|
||||
<Radio name={data.id} iconSize={16} checkedColor={RED}>
|
||||
{Info}
|
||||
</Radio>
|
||||
) : (
|
||||
Info
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Cell
|
||||
class={bem({ disabled })}
|
||||
valueClass={bem('value')}
|
||||
clickable={switchable && !disabled}
|
||||
scopedSlots={{
|
||||
default: renderContent,
|
||||
'right-icon': renderRightIcon
|
||||
}}
|
||||
onClick={onSelect}
|
||||
{...inherit(ctx)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
AddressItem.props = {
|
||||
data: Object,
|
||||
disabled: Boolean,
|
||||
switchable: Boolean
|
||||
};
|
||||
|
||||
export default createComponent<AddressItemProps, AddressItemEvents>(AddressItem);
|
||||
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-address-list
|
||||
v-model="chosenAddressId"
|
||||
:list="$t('list')"
|
||||
:disabled-list="$t('disabledList')"
|
||||
:disabled-text="$t('disabledText')"
|
||||
@add="onAdd"
|
||||
@edit="onEdit"
|
||||
/>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
list: [
|
||||
{
|
||||
id: '1',
|
||||
name: '张三',
|
||||
tel: '13000000000',
|
||||
address: '浙江省杭州市西湖区文三路 138 号东方通信大厦 7 楼 501 室'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '李四',
|
||||
tel: '1310000000',
|
||||
address: '浙江省杭州市拱墅区莫干山路 50 号'
|
||||
}
|
||||
],
|
||||
disabledList: [
|
||||
{
|
||||
id: '3',
|
||||
name: '王五',
|
||||
tel: '1320000000',
|
||||
address: '浙江省杭州市滨江区江南大道 15 号'
|
||||
}
|
||||
],
|
||||
add: '新增地址',
|
||||
edit: '编辑地址',
|
||||
disabledText: '以下地址超出配送范围'
|
||||
},
|
||||
'en-US': {
|
||||
list: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'John Snow',
|
||||
tel: '13000000000',
|
||||
address: 'Somewhere'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Ned Stark',
|
||||
tel: '1310000000',
|
||||
address: 'Somewhere'
|
||||
}
|
||||
],
|
||||
disabledList: [
|
||||
{
|
||||
id: '3',
|
||||
name: 'Tywin',
|
||||
tel: '1320000000',
|
||||
address: 'Somewhere'
|
||||
}
|
||||
],
|
||||
add: 'Add',
|
||||
edit: 'Edit',
|
||||
disabledText: 'The following address is out of range'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
chosenAddressId: '1'
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onAdd() {
|
||||
this.$toast(this.$t('add'));
|
||||
},
|
||||
|
||||
onEdit(item, index) {
|
||||
this.$toast(`${this.$t('edit')}:${index}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,103 @@
|
||||
# AddressList
|
||||
|
||||
### Install
|
||||
``` javascript
|
||||
import { AddressList } from 'vant';
|
||||
|
||||
Vue.use(AddressList);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-address-list
|
||||
v-model="chosenAddressId"
|
||||
:list="list"
|
||||
:disabled-list="disabledList"
|
||||
disabled-text="The following address is out of range"
|
||||
@add="onAdd"
|
||||
@edit="onEdit"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
chosenAddressId: '1',
|
||||
list: [
|
||||
{
|
||||
id: '1',
|
||||
name: 'John Snow',
|
||||
tel: '13000000000',
|
||||
address: 'Somewhere'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Ned Stark',
|
||||
tel: '1310000000',
|
||||
address: 'Somewhere'
|
||||
}
|
||||
],
|
||||
disabledList: [
|
||||
{
|
||||
id: '3',
|
||||
name: 'Tywin',
|
||||
tel: '1320000000',
|
||||
address: 'Somewhere'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onAdd() {
|
||||
Toast('Add');
|
||||
},
|
||||
onEdit(item, index) {
|
||||
Toast('Edit:' + index);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| v-model | Id of chosen address | String | - |
|
||||
| list | Address list | Array | `[]` |
|
||||
| disabled-list | Disabled address list | `Array` | `[]` |
|
||||
| disabled-text | Disabled text | `String` | - |
|
||||
| switchable | Whether to allow switch address | `Boolean` | `true` |
|
||||
| add-button-text | Add button text | String | `Add new address` |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
|------|------|------|
|
||||
| add | Triggered when click add button | - |
|
||||
| edit | Triggered when edit address | item: address object,index |
|
||||
| select | Triggered when select address | item: address object,index |
|
||||
| edit-disabled | Triggered when edit disabled address | item: address object,index |
|
||||
| select-disabled | Triggered when select disabled address | item: address object,index |
|
||||
|
||||
### Address Data Structure
|
||||
|
||||
| key | Description | Type |
|
||||
|------|------|------|
|
||||
| id | Id | `String | Number` |
|
||||
| name | Name | `String` |
|
||||
| tel | Phone | `String | Number` |
|
||||
| address | Address | `String` |
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description |
|
||||
|------|------|
|
||||
| default | Custom content after list |
|
||||
| top | Custom content before list |
|
||||
@@ -0,0 +1,57 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-address-list {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
padding-bottom: 100px;
|
||||
|
||||
&__add {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: @address-list-add-button-z-index;
|
||||
}
|
||||
|
||||
&__disabled-text {
|
||||
padding: 0 15px;
|
||||
color: @address-list-disabled-text-color;
|
||||
font-size: @address-list-disabled-text-font-size;
|
||||
line-height: @address-list-disabled-text-line-height;
|
||||
}
|
||||
}
|
||||
|
||||
.van-address-item {
|
||||
padding: @address-list-item-padding;
|
||||
|
||||
&__value {
|
||||
padding-right: 34px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
margin-bottom: 5px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__address {
|
||||
color: @address-list-item-text-color;
|
||||
font-size: @address-list-item-font-size;
|
||||
line-height: @address-list-item-line-height;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
.van-address-item__name,
|
||||
.van-address-item__address {
|
||||
color: @address-list-item-disabled-text-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__edit {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 15px;
|
||||
font-size: @address-list-edit-icon-size;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { emit, inherit } from '../utils/functional';
|
||||
import Button from '../button';
|
||||
import RadioGroup from '../radio-group';
|
||||
import AddressItem, { AddressItemData } from './Item';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { ScopedSlot, DefaultSlots } from '../utils/types';
|
||||
|
||||
export type AddressListProps = {
|
||||
value?: string | number;
|
||||
switchable: boolean;
|
||||
disabledText?: string;
|
||||
addButtonText?: string;
|
||||
list: AddressItemData[];
|
||||
disabledList: AddressItemData[];
|
||||
};
|
||||
|
||||
export type AddressListSlots = DefaultSlots & {
|
||||
top?: ScopedSlot;
|
||||
};
|
||||
|
||||
const [createComponent, bem, t] = createNamespace('address-list');
|
||||
|
||||
function AddressList(
|
||||
h: CreateElement,
|
||||
props: AddressListProps,
|
||||
slots: AddressListSlots,
|
||||
ctx: RenderContext<AddressListProps>
|
||||
) {
|
||||
const getList = (list: AddressItemData[], disabled?: boolean) =>
|
||||
list.map((item, index) => (
|
||||
<AddressItem
|
||||
data={item}
|
||||
key={item.id}
|
||||
disabled={disabled}
|
||||
switchable={props.switchable}
|
||||
onSelect={() => {
|
||||
emit(ctx, disabled ? 'select-disabled' : 'select', item, index);
|
||||
}}
|
||||
onEdit={() => {
|
||||
emit(ctx, disabled ? 'edit-disabled' : 'edit', item, index);
|
||||
}}
|
||||
/>
|
||||
));
|
||||
|
||||
const List = getList(props.list);
|
||||
const DisabledList = getList(props.disabledList, true);
|
||||
|
||||
return (
|
||||
<div class={bem()} {...inherit(ctx)}>
|
||||
{slots.top && slots.top()}
|
||||
<RadioGroup
|
||||
value={props.value}
|
||||
onInput={(event: Event) => {
|
||||
emit(ctx, 'input', event);
|
||||
}}
|
||||
>
|
||||
{List}
|
||||
</RadioGroup>
|
||||
{props.disabledText && (
|
||||
<div class={bem('disabled-text')}>{props.disabledText}</div>
|
||||
)}
|
||||
{DisabledList}
|
||||
{slots.default && slots.default()}
|
||||
<Button
|
||||
square
|
||||
size="large"
|
||||
type="danger"
|
||||
class={bem('add')}
|
||||
text={props.addButtonText || t('add')}
|
||||
onClick={() => {
|
||||
emit(ctx, 'add');
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
AddressList.props = {
|
||||
list: Array,
|
||||
disabledList: Array,
|
||||
disabledText: String,
|
||||
addButtonText: String,
|
||||
value: [String, Number],
|
||||
switchable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
};
|
||||
|
||||
export default createComponent<AddressListProps>(AddressList);
|
||||
@@ -0,0 +1,38 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-address-list">
|
||||
<div role="radiogroup" class="van-radio-group">
|
||||
<div class="van-cell van-cell--clickable van-address-item">
|
||||
<div class="van-cell__value van-cell__value--alone van-address-item__value">
|
||||
<div role="radio" tabindex="0" aria-checked="true" class="van-radio">
|
||||
<div class="van-radio__icon van-radio__icon--round van-radio__icon--checked" style="font-size: 16px;"><i class="van-icon van-icon-success" style="border-color: #f44; background-color: rgb(255, 68, 68);">
|
||||
<!----></i></div><span class="van-radio__label"><div class="van-address-item__name">张三,13000000000</div><div class="van-address-item__address">浙江省杭州市西湖区文三路 138 号东方通信大厦 7 楼 501 室</div></span>
|
||||
</div>
|
||||
</div><i class="van-icon van-icon-edit van-address-item__edit">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div class="van-cell van-cell--clickable van-address-item">
|
||||
<div class="van-cell__value van-cell__value--alone van-address-item__value">
|
||||
<div role="radio" tabindex="-1" aria-checked="false" class="van-radio">
|
||||
<div class="van-radio__icon van-radio__icon--round" style="font-size: 16px;"><i class="van-icon van-icon-success">
|
||||
<!----></i></div><span class="van-radio__label"><div class="van-address-item__name">李四,1310000000</div><div class="van-address-item__address">浙江省杭州市拱墅区莫干山路 50 号</div></span>
|
||||
</div>
|
||||
</div><i class="van-icon van-icon-edit van-address-item__edit">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-address-list__disabled-text">以下地址超出配送范围</div>
|
||||
<div class="van-cell van-address-item van-address-item--disabled">
|
||||
<div class="van-cell__value van-cell__value--alone van-address-item__value">
|
||||
<div class="van-address-item__name">王五,1320000000</div>
|
||||
<div class="van-address-item__address">浙江省杭州市滨江区江南大道 15 号</div>
|
||||
</div><i class="van-icon van-icon-edit van-address-item__edit">
|
||||
<!----></i>
|
||||
</div><button class="van-button van-button--danger van-button--large van-button--square van-address-list__add"><span class="van-button__text">新增地址</span></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,22 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`unswitchable 1`] = `
|
||||
<div class="van-address-list">
|
||||
<div role="radiogroup" class="van-radio-group">
|
||||
<div class="van-cell van-address-item">
|
||||
<div class="van-cell__value van-cell__value--alone van-address-item__value">
|
||||
<div class="van-address-item__name">张三,13000000000</div>
|
||||
<div class="van-address-item__address">浙江省杭州市西湖区文三路 138 号东方通信大厦 7 楼 501 室</div>
|
||||
</div><i class="van-icon van-icon-edit van-address-item__edit">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div class="van-cell van-address-item">
|
||||
<div class="van-cell__value van-cell__value--alone van-address-item__value">
|
||||
<div class="van-address-item__name">李四,1310000000</div>
|
||||
<div class="van-address-item__address">浙江省杭州市拱墅区莫干山路 50 号</div>
|
||||
</div><i class="van-icon van-icon-edit van-address-item__edit">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div><button class="van-button van-button--danger van-button--large van-button--square van-address-list__add"><span class="van-button__text">新增地址</span></button>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,46 @@
|
||||
import { mount } from '../../../test/utils';
|
||||
import AddressList from '..';
|
||||
|
||||
const list = [
|
||||
{
|
||||
id: '1',
|
||||
name: '张三',
|
||||
tel: '13000000000',
|
||||
address: '浙江省杭州市西湖区文三路 138 号东方通信大厦 7 楼 501 室'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '李四',
|
||||
tel: '1310000000',
|
||||
address: '浙江省杭州市拱墅区莫干山路 50 号'
|
||||
}
|
||||
];
|
||||
|
||||
test('unswitchable', () => {
|
||||
const wrapper = mount(AddressList, {
|
||||
propsData: {
|
||||
list,
|
||||
switchable: false
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('select event', () => {
|
||||
const onSelect = jest.fn();
|
||||
const wrapper = mount(AddressList, {
|
||||
propsData: {
|
||||
list
|
||||
},
|
||||
context: {
|
||||
on: {
|
||||
select: onSelect
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.find('.van-radio__icon').trigger('click');
|
||||
|
||||
expect(onSelect).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -0,0 +1,104 @@
|
||||
# AddressList 地址列表
|
||||
|
||||
### 引入
|
||||
``` javascript
|
||||
import { AddressList } from 'vant';
|
||||
|
||||
Vue.use(AddressList);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
```html
|
||||
<van-address-list
|
||||
v-model="chosenAddressId"
|
||||
:list="list"
|
||||
:disabled-list="disabledList"
|
||||
disabled-text="以下地址超出配送范围"
|
||||
@add="onAdd"
|
||||
@edit="onEdit"
|
||||
/>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
chosenAddressId: '1',
|
||||
list: [
|
||||
{
|
||||
id: '1',
|
||||
name: '张三',
|
||||
tel: '13000000000',
|
||||
address: '浙江省杭州市西湖区文三路 138 号东方通信大厦 7 楼 501 室'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: '李四',
|
||||
tel: '1310000000',
|
||||
address: '浙江省杭州市拱墅区莫干山路 50 号'
|
||||
}
|
||||
],
|
||||
disabledList: [
|
||||
{
|
||||
id: '3',
|
||||
name: '王五',
|
||||
tel: '1320000000',
|
||||
address: '浙江省杭州市滨江区江南大道 15 号'
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onAdd() {
|
||||
Toast('新增地址');
|
||||
},
|
||||
|
||||
onEdit(item, index) {
|
||||
Toast('编辑地址:' + index);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| v-model | 当前选中地址的 id | `String` | - | - |
|
||||
| list | 地址列表 | `Array` | `[]` | - |
|
||||
| add-button-text | 底部按钮文字 | `String` | `新增地址` | - |
|
||||
| disabled-list | 不可配送地址列表 | `Array` | `[]` | 1.3.0 |
|
||||
| disabled-text | 不可配送提示文案 | `String` | - | 1.3.0 |
|
||||
| switchable | 是否允许切换地址 | `Boolean` | `true` | 1.3.8 |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 | 版本 |
|
||||
|------|------|------|------|
|
||||
| add | 点击新增按钮时触发 | - | - |
|
||||
| edit | 点击编辑按钮时触发 | item: 地址对象,index: 索引 | - |
|
||||
| select | 切换选中的地址时触发 | item: 地址对象,index: 索引 | - |
|
||||
| edit-disabled | 编辑不可配送的地址时触发 | item: 地址对象,index: 索引 | 1.3.0 |
|
||||
| select-disabled | 选中不可配送的地址时触发 | item: 地址对象,index: 索引 | 1.3.0 |
|
||||
|
||||
### 地址列表字段说明
|
||||
|
||||
| key | 说明 | 类型 |
|
||||
|------|------|------|
|
||||
| id | 每条地址的唯一标识 | `String | Number` |
|
||||
| name | 收货人姓名 | `String` |
|
||||
| tel | 收货人手机号 | `String | Number` |
|
||||
| address | 收货地址 | `String` |
|
||||
|
||||
### Slots
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| default | 在列表下方插入内容 |
|
||||
| top | 在顶部插入内容 |
|
||||
@@ -0,0 +1,71 @@
|
||||
export default {
|
||||
province_list: {
|
||||
110000: 'Beijing',
|
||||
330000: 'Zhejiang',
|
||||
810000: 'Hong Kong'
|
||||
},
|
||||
city_list: {
|
||||
110100: 'Beijing City',
|
||||
330100: 'Hangzhou',
|
||||
330200: 'Ningbo',
|
||||
330300: 'Wenzhou',
|
||||
330400: 'Jiaxin',
|
||||
331100: 'Lishui',
|
||||
810100: 'Hong Kong Island',
|
||||
810200: 'Kowloon',
|
||||
810300: 'New Territories'
|
||||
},
|
||||
county_list: {
|
||||
110101: 'Dongcheng',
|
||||
110102: 'Xicheng',
|
||||
110105: 'Chaoyang',
|
||||
110106: 'Fengtai',
|
||||
110108: 'Haidian',
|
||||
110111: 'Fangshan',
|
||||
110112: 'Tongzhou',
|
||||
110113: 'Shunyi',
|
||||
110114: 'Changping',
|
||||
110115: 'Daxing',
|
||||
330105: 'Gongshu',
|
||||
330106: 'Xihu',
|
||||
330108: 'Binjiang',
|
||||
330109: 'Xiaoshan',
|
||||
330110: 'Yuhang',
|
||||
330111: 'Fuyang',
|
||||
330127: 'Chunan',
|
||||
330182: 'Jiande',
|
||||
330185: 'Linan',
|
||||
330206: 'Beilun',
|
||||
330211: 'Zhenhai',
|
||||
330225: 'Xiangshan',
|
||||
330226: 'Ninghai',
|
||||
330281: 'Yuyao',
|
||||
330282: 'Cixi',
|
||||
330327: 'Cangnan',
|
||||
330328: 'Wencheng',
|
||||
330329: 'Shuntai',
|
||||
330381: 'Ruian',
|
||||
330382: 'Yueqing',
|
||||
330402: 'Nanhu',
|
||||
330421: 'Jiashan',
|
||||
330424: 'Haiyan',
|
||||
330481: 'Haining',
|
||||
330482: 'Pinghu',
|
||||
330483: 'Tongxiang',
|
||||
331102: 'Liandu District',
|
||||
331121: 'Qingtian County',
|
||||
331125: 'Yunhe County',
|
||||
331181: 'Longquan County',
|
||||
810101: 'Central',
|
||||
810102: 'Wan Chai',
|
||||
810202: 'Mong Kok',
|
||||
810203: 'Sham Shui Po',
|
||||
810204: 'Chuk Un',
|
||||
810205: 'Kwun Tong',
|
||||
810303: 'Sha Tin',
|
||||
810305: 'Yuen Long',
|
||||
810306: 'Tuen Mun',
|
||||
810307: 'Tsuen Wan',
|
||||
810309: 'Lantau Island'
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
province_list: {
|
||||
110000: '北京市',
|
||||
120000: '天津市'
|
||||
},
|
||||
city_list: {
|
||||
110100: '北京市',
|
||||
110200: '县',
|
||||
120100: '天津市',
|
||||
120200: '县'
|
||||
},
|
||||
county_list: {
|
||||
110101: '东城区',
|
||||
110102: '西城区',
|
||||
110228: '密云县',
|
||||
110229: '延庆县',
|
||||
120101: '和平区',
|
||||
120102: '河东区',
|
||||
120225: '蓟县'
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-area :area-list="$t('areaList')" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title2')">
|
||||
<van-area
|
||||
:area-list="$t('areaList')"
|
||||
:value="value"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title3')">
|
||||
<van-area
|
||||
:area-list="$t('areaList')"
|
||||
:columns-num="2"
|
||||
:title="$t('title')"
|
||||
/>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AreaList from './area';
|
||||
import AreaListEn from './area-en';
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
title2: '选中省市区',
|
||||
title3: '配置显示列',
|
||||
areaList: AreaList
|
||||
},
|
||||
'en-US': {
|
||||
title2: 'Initial Value',
|
||||
title3: 'Columns Number',
|
||||
areaList: AreaListEn
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
value: '330302'
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,121 @@
|
||||
# Area
|
||||
|
||||
### Intro
|
||||
|
||||
The Picker component is usually used with [Popup](#/en-US/popup) Component.
|
||||
|
||||
### Install
|
||||
|
||||
``` javascript
|
||||
import { Area } from 'vant';
|
||||
|
||||
Vue.use(Area);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
To initailize `Area` component, `area-list` property is required. Data structure will be introduced later.
|
||||
|
||||
```html
|
||||
<van-area :area-list="areaList" />
|
||||
```
|
||||
|
||||
### Initial Value
|
||||
|
||||
To have a selected value,simply pass the `code` of target area to `value` property.
|
||||
|
||||
```html
|
||||
<van-area :area-list="areaList" value="110101" />
|
||||
```
|
||||
|
||||
### Columns Number
|
||||
|
||||
`columns-num` property is used to config number of columns to be displayed. This component has 3 columns corresponding to a 3 level picker by default.
|
||||
Set `columns-num` with 2, you'll have a 2 level picker.
|
||||
|
||||
```html
|
||||
<van-area :area-list="areaList" :columns-num="2" title="Title" />
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| value | the `code` of selected area | `String` | - |
|
||||
| title | Toolbar title | `String` | - |
|
||||
| area-list | Area data | `Object` | - |
|
||||
| columns-num | level of picker | `String | Number` | `3` |
|
||||
| item-height | Option height | `Number` | `44` |
|
||||
| loading | Whether to show loading prompt | `Boolean` | `false` |
|
||||
| visible-item-count | Count of visible columns | `Number` | `5` |
|
||||
| confirm-button-text | Text of confirm button | `String` | `Confirm` |
|
||||
| cancel-button-text | Text of cancel button | `String` | `Cancel` |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
|------|------|------|
|
||||
| confirm | triggers when clicking the confirm button | an array |
|
||||
| cancel | triggers when clicking the cancel button | - |
|
||||
| change | Triggered when current option changed | Picker instance, current values,column index |
|
||||
|
||||
### Methods
|
||||
|
||||
Use ref to get area instance and call instance methods
|
||||
|
||||
| Name | Attribute | Return value | Description |
|
||||
|------|------|------|------|
|
||||
| reset | code: string | - | Reset all options by code |
|
||||
|
||||
### areaList Data Structure
|
||||
|
||||
An object contains three properties: `province_list`, `city_list` and `county_list`.
|
||||
Each property is a simple key-value object, key is a 6-bit code of the area of which first two bits stand for the province or state, middle two bits are used as city code and the last two are district code, value is the name of the area. If the code stands for an area that has sub-areas, lower bits of it will be filled with 0.
|
||||
|
||||
Example of `AreaList`
|
||||
|
||||
```javascript
|
||||
{
|
||||
province_list: {
|
||||
110000: 'Beijing',
|
||||
330000: 'Zhejiang Province'
|
||||
},
|
||||
city_list: {
|
||||
110100: 'Beijing City',
|
||||
330100: 'Hangzhou',
|
||||
},
|
||||
county_list: {
|
||||
110101: 'Dongcheng District',
|
||||
110102: 'Xicheng District',
|
||||
110105: 'Chaoyang District',
|
||||
110106: 'Fengtai District'
|
||||
330105: 'Gongshu District',
|
||||
330106: 'Xihu District',
|
||||
// ....
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
All code of China: [Area.json](https://github.com/youzan/vant/blob/dev/packages/area/demo/area-en.js)
|
||||
|
||||
### argument of callback function confirm
|
||||
|
||||
An array contains selected area objects.
|
||||
|
||||
`code` - code of selected area, `name` - name of selected area
|
||||
```javascript
|
||||
[{
|
||||
code: '330000',
|
||||
name: 'Zhejiang Province'
|
||||
}, {
|
||||
code: '330100',
|
||||
name: 'Hangzhou'
|
||||
},{
|
||||
code: '330105',
|
||||
name: 'Xihu District'
|
||||
}]
|
||||
```
|
||||
@@ -0,0 +1,209 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import Picker from '../picker';
|
||||
import { pickerProps } from '../picker/shared';
|
||||
|
||||
const [createComponent, bem] = createNamespace('area');
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
...pickerProps,
|
||||
value: String,
|
||||
areaList: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
columnsNum: {
|
||||
type: [String, Number],
|
||||
default: 3
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
code: this.value,
|
||||
columns: [{ values: [] }, { values: [] }, { values: [] }]
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
province() {
|
||||
return this.areaList.province_list || {};
|
||||
},
|
||||
|
||||
city() {
|
||||
return this.areaList.city_list || {};
|
||||
},
|
||||
|
||||
county() {
|
||||
return this.areaList.county_list || {};
|
||||
},
|
||||
|
||||
displayColumns() {
|
||||
return this.columns.slice(0, +this.columnsNum);
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value() {
|
||||
this.code = this.value;
|
||||
this.setValues();
|
||||
},
|
||||
|
||||
areaList: {
|
||||
deep: true,
|
||||
handler() {
|
||||
this.setValues();
|
||||
}
|
||||
},
|
||||
|
||||
columnsNum() {
|
||||
this.$nextTick(() => {
|
||||
this.setValues();
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.setValues();
|
||||
},
|
||||
|
||||
methods: {
|
||||
// get list by code
|
||||
getList(type, code) {
|
||||
let result = [];
|
||||
if (type !== 'province' && !code) {
|
||||
return result;
|
||||
}
|
||||
|
||||
const list = this[type];
|
||||
result = Object.keys(list).map(listCode => ({
|
||||
code: listCode,
|
||||
name: list[listCode]
|
||||
}));
|
||||
|
||||
if (code) {
|
||||
// oversea code
|
||||
if (code[0] === '9' && type === 'city') {
|
||||
code = '9';
|
||||
}
|
||||
|
||||
result = result.filter(item => item.code.indexOf(code) === 0);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
// get index by code
|
||||
getIndex(type, code) {
|
||||
let compareNum = type === 'province' ? 2 : type === 'city' ? 4 : 6;
|
||||
const list = this.getList(type, code.slice(0, compareNum - 2));
|
||||
|
||||
// oversea code
|
||||
if (code[0] === '9' && type === 'province') {
|
||||
compareNum = 1;
|
||||
}
|
||||
|
||||
code = code.slice(0, compareNum);
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
if (list[i].code.slice(0, compareNum) === code) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
|
||||
onChange(picker, values, index) {
|
||||
this.code = values[index].code;
|
||||
this.setValues();
|
||||
this.$emit('change', picker, picker.getValues(), index);
|
||||
},
|
||||
|
||||
setValues() {
|
||||
let code = this.code || Object.keys(this.county)[0] || '';
|
||||
const { picker } = this.$refs;
|
||||
const province = this.getList('province');
|
||||
const city = this.getList('city', code.slice(0, 2));
|
||||
|
||||
if (!picker) {
|
||||
return;
|
||||
}
|
||||
|
||||
picker.setColumnValues(0, province);
|
||||
picker.setColumnValues(1, city);
|
||||
|
||||
if (city.length && code.slice(2, 4) === '00') {
|
||||
[{ code }] = city;
|
||||
}
|
||||
|
||||
picker.setColumnValues(2, this.getList('county', code.slice(0, 4)));
|
||||
picker.setIndexes([
|
||||
this.getIndex('province', code),
|
||||
this.getIndex('city', code),
|
||||
this.getIndex('county', code)
|
||||
]);
|
||||
},
|
||||
|
||||
getValues() {
|
||||
return this.$refs.picker ? this.$refs.picker.getValues().filter(value => !!value) : [];
|
||||
},
|
||||
|
||||
getArea() {
|
||||
const values = this.getValues();
|
||||
const area = {
|
||||
code: '',
|
||||
country: '',
|
||||
province: '',
|
||||
city: '',
|
||||
county: ''
|
||||
};
|
||||
|
||||
if (!values.length) {
|
||||
return area;
|
||||
}
|
||||
|
||||
const names = values.map(item => item.name);
|
||||
|
||||
area.code = values[values.length - 1].code;
|
||||
if (area.code[0] === '9') {
|
||||
area.country = names[1] || '';
|
||||
area.province = names[2] || '';
|
||||
} else {
|
||||
area.province = names[0] || '';
|
||||
area.city = names[1] || '';
|
||||
area.county = names[2] || '';
|
||||
}
|
||||
|
||||
return area;
|
||||
},
|
||||
|
||||
reset(code) {
|
||||
this.code = code || '';
|
||||
this.setValues();
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
const on = {
|
||||
...this.$listeners,
|
||||
change: this.onChange
|
||||
};
|
||||
|
||||
return (
|
||||
<Picker
|
||||
ref="picker"
|
||||
class={bem()}
|
||||
showToolbar
|
||||
valueKey="name"
|
||||
title={this.title}
|
||||
loading={this.loading}
|
||||
columns={this.displayColumns}
|
||||
itemHeight={this.itemHeight}
|
||||
visibleItemCount={this.visibleItemCount}
|
||||
cancelButtonText={this.cancelButtonText}
|
||||
confirmButtonText={this.confirmButtonText}
|
||||
{...{ on }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,226 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">河北省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">山西省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">内蒙古自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">辽宁省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">吉林省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">黑龙江省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">上海市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">江苏省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">浙江省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">安徽省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">福建省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">江西省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">山东省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">河南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">湖北省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">湖南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">广东省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">广西壮族自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">海南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">重庆市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">四川省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">贵州省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">云南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">西藏自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">陕西省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">甘肃省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">青海省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">宁夏回族自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">新疆维吾尔自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">台湾省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">香港特别行政区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">澳门特别行政区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">海外</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">东城区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">西城区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">朝阳区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">丰台区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">石景山区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">海淀区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">门头沟区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">房山区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">通州区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">顺义区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">昌平区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">大兴区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">怀柔区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">平谷区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">密云区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">延庆区</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, -352px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">河北省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">山西省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">内蒙古自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">辽宁省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">吉林省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">黑龙江省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">上海市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">江苏省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">浙江省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">安徽省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">福建省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">江西省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">山东省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">河南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">湖北省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">湖南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">广东省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">广西壮族自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">海南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">重庆市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">四川省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">贵州省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">云南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">西藏自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">陕西省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">甘肃省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">青海省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">宁夏回族自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">新疆维吾尔自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">台湾省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">香港特别行政区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">澳门特别行政区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">海外</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 0px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">杭州市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">宁波市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">温州市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">嘉兴市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">湖州市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">绍兴市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">金华市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">衢州市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">舟山市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">台州市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">丽水市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">鹿城区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">龙湾区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">瓯海区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">洞头区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">永嘉县</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">平阳县</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">苍南县</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">文成县</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">泰顺县</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">瑞安市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">乐清市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div class="van-ellipsis van-picker__title">标题</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">河北省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">山西省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">内蒙古自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">辽宁省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">吉林省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">黑龙江省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">上海市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">江苏省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">浙江省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">安徽省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">福建省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">江西省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">山东省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">河南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">湖北省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">湖南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">广东省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">广西壮族自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">海南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">重庆市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">四川省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">贵州省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">云南省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">西藏自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">陕西省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">甘肃省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">青海省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">宁夏回族自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">新疆维吾尔自治区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">台湾省</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">香港特别行政区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">澳门特别行政区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">海外</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,291 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`change option 1`] = `
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">县</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">东城区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">西城区</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`change option 2`] = `
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 44px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">县</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">和平区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">河东区</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`change option 3`] = `
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 44px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">县</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 44px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">和平区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">河东区</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`columns-num prop 1`] = `
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">县</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`reset method 1`] = `
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 44px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 44px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">县</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">蓟县</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`reset method 2`] = `
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">县</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">东城区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">西城区</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`watch areaList & code 1`] = `
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">县</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">东城区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">西城区</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`watch areaList & code 2`] = `
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">县</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">东城区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">西城区</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`watch areaList & code 3`] = `
|
||||
<div class="van-picker van-area">
|
||||
<div class="van-hairline--top-bottom van-picker__toolbar">
|
||||
<div role="button" tabindex="0" class="van-picker__cancel">取消</div>
|
||||
<div role="button" tabindex="0" class="van-picker__confirm">确认</div>
|
||||
</div>
|
||||
<!---->
|
||||
<div class="van-picker__columns" style="height: 220px;">
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">天津市</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">北京市</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">县</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker-column">
|
||||
<ul class="van-picker-column__wrapper" style="transform: translate3d(0, 88px, 0); transition-duration: 0ms; transition-property: none; line-height: 44px;">
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">东城区</li>
|
||||
<li class="van-ellipsis van-picker-column__item" style="height: 44px;">西城区</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="van-picker__mask" style="background-size: 100% 88px;"></div>
|
||||
<div class="van-hairline--top-bottom van-picker__frame" style="height: 44px;"></div>
|
||||
</div>
|
||||
<!---->
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,124 @@
|
||||
import Area from '..';
|
||||
import areaList from '../demo/area.simple';
|
||||
import { mount, later, triggerDrag } from '../../../test/utils';
|
||||
|
||||
const firstOption = [
|
||||
{ code: '110000', name: '北京市' },
|
||||
{ code: '110100', name: '北京市' },
|
||||
{ code: '110101', name: '东城区' }
|
||||
];
|
||||
|
||||
const secondOption = [
|
||||
{ code: '120000', name: '天津市' },
|
||||
{ code: '120100', name: '天津市' },
|
||||
{ code: '120101', name: '和平区' }
|
||||
];
|
||||
|
||||
test('confirm & cancel event', async () => {
|
||||
const onConfirm = jest.fn();
|
||||
const onCancel = jest.fn();
|
||||
const wrapper = mount(Area, {
|
||||
propsData: {
|
||||
areaList
|
||||
},
|
||||
listeners: {
|
||||
confirm: onConfirm,
|
||||
cancel: onCancel
|
||||
}
|
||||
});
|
||||
|
||||
await later();
|
||||
|
||||
wrapper.find('.van-picker__confirm').trigger('click');
|
||||
wrapper.find('.van-picker__cancel').trigger('click');
|
||||
|
||||
expect(onConfirm).toHaveBeenCalledWith(firstOption, [0, 0, 0]);
|
||||
expect(onCancel).toHaveBeenCalledWith(firstOption, [0, 0, 0]);
|
||||
});
|
||||
|
||||
test('watch areaList & code', async () => {
|
||||
const wrapper = mount(Area, {
|
||||
propsData: {
|
||||
areaList
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
wrapper.setProps({ value: '110117' });
|
||||
|
||||
await later();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
wrapper.setProps({
|
||||
value: ''
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('change option', () => {
|
||||
const onChange = jest.fn();
|
||||
const wrapper = mount(Area, {
|
||||
propsData: {
|
||||
areaList
|
||||
},
|
||||
listeners: {
|
||||
change: onChange
|
||||
}
|
||||
});
|
||||
|
||||
const columns = wrapper.findAll('.van-picker-column');
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
triggerDrag(columns.at(0), 0, -100);
|
||||
columns.at(0).find('ul').trigger('transitionend');
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
triggerDrag(columns.at(2), 0, -100);
|
||||
columns.at(2).find('ul').trigger('transitionend');
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
|
||||
expect(onChange.mock.calls[0][1]).toEqual(secondOption);
|
||||
});
|
||||
|
||||
test('getValues method', () => {
|
||||
const wrapper = mount(Area, {
|
||||
propsData: {
|
||||
areaList
|
||||
},
|
||||
created() {
|
||||
expect(this.getValues()).toEqual([]);
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper.vm.getValues()).toEqual(firstOption);
|
||||
});
|
||||
|
||||
test('reset method', async () => {
|
||||
const wrapper = mount(Area, {
|
||||
propsData: {
|
||||
areaList,
|
||||
value: '120225'
|
||||
}
|
||||
});
|
||||
|
||||
await later();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
wrapper.vm.reset();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('columns-num prop', async () => {
|
||||
const wrapper = mount(Area, {
|
||||
propsData: {
|
||||
areaList,
|
||||
columnsNum: 3
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.setProps({
|
||||
columnsNum: 2
|
||||
});
|
||||
|
||||
await later();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,131 @@
|
||||
# Area 省市区选择
|
||||
|
||||
### 介绍
|
||||
|
||||
省市取选择组件通常与 [弹出层](#/zh-CN/popup) 组件配合使用
|
||||
|
||||
### 引入
|
||||
|
||||
```javascript
|
||||
import { Area } from 'vant';
|
||||
|
||||
Vue.use(Area);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
要初始化一个`Area`组件,你需要传入一个`area-list`属性,数据格式具体可看下面数据格式章节
|
||||
|
||||
```html
|
||||
<van-area :area-list="areaList" />
|
||||
```
|
||||
|
||||
### 选中省市区
|
||||
|
||||
如果想选中某个省市区,需要传入一个`value`属性,绑定对应的省市区`code`
|
||||
|
||||
```html
|
||||
<van-area :area-list="areaList" value="110101" />
|
||||
```
|
||||
|
||||
### 配置显示列
|
||||
|
||||
可以通过`columns-num`属性配置省市区显示的列数,默认情况下会显示省市区,当你设置为`2`,则只会显示省市选择
|
||||
|
||||
```html
|
||||
<van-area :area-list="areaList" :columns-num="2" title="标题" />
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| value | 当前选中的省市区`code` | `String` | - | - |
|
||||
| title | 顶部栏标题 | `String` | - | - |
|
||||
| area-list | 省市区数据,格式见下方 | `Object` | - | - |
|
||||
| columns-num | 显示列数,3-省市区,2-省市,1-省 | `String | Number` | `3` | - |
|
||||
| loading | 是否显示加载状态 | `Boolean` | `false` | - |
|
||||
| item-height | 选项高度 | `Number` | `44` | - |
|
||||
| visible-item-count | 可见的选项个数 | `Number` | `5` | - |
|
||||
| confirm-button-text | 确认按钮文字 | `String` | `确认` | 1.5.3 |
|
||||
| cancel-button-text | 取消按钮文字 | `String` | `取消` | 1.5.3 |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件 | 说明 | 回调参数 |
|
||||
| --- | --- | --- |
|
||||
| confirm | 点击右上方完成按钮 | 一个数组参数,具体格式看下方数据格式章节 |
|
||||
| cancel | 点击取消按钮时 | - |
|
||||
| change | 选项改变时触发 | Picker 实例,所有列选中值,当前列对应的索引 |
|
||||
|
||||
### 方法
|
||||
|
||||
通过 ref 可以获取到 area 实例并调用实例方法
|
||||
|
||||
| 方法名 | 参数 | 返回值 | 介绍 |
|
||||
|------|------|------|------|
|
||||
| reset | code: string | - | 根据 code 重置所有选项,若不传 code,则重置到第一项 |
|
||||
|
||||
### 省市区列表数据格式
|
||||
|
||||
整体是一个 Object,包含 `province_list`, `city_list`, `county_list` 三个 key。
|
||||
|
||||
每项以省市区编码作为 key,省市区名字作为 value。编码为 6 位数字,前两位代表省份,中间两位代表城市,后两位代表区县,以 0 补足 6 位。如北京编码为 `11`,以零补足 6 位,为 `110000`。
|
||||
|
||||
`AreaList`具体格式如下:
|
||||
|
||||
```javascript
|
||||
{
|
||||
province_list: {
|
||||
110000: '北京市',
|
||||
120000: '天津市'
|
||||
},
|
||||
city_list: {
|
||||
110100: '北京市',
|
||||
110200: '县',
|
||||
120100: '天津市',
|
||||
120200: '县'
|
||||
},
|
||||
county_list: {
|
||||
110101: '东城区',
|
||||
110102: '西城区',
|
||||
110105: '朝阳区',
|
||||
110106: '丰台区'
|
||||
120101: '和平区',
|
||||
120102: '河东区',
|
||||
120103: '河西区',
|
||||
120104: '南开区',
|
||||
120105: '河北区',
|
||||
// ....
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
完整数据见 [Area.json](https://github.com/youzan/vant/blob/dev/packages/area/demo/area.js)
|
||||
|
||||
### 点击完成时返回的数据格式
|
||||
|
||||
返回的数据整体为一个数组,数组内包含 `columnsNum` 个数据, 每个数据对应一列选项中被选中的数据。
|
||||
|
||||
`code` 代表被选中的地区编码, `name` 代表被选中的地区名称
|
||||
|
||||
```javascript
|
||||
[
|
||||
{
|
||||
code: '110000',
|
||||
name: '北京市'
|
||||
},
|
||||
{
|
||||
code: '110100',
|
||||
name: '北京市'
|
||||
},
|
||||
{
|
||||
code: '110101',
|
||||
name: '东城区'
|
||||
}
|
||||
];
|
||||
```
|
||||
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('type')">
|
||||
<div class="demo-button-row">
|
||||
<van-button type="default">{{ $t('default') }}</van-button>
|
||||
<van-button type="primary">{{ $t('primary') }}</van-button>
|
||||
<van-button type="info">{{ $t('info') }}</van-button>
|
||||
</div>
|
||||
<van-button type="danger">{{ $t('danger') }}</van-button>
|
||||
<van-button type="warning">{{ $t('warning') }}</van-button>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('plain')">
|
||||
<van-button
|
||||
plain
|
||||
type="primary"
|
||||
:text="$t('plain')"
|
||||
/>
|
||||
<van-button
|
||||
plain
|
||||
type="danger"
|
||||
:text="$t('plain')"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('hairline')">
|
||||
<van-button
|
||||
plain
|
||||
hairline
|
||||
type="primary"
|
||||
:text="$t('hairlineButton')"
|
||||
/>
|
||||
<van-button
|
||||
plain
|
||||
hairline
|
||||
type="danger"
|
||||
:text="$t('hairlineButton')"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('disabled')">
|
||||
<van-button
|
||||
disabled
|
||||
type="primary"
|
||||
:text="$t('disabled')"
|
||||
/>
|
||||
<van-button
|
||||
disabled
|
||||
type="danger"
|
||||
:text="$t('disabled')"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('loadingStatus')">
|
||||
<van-button
|
||||
loading
|
||||
type="primary"
|
||||
/>
|
||||
<van-button
|
||||
loading
|
||||
type="primary"
|
||||
loading-type="spinner"
|
||||
/>
|
||||
<van-button
|
||||
loading
|
||||
:loading-text="$t('loadingText')"
|
||||
type="danger"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('shape')">
|
||||
<van-button
|
||||
type="primary"
|
||||
square
|
||||
:text="$t('square')"
|
||||
/>
|
||||
<van-button
|
||||
type="danger"
|
||||
round
|
||||
:text="$t('round')"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('icon')">
|
||||
<van-button
|
||||
type="primary"
|
||||
icon="star-o"
|
||||
/>
|
||||
<van-button
|
||||
type="primary"
|
||||
icon="star-o"
|
||||
:text="$t('button')"
|
||||
/>
|
||||
<van-button
|
||||
plain
|
||||
type="primary"
|
||||
icon="https://img.yzcdn.cn/vant/logo.png"
|
||||
:text="$t('button')"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('size')">
|
||||
<van-button
|
||||
type="primary"
|
||||
size="large"
|
||||
>
|
||||
{{ $t('large') }}
|
||||
</van-button>
|
||||
<van-button
|
||||
type="primary"
|
||||
size="normal"
|
||||
>
|
||||
{{ $t('normal') }}
|
||||
</van-button>
|
||||
<van-button
|
||||
type="primary"
|
||||
size="small"
|
||||
>
|
||||
{{ $t('small') }}
|
||||
</van-button>
|
||||
<van-button
|
||||
type="primary"
|
||||
size="mini"
|
||||
>
|
||||
{{ $t('mini') }}
|
||||
</van-button>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
type: '按钮类型',
|
||||
size: '按钮尺寸',
|
||||
icon: '图标按钮',
|
||||
loading: '加载状态',
|
||||
shape: '按钮形状',
|
||||
default: '默认按钮',
|
||||
primary: '主要按钮',
|
||||
info: '信息按钮',
|
||||
danger: '危险按钮',
|
||||
warning: '警告按钮',
|
||||
large: '大号按钮',
|
||||
normal: '普通按钮',
|
||||
small: '小型按钮',
|
||||
mini: '迷你按钮',
|
||||
plain: '朴素按钮',
|
||||
square: '方形按钮',
|
||||
round: '圆形按钮',
|
||||
hairline: '细边框',
|
||||
hairlineButton: '细边框按钮',
|
||||
loadingText: '加载中...'
|
||||
},
|
||||
'en-US': {
|
||||
type: 'Type',
|
||||
size: 'Size',
|
||||
icon: 'Icon',
|
||||
loading: 'Loading',
|
||||
shape: 'Shape',
|
||||
default: 'Default',
|
||||
primary: 'Primary',
|
||||
info: 'Info',
|
||||
danger: 'Danger',
|
||||
warning: 'Warning',
|
||||
large: 'Large',
|
||||
normal: 'Normal',
|
||||
small: 'Small',
|
||||
mini: 'Mini',
|
||||
plain: 'Plain',
|
||||
square: 'Square',
|
||||
round: 'Round',
|
||||
hairline: 'Hairline',
|
||||
hairlineButton: 'Hairline',
|
||||
loadingText: 'Loading...'
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.demo-button {
|
||||
.van-button {
|
||||
&--large {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
&--small,
|
||||
&--normal:not(:last-child) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.van-doc-demo-block {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.van-doc-demo-block__title {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
&-row {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,106 @@
|
||||
# Button
|
||||
|
||||
### Install
|
||||
|
||||
``` javascript
|
||||
import { Button } from 'vant';
|
||||
|
||||
Vue.use(Button);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Type
|
||||
|
||||
```html
|
||||
<van-button type="default">Default</van-button>
|
||||
<van-button type="primary">Primary</van-button>
|
||||
<van-button type="info">Info</van-button>
|
||||
<van-button type="danger">Danger</van-button>
|
||||
<van-button type="warning">Warning</van-button>
|
||||
```
|
||||
|
||||
### Plain
|
||||
|
||||
```html
|
||||
<van-button plain type="primary">Primary</van-button>
|
||||
<van-button plain type="danger">Danger</van-button>
|
||||
```
|
||||
|
||||
### Hairline
|
||||
|
||||
```html
|
||||
<van-button plain hairline type="primary">Hairline</van-button>
|
||||
<van-button plain hairline type="danger">Hairline</van-button>
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
```html
|
||||
<van-button disabled type="primary">Diabled</van-button>
|
||||
<van-button disabled type="danger">Diabled</van-button>
|
||||
```
|
||||
|
||||
### Loading
|
||||
|
||||
```html
|
||||
<van-button loading type="primary" />
|
||||
<van-button loading type="primary" loading-type="spinner" />
|
||||
<van-button loading type="danger" loading-text="Loading..." />
|
||||
```
|
||||
|
||||
### Shape
|
||||
|
||||
```html
|
||||
<van-button square type="primary">Square</van-button>
|
||||
<van-button round type="danger">Round</van-button>
|
||||
```
|
||||
|
||||
### Icon
|
||||
|
||||
```html
|
||||
<van-button icon="star-o" type="primary" />
|
||||
<van-button icon="star-o" type="primary">Button</van-button>
|
||||
<van-button icon="https://img.yzcdn.cn/vant/logo.png" type="danger">Button</van-button>
|
||||
```
|
||||
|
||||
### Size
|
||||
|
||||
```html
|
||||
<van-button type="primary" size="large">Large</van-button>
|
||||
<van-button type="primary" size="normal">Normal</van-button>
|
||||
<van-button type="primary" size="small">Small</van-button>
|
||||
<van-button type="primary" size="mini">Mini</van-button>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| type | Can be set to `primary` `info` `warning` `danger` | `String` | `default` |
|
||||
| size | Can be set to `large` `small` `mini` | `String` | `normal` |
|
||||
| text | Text | `String` | - |
|
||||
| icon | Left Icon | `String` | - |
|
||||
| tag | HTML Tag | `String` | `button` |
|
||||
| native-type | Native Type Attribute | `String` | `''` |
|
||||
| plain | Whether to be plain button | `Boolean` | `false` |
|
||||
| block | Whether to set display block | `Boolean` | `false` |
|
||||
| round | Whether to be round button | `Boolean` | `false` |
|
||||
| square | Whether to be square button | `Boolean` | `false` |
|
||||
| disabled | Whether to disable button | `Boolean` | `false` |
|
||||
| loading | Whether show loading status | `Boolean` | `false` |
|
||||
| loading-text | Loading text | `String` | - |
|
||||
| loading-type | Loading type, can be set to `spinner` | `String` | `circular` |
|
||||
| loading-size | Loading icon size | `String` | `20px` |
|
||||
| url | Link URL | `String` | - |
|
||||
| to | Target route of the link, same as to of `vue-router` | `String | Object` | - |
|
||||
| replace | If true, the navigation will not leave a history record | `Boolean` | `false` |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
|------|------|------|
|
||||
| click | Triggered when click button and not disabled or loading | event: Event |
|
||||
| touchstart | Triggered when touch start | event: TouchEvent |
|
||||
@@ -0,0 +1,176 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-button {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
box-sizing: border-box;
|
||||
height: @button-default-height;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: @button-default-font-size;
|
||||
line-height: @button-default-line-height;
|
||||
text-align: center;
|
||||
border-radius: @button-border-radius;
|
||||
-webkit-appearance: none;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: @black;
|
||||
border: inherit;
|
||||
border-color: @black;
|
||||
border-radius: inherit; /* inherit parent's border radius */
|
||||
transform: translate(-50%, -50%);
|
||||
opacity: 0;
|
||||
content: ' ';
|
||||
}
|
||||
|
||||
&:active::before {
|
||||
opacity: .1;
|
||||
}
|
||||
|
||||
&--loading,
|
||||
&--disabled {
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&--default {
|
||||
color: @button-default-color;
|
||||
background-color: @button-default-background-color;
|
||||
border: @button-border-width solid @button-default-border-color;
|
||||
}
|
||||
|
||||
&--primary {
|
||||
color: @button-primary-color;
|
||||
background-color: @button-primary-background-color;
|
||||
border: @button-border-width solid @button-primary-border-color;
|
||||
}
|
||||
|
||||
&--info {
|
||||
color: @button-info-color;
|
||||
background-color: @button-info-background-color;
|
||||
border: @button-border-width solid @button-info-border-color;
|
||||
}
|
||||
|
||||
&--danger {
|
||||
color: @button-danger-color;
|
||||
background-color: @button-danger-background-color;
|
||||
border: @button-border-width solid @button-danger-border-color;
|
||||
}
|
||||
|
||||
&--warning {
|
||||
color: @button-warning-color;
|
||||
background-color: @button-warning-background-color;
|
||||
border: @button-border-width solid @button-warning-border-color;
|
||||
}
|
||||
|
||||
&--plain {
|
||||
background-color: @button-plain-background-color;
|
||||
|
||||
&.van-button--primary {
|
||||
color: @button-primary-background-color;
|
||||
}
|
||||
|
||||
&.van-button--info {
|
||||
color: @button-info-background-color;
|
||||
}
|
||||
|
||||
&.van-button--danger {
|
||||
color: @button-danger-background-color;
|
||||
}
|
||||
|
||||
&.van-button--warning {
|
||||
color: @button-warning-background-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--large {
|
||||
width: 100%;
|
||||
height: @button-large-height;
|
||||
line-height: @button-large-line-height;
|
||||
}
|
||||
|
||||
&--normal {
|
||||
padding: 0 15px;
|
||||
font-size: @button-normal-font-size;
|
||||
}
|
||||
|
||||
&--small {
|
||||
min-width: @button-small-min-width;
|
||||
height: @button-small-height;
|
||||
padding: 0 8px;
|
||||
font-size: @button-small-font-size;
|
||||
line-height: @button-small-line-height;
|
||||
}
|
||||
|
||||
&__loading {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&--mini {
|
||||
display: inline-block;
|
||||
min-width: @button-mini-min-width;
|
||||
height: @button-mini-height;
|
||||
font-size: @button-mini-font-size;
|
||||
line-height: @button-mini-line-height;
|
||||
|
||||
& + .van-button--mini {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&--block {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
opacity: @button-disabled-opacity;
|
||||
}
|
||||
|
||||
&--round {
|
||||
border-radius: @button-round-border-radius;
|
||||
}
|
||||
|
||||
&--square {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
min-width: 1em;
|
||||
font-size: 1.2em;
|
||||
line-height: inherit;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&__icon + &__text,
|
||||
&__loading + &__text {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
&--hairline {
|
||||
border-width: 0;
|
||||
|
||||
&::after {
|
||||
border-color: inherit;
|
||||
border-radius: @button-border-radius * 2;
|
||||
}
|
||||
|
||||
&.van-button--round::after {
|
||||
border-radius: @button-round-border-radius;
|
||||
}
|
||||
|
||||
&.van-button--square::after {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { emit, inherit } from '../utils/functional';
|
||||
import { routeProps, RouteProps, functionalRoute } from '../utils/router';
|
||||
import Icon from '../icon';
|
||||
import Loading, { LoadingType } from '../loading';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
|
||||
export type ButtonType = 'default' | 'primary' | 'info' | 'warning' | 'danger';
|
||||
|
||||
export type ButtonSize = 'large' | 'normal' | 'small' | 'mini';
|
||||
|
||||
export type ButtonProps = RouteProps & {
|
||||
tag: keyof HTMLElementTagNameMap | string;
|
||||
type: ButtonType;
|
||||
size: ButtonSize;
|
||||
text?: string;
|
||||
icon?: string;
|
||||
block?: boolean;
|
||||
plain?: boolean;
|
||||
round?: boolean;
|
||||
square?: boolean;
|
||||
loading?: boolean;
|
||||
hairline?: boolean;
|
||||
disabled?: boolean;
|
||||
nativeType?: string;
|
||||
loadingSize: string;
|
||||
loadingType?: LoadingType;
|
||||
loadingText?: string;
|
||||
};
|
||||
|
||||
export type ButtonEvents = {
|
||||
onClick?(event: Event): void;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('button');
|
||||
|
||||
function Button(
|
||||
h: CreateElement,
|
||||
props: ButtonProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<ButtonProps>
|
||||
) {
|
||||
const { tag, icon, type, disabled, loading, hairline, loadingText } = props;
|
||||
|
||||
function onClick(event: Event) {
|
||||
if (!loading && !disabled) {
|
||||
emit(ctx, 'click', event);
|
||||
functionalRoute(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
function onTouchstart(event: TouchEvent) {
|
||||
emit(ctx, 'touchstart', event);
|
||||
}
|
||||
|
||||
const classes = [
|
||||
bem([
|
||||
type,
|
||||
props.size,
|
||||
{
|
||||
disabled,
|
||||
hairline,
|
||||
block: props.block,
|
||||
plain: props.plain,
|
||||
round: props.round,
|
||||
square: props.square
|
||||
}
|
||||
]),
|
||||
{ 'van-hairline--surround': hairline }
|
||||
];
|
||||
|
||||
function Content() {
|
||||
const content = [];
|
||||
|
||||
if (loading) {
|
||||
content.push(
|
||||
<Loading
|
||||
class={bem('loading')}
|
||||
size={props.loadingSize}
|
||||
type={props.loadingType}
|
||||
color={type === 'default' ? undefined : ''}
|
||||
/>
|
||||
);
|
||||
} else if (icon) {
|
||||
content.push(<Icon name={icon} class={bem('icon')} />);
|
||||
}
|
||||
|
||||
let text;
|
||||
if (loading) {
|
||||
text = loadingText;
|
||||
} else {
|
||||
text = slots.default ? slots.default() : props.text;
|
||||
}
|
||||
|
||||
if (text) {
|
||||
content.push(<span class={bem('text')}>{text}</span>);
|
||||
}
|
||||
|
||||
return content;
|
||||
}
|
||||
|
||||
return (
|
||||
<tag
|
||||
class={classes}
|
||||
type={props.nativeType}
|
||||
disabled={disabled}
|
||||
onClick={onClick}
|
||||
onTouchstart={onTouchstart}
|
||||
{...inherit(ctx)}
|
||||
>
|
||||
{Content()}
|
||||
</tag>
|
||||
);
|
||||
}
|
||||
|
||||
Button.props = {
|
||||
...routeProps,
|
||||
text: String,
|
||||
icon: String,
|
||||
block: Boolean,
|
||||
plain: Boolean,
|
||||
round: Boolean,
|
||||
square: Boolean,
|
||||
loading: Boolean,
|
||||
hairline: Boolean,
|
||||
disabled: Boolean,
|
||||
nativeType: String,
|
||||
loadingText: String,
|
||||
loadingType: String,
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'button'
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
size: {
|
||||
type: String,
|
||||
default: 'normal'
|
||||
},
|
||||
loadingSize: {
|
||||
type: String,
|
||||
default: '20px'
|
||||
}
|
||||
};
|
||||
|
||||
export default createComponent<ButtonProps, ButtonEvents>(Button);
|
||||
@@ -0,0 +1,38 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="demo-button-row"><button class="van-button van-button--default van-button--normal"><span class="van-button__text">默认按钮</span></button> <button class="van-button van-button--primary van-button--normal"><span class="van-button__text">主要按钮</span></button> <button class="van-button van-button--info van-button--normal"><span class="van-button__text">信息按钮</span></button></div> <button class="van-button van-button--danger van-button--normal"><span class="van-button__text">危险按钮</span></button> <button class="van-button van-button--warning van-button--normal"><span class="van-button__text">警告按钮</span></button>
|
||||
</div>
|
||||
<div><button class="van-button van-button--primary van-button--normal van-button--plain"><span class="van-button__text">朴素按钮</span></button> <button class="van-button van-button--danger van-button--normal van-button--plain"><span class="van-button__text">朴素按钮</span></button></div>
|
||||
<div><button class="van-button van-button--primary van-button--normal van-button--hairline van-button--plain van-hairline--surround"><span class="van-button__text">细边框按钮</span></button> <button class="van-button van-button--danger van-button--normal van-button--hairline van-button--plain van-hairline--surround"><span class="van-button__text">细边框按钮</span></button></div>
|
||||
<div><button disabled="disabled" class="van-button van-button--primary van-button--normal van-button--disabled"><span class="van-button__text">禁用状态</span></button> <button disabled="disabled" class="van-button van-button--danger van-button--normal van-button--disabled"><span class="van-button__text">禁用状态</span></button></div>
|
||||
<div><button class="van-button van-button--primary van-button--normal">
|
||||
<div class="van-loading van-loading--circular van-button__loading"><span class="van-loading__spinner van-loading__spinner--circular" style="width: 20px; height: 20px;"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
|
||||
</button> <button class="van-button van-button--primary van-button--normal">
|
||||
<div class="van-loading van-loading--spinner van-button__loading"><span class="van-loading__spinner van-loading__spinner--spinner" style="width: 20px; height: 20px;"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></span></div>
|
||||
</button> <button class="van-button van-button--danger van-button--normal">
|
||||
<div class="van-loading van-loading--circular van-button__loading"><span class="van-loading__spinner van-loading__spinner--circular" style="width: 20px; height: 20px;"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div><span class="van-button__text">加载中...</span>
|
||||
</button></div>
|
||||
<div><button class="van-button van-button--primary van-button--normal van-button--square"><span class="van-button__text">方形按钮</span></button> <button class="van-button van-button--danger van-button--normal van-button--round"><span class="van-button__text">圆形按钮</span></button></div>
|
||||
<div><button class="van-button van-button--primary van-button--normal"><i class="van-icon van-icon-star-o van-button__icon">
|
||||
<!----></i></button> <button class="van-button van-button--primary van-button--normal"><i class="van-icon van-icon-star-o van-button__icon">
|
||||
<!----></i><span class="van-button__text">按钮</span></button> <button class="van-button van-button--primary van-button--normal van-button--plain"><i class="van-icon van-button__icon">
|
||||
<div class="van-image van-icon__image"><img src="https://img.yzcdn.cn/vant/logo.png" class="van-image__img">
|
||||
<div class="van-image__loading"><i class="van-icon van-icon-photo-o" style="font-size: 22px;">
|
||||
<!----></i></div>
|
||||
</div>
|
||||
<!---->
|
||||
</i><span class="van-button__text">按钮</span></button></div>
|
||||
<div><button class="van-button van-button--primary van-button--large"><span class="van-button__text">
|
||||
大号按钮
|
||||
</span></button> <button class="van-button van-button--primary van-button--normal"><span class="van-button__text">
|
||||
普通按钮
|
||||
</span></button> <button class="van-button van-button--primary van-button--small"><span class="van-button__text">
|
||||
小型按钮
|
||||
</span></button> <button class="van-button van-button--primary van-button--mini"><span class="van-button__text">
|
||||
迷你按钮
|
||||
</span></button></div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`loading size 1`] = `
|
||||
<button class="van-button van-button--default van-button--normal">
|
||||
<div class="van-loading van-loading--circular van-button__loading"><span class="van-loading__spinner van-loading__spinner--circular" style="color: rgb(201, 201, 201); width: 10px; height: 10px;"><svg viewBox="25 25 50 50" class="van-loading__circular"><circle cx="50" cy="50" r="20" fill="none"></circle></svg></span></div>
|
||||
</button>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,74 @@
|
||||
import { mount } from '../../../test/utils';
|
||||
import Button from '..';
|
||||
|
||||
test('loading size', () => {
|
||||
const wrapper = mount(Button, {
|
||||
propsData: {
|
||||
loading: true,
|
||||
loadingSize: '10px'
|
||||
}
|
||||
});
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('click event', () => {
|
||||
const onClick = jest.fn();
|
||||
const wrapper = mount(Button, {
|
||||
context: {
|
||||
on: {
|
||||
click: onClick
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.trigger('click');
|
||||
expect(onClick).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('not trigger click event when disabled', () => {
|
||||
const onClick = jest.fn();
|
||||
const wrapper = mount(Button, {
|
||||
propsData: {
|
||||
disabled: true
|
||||
},
|
||||
context: {
|
||||
on: {
|
||||
click: onClick
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.trigger('click');
|
||||
expect(onClick).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('not trigger click event when loading', () => {
|
||||
const onClick = jest.fn();
|
||||
const wrapper = mount(Button, {
|
||||
propsData: {
|
||||
loading: true
|
||||
},
|
||||
context: {
|
||||
on: {
|
||||
click: onClick
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.trigger('click');
|
||||
expect(onClick).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('touchstart event', () => {
|
||||
const onTouchstart = jest.fn();
|
||||
const wrapper = mount(Button, {
|
||||
context: {
|
||||
on: {
|
||||
touchstart: onTouchstart
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.trigger('touchstart');
|
||||
expect(onTouchstart).toHaveBeenCalled();
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
# Button 按钮
|
||||
|
||||
### 引入
|
||||
|
||||
``` javascript
|
||||
import { Button } from 'vant';
|
||||
|
||||
Vue.use(Button);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 按钮类型
|
||||
|
||||
支持`default`、`primary`、`info`、`warning`、`danger`五种类型,默认为`default`
|
||||
|
||||
```html
|
||||
<van-button type="default">默认按钮</van-button>
|
||||
<van-button type="primary">主要按钮</van-button>
|
||||
<van-button type="info">信息按钮</van-button>
|
||||
<van-button type="warning">警告按钮</van-button>
|
||||
<van-button type="danger">危险按钮</van-button>
|
||||
```
|
||||
|
||||
### 朴素按钮
|
||||
|
||||
通过`plain`属性将按钮设置为朴素按钮,朴素按钮的文字为按钮颜色,背景为白色。
|
||||
|
||||
```html
|
||||
<van-button plain type="primary">朴素按钮</van-button>
|
||||
<van-button plain type="danger">朴素按钮</van-button>
|
||||
```
|
||||
|
||||
### 细边框
|
||||
|
||||
设置`hairline`属性可以开启 0.5px 边框,基于伪类实现
|
||||
|
||||
```html
|
||||
<van-button plain hairline type="primary">细边框按钮</van-button>
|
||||
<van-button plain hairline type="danger">细边框按钮</van-button>
|
||||
```
|
||||
|
||||
### 禁用状态
|
||||
|
||||
通过`disabled`属性来禁用按钮,禁用状态下按钮不可点击
|
||||
|
||||
```html
|
||||
<van-button disabled type="primary">禁用状态</van-button>
|
||||
<van-button disabled type="danger">禁用状态</van-button>
|
||||
```
|
||||
|
||||
### 加载状态
|
||||
|
||||
通过`loading`属性设置按钮为加载状态,加载状态下默认会隐藏按钮文字,可以通过`loading-text`设置加载状态下的文字
|
||||
|
||||
```html
|
||||
<van-button loading type="primary" />
|
||||
<van-button loading type="primary" loading-type="spinner" />
|
||||
<van-button loading type="danger" loading-text="加载中..." />
|
||||
```
|
||||
|
||||
### 按钮形状
|
||||
|
||||
通过`square`设置方形按钮,通过`round`设置圆形按钮
|
||||
|
||||
```html
|
||||
<van-button square type="primary">方形按钮</van-button>
|
||||
<van-button round type="danger">圆形按钮</van-button>
|
||||
```
|
||||
|
||||
### 图标按钮
|
||||
|
||||
通过`icon`属性设置按钮图标,支持 Icon 组件里的所有图标,也可以传入图标 URL
|
||||
|
||||
```html
|
||||
<van-button icon="star-o" type="primary" />
|
||||
<van-button icon="star-o" type="primary">按钮</van-button>
|
||||
<van-button icon="https://img.yzcdn.cn/vant/logo.png" type="danger">按钮</van-button>
|
||||
```
|
||||
|
||||
### 按钮尺寸
|
||||
|
||||
支持`large`、`normal`、`small`、`mini`四种尺寸,默认为`normal`
|
||||
|
||||
```html
|
||||
<van-button type="primary" size="large">大号按钮</van-button>
|
||||
<van-button type="primary" size="normal">普通按钮</van-button>
|
||||
<van-button type="primary" size="small">小型按钮</van-button>
|
||||
<van-button type="primary" size="mini">迷你按钮</van-button>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| type | 类型,可选值为 `primary` `info` `warning` `danger` | `String` | `default` | 1.6.6 |
|
||||
| size | 尺寸,可选值为 `large` `small` `mini` | `String` | `normal` | - |
|
||||
| text | 按钮文字 | `String` | - | - |
|
||||
| icon | 左侧图标名称或图片链接,可选值见 Icon 组件 | `String` | - | 2.0.0 |
|
||||
| tag | HTML 标签 | `String` | `button` | - |
|
||||
| native-type | 原生 button 标签 type 属性 | `String` | - | - |
|
||||
| block | 是否为块级元素 | `Boolean` | `false` | - |
|
||||
| plain | 是否为朴素按钮 | `Boolean` | `false` | 1.1.13 |
|
||||
| square | 是否为方形按钮 | `Boolean` | `false` | 1.2.0 |
|
||||
| round | 是否为圆形按钮 | `Boolean` | `false` | 1.3.4 |
|
||||
| disabled | 是否禁用按钮 | `Boolean` | `false` | - |
|
||||
| hairline | 是否使用 0.5px 边框 | `Boolean` | `false` | 1.6.11 |
|
||||
| loading | 是否显示为加载状态 | `Boolean` | `false` | - |
|
||||
| loading-text | 加载状态提示文字 | `String` | - | 1.6.3 |
|
||||
| loading-type | 加载图标类型,可选值为`spinner` | `String` | `circular` | 2.0.0 |
|
||||
| loading-size | 加载图标大小 | `String` | `20px` | 1.6.7 |
|
||||
| url | 跳转链接 | `String` | - | 1.6.5 |
|
||||
| to | 路由跳转对象,同 `vue-router` 的 to | `String | Object` | - | 1.6.5 |
|
||||
| replace | 跳转时是否替换当前页面历史 | `Boolean` | `false` | 1.6.5 |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|------|------|------|
|
||||
| click | 点击按钮,且按钮状态不为加载或禁用时触发 | event: Event |
|
||||
| touchstart | 开始触摸按钮时触发 | event: TouchEvent |
|
||||
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<demo-section background="white">
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-card
|
||||
num="2"
|
||||
price="2.00"
|
||||
:desc="$t('desc')"
|
||||
:title="$t('title')"
|
||||
:thumb="imageURL"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('discountInfo')">
|
||||
<van-card
|
||||
num="2"
|
||||
price="2.00"
|
||||
origin-price="10.00"
|
||||
:tag="$t('tag')"
|
||||
:desc="$t('desc')"
|
||||
:title="$t('title')"
|
||||
:thumb="imageURL"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('customContent')">
|
||||
<van-card
|
||||
num="2"
|
||||
price="2.00"
|
||||
:desc="$t('desc')"
|
||||
:title="$t('title')"
|
||||
:thumb="imageURL"
|
||||
>
|
||||
<template #tags>
|
||||
<div>
|
||||
<van-tag
|
||||
plain
|
||||
type="danger"
|
||||
style="margin-right: 5px;"
|
||||
>
|
||||
标签
|
||||
</van-tag>
|
||||
<van-tag
|
||||
plain
|
||||
type="danger"
|
||||
>
|
||||
标签
|
||||
</van-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template #footer>
|
||||
<div>
|
||||
<van-button
|
||||
round
|
||||
size="mini"
|
||||
>
|
||||
{{ $t('button') }}
|
||||
</van-button>
|
||||
<van-button
|
||||
round
|
||||
size="mini"
|
||||
>
|
||||
{{ $t('button') }}
|
||||
</van-button>
|
||||
</div>
|
||||
</template>
|
||||
</van-card>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
title: '商品名称',
|
||||
discountInfo: '营销信息',
|
||||
customContent: '自定义内容'
|
||||
},
|
||||
'en-US': {
|
||||
discountInfo: 'Discount Info',
|
||||
customContent: 'Custom Content'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
imageURL: 'https://img.yzcdn.cn/vant/t-thirt.jpg'
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
# Card
|
||||
|
||||
### Install
|
||||
``` javascript
|
||||
import { Card } from 'vant';
|
||||
|
||||
Vue.use(Card);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-card
|
||||
num="2"
|
||||
price="2.00"
|
||||
title="Title"
|
||||
desc="Description"
|
||||
thumb="https://img.yzcdn.cn/vant/t-thirt.jpg"
|
||||
/>
|
||||
```
|
||||
|
||||
### Discount Info
|
||||
|
||||
```html
|
||||
<van-card
|
||||
num="2"
|
||||
tag="Tag"
|
||||
price="2.00"
|
||||
title="Title"
|
||||
desc="Description"
|
||||
origin-price="10.00"
|
||||
thumb="https://img.yzcdn.cn/vant/t-thirt.jpg"
|
||||
/>
|
||||
```
|
||||
|
||||
### Custom Content
|
||||
|
||||
Use slot to custom content.
|
||||
|
||||
```html
|
||||
<van-card
|
||||
num="2"
|
||||
title="Title"
|
||||
desc="Description"
|
||||
price="2.00"
|
||||
thumb="https://img.yzcdn.cn/vant/t-thirt.jpg"
|
||||
>
|
||||
<div slot="tags">
|
||||
<van-tag plain type="danger">Tag</van-tag>
|
||||
<van-tag plain type="danger">Tag</van-tag>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<van-button size="mini">Button</van-button>
|
||||
<van-button size="mini">Button</van-button>
|
||||
</div>
|
||||
</van-card>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| thumb | Left thumb image URL | `String` | - |
|
||||
| title | Title | `String` | - |
|
||||
| desc | Description | `String` | - |
|
||||
| tag | Tag | `String` | - |
|
||||
| num | Number | `String | Number` | - |
|
||||
| price | Price | `String | Number` | - |
|
||||
| origin-price | Origin price | `String | Number` | - |
|
||||
| centered | Whether content vertical centered | `Boolean` | `false` |
|
||||
| currency | Currency symbol | `String` | `¥` |
|
||||
| thumb-link | Thumb link URL | `String` | - |
|
||||
| lazy-load | Whether to enable thumb lazy load,should register [Lazyload](#/en-US/lazyload) component | `Boolean` | `false` |
|
||||
|
||||
### Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
|------|------|------|
|
||||
| click | Triggered when clicked | - |
|
||||
| click-thumb | Triggered when thumb clicked | - |
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description |
|
||||
|------|------|
|
||||
| title | Custom title |
|
||||
| desc | Custom description |
|
||||
| num | Custom num |
|
||||
| price | Custom price |
|
||||
| origin-price | Custom origin price |
|
||||
| bottom | Custom price bottom |
|
||||
| thumb | Custom thumb |
|
||||
| tag | Custom thumb tag |
|
||||
| tags | Custom tags |
|
||||
| footer | Custom footer |
|
||||
@@ -0,0 +1,96 @@
|
||||
@import '../style/var';
|
||||
@import '../style/mixins/ellipsis';
|
||||
|
||||
.van-card {
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
padding: @card-padding;
|
||||
color: @card-text-color;
|
||||
font-size: @card-font-size;
|
||||
background-color: @card-background-color;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
&__thumb {
|
||||
position: relative;
|
||||
flex: none;
|
||||
width: @card-thumb-size;
|
||||
height: @card-thumb-size;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
min-width: 0; /* hack for flex box ellipsis */
|
||||
min-height: @card-thumb-size;
|
||||
|
||||
&--centered {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__title,
|
||||
&__desc {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
&__title {
|
||||
max-height: 32px;
|
||||
font-weight: 500;
|
||||
line-height: @card-title-line-height;
|
||||
|
||||
.multi-ellipsis(2);
|
||||
}
|
||||
|
||||
&__desc {
|
||||
max-height: @card-desc-line-height;
|
||||
color: @card-desc-color;
|
||||
line-height: @card-desc-line-height;
|
||||
}
|
||||
|
||||
&__bottom {
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
&__price {
|
||||
display: inline-block;
|
||||
color: @card-price-color;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
&__origin-price {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
color: @card-origin-price-color;
|
||||
font-size: @card-origin-price-font-size;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
&__num {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&__tag {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
flex: none;
|
||||
text-align: right;
|
||||
|
||||
.van-button {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,190 @@
|
||||
import { createNamespace, isDef } from '../utils';
|
||||
import { emit, inherit } from '../utils/functional';
|
||||
import Tag from '../tag';
|
||||
import Image from '../image';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots, ScopedSlot } from '../utils/types';
|
||||
|
||||
export type CardProps = {
|
||||
tag?: string;
|
||||
num?: number | string;
|
||||
desc?: string;
|
||||
thumb?: string;
|
||||
title?: string;
|
||||
price?: number | string;
|
||||
currency: string;
|
||||
centered?: boolean;
|
||||
lazyLoad?: boolean;
|
||||
thumbLink?: string;
|
||||
originPrice?: number | string;
|
||||
};
|
||||
|
||||
export type CardSlots = DefaultSlots & {
|
||||
num?: ScopedSlot;
|
||||
tag?: ScopedSlot;
|
||||
tags?: ScopedSlot;
|
||||
desc?: ScopedSlot;
|
||||
title?: ScopedSlot;
|
||||
thumb?: ScopedSlot;
|
||||
price?: ScopedSlot;
|
||||
bottom?: ScopedSlot;
|
||||
footer?: ScopedSlot;
|
||||
'origin-price'?: ScopedSlot;
|
||||
};
|
||||
|
||||
export type CardEvents = {
|
||||
onClick?(event: Event): void;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('card');
|
||||
|
||||
function Card(
|
||||
h: CreateElement,
|
||||
props: CardProps,
|
||||
slots: CardSlots,
|
||||
ctx: RenderContext<CardProps>
|
||||
) {
|
||||
const { thumb } = props;
|
||||
|
||||
const showNum = slots.num || isDef(props.num);
|
||||
const showPrice = slots.price || isDef(props.price);
|
||||
const showOriginPrice = slots['origin-price'] || isDef(props.originPrice);
|
||||
const showBottom = showNum || showPrice || showOriginPrice;
|
||||
|
||||
const onThumbClick = () => {
|
||||
emit(ctx, 'click-thumb');
|
||||
};
|
||||
|
||||
function ThumbTag() {
|
||||
if (slots.tag || props.tag) {
|
||||
return (
|
||||
<div class={bem('tag')}>
|
||||
{slots.tag ? (
|
||||
slots.tag()
|
||||
) : (
|
||||
<Tag mark type="danger">
|
||||
{props.tag}
|
||||
</Tag>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Thumb() {
|
||||
if (slots.thumb || thumb) {
|
||||
return (
|
||||
<a href={props.thumbLink} class={bem('thumb')} onClick={onThumbClick}>
|
||||
{slots.thumb ? (
|
||||
slots.thumb()
|
||||
) : (
|
||||
<Image
|
||||
src={thumb}
|
||||
width="100%"
|
||||
height="100%"
|
||||
fit="contain"
|
||||
lazy-load={props.lazyLoad}
|
||||
/>
|
||||
)}
|
||||
{ThumbTag()}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Title() {
|
||||
if (slots.title) {
|
||||
return slots.title();
|
||||
}
|
||||
|
||||
if (props.title) {
|
||||
return <div class={bem('title')}>{props.title}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Desc() {
|
||||
if (slots.desc) {
|
||||
return slots.desc();
|
||||
}
|
||||
|
||||
if (props.desc) {
|
||||
return <div class={[bem('desc'), 'van-ellipsis']}>{props.desc}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Price() {
|
||||
if (showPrice) {
|
||||
return (
|
||||
<div class={bem('price')}>
|
||||
{slots.price ? slots.price() : `${props.currency} ${props.price}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function OriginPrice() {
|
||||
if (showOriginPrice) {
|
||||
const slot = slots['origin-price'];
|
||||
return (
|
||||
<div class={bem('origin-price')}>
|
||||
{slot ? slot() : `${props.currency} ${props.originPrice}`}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function Num() {
|
||||
if (showNum) {
|
||||
return <div class={bem('num')}>{slots.num ? slots.num() : `x ${props.num}`}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Footer() {
|
||||
if (slots.footer) {
|
||||
return <div class={bem('footer')}>{slots.footer()}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={bem()} {...inherit(ctx, true)}>
|
||||
<div class={bem('header')}>
|
||||
{Thumb()}
|
||||
<div class={bem('content', { centered: props.centered })}>
|
||||
{Title()}
|
||||
{Desc()}
|
||||
{slots.tags && slots.tags()}
|
||||
{showBottom && (
|
||||
<div class="van-card__bottom">
|
||||
{Price()}
|
||||
{OriginPrice()}
|
||||
{Num()}
|
||||
{slots.bottom && slots.bottom()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{Footer()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Card.props = {
|
||||
tag: String,
|
||||
desc: String,
|
||||
thumb: String,
|
||||
title: String,
|
||||
centered: Boolean,
|
||||
lazyLoad: Boolean,
|
||||
thumbLink: String,
|
||||
num: [Number, String],
|
||||
price: [Number, String],
|
||||
originPrice: [Number, String],
|
||||
currency: {
|
||||
type: String,
|
||||
default: '¥'
|
||||
}
|
||||
};
|
||||
|
||||
export default createComponent<CardProps, CardEvents>(Card);
|
||||
@@ -0,0 +1,77 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-card">
|
||||
<div class="van-card__header"><a class="van-card__thumb">
|
||||
<div class="van-image" style="width: 100%; height: 100%;"><img src="https://img.yzcdn.cn/vant/t-thirt.jpg" class="van-image__img" style="object-fit: contain;">
|
||||
<div class="van-image__loading"><i class="van-icon van-icon-photo-o" style="font-size: 22px;">
|
||||
<!----></i></div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="van-card__content">
|
||||
<div class="van-card__title">商品名称</div>
|
||||
<div class="van-card__desc van-ellipsis">描述信息</div>
|
||||
<div class="van-card__bottom">
|
||||
<div class="van-card__price">¥ 2.00</div>
|
||||
<div class="van-card__num">x 2</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-card">
|
||||
<div class="van-card__header"><a class="van-card__thumb">
|
||||
<div class="van-image" style="width: 100%; height: 100%;"><img src="https://img.yzcdn.cn/vant/t-thirt.jpg" class="van-image__img" style="object-fit: contain;">
|
||||
<div class="van-image__loading"><i class="van-icon van-icon-photo-o" style="font-size: 22px;">
|
||||
<!----></i></div>
|
||||
</div>
|
||||
<div class="van-card__tag"><span class="van-tag van-tag--mark" style="background-color: rgb(255, 68, 68);">标签</span></div>
|
||||
</a>
|
||||
<div class="van-card__content">
|
||||
<div class="van-card__title">商品名称</div>
|
||||
<div class="van-card__desc van-ellipsis">描述信息</div>
|
||||
<div class="van-card__bottom">
|
||||
<div class="van-card__price">¥ 2.00</div>
|
||||
<div class="van-card__origin-price">¥ 10.00</div>
|
||||
<div class="van-card__num">x 2</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-card">
|
||||
<div class="van-card__header"><a class="van-card__thumb">
|
||||
<div class="van-image" style="width: 100%; height: 100%;"><img src="https://img.yzcdn.cn/vant/t-thirt.jpg" class="van-image__img" style="object-fit: contain;">
|
||||
<div class="van-image__loading"><i class="van-icon van-icon-photo-o" style="font-size: 22px;">
|
||||
<!----></i></div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="van-card__content">
|
||||
<div class="van-card__title">商品名称</div>
|
||||
<div class="van-card__desc van-ellipsis">描述信息</div>
|
||||
<div><span class="van-tag van-tag--plain van-hairline--surround" style="margin-right: 5px; color: rgb(255, 68, 68);">
|
||||
标签
|
||||
</span> <span class="van-tag van-tag--plain van-hairline--surround" style="color: rgb(255, 68, 68);">
|
||||
标签
|
||||
</span></div>
|
||||
<div class="van-card__bottom">
|
||||
<div class="van-card__price">¥ 2.00</div>
|
||||
<div class="van-card__num">x 2</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-card__footer">
|
||||
<div><button class="van-button van-button--default van-button--mini van-button--round"><span class="van-button__text">
|
||||
按钮
|
||||
</span></button> <button class="van-button van-button--default van-button--mini van-button--round"><span class="van-button__text">
|
||||
按钮
|
||||
</span></button></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,54 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`render bottom slot 1`] = `
|
||||
<div class="van-card">
|
||||
<div class="van-card__header">
|
||||
<div class="van-card__content">
|
||||
<div class="van-card__bottom">
|
||||
<div class="van-card__price">¥ 100</div>Custom Bottom
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render origin-price slot 1`] = `
|
||||
<div class="van-card">
|
||||
<div class="van-card__header">
|
||||
<div class="van-card__content">
|
||||
<div class="van-card__bottom">
|
||||
<div class="van-card__origin-price">Custom Origin Price</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render price & num slot 1`] = `
|
||||
<div class="van-card">
|
||||
<div class="van-card__header">
|
||||
<div class="van-card__content">
|
||||
<div class="van-card__bottom">
|
||||
<div class="van-card__price">Custom Price</div>
|
||||
<div class="van-card__num">Custom Num</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render thumb & tag slot 1`] = `
|
||||
<div class="van-card">
|
||||
<div class="van-card__header"><a class="van-card__thumb">Custom Thumb<div class="van-card__tag">Custom Tag</div></a>
|
||||
<div class="van-card__content"></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render title & desc slot 1`] = `
|
||||
<div class="van-card">
|
||||
<div class="van-card__header">
|
||||
<div class="van-card__content">Custom TitleCustom desc</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,58 @@
|
||||
import Card from '..';
|
||||
import { mount } from '../../../test/utils';
|
||||
|
||||
test('render price & num slot', () => {
|
||||
const wrapper = mount(Card, {
|
||||
scopedSlots: {
|
||||
num: () => 'Custom Num',
|
||||
price: () => 'Custom Price'
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render origin-price slot', () => {
|
||||
const wrapper = mount(Card, {
|
||||
scopedSlots: {
|
||||
'origin-price': () => 'Custom Origin Price'
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render bottom slot', () => {
|
||||
const wrapper = mount(Card, {
|
||||
propsData: {
|
||||
price: 100
|
||||
},
|
||||
scopedSlots: {
|
||||
bottom: () => 'Custom Bottom'
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render thumb & tag slot', () => {
|
||||
const wrapper = mount(Card, {
|
||||
scopedSlots: {
|
||||
tag: () => 'Custom Tag',
|
||||
thumb: () => 'Custom Thumb'
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render title & desc slot', () => {
|
||||
const wrapper = mount(Card, {
|
||||
scopedSlots: {
|
||||
title: () => 'Custom Title',
|
||||
desc: () => 'Custom desc'
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,101 @@
|
||||
# Card 卡片
|
||||
|
||||
### 引入
|
||||
``` javascript
|
||||
import { Card } from 'vant';
|
||||
|
||||
Vue.use(Card);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
```html
|
||||
<van-card
|
||||
num="2"
|
||||
price="2.00"
|
||||
desc="描述信息"
|
||||
title="商品标题"
|
||||
thumb="https://img.yzcdn.cn/vant/t-thirt.jpg"
|
||||
/>
|
||||
```
|
||||
|
||||
### 营销信息
|
||||
|
||||
通过`origin-price`设置商品原价,通过`tag`设置商品左上角标签
|
||||
|
||||
```html
|
||||
<van-card
|
||||
num="2"
|
||||
tag="标签"
|
||||
price="2.00"
|
||||
desc="描述信息"
|
||||
title="商品标题"
|
||||
thumb="https://img.yzcdn.cn/vant/t-thirt.jpg"
|
||||
origin-price="10.00"
|
||||
/>
|
||||
```
|
||||
|
||||
### 自定义内容
|
||||
|
||||
`Card`组件提供了多个插槽,可以灵活地自定义内容
|
||||
|
||||
```html
|
||||
<van-card
|
||||
num="2"
|
||||
price="2.00"
|
||||
desc="描述信息"
|
||||
title="商品标题"
|
||||
thumb="https://img.yzcdn.cn/vant/t-thirt.jpg"
|
||||
>
|
||||
<div slot="tags">
|
||||
<van-tag plain type="danger">标签</van-tag>
|
||||
<van-tag plain type="danger">标签</van-tag>
|
||||
</div>
|
||||
<div slot="footer">
|
||||
<van-button size="mini">按钮</van-button>
|
||||
<van-button size="mini">按钮</van-button>
|
||||
</div>
|
||||
</van-card>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| thumb | 左侧图片 URL | `String` | - | - |
|
||||
| title | 标题 | `String` | - | - |
|
||||
| desc | 描述 | `String` | - | - |
|
||||
| tag | 图片角标 | `String` | - | 1.3.4 |
|
||||
| num | 商品数量 | `String | Number` | - | - |
|
||||
| price | 商品价格 | `String | Number` | - | - |
|
||||
| origin-price | 商品划线原价 | `String | Number` | - | 1.3.6 |
|
||||
| centered | 内容是否垂直居中 | `Boolean` | `false` | - |
|
||||
| currency | 货币符号 | `String` | `¥` | - |
|
||||
| thumb-link | 点击左侧图片后的跳转链接 | `String` | - | 1.3.4 |
|
||||
| lazy-load | 是否开启图片懒加载,须配合 [Lazyload](#/zh-CN/lazyload) 组件使用 | `Boolean` | `false` | 1.5.0 |
|
||||
|
||||
### Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|------|------|------|
|
||||
| click | 点击时触发 | - |
|
||||
| click-thumb | 点击自定义图片时触发 | - |
|
||||
|
||||
### Slots
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| title | 自定义标题 |
|
||||
| desc | 自定义描述 |
|
||||
| num | 自定义数量 |
|
||||
| price | 自定义价格 |
|
||||
| origin-price | 自定义商品原价 |
|
||||
| bottom | 自定义价格下方区域 |
|
||||
| thumb | 自定义图片 |
|
||||
| tag | 自定义图片角标 |
|
||||
| tags | 自定义描述下方标签区域 |
|
||||
| footer | 自定义右下角内容 |
|
||||
@@ -0,0 +1,12 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-cell-group {
|
||||
background-color: @cell-group-background-color;
|
||||
|
||||
&__title {
|
||||
padding: @cell-group-title-padding;
|
||||
color: @cell-group-title-color;
|
||||
font-size: @cell-group-title-font-size;
|
||||
line-height: @cell-group-title-line-height;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { inherit } from '../utils/functional';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots } from '../utils/types';
|
||||
|
||||
export type CellGroupProps = {
|
||||
title?: string;
|
||||
border: boolean
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('cell-group');
|
||||
|
||||
function CellGroup(
|
||||
h: CreateElement,
|
||||
props: CellGroupProps,
|
||||
slots: DefaultSlots,
|
||||
ctx: RenderContext<CellGroupProps>
|
||||
) {
|
||||
const Group = (
|
||||
<div
|
||||
class={[bem(), { 'van-hairline--top-bottom': props.border }]}
|
||||
{...inherit(ctx, true)}
|
||||
>
|
||||
{slots.default && slots.default()}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (props.title) {
|
||||
return (
|
||||
<div>
|
||||
<div class={bem('title')}>{props.title}</div>
|
||||
{Group}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return Group;
|
||||
}
|
||||
|
||||
CellGroup.props = {
|
||||
title: String,
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
};
|
||||
|
||||
export default createComponent<CellGroupProps>(CellGroup);
|
||||
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-cell-group>
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
:value="$t('content')"
|
||||
/>
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
:value="$t('content')"
|
||||
:label="$t('desc')"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('largeSize')">
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
:value="$t('content')"
|
||||
size="large"
|
||||
/>
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
:value="$t('content')"
|
||||
size="large"
|
||||
:label="$t('desc')"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('showIcon')">
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
:value="$t('content')"
|
||||
icon="location-o"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('valueOnly')">
|
||||
<van-cell :value="$t('content')" />
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('showArrow')">
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
is-link
|
||||
/>
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
is-link
|
||||
:value="$t('content')"
|
||||
/>
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
is-link
|
||||
arrow-direction="down"
|
||||
:value="$t('content')"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('router')">
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
is-link
|
||||
url="//youzan.github.io/vant/mobile.html"
|
||||
/>
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
is-link
|
||||
to="index"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('groupTitle')">
|
||||
<van-cell-group :title="`${$t('group')} 1`">
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
:value="$t('content')"
|
||||
/>
|
||||
</van-cell-group>
|
||||
<van-cell-group :title="`${$t('group')} 2`">
|
||||
<van-cell
|
||||
:title="$t('cell')"
|
||||
:value="$t('content')"
|
||||
/>
|
||||
</van-cell-group>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('advancedUsage')">
|
||||
<van-cell
|
||||
:value="$t('content')"
|
||||
is-link
|
||||
>
|
||||
<template #title>
|
||||
<span class="custom-title">{{ $t('cell') }}</span>
|
||||
<van-tag type="danger">{{ $t('tag') }}</van-tag>
|
||||
</template>
|
||||
</van-cell>
|
||||
<van-cell
|
||||
icon="shop-o"
|
||||
:title="$t('cell')"
|
||||
>
|
||||
<template #right-icon>
|
||||
<van-icon
|
||||
name="search"
|
||||
style="line-height: inherit;"
|
||||
/>
|
||||
</template>
|
||||
</van-cell>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
cell: '单元格',
|
||||
valueOnly: '只设置 value',
|
||||
showIcon: '展示图标',
|
||||
showArrow: '展示箭头',
|
||||
largeSize: '单元格大小',
|
||||
group: '分组',
|
||||
groupTitle: '分组标题',
|
||||
router: '页面跳转'
|
||||
},
|
||||
'en-US': {
|
||||
cell: 'Cell title',
|
||||
valueOnly: 'Value only',
|
||||
showIcon: 'Left Icon',
|
||||
showArrow: 'Link',
|
||||
largeSize: 'Size',
|
||||
group: 'Group',
|
||||
groupTitle: 'Group Title',
|
||||
router: 'Router'
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.demo-cell {
|
||||
.custom-title {
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,141 @@
|
||||
# Cell
|
||||
|
||||
### Install
|
||||
``` javascript
|
||||
import { Cell, CellGroup } from 'vant';
|
||||
|
||||
Vue.use(Cell).use(CellGroup);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-cell title="Cell title" value="Content" />
|
||||
<van-cell title="Cell title" value="Content" label="Description" />
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Size
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-cell title="Cell title" value="Content" size="large" />
|
||||
<van-cell title="Cell title" value="Content" size="large" label="Description" />
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Left Icon
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-cell title="Cell title" icon="location-o" />
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Value only
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-cell value="Content" />
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Link
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-cell title="Cell title" is-link />
|
||||
<van-cell title="Cell title" is-link value="Content" />
|
||||
<van-cell title="Cell title" is-link arrow-direction="down" value="Content" />
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Router
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-cell title="Cell title" is-link url="//youzan.github.io/vant/mobile.html" />
|
||||
<van-cell title="Cell title" is-link to="index" />
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Group Title
|
||||
|
||||
```html
|
||||
<van-cell-group title="Group 1">
|
||||
<van-cell title="Cell title" value="Content" />
|
||||
</van-cell-group>
|
||||
<van-cell-group title="Group 2">
|
||||
<van-cell title="Cell title" value="Content" />
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### Advanced Usage
|
||||
|
||||
```html
|
||||
<van-cell value="Content" is-link>
|
||||
<template slot="title">
|
||||
<span class="custom-title">Cell title</span>
|
||||
<van-tag type="danger">Tag</van-tag>
|
||||
</template>
|
||||
</van-cell>
|
||||
|
||||
<van-cell title="Cell title" icon="shop-o">
|
||||
<van-icon
|
||||
slot="right-icon"
|
||||
name="search"
|
||||
style="line-height: inherit;"
|
||||
/>
|
||||
</van-cell>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### CellGroup Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| title | Group title | `String` | - |
|
||||
| border | Whether to show outer border | `Boolean` | `true` |
|
||||
|
||||
### Cell Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| icon | Left Icon | `String` | - |
|
||||
| title | Title | `String | Number` | - |
|
||||
| value | Right text | `String | Number` | - |
|
||||
| label | Description below the title | `String` | - |
|
||||
| size | Size,can be set to `large` | `String` | - |
|
||||
| border | Whether to show inner border | `Boolean` | `true` |
|
||||
| center | Whether to center content vertically | `Boolean` | `true` |
|
||||
| url | Link URL | `String` | - |
|
||||
| to | Target route of the link, same as to of `vue-router` | `String | Object` | - |
|
||||
| replace | If true, the navigation will not leave a history record | `Boolean` | `false` |
|
||||
| clickable | Whether to show click feedback when clicked | `Boolean` | `false` |
|
||||
| is-link | Whether to show link icon | `Boolean` | `false` |
|
||||
| required | Whether to show required mark | `Boolean` | `false` |
|
||||
| arrow-direction | Can be set to `left` `up` `down` | `String` | - |
|
||||
| title-style | Title style | `any` | - |
|
||||
| title-class | Title className | `any` | - |
|
||||
| value-class | Value className | `any` | - |
|
||||
| label-class | Label className | `any` | - |
|
||||
|
||||
### Cell Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
|------|------|------|
|
||||
| click | Triggered when click cell | - |
|
||||
|
||||
### Cell Slots
|
||||
|
||||
| Name | Description |
|
||||
|------|------|
|
||||
| default | Default slot |
|
||||
| icon | Custom icon |
|
||||
| title | Custom title |
|
||||
| label | Custom label |
|
||||
| right-icon | Custom right icon |
|
||||
@@ -0,0 +1,100 @@
|
||||
@import '../style/var';
|
||||
@import "../style/mixins/hairline";
|
||||
|
||||
.van-cell {
|
||||
position: relative;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: @cell-vertical-padding @cell-horizontal-padding;
|
||||
overflow: hidden;
|
||||
color: @cell-text-color;
|
||||
font-size: @cell-font-size;
|
||||
line-height: @cell-line-height;
|
||||
background-color: @cell-background-color;
|
||||
|
||||
&:not(:last-child)::after {
|
||||
.hairline-bottom(@cell-border-color, 15px);
|
||||
}
|
||||
|
||||
&--borderless::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-top: @cell-label-margin-top;
|
||||
color: @cell-label-color;
|
||||
font-size: @cell-label-font-size;
|
||||
line-height: @cell-label-line-height;
|
||||
}
|
||||
|
||||
&__title,
|
||||
&__value {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__value {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: @cell-value-color;
|
||||
text-align: right;
|
||||
vertical-align: middle;
|
||||
|
||||
&--alone {
|
||||
color: @text-color;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
&__left-icon,
|
||||
&__right-icon {
|
||||
min-width: 1em;
|
||||
height: @cell-line-height;
|
||||
font-size: @cell-icon-size;
|
||||
line-height: @cell-line-height;
|
||||
}
|
||||
|
||||
&__left-icon {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
&__right-icon {
|
||||
margin-left: 5px;
|
||||
color: @cell-right-icon-color;
|
||||
}
|
||||
|
||||
&--clickable {
|
||||
&:active {
|
||||
background-color: @cell-active-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--required {
|
||||
overflow: visible;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 7px;
|
||||
color: @cell-required-color;
|
||||
font-size: @cell-font-size;
|
||||
content: '*';
|
||||
}
|
||||
}
|
||||
|
||||
&--center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&--large {
|
||||
padding-top: @cell-large-vertical-padding;
|
||||
padding-bottom: @cell-large-vertical-padding;
|
||||
|
||||
.van-cell__title {
|
||||
font-size: @cell-large-title-font-size;
|
||||
}
|
||||
|
||||
.van-cell__label {
|
||||
font-size: @cell-large-label-font-size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import { createNamespace, isDef } from '../utils';
|
||||
import { cellProps, SharedCellProps } from './shared';
|
||||
import { emit, inherit } from '../utils/functional';
|
||||
import { routeProps, RouteProps, functionalRoute } from '../utils/router';
|
||||
import Icon from '../icon';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { ScopedSlot, DefaultSlots } from '../utils/types';
|
||||
import { Mods } from '../utils/create/bem';
|
||||
|
||||
export type CellProps = RouteProps &
|
||||
SharedCellProps & {
|
||||
arrowDirection?: string;
|
||||
};
|
||||
|
||||
export type CellSlots = DefaultSlots & {
|
||||
icon?: ScopedSlot;
|
||||
title?: ScopedSlot;
|
||||
label?: ScopedSlot;
|
||||
extra?: ScopedSlot;
|
||||
'right-icon'?: ScopedSlot;
|
||||
};
|
||||
|
||||
export type CellEvents = {
|
||||
onClick?(event: Event): void;
|
||||
};
|
||||
|
||||
const [createComponent, bem] = createNamespace('cell');
|
||||
|
||||
function Cell(
|
||||
h: CreateElement,
|
||||
props: CellProps,
|
||||
slots: CellSlots,
|
||||
ctx: RenderContext<CellProps>
|
||||
) {
|
||||
const { icon, size, title, label, value, isLink, arrowDirection } = props;
|
||||
|
||||
const showTitle = slots.title || isDef(title);
|
||||
const showValue = slots.default || isDef(value);
|
||||
const showLabel = slots.label || isDef(label);
|
||||
|
||||
const Label = showLabel && (
|
||||
<div class={[bem('label'), props.labelClass]}>
|
||||
{slots.label ? slots.label() : label}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Title = showTitle && (
|
||||
<div class={[bem('title'), props.titleClass]} style={props.titleStyle}>
|
||||
{slots.title ? slots.title() : <span>{title}</span>}
|
||||
{Label}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Value = showValue && (
|
||||
<div class={[bem('value', { alone: !slots.title && !title }), props.valueClass]}>
|
||||
{slots.default ? slots.default() : <span>{value}</span>}
|
||||
</div>
|
||||
);
|
||||
|
||||
const LeftIcon = slots.icon
|
||||
? slots.icon()
|
||||
: icon && <Icon class={bem('left-icon')} name={icon} />;
|
||||
|
||||
const rightIconSlot = slots['right-icon'];
|
||||
const RightIcon = rightIconSlot
|
||||
? rightIconSlot()
|
||||
: isLink && (
|
||||
<Icon
|
||||
class={bem('right-icon')}
|
||||
name={arrowDirection ? `arrow-${arrowDirection}` : 'arrow'}
|
||||
/>
|
||||
);
|
||||
|
||||
function onClick(event: Event) {
|
||||
emit(ctx, 'click', event);
|
||||
functionalRoute(ctx);
|
||||
}
|
||||
|
||||
const classes: Mods = {
|
||||
center: props.center,
|
||||
required: props.required,
|
||||
borderless: !props.border,
|
||||
clickable: isLink || props.clickable
|
||||
};
|
||||
|
||||
if (size) {
|
||||
classes[size] = size;
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={bem(classes)} onClick={onClick} {...inherit(ctx)}>
|
||||
{LeftIcon}
|
||||
{Title}
|
||||
{Value}
|
||||
{RightIcon}
|
||||
{slots.extra && slots.extra()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Cell.props = {
|
||||
...cellProps,
|
||||
...routeProps,
|
||||
arrowDirection: String
|
||||
};
|
||||
|
||||
export default createComponent<CellProps, CellEvents, CellSlots>(Cell);
|
||||
@@ -0,0 +1,36 @@
|
||||
export type SharedCellProps = {
|
||||
icon?: string;
|
||||
size?: string;
|
||||
border: boolean;
|
||||
center?: boolean;
|
||||
isLink?: boolean;
|
||||
required?: boolean;
|
||||
clickable?: boolean;
|
||||
titleStyle?: any;
|
||||
titleClass?: any;
|
||||
valueClass?: any;
|
||||
labelClass?: any;
|
||||
title?: string | number;
|
||||
value?: string | number;
|
||||
label?: string | number;
|
||||
}
|
||||
|
||||
export const cellProps = {
|
||||
icon: String,
|
||||
size: String,
|
||||
center: Boolean,
|
||||
isLink: Boolean,
|
||||
required: Boolean,
|
||||
clickable: Boolean,
|
||||
titleStyle: null as any,
|
||||
titleClass: null as any,
|
||||
valueClass: null as any,
|
||||
labelClass: null as any,
|
||||
title: [String, Number],
|
||||
value: [String, Number],
|
||||
label: [String, Number],
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,102 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-cell-group van-hairline--top-bottom">
|
||||
<div class="van-cell">
|
||||
<div class="van-cell__title"><span>单元格</span></div>
|
||||
<div class="van-cell__value"><span>内容</span></div>
|
||||
</div>
|
||||
<div class="van-cell">
|
||||
<div class="van-cell__title"><span>单元格</span>
|
||||
<div class="van-cell__label">描述信息</div>
|
||||
</div>
|
||||
<div class="van-cell__value"><span>内容</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell van-cell--large">
|
||||
<div class="van-cell__title"><span>单元格</span></div>
|
||||
<div class="van-cell__value"><span>内容</span></div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--large">
|
||||
<div class="van-cell__title"><span>单元格</span>
|
||||
<div class="van-cell__label">描述信息</div>
|
||||
</div>
|
||||
<div class="van-cell__value"><span>内容</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell"><i class="van-icon van-icon-location-o van-cell__left-icon">
|
||||
<!----></i>
|
||||
<div class="van-cell__title"><span>单元格</span></div>
|
||||
<div class="van-cell__value"><span>内容</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell">
|
||||
<div class="van-cell__value van-cell__value--alone"><span>内容</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>单元格</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>单元格</span></div>
|
||||
<div class="van-cell__value"><span>内容</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>单元格</span></div>
|
||||
<div class="van-cell__value"><span>内容</span></div><i class="van-icon van-icon-arrow-down van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>单元格</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>单元格</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-cell-group__title">分组 1</div>
|
||||
<div class="van-cell-group van-hairline--top-bottom">
|
||||
<div class="van-cell">
|
||||
<div class="van-cell__title"><span>单元格</span></div>
|
||||
<div class="van-cell__value"><span>内容</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell-group__title">分组 2</div>
|
||||
<div class="van-cell-group van-hairline--top-bottom">
|
||||
<div class="van-cell">
|
||||
<div class="van-cell__title"><span>单元格</span></div>
|
||||
<div class="van-cell__value"><span>内容</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span class="custom-title">单元格</span> <span class="van-tag" style="background-color: rgb(255, 68, 68);">标签</span></div>
|
||||
<div class="van-cell__value"><span>内容</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div class="van-cell"><i class="van-icon van-icon-shop-o van-cell__left-icon">
|
||||
<!----></i>
|
||||
<div class="van-cell__title"><span>单元格</span></div><i class="van-icon van-icon-search" style="line-height: inherit;">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,17 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`arrow direction 1`] = `
|
||||
<div class="van-cell van-cell--clickable"><i class="van-icon van-icon-arrow-down van-cell__right-icon">
|
||||
<!----></i></div>
|
||||
`;
|
||||
|
||||
exports[`render slot 1`] = `
|
||||
<div class="van-cell">Custom Icon<div class="van-cell__title">Custom Title<div class="van-cell__label">Custom Label</div>
|
||||
</div>Custom Extra</div>
|
||||
`;
|
||||
|
||||
exports[`title-style prop 1`] = `
|
||||
<div class="van-cell">
|
||||
<div class="van-cell__title" style="color: red;"><span>title</span></div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,58 @@
|
||||
import Cell from '..';
|
||||
import { mount } from '../../../test/utils';
|
||||
|
||||
test('click event', () => {
|
||||
const click = jest.fn();
|
||||
const wrapper = mount(Cell, {
|
||||
context: {
|
||||
on: {
|
||||
click
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.trigger('click');
|
||||
expect(click).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('arrow direction', () => {
|
||||
const wrapper = mount(Cell, {
|
||||
propsData: {
|
||||
isLink: true,
|
||||
arrowDirection: 'down'
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('render slot', () => {
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<cell>
|
||||
<template v-slot:icon>Custom Icon</template>
|
||||
<template v-slot:title>Custom Title</template>
|
||||
<template v-slot:label>Custom Label</template>
|
||||
<template v-slot:extra>Custom Extra</template>
|
||||
</cell>
|
||||
`,
|
||||
components: {
|
||||
Cell
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('title-style prop', () => {
|
||||
const wrapper = mount(Cell, {
|
||||
propsData: {
|
||||
title: 'title',
|
||||
titleStyle: {
|
||||
color: 'red'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,147 @@
|
||||
# Cell 单元格
|
||||
|
||||
### 引入
|
||||
``` javascript
|
||||
import { Cell, CellGroup } from 'vant';
|
||||
|
||||
Vue.use(Cell).use(CellGroup);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
`Cell`可以单独使用,也可以与`CellGroup`搭配使用。`CellGroup`可以为`Cell`提供上下外边框。
|
||||
|
||||
```html
|
||||
<van-cell-group>
|
||||
<van-cell title="单元格" value="内容" />
|
||||
<van-cell title="单元格" value="内容" label="描述信息" />
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### 单元格大小
|
||||
|
||||
通过`size`属性可以控制单元格的大小
|
||||
|
||||
```html
|
||||
<van-cell title="单元格" value="内容" size="large" />
|
||||
<van-cell title="单元格" value="内容" size="large" label="描述信息" />
|
||||
```
|
||||
|
||||
### 展示图标
|
||||
|
||||
通过`icon`属性在标题左侧展示图标
|
||||
|
||||
```html
|
||||
<van-cell title="单元格" icon="location-o" />
|
||||
```
|
||||
|
||||
### 只设置 value
|
||||
|
||||
只设置`value`时会向左对齐
|
||||
|
||||
```html
|
||||
<van-cell value="内容" />
|
||||
```
|
||||
|
||||
### 展示箭头
|
||||
|
||||
传入`is-link`属性则会在右侧显示箭头,并且可以通过传入`arrow-direction`属性控制箭头方向
|
||||
|
||||
```html
|
||||
<van-cell title="单元格" is-link />
|
||||
<van-cell title="单元格" is-link value="内容" />
|
||||
<van-cell title="单元格" is-link arrow-direction="down" value="内容" />
|
||||
```
|
||||
|
||||
### 页面跳转
|
||||
|
||||
可以通过`url`属性进行页面跳转,或通过`to`属性进行 vue-router 跳转
|
||||
|
||||
```html
|
||||
<van-cell title="单元格" is-link url="//youzan.github.io/vant/mobile.html" />
|
||||
<van-cell title="单元格" is-link to="index" />
|
||||
```
|
||||
|
||||
### 分组标题
|
||||
|
||||
通过`CellGroup`的`title`属性可以指定分组标题
|
||||
|
||||
```html
|
||||
<van-cell-group title="分组1">
|
||||
<van-cell title="单元格" value="内容" />
|
||||
</van-cell-group>
|
||||
<van-cell-group title="分组2">
|
||||
<van-cell title="单元格" value="内容" />
|
||||
</van-cell-group>
|
||||
```
|
||||
|
||||
### 高级用法
|
||||
|
||||
如以上用法不能满足你的需求,可以使用对应的`slot`来自定义显示的内容
|
||||
|
||||
```html
|
||||
<van-cell value="内容" is-link>
|
||||
<template slot="title">
|
||||
<span class="custom-title">单元格</span>
|
||||
<van-tag type="danger">标签</van-tag>
|
||||
</template>
|
||||
</van-cell>
|
||||
|
||||
<van-cell title="单元格" icon="shop-o">
|
||||
<van-icon
|
||||
slot="right-icon"
|
||||
name="search"
|
||||
style="line-height: inherit;"
|
||||
/>
|
||||
</van-cell>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### CellGroup Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| title | 分组标题 | `String` | `-` | 1.6.9 |
|
||||
| border | 是否显示外边框 | `Boolean` | `true` | - |
|
||||
|
||||
### Cell Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| icon | 左侧图标名称或图片链接,可选值见 Icon 组件 | `String` | - | - |
|
||||
| title | 左侧标题 | `String | Number` | - | - |
|
||||
| value | 右侧内容 | `String | Number` | - | - |
|
||||
| label | 标题下方的描述信息 | `String` | - | - |
|
||||
| size | 单元格大小,可选值为 `large` | `String` | - | 1.4.4 |
|
||||
| url | 跳转链接 | `String` | - | - |
|
||||
| to | 路由跳转对象,同 `vue-router` 的 to | `String | Object` | - | - |
|
||||
| border | 是否显示内边框 | `Boolean` | `true` | - |
|
||||
| replace | 跳转时是否替换当前页面历史 | `Boolean` | `false` | - |
|
||||
| clickable | 是否开启点击反馈 | `Boolean` | `false` | - |
|
||||
| is-link | 是否展示右侧箭头并开启点击反馈 | `Boolean` | `false` | - |
|
||||
| required | 是否显示表单必填星号 | `Boolean` | `false` | - |
|
||||
| center | 是否使内容垂直居中 | `Boolean` | `false` | 1.0.3 |
|
||||
| arrow-direction | 箭头方向,可选值为 `left` `up` `down` | `String` | - | 1.1.10 |
|
||||
| title-style | 左侧标题额外样式 | `any` | - | 1.6.17 |
|
||||
| title-class | 左侧标题额外类名 | `any` | - | 1.4.8 |
|
||||
| value-class | 右侧内容额外类名 | `any` | - | 1.4.8 |
|
||||
| label-class | 描述信息额外类名 | `any` | - | 1.4.8 |
|
||||
|
||||
### Cell Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|------|------|------|
|
||||
| click | 点击单元格时触发 | - |
|
||||
|
||||
### Cell Slots
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| default | 自定义`value`显示内容 |
|
||||
| title | 自定义标题显示内容 |
|
||||
| label | 自定义标题下方描述显示内容 |
|
||||
| icon | 自定义左侧图标 |
|
||||
| right-icon | 自定义右侧按钮,默认为`arrow` |
|
||||
@@ -0,0 +1,24 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { ParentMixin } from '../mixins/relation';
|
||||
|
||||
const [createComponent, bem] = createNamespace('checkbox-group');
|
||||
|
||||
export default createComponent({
|
||||
mixins: [ParentMixin('vanCheckbox')],
|
||||
|
||||
props: {
|
||||
max: Number,
|
||||
value: Array,
|
||||
disabled: Boolean
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
this.$emit('change', val);
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
return <div class={bem()}>{this.slots()}</div>;
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-checkbox v-model="checkbox1">{{ $t('checkbox') }}</van-checkbox>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('disabled')">
|
||||
<van-checkbox
|
||||
:value="false"
|
||||
disabled
|
||||
>
|
||||
{{ $t('checkbox') }}
|
||||
</van-checkbox>
|
||||
<van-checkbox
|
||||
:value="true"
|
||||
disabled
|
||||
>
|
||||
{{ $t('checkbox') }}
|
||||
</van-checkbox>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('customColor')">
|
||||
<van-checkbox
|
||||
v-model="checkbox2"
|
||||
checked-color="#07c160"
|
||||
>
|
||||
{{ $t('customColor') }}
|
||||
</van-checkbox>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('customIcon')">
|
||||
<van-checkbox v-model="checkbox3">
|
||||
{{ $t('customIcon') }}
|
||||
<template #icon="{ checked }">
|
||||
<img :src="checked ? icon.active : icon.inactive">
|
||||
</template>
|
||||
</van-checkbox>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title3')">
|
||||
<van-checkbox-group v-model="result">
|
||||
<van-checkbox
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:name="item"
|
||||
>
|
||||
{{ $t('checkbox') }} {{ item }}
|
||||
</van-checkbox>
|
||||
</van-checkbox-group>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title4')">
|
||||
<van-checkbox-group
|
||||
v-model="result2"
|
||||
:max="2"
|
||||
>
|
||||
<van-checkbox
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:name="item"
|
||||
>
|
||||
{{ $t('checkbox') }} {{ item }}
|
||||
</van-checkbox>
|
||||
</van-checkbox-group>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title5')">
|
||||
<van-checkbox-group v-model="result3">
|
||||
<van-cell-group>
|
||||
<van-cell
|
||||
v-for="(item, index) in list"
|
||||
clickable
|
||||
:key="index"
|
||||
:title="$t('checkbox') + item"
|
||||
@click="toggle(index)"
|
||||
>
|
||||
<van-checkbox
|
||||
ref="checkboxes"
|
||||
slot="right-icon"
|
||||
:name="item"
|
||||
/>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-checkbox-group>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
checkbox: '复选框',
|
||||
customIcon: '自定义图标',
|
||||
customColor: '自定义颜色',
|
||||
title3: '复选框组',
|
||||
title4: '设置最大可选数',
|
||||
title5: '搭配单元格组件使用'
|
||||
},
|
||||
'en-US': {
|
||||
checkbox: 'Checkbox',
|
||||
customIcon: 'Custom Icon',
|
||||
customColor: 'Custom Color',
|
||||
title3: 'Checkbox Group',
|
||||
title4: 'Maximum amount of checked options',
|
||||
title5: 'Inside a Cell'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
checkbox1: true,
|
||||
checkbox2: true,
|
||||
checkbox3: true,
|
||||
list: [
|
||||
'a',
|
||||
'b',
|
||||
'c'
|
||||
],
|
||||
result: ['a', 'b'],
|
||||
result2: [],
|
||||
result3: [],
|
||||
icon: {
|
||||
active: 'https://img.yzcdn.cn/vant/user-active.png',
|
||||
inactive: 'https://img.yzcdn.cn/vant/user-inactive.png'
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle(index) {
|
||||
this.$refs.checkboxes[index].toggle();
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.demo-checkbox {
|
||||
.van-checkbox {
|
||||
margin: 10px 0 0 20px;
|
||||
}
|
||||
|
||||
.van-cell {
|
||||
.van-checkbox {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,189 @@
|
||||
# Checkbox
|
||||
|
||||
### Install
|
||||
``` javascript
|
||||
import { Checkbox, CheckboxGroup } from 'vant';
|
||||
|
||||
Vue.use(Checkbox).use(CheckboxGroup);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-checkbox v-model="checked">Checkbox</van-checkbox>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
checked: true
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Disabled
|
||||
|
||||
```html
|
||||
<van-checkbox v-model="checked" disabled>Checkbox</van-checkbox>
|
||||
```
|
||||
|
||||
### Custom Color
|
||||
|
||||
```html
|
||||
<van-checkbox v-model="checked" checked-color="#07c160">Checkbox</van-checkbox>
|
||||
```
|
||||
|
||||
### Custom Icon
|
||||
|
||||
Use icon slot to custom icon
|
||||
|
||||
```html
|
||||
<van-checkbox v-model="checked">
|
||||
Custom Icon
|
||||
<img
|
||||
slot="icon"
|
||||
slot-scope="props"
|
||||
:src="props.checked ? icon.active : icon.inactive"
|
||||
>
|
||||
</van-checkbox>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
checked: true,
|
||||
icon: {
|
||||
active: 'https://img.yzcdn.cn/vant/user-active.png',
|
||||
inactive: 'https://img.yzcdn.cn/vant/user-inactive.png'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Checkbox Group
|
||||
|
||||
When Checkboxes are inside a CheckboxGroup, the checked checkboxes's name is an array and bound with CheckboxGroup by v-model.
|
||||
|
||||
```html
|
||||
<van-checkbox-group v-model="result">
|
||||
<van-checkbox
|
||||
v-for="(item, index) in list"
|
||||
:key="item"
|
||||
:name="item"
|
||||
>
|
||||
Checkbox {{ item }}
|
||||
</van-checkbox>
|
||||
</van-checkbox-group>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: ['a', 'b', 'c'],
|
||||
result: ['a', 'b']
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Maximum amount of checked options
|
||||
|
||||
```html
|
||||
<van-checkbox-group v-model="result" :max="2">
|
||||
<van-checkbox
|
||||
v-for="(item, index) in list"
|
||||
:name="item"
|
||||
:key="item"
|
||||
>
|
||||
Checkbox {{ item }}
|
||||
</van-checkbox>
|
||||
</van-checkbox-group>
|
||||
```
|
||||
|
||||
### Inside a Cell
|
||||
|
||||
```html
|
||||
<van-checkbox-group v-model="result">
|
||||
<van-cell-group>
|
||||
<van-cell
|
||||
v-for="(item, index) in list"
|
||||
clickable
|
||||
:key="item"
|
||||
:title="`Checkbox ${item}`"
|
||||
@click="toggle(index)"
|
||||
>
|
||||
<van-checkbox
|
||||
:name="item"
|
||||
ref="checkboxes"
|
||||
slot="right-icon"
|
||||
/>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-checkbox-group>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
methods: {
|
||||
toggle(index) {
|
||||
this.$refs.checkboxes[index].toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Checkbox Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| name | Checkbox name | `any` | - |
|
||||
| shape | Can be set to `square` | `String` | `round` |
|
||||
| v-model | Check status | `Boolean` | `false` |
|
||||
| disabled | Diable checkbox | `Boolean` | `false` |
|
||||
| icon-size | Icon size | `String | Number` | `20px` |
|
||||
| label-disabled | Whether to disable label click | `Boolean` | `false` |
|
||||
| label-position | Can be set to `left` | `String` | `right` |
|
||||
| checked-color | Checked color | `String` | `#1989fa` | - |
|
||||
|
||||
### CheckboxGroup Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| v-model | Names of all checked checkboxes | `Array` | - |
|
||||
| disabled | Disable all checkboxes | `Boolean` | `false` |
|
||||
| max | Maximum amount of checked options | `Number` | `0`(Unlimited) |
|
||||
|
||||
### Checkbox Events
|
||||
|
||||
| Event | Description | Parameters |
|
||||
|------|------|------|
|
||||
| change | Triggered when value changed | current value |
|
||||
| click | Triggered when click checkbox | event: Event |
|
||||
|
||||
### CheckboxGroup Events
|
||||
|
||||
| Event | Description | Parameters |
|
||||
|------|------|------|
|
||||
| change | Triggered when value changed | current value |
|
||||
|
||||
### Checkbox Slots
|
||||
|
||||
| Name | Description | slot-scope |
|
||||
|------|------|------|
|
||||
| default | Custom label | - |
|
||||
| icon | Custom icon | checked: whether to be checked |
|
||||
|
||||
### Checkbox Methods
|
||||
|
||||
Use ref to get checkbox instance and call instance methods
|
||||
|
||||
| Name | Attribute | Return value | Description |
|
||||
|------|------|------|------|
|
||||
| toggle | - | - | Toggle check status |
|
||||
@@ -0,0 +1,85 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { CheckboxMixin } from '../mixins/checkbox';
|
||||
|
||||
const [createComponent, bem] = createNamespace('checkbox');
|
||||
|
||||
export default createComponent({
|
||||
mixins: [CheckboxMixin({
|
||||
bem,
|
||||
role: 'checkbox',
|
||||
parent: 'vanCheckbox'
|
||||
})],
|
||||
|
||||
computed: {
|
||||
checked: {
|
||||
get() {
|
||||
return this.parent ? this.parent.value.indexOf(this.name) !== -1 : this.value;
|
||||
},
|
||||
|
||||
set(val) {
|
||||
if (this.parent) {
|
||||
this.setParentValue(val);
|
||||
} else {
|
||||
this.$emit('input', val);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
value(val) {
|
||||
this.$emit('change', val);
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
toggle() {
|
||||
const checked = !this.checked;
|
||||
|
||||
// When toggle method is called multiple times at the same time,
|
||||
// only the last call is valid.
|
||||
// This is a hack for usage inside Cell.
|
||||
clearTimeout(this.toggleTask);
|
||||
this.toggleTask = setTimeout(() => {
|
||||
this.checked = checked;
|
||||
});
|
||||
},
|
||||
|
||||
onClickIcon() {
|
||||
if (!this.isDisabled) {
|
||||
this.toggle();
|
||||
}
|
||||
},
|
||||
|
||||
onClickLabel() {
|
||||
if (!this.isDisabled && !this.labelDisabled) {
|
||||
this.toggle();
|
||||
}
|
||||
},
|
||||
|
||||
setParentValue(val) {
|
||||
const { parent } = this;
|
||||
const value = parent.value.slice();
|
||||
|
||||
if (val) {
|
||||
if (parent.max && value.length >= parent.max) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (value.indexOf(this.name) === -1) {
|
||||
value.push(this.name);
|
||||
parent.$emit('input', value);
|
||||
}
|
||||
} else {
|
||||
const index = value.indexOf(this.name);
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (index !== -1) {
|
||||
value.splice(index, 1);
|
||||
parent.$emit('input', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,69 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-checkbox {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
|
||||
&__icon {
|
||||
flex: none;
|
||||
height: 1em;
|
||||
font-size: @checkbox-size;
|
||||
line-height: 1em;
|
||||
|
||||
.van-icon {
|
||||
display: block;
|
||||
box-sizing: border-box;
|
||||
width: 1.25em;
|
||||
height: 1.25em;
|
||||
color: transparent;
|
||||
font-size: .8em;
|
||||
line-height: inherit;
|
||||
text-align: center;
|
||||
border: 1px solid @checkbox-border-color;
|
||||
transition: @checkbox-transition-duration;
|
||||
}
|
||||
|
||||
&--round {
|
||||
.van-icon {
|
||||
border-radius: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&--checked {
|
||||
.van-icon {
|
||||
color: @white;
|
||||
background-color: @checkbox-checked-icon-color;
|
||||
border-color: @checkbox-checked-icon-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
.van-icon {
|
||||
background-color: @checkbox-disabled-background-color;
|
||||
border-color: @checkbox-disabled-icon-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled&--checked {
|
||||
.van-icon {
|
||||
color: @checkbox-disabled-icon-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
margin-left: @checkbox-label-margin;
|
||||
color: @checkbox-label-color;
|
||||
line-height: @checkbox-size;
|
||||
|
||||
&--left {
|
||||
margin: 0 @checkbox-label-margin 0 0;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
color: @checkbox-disabled-label-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div role="checkbox" tabindex="0" aria-checked="true" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked"><i class="van-icon van-icon-success">
|
||||
<!----></i></div><span class="van-checkbox__label">复选框</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div role="checkbox" tabindex="-1" aria-checked="false" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--disabled"><i class="van-icon van-icon-success">
|
||||
<!----></i></div><span class="van-checkbox__label van-checkbox__label--disabled">
|
||||
复选框
|
||||
</span>
|
||||
</div>
|
||||
<div role="checkbox" tabindex="-1" aria-checked="true" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--disabled van-checkbox__icon--checked"><i class="van-icon van-icon-success">
|
||||
<!----></i></div><span class="van-checkbox__label van-checkbox__label--disabled">
|
||||
复选框
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div role="checkbox" tabindex="0" aria-checked="true" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked"><i class="van-icon van-icon-success" style="border-color: #07c160; background-color: rgb(7, 193, 96);">
|
||||
<!----></i></div><span class="van-checkbox__label">
|
||||
自定义颜色
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div role="checkbox" tabindex="0" aria-checked="true" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked"><img src="https://img.yzcdn.cn/vant/user-active.png"></div><span class="van-checkbox__label">
|
||||
自定义图标
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-checkbox-group">
|
||||
<div role="checkbox" tabindex="0" aria-checked="true" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked"><i class="van-icon van-icon-success">
|
||||
<!----></i></div><span class="van-checkbox__label">
|
||||
复选框 a
|
||||
</span>
|
||||
</div>
|
||||
<div role="checkbox" tabindex="0" aria-checked="true" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round van-checkbox__icon--checked"><i class="van-icon van-icon-success">
|
||||
<!----></i></div><span class="van-checkbox__label">
|
||||
复选框 b
|
||||
</span>
|
||||
</div>
|
||||
<div role="checkbox" tabindex="0" aria-checked="false" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round"><i class="van-icon van-icon-success">
|
||||
<!----></i></div><span class="van-checkbox__label">
|
||||
复选框 c
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-checkbox-group">
|
||||
<div role="checkbox" tabindex="0" aria-checked="false" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round"><i class="van-icon van-icon-success">
|
||||
<!----></i></div><span class="van-checkbox__label">
|
||||
复选框 a
|
||||
</span>
|
||||
</div>
|
||||
<div role="checkbox" tabindex="0" aria-checked="false" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round"><i class="van-icon van-icon-success">
|
||||
<!----></i></div><span class="van-checkbox__label">
|
||||
复选框 b
|
||||
</span>
|
||||
</div>
|
||||
<div role="checkbox" tabindex="0" aria-checked="false" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round"><i class="van-icon van-icon-success">
|
||||
<!----></i></div><span class="van-checkbox__label">
|
||||
复选框 c
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-checkbox-group">
|
||||
<div class="van-cell-group van-hairline--top-bottom">
|
||||
<div class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>复选框a</span></div>
|
||||
<div role="checkbox" tabindex="0" aria-checked="false" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round"><i class="van-icon van-icon-success">
|
||||
<!----></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>复选框b</span></div>
|
||||
<div role="checkbox" tabindex="0" aria-checked="false" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round"><i class="van-icon van-icon-success">
|
||||
<!----></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-cell van-cell--clickable">
|
||||
<div class="van-cell__title"><span>复选框c</span></div>
|
||||
<div role="checkbox" tabindex="0" aria-checked="false" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round"><i class="van-icon van-icon-success">
|
||||
<!----></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,15 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`label disabled 1`] = `
|
||||
<div role="checkbox" tabindex="0" aria-checked="undefined" class="van-checkbox">
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round"><i class="van-icon van-icon-success">
|
||||
<!----></i></div><span class="van-checkbox__label">Label</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`label-position prop 1`] = `
|
||||
<div role="checkbox" tabindex="0" aria-checked="undefined" class="van-checkbox"><span class="van-checkbox__label van-checkbox__label--left">Label</span>
|
||||
<div class="van-checkbox__icon van-checkbox__icon--round"><i class="van-icon van-icon-success">
|
||||
<!----></i></div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,107 @@
|
||||
import Checkbox from '..';
|
||||
import CheckboxGroup from '../../checkbox-group';
|
||||
import { mount, later } from '../../../test/utils';
|
||||
|
||||
test('switch checkbox', async () => {
|
||||
const wrapper = mount(Checkbox);
|
||||
|
||||
wrapper.vm.$on('input', value => {
|
||||
wrapper.setData({ value });
|
||||
});
|
||||
|
||||
const icon = wrapper.find('.van-checkbox__icon');
|
||||
icon.trigger('click');
|
||||
await later();
|
||||
icon.trigger('click');
|
||||
await later();
|
||||
expect(wrapper.emitted('input')).toEqual([[true], [false]]);
|
||||
expect(wrapper.emitted('change')).toEqual([[true], [false]]);
|
||||
});
|
||||
|
||||
test('disabled', () => {
|
||||
const wrapper = mount(Checkbox, {
|
||||
propsData: {
|
||||
disabled: true
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.find('.van-checkbox__icon').trigger('click');
|
||||
expect(wrapper.emitted('input')).toBeFalsy();
|
||||
});
|
||||
|
||||
test('label disabled', () => {
|
||||
const wrapper = mount(Checkbox, {
|
||||
scopedSlots: {
|
||||
default: () => 'Label'
|
||||
},
|
||||
propsData: {
|
||||
labelDisabled: true
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.find('.van-checkbox__label').trigger('click');
|
||||
expect(wrapper.emitted('input')).toBeFalsy();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('checkbox group', async () => {
|
||||
const wrapper = mount({
|
||||
template: `
|
||||
<checkbox-group v-model="result" :max="2">
|
||||
<checkbox v-for="item in list" :key="item" :name="item"></checkbox>
|
||||
</checkbox-group>
|
||||
`,
|
||||
components: {
|
||||
Checkbox,
|
||||
CheckboxGroup
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
result: [],
|
||||
list: ['a', 'b', 'c']
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const icons = wrapper.findAll('.van-checkbox__icon');
|
||||
icons.at(0).trigger('click');
|
||||
await later();
|
||||
icons.at(1).trigger('click');
|
||||
await later();
|
||||
icons.at(2).trigger('click');
|
||||
expect(wrapper.vm.result).toEqual(['a', 'b']);
|
||||
|
||||
await later();
|
||||
icons.at(0).trigger('click');
|
||||
await later();
|
||||
expect(wrapper.vm.result).toEqual(['b']);
|
||||
});
|
||||
|
||||
test('click event', () => {
|
||||
const onClick = jest.fn();
|
||||
const wrapper = mount(Checkbox, {
|
||||
listeners: {
|
||||
click: onClick
|
||||
}
|
||||
});
|
||||
|
||||
wrapper.trigger('click');
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
|
||||
const icon = wrapper.find('.van-checkbox__icon');
|
||||
icon.trigger('click');
|
||||
expect(onClick).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
test('label-position prop', () => {
|
||||
const wrapper = mount(Checkbox, {
|
||||
scopedSlots: {
|
||||
default: () => 'Label'
|
||||
},
|
||||
propsData: {
|
||||
labelPosition: 'left'
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
# Checkbox 复选框
|
||||
|
||||
### 引入
|
||||
``` javascript
|
||||
import { Checkbox, CheckboxGroup } from 'vant';
|
||||
|
||||
Vue.use(Checkbox).use(CheckboxGroup);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
通过`v-model`绑定 checkbox 的勾选状态
|
||||
|
||||
```html
|
||||
<van-checkbox v-model="checked">复选框</van-checkbox>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
checked: true
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 禁用状态
|
||||
|
||||
```html
|
||||
<van-checkbox v-model="checked" disabled>复选框</van-checkbox>
|
||||
```
|
||||
|
||||
### 自定义颜色
|
||||
|
||||
```html
|
||||
<van-checkbox v-model="checked" checked-color="#07c160">复选框</van-checkbox>
|
||||
```
|
||||
|
||||
### 自定义图标
|
||||
|
||||
通过 icon 插槽自定义图标,可以通过 `slot-scope` 判断是否为选中状态
|
||||
|
||||
```html
|
||||
<van-checkbox v-model="checked">
|
||||
自定义图标
|
||||
<img
|
||||
slot="icon"
|
||||
slot-scope="props"
|
||||
:src="props.checked ? icon.active : icon.inactive"
|
||||
>
|
||||
</van-checkbox>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
data() {
|
||||
checked: true,
|
||||
icon: {
|
||||
active: 'https://img.yzcdn.cn/vant/user-active.png',
|
||||
inactive: 'https://img.yzcdn.cn/vant/user-inactive.png'
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 复选框组
|
||||
|
||||
与`van-checkbox-group`一起使用,选中值是一个数组,通过`v-model`绑定在`van-checkbox-group`上,数组中的项即为选中的`Checkbox`的`name`属性设置的值
|
||||
|
||||
```html
|
||||
<van-checkbox-group v-model="result">
|
||||
<van-checkbox
|
||||
v-for="(item, index) in list"
|
||||
:key="item"
|
||||
:name="item"
|
||||
>
|
||||
复选框 {{ item }}
|
||||
</van-checkbox>
|
||||
</van-checkbox-group>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: ['a', 'b', 'c'],
|
||||
result: ['a', 'b']
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 设置最大可选数
|
||||
|
||||
```html
|
||||
<van-checkbox-group v-model="result" :max="2">
|
||||
<van-checkbox
|
||||
v-for="(item, index) in list"
|
||||
:key="item"
|
||||
:name="item"
|
||||
>
|
||||
复选框 {{ item }}
|
||||
</van-checkbox>
|
||||
</van-checkbox-group>
|
||||
```
|
||||
|
||||
### 搭配单元格组件使用
|
||||
|
||||
此时你需要再引入`Cell`和`CellGroup`组件,并通过 checkbox 的 toggle 方法手动触发切换
|
||||
|
||||
```html
|
||||
<van-checkbox-group v-model="result">
|
||||
<van-cell-group>
|
||||
<van-cell
|
||||
v-for="(item, index) in list"
|
||||
clickable
|
||||
:key="item"
|
||||
:title="`复选框 ${item}`"
|
||||
@click="toggle(index)"
|
||||
>
|
||||
<van-checkbox
|
||||
:name="item"
|
||||
ref="checkboxes"
|
||||
slot="right-icon"
|
||||
/>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</van-checkbox-group>
|
||||
```
|
||||
|
||||
```js
|
||||
export default {
|
||||
methods: {
|
||||
toggle(index) {
|
||||
this.$refs.checkboxes[index].toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Checkbox Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| name | 标识符 | `any` | - | - |
|
||||
| shape | 形状,可选值为 `square` | `String` | `round` | - |
|
||||
| v-model | 是否为选中状态 | `Boolean` | `false` | - |
|
||||
| disabled | 是否禁用复选框 | `Boolean` | `false` | - |
|
||||
| icon-size | 图标大小,默认单位为`px` | `String | Number` | `20px` | 2.0.0 |
|
||||
| label-disabled | 是否禁用复选框文本点击 | `Boolean` | `false` | - |
|
||||
| label-position | 文本位置,可选值为 `left` | `String` | `right` | 1.1.11 |
|
||||
| checked-color | 选中状态颜色 | `String` | `#1989fa` | 1.4.3 |
|
||||
|
||||
### CheckboxGroup Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| v-model | 所有选中项的标识符 | `Array` | - | - |
|
||||
| disabled | 是否禁用所有复选框 | `Boolean` | `false` | - |
|
||||
| max | 设置最大可选数,0 为无限制 | `Number` | `0` | - |
|
||||
|
||||
### Checkbox Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|------|------|------|
|
||||
| change | 当绑定值变化时触发的事件 | 当前组件的值 |
|
||||
| click | 点击复选框时触发 | event: Event |
|
||||
|
||||
### CheckboxGroup Events
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|------|------|------|
|
||||
| change | 当绑定值变化时触发的事件 | 当前组件的值 |
|
||||
|
||||
### Checkbox Slots
|
||||
|
||||
| 名称 | 说明 | slot-scope |
|
||||
|------|------|------|
|
||||
| default | 自定义文本 | - |
|
||||
| icon | 自定义图标 | checked: 是否为选中状态 |
|
||||
|
||||
### Checkbox 方法
|
||||
|
||||
通过 ref 可以获取到 checkbox 实例并调用实例方法
|
||||
|
||||
| 方法名 | 参数 | 返回值 | 介绍 |
|
||||
|------|------|------|------|
|
||||
| toggle | - | - | 切换选中状态 |
|
||||
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-circle
|
||||
v-model="currentRate1"
|
||||
:rate="rate"
|
||||
:speed="100"
|
||||
size="120px"
|
||||
:text="currentRate1.toFixed(0) + '%'"
|
||||
/>
|
||||
<van-circle
|
||||
v-model="currentRate2"
|
||||
color="#07c160"
|
||||
fill="#fff"
|
||||
:rate="rate"
|
||||
size="120px"
|
||||
layer-color="#ebedf0"
|
||||
:speed="100"
|
||||
:stroke-width="60"
|
||||
:clockwise="false"
|
||||
:text="currentRate2.toFixed(0) + '%'"
|
||||
/>
|
||||
<div>
|
||||
<van-button
|
||||
:text="$t('add')"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="add"
|
||||
/>
|
||||
<van-button
|
||||
:text="$t('decrease')"
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="reduce"
|
||||
/>
|
||||
</div>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const format = rate => Math.min(Math.max(rate, 0), 100);
|
||||
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
title2: '样式定制'
|
||||
},
|
||||
'en-US': {
|
||||
title2: 'Custom Style'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
rate: 30,
|
||||
currentRate1: 0,
|
||||
currentRate2: 0
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
add() {
|
||||
this.rate = format(this.rate + 20);
|
||||
},
|
||||
|
||||
reduce() {
|
||||
this.rate = format(this.rate - 20);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.demo-circle {
|
||||
.van-circle {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.van-button {
|
||||
margin: 15px 0 0 10px;
|
||||
|
||||
&:first-of-type {
|
||||
margin-left: 15px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,76 @@
|
||||
# Circle
|
||||
|
||||
### Install
|
||||
``` javascript
|
||||
import { Circle } from 'vant';
|
||||
|
||||
Vue.use(Circle);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
:rate="30"
|
||||
:speed="100"
|
||||
:text="text"
|
||||
/>
|
||||
```
|
||||
|
||||
``` javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentRate: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
text() {
|
||||
return this.currentRate.toFixed(0) + '%'
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Custom style
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
color="#07c160"
|
||||
fill="#fff"
|
||||
size="120px"
|
||||
layer-color="#ebedf0"
|
||||
:text="text"
|
||||
:rate="rate"
|
||||
:speed="100"
|
||||
:clockwise="false"
|
||||
:stroke-width="60"
|
||||
/>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| v-model | Current rate | `Number` | - |
|
||||
| rate | Target rate | `Number` | `100` |
|
||||
| size | Circle size | `String` | `100px` |
|
||||
| color | Progress bar color | `String` | `#1989fa` |
|
||||
| layer-color | Layer color | `String` | `#fff` |
|
||||
| fill | Fill color | `String` | `none` |
|
||||
| speed | Animate speed(rate/s)| `Number` | - |
|
||||
| text | Text | `String` | - |
|
||||
| stroke-width | Stroke width | `Number` | `40` |
|
||||
| clockwise | Is clockwise | `Boolean` | `true` |
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description |
|
||||
|------|------|
|
||||
| default | custom text content |
|
||||
@@ -0,0 +1,117 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { raf, cancelRaf } from '../utils/dom/raf';
|
||||
import { BLUE, WHITE } from '../utils/color';
|
||||
|
||||
const [createComponent, bem] = createNamespace('circle');
|
||||
const PERIMETER = 3140;
|
||||
const PATH = 'M 530 530 m -500, 0 a 500, 500 0 1, 1 1000, 0 a 500, 500 0 1, 1 -1000, 0';
|
||||
|
||||
function format(rate) {
|
||||
return Math.min(Math.max(rate, 0), 100);
|
||||
}
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
text: String,
|
||||
value: Number,
|
||||
speed: Number,
|
||||
size: {
|
||||
type: String,
|
||||
default: '100px'
|
||||
},
|
||||
fill: {
|
||||
type: String,
|
||||
default: 'none'
|
||||
},
|
||||
rate: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
layerColor: {
|
||||
type: String,
|
||||
default: WHITE
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: BLUE
|
||||
},
|
||||
strokeWidth: {
|
||||
type: Number,
|
||||
default: 40
|
||||
},
|
||||
clockwise: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
style() {
|
||||
return {
|
||||
width: this.size,
|
||||
height: this.size
|
||||
};
|
||||
},
|
||||
|
||||
layerStyle() {
|
||||
let offset = (PERIMETER * (100 - this.value)) / 100;
|
||||
offset = this.clockwise ? offset : PERIMETER * 2 - offset;
|
||||
return {
|
||||
stroke: `${this.color}`,
|
||||
strokeDashoffset: `${offset}px`,
|
||||
strokeWidth: `${this.strokeWidth + 1}px`
|
||||
};
|
||||
},
|
||||
|
||||
hoverStyle() {
|
||||
return {
|
||||
fill: `${this.fill}`,
|
||||
stroke: `${this.layerColor}`,
|
||||
strokeWidth: `${this.strokeWidth}px`
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
watch: {
|
||||
rate: {
|
||||
handler() {
|
||||
this.startTime = Date.now();
|
||||
this.startRate = this.value;
|
||||
this.endRate = format(this.rate);
|
||||
this.increase = this.endRate > this.startRate;
|
||||
this.duration = Math.abs(((this.startRate - this.endRate) * 1000) / this.speed);
|
||||
if (this.speed) {
|
||||
cancelRaf(this.rafId);
|
||||
this.rafId = raf(this.animate);
|
||||
} else {
|
||||
this.$emit('input', this.endRate);
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
animate() {
|
||||
const now = Date.now();
|
||||
const progress = Math.min((now - this.startTime) / this.duration, 1);
|
||||
const rate = progress * (this.endRate - this.startRate) + this.startRate;
|
||||
this.$emit('input', format(parseFloat(rate.toFixed(1))));
|
||||
if (this.increase ? rate < this.endRate : rate > this.endRate) {
|
||||
this.rafId = raf(this.animate);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
return (
|
||||
<div class={bem()} style={this.style}>
|
||||
<svg viewBox="0 0 1060 1060">
|
||||
<path class={bem('hover')} style={this.hoverStyle} d={PATH} />
|
||||
<path class={bem('layer')} style={this.layerStyle} d={PATH} />
|
||||
</svg>
|
||||
{this.slots() || (this.text && <div class={bem('text')}>{this.text}</div>)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-circle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
text-align: center;
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&__layer {
|
||||
transform: rotate(90deg);
|
||||
// should not use transform-origin: center
|
||||
// that will cause incorrect style in android devices
|
||||
transform-origin: 530px 530px;
|
||||
fill: none;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 3140;
|
||||
stroke-dashoffset: 3140;
|
||||
}
|
||||
|
||||
&__text {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
color: @circle-text-color;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-circle" style="width: 120px; height: 120px;"><svg viewBox="0 0 1060 1060">
|
||||
<path d="M 530 530 m -500, 0 a 500, 500 0 1, 1 1000, 0 a 500, 500 0 1, 1 -1000, 0" class="van-circle__hover" style="fill: none; stroke: #fff; stroke-width: 40px;"></path>
|
||||
<path d="M 530 530 m -500, 0 a 500, 500 0 1, 1 1000, 0 a 500, 500 0 1, 1 -1000, 0" class="van-circle__layer" style="stroke: #1989fa; stroke-dashoffset: 3140px; stroke-width: 41px;"></path>
|
||||
</svg>
|
||||
<div class="van-circle__text">0%</div>
|
||||
</div>
|
||||
<div class="van-circle" style="width: 120px; height: 120px;"><svg viewBox="0 0 1060 1060">
|
||||
<path d="M 530 530 m -500, 0 a 500, 500 0 1, 1 1000, 0 a 500, 500 0 1, 1 -1000, 0" class="van-circle__hover" style="fill: #fff; stroke: #ebedf0; stroke-width: 60px;"></path>
|
||||
<path d="M 530 530 m -500, 0 a 500, 500 0 1, 1 1000, 0 a 500, 500 0 1, 1 -1000, 0" class="van-circle__layer" style="stroke: #07c160; stroke-dashoffset: 3140px; stroke-width: 61px;"></path>
|
||||
</svg>
|
||||
<div class="van-circle__text">0%</div>
|
||||
</div>
|
||||
<div><button class="van-button van-button--primary van-button--small"><span class="van-button__text">增加</span></button> <button class="van-button van-button--danger van-button--small"><span class="van-button__text">减少</span></button></div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,77 @@
|
||||
# Circle 环形进度条
|
||||
|
||||
### 引入
|
||||
``` javascript
|
||||
import { Circle } from 'vant';
|
||||
|
||||
Vue.use(Circle);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
通过 `rate` 指定目标进度,`v-model` 代表当前进度,`speed` 控制动画速度
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
:rate="30"
|
||||
:speed="100"
|
||||
:text="text"
|
||||
/>
|
||||
```
|
||||
|
||||
``` javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
currentRate: 0
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
text() {
|
||||
return this.currentRate.toFixed(0) + '%'
|
||||
}
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 样式定制
|
||||
|
||||
```html
|
||||
<van-circle
|
||||
v-model="currentRate"
|
||||
color="#07c160"
|
||||
fill="#fff"
|
||||
size="120px"
|
||||
layer-color="#ebedf0"
|
||||
:text="text"
|
||||
:rate="rate"
|
||||
:speed="100"
|
||||
:clockwise="false"
|
||||
:stroke-width="60"
|
||||
/>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| v-model | 当前进度 | `Number` | - | - |
|
||||
| rate | 目标进度 | `Number` | `100` | - |
|
||||
| size | 圆环直径 | `String` | `100px` | - |
|
||||
| color | 进度条颜色 | `String` | `#1989fa` | - |
|
||||
| layer-color | 轨道颜色 | `String` | `#fff` | - |
|
||||
| fill | 填充颜色 | `String` | `none` | - |
|
||||
| speed | 动画速度(单位为 rate/s)| `Number` | - | - |
|
||||
| text | 文字 | `String` | - | - |
|
||||
| stroke-width | 进度条宽度 | `Number` | `40` | - |
|
||||
| clockwise | 是否顺时针增加 | `Boolean` | `true` | - |
|
||||
|
||||
### Slots
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| default | 自定义文字内容 |
|
||||
@@ -0,0 +1,131 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-row>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row>
|
||||
<van-col span="4">span: 4</van-col>
|
||||
<van-col
|
||||
span="10"
|
||||
offset="4"
|
||||
>
|
||||
offset: 4, span: 10
|
||||
</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row>
|
||||
<van-col
|
||||
offset="12"
|
||||
span="12"
|
||||
>
|
||||
offset: 12, span: 12
|
||||
</van-col>
|
||||
</van-row>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title2')">
|
||||
<van-row gutter="20">
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
</van-row>
|
||||
</demo-block>
|
||||
|
||||
<demo-block
|
||||
v-if="!$attrs.weapp"
|
||||
:title="$t('title3')"
|
||||
>
|
||||
<van-row type="flex">
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row
|
||||
type="flex"
|
||||
justify="center"
|
||||
>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row
|
||||
type="flex"
|
||||
justify="end"
|
||||
>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row
|
||||
type="flex"
|
||||
justify="space-between"
|
||||
>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row
|
||||
type="flex"
|
||||
justify="space-around"
|
||||
>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
title2: '在列元素之间增加间距',
|
||||
title3: 'Flex 布局'
|
||||
},
|
||||
'en-US': {
|
||||
title2: 'Column Spacing',
|
||||
title3: 'Flex Layout'
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '../../style/var';
|
||||
|
||||
.demo-col {
|
||||
.van-doc-demo-block {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.van-doc-demo-block__title {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.van-col {
|
||||
margin-bottom: 10px;
|
||||
color: @white;
|
||||
font-size: 13px;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
background-clip: content-box;
|
||||
|
||||
&:nth-child(odd) {
|
||||
background-color: #39a9ed;
|
||||
}
|
||||
|
||||
&:nth-child(even) {
|
||||
background-color: #66c6f2;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,107 @@
|
||||
# Layout
|
||||
|
||||
### Intro
|
||||
|
||||
Quickly and easily create layouts with `van-row` and `van-col`
|
||||
|
||||
### Install
|
||||
|
||||
``` javascript
|
||||
import { Row, Col } from 'vant';
|
||||
|
||||
Vue.use(Row).use(Col);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Layout are based on 24-column. The attribute `span` in `Col` means the number of column the grid spans. Of course, You can use `offset` attribute to set number of spacing on the left side of the grid.
|
||||
|
||||
```html
|
||||
<van-row>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row>
|
||||
<van-col span="4">span: 4</van-col>
|
||||
<van-col span="10" offset="4">offset: 4, span: 10</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row>
|
||||
<van-col offset="12" span="12">offset: 12, span: 12</van-col>
|
||||
</van-row>
|
||||
```
|
||||
|
||||
|
||||
### Column Spacing
|
||||
|
||||
Set grid spacing using `gutter` attribute. The default value is 0
|
||||
|
||||
|
||||
```html
|
||||
<van-row gutter="20">
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
</van-row>
|
||||
```
|
||||
|
||||
### Flex Layout
|
||||
|
||||
Setting `type` to `flex` to enable flex layout
|
||||
|
||||
```html
|
||||
<van-row type="flex">
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row type="flex" justify="center">
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row type="flex" justify="end">
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row type="flex" justify="space-between">
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row type="flex" justify="space-around">
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Row Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| type | Layout type, can be set to `flex` | `String` | - |
|
||||
| gutter | Grid spacing(px) | `String | Number` | - |
|
||||
| tag | Custom element tag | `String` | `div` |
|
||||
| justify | Flex main axis,can be set to end/center/space-around/space-between | `String` | `start` |
|
||||
| align | Flex cross axis, be set to center/bottom | `String` | `top` |
|
||||
|
||||
### Col Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| span | Number of column the grid spans | `String | Number` | - |
|
||||
| offset | Number of spacing on the left side of the grid | `String | Number` | - |
|
||||
| tag | Custom element tag | `String` | `div` |
|
||||
@@ -0,0 +1,34 @@
|
||||
import { createNamespace } from '../utils';
|
||||
|
||||
const [createComponent, bem] = createNamespace('col');
|
||||
|
||||
export default createComponent({
|
||||
props: {
|
||||
span: [Number, String],
|
||||
offset: [Number, String],
|
||||
tag: {
|
||||
type: String,
|
||||
default: 'div'
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
gutter() {
|
||||
return (this.$parent && Number(this.$parent.gutter)) || 0;
|
||||
},
|
||||
|
||||
style() {
|
||||
const padding = `${this.gutter / 2}px`;
|
||||
return this.gutter ? { paddingLeft: padding, paddingRight: padding } : {};
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
const { span, offset } = this;
|
||||
return (
|
||||
<this.tag class={bem({ [span]: span, [`offset-${offset}`]: offset })} style={this.style}>
|
||||
{this.slots()}
|
||||
</this.tag>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,14 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-col {
|
||||
float: left;
|
||||
box-sizing: border-box;
|
||||
min-height: 1px;
|
||||
}
|
||||
|
||||
.generate-col(24);
|
||||
.generate-col(@n, @i: 1) when (@i =< @n) {
|
||||
.van-col--@{i} { width: @i * 100% / 24; }
|
||||
.van-col--offset-@{i} { margin-left: @i * 100% / 24; }
|
||||
.generate-col(@n, (@i + 1));
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-row">
|
||||
<div class="van-col van-col--8">span: 8</div>
|
||||
<div class="van-col van-col--8">span: 8</div>
|
||||
<div class="van-col van-col--8">span: 8</div>
|
||||
</div>
|
||||
<div class="van-row">
|
||||
<div class="van-col van-col--4">span: 4</div>
|
||||
<div class="van-col van-col--10 van-col--offset-4">
|
||||
offset: 4, span: 10
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-row">
|
||||
<div class="van-col van-col--12 van-col--offset-12">
|
||||
offset: 12, span: 12
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-row" style="margin-left: -10px; margin-right: -10px;">
|
||||
<div class="van-col van-col--8" style="padding-left: 10px; padding-right: 10px;">span: 8</div>
|
||||
<div class="van-col van-col--8" style="padding-left: 10px; padding-right: 10px;">span: 8</div>
|
||||
<div class="van-col van-col--8" style="padding-left: 10px; padding-right: 10px;">span: 8</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-row van-row--flex">
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
</div>
|
||||
<div class="van-row van-row--flex van-row--justify-center">
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
</div>
|
||||
<div class="van-row van-row--flex van-row--justify-end">
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
</div>
|
||||
<div class="van-row van-row--flex van-row--justify-space-between">
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
</div>
|
||||
<div class="van-row van-row--flex van-row--justify-space-around">
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
<div class="van-col van-col--6">span: 6</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,111 @@
|
||||
# Layout 布局
|
||||
|
||||
### 介绍
|
||||
|
||||
Layout 提供了`van-row`和`van-col`两个组件来进行行列布局
|
||||
|
||||
|
||||
### 引入
|
||||
|
||||
``` javascript
|
||||
import { Row, Col } from 'vant';
|
||||
|
||||
Vue.use(Row).use(Col);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基本用法
|
||||
|
||||
Layout 组件提供了`24列栅格`,通过在`Col`上添加`span`属性设置列所占的宽度百分比
|
||||
此外,添加`offset`属性可以设置列的偏移宽度,计算方式与 span 相同
|
||||
|
||||
```html
|
||||
<van-row>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row>
|
||||
<van-col span="4">span: 4</van-col>
|
||||
<van-col span="10" offset="4">offset: 4, span: 10</van-col>
|
||||
</van-row>
|
||||
|
||||
<van-row>
|
||||
<van-col offset="12" span="12">offset: 12, span: 12</van-col>
|
||||
</van-row>
|
||||
```
|
||||
|
||||
### 设置列元素间距
|
||||
|
||||
通过`gutter`属性可以设置列元素之间的间距,默认间距为 0
|
||||
|
||||
```html
|
||||
<van-row gutter="20">
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
<van-col span="8">span: 8</van-col>
|
||||
</van-row>
|
||||
```
|
||||
|
||||
### Flex 布局
|
||||
|
||||
将 `type` 属性设置为 flex 可以启用 flex 布局,便于进行灵活的对齐
|
||||
|
||||
```html
|
||||
<!-- 左对齐 -->
|
||||
<van-row type="flex">
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<!-- 居中 -->
|
||||
<van-row type="flex" justify="center">
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<!-- 右对齐 -->
|
||||
<van-row type="flex" justify="end">
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<!-- 两端对齐 -->
|
||||
<van-row type="flex" justify="space-between">
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
|
||||
<!-- 每个元素的两侧间隔相等 -->
|
||||
<van-row type="flex" justify="space-around">
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
<van-col span="6">span: 6</van-col>
|
||||
</van-row>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Row Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| type | 布局方式,可选值为`flex` | `String` | - | 1.1.9 |
|
||||
| gutter | 列元素之间的间距(单位为px) | `String | Number` | - | - |
|
||||
| tag | 自定义元素标签 | `String` | `div` | - |
|
||||
| justify | Flex 主轴对齐方式,可选值为 `end` `center` <br> `space-around` `space-between` | `String` | `start` | 1.1.9 |
|
||||
| align | Flex 交叉轴对齐方式,可选值为 `center` `bottom` | `String` | `top` | 1.1.9 |
|
||||
|
||||
### Col Props
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| span | 列元素宽度 | `String | Number` | - | - |
|
||||
| offset | 列元素偏移距离 | `String | Number` | - | - |
|
||||
| tag | 自定义元素标签 | `String` | `div` | - |
|
||||
@@ -0,0 +1,150 @@
|
||||
import { createNamespace, isDef } from '../utils';
|
||||
import { raf } from '../utils/dom/raf';
|
||||
import Cell from '../cell';
|
||||
import { cellProps } from '../cell/shared';
|
||||
import { ChildrenMixin } from '../mixins/relation';
|
||||
|
||||
const [createComponent, bem] = createNamespace('collapse-item');
|
||||
const CELL_SLOTS = ['title', 'icon', 'right-icon'];
|
||||
|
||||
export default createComponent({
|
||||
mixins: [ChildrenMixin('vanCollapse')],
|
||||
|
||||
props: {
|
||||
...cellProps,
|
||||
name: [String, Number],
|
||||
disabled: Boolean,
|
||||
isLink: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
show: null,
|
||||
inited: null
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentName() {
|
||||
return isDef(this.name) ? this.name : this.index;
|
||||
},
|
||||
|
||||
expanded() {
|
||||
if (!this.parent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { value } = this.parent;
|
||||
return this.parent.accordion
|
||||
? value === this.currentName
|
||||
: value.some(name => name === this.currentName);
|
||||
}
|
||||
},
|
||||
|
||||
created() {
|
||||
this.show = this.expanded;
|
||||
this.inited = this.expanded;
|
||||
},
|
||||
|
||||
watch: {
|
||||
expanded(expanded, prev) {
|
||||
if (prev === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (expanded) {
|
||||
this.show = true;
|
||||
this.inited = true;
|
||||
}
|
||||
|
||||
raf(() => {
|
||||
const { content, wrapper } = this.$refs;
|
||||
if (!content || !wrapper) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { clientHeight } = content;
|
||||
if (clientHeight) {
|
||||
const contentHeight = `${clientHeight}px`;
|
||||
wrapper.style.height = expanded ? 0 : contentHeight;
|
||||
raf(() => {
|
||||
wrapper.style.height = expanded ? contentHeight : 0;
|
||||
});
|
||||
} else {
|
||||
this.onTransitionEnd();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
onClick() {
|
||||
if (this.disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { parent } = this;
|
||||
const name =
|
||||
parent.accordion && this.currentName === parent.value ? '' : this.currentName;
|
||||
this.parent.switch(name, !this.expanded);
|
||||
},
|
||||
|
||||
onTransitionEnd() {
|
||||
if (!this.expanded) {
|
||||
this.show = false;
|
||||
} else {
|
||||
this.$refs.wrapper.style.height = null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
const { disabled, expanded } = this;
|
||||
|
||||
const titleSlots = CELL_SLOTS.reduce((slots, name) => {
|
||||
if (this.slots(name)) {
|
||||
slots[name] = () => this.slots(name);
|
||||
}
|
||||
return slots;
|
||||
}, {});
|
||||
|
||||
if (this.slots('value')) {
|
||||
titleSlots.default = () => this.slots('value');
|
||||
}
|
||||
|
||||
const Title = (
|
||||
<Cell
|
||||
role="button"
|
||||
class={bem('title', { disabled, expanded })}
|
||||
onClick={this.onClick}
|
||||
scopedSlots={titleSlots}
|
||||
tabindex={disabled ? -1 : 0}
|
||||
aria-expanded={String(expanded)}
|
||||
{...{ props: this.$props }}
|
||||
/>
|
||||
);
|
||||
|
||||
const Content = this.inited && (
|
||||
<div
|
||||
vShow={this.show}
|
||||
ref="wrapper"
|
||||
class={bem('wrapper')}
|
||||
onTransitionend={this.onTransitionEnd}
|
||||
>
|
||||
<div ref="content" class={bem('content')}>
|
||||
{this.slots()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div class={[bem(), { 'van-hairline--top': this.index }]}>
|
||||
{Title}
|
||||
{Content}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-collapse-item {
|
||||
&__title {
|
||||
.van-cell__right-icon::before {
|
||||
transform: rotate(90deg);
|
||||
transition: @collapse-item-transition-duration;
|
||||
}
|
||||
|
||||
&::after {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
&--expanded {
|
||||
.van-cell__right-icon::before {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
&::after {
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
&,
|
||||
& .van-cell__right-icon {
|
||||
color: @collapse-item-title-disabled-color;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__wrapper {
|
||||
overflow: hidden;
|
||||
transition: height @collapse-item-transition-duration ease-in-out;
|
||||
will-change: height;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: @collapse-item-content-padding;
|
||||
color: @collapse-item-content-text-color;
|
||||
font-size: @collapse-item-content-font-size;
|
||||
line-height: @collapse-item-content-line-height;
|
||||
background-color: @collapse-item-content-background-color;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-collapse v-model="active1">
|
||||
<van-collapse-item :title="$t('title') + 1">{{ $t('text') }}</van-collapse-item>
|
||||
<van-collapse-item :title="$t('title') + 2">{{ $t('text') }}</van-collapse-item>
|
||||
<van-collapse-item
|
||||
:title="$t('title') + 3"
|
||||
disabled
|
||||
>
|
||||
{{ $t('text') }}
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('accordion')">
|
||||
<van-collapse
|
||||
v-model="active2"
|
||||
accordion
|
||||
>
|
||||
<van-collapse-item :title="$t('title') + 1">{{ $t('text') }}</van-collapse-item>
|
||||
<van-collapse-item :title="$t('title') + 2">{{ $t('text') }}</van-collapse-item>
|
||||
<van-collapse-item :title="$t('title') + 3">{{ $t('text') }}</van-collapse-item>
|
||||
</van-collapse>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('titleSlot')">
|
||||
<van-collapse v-model="active3">
|
||||
<van-collapse-item>
|
||||
<template #title>
|
||||
{{ $t('title') + 1 }}<van-icon name="question-o" />
|
||||
</template>
|
||||
{{ $t('text') }}
|
||||
</van-collapse-item>
|
||||
<van-collapse-item
|
||||
:title="$t('title') + 2"
|
||||
:value="$t('content')"
|
||||
icon="shop-o"
|
||||
>
|
||||
{{ $t('text') }}
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
accordion: '手风琴',
|
||||
titleSlot: '自定义标题内容',
|
||||
text: '代码是写出来给人看的,附带能在机器上运行'
|
||||
},
|
||||
'en-US': {
|
||||
accordion: 'Accordion',
|
||||
titleSlot: 'Custom title',
|
||||
text: 'Content'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
active1: [0],
|
||||
active2: 0,
|
||||
active3: []
|
||||
};
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
@import '../../style/var';
|
||||
|
||||
.demo-collapse {
|
||||
.van-icon-question-o {
|
||||
margin-left: 5px;
|
||||
color: @blue;
|
||||
font-size: 15px;
|
||||
vertical-align: -3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,115 @@
|
||||
# Collapse
|
||||
|
||||
### Install
|
||||
``` javascript
|
||||
import { Collapse, CollapseItem } from 'vant';
|
||||
|
||||
Vue.use(Collapse).use(CollapseItem);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
Use `v-model` to control the name of active panels
|
||||
|
||||
```html
|
||||
<van-collapse v-model="activeNames">
|
||||
<van-collapse-item title="Title1" name="1">Content</van-collapse-item>
|
||||
<van-collapse-item title="Title2" name="2">Content</van-collapse-item>
|
||||
<van-collapse-item title="Title3" name="3" disabled>Content</van-collapse-item>
|
||||
</van-collapse>
|
||||
```
|
||||
|
||||
``` javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeNames: ['1']
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Accordion
|
||||
|
||||
In accordion mode, only one panel can be expanded at the same time.
|
||||
|
||||
```html
|
||||
<van-collapse v-model="activeName" accordion>
|
||||
<van-collapse-item title="Title1" name="1">Content</van-collapse-item>
|
||||
<van-collapse-item title="Title2" name="2">Content</van-collapse-item>
|
||||
<van-collapse-item title="Title3" name="3">Content</van-collapse-item>
|
||||
</van-collapse>
|
||||
```
|
||||
|
||||
``` javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeName: '1'
|
||||
};
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Custom title
|
||||
|
||||
```html
|
||||
<van-collapse v-model="activeNames">
|
||||
<van-collapse-item name="1">
|
||||
<div slot="title">Title1 <van-icon name="question-o" /></div>
|
||||
Content
|
||||
</van-collapse-item>
|
||||
<van-collapse-item
|
||||
title="Title2"
|
||||
name="2"
|
||||
icon="shop-o"
|
||||
>
|
||||
Content
|
||||
</van-collapse-item>
|
||||
</van-collapse>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Collapse Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| v-model | names of current active panels | `Array | String | Number` | - |
|
||||
| accordion | Whether to be accordion mode | `Boolean` | `false` |
|
||||
| border | Whether to show outer border | `Boolean` | `true` |
|
||||
|
||||
### Collapse Events
|
||||
|
||||
| Event | Description | Arguments |
|
||||
|------|------|------|
|
||||
| change | Triggered when switch panel | activeNames: `string | array` |
|
||||
|
||||
### CollapseItem Props
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| name | Name | `String | Number` | `index` |
|
||||
| icon | Left Icon | `String` | - |
|
||||
| size | Title size,can be set to `large` | `String` | - |
|
||||
| title | Title | `String | Number` | - |
|
||||
| value | Right text | `String | Number` | - |
|
||||
| label | Description below the title | `String` | - |
|
||||
| border | Whether to show inner border | `Boolean` | `true` |
|
||||
| disabled | Whether to disabled collapse | `Boolean` | `false` |
|
||||
| is-link | Whether to show link icon | `Boolean` | `true` |
|
||||
| title-class | Title className | `String` | - |
|
||||
| value-class | Value className | `String` | - |
|
||||
| label-class | Label className | `String` | - |
|
||||
|
||||
### CollapseItem Slots
|
||||
|
||||
| Name | Description |
|
||||
|------|------|
|
||||
| default | Content |
|
||||
| value | Custom value |
|
||||
| icon | Custom icon |
|
||||
| title | Custom title |
|
||||
| right-icon | Custom right icon |
|
||||
@@ -0,0 +1,37 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { ParentMixin } from '../mixins/relation';
|
||||
|
||||
const [createComponent, bem] = createNamespace('collapse');
|
||||
|
||||
export default createComponent({
|
||||
mixins: [ParentMixin('vanCollapse')],
|
||||
|
||||
props: {
|
||||
accordion: Boolean,
|
||||
value: [String, Number, Array],
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
switch(name, expanded) {
|
||||
if (!this.accordion) {
|
||||
name = expanded
|
||||
? this.value.concat(name)
|
||||
: this.value.filter(activeName => activeName !== name);
|
||||
}
|
||||
this.$emit('change', name);
|
||||
this.$emit('input', name);
|
||||
}
|
||||
},
|
||||
|
||||
render(h) {
|
||||
return (
|
||||
<div class={[bem(), { 'van-hairline--top-bottom': this.border }]}>
|
||||
{this.slots()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-collapse van-hairline--top-bottom">
|
||||
<div class="van-collapse-item">
|
||||
<div role="button" tabindex="0" aria-expanded="true" class="van-cell van-cell--clickable van-collapse-item__title van-collapse-item__title--expanded">
|
||||
<div class="van-cell__title"><span>标题1</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div class="van-collapse-item__wrapper">
|
||||
<div class="van-collapse-item__content">代码是写出来给人看的,附带能在机器上运行</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-collapse-item van-hairline--top">
|
||||
<div role="button" tabindex="0" aria-expanded="false" class="van-cell van-cell--clickable van-collapse-item__title">
|
||||
<div class="van-cell__title"><span>标题2</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-collapse-item van-hairline--top">
|
||||
<div role="button" tabindex="-1" aria-expanded="false" class="van-cell van-cell--clickable van-collapse-item__title van-collapse-item__title--disabled">
|
||||
<div class="van-cell__title"><span>标题3</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-collapse van-hairline--top-bottom">
|
||||
<div class="van-collapse-item">
|
||||
<div role="button" tabindex="0" aria-expanded="true" class="van-cell van-cell--clickable van-collapse-item__title van-collapse-item__title--expanded">
|
||||
<div class="van-cell__title"><span>标题1</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
<div class="van-collapse-item__wrapper">
|
||||
<div class="van-collapse-item__content">代码是写出来给人看的,附带能在机器上运行</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-collapse-item van-hairline--top">
|
||||
<div role="button" tabindex="0" aria-expanded="false" class="van-cell van-cell--clickable van-collapse-item__title">
|
||||
<div class="van-cell__title"><span>标题2</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-collapse-item van-hairline--top">
|
||||
<div role="button" tabindex="0" aria-expanded="false" class="van-cell van-cell--clickable van-collapse-item__title">
|
||||
<div class="van-cell__title"><span>标题3</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-collapse van-hairline--top-bottom">
|
||||
<div class="van-collapse-item">
|
||||
<div role="button" tabindex="0" aria-expanded="false" class="van-cell van-cell--clickable van-collapse-item__title">
|
||||
<div class="van-cell__title">
|
||||
标题1<i class="van-icon van-icon-question-o">
|
||||
<!----></i></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-collapse-item van-hairline--top">
|
||||
<div role="button" tabindex="0" aria-expanded="false" class="van-cell van-cell--clickable van-collapse-item__title"><i class="van-icon van-icon-shop-o van-cell__left-icon">
|
||||
<!----></i>
|
||||
<div class="van-cell__title"><span>标题2</span></div>
|
||||
<div class="van-cell__value"><span>内容</span></div><i class="van-icon van-icon-arrow van-cell__right-icon">
|
||||
<!----></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user