| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- <template>
- <FlexRow position="relative" align="center">
- <slot name="addonBefore">
- <template v-if="addonBefore">
- <text>{{ addonBefore }}</text>
- <Width :width="prefixTextSpace" />
- </template>
- </slot>
- <slot name="minus" :disabled="disabled || value <= min" :onClick="minus">
- <IconButton
- :icon="minusIcon"
- :disabled="disabled || value <= min"
- :buttonStyle="{
- ...themeStyles.button.value,
- ...selectObjectByType(size, 'medium', {
- small: themeStyles.buttonSmall.value,
- medium: themeStyles.buttonMedium.value,
- large: themeStyles.buttonLarge.value,
- })
- }"
- :padding="5"
- :size="iconSize"
- @click="minus"
- />
- </slot>
- <slot
- name="center"
- :disableInput="disableInput"
- :value="stringValue"
- :integer="integer",
- :onChangeText="onChangeText"
- :onBlur="onTextBlur"
- :editable="!disabled"
- >
- <view :style="{
- ...themeStyles.inputWrapper.value,
- ...selectObjectByType(size, 'medium', {
- small: themeStyles.inputWrapperSmall.value,
- medium: themeStyles.inputWrapperMedium.value,
- large: themeStyles.inputWrapperLarge.value,
- }),
- }">
- <input
- :style="{
- ...themeStyles.input.value,
- width: themeContext.resolveThemeSize(inputWidth),
- color: themeContext.resolveThemeColor(textColor)
- }"
- :disabled="disabled || disableInput"
- :value="stringValue"
- :placeholder="placeholder"
- :placeholder-style="`color: ${themeContext.resolveThemeColor(placeholderTextColor)}`"
- type="number"
- confirm-type="done"
- @input="(e: any) => onChangeText(e.detail.value)"
- @blur="onTextBlur"
- @confirm="onTextBlur"
- />
- </view>
- </slot>
- <slot name="add" :disabled="disabled || !max || value >= max" :onClick="add">
- <IconButton
- :icon="addIcon"
- :disabled="disabled || (max ? value >= max : undefined)"
- :buttonStyle="{
- ...themeStyles.button.value,
- ...selectObjectByType(size, 'medium', {
- small: themeStyles.buttonSmall.value,
- medium: themeStyles.buttonMedium.value,
- large: themeStyles.buttonLarge.value,
- })
- }"
- :padding="5"
- :size="iconSize"
- @click="add"
- />
- </slot>
- <slot name="addonAfter">
- <template v-if="addonAfter">
- <Width :width="prefixTextSpace" />
- <text>{{ addonAfter }}</text>
- </template>
- </slot>
- </FlexRow>
- </template>
- <script setup lang="ts">
- import { computed, onMounted, ref, toRef, watch } from 'vue';
- import IconButton from '../basic/IconButton.vue';
- import FlexRow from '../layout/FlexRow.vue';
- import { propGetThemeVar, useTheme } from '../theme/ThemeDefine';
- import { StringUtils } from '@imengyu/imengyu-utils';
- import { DynamicColor, DynamicSize, selectObjectByType } from '../theme/ThemeTools';
- import { useFieldChildValueInjector } from './FormContext';
- import Width from '../layout/space/Width.vue';
- export interface StepperProps {
- /**
- * 当前数值
- * @default 0
- */
- modelValue?: number;
- /**
- * 最大值,为空无限制
- * @default undefined
- */
- max?: number;
- /**
- * 最小值
- * @default 1
- */
- min?: number;
- /**
- * 中间输入框的宽度
- * @default 50
- */
- inputWidth?: number;
- /**
- * 组件大小
- * @default 'medium'
- */
- size?: 'small' | 'medium' | 'large';
- /**
- * 步长,每次点击时改变的值
- * @default 1
- */
- step?: number;
- /**
- * 初始值,当 value 为空时生效
- * @default 0
- */
- defaultValue?: number;
- /**
- * 固定显示的小数位数
- * @default 0
- */
- decimalLength?: number;
- /**
- * 是否禁用
- * @default false
- */
- disabled?: boolean;
- /**
- * 是否禁用输入框
- * @default false
- */
- disableInput?: boolean;
- /**
- * 是否只允许输入整数
- * @default false
- */
- integer?: boolean;
- /**
- * 输入框的文本颜色
- */
- textColor?: string,
- /**
- * 输入框的占位符
- */
- placeholder?: string,
- /**
- * 输入框的占位符颜色
- */
- placeholderTextColor?: string,
- /**
- * 加号图标名称
- * @default 'add-bold'
- */
- addIcon?: string;
- /**
- * 键号图标名称
- * @default 'minus-bold'
- */
- minusIcon?: string;
- /**
- * 组件左侧添加的文本
- */
- addonBefore?: string,
- /**
- * 组件右侧添加的文本
- */
- addonAfter?: string,
- }
- const emit = defineEmits([ 'update:modelValue' ])
- const props = withDefaults(defineProps<StepperProps>(), {
- modelValue: 0,
- max: undefined,
- min: 1,
- inputWidth: () => propGetThemeVar('StepperInputWidth', 100),
- step: 1,
- defaultValue: 0,
- decimalLength: 0,
- disabled: false,
- disableInput: false,
- integer: false,
- addIcon: () => propGetThemeVar('StepperAddIcon', 'add-bold'),
- minusIcon: () => propGetThemeVar('StepperMinusIcon', 'minus-bold'),
- });
- const themeContext = useTheme();
- const themeStyles = themeContext.useThemeStyles({
- button: {
- borderRadius: DynamicSize('StepperButtonBorderRadius', 0),
- backgroundColor: DynamicColor('StepperButtonBackgroundColor', 'light'),
- },
- buttonLarge: {
- borderRadius: DynamicSize('StepperButtonBorderRadius', 0),
- paddingTop: DynamicSize('StepperButtonPaddingVertical', 8),
- paddingBottom: DynamicSize('StepperButtonPaddingVertical', 8),
- paddingLeft: DynamicSize('StepperButtonPaddingHorizontal', 8),
- paddingRight: DynamicSize('StepperButtonPaddingHorizontal', 8),
- backgroundColor: DynamicColor('StepperButtonBackgroundColor', 'light'),
- },
- buttonMedium: {
- paddingTop: DynamicSize('StepperButtonPaddingVertical', 4),
- paddingBottom: DynamicSize('StepperButtonPaddingVertical', 4),
- paddingLeft: DynamicSize('StepperButtonPaddingHorizontal', 4),
- paddingRight: DynamicSize('StepperButtonPaddingHorizontal', 4),
- },
- buttonSmall: {
- borderRadius: DynamicSize('StepperButtonBorderRadius', 0),
- paddingTop: DynamicSize('StepperButtonPaddingVertical', 2),
- paddingBottom: DynamicSize('StepperButtonPaddingVertical', 2),
- paddingLeft: DynamicSize('StepperButtonPaddingHorizontal', 2),
- paddingRight: DynamicSize('StepperButtonPaddingHorizontal', 2),
- },
- inputWrapper: {
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- alignSelf: 'stretch',
- backgroundColor: DynamicColor('StepperInputBackgroundColor', 'light'),
- },
- inputWrapperSmall: {
- paddingTop: DynamicSize('StepperInputPaddingVertical', 0),
- paddingBottom: DynamicSize('StepperInputPaddingVertical', 0),
- paddingLeft: DynamicSize('StepperInputPaddingHorizontal', 6),
- paddingRight: DynamicSize('StepperInputPaddingHorizontal', 6),
- marginLeft: DynamicSize('StepperInputMarginHorizontal', 2),
- marginRight: DynamicSize('StepperInputMarginHorizontal', 2),
- marginTop: DynamicSize('StepperInputMarginVertical', 0),
- marginBottom: DynamicSize('StepperInputMarginVertical', 0),
- },
- inputWrapperMedium: {
- paddingTop: DynamicSize('StepperInputPaddingVertical', 0),
- paddingBottom: DynamicSize('StepperInputPaddingVertical', 0),
- paddingLeft: DynamicSize('StepperInputPaddingHorizontal', 10),
- paddingRight: DynamicSize('StepperInputPaddingHorizontal', 10),
- marginLeft: DynamicSize('StepperInputMarginHorizontal', 4),
- marginRight: DynamicSize('StepperInputMarginHorizontal', 4),
- marginTop: DynamicSize('StepperInputMarginVertical', 0),
- marginBottom: DynamicSize('StepperInputMarginVertical', 0),
- },
- inputWrapperLarge: {
- paddingTop: DynamicSize('StepperInputPaddingVertical', 0),
- paddingBottom: DynamicSize('StepperInputPaddingVertical', 0),
- paddingLeft: DynamicSize('StepperInputPaddingHorizontal', 20),
- paddingRight: DynamicSize('StepperInputPaddingHorizontal', 20),
- marginLeft: DynamicSize('StepperInputMarginHorizontal', 8),
- marginRight: DynamicSize('StepperInputMarginHorizontal', 8),
- marginTop: DynamicSize('StepperInputMarginVertical', 0),
- marginBottom: DynamicSize('StepperInputMarginVertical', 0),
- },
- input: {
- textAlign: 'center',
- color: DynamicColor('StepperInputTextColor', 'text.content'),
- },
- });
- const prefixTextSpace = computed(() => themeContext.resolveThemeSize('StepperPrefixTextSpace', 12));
- const iconSize = computed(() => {
- switch (props.size) {
- case 'small':
- return themeContext.resolveThemeSize('StepperIconSizeSmall', 24);
- default:
- case 'medium':
- return themeContext.resolveThemeSize('StepperIconSizeMedium', 36);
- case 'large':
- return themeContext.resolveThemeSize('StepperIconSizeLarge', 48);
- }
- });
- const {
- value,
- updateValue,
- } = useFieldChildValueInjector(
- toRef(props, 'modelValue'),
- (v) => emit('update:modelValue', v)
- );
- const stringValue = ref('');
- watch(() => [ value.value, props.decimalLength, props.integer ], () => {
- const v = value.value, d = props.decimalLength, i = props.integer;
- //数据更改时更新文字
- stringValue.value = ((i || d === 0) ? v.toString() : v.toFixed(d));
- })
- function setTextString(v: number) {
- stringValue.value = ((props.integer || props.decimalLength === 0) ? Math.floor(v).toString() : v.toFixed(props.decimalLength));
- }
- function onChangeText(v: string) {
- stringValue.value = v;
- }
- function onTextBlur() {
- const s = stringValue.value;
- //输入框失去焦点后校验数值,用户可以从输入框直接输入数据
- if (StringUtils.isNullOrEmpty(s) || !StringUtils.isNumber(s)) {
- setTextString(props.defaultValue);
- emitChange(props.defaultValue);
- return;
- }
- let newValue = props.integer ? parseInt(s, 10) : parseFloat(s);
- if (newValue < props.min )
- newValue = props.min;
- if (props.max && newValue > props.max)
- newValue = props.max;
- setTextString(newValue);
- emitChange(newValue);
- }
- function emitChange(newValue: number) {
- updateValue(newValue);
- }
- function add() {
- //加
- let newValue = value.value + props.step;
- if (props.max && newValue > props.max)
- newValue = props.max;
- emitChange(newValue);
- }
- function minus() {
- //键
- let newValue = value.value - props.step;
- if (newValue < props.min )
- newValue = props.min;
- emitChange(newValue);
- }
- onMounted(() => {
- setTextString(props.modelValue);
- })
- </script>
|