tangning vor 8 Monaten
Ursprung
Commit
320315ad52

+ 2 - 0
src/components/Form/src/componentMap.ts

@@ -24,6 +24,7 @@ import {
 import ApiRadioGroup from './components/ApiRadioGroup.vue';
 import RadioButtonGroup from './components/RadioButtonGroup.vue';
 import ApiSelect from './components/ApiSelect.vue';
+import NumberRange from './components/NumberRange.vue';
 import ApiTreeSelect from './components/ApiTreeSelect.vue';
 import ApiCascader from './components/ApiCascader.vue';
 import { BasicUpload } from '/@/components/Upload';
@@ -43,6 +44,7 @@ componentMap.set('AutoComplete', AutoComplete);
 
 componentMap.set('Select', Select);
 componentMap.set('ApiSelect', ApiSelect);
+componentMap.set('NumberRange', NumberRange);
 componentMap.set('TreeSelect', TreeSelect);
 componentMap.set('ApiTreeSelect', ApiTreeSelect);
 componentMap.set('ApiRadioGroup', ApiRadioGroup);

+ 323 - 0
src/components/Form/src/components/NumberRange.vue

@@ -0,0 +1,323 @@
+<template>
+  <div class="number-range-container">
+    <!-- <div
+      :id="usePrepend ? 'prepend' : ''"
+      :class="{ 'slot-default': slotStyle === 'default', 'slot-pend ': usePrepend }"
+    >
+      <slot name="prepend">
+      </slot>
+    </div> -->
+    <div
+      class="number-range"
+      :class="{
+        'is-disabled': disabled,
+        'is-focus': isFocus,
+        'number-range-left-border-radius-0': usePrepend,
+        'number-range-right-border-radius-0': useAppend,
+      }"
+    >
+      <input-number
+        :disabled="disabled"
+        placeholder="最小值"
+        @blur="handleBlur"
+        @focus="handleFocus"
+        @change="handleChangeMinValue"
+        v-model:value="minValue_"
+        v-bind="$attrs"
+      />
+      <div class="to" style="margin: 0 10px">
+        <span>{{ to }}</span>
+      </div>
+      <input-number
+        :disabled="disabled"
+        placeholder="最大值"
+        @blur="handleBlur"
+        @focus="handleFocus"
+        @change="handleChangeMaxValue"
+        v-model:value="maxValue_"
+        v-bind="$attrs"
+      />
+    </div>
+    <!-- <div
+      :id="useAppend ? 'append' : ''"
+      :class="{ 'slot-default': slotStyle === 'default', 'slot-pend ': useAppend }"
+    >
+      <slot name="append">
+      </slot>
+    </div> -->
+  </div>
+</template>
+<script lang="ts" setup name="numberRange">
+  import { computed, ref, useSlots } from 'vue';
+  import { InputNumber } from 'ant-design-vue';
+  const props = defineProps({
+    value: {
+      type: Array,
+      default: () => [undefined, undefined], // 调用时使用v-model="[min,max]" 绑定
+    },
+    // 是否禁用
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+    to: {
+      type: String,
+      default: '至',
+    },
+    // 限制取值范围
+    valueRange: {
+      type: Array,
+      default: () => [],
+      validator(val: []) {
+        if (val && val.length > 0) {
+          // @ts-ignore
+          if (val.length !== 2) {
+            throw new Error('请传入长度为2的Number数组');
+          }
+          // @ts-ignore
+          if (typeof val[0] !== 'number' || typeof val[1] !== 'number') {
+            throw new Error('取值范围只接受Number类型,请确认');
+          }
+          // @ts-ignore
+          if (val[1] < val[0]) {
+            throw new Error('valueRange格式须为[最小值,最大值],请确认');
+          }
+        }
+        return true;
+      },
+    },
+    // 插槽样式
+    slotStyle: {
+      type: String, // default --异色背景 |  plain--无背景色
+      default: 'default',
+    },
+  });
+
+  const emit = defineEmits(['update:value', 'options-change', 'update:maxValue', 'change']);
+  console.log('props', props);
+  const minValue_ = computed({
+    get() {
+      console.log('props', props.value);
+      return props.value[0] || undefined;
+    },
+    set(value) {
+      // emit('update:minValue', value);
+      emit('update:value', [value, maxValue_.value]);
+    },
+  });
+
+  const maxValue_ = computed({
+    get() {
+      return props.value[1] || undefined;
+    },
+    set(value) {
+      // emit('update:maxValue', value);
+      emit('update:value', [minValue_.value, value]);
+    },
+  });
+
+  const handleChangeMinValue = (value: number) => {
+    console.log('handleChangeMinValue', value);
+    // 非数字空返回null
+    if (isNaN(value)) {
+      emit('update:minValue', null);
+      return;
+    }
+    // 初始化数字精度
+    const newMinValue = value;
+    console.log('handleChangeMinValue', newMinValue);
+    // min > max 交换min max
+    if (
+      typeof newMinValue === 'number' &&
+      parseFloat(String(newMinValue)) > parseFloat(String(maxValue_.value))
+    ) {
+      // 取值范围判定
+      const { min, max } = decideValueRange(Number(maxValue_.value), newMinValue);
+      console.log('handleChangeMinValue1', min, max);
+    // 更新绑定值
+      updateValue(min, max);
+    } else {
+      // 取值范围判定
+      const { min, max } = decideValueRange(newMinValue, Number(maxValue_.value));
+      console.log('handleChangeMinValue2', min, max);
+      // 更新绑定值
+      updateValue(min, max);
+    }
+  };
+
+  const handleChangeMaxValue = (value: number) => {
+    // 非数字空返回null
+    if (isNaN(value)) {
+      emit('update:maxValue', null);
+      return;
+    }
+    // 初始化数字精度
+    console.log('handleChangeMinValue2', value, minValue_.value);
+    const newMaxValue = value;
+    // max < min 交换min max
+    if (
+      typeof newMaxValue === 'number' &&
+      parseFloat(String(newMaxValue)) < parseFloat(String(minValue_.value))
+    ) {
+      // 取值范围判定
+      const { min, max } = decideValueRange(newMaxValue, Number(minValue_.value));
+      // 更新绑定值
+      updateValue(min, max);
+    } else {
+      // 取值范围判定
+      const { min, max } = decideValueRange(Number(minValue_.value), newMaxValue);
+      // 更新绑定值
+      updateValue(min, max);
+    }
+  };
+
+  // 更新数据
+  const updateValue = (min: number, max: number) => {
+    // emit('update:minValue', min);
+    // emit('update:maxValue', max);
+    console.log('updateValue', [min, max]);
+    emit('update:value', [min, max]);
+    emit('options-change', [min, max]);
+    emit('change', [min, max]);
+    console.log('props', [min, max], props);
+  };
+
+  // 取值范围判定
+  const decideValueRange = (min: number, max: number) => {
+    if (props.valueRange && props.valueRange.length > 0) {
+      // @ts-ignore
+      min =
+        min < props.valueRange[0]
+          ? props.valueRange[0]
+          : min > props.valueRange[1]
+          ? props.valueRange[1]
+          : min;
+      // @ts-ignore
+      max = max > props.valueRange[1] ? props.valueRange[1] : max;
+    }
+    return { min, max };
+  };
+
+  // input焦点事件
+  const isFocus = ref();
+
+  const handleFocus = () => {
+    isFocus.value = true;
+  };
+
+  const handleBlur = () => {
+    isFocus.value = false;
+  };
+
+  // 处理数字精度
+  const parsePrecision = (number: number, precision = 0) => {
+    return parseFloat(
+      String(Math.round(number * Math.pow(10, precision)) / Math.pow(10, precision)),
+    );
+  };
+
+  // 判断插槽是否被使用
+  // 组件外部使用时插入了
+  // <template #插槽名 >
+  // </template>
+  // 无论template标签内是否插入了内容,均视为已使用该插槽
+  const slots = useSlots();
+  const usePrepend = computed(() => {
+    // 前缀插槽
+    return slots && slots.prepend ? true : false;
+  });
+  const useAppend = computed(() => {
+    // 后缀插槽
+    return slots && slots.append ? true : false;
+  });
+</script>
+<style lang="less" scoped>
+  .number-range-container {
+    display: flex;
+    height: 100%;
+    .slot-pend {
+      white-space: nowrap;
+      color: var(--el-color-info);
+      border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
+    }
+    #prepend {
+      padding: 0 20px;
+      box-shadow: 1px 0 0 0 var(--el-input-border-color, var(--el-border-color)) inset,
+        0 1px 0 0 var(--el-input-border-color, var(--el-border-color)) inset,
+        0 -1px 0 0 var(--el-input-border-color, var(--el-border-color)) inset;
+      border-right: 0;
+      border-top-right-radius: 0;
+      border-bottom-right-radius: 0;
+    }
+    #append {
+      padding: 0 15px;
+      box-shadow: 0 1px 0 0 var(--el-input-border-color, var(--el-border-color)) inset,
+        0 -1px 0 0 var(--el-input-border-color, var(--el-border-color)) inset,
+        -1px 0 0 0 var(--el-input-border-color, var(--el-border-color)) inset;
+      border-left: 0;
+      border-top-left-radius: 0;
+      border-bottom-left-radius: 0;
+    }
+    .slot-default {
+      background-color: var(--el-fill-color-light);
+    }
+
+    .number-range-left-border-radius-0 {
+      border-top-left-radius: 0 !important;
+      border-bottom-left-radius: 0 !important;
+    }
+    .number-range-right-border-radius-0 {
+      border-top-right-radius: 0 !important;
+      border-bottom-right-radius: 0 !important;
+    }
+
+    .number-range {
+      background-color: var(--el-bg-color) !important;
+      box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
+      border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
+      padding: 0 2px;
+      display: flex;
+      flex-direction: row;
+      width: auto;
+      justify-content: center;
+      align-items: center;
+      color: var(--el-input-text-color, var(--el-text-color-regular));
+      transition: var(--el-transition-box-shadow);
+      transform: translate3d(0, 0, 0);
+      overflow: hidden;
+
+      .to {
+        margin-top: 1px;
+      }
+    }
+
+    .is-focus {
+      transition: all 0.3s;
+      box-shadow: 0 0 0 1px var(--el-color-primary) inset !important;
+    }
+    .is-disabled {
+      background-color: var(--el-input-bg-color);
+      color: var(--el-input-text-color, var(--el-text-color-regular));
+      cursor: not-allowed;
+      .to {
+        height: calc(100% - 3px);
+        background-color: var(--el-fill-color-light) !important;
+      }
+    }
+  }
+
+  :deep(.el-input) {
+    border: none;
+  }
+  :deep(.el-input__wrapper) {
+    margin: 0;
+    padding: 0 15px;
+    background-color: transparent;
+    border: none !important;
+    box-shadow: none !important;
+    &.is-focus {
+      border: none !important;
+      box-shadow: none !important;
+    }
+  }
+</style>

+ 1 - 0
src/components/Form/src/types/index.ts

@@ -112,4 +112,5 @@ export type ComponentType =
   | 'Render'
   | 'Slider'
   | 'Rate'
+  | 'NumberRange'
   | 'Divider';

+ 16 - 16
src/views/productOperation/data.ts

@@ -94,22 +94,9 @@ export const getSchemas = (val) => {
       },
     },
     {
-      field: 'shootCount1',
-      component: 'InputNumber',
-      label: '最小点位数',
-      ifShow: val != 3,
-      componentProps: {
-        min: 0,
-      },
-      colProps: {
-        xl: 7,
-        xxl: 7,
-      },
-    },
-    {
-      field: 'shootCount2',
-      component: 'InputNumber',
-      label: '最大点位数',
+      field: 'shootCounts',
+      component: 'NumberRange',
+      label: '点位数量',
       ifShow: val != 3,
       componentProps: {
         min: 0,
@@ -119,6 +106,19 @@ export const getSchemas = (val) => {
         xxl: 7,
       },
     },
+    // {
+    //   field: 'shootCount2',
+    //   component: 'InputNumber',
+    //   label: '最大点位数',
+    //   ifShow: val != 3,
+    //   componentProps: {
+    //     min: 0,
+    //   },
+    //   colProps: {
+    //     xl: 7,
+    //     xxl: 7,
+    //   },
+    // },
   ];
 };
 export const getquanjingSchemas = () => {

+ 336 - 0
src/views/productOperation/modal/NumberRange.vue

@@ -0,0 +1,336 @@
+<template>
+  <div class="number-range-container">
+    <div
+      :id="usePrepend ? 'prepend' : ''"
+      :class="{ 'slot-default': slotStyle === 'default', 'slot-pend ': usePrepend }"
+    >
+      <slot name="prepend">
+        <!-- 前缀插槽 -->
+      </slot>
+    </div>
+    <div
+      class="number-range"
+      :class="{
+        'is-disabled': disabled,
+        'is-focus': isFocus,
+        'number-range-left-border-radius-0': usePrepend,
+        'number-range-right-border-radius-0': useAppend,
+      }"
+    >
+      <InputNumber
+        :disabled="disabled"
+        placeholder="最小值"
+        @blur="handleBlur"
+        @focus="handleFocus"
+        @change="handleChangeMinValue"
+        v-model="minValue_"
+        v-bind="$attrs"
+        v-on="['update:minValue']"
+        :controls="false"
+      />
+      <div class="to">
+        <span>{{ to }}</span>
+      </div>
+      <InputNumber
+        :disabled="disabled"
+        placeholder="最大值"
+        @blur="handleBlur"
+        @focus="handleFocus"
+        @change="handleChangeMaxValue"
+        v-model="maxValue_"
+        v-bind="$attrs"
+        v-on="['update:maxValue']"
+        :controls="false"
+      />
+    </div>
+    <div
+      :id="useAppend ? 'append' : ''"
+      :class="{ 'slot-default': slotStyle === 'default', 'slot-pend ': useAppend }"
+    >
+      <slot name="append">
+        <!-- 后缀插槽 -->
+      </slot>
+    </div>
+  </div>
+</template>
+<script lang="ts" setup name="numberRange">
+  import { computed } from 'vue';
+  import { InputNumber } from 'ant-design-vue';
+  const props = defineProps({
+    modelValue: {
+      type: Array,
+      default: () => [null, null], // 调用时使用v-model="[min,max]" 绑定
+    },
+    minValue: {
+      type: Number,
+      default: null, // 调用时使用v-model:min-value="" 绑定多个v-model
+    },
+    maxValue: {
+      type: Number,
+      default: null, // 调用时使用v-model:max-value="" 绑定多个v-model
+    },
+    // 是否禁用
+    disabled: {
+      type: Boolean,
+      default: false,
+    },
+    to: {
+      type: String,
+      default: '至',
+    },
+    // 精度参数 -保留小数位数
+    precision: {
+      type: Number,
+      default: 0,
+      validator(val: number) {
+        return val >= 0 && val === parseInt(String(val), 10);
+      },
+    },
+    // 限制取值范围
+    valueRange: {
+      type: Array,
+      default: () => [],
+      validator(val: []) {
+        if (val && val.length > 0) {
+          // @ts-ignore
+          if (val.length !== 2) {
+            throw new Error('请传入长度为2的Number数组');
+          }
+          // @ts-ignore
+          if (typeof val[0] !== 'number' || typeof val[1] !== 'number') {
+            throw new Error('取值范围只接受Number类型,请确认');
+          }
+          // @ts-ignore
+          if (val[1] < val[0]) {
+            throw new Error('valueRange格式须为[最小值,最大值],请确认');
+          }
+        }
+        return true;
+      },
+    },
+    // 插槽样式
+    slotStyle: {
+      type: String, // default --异色背景 |  plain--无背景色
+      default: 'default',
+    },
+  });
+
+  const emit = defineEmits(['update:modelValue', 'update:minValue', 'update:maxValue', 'change']);
+
+  const minValue_ = computed({
+    get() {
+      return props.minValue || props.modelValue[0] || null;
+    },
+    set(value) {
+      emit('update:minValue', value);
+      emit('update:modelValue', [value, maxValue_.value]);
+    },
+  });
+
+  const maxValue_ = computed({
+    get() {
+      return props.maxValue || props.modelValue[1] || null;
+    },
+    set(value) {
+      emit('update:maxValue', value);
+      emit('update:modelValue', [minValue_.value, value]);
+    },
+  });
+
+  const handleChangeMinValue = (value: number) => {
+    // 非数字空返回null
+    if (isNaN(value)) {
+      emit('update:minValue', null);
+      return;
+    }
+    // 初始化数字精度
+    const newMinValue = parsePrecision(value, props.precision);
+    // min > max 交换min max
+    if (
+      typeof newMinValue === 'number' &&
+      parseFloat(String(newMinValue)) > parseFloat(String(maxValue_.value))
+    ) {
+      // 取值范围判定
+      const { min, max } = decideValueRange(Number(maxValue_.value), newMinValue);
+      // 更新绑定值
+      updateValue(min, max);
+    } else {
+      // 取值范围判定
+      const { min, max } = decideValueRange(newMinValue, Number(maxValue_.value));
+      // 更新绑定值
+      updateValue(min, max);
+    }
+  };
+
+  const handleChangeMaxValue = (value: number) => {
+    // 非数字空返回null
+    if (isNaN(value)) {
+      emit('update:maxValue', null);
+      return;
+    }
+    // 初始化数字精度
+    const newMaxValue = parsePrecision(value, props.precision);
+    // max < min 交换min max
+    if (
+      typeof newMaxValue === 'number' &&
+      parseFloat(String(newMaxValue)) < parseFloat(String(minValue_.value))
+    ) {
+      // 取值范围判定
+      const { min, max } = decideValueRange(newMaxValue, Number(minValue_.value));
+      // 更新绑定值
+      updateValue(min, max);
+    } else {
+      // 取值范围判定
+      const { min, max } = decideValueRange(Number(minValue_.value), newMaxValue);
+      // 更新绑定值
+      updateValue(min, max);
+    }
+  };
+
+  // 更新数据
+  const updateValue = (min: number, max: number) => {
+    emit('update:minValue', min);
+    emit('update:maxValue', max);
+    emit('update:modelValue', [min, max]);
+    emit('change', { min, max });
+  };
+
+  // 取值范围判定
+  const decideValueRange = (min: number, max: number) => {
+    if (props.valueRange && props.valueRange.length > 0) {
+      // @ts-ignore
+      min =
+        min < props.valueRange[0]
+          ? props.valueRange[0]
+          : min > props.valueRange[1]
+          ? props.valueRange[1]
+          : min;
+      // @ts-ignore
+      max = max > props.valueRange[1] ? props.valueRange[1] : max;
+    }
+    return { min, max };
+  };
+
+  // input焦点事件
+  const isFocus = ref();
+
+  const handleFocus = () => {
+    isFocus.value = true;
+  };
+
+  const handleBlur = () => {
+    isFocus.value = false;
+  };
+
+  // 处理数字精度
+  const parsePrecision = (number: number, precision = 0) => {
+    return parseFloat(
+      String(Math.round(number * Math.pow(10, precision)) / Math.pow(10, precision)),
+    );
+  };
+
+  // 判断插槽是否被使用
+  // 组件外部使用时插入了
+  // <template #插槽名 >
+  // </template>
+  // 无论template标签内是否插入了内容,均视为已使用该插槽
+  const slots = useSlots();
+  const usePrepend = computed(() => {
+    // 前缀插槽
+    return slots && slots.prepend ? true : false;
+  });
+  const useAppend = computed(() => {
+    // 后缀插槽
+    return slots && slots.append ? true : false;
+  });
+</script>
+<style lang="scss" scoped>
+  .number-range-container {
+    display: flex;
+    height: 100%;
+    .slot-pend {
+      white-space: nowrap;
+      color: var(--el-color-info);
+      border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
+    }
+    #prepend {
+      padding: 0 20px;
+      box-shadow: 1px 0 0 0 var(--el-input-border-color, var(--el-border-color)) inset,
+        0 1px 0 0 var(--el-input-border-color, var(--el-border-color)) inset,
+        0 -1px 0 0 var(--el-input-border-color, var(--el-border-color)) inset;
+      border-right: 0;
+      border-top-right-radius: 0;
+      border-bottom-right-radius: 0;
+    }
+    #append {
+      padding: 0 15px;
+      box-shadow: 0 1px 0 0 var(--el-input-border-color, var(--el-border-color)) inset,
+        0 -1px 0 0 var(--el-input-border-color, var(--el-border-color)) inset,
+        -1px 0 0 0 var(--el-input-border-color, var(--el-border-color)) inset;
+      border-left: 0;
+      border-top-left-radius: 0;
+      border-bottom-left-radius: 0;
+    }
+    .slot-default {
+      background-color: var(--el-fill-color-light);
+    }
+
+    .number-range-left-border-radius-0 {
+      border-top-left-radius: 0 !important;
+      border-bottom-left-radius: 0 !important;
+    }
+    .number-range-right-border-radius-0 {
+      border-top-right-radius: 0 !important;
+      border-bottom-right-radius: 0 !important;
+    }
+
+    .number-range {
+      background-color: var(--el-bg-color) !important;
+      box-shadow: 0 0 0 1px var(--el-input-border-color, var(--el-border-color)) inset;
+      border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
+      padding: 0 2px;
+      display: flex;
+      flex-direction: row;
+      width: 100%;
+      justify-content: center;
+      align-items: center;
+      color: var(--el-input-text-color, var(--el-text-color-regular));
+      transition: var(--el-transition-box-shadow);
+      transform: translate3d(0, 0, 0);
+      overflow: hidden;
+
+      .to {
+        margin-top: 1px;
+      }
+    }
+
+    .is-focus {
+      transition: all 0.3s;
+      box-shadow: 0 0 0 1px var(--el-color-primary) inset !important;
+    }
+    .is-disabled {
+      background-color: var(--el-input-bg-color);
+      color: var(--el-input-text-color, var(--el-text-color-regular));
+      cursor: not-allowed;
+      .to {
+        height: calc(100% - 3px);
+        background-color: var(--el-fill-color-light) !important;
+      }
+    }
+  }
+
+  :deep(.el-input) {
+    border: none;
+  }
+  :deep(.el-input__wrapper) {
+    margin: 0;
+    padding: 0 15px;
+    background-color: transparent;
+    border: none !important;
+    box-shadow: none !important;
+    &.is-focus {
+      border: none !important;
+      box-shadow: none !important;
+    }
+  }
+</style>