Skip to content

Commit aba1e9e

Browse files
kagolgitee-org
authored andcommitted
!248 feat: 完善组件功能,补全文档
Merge pull request !248 from 星辰大海/dev
2 parents 2861ae8 + 63b3a7d commit aba1e9e

File tree

5 files changed

+467
-66
lines changed

5 files changed

+467
-66
lines changed

devui/tabs/index.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
import type { App } from 'vue'
2-
import Tabs from './src/tabs'
1+
import { App } from 'vue';
2+
// import type { App } from 'vue';
3+
import Tabs from './src/tabs';
4+
import Tab from './src/tab';
35

4-
Tabs.install = function(app: App) {
5-
app.component(Tabs.name, Tabs)
6-
}
6+
Tabs.install = function (app: App) {
7+
app.component(Tabs.name, Tabs);
8+
app.component(Tab.name, Tab);
9+
};
710

8-
export { Tabs }
11+
export { Tabs };
912

1013
export default {
1114
title: 'Tabs 选项卡',
1215
category: '导航',
1316
status: '60%',
1417
install(app: App): void {
15-
app.use(Tabs as any)
18+
app.use(Tabs as any);
1619
}
17-
}
20+
};

devui/tabs/src/tab.tsx

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineComponent, inject } from 'vue'
1+
import { defineComponent, inject } from 'vue';
22
import { Tabs } from './tabs';
33

44
export default defineComponent({
@@ -17,19 +17,20 @@ export default defineComponent({
1717
default: false
1818
}
1919
},
20-
setup(props, {slots}) {
21-
const tabs = inject<Tabs>(
22-
'tabs'
23-
);
20+
setup(props, { slots }) {
21+
const tabs = inject<Tabs>('tabs');
2422
tabs.state.data.push(props);
2523
return () => {
26-
const content = tabs.state.showContent && tabs.state.active === props.id ? (
27-
<div class="devui-tab-content">
28-
<div role="tabpanel" class="devui-tab-pane in active">
29-
{slots.default()}
30-
</div>
31-
</div>): null;
32-
return content
33-
}
24+
const { id } = props;
25+
const content =
26+
tabs.state.showContent && tabs.state.active === id ? (
27+
<div class='devui-tab-content'>
28+
<div role='tabpanel' class='devui-tab-pane in active'>
29+
{slots.default()}
30+
</div>
31+
</div>
32+
) : null;
33+
return content;
34+
};
3435
}
35-
})
36+
});

devui/tabs/src/tabs.scss

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
1-
@import '../../style/theme/color';
2-
@import '../../style/theme/variables';
31
@import '../../style/mixins/index';
4-
@import '../../style/theme/font';
5-
@import '../../style/theme/corner';
2+
@import '../../styles-var/devui-var.scss';
63

74
$devui-tab-options-bg: $devui-list-item-hover-bg;
85

@@ -15,6 +12,7 @@ $devui-tab-options-bg: $devui-list-item-hover-bg;
1512
font-size: $devui-font-size;
1613
background: transparent;
1714
font-weight: bold;
15+
list-style: none;
1816

1917
li {
2018
cursor: pointer;
@@ -26,6 +24,7 @@ $devui-tab-options-bg: $devui-list-item-hover-bg;
2624
line-height: 30px;
2725
background-color: transparent;
2826
padding: 0;
27+
text-decoration: none;
2928
color: $devui-text;
3029
}
3130

@@ -297,11 +296,18 @@ $devui-tab-options-bg: $devui-list-item-hover-bg;
297296
}
298297

299298
.devui-nav {
299+
list-style: none;
300+
padding-left: 0;
301+
300302
li {
301-
a.custom-width {
302-
display: inline-block;
303-
padding: 0;
304-
text-align: center;
303+
a {
304+
text-decoration: none;
305+
306+
&.custom-width {
307+
display: inline-block;
308+
padding: 0;
309+
text-align: center;
310+
}
305311
}
306312
}
307313
}
@@ -353,5 +359,7 @@ $devui-tab-options-bg: $devui-list-item-hover-bg;
353359
box-shadow: 0 2px 4px 0 $devui-light-shadow;
354360
top: 1px;
355361
height: 30px;
356-
transition: left 0.3s cubic-bezier(0.645, 0.045, 0.355, 1), width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
362+
transition:
363+
left 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
364+
width 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
357365
}

devui/tabs/src/tabs.tsx

Lines changed: 142 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
1-
import { computed, defineComponent, provide, reactive } from 'vue'
1+
import {
2+
defineComponent,
3+
onBeforeMount,
4+
onMounted,
5+
onUpdated,
6+
PropType,
7+
provide,
8+
reactive,
9+
ref
10+
} from 'vue';
211
import './tabs.scss';
312

413
export type Active = string | number | null;
5-
export type TabsType = 'tabs' | 'pills' | 'options' | 'wrapped' | 'slider'
14+
export type TabsType = 'tabs' | 'pills' | 'options' | 'wrapped' | 'slider';
615
export interface Tabs {
716
state: TabsState
817
}
@@ -18,7 +27,7 @@ export default defineComponent({
1827
type: [String, Number],
1928
default: null
2029
},
21-
// TODO:其中 slider 类型还没有实现
30+
2231
type: {
2332
type: String as () => TabsType,
2433
default: 'tabs'
@@ -42,51 +51,149 @@ export default defineComponent({
4251
cssClass: {
4352
type: String,
4453
default: ''
54+
},
55+
beforeChange: {
56+
type: Function as PropType<(id: Active) => boolean>,
57+
default: null
4558
}
4659
},
47-
// TODO: beforeChange没有完成实现
48-
emits: ['update:modelValue', 'activeTabChange', 'beforeChange'],
60+
61+
emits: ['update:modelValue', 'activeTabChange'],
4962
setup(props, { emit, slots }) {
50-
const active = computed(() => {
51-
return props.modelValue
52-
})
63+
const tabsEle = ref(null);
64+
const data = reactive({ offsetLeft: 0, offsetWidth: 0, id: null });
5365
const state: TabsState = reactive({
5466
data: [],
55-
active,
67+
active: props.modelValue,
5668
showContent: props.showContent
5769
});
5870
provide<Tabs>('tabs', {
5971
state
6072
});
61-
function activateTab(tab: Active) {
62-
emit('beforeChange');
63-
emit('update:modelValue', tab);
64-
if (props.reactivable) {
65-
emit('activeTabChange', tab)
73+
74+
const canChange = function (currentTab: Active) {
75+
let changeResult = Promise.resolve(true);
76+
if (typeof props.beforeChange === 'function') {
77+
const result: any = props.beforeChange(currentTab);
78+
if (typeof result !== 'undefined') {
79+
if (result.then) {
80+
changeResult = result;
81+
} else {
82+
console.log(result);
83+
changeResult = Promise.resolve(result);
84+
}
85+
}
6686
}
67-
}
6887

88+
return changeResult;
89+
};
90+
const activeClick = function (item, tabEl?) {
91+
if (!props.reactivable && props.modelValue === item.id) {
92+
return;
93+
}
94+
canChange(item.id).then((change) => {
95+
if (!change) {
96+
return;
97+
}
98+
const tab = state.data.find((itemOption) => itemOption.id === item.id);
99+
if (tab && !tab.disabled) {
100+
emit('update:modelValue', tab.id);
101+
if (props.type === 'slider' && tabEl && tabsEle) {
102+
this.offsetLeft =
103+
tabEl.getBoundingClientRect().left -
104+
this.tabsEle.nativeElement.getBoundingClientRect().left;
105+
this.offsetWidth = tabEl.getBoundingClientRect().width;
106+
}
107+
emit('activeTabChange', tab.id);
108+
}
109+
});
110+
};
69111
const ulClass: string[] = [props.type];
70112
props.cssClass && ulClass.push(props.cssClass);
71-
props.vertical && ulClass.push('devui-nav-stacked')
72-
return () => {
73-
return <div >
74-
<ul role="tablist" class={`devui-nav devui-nav-${ulClass.join(' ')}`} id="devuiTabs11">
75-
{
76-
state.data.map((item) => {
77-
return <li role="presentation" onClick={() => activateTab((item.id || item.tabId))} class={active.value === (item.id || item.tabId) ? 'active' : ''} id={item.id || item.tabId} >
78-
<a role="tab" data-toggle={item.id} aria-expanded={active.value === (item.id || item.tabId)}>
79-
<span >{item.title}</span>
80-
</a>
81-
</li>
82-
})
113+
props.vertical && ulClass.push('devui-nav-stacked');
114+
onUpdated(() => {
115+
if (props.type === 'slider') {
116+
// 延时等待active样式切换至正确的tab
117+
setTimeout(() => {
118+
const tabEle = tabsEle.value.querySelector(
119+
'#' + props.modelValue + '.active'
120+
);
121+
if (tabEle) {
122+
data.offsetLeft =
123+
tabEle.getBoundingClientRect().left -
124+
tabsEle.value.getBoundingClientRect().left;
125+
data.offsetWidth = tabEle.getBoundingClientRect().width;
83126
}
84-
<div class={`devui-nav-${props.type}-animation`}></div>
85-
</ul>
86-
{slots.default()}
87-
</div>
88-
89-
}
127+
});
128+
}
129+
});
130+
onBeforeMount(() => {
131+
if (
132+
props.type !== 'slider' &&
133+
props.modelValue === undefined &&
134+
state.data.length > 0
135+
) {
136+
activeClick(state.data[0]);
137+
}
138+
});
139+
onMounted(() => {
140+
if (
141+
props.type === 'slider' &&
142+
props.modelValue === undefined &&
143+
state.data.length > 0 &&
144+
state.data[0]
145+
) {
146+
activeClick(
147+
state.data[0].tabsEle.value.getElementById(state.data[0].tabId)
148+
);
149+
}
150+
});
151+
return () => {
152+
return (
153+
<div>
154+
<ul
155+
ref={tabsEle}
156+
role='tablist'
157+
class={`devui-nav devui-nav-${ulClass.join(' ')}`}
158+
id='devuiTabs11'
159+
>
160+
{state.data.map((item) => {
161+
return (
162+
<li
163+
role='presentation'
164+
onClick={() => {
165+
activeClick(item);
166+
}}
167+
class={
168+
(props.modelValue === (item.id || item.tabId)
169+
? 'active'
170+
: '') +
171+
' ' +
172+
(item.disabled ? 'disabled' : '')
173+
}
174+
id={item.id || item.tabId}
175+
>
176+
<a
177+
role='tab'
178+
data-toggle={item.id}
179+
aria-expanded={props.modelValue === (item.id || item.tabId)}
180+
>
181+
<span>{item.title}</span>
182+
</a>
183+
</li>
184+
);
185+
})}
186+
<div
187+
class={`devui-nav-${props.type}-animation`}
188+
style={{
189+
left: data.offsetLeft + 'px',
190+
width: data.offsetWidth + 'px'
191+
}}
192+
></div>
193+
</ul>
194+
{slots.default()}
195+
</div>
196+
);
197+
};
90198
}
91-
})
92-
199+
});

0 commit comments

Comments
 (0)