Skip to content

Commit f593e8d

Browse files
committed
refactor AssetRangeInput to tsx component
1 parent d2d06ea commit f593e8d

File tree

9 files changed

+369
-305
lines changed

9 files changed

+369
-305
lines changed

.eslintignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ doczrc.js
1616
.eslintrc.js
1717
*.config.js
1818
*.conf.js
19-
19+
gulpfile.js

gulpfile.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
'use strict';
2+
const gulp = require('gulp');
3+
4+
module.exports = function (config) {
5+
const { task, params } = config;
6+
7+
const [ compileCJS, compileES, compileSFC, ...rest ] = task;
8+
return [ gulp.series(compileCJS, compileES, compileSFC), ...rest ];
9+
}

omni.config.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use strict';
22
const fs = require('fs');
33
const path = require('path');
4+
const configuration = require('./gulpfile');
45

56
module.exports = {
67
type: 'component-vue', // 项目类型,请勿任意变动 (project type, please don't modify)
@@ -33,6 +34,8 @@ module.exports = {
3334
] // 构建结果保留其他资源的路径 (reserve other asset paths)
3435
},
3536

37+
configuration,
38+
3639
preflight: {
3740
typescript: true, // 构建时是否处理ts或tsx文件 (whether or not process the ts or tsx files)
3841
// test: true, // 构建时是否进行单元测试 (whether or not process unit-test)

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@
5757
"@commitlint/cli": "8.3.5",
5858
"@foxone/uikit": "~2.0.59",
5959
"@mdi/js": "^5.9.55",
60-
"@omni-door/cli": "^2.5.3",
61-
"@omni-door/gulp-plugin-vue-sfc": "^0.0.8",
60+
"@omni-door/cli": "^2.5.5",
61+
"@omni-door/gulp-plugin-vue-sfc": "^0.1.0",
6262
"@storybook/addon-actions": "5.3.21",
6363
"@storybook/addon-console": "1.2.2",
6464
"@storybook/addon-docs": "5.3.21",
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
import {
2+
defineComponent,
3+
PropType,
4+
onMounted
5+
} from '@vue/composition-api';
6+
import classnames from '@utils/classnames';
7+
import { format } from '@utils/number';
8+
import { VLayout } from 'vuetify/lib';
9+
import { FAssetAmountInput } from '@foxone/uikit/src/components/FAssetAmountInput';
10+
import { FButton } from '@foxone/uikit/src/components/FButton';
11+
import 'vue-slider-component/theme/default.css';
12+
/* import types */
13+
import type { CreateElement, VNode } from 'vue';
14+
import type { MixinAsset } from '@foxone/uikit/src/components/FAssetAmountInput/types';
15+
16+
// avoid typescript error
17+
const VueSlider = require('vue-slider-component');
18+
19+
export interface INPUT_TIPS {
20+
amount?: number;
21+
amountSymbol?: string;
22+
tipLeft?: string;
23+
tipRight: string;
24+
}
25+
26+
export type ERROR = string | ((value: number | string) => string | VNode) | VNode;
27+
28+
export default defineComponent({
29+
name: 'AssetRangeInput',
30+
31+
model: {
32+
prop: 'value',
33+
event: 'change'
34+
},
35+
36+
props: {
37+
value: {
38+
type: [String, Number],
39+
default: ''
40+
},
41+
className: {
42+
type: String,
43+
default: ''
44+
},
45+
min: {
46+
type: Number,
47+
default: 0
48+
},
49+
max: {
50+
type: Number,
51+
default: 0
52+
},
53+
color: {
54+
type: String,
55+
default: 'primary'
56+
},
57+
disabledInput: {
58+
type: Boolean,
59+
default: void 0
60+
},
61+
disabledSlider: {
62+
type: Boolean,
63+
default: void 0
64+
},
65+
disabledBtn: {
66+
type: Boolean,
67+
default: void 0
68+
},
69+
selectable: {
70+
type: Boolean,
71+
default: true
72+
},
73+
border: {
74+
type: Boolean,
75+
default: false
76+
},
77+
loading: {
78+
type: Boolean,
79+
default: false
80+
},
81+
assets: {
82+
type: Array as PropType<MixinAsset[]>,
83+
default: () => []
84+
},
85+
asset: {
86+
type: Object as PropType<MixinAsset>,
87+
default: () => ({})
88+
},
89+
inputTips: {
90+
type: Object as PropType<INPUT_TIPS>,
91+
default: () => ({})
92+
},
93+
error: {
94+
type: [String, Function] as PropType<ERROR>,
95+
default: ''
96+
},
97+
ratioText: {
98+
type: String,
99+
default: 'Ratio'
100+
},
101+
showSlider: {
102+
type: Boolean,
103+
default: true
104+
},
105+
btnText: {
106+
type: String,
107+
default: 'Confirm'
108+
},
109+
showBtn: {
110+
type: Boolean,
111+
default: true
112+
},
113+
},
114+
115+
setup(props) {
116+
const classes = classnames('asset-range-input');
117+
118+
onMounted(() => {
119+
console.info('AssetRangeInput mounted!');
120+
});
121+
122+
return {
123+
classes
124+
};
125+
},
126+
127+
data: () => {
128+
const _value_input = '' as string | number;
129+
const _value_slider = 0 as string | number;
130+
return {
131+
_value_input,
132+
_value_slider
133+
}
134+
},
135+
136+
computed: {
137+
bindAsset: {
138+
get(): MixinAsset {
139+
return this.asset;
140+
},
141+
set(val) {
142+
this.$emit('update:asset', val);
143+
}
144+
},
145+
hasInputLeftTips(): boolean {
146+
return !!(this.inputTips?.amount || this.inputTips?.amountSymbol || this.inputTips?.tipLeft);
147+
},
148+
hasUpperLimit(): number {
149+
return this.max && (1 / this.max);
150+
}
151+
},
152+
153+
watch: {
154+
value: function (val: string) {
155+
if (!this.isInRange(+val)) this.$emit('error:limit');
156+
this._value_input = val;
157+
this.setSliderValueWithLimit(+val);
158+
}
159+
},
160+
161+
created() {
162+
if (!this.isInRange(+this.value)) this.$emit('error:limit');
163+
this._value_input = this.value;
164+
this.setSliderValueWithLimit(+this.value);
165+
},
166+
167+
methods: {
168+
isInRange(val: number) {
169+
return val >= this.min && (!this.hasUpperLimit || val <= this.max);
170+
},
171+
setSliderValueWithLimit(val: number) {
172+
if (this.hasUpperLimit && typeof val === 'number') {
173+
let _val = val / this.max * 100;
174+
if (_val > 100) _val = 100;
175+
if (_val < 0) _val = 0;
176+
this._value_slider = format({
177+
n: _val,
178+
p: 2
179+
});
180+
}
181+
}
182+
},
183+
184+
render(h: CreateElement): VNode {
185+
const btn = this.$slots.button;
186+
const slider = this.$slots.slider;
187+
const inputTips = this.$slots.inputTips;
188+
const sliderTips = this.$slots.sliderTips;
189+
const scopedSlots = {
190+
activator: this.$scopedSlots.activator
191+
};
192+
193+
return (
194+
<VLayout
195+
class={this.classes(void 0, this.className)}
196+
attrs={{
197+
column: true,
198+
'align-center': true
199+
}}
200+
>
201+
<div class={this.classes('top')}>
202+
<FAssetAmountInput
203+
vModel={this._value_input}
204+
assets={this.assets}
205+
asset={this.bindAsset}
206+
selectable={this.selectable}
207+
border={this.border}
208+
loading={this.loading}
209+
vOn={{
210+
...this.$listeners,
211+
input: (val, e) => {
212+
if (!this.isInRange(+val)) this.$emit('error:limit');
213+
this._value_input = val;
214+
this.$emit('input', val);
215+
if (!this.value) this.$nextTick(() => this.$forceUpdate());
216+
this.setSliderValueWithLimit(+val);
217+
}
218+
}}
219+
scopedSlots={scopedSlots}
220+
/>
221+
{
222+
inputTips ?? <VLayout
223+
class={this.classes('top-tips', 'mt-2 f-greyscale-3 f-caption')}
224+
attrs={{
225+
'justify-space-between': true,
226+
'align-center': true
227+
}}
228+
>
229+
{
230+
this.hasInputLeftTips
231+
? <div class={this.classes('top-tips-left')}>
232+
{
233+
this.inputTips?.tipLeft
234+
? <span class={this.classes('top-tips-left-txt', 'mr-1')}>
235+
{ this.inputTips?.tipLeft }
236+
</span>
237+
: null
238+
}
239+
{
240+
this.inputTips?.amount
241+
? <span
242+
class={this.classes('top-tips-left-amount', 'f-greyscale-1 font-weight-bold')}
243+
vOn:click={() => {
244+
if (!this.isInRange(+this.inputTips?.amount!)) this.$emit('error:limit');
245+
this._value_input = `${this.inputTips?.amount!}`;
246+
this.setSliderValueWithLimit(+this._value_input);
247+
this.$emit('input', this._value_input);
248+
this.$emit('click:amount');
249+
this.$nextTick(() => this.$forceUpdate());
250+
}}
251+
>
252+
{ `${this.inputTips?.amount}${this.inputTips?.amountSymbol ?? ''}` }
253+
</span>
254+
: null
255+
}
256+
</div>
257+
: null
258+
}
259+
{
260+
this.inputTips?.tipRight
261+
? <div class={this.classes('top-tips-right')}>
262+
{ this.inputTips.tipRight }
263+
</div>
264+
: null
265+
}
266+
</VLayout>
267+
}
268+
{
269+
this.error
270+
? <div class={this.classes('top-error', 'f-caption pt-2')}>
271+
{ typeof this.error === 'function' ? this.error(this._value_input) : this.error }
272+
</div>
273+
: <div class={this.classes('top-error-placeholder')} />
274+
}
275+
</div>
276+
277+
{
278+
this.showSlider
279+
? slider ?? <div class={this.classes('mid', 'mb-8')}>
280+
{
281+
sliderTips ?? <VLayout
282+
class={this.classes('mid-tips', 'mb-5 f-greyscale-3 f-body-2')}
283+
attrs={{
284+
'justify-space-between': true,
285+
'align-center': true
286+
}}
287+
>
288+
<span>{ this.ratioText }</span>
289+
<span class='f-greyscale-1 font-weight-bold'>{ `${this._value_slider ?? 0}%` }</span>
290+
</VLayout>
291+
}
292+
<VueSlider
293+
vModel={this._value_slider}
294+
height={8}
295+
min={0}
296+
max={100}
297+
interval={0.01}
298+
lazy={true}
299+
tooltip='none'
300+
contained={true}
301+
disabled={this.disabledSlider}
302+
vOn:change={
303+
val => {
304+
this._value_slider = +val;
305+
if (this.hasUpperLimit) {
306+
this._value_input = format({
307+
n: +val / 100 * this.max,
308+
p: 8
309+
});
310+
this.$emit('input', this._value_input);
311+
}
312+
this.$nextTick(() => this.$forceUpdate());
313+
}
314+
}
315+
/>
316+
</div>
317+
: null
318+
}
319+
320+
{
321+
this.showBtn
322+
? btn ?? <FButton
323+
class='px-8 py-4'
324+
color={this.color}
325+
attrs={{
326+
disabled: this.disabledBtn !== void 0 ? this.disabledBtn : !this.isInRange(+this._value_input)
327+
}}
328+
vOn:click={() => this.$emit('click:button')}
329+
>
330+
{ this.btnText }
331+
</FButton>
332+
: null
333+
}
334+
</VLayout>
335+
);
336+
}
337+
});

0 commit comments

Comments
 (0)