index.vue 3.13 KB
<template>
  <uni-popup ref="popup" type="bottom" :mask-click="false" :safe-area="true">
    <view class="filter-modal">
      <view class="header">
        <text class="title">{{ title }}</text>
        <text class="close" @click="onClose">×</text>
      </view>

      <view class="body">
        <!-- 父组件传入表单内容,通过作用域插槽拿到 model -->
        <slot :model="innerModel"></slot>
      </view>

      <view class="footer">
        <button class="btn reset" @click="onReset">重置</button>
        <button class="btn confirm" type="primary" @click="onConfirm">确认</button>
      </view>
    </view>
  </uni-popup>
</template>

<script>
export default {
  name: 'FilterModal',
  props: {
    // v-model:visible 控制显示;初始值
    visible: { type: Boolean, default: false },
    // v-model:value 过滤表单对象
    value: { type: Object, default: () => ({}) },
    title: { type: String, default: '筛选' }
  },
  data() {
    return {
      innerModel: JSON.parse(JSON.stringify(this.value || {}))
    }
  },
  watch: {
    value: {
      deep: true,
      handler(v) {
        this.innerModel = JSON.parse(JSON.stringify(v || {}))
      }
    },
    visible(v) {
      if (v) this.open()
      else this.close()
    }
  },
  mounted() {
    if (this.visible) this.open()
  },
  methods: {
    open() {
      this.$refs.popup && this.$refs.popup.open()
      this.$emit('update:visible', true)
    },
    close() {
      this.$refs.popup && this.$refs.popup.close()
      this.$emit('update:visible', false)
    },
    onClose() {
      this.close()
      this.$emit('close')
    },
    onReset() {
      const m = this.innerModel || {}
      Object.keys(m).forEach(k => {
        const val = m[k]
        if (Array.isArray(val)) m[k] = []
        else if (val && typeof val === 'object') m[k] = null
        else m[k] = ''
      })
      // 不关闭弹框,仅告知父组件当前值
      this.$emit('reset', { ...m })
      this.$emit('update:value', { ...m })
    },
    onConfirm() {
      const payload = { ...this.innerModel }
      this.$emit('confirm', payload)
      this.$emit('input', payload)
      this.$emit('update:value', payload)
      this.close()
    }
  }
}
</script>

<style lang="scss" scoped>
.filter-modal {
  width: 100%;
  max-height: 70vh;
  background: #fff;
  border-radius: 20rpx 20rpx 0 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.header {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 24rpx 28rpx;
  border-bottom: 1rpx solid #f0f0f0;
}
.title {
  font-size: 36rpx;
  font-weight: 600;
  flex: 1;
  text-align: center;
}
.close {
  font-size: 36rpx;
  color: #666;
}
.body {
  flex: 1 1 auto;
  padding: 32rpx 32rpx 48rpx;
  overflow-y: auto;
}
.footer {
  flex: 0 0 auto;
  display: flex;
  gap: 32rpx;
  padding: 32rpx;
  border-top: 1rpx solid #f5f5f5;
}
.btn {
  flex: 1;
  height: 80rpx;
  line-height: 80rpx;
  border-radius: 12rpx;
  font-size: 32rpx;
}
.reset {
  background: $theme-primary-plain-bg;
  color: $theme-primary;
  &::after {
    border: none;
  }
}
.confirm {
  background: $theme-primary;
  color: #fff;
}
</style>