| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- <template>
- <view
- class="nana-slider"
- :id="id"
- :style="{
- opacity: disabled ? 0.6 : 1,
- width: direction == 'horizontal' ? undefined : themeContext.resolveSize(dotSize),
- height: direction == 'vertical' ? undefined : themeContext.resolveSize(dotSize),
- minWidth: themeContext.resolveSize(dotSize),
- minHeight: themeContext.resolveSize(dotSize),
- ...innerStyle,
- }"
- @touchstart="handleTouchStart"
- @touchmove="handleTouchMove"
- @touchend="handleTouchEnd"
- @touchcancel="handleTouchEnd"
- @mousedown="handleTouchStart"
- @mousemove="handleTouchMove"
- @mouseup="handleTouchEnd"
- >
- <view
- class="dot"
- :style="{
- ...themeStyles.toggleDot.value,
- width: themeContext.resolveSize(dotSize),
- height: themeContext.resolveSize(dotSize),
- borderRadius: themeContext.resolveSize(dotSize / 2),
- backgroundColor: themeContext.resolveThemeColor(dotColor),
- left: direction == 'horizontal' ? `calc(${valuePrecent}% - ${themeContext.resolveSize(dotSize / 2)})` : undefined ,
- top: direction == 'vertical' ? `calc(${valuePrecent}% - ${themeContext.resolveSize(dotSize / 2)})` : undefined,
- ...dotStyle,
- }"
- />
- <view class="bar" :style="{
- backgroundColor: themeContext.resolveThemeColor(inactiveColor),
- width: direction == 'horizontal' ? '100%' : themeContext.resolveThemeSize(size),
- height: direction == 'vertical' ? '100%' : themeContext.resolveThemeSize(size),
- }">
- <view
- class="active"
- :style="{
- backgroundColor: themeContext.resolveThemeColor(activeColor),
- width: direction == 'horizontal' ? `${valuePrecent}%` : themeContext.resolveThemeSize(size),
- height: direction == 'vertical' ? `${valuePrecent}%` : themeContext.resolveThemeSize(size),
- }"
- />
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { computed, getCurrentInstance, onMounted, toRef } from 'vue';
- import { propGetThemeVar, useTheme, type ViewStyle } from '../theme/ThemeDefine';
- import { DynamicVar } from '../theme/ThemeTools';
- import { useFieldChildValueInjector } from './FormContext';
- import { RandomUtils } from '@imengyu/imengyu-utils';
- export interface SliderProps {
- /**
- * 数值
- * @default 50
- */
- modelValue?: number;
- /**
- * 滑动方向
- * @default horizontal
- */
- direction?: 'horizontal' | 'vertical';
- /**
- * 主颜色
- * @default primary
- */
- activeColor?: string;
- /**
- * 反色
- * @default grey
- */
- inactiveColor?: string;
- /**
- * 条的大小
- * @default 6
- */
- size?: string|number;
- /**
- * 开关点的颜色
- * @default lightButton
- */
- dotColor?: string;
- /**
- * 开关大小
- * @default 40
- */
- dotSize?: number;
- /**
- * 点的自定义样式
- */
- dotStyle?: ViewStyle;
- /**
- * 是否禁用
- * @default false
- */
- disabled?: boolean;
- /**
- * 最小值
- * @default 0
- */
- min?: number;
- /**
- * 最大值
- * @default 100
- */
- max?: number;
- /**
- * 步长
- */
- step?: number;
- /**
- * 内部样式
- */
- innerStyle?: ViewStyle,
- }
- const emit = defineEmits([ 'update:modelValue' ])
- const props = withDefaults(defineProps<SliderProps>(), {
- modelValue: 50,
- activeColor: () => propGetThemeVar('SliderActiveColor', 'primary'),
- inactiveColor: () => propGetThemeVar('SliderInactiveColor', 'background.switch'),
- dotColor: () => propGetThemeVar('SliderDotColor', 'lightButton'),
- dotSize: () => propGetThemeVar('SliderDotSize', 40),
- disabled: false,
- direction: 'horizontal',
- min: 0,
- max: 100,
- size: () => propGetThemeVar('SliderSize', 6),
- });
- const themeContext = useTheme();
- const themeStyles = themeContext.useThemeStyles({
- toggleDot: {
- boxShadow: DynamicVar('SliderDotShadow', 'rgba(0, 0, 0, 0.1) 2px 2px 8px 2px'),
- },
- });
- const valuePrecent = computed(() => {
- return (value.value - props.min) / (props.max - props.min) * 100
- });
- const {
- value,
- updateValue,
- context,
- } = useFieldChildValueInjector(
- toRef(props, 'modelValue'),
- (v) => emit('update:modelValue', v)
- );
- const instance = getCurrentInstance();
- const id = RandomUtils.genNonDuplicateID(12);
- let absLeft = 0;
- let absWidth = 0;
- let pressed = false;
- function doUpdateValue(v: number) {
- v = Math.max(props.min, Math.min(props.max, v));
- if (props.step)
- v = Math.round(v / props.step) * props.step;
- updateValue(v);
- }
- function handleDrag(x: number) {
- let v = (x - absLeft) / absWidth;
- v = v * (props.max - props.min) + props.min;
- if (v !== value.value)
- doUpdateValue(v);
- }
- function handleTouchStart(e: any) {
- if (props.disabled)
- return;
- e.stopPropagation();
- const query = uni.createSelectorQuery();
- query
- // #ifdef MP
- .in(instance)
- // #endif
- .select('#' + id)
- .boundingClientRect((res) => {
- if (res) {
- absLeft = (res as any).left;
- absWidth = (res as any).width;
- pressed = true;
- }
- handleDrag(e.touches[0]?.clientX);
- }).exec();
- }
- function handleTouchMove(e: any) {
- if (props.disabled)
- return;
- if (!pressed)
- return;
- e.stopPropagation();
- handleDrag(e.touches[0]?.clientX);
- }
- function handleTouchEnd() {
- pressed = false;
- }
- defineOptions({
- options: {
- styleIsolation: "shared",
- virtualHost: true,
- }
- })
- </script>
- <style lang="scss">
- .nana-slider {
- display: flex;
- flex-direction: row;
- justify-content: center;
- align-items: center;
- position: relative;
- overflow: visible;
- flex: 1;
- .bar {
- display: flex;
- position: relative;
- overflow: hidden;
- .active {
- position: absolute;
- }
- }
- .dot {
- display: flex;
- position: absolute;
- z-index: 10,
- }
- }
- </style>
|