提交 8cd739ec 编写于 作者: richard_1015's avatar richard_1015

Merge branch 'next' of https://github.com/jdf2e/nutui into next

......@@ -487,6 +487,16 @@
"sort": 6,
"show": true,
"author": "Drjingfubo"
},
{
"version": "3.0.0",
"name": "TextArea",
"type": "component",
"cName": "文本域",
"desc": "文本输入",
"sort": 7,
"show": true,
"author": "gx"
}
]
},
......
......@@ -178,9 +178,9 @@ setup() {
| container-id | 在 useWindow 属性为 false 的时候,自定义设置节点ID | String | `''` |
| load-more-txt | “没有更多数”据展示文案 | String | `'哎呀,这里是底部了啦'` |
| is-open-refresh | 是否开启下拉刷新 | Boolean | `false` |
| pull-icon | 下拉刷新[图标名称](#/icon) | String | `https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png` |
| pull-icon | 下拉刷新[图标名称](#/icon) | String | <img src="https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png" width=40/> |
| pull-txt | 下拉刷新提示文案 | String | `松手刷新` |
| load-icon | 上拉加载[图标名称](#/icon) | Boolean | `https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png` |
| load-icon | 上拉加载[图标名称](#/icon) | Boolean | <img src="https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png" width=40 /> |
| load-txt | 上拉加载提示文案 | String | `加载中...` |
### Events
......
......@@ -57,34 +57,6 @@
placeholder="支持小数点的输入"
label="数字:"
/>
<h2>文本域</h2>
<nut-input
v-model:value="state.val7"
@change="change"
:autosize="true"
type="textarea"
placeholder="文本域"
label="留言:"
/>
<nut-input
v-model:value="state.val7"
@change="change"
rows="5"
type="textarea"
placeholder="设置输入五行"
label="留言:"
/>
<h2>显示字数统计</h2>
<nut-input
v-model:value="state.val8"
@change="change"
rows="5"
:limitShow="true"
maxLength="20"
type="textarea"
placeholder="设置输入五行"
label="留言:"
/>
</div>
</template>
......@@ -120,6 +92,7 @@ export default createDemo({
const clear = (num: string | number) => {
console.log('clear:', num);
};
return {
state,
change,
......
......@@ -20,8 +20,20 @@ app.use(input);
双向绑定
```html
<nut-input v-model:value="state.val1" @change="change" label="标题:" />
<nut-input
v-model:value="state.val1"
@change="change"
@focus="focus"
@blur="blur"
label="文本"
/>
<nut-input placeholder="请输入文本"
@change="change"
v-model:value="state.val0"
:requireShow="true"
label="文本"
@clear="clear"
/>
```
### 禁用和只读
......@@ -44,19 +56,7 @@ app.use(input);
<nut-input v-model:value="state.val5" @change="change" type="digit" label="整数:" />
<nut-input v-model:value="state.val6" @change="change" type="digit" placeholder="支持小数点的输入" label="数字:"/>
```
### 文本域
```html
<nut-input v-model:value="state.val7" @change="change" autosize="true" type="textarea" placeholder="文本域" label="留言:"/>
<nut-input v-model:value="state.val7" @change="change" rows="5" type="textarea" placeholder="设置输入五行" label="留言:"/>
```
### 文本域字数统计
```html
<nut-input v-model:value="state.val8" @change="change" rows="5" limitShow="true" maxLength="20" type="textarea" placeholder="设置输入五行" label="留言:"/>
```
| 参数 | 说明 | 类型 | 默认值 |
......@@ -64,15 +64,13 @@ app.use(input);
| type | 类型,可选值为 `text` `textarea` `number` 等 | String |`text` |
| value | 输入值,双向绑定 | String | - |
| placeholder | 为空时占位符 | String | - |
| placeholder-style | placeholder 样式 | String | - |
| label | 左侧文案 | string | - |
| requireShow |左侧*号是否展示 | boolean | `false` |
| disabled | 是否禁用 | boolean | `false` |
| readonly | 是否只读 | boolean | `false` |
| clear-btn | 是否带清除按钮(icon) | boolean | `true` |
| required | 是否带必填的*号,且blur事件做非空校验 | boolean | `false` |
| maxlength | 限制最长输入字符 | string/number | - |
| rows | textarea时高度 | string/number | 2 |
| limit-show | textarea时是否展示输入字符。须设置maxlength | boolean | `false` |
| disableClear | 禁止展示清除icon | boolean | false |
| textAlign | 文本位置 | string | `left` |
| change | 输入内容时触发 | function | - |
| focus | 聚焦时触发 | function | - |
| blur | 失焦时触发 | function | - |
......
......@@ -28,29 +28,6 @@
position: absolute;
right: 15px;
}
.nut-text {
flex: 1;
padding: 0 10px;
.nut-text-limit {
float: right;
color: rgba(153, 153, 153, 1);
}
.nut-text-core {
outline: none;
display: block;
box-sizing: border-box;
width: 100%;
min-width: 0;
margin: 0;
padding: 0;
color: #323233;
line-height: inherit;
text-align: left;
background-color: transparent;
border: 0;
resize: none;
}
}
}
.nut-input-disabled {
color: #c8c9cc !important;
......
<template>
<view :class="['nut-input', { 'nut-input-disabled': disabled }]">
<view :class="classes">
<view class="nut-input-label">
<view class="nut-input-require" v-if="requireShow">*</view>
<view v-if="label" class="label-string">{{ label }}</view>
</view>
<view v-if="type === 'textarea'" class="nut-text">
<textarea
:style="styles"
:rows="rows"
@input="valueChange"
v-model="state.curretvalue"
class="nut-text-core"
:maxlength="maxLength"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
>
</textarea>
<span class="nut-text-limit" v-if="limitShow">
<span :class="[{ 'nut-field-over': state.textNum > maxLength }]">{{
state.textNum
}}</span>
<span>/{{ maxLength }}</span>
</span>
</view>
<input
v-else
class="input-text"
:style="styles"
:type="type"
......@@ -43,7 +21,7 @@
@click="handleClear"
class="nut-textinput-clear"
v-if="!disableClear && !readonly"
v-show="type !== 'textarea' && active"
v-show="active && state.curretvalue.length > 0"
>
<nut-icon name="close-little" size="12px"></nut-icon>
</view>
......@@ -54,43 +32,30 @@ import { ref, toRefs, reactive, computed } from 'vue';
import { createComponent } from '@/utils/create';
import { formatNumber } from './util';
const { create } = createComponent('input');
const { componentName, create } = createComponent('input');
interface Events {
eventName: 'change' | 'focus' | 'blur' | 'clear' | 'update:value';
params: (string | number | Event)[];
}
export default create({
props: {
type: {
type: String,
default: 'text'
},
textAlign: {
type: String,
default: 'left'
},
limitShow: {
type: Boolean,
default: false
},
maxLength: {
type: String,
value: {
type: [String, Number],
default: ''
},
requireShow: {
type: Boolean,
default: false
},
rows: {
placeholder: {
type: String,
default: ''
default: '请输入信息'
},
label: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '请输入信息'
},
readonly: {
requireShow: {
type: Boolean,
default: false
},
......@@ -98,12 +63,16 @@ export default create({
type: Boolean,
default: false
},
autosize: {
readonly: {
type: Boolean,
default: false
},
value: {
type: [String, Number],
textAlign: {
type: String,
default: 'left'
},
maxLength: {
type: String,
default: ''
},
disableClear: {
......@@ -115,25 +84,13 @@ export default create({
emits: ['change', 'update:value', 'blur', 'focus', 'clear', 'error'],
setup(props, { emit }) {
interface Events {
eventName:
| 'change'
| 'focus'
| 'blur'
| 'clear'
| 'update:value'
| 'error';
params: (string | number | Event)[];
}
const {
label,
placeholder,
disabled,
readonly,
requireShow,
maxLength,
rows
maxLength
} = props;
const { value } = toRefs(props);
const active = ref(false);
......@@ -141,14 +98,15 @@ export default create({
curretvalue: value,
textNum: String(value.value).length
});
const classes = computed(() => {
return {
[componentName]: true,
'nut-input-disabled': disabled
};
});
const styles = computed(() => {
const rize =
props.type == 'textarea'
? `'resize':${props.autosize ? 'none' : 'horizontal'}`
: '';
return {
'text-align': props.textAlign,
rize
'text-align': props.textAlign
};
});
const emitChange = (envs: Array<Events>) => {
......@@ -162,12 +120,6 @@ export default create({
if (maxLength && val.length > Number(maxLength)) {
val = val.slice(0, Number(maxLength));
emitChange([
{
eventName: 'error',
params: [val]
}
]);
}
if (props.type == 'digit') {
val = formatNumber(val, true);
......@@ -176,8 +128,6 @@ export default create({
val = formatNumber(val, false);
}
state.textNum = val.length;
// input.value = val;
//state.curretvalue = val;
emitChange([
{
eventName: 'update:value',
......@@ -243,7 +193,6 @@ export default create({
placeholder,
label,
disabled,
rows,
state,
styles,
active,
......@@ -252,6 +201,7 @@ export default create({
valueFocus,
valueBlur,
handleClear,
classes,
emitChange
};
}
......
......@@ -12,17 +12,23 @@
<view-block
class="nut-picker__item"
:key="index"
v-for="(item, index) in state.options"
v-for="(item, index) in options"
>{{ dataType === 'cascade' ? item.text : item }}</view-block
>
</view-block>
</view-block>
</template>
<script lang="ts">
import { reactive, ref, watch, computed } from 'vue';
import { reactive, ref, watch, computed, toRefs, onMounted } from 'vue';
import { createComponent } from '@/utils/create';
import { useTouch } from '@/utils/useTouch';
import { commonProps } from './commonProps';
import {
PickerObjOpt,
PickerOption,
PickerObjectColumn,
PickerObjectColumns
} from './types';
const MOMENTUM_LIMIT_DISTANCE = 15;
const MOMENTUM_LIMIT_TIME = 300;
const DEFAULT_DURATION = 200;
......@@ -34,7 +40,6 @@ function stopPropagation(event: Event) {
event.stopPropagation();
}
function preventDefault(event: Event, isStopPropagation?: boolean) {
/* istanbul ignore else */
if (typeof event.cancelable !== 'boolean' || event.cancelable) {
event.preventDefault();
}
......@@ -44,7 +49,7 @@ function preventDefault(event: Event, isStopPropagation?: boolean) {
}
}
function getElementTranslateY(element) {
function getElementTranslateY(element: Element) {
const style = window.getComputedStyle(element);
const transform = style.transform || style.webkitTransform;
const translateY = transform.slice(7, transform.length - 1).split(', ')[5];
......@@ -54,7 +59,7 @@ export function isObject(val: unknown): val is Record<any, any> {
return val !== null && typeof val === 'object';
}
function isOptionDisabled(option) {
function isOptionDisabled(option: PickerObjectColumn) {
return isObject(option) && option.disabled;
}
......@@ -66,46 +71,57 @@ export default create({
emits: ['click', 'change'],
setup(props, { emit }) {
let moving;
let startOffset, touchStartTime, momentumOffset, transitionEndTrigger;
const wrapper = ref();
const state = reactive({
index: props.defaultIndex,
offset: 0,
duration: 0,
options: props.listData
options: props.listData as PickerObjectColumn[],
moving: false,
startOffset: 0,
touchStartTime: 0,
momentumOffset: 0,
transitionEndTrigger: null as null | Function
});
watch(
() => props.listData,
val => {
if (val) {
state.options = val;
}
}
);
const wrapper = ref();
const touch = useTouch();
const count = () => state.options.length;
const _show = ref(false);
const getIndexByOffset = offset =>
range(Math.round(-offset / props.itemHeight), 0, count() - 1);
const baseOffset = () =>
(props.itemHeight * (props.visibleItemCount - 1)) / 2;
const wrapperStyle = computed(() => ({
transform: `translate3d(0, ${state.offset + baseOffset()}px, 0)`,
transitionDuration: `${state.duration}ms`,
transitionProperty: state.duration ? 'all' : 'none'
}));
const handleClick = (event: Event) => {
emit('click', event);
};
const getIndexByOffset = (offset: number) => {
return range(
Math.round(-offset / +props.itemHeight),
0,
state.options.length - 1
);
};
const baseOffset = () => {
return (+props.itemHeight * (+props.visibleItemCount - 1)) / 2;
};
const stopMomentum = () => {
moving = false;
state.moving = false;
state.duration = 0;
if (transitionEndTrigger) {
transitionEndTrigger();
transitionEndTrigger = null;
if (state.transitionEndTrigger) {
state.transitionEndTrigger();
state.transitionEndTrigger = null;
}
};
const adjustIndex = index => {
index = range(index, 0, count());
const adjustIndex = (index: number) => {
index = range(index, 0, state.options.length);
for (let i = index; i < count(); i++) {
for (let i = index; i < state.options.length; i++) {
if (!isOptionDisabled(state.options[i])) return i;
}
for (let i = index - 1; i >= 0; i--) {
......@@ -113,10 +129,10 @@ export default create({
}
};
const setIndex = (index, emitChange = false) => {
const setIndex = (index: number, emitChange = false) => {
index = adjustIndex(index) || 0;
const offset = -index * props.itemHeight;
const offset = -index * +props.itemHeight;
const trigger = () => {
if (index !== state.index) {
state.index = index;
......@@ -127,22 +143,16 @@ export default create({
}
};
if (moving && offset !== state.offset) {
transitionEndTrigger = trigger;
if (state.moving && offset !== state.offset) {
state.transitionEndTrigger = trigger;
} else {
trigger();
}
state.offset = offset;
};
watch(
() => props.defaultIndex,
val => {
setIndex(val);
}
);
setIndex(props.defaultIndex);
const momentum = (distance, duration) => {
const momentum = (distance: number, duration: number) => {
const speed = Math.abs(distance / duration);
distance = state.offset + (speed / 0.003) * (distance < 0 ? -1 : 1);
......@@ -151,57 +161,58 @@ export default create({
setIndex(index, true);
};
const onTouchStart = event => {
const onTouchStart = (event: Event) => {
if (props.readonly) {
return;
}
touch.start(event);
if (moving) {
if (state.moving) {
const translateY = getElementTranslateY(wrapper.value);
state.offset = Math.min(0, translateY - baseOffset());
startOffset = state.offset;
state.startOffset = state.offset;
} else {
startOffset = state.offset;
state.startOffset = state.offset;
}
state.duration = 0;
touchStartTime = Date.now();
momentumOffset = startOffset;
transitionEndTrigger = null;
state.touchStartTime = Date.now();
state.momentumOffset = state.startOffset;
state.transitionEndTrigger = null;
};
const onTouchMove = event => {
const onTouchMove = (event: Event) => {
if (props.readonly) {
return;
}
moving = true;
state.moving = true;
touch.move(event);
if (touch.isVertical()) {
moving = true;
state.moving = true;
preventDefault(event, true);
}
const moveOffset = startOffset + touch.deltaY.value;
const moveOffset = state.startOffset + touch.deltaY.value;
if (moveOffset > props.itemHeight) {
state.offset = props.itemHeight;
state.offset = props.itemHeight as number;
} else {
state.offset = startOffset + touch.deltaY.value;
state.offset = state.startOffset + touch.deltaY.value;
}
const now = Date.now();
if (now - touchStartTime > MOMENTUM_LIMIT_TIME) {
touchStartTime = now;
momentumOffset = state.offset;
if (now - state.touchStartTime > MOMENTUM_LIMIT_TIME) {
state.touchStartTime = now;
state.momentumOffset = state.offset;
}
};
const onTouchEnd = () => {
const index = getIndexByOffset(state.offset);
state.duration = DEFAULT_DURATION;
setIndex(index, true);
const distance = state.offset - momentumOffset;
const duration = Date.now() - touchStartTime;
const distance = state.offset - state.momentumOffset;
const duration = Date.now() - state.touchStartTime;
const allowMomentum =
duration < MOMENTUM_LIMIT_TIME &&
......@@ -212,25 +223,37 @@ export default create({
return;
}
};
const handleClick = (event: Event) => {
emit('click', event);
};
const wrapperStyle = computed(() => ({
transform: `translate3d(0, ${state.offset + baseOffset()}px, 0)`,
transitionDuration: `${state.duration}ms`,
transitionProperty: state.duration ? 'all' : 'none'
}));
onMounted(() => {
setIndex(+props.defaultIndex);
});
watch(
() => props.listData,
val => {
if (val) {
state.options = val as PickerObjectColumn[];
}
}
);
watch(
() => props.defaultIndex,
val => {
setIndex(+val);
}
);
return {
...toRefs(state),
wrapper,
onTouchStart,
onTouchMove,
onTouchEnd,
wrapperStyle,
state,
stopMomentum,
columns: state.options,
height: Number(props.visibleItemCount) * props.itemHeight
height: Number(props.visibleItemCount) * +props.itemHeight
};
}
});
......
export const commonProps = {
listData: {
type: Array,
default: []
default: () => {
return [];
}
},
readonly: {
type: Boolean,
default: false
},
visibleItemCount: {
type: [Number],
type: [Number, String],
default: 7
},
defaultIndex: {
......@@ -16,7 +18,7 @@ export const commonProps = {
default: 0
},
itemHeight: {
type: [Number],
type: [Number, String],
default: 35
}
};
......@@ -95,8 +95,8 @@ export default createDemo({
}`
);
const desc3 = ref(
`${listData3[0].text}
${listData3[0].children[0].text}
`${listData3[0].text}
${listData3[0].children[0].text}
${listData3[0].children[0].children[0].text}`
);
const descList = [desc, desc2, desc3];
......@@ -110,16 +110,16 @@ export default createDemo({
desc,
desc2,
desc3,
open: index => {
open: (index: number) => {
showList[index - 1].value = true;
},
confirm: res => {
confirm: (res: any) => {
desc.value = res;
},
confirm2: res => {
confirm2: (res: any) => {
desc2.value = res.join(' ');
},
confirm3: res => {
confirm3: (res: any) => {
desc3.value = res.join(' ');
}
};
......
# picker组件
# Picker组件
### 介绍
......
<template>
<view-block class="nut-picker">
<view-block :class="classes">
<nut-popup
position="bottom"
:style="{ height: height + 56 + 'px' }"
......@@ -7,9 +7,9 @@
@close="close"
>
<view-block class="nut-picker__bar">
<view-block class="nut-picker__left" @click="close()"> 取消</view-block>
<view-block class="nut-picker__left" @click="close">取消</view-block>
<view-block> {{ title }}</view-block>
<view-block @click="confirm()"> 确定</view-block>
<view-block @click="confirm()">确定</view-block>
</view-block>
<view-block class="nut-picker__column">
......@@ -45,12 +45,18 @@
</view-block>
</template>
<script lang="ts">
import { reactive, ref, watch, computed, toRaw } from 'vue';
import { reactive, watch, computed, toRaw, toRefs } from 'vue';
import { createComponent } from '@/utils/create';
import column from './Column.vue';
import popup from '@/packages/popup/index.vue';
import { commonProps } from './commonProps';
const { create } = createComponent('picker');
import {
PickerObjOpt,
PickerOption,
PickerObjectColumn,
PickerObjectColumns
} from './types';
const { create, componentName } = createComponent('picker');
export default create({
children: [column, popup],
......@@ -65,61 +71,82 @@ export default create({
},
...commonProps
},
components: { column },
emits: ['close', 'change', 'confirm', 'update:isVisible'],
setup(props, { emit }) {
const show = ref(false);
const defaultIndex = ref(props.defaultIndex);
const formattedColumns: any = ref(props.listData);
//临时变量,当点击确定时候赋值
let _defaultIndex = props.defaultIndex;
const childrenKey = 'children';
const valuesKey = 'values';
const state = reactive({
show: false,
formattedColumns: props.listData as PickerObjectColumn[],
defaultIndex: props.defaultIndex as number
});
//临时变量,当点击确定时候赋值
let _defaultIndex = props.defaultIndex;
let defaultIndexList: number[] = [];
watch(
() => props.isVisible,
val => {
show.value = val;
}
);
const classes = computed(() => {
const prefixCls = componentName;
return {
[prefixCls]: true
};
});
watch(
() => props.listData,
val => {
formattedColumns.value = val;
}
);
const top = computed(() => {
return (Number(+props.visibleItemCount - 1) / 2) * +props.itemHeight;
});
const addDefaultIndexList = listData => {
defaultIndexList = [];
listData.forEach(res => {
defaultIndexList.push(res.defaultIndex);
});
};
const dataType = computed(() => {
const firstColumn = formattedColumns.value[0] || {};
const height = computed(() => {
return Number(props.visibleItemCount) * +props.itemHeight;
});
const dataType = computed(() => {
const firstColumn = state.formattedColumns[0] as PickerObjectColumn;
if (typeof firstColumn === 'object') {
if (firstColumn?.[childrenKey]) {
if (firstColumn[childrenKey]) {
return 'cascade';
} else if (firstColumn?.[valuesKey]) {
addDefaultIndexList(props.listData);
//多列
addDefaultIndexList(props.listData as PickerObjectColumn[]);
return 'multipleColumns';
}
}
return 'text';
});
const formatCascade = (listData, defaultIndex) => {
const formatted: any[] = [];
let children = listData;
const columnList = computed(() => {
if (dataType.value === 'text') {
return [
{ values: state.formattedColumns, defaultIndex: state.defaultIndex }
];
} else if (dataType.value === 'multipleColumns') {
return state.formattedColumns;
} else if (dataType.value === 'cascade') {
return formatCascade(
state.formattedColumns as PickerObjectColumn[],
state.defaultIndex
);
}
return state.formattedColumns;
});
const addDefaultIndexList = (listData: PickerObjectColumn[]) => {
defaultIndexList = [];
listData.forEach(res => {
defaultIndexList.push(res.defaultIndex as number);
});
};
const formatCascade = (
listData: PickerObjectColumn[],
defaultIndex: number
) => {
const formatted: PickerObjectColumn[] = [];
let children = listData as PickerObjectColumns;
children.defaultIndex = defaultIndex;
while (children) {
formatted.push({
values: children,
defaultIndex: children.defaultIndex
defaultIndex: defaultIndex
});
children = children?.[children.defaultIndex || 0].children;
}
......@@ -127,94 +154,104 @@ export default create({
return formatted;
};
const columnList = computed(() => {
if (dataType.value === 'text') {
return [
{ values: formattedColumns.value, defaultIndex: defaultIndex.value }
];
} else if (dataType.value === 'multipleColumns') {
return formattedColumns.value;
} else if (dataType.value === 'cascade') {
return formatCascade(formattedColumns.value, defaultIndex.value);
}
return formattedColumns.value;
});
const getCascadeData = (listData, defaultIndex) => {
let arr = listData;
const getCascadeData = (
listData: PickerObjectColumn[],
defaultIndex: number
) => {
let arr = listData as PickerObjectColumns;
arr.defaultIndex = defaultIndex;
const dataList: string[] = [];
while (arr) {
const item = arr[arr.defaultIndex ?? 0];
dataList.push(item.text);
dataList.push(item.text as string);
arr = item.children;
}
return dataList;
};
return {
show,
column,
title: props.title,
dataType,
columnList,
top: (Number(props.visibleItemCount - 1) / 2) * props.itemHeight,
height: Number(props.visibleItemCount) * props.itemHeight,
close: () => {
emit('close');
emit('update:isVisible', false);
},
changeHandler: (columnIndex, dataIndex) => {
if (dataType.value === 'cascade') {
let cursor: any = toRaw(formattedColumns.value);
//最外层使用props.defaultIndex作为初始index
if (columnIndex === 0) {
defaultIndex.value = dataIndex;
} else {
let i = 0;
while (cursor) {
if (i === columnIndex) {
cursor.defaultIndex = dataIndex;
} else if (i > columnIndex) {
cursor.defaultIndex = 0;
}
cursor = cursor[cursor.defaultIndex || 0].children;
i++;
const close = () => {
emit('close');
emit('update:isVisible', false);
};
const changeHandler = (columnIndex: number, dataIndex: number) => {
if (dataType.value === 'cascade') {
let cursor = toRaw(state.formattedColumns) as PickerObjectColumns;
if (columnIndex === 0) {
state.defaultIndex = dataIndex;
} else {
let i = 0;
while (cursor) {
if (i === columnIndex) {
cursor.defaultIndex = dataIndex;
} else if (i > columnIndex) {
cursor.defaultIndex = 0;
}
cursor = cursor[cursor.defaultIndex || 0].children;
i++;
}
} else if (dataType.value === 'text') {
_defaultIndex = dataIndex;
} else if (dataType.value === 'multipleColumns') {
defaultIndexList[columnIndex] = dataIndex;
const val = defaultIndexList.map(
(res, i) => toRaw(formattedColumns.value)[i].values[res]
);
console.log('val', defaultIndexList);
emit('change', val);
}
},
confirm: () => {
if (dataType.value === 'text') {
defaultIndex.value = _defaultIndex;
emit('confirm', formattedColumns.value[_defaultIndex]);
} else if (dataType.value === 'multipleColumns') {
for (let i = 0; i < defaultIndexList.length; i++) {
formattedColumns.value[i].defaultIndex = defaultIndexList[i];
}
const checkedArr = toRaw(formattedColumns.value).map(
(res: any) => res.values[res.defaultIndex]
);
console.log(formattedColumns.value);
emit('confirm', checkedArr);
} else if (dataType.value === 'cascade') {
emit(
'confirm',
getCascadeData(toRaw(formattedColumns.value), defaultIndex.value)
);
} else if (dataType.value === 'text') {
_defaultIndex = dataIndex;
} else if (dataType.value === 'multipleColumns') {
defaultIndexList[columnIndex] = dataIndex;
const val = defaultIndexList.map(
(res, i) =>
toRaw(state.formattedColumns as PickerObjectColumns)[i].values[res]
);
emit('change', val);
}
};
const confirm = () => {
if (dataType.value === 'text') {
state.defaultIndex = _defaultIndex as number;
emit('confirm', state.formattedColumns[_defaultIndex as number]);
} else if (dataType.value === 'multipleColumns') {
for (let i = 0; i < defaultIndexList.length; i++) {
state.formattedColumns[i].defaultIndex = defaultIndexList[i];
}
const checkedArr = toRaw(state.formattedColumns).map(
(res: PickerObjectColumn) =>
res.values && res.values[res.defaultIndex as number]
);
emit('confirm', checkedArr);
} else if (dataType.value === 'cascade') {
emit(
'confirm',
getCascadeData(toRaw(state.formattedColumns), state.defaultIndex)
);
}
emit('update:isVisible', false);
};
watch(
() => props.isVisible,
val => {
state.show = val;
}
);
emit('update:isVisible', false);
watch(
() => props.listData,
val => {
state.formattedColumns = val as PickerObjectColumns;
}
);
return {
classes,
...toRefs(state),
column,
dataType,
columnList,
top,
height,
close,
changeHandler,
confirm
};
}
});
......
export type PickerObjOpt = {
text?: string;
[key: string]: any;
};
export type PickerOption = string | PickerObjOpt;
export type PickerObjectColumn = {
values?: PickerOption[];
defaultIndex?: number;
children?: PickerOption[];
[key: string]: any;
};
export type PickerObjectColumns = PickerObjectColumn & PickerObjOpt[];
<template>
<div class="demo-nopading">
<h2>基础用法</h2>
<nut-textarea
v-model:value="state.val0"
@change="change"
rows="5"
placeholder="高度可拉伸"
:autosize="true"
label="留言:"
/>
<h2>显示字数统计</h2>
<nut-textarea
v-model:value="state.val1"
@change="change"
rows="5"
:limitShow="true"
maxLength="20"
type="textarea"
placeholder="设置输入五行"
label="留言:"
/>
</div>
</template>
<script lang="ts">
import { reactive } from 'vue';
import { createComponent } from '@/utils/create';
const { createDemo } = createComponent('textarea');
export default createDemo({
setup() {
const state = reactive({
val0: '',
val1: '初始数据'
});
setTimeout(function() {
state.val1 = '异步测试数据,2秒';
}, 2000);
const change = (num: string | number) => {
console.log('change: ', num);
};
const focus = (num: string | number) => {
console.log('focus:', num);
};
const blur = (num: string | number) => {
console.log('blur:', num);
};
const clear = (num: string | number) => {
console.log('clear:', num);
};
return {
state,
change,
blur,
clear,
focus
};
}
});
</script>
<style lang="scss" scoped>
.demo-nopading {
height: 100%;
background: #f7f8fa;
overflow-x: hidden;
overflow-y: auto;
padding: 0;
padding-top: 57px;
h2 {
padding-left: 25px;
margin-top: 25px;
margin-bottom: 10px;
color: #909ca4;
}
}
</style>
# Input 输入框组件
### 介绍
### 安装
``` javascript
import { createApp } from 'vue';
import { input } from '@nutui/nutui';
const app = createApp();
app.use(input);
```
## 代码演示
### 基础用法
```html
<nut-textarea
v-model:value="state.val0"
@change="change"
rows="5"
placeholder="高度可拉伸"
:autosize="true"
label="留言:"
/>
```
### 显示字数统计
```html
<nut-textarea
v-model:value="state.val1"
@change="change"
rows="5"
:limitShow="true"
maxLength="20"
type="textarea"
placeholder="设置输入五行"
label="留言:"
/>
```
| 参数 | 说明 | 类型 | 默认值 |
|--------------|----------------------------------|--------|------------------|
| value | 输入值,双向绑定 | String | - |
| placeholder | 为空时占位符 | String | - |
| label | 左侧文案 | string | - |
| maxlength | 限制最长输入字符 | string/number | - |
| rows | textarea时高度 | string/number | 2 |
| limit-show | textarea时是否展示输入字符。须设置maxlength | boolean | `false` |
| change | 输入内容时触发 | function | - |
| focus | 聚焦时触发 | function | - |
| blur | 失焦时触发 | function | - |
| clear | 点击清空时触发 | function | - |
.nut-textarea {
position: relative;
width: 100%;
padding: 10px 0px 10px 25px;
display: flex;
background: rgba(255, 255, 255, 1);
border-bottom: 1px solid rgba(234, 240, 251, 1);
font-size: 14px;
input {
width: 230px;
flex: 1;
padding: 0 10px;
}
.nut-input-label {
width: 80px;
overflow: hidden;
display: inline-block;
text-align: left;
.label-string {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.nut-textinput-clear {
width: 16px;
height: 16px;
position: absolute;
right: 15px;
}
.nut-text {
flex: 1;
padding: 0 10px;
.nut-text-limit {
float: right;
color: rgba(153, 153, 153, 1);
}
.nut-text-core {
outline: none;
display: block;
box-sizing: border-box;
width: 100%;
min-width: 0;
margin: 0;
padding: 0;
color: #323233;
line-height: inherit;
text-align: left;
background-color: transparent;
border: 0;
resize: none;
}
}
}
<template>
<view class="nut-textarea">
<view class="nut-input-label">
<view v-if="props.label" class="label-string">{{ props.label }}</view>
</view>
<view class="nut-text">
<textarea
:style="styles"
:rows="props.rows"
@input="valueChange"
v-model="state.curretvalue"
class="nut-text-core"
:maxlength="maxLength"
:placeholder="props.placeholder"
:disabled="props.disabled"
:readonly="props.readonly"
>
</textarea>
<view class="nut-text-limit" v-if="limitShow">
<view :class="[{ 'nut-field-over': state.textNum > maxLength }]">{{
state.textNum
}}</view>
<view>/{{ maxLength }}</view>
</view>
</view>
</view>
</template>
<script lang="ts">
import { ref, toRefs, reactive, computed } from 'vue';
import { createComponent } from '@/utils/create';
const { componentName, create } = createComponent('textarea');
interface Events {
eventName: 'change' | 'focus' | 'blur' | 'clear' | 'update:value';
params: (string | number | Event)[];
}
export default create({
props: {
textAlign: {
type: String,
default: 'left'
},
limitShow: {
type: Boolean,
default: false
},
maxLength: {
type: String,
default: ''
},
rows: {
type: String,
default: ''
},
label: {
type: String,
default: ''
},
placeholder: {
type: String,
default: '请输入信息'
},
readonly: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
autosize: {
type: Boolean,
default: false
},
value: {
type: [String, Number],
default: ''
}
},
emits: ['change', 'update:value', 'blur', 'focus', 'clear', 'error'],
setup(props, { emit }) {
const { maxLength } = props;
const { value } = toRefs(props);
const active = ref(false);
const state = reactive({
curretvalue: value,
textNum: String(value.value).length
});
const classes = computed(() => {
return {
[componentName]: true
};
});
const styles = computed(() => {
return {
'text-align': props.textAlign,
resize: props.autosize ? 'vertical' : 'none'
};
});
const emitChange = (envs: Array<Events>) => {
envs.forEach((item: Events) => {
return emit(item.eventName, ...item.params);
});
};
const valueChange = (e: Event) => {
const input = e.target as HTMLInputElement;
let val = input.value;
if (maxLength && val.length > Number(maxLength)) {
val = val.slice(0, Number(maxLength));
}
state.textNum = val.length;
emitChange([
{
eventName: 'update:value',
params: [val]
},
{
eventName: 'change',
params: [val]
}
]);
};
const valueFocus = (e: Event) => {
active.value = true;
const input = e.target as HTMLInputElement;
let val = input.value;
val = String(val);
emitChange([
{
eventName: 'update:value',
params: [state.curretvalue]
},
{
eventName: 'focus',
params: [val]
}
]);
};
const valueBlur = (e: Event) => {
setTimeout(() => {
active.value = false;
}, 400);
const input = e.target as HTMLInputElement;
let val = input.value;
val = String(val);
emitChange([
{
eventName: 'update:value',
params: [val]
},
{
eventName: 'blur',
params: [val]
}
]);
};
const handleClear = () => {
const val = '';
emitChange([
{
eventName: 'update:value',
params: [val]
},
{
eventName: 'clear',
params: [val]
}
]);
};
return {
props,
value,
state,
styles,
active,
maxLength,
valueChange,
valueFocus,
valueBlur,
handleClear,
emitChange
};
}
});
</script>
<style lang="scss">
@import 'index.scss';
</style>
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册