[Improvement] add BEM helper mixin (#904)

This commit is contained in:
neverland 2018-04-22 11:33:54 +08:00 committed by GitHub
parent f92caf0e5c
commit 051116df4c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 132 additions and 111 deletions

View File

@ -1,32 +1,30 @@
<template> <template>
<transition name="van-slide-bottom"> <transition name="van-slide-bottom">
<div class="van-actionsheet" :class="{ 'van-actionsheet--withtitle': title }" v-show="value"> <div v-show="value" :class="b({ 'withtitle': title })">
<div class="van-actionsheet__header van-hairline--top-bottom" v-if="title"> <div v-if="title" class="van-hairline--top-bottom" :class="b('header')">
<div v-text="title" /> <div v-text="title" />
<icon name="close" @click="handleCancel" /> <icon name="close" @click="onCancel" />
</div> </div>
<ul v-else class="van-hairline--bottom"> <ul v-else class="van-hairline--bottom">
<li <li
v-for="(item, index) in actions" v-for="item in actions"
:key="index" :class="[b('item'), item.className, 'van-hairline--top']"
class="van-actionsheet__item van-hairline--top"
:class="[item.className, { 'van-actionsheet__item--loading': item.loading }]"
@click.stop="onClickItem(item)" @click.stop="onClickItem(item)"
> >
<template v-if="!item.loading"> <template v-if="!item.loading">
<span class="van-actionsheet__name">{{ item.name }}</span> <span :class="b('name')">{{ item.name }}</span>
<span class="van-actionsheet__subname" v-if="item.subname">{{ item.subname }}</span> <span :class="b('subname')" v-if="item.subname">{{ item.subname }}</span>
</template> </template>
<loading v-else class="van-actionsheet__loading" size="20px" /> <loading v-else :class="b('loading')" size="20px" />
</li> </li>
</ul> </ul>
<div <div
v-if="cancelText" v-if="cancelText"
v-text="cancelText" v-text="cancelText"
class="van-actionsheet__item van-actionsheet__cancel van-hairline--top" :class="[b('cancel'), 'van-hairline--top']"
@click="handleCancel" @click="onCancel"
/> />
<div v-else class="van-actionsheet__content"> <div v-else :class="b('content')">
<slot /> <slot />
</div> </div>
</div> </div>
@ -67,7 +65,7 @@ export default create({
} }
}, },
handleCancel() { onCancel() {
this.$emit('input', false); this.$emit('input', false);
this.$emit('cancel'); this.$emit('cancel');
} }

View File

@ -9,31 +9,26 @@
rows="1" rows="1"
:value="value" :value="value"
:error="isError" :error="isError"
:on-icon-click="onIconClick" @click-icon="onIconClick"
@input="$emit('input', $event)" @input="$emit('input', $event)"
@focus="onFocus" @focus="onFocus"
@blur="onBlur" @blur="onBlur"
> >
<div slot="icon"> <div slot="icon">
<span v-if="showIcon && isAndroid" class="van-address-edit-detail__finish-edit">{{ $t('complete') }}</span> <span v-if="showIcon && isAndroid" :class="b('finish')">{{ $t('complete') }}</span>
<icon v-else-if="showIcon" name="clear" /> <icon v-else-if="showIcon" name="clear" />
</div> </div>
</field> </field>
<cell-group :border="false" v-if="showSearchList">
<cell-group class="van-address-edit-detail__suggest-list" v-if="showSearchList">
<cell <cell
v-for="express in searchResult" v-for="express in searchResult"
:key="express.name + express.address" :key="express.name + express.address"
class="van-address-edit-detail__suggest-item" :title="express.name"
:label="express.address"
icon="location"
clickable clickable
@click="onSuggestSelect(express)" @click="onSelect(express)"
> />
<icon name="location" class="van-address-edit-detail__location" />
<div class="van-address-edit-detail__item-info">
<p class="van-address-edit-detail__title" v-if="isString(express.name)">{{ express.name }}</p>
<p class="van-address-edit-detail__subtitle" v-if="isString(express.address)">{{ express.address }}</p>
</div>
</cell>
</cell-group> </cell-group>
</div> </div>
</template> </template>
@ -97,7 +92,7 @@ export default create({
} }
}, },
onSuggestSelect(express) { onSelect(express) {
this.$emit('input', `${express.address || ''} ${express.name || ''}`.trim()); this.$emit('input', `${express.address || ''} ${express.name || ''}`.trim());
this.$emit('select-search', express); this.$emit('select-search', express);
}, },

View File

@ -1,11 +1,11 @@
<template> <template>
<div class="van-address-edit"> <div :class="b()">
<cell-group> <cell-group>
<field <field
v-model="data.name"
maxlength="15" maxlength="15"
:placeholder="$t('name')" :placeholder="$t('name')"
:label="$t('label.name')" :label="$t('label.name')"
v-model="data.name"
:error="errorInfo.name" :error="errorInfo.name"
@focus="onFocus('name')" @focus="onFocus('name')"
/> />
@ -19,7 +19,7 @@
/> />
<cell <cell
clickable clickable
class="van-address-edit__area" :class="b('area')"
:title="$t('area')" :title="$t('area')"
@click="showArea = true" @click="showArea = true"
> >
@ -57,7 +57,7 @@
:title="$t('defaultAddress')" :title="$t('defaultAddress')"
/> />
</cell-group> </cell-group>
<div v-show="!hideBottomFields" class="van-address-edit__buttons"> <div v-show="!hideBottomFields" :class="b('buttons')">
<van-button block :loading="isSaving" @click="onSave" type="primary"> <van-button block :loading="isSaving" @click="onSave" type="primary">
{{ $t('save') }} {{ $t('save') }}
</van-button> </van-button>

View File

@ -1,20 +1,26 @@
<template> <template>
<div class="van-address-list"> <div :class="b()">
<radio-group :value="value" @input="$emit('input', $event)" class="van-address-list__group"> <radio-group :value="value" :class="b('group')" @input="$emit('input', $event)">
<cell-group> <cell-group>
<cell v-for="(item, index) in list" :key="item.id" is-link> <cell v-for="(item, index) in list" :key="item.id" is-link>
<radio :name="item.id" @click="$emit('select', item, index)"> <radio :name="item.id" @click="$emit('select', item, index)">
<div class="van-address-list__name">{{ item.name }}{{ item.tel }}</div> <div :class="b('name')">{{ item.name }}{{ item.tel }}</div>
<div class="van-address-list__address">{{ $t('address') }}{{ item.address }}</div> <div :class="b('address')">{{ $t('address') }}{{ item.address }}</div>
</radio> </radio>
<icon slot="right-icon" name="edit" class="van-address-list__edit" @click="$emit('edit', item, index)" /> <icon
slot="right-icon"
name="edit"
:class="b('edit')"
@click="$emit('edit', item, index)"
/>
</cell> </cell>
</cell-group> </cell-group>
</radio-group> </radio-group>
<cell <cell
icon="add" icon="add"
is-link is-link
class="van-address-list__add van-hairline--top" :class="b('add')"
class="van-hairline--top"
:title="addButtonText || $t('add')" :title="addButtonText || $t('add')"
@click="$emit('add')" @click="$emit('add')"
/> />

View File

@ -1,6 +1,6 @@
<template> <template>
<picker <picker
class="van-area" :class="b()"
ref="picker" ref="picker"
show-toolbar show-toolbar
value-key="name" value-key="name"

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="van-badge-group van-hairline--top-bottom"> <div :class="b()" class="van-hairline--top-bottom">
<slot /> <slot />
</div> </div>
</template> </template>

View File

@ -1,6 +1,10 @@
<template> <template>
<a class="van-badge van-hairline" :class="{ 'van-badge--select': isSelect }" :href="url" @click="onClick"> <a
<div v-if="isDef(info)" class="van-badge__info">{{ info }}</div> :class="[b({ select }), 'van-hairline']"
:href="url"
@click="onClick"
>
<div v-if="isDef(info)" :class="b('info')">{{ info }}</div>
{{ title }} {{ title }}
</a> </a>
</template> </template>
@ -23,7 +27,7 @@ export default create({
}, },
computed: { computed: {
isSelect() { select() {
return this.$parent.badges.indexOf(this) === this.$parent.activeKey; return this.$parent.badges.indexOf(this) === this.$parent.activeKey;
} }
}, },

View File

@ -3,22 +3,21 @@
:is="tag" :is="tag"
:type="nativeType" :type="nativeType"
:disabled="disabled" :disabled="disabled"
class="van-button" :class="b([
:class="[ type,
'van-button--' + type, size,
'van-button--' + size,
{ {
'van-button--disabled': disabled, block,
'van-button--loading': loading, loading,
'van-button--block': block, disabled,
'van-button--bottom-action': bottomAction, unclickable: disabled || loading,
'van-button--unclickable': disabled || loading 'bottom-action': bottomAction
} }
]" ])"
@click="onClick" @click="onClick"
> >
<loading v-if="loading" size="20px" :color="type === 'default' ? 'black' : 'white'" /> <loading v-if="loading" size="20px" :color="type === 'default' ? 'black' : 'white'" />
<span class="van-button__text"> <span :class="b('text')">
<slot>{{ text }}</slot> <slot>{{ text }}</slot>
</span> </span>
</component> </component>

View File

@ -1,32 +1,33 @@
<template> <template>
<div class="van-card" :class="{ 'van-card--center': centered }"> <div :class="b({ center: centered })">
<div class="van-card__thumb"> <div :class="b('thumb')">
<slot name="thumb"> <slot name="thumb">
<img :src="thumb" class="van-card__img" > <img :src="thumb" :class="b('img')" >
</slot> </slot>
</div> </div>
<div class="van-card__content"> <div :class="b('content')">
<slot name="title"> <slot name="title">
<div class="van-card__row" v-if="title || price !== undefined"> <div :class="b('row')" v-if="title || isDef(price)">
<div v-if="title" class="van-card__title">{{ title }}</div> <div v-if="title" :class="b('title')">{{ title }}</div>
<div v-if="price !== undefined" class="van-card__price">{{ currency }} {{ price }}</div> <div v-if="isDef(price)" :class="b('price')">{{ currency }} {{ price }}</div>
</div> </div>
</slot> </slot>
<slot name="desc"> <slot name="desc">
<div class="van-card__row" v-if="desc || num !== undefined"> <div :class="b('row')" v-if="desc || isDef(num)">
<div v-if="desc" class="van-card__desc">{{ desc }}</div> <div v-if="desc" :class="b('desc')">{{ desc }}</div>
<div v-if="num !== undefined" class="van-card__num">x {{ num }}</div> <div v-if="isDef(num)" :class="b('num')">x {{ num }}</div>
</div> </div>
</slot> </slot>
<slot name="tags" /> <slot name="tags" />
</div> </div>
<div class="van-card__footer" v-if="$slots.footer"> <div :class="b('footer')" v-if="$slots.footer">
<slot name="footer" /> <slot name="footer" />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import { isDef } from '../utils';
import create from '../utils/create'; import create from '../utils/create';
export default create({ export default create({
@ -43,6 +44,10 @@ export default create({
type: String, type: String,
default: '¥' default: '¥'
} }
},
methods: {
isDef
} }
}); });
</script> </script>

45
packages/mixins/bem.js Normal file
View File

@ -0,0 +1,45 @@
/**
* bem helper
* b() // 'button'
* b('text') // 'button__text'
* b({ disabled }) // 'button button--disabled'
* b('text', { disabled }) // 'button__text button__text--disabled'
* b(['disabled', 'primary']) // 'button button--disabled button--primary'
*/
const ELEMENT = '__';
const MODS = '--';
const join = (name, el, symbol) => el ? name + symbol + el : name;
const prefix = (name, mods) => {
if (typeof mods === 'string') {
return join(name, mods, MODS);
}
if (Array.isArray(mods)) {
return mods.map(item => prefix(name, item));
}
const ret = {};
Object.keys(mods).forEach(key => {
ret[name + MODS + key] = mods[key];
});
return ret;
};
export default {
methods: {
b(el, mods) {
const { name } = this.$options;
if (el && typeof el !== 'string') {
mods = el;
el = '';
}
el = join(name, el, ELEMENT);
return mods ? [el, prefix(name, mods)] : el;
}
}
};

View File

@ -10,7 +10,6 @@
<li <li
v-if="isMultiMode" v-if="isMultiMode"
v-for="(page, index) in pages" v-for="(page, index) in pages"
:key="index"
class="van-pagination__item van-pagination__page van-hairline" class="van-pagination__item van-pagination__page van-hairline"
:class="{ 'van-pagination--active': page.active }" :class="{ 'van-pagination--active': page.active }"
@click="selectPage(page.number)" @click="selectPage(page.number)"

View File

@ -23,7 +23,6 @@
<!-- 已有的图片,图片右上角显示删除按钮 --> <!-- 已有的图片,图片右上角显示删除按钮 -->
<div <div
v-for="(img, index) in imgList" v-for="(img, index) in imgList"
:key="index"
class="van-sku-img-uploader__img" class="van-sku-img-uploader__img"
> >
<img :src="img"> <img :src="img">

View File

@ -12,7 +12,6 @@
<div v-if="type === 'line'" class="van-tabs__nav-bar" :style="navBarStyle" /> <div v-if="type === 'line'" class="van-tabs__nav-bar" :style="navBarStyle" />
<div <div
v-for="(tab, index) in tabs" v-for="(tab, index) in tabs"
:key="index"
ref="tabs" ref="tabs"
class="van-tab" class="van-tab"
:class="{ :class="{

View File

@ -2,6 +2,7 @@
* Create a basic component with common options * Create a basic component with common options
*/ */
import '../locale'; import '../locale';
import bem from '../mixins/bem';
import i18n from '../mixins/i18n'; import i18n from '../mixins/i18n';
const install = function(Vue) { const install = function(Vue) {
@ -12,7 +13,7 @@ export default function(sfc) {
sfc.name = 'van-' + sfc.name; sfc.name = 'van-' + sfc.name;
sfc.install = sfc.install || install; sfc.install = sfc.install || install;
sfc.mixins = sfc.mixins || []; sfc.mixins = sfc.mixins || [];
sfc.mixins.push(i18n); sfc.mixins.push(i18n, bem);
return sfc; return sfc;
}; };

View File

@ -15,7 +15,8 @@
background-color: $white; background-color: $white;
} }
&__item { &__item,
&__cancel {
height: 50px; height: 50px;
line-height: 50px; line-height: 50px;
font-size: 16px; font-size: 16px;

View File

@ -3,6 +3,10 @@
.van-address-edit { .van-address-edit {
&__buttons { &__buttons {
padding: 20px 10px; padding: 20px 10px;
.van-button {
margin-bottom: 10px;
}
} }
&__area { &__area {
@ -19,46 +23,12 @@
} }
} }
.van-button {
margin-bottom: 10px;
}
.van-icon-clear { .van-icon-clear {
color: $gray-dark; color: $gray-dark;
} }
&-detail { &-detail__finish {
&__finish-edit { color: $blue;
color: $blue; font-size: 12px;
font-size: 12px;
}
&__suggest-list {
&::after {
border-top-width: 0;
}
}
&__location {
float: left;
font-size: 18px;
color: $gray-darker;
}
&__item-info {
margin-left: 26px;
line-height: 1.6;
}
&__title {
font-size: 14px;
margin: 0 0 4px;
}
&__subtitle {
margin: 0;
font-size: 12px;
color: $gray-darker;
}
} }
} }

View File

@ -192,21 +192,21 @@ describe('AddressEdit', () => {
wrapper.find('.van-field__control')[2].trigger('focus'); wrapper.find('.van-field__control')[2].trigger('focus');
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
const items = wrapper.find('.van-address-edit-detail__suggest-item'); const items = wrapper.find('.van-icon-location');
expect(items.length).to.equal(3); expect(items.length).to.equal(3);
items[0].trigger('click'); items[0].element.parentNode.click();
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(wrapper.find('.van-field__control')[2].element.value).to.equal( expect(wrapper.find('.van-field__control')[2].element.value).to.equal(
'杭州市西湖区 黄龙万科中心' '杭州市西湖区 黄龙万科中心'
); );
items[1].trigger('click'); items[1].element.parentNode.click();
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect(wrapper.find('.van-field__control')[2].element.value).to.equal( expect(wrapper.find('.van-field__control')[2].element.value).to.equal(
'黄龙万科中心H座' '黄龙万科中心H座'
); );
items[2].trigger('click'); items[2].element.parentNode.click();
wrapper.vm.$nextTick(() => { wrapper.vm.$nextTick(() => {
expect( expect(