ie-picker.vue 5.0 KB

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