ie-picker.vue 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <template>
  2. <view class="w-full" @click="handleClick">
  3. <view class="flex items-center gap-x-10" :style="customStyle">
  4. <view v-if="matchValue" class="flex-1 text-fore-title" :style="getValueStyle"
  5. :class="{ 'text-[#c0c4cc]': disabled || readonly }">
  6. <slot :label="label">
  7. {{ label }}
  8. </slot>
  9. </view>
  10. <view v-else class="flex-1 text-[#c0c4cc]" :style="getPlaceholderStyle">{{ placeholder }}</view>
  11. <view v-if="!readonly" class="transition-all duration-300">
  12. <uv-icon :name="icon" size="15" color="#B3B3B3" />
  13. </view>
  14. </view>
  15. </view>
  16. <root-portal>
  17. <uv-picker ref="pickerRef" :showToolbar="false" :columns="columns" :defaultIndex="defaultIndex" :round="16"
  18. activeColor="#31A0FC" :keyName="keyLabel" :title="title" @change="handleChange" @close="onClose">
  19. <template #toolbar>
  20. <view class="flex items-center justify-between pt-20">
  21. <view class="px-46 py-20 text-28 text-fore-light" @click="handleCancel">取消</view>
  22. <text class="text-30 text-fore-title font-bold">{{ title }}</text>
  23. <view class="px-46 py-20 text-28 text-fore-title" @click="handleConfirm">确认</view>
  24. </view>
  25. </template>
  26. </uv-picker>
  27. </root-portal>
  28. </template>
  29. <script lang="ts" setup>
  30. const modelValue = defineModel<string | number>('modelValue', {
  31. default: ''
  32. });
  33. const props = defineProps({
  34. placeholder: {
  35. type: String,
  36. default: '请选择',
  37. },
  38. title: {
  39. type: String,
  40. default: '',
  41. },
  42. icon: {
  43. type: String,
  44. default: 'arrow-right',
  45. },
  46. list: {
  47. type: Array as PropType<any[]>,
  48. default: () => [],
  49. },
  50. keyLabel: {
  51. type: String,
  52. default: 'label',
  53. },
  54. keyValue: {
  55. type: String,
  56. default: 'value',
  57. },
  58. disabled: {
  59. type: Boolean,
  60. default: false,
  61. },
  62. readonly: {
  63. type: Boolean,
  64. default: false,
  65. },
  66. customStyle: {
  67. type: Object,
  68. default: () => ({}),
  69. },
  70. placeholderStyle: {
  71. type: Object,
  72. default: () => ({}),
  73. },
  74. fontSize: {
  75. type: Number,
  76. default: 30,
  77. }
  78. });
  79. const defaultIndex = ref([0]);
  80. const label = ref('');
  81. const isOpen = ref(false);
  82. const matchValue = ref(false);
  83. const columns = computed(() => {
  84. return [props.list];
  85. });
  86. const getPlaceholderStyle = computed(() => {
  87. return {
  88. ...props.placeholderStyle,
  89. fontSize: props.fontSize + 'rpx'
  90. }
  91. });
  92. const getValueStyle = computed(() => {
  93. return {
  94. fontSize: props.fontSize + 'rpx'
  95. }
  96. });
  97. const init = () => {
  98. if (modelValue.value !== null && modelValue.value !== undefined && modelValue.value !== '') {
  99. const index = props.list.findIndex(item => item[props.keyValue] == modelValue.value);
  100. if (index !== -1) {
  101. defaultIndex.value = [index];
  102. label.value = props.list[index][props.keyLabel];
  103. matchValue.value = true;
  104. } else {
  105. // 如果找不到匹配项,重置状态
  106. defaultIndex.value = [0];
  107. label.value = props.placeholder;
  108. matchValue.value = false;
  109. }
  110. } else {
  111. defaultIndex.value = [0];
  112. label.value = props.placeholder;
  113. matchValue.value = false;
  114. }
  115. }
  116. // 使用 watch 替代 watchEffect,避免循环更新
  117. watch([() => modelValue.value, () => props.list], init, { immediate: true });
  118. const pickerRef = ref();
  119. const emit = defineEmits<{
  120. (e: 'change', value: number, selectedItem: any): void;
  121. (e: 'click'): void;
  122. }>();
  123. const handleConfirm = () => {
  124. const { value } = pickerRef.value.manualConfirm();
  125. const oldValue = modelValue.value;
  126. const newValue = value[0][props.keyValue];
  127. // 更新 modelValue
  128. modelValue.value = newValue;
  129. // 手动更新显示状态,确保界面同步
  130. const selectedItem = value[0];
  131. label.value = selectedItem[props.keyLabel];
  132. matchValue.value = true;
  133. // 发出 change 事件
  134. if (oldValue !== newValue) {
  135. emit('change', newValue, selectedItem);
  136. }
  137. pickerRef.value.close();
  138. }
  139. const handleCancel = () => {
  140. pickerRef.value.close();
  141. }
  142. const handleChange = () => { }
  143. const handleClick = () => {
  144. emit('click');
  145. if (props.disabled) {
  146. return;
  147. }
  148. init();
  149. isOpen.value = true;
  150. pickerRef.value.open();
  151. }
  152. const onClose = () => {
  153. isOpen.value = false;
  154. }
  155. </script>
  156. <style lang="scss" scoped></style>