| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248 |
- <template>
- <view class="u-picker-field" @click="openPicker" :class="{ disabled }">
- <view class="field-left">
- <text class="field-text" :class="{ placeholder: !selectedText }">
- {{ selectedText || placeholder }}
- </text>
- </view>
- <view class="field-right">
- <up-icon
- name="arrow-down"
- size="16"
- color="var(--text-02)"
- :style="{
- transform: visible ? 'rotate(180deg)' : 'rotate(0deg)',
- transition: 'transform .2s ease',
- }"
- />
- </view>
- <!-- up-picker -->
- <up-picker
- v-model:show="visible"
- :columns="normalizedColumns"
- :loading="loading"
- :valueName="valueKey"
- :keyName="labelKey"
- :item-height="itemHeight"
- :confirm-text="t(confirmText)"
- :cancel-text="t(cancelText)"
- @confirm="onConfirm"
- @cancel="onCancel"
- @close="onClose"
- />
- </view>
- </template>
- <script setup>
- import { computed, ref, watch } from "vue";
- import { t } from "@/locale";
- const props = defineProps({
- // v-model
- modelValue: {
- type: [String, Number, Object, Array],
- default: "",
- },
- // up-picker 的 columns(支持一列或多列)
- columns: {
- type: Array,
- default: () => [],
- },
- // 当 columns 为对象数组时的显示/取值字段
- labelKey: {
- type: String,
- default: "label",
- },
- valueKey: {
- type: String,
- default: "value",
- },
- placeholder: {
- type: String,
- default: "",
- },
- disabled: {
- type: Boolean,
- default: false,
- },
- loading: {
- type: Boolean,
- default: false,
- },
- itemHeight: {
- type: Number,
- default: 44,
- },
- confirmText: {
- type: String,
- default: "确认",
- },
- cancelText: {
- type: String,
- default: "取消",
- },
- // 是否在值为空时自动选中第一项
- autoSelectFirst: {
- type: Boolean,
- default: true,
- },
- });
- const emit = defineEmits(["update:modelValue", "change", "open", "close"]);
- const visible = ref(false);
- // 统一成多列结构:[[...]]
- const normalizedColumns = computed(() => {
- if (props.columns.length === 0) return [];
- const isMulti = Array.isArray(props.columns[0]);
- return isMulti ? props.columns : [props.columns];
- });
- // 如果 modelValue 为空且有可选项,则默认选中第一项(受 autoSelectFirst 控制)
- watch(
- () => normalizedColumns.value,
- (cols) => {
- if (!props.autoSelectFirst) return;
- if (!cols || !cols.length) return;
- const mv = props.modelValue;
- const isEmpty = mv === undefined || mv === null || mv === "";
- if (!isEmpty) return;
- const firstCol = cols[0] || [];
- const first = firstCol[0];
- if (first === undefined) return;
- const value = typeof first === "object" ? first[props.valueKey] : first;
- const label =
- typeof first === "object" ? first[props.labelKey] ?? "" : String(first);
- emit("update:modelValue", value);
- emit("change", { value, label, raw: { type: "init" } });
- },
- { immediate: true, deep: true }
- );
- // 选中文本(以第一列为主,常见场景)
- const selectedText = computed(() => {
- const firstCol = normalizedColumns.value[0] || [];
- if (!firstCol.length) return "";
- // 根据 modelValue 匹配文本
- const mv = props.modelValue;
- if (mv === null || mv === undefined || mv === "") return "";
- // 如果是对象直接取 labelKey
- if (typeof mv === "object" && !Array.isArray(mv)) {
- return mv[props.labelKey] ?? "";
- }
- // 原始值:在第一列匹配
- const found = firstCol.find((item) => {
- if (typeof item === "object") return item[props.valueKey] === mv;
- return item === mv;
- });
- if (!found) return "";
- return typeof found === "object"
- ? found[props.labelKey] ?? ""
- : String(found);
- });
- const openPicker = () => {
- if (props.disabled) return;
- visible.value = true;
- emit("open");
- };
- const onClose = () => {
- emit("close");
- };
- const onCancel = () => {
- visible.value = false;
- };
- // up-picker 的 confirm 事件回调参数在不同版本可能不同,这里做兼容处理
- const onConfirm = (e) => {
- const firstCol = normalizedColumns.value[0] || [];
- let picked;
- let pickedDisplay = "";
- // 优先从 e.value 推断
- if (e && Array.isArray(e.value) && e.value.length) {
- const v0 = e.value[0];
- if (typeof v0 === "object") {
- picked = v0[props.valueKey];
- pickedDisplay = v0[props.labelKey] ?? "";
- } else {
- // v0 是 label,找到对应项拿到 value
- const found = firstCol.find((item) =>
- typeof item === "object" ? item[props.labelKey] === v0 : item === v0
- );
- if (found) {
- picked = typeof found === "object" ? found[props.valueKey] : found;
- pickedDisplay =
- typeof found === "object"
- ? found[props.labelKey] ?? ""
- : String(found);
- } else {
- picked = v0;
- pickedDisplay = String(v0);
- }
- }
- } else if (e && (Array.isArray(e.indexs) || typeof e.index === "number")) {
- const idx = Array.isArray(e.indexs) ? e.indexs[0] : e.index;
- const it = firstCol[idx];
- if (it) {
- picked = typeof it === "object" ? it[props.valueKey] : it;
- pickedDisplay =
- typeof it === "object" ? it[props.labelKey] ?? "" : String(it);
- }
- }
- // 回填 & 透传事件
- if (picked !== undefined) {
- emit("update:modelValue", picked);
- emit("change", { value: picked, label: pickedDisplay, raw: e });
- }
- visible.value = false;
- };
- </script>
- <style lang="less" scoped>
- .u-picker-field {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 16rpx 20rpx;
- background-color: var(--light);
- border-radius: 12rpx;
- &.disabled {
- opacity: 0.6;
- }
- .field-left {
- flex: 1;
- min-width: 0;
- .field-text {
- color: var(--text);
- font-size: 28rpx;
- line-height: 40rpx;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- &.placeholder {
- color: var(--text-02);
- }
- }
- }
- .field-right {
- margin-left: 12rpx;
- display: flex;
- align-items: center;
- }
- }
- </style>
|