ie-picker.vue 6.0 KB

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