| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260 |
- <template>
- <view
- :id="id"
- class="nana-rate"
- @touchstart="handleTouchStart"
- @touchmove="handleTouchMove"
- @touchend="handleTouchEnd"
- @mouseup="handleTouchEnd"
- @mousedown="handleTouchStart"
- @mousemove="handleTouchMove"
- >
- <template
- v-for="i of count"
- :key="i"
- >
- <view v-if="i - 1 === Math.floor(value) && i - 1 < Math.ceil(value)" class="active-half">
- <view :style="(starActiveHalfStyle as any)">
- <Icon
- :icon="icon"
- v-bind="props.starActiveStyle"
- :color="disabled ? starDisableColor : starActiveColor"
- :size="size"
- :style="starActiveStyle"
- />
- </view>
- <Icon
- :icon="voidIcon"
- v-bind="props.starStyle"
- :color="disabled ? starDisableColor : starColor"
- :size="size"
- :style="starDeactiveStyle"
- />
- </view>
- <Icon
- v-else-if="i <= value"
- :icon="icon"
- v-bind="props.starActiveStyle"
- :color="disabled ? starDisableColor : starActiveColor"
- :size="size"
- :style="starActiveStyle"
- />
- <Icon
- v-else-if="i > value"
- :icon="voidIcon"
- v-bind="props.starStyle"
- :color="disabled ? starDisableColor : starColor"
- :size="size"
- :style="starDeactiveStyle"
- />
- </template>
- </view>
- </template>
- <script setup lang="ts">
- import { computed, getCurrentInstance, toRef } from 'vue';
- import { propGetThemeVar, useTheme } from '../theme/ThemeDefine';
- import { useFieldChildValueInjector } from './FormContext';
- import type { IconProps } from '../basic/Icon.vue';
- import Icon from '../basic/Icon.vue';
- import { RandomUtils } from '@imengyu/imengyu-utils';
- import { rpx2px } from '../theme/ThemeTools';
- export interface RateProps {
- /**
- * 评星组件参数值
- * @default 0
- */
- modelValue?: number;
- /**
- * 星星的数量
- * @default 5
- */
- count?: number;
- /**
- * 是否可以为0星
- * @default false
- */
- canbeZero?: boolean,
- /**
- * 星星未激活自定义样式
- */
- starStyle?: IconProps;
- /**
- * 星星激活自定义样式
- */
- starActiveStyle?: IconProps;
- /**
- * 星星激活的颜色
- * @default warning
- */
- starActiveColor?: string;
- /**
- * 星星未激活的颜色
- * @default grey
- */
- starColor?: string;
- /**
- * 星星禁用的颜色
- * @default grey
- */
- starDisableColor?: string;
- /**
- * 选中时的图标
- * @default 'favorite-filling'
- */
- icon?: string;
- /**
- * 未选中时的图标
- * @default 'favorite'
- */
- voidIcon?: string;
- /**
- * 星星之间的间距
- * @default 3
- */
- space?: number;
- /**
- * 用户是否可以选择出半星
- * @default false
- */
- half?: boolean;
- /**
- * 是否只读
- * @default false
- */
- readonly?: boolean;
- /**
- * 评星组件是否禁用
- * @default false
- */
- disabled?: boolean;
- /**
- * 评星组件大小
- * @default 48
- */
- size?: number;
- }
- const emit = defineEmits([ 'update:modelValue' ])
- const props = withDefaults(defineProps<RateProps>(), {
- modelValue: 0,
- count: 5,
- starActiveColor: () => propGetThemeVar('RateStarActiveColor', 'warning'),
- starColor: () => propGetThemeVar('RateStarColor', 'grey'),
- starDisableColor: () => propGetThemeVar('RateStarDisableColor', 'grey'),
- icon: () => propGetThemeVar('RateIcon', 'favorite-filling'),
- voidIcon: () => propGetThemeVar('RateVoidIcon', 'favorite'),
- space: () => propGetThemeVar('RateSpace', 3),
- size: () => propGetThemeVar('RateSize', 48),
- });
- const themeContext = useTheme();
- const starActiveStyle = computed(() => ({
- marginRight: themeContext.resolveThemeSize(props.space),
- ...props.starActiveStyle,
- }));
- const starActiveHalfStyle = computed(() => ({
- ...props.starActiveStyle,
- position: 'absolute',
- left: 0,
- top: 0,
- width: '50%',
- overflow: 'hidden',
- zIndex: 1,
- }));
- const starDeactiveStyle = computed(() => ({
- marginRight: themeContext.resolveThemeSize(props.space),
- ...props.starStyle,
- }));
- const id = RandomUtils.genNonDuplicateID(12);
- const instance = getCurrentInstance();
- const width = computed(() => (props.size + props.space) * props.count);
- const {
- value,
- updateValue,
- } = useFieldChildValueInjector(
- toRef(props, 'modelValue'),
- (v) => emit('update:modelValue', v)
- );
- let absLeft = 0;
- let pressed = false;
- function handleDrag(x: number) {
- let v = (x - absLeft) / rpx2px(width.value) * props.count;
- if (props.half) {
- let demcial = v - Math.floor(v);
- if (demcial < 0.2) demcial = 0;
- else if (demcial > 0.8) demcial = 1;
- else demcial = 0.5;
- v = Math.floor(v) + demcial;
- }
- else {
- v = Math.ceil(v);
- }
- if (v === 0 && !props.canbeZero)
- v = props.half ? 0.5 : 1;
- v = Math.max(0, Math.min(v, props.count));
- if (v !== value.value) {
- updateValue(v);
- }
- }
- function handleTouchStart(e: any) {
- if (props.readonly || props.disabled)
- return;
- e.stopPropagation();
- const query = uni.createSelectorQuery();
- query
- .in(instance)
- .select('#' + id)
- .boundingClientRect((res) => {
- if (res)
- absLeft = (res as any).left;
- handleDrag(e.touches[0]?.clientX);
- pressed = true;
- }).exec();
- }
- function handleTouchMove(e: any) {
- if (props.readonly || 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-rate {
- display: flex;
- flex-direction: row;
- position: relative;
- overflow: hidden;
- .active-half {
- position: relative;
- }
- }
- </style>
|