AgePickerGlobal.vue 4.62 KB
<template>
  <div>
    <!-- 标签 -->
    <div v-if="label" class="text-sm text-gray-600 mb-2 flex items-center">
      <span v-if="required" class="text-red-500 mr-1">*</span>
      <span>{{ label }}</span>
    </div>

    <!-- 触发区域 -->
    <div
      class="flex justify-between items-center border border-gray-200 rounded-lg p-3 bg-gray-50"
      @tap="handleTap"
    >
      <span :class="displayValue ? 'text-gray-900' : 'text-gray-400'" class="text-sm">
        {{ displayValue || placeholder }}
      </span>
      <IconFont name="right" size="14" color="#9CA3AF" />
    </div>

    <!-- Picker 弹窗 -->
    <nut-popup
      position="bottom"
      v-model:visible="showPicker"
      :z-index="9999"
      :overlay="true"
    >
      <nut-picker
        v-model="pickerValue"
        :columns="ageColumns"
        @confirm="onConfirm"
        @cancel="onCancel"
      />
    </nut-popup>
  </div>
</template>

<script setup>
/**
 * 年龄选择器组件(全局弹窗管理器版本)
 *
 * @description 使用 NutUI Popup + Picker 实现年龄选择
 *              - 显示格式:3位数字(如 018 表示 18 岁)
 *              - 提交格式:数字(如 18)
 *              - 年龄范围:0-120 岁
 *              - 使用 GlobalPopupManager 管理弹窗层级
 * @author Claude Code
 * @version 2.0.0 - 支持全局弹窗管理器
 * @example
 * <AgePickerGlobal
 *   v-model="age"
 *   label="年龄"
 *   placeholder="请选择年龄"
 * />
 */
import { ref, computed, watch, onMounted } from 'vue'
import IconFont from '@/components/IconFont.vue'
import { useGlobalPopup } from './GlobalPopupManager'

/**
 * 使用全局弹窗管理器
 */
const { registerPopup, activatePopup, deactivatePopup } = useGlobalPopup()

/**
 * 弹窗 ID(由 GlobalPopupManager 分配)
 * @type {Ref<string|null>}
 */
const popupId = ref(null)

/**
 * 组件挂载时注册弹窗
 */
onMounted(() => {
  popupId.value = registerPopup()
})

/**
 * 组件属性
 */
const props = defineProps({
  /**
   * 标签文本
   * @type {string}
   */
  label: {
    type: String,
    default: ''
  },

  /**
   * 是否必填
   * @type {boolean}
   */
  required: {
    type: Boolean,
    default: false
  },

  /**
   * 占位符文本
   * @type {string}
   */
  placeholder: {
    type: String,
    default: '请选择年龄'
  },

  /**
   * 绑定的值(数字)
   * @type {number}
   */
  modelValue: {
    type: Number,
    default: null
  }
})

/**
 * 组件事件
 */
const emit = defineEmits([
  /**
   * 更新值事件
   * @event update:modelValue
   * @param {number} value - 选中的年龄
   */
  'update:modelValue',

  /**
   * 值变化事件
   * @event change
   * @param {number} value - 选中的年龄
   */
  'change'
])

/**
 * 控制 Picker 显示
 */
const showPicker = ref(false)

/**
 * Picker 当前值(3位数字格式)
 */
const pickerValue = ref(['018'])

/**
 * 年龄选项列(0-120 岁,3位数字格式)
 */
const ageColumns = computed(() => {
  return [
    Array.from({ length: 121 }, (_, i) => ({
      text: `${i} 岁`,
      value: String(i).padStart(3, '0')
    }))
  ]
})

/**
 * 显示的值(转换为中文格式)
 */
const displayValue = computed(() => {
  if (props.modelValue === null || props.modelValue === undefined) {
    return ''
  }
  return `${props.modelValue} 岁`
})

/**
 * 点击触发区域
 */
const handleTap = () => {
  // 激活弹窗(隐藏父弹窗底部按钮)
  if (popupId.value) {
    activatePopup(popupId.value)
  }

  // 如果有值,转换为3位数字格式
  if (props.modelValue !== null && props.modelValue !== undefined) {
    pickerValue.value = [String(props.modelValue).padStart(3, '0')]
  }

  showPicker.value = true
}

/**
 * 确认选择
 * @param {Object} { selectedValue } - Picker 返回的值
 *
 * @example
 * // 用户选择 18 岁
 * onConfirm({ selectedValue: ['018'] })
 */
const onConfirm = ({ selectedValue }) => {
  // 将3位数字格式转换为普通数字
  const age = parseInt(selectedValue[0], 10)

  emit('update:modelValue', age)
  emit('change', age)

  // 停用弹窗(恢复父弹窗底部按钮)
  if (popupId.value) {
    deactivatePopup(popupId.value)
  }

  showPicker.value = false
}

/**
 * 取消选择
 */
const onCancel = () => {
  // 停用弹窗(恢复父弹窗底部按钮)
  if (popupId.value) {
    deactivatePopup(popupId.value)
  }

  showPicker.value = false
}

/**
 * 监听 modelValue 变化,同步到 pickerValue
 */
watch(
  () => props.modelValue,
  (newVal) => {
    if (newVal !== null && newVal !== undefined) {
      pickerValue.value = [String(newVal).padStart(3, '0')]
    }
  }
)
</script>

<style lang="less">
/* 组件样式 */
</style>