| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232 |
- <template>
- <template v-if="mode === 'picker'">
- <picker :range="options" range-key="name" @change="selectChange" :disabled="false">
- <slot></slot>
- </picker>
- </template>
- <template v-else-if="mode === 'custom'">
- <view @click="showModel = true" class="flex1">
- <slot></slot>
- </view>
- <up-popup v-model:show="showModel" mode="center" round="30rpx" :safeAreaInsetBottom="false" closeable @close="showModel = false">
- <view class="w-700">
- <view class="pd-24">
- <view class="f-s-32 c-#333 f-w-500">{{ title }}</view>
- </view>
- <scroll-view scroll-y style="max-height: 70vh">
- <view class="pd3-10-24-24">
- <ut-row gap="16rpx">
- <template v-for="(item, index) in options" :key="index">
- <ut-col :span="item.span">
- <view @click="clickCol(item)" class="ut-custom-item-sheet p-rtv" :class="{ active: checkeds[item.value] }"
- >{{ item?.remark || item?.name }}
- <image v-if="checkeds[item.value]" class="w-40 h-40 checked-icon" src="https://ta.zycpzs.cn/oss-file/smart-trace/szyy/images-lm/price/checked1.png" mode="widthFix" />
- </view>
- </ut-col>
- </template>
- </ut-row>
- </view>
- </scroll-view>
- <view v-if="multiple" class="d-flex j-c pd-24">
- <up-button class="mr-20" @click="onCancel">取消</up-button>
- <up-button type="primary" @click="onConfirm">确定</up-button>
- </view>
- </view>
- </up-popup>
- </template>
- </template>
- <script setup lang="ts">
- import { ref, watch, computed } from 'vue';
- const props = defineProps({
- modelValue: {
- // 支持数组或字符串:多选时根据现有类型返回对应格式
- type: [Array, String],
- default: () => [],
- },
- title: {
- type: String,
- default: '系统提示',
- },
- showTitle: {
- type: Boolean,
- default: true,
- },
- tabs: {
- type: Array,
- default: () => [],
- },
- mode: {
- type: String,
- default: 'picker', // 原生 picker 模式 或自定义弹窗模式 custom
- },
- // 是否多选
- multiple: {
- type: Boolean,
- default: false, // 只有自定义弹窗模式下生效
- },
- valueType: {
- type: String,
- default: 'string', // 数组 array 或 字符串 string,只有多选时生效
- },
- });
- const options = computed(() => {
- return props.tabs.map((item: any) => {
- // 如果 elTagClass正则表达式匹配的是数字,则作为span使用,否则默认10,如果是个对象字符,取span属性值
- let span = 10;
- // Unexpected end of JSON input
- // at JSON.parse (<anonymous>)
- if (item.elTagClass) {
- const num = Number(item.elTagClass);
- if (!isNaN(num)) {
- span = num;
- } else {
- try {
- const obj = JSON.parse(item.elTagClass);
- if (obj && obj.span) {
- span = obj.span;
- }
- } catch (e) {
- // do nothing
- }
- }
- }
- return {
- name: item.label,
- value: item.value,
- span,
- };
- });
- });
- const showModel = ref(false);
- const emit = defineEmits(['close', 'confirm', 'open', 'update:show', 'update:modelValue', 'change']);
- const checkeds = ref<any>({});
- // 未选择之前的
- const initialCheckeds = ref<any>({});
- const close = () => {
- showModel.value = false;
- // 如果有初始值,恢复初始值
- checkeds.value = { ...initialCheckeds.value };
- emit('update:show', false);
- emit('close');
- };
- // 获取当前已选中的值列表
- const getSelectedValues = () => {
- return Object.keys(checkeds.value).filter((key) => checkeds.value[key]);
- };
- const clickCol = (item: any) => {
- // 判断是单选还是多选
- if (props.multiple) {
- // 多选
- if (checkeds.value[item.value]) {
- // 取消选择
- delete checkeds.value[item.value];
- } else {
- // 选择
- checkeds.value[item.value] = true;
- }
- } else {
- // 单选
- checkeds.value = {
- [item.value]: true,
- };
- emit('update:modelValue', item.value);
- emit('change', item.value);
- close();
- }
- };
- // 多选确认
- const onConfirm = () => {
- const values = getSelectedValues();
- let out: any;
- if (Array.isArray(props.modelValue) || props.valueType === 'array') {
- out = values;
- } else {
- // 字符串场景下,用逗号拼接
- out = values.join(',');
- }
- emit('update:modelValue', out);
- emit('change', out);
- // 确认后更新初始选中值
- initialCheckeds.value = { ...checkeds.value };
- close();
- };
- // 多选取消
- const onCancel = () => {
- close();
- };
- const selectChange = (item: any) => {
- const selectedValues = options.value[item.detail.value].value;
- emit('update:modelValue', selectedValues);
- emit('change', selectedValues);
- close();
- };
- watch(
- () => props.modelValue,
- (newVal: any) => {
- // 初始化已选择项
- let selected: any = {};
- if (props.multiple) {
- if (Array.isArray(newVal)) {
- newVal.forEach((val: any) => {
- selected[val] = true;
- });
- } else if (typeof newVal === 'string' && newVal) {
- newVal
- .split(/[,,]/)
- .map((val: string) => val.trim())
- .filter((val: string) => !!val)
- .forEach((val: string) => {
- selected[val] = true;
- });
- }
- } else if (newVal != null) {
- selected = {
- [newVal as string]: true,
- };
- }
- checkeds.value = selected;
- initialCheckeds.value = { ...selected };
- },
- { immediate: true },
- );
- </script>
- <script lang="ts">
- export default {
- options: {
- // 微信小程序中 options 选项
- multipleSlots: true, // 在组件定义时的选项中启动多slot支持,默认启用
- styleIsolation: 'shared', // 启动样式隔离。当使用页面自定义组件,希望父组件影响子组件样式时可能需要配置。具体配置选项参见:微信小程序自定义组件的样式
- addGlobalClass: true, // 表示页面样式将影响到自定义组件,但自定义组件中指定的样式不会影响页面。这个选项等价于设置 styleIsolation: apply-shared
- virtualHost: true // 将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等,而是希望自定义组件内部的第一层节点能够响应 flex 布局或者样式由自定义组件本身完全决定
- }
- }
- </script>
- <style lang="scss" scoped>
- .ut-custom-item-sheet {
- padding: 18rpx 6rpx;
- border: 1rpx solid #f7f7f7;
- background-color: #f7f7f7;
- border-radius: 10rpx;
- text-align: center;
- font-size: 30rpx;
- color: #333;
- &.active {
- border-color: #37a954;
- background-color: #e6f4f0;
- color: #37a954;
- }
- }
- .checked-icon {
- position: absolute;
- right: 0;
- bottom: 0;
- }
- </style>
|