点口管理
This commit is contained in:
@@ -1,163 +0,0 @@
|
||||
<template>
|
||||
<u-popup v-model="visible" mode="bottom" height="80%">
|
||||
<view class="project-select">
|
||||
<view class="project-select-btns">
|
||||
<u-button size="mini" type="info" @click="handleCancel">取消</u-button>
|
||||
<text></text>
|
||||
<u-button size="mini" type="primary" @click="handleConfirm">确定</u-button>
|
||||
</view>
|
||||
<view class="project-select-filter">
|
||||
<u-search placeholder="请输入项目名称" height="60" v-model="searchKey" :show-action="false"
|
||||
@change="handleSearchKey"></u-search>
|
||||
</view>
|
||||
<scroll-view scroll-y="true" class="project-select-inner">
|
||||
<view class="project-select-inner-wrap">
|
||||
<view class="li" v-for="(item,idx) in filteredList" :key="idx"
|
||||
:class="selectObj && selectObj[props.valueKey] == item[props.valueKey] ? 'active' : ''"
|
||||
@click="handerChangList(item)"
|
||||
>
|
||||
{{ item[props.labelKey] }}
|
||||
</view>
|
||||
<view v-if="filteredList.length === 0" class="empty-tip">暂无匹配项目</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
watch,
|
||||
computed
|
||||
} from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
searchKey: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
selectId: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue', 'cancel', 'confirm', 'searchChange'])
|
||||
|
||||
const visible = ref(false)
|
||||
const searchKey = ref("")
|
||||
|
||||
const selectObj = ref(null)
|
||||
|
||||
// 过滤后的列表
|
||||
const filteredList = computed(() => {
|
||||
if (!props.searchKey) return props.list
|
||||
return props.list.filter(item =>
|
||||
item[props.labelKey]?.includes(props.searchKey)
|
||||
)
|
||||
})
|
||||
|
||||
watch(() => props.modelValue, (val) => {
|
||||
visible.value = val
|
||||
if(val){
|
||||
const selectedItem = props.list.find(item => item[props.valueKey] === props.selectId)
|
||||
if (selectedItem) {
|
||||
selectObj.value = selectedItem
|
||||
} else {
|
||||
selectObj.value = null
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
watch(visible, (val) => {
|
||||
emit('update:modelValue', val)
|
||||
})
|
||||
const handerChangList = (item)=>{
|
||||
selectObj.value = item
|
||||
}
|
||||
// 点击取消
|
||||
const handleCancel = () => {
|
||||
emit('update:modelValue', false)
|
||||
}
|
||||
// 点击确认
|
||||
const handleConfirm = () => {
|
||||
emit("confirm", selectObj.value)
|
||||
}
|
||||
// 搜索
|
||||
const handleSearchKey = (val) => {
|
||||
emit('searchChange', val)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.project-select {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #fff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
|
||||
.project-select-btns {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.project-select-filter {
|
||||
padding: 16rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.project-select-inner {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
.project-select-inner-wrap {
|
||||
padding: 0 32rpx 32rpx;
|
||||
|
||||
.li {
|
||||
padding: 28rpx 0;
|
||||
border-bottom: 1rpx solid #f5f6f8;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
transition: all 0.2s;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $u-type-primary;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
padding: 80rpx 0;
|
||||
color: #999;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+179
-283
@@ -1,22 +1,24 @@
|
||||
<template>
|
||||
<u-popup v-model="localVisible" mode="bottom" :height="height">
|
||||
<view class="nbd-select">
|
||||
<!-- 标题栏 -->
|
||||
<view class="nbd-select-header" v-if="title">
|
||||
<text class="nbd-select-title">{{ title }}</text>
|
||||
<text class="nbd-select-tip" v-if="tip">{{ tip }}</text>
|
||||
<!-- 顶部按钮 -->
|
||||
<view class="nbd-select-header">
|
||||
<u-button size="mini" @click="handleCancel">
|
||||
{{ cancelText }}
|
||||
</u-button>
|
||||
<view v-if="title || tip" class="nbd-select-title-wrap">
|
||||
<text class="nbd-select-title">{{ title }}</text>
|
||||
<text class="nbd-select-tip" v-if="tip">{{ tip }}</text>
|
||||
</view>
|
||||
<u-button size="mini" type="primary" @click="handleConfirm">
|
||||
{{ confirmText }}
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<view class="nbd-select-filter" v-if="showSearch">
|
||||
<u-search
|
||||
:placeholder="searchPlaceholder"
|
||||
:height="72"
|
||||
v-model="searchKey"
|
||||
:show-action="false"
|
||||
@change="handleSearch"
|
||||
@clear="handleSearch"
|
||||
></u-search>
|
||||
<u-search :placeholder="searchPlaceholder" :height="60" v-model="searchKey" :show-action="false"
|
||||
@change="handleSearch" @clear="handleSearch"></u-search>
|
||||
</view>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
@@ -28,298 +30,192 @@
|
||||
</view>
|
||||
|
||||
<!-- 列表项 -->
|
||||
<view
|
||||
v-for="(item, idx) in filteredList"
|
||||
:key="idx"
|
||||
class="nbd-select-item"
|
||||
:class="{ active: isSelected(item) }"
|
||||
@click="handleItemClick(item)"
|
||||
>
|
||||
<view v-for="(item, idx) in filteredList" :key="idx" class="nbd-select-item"
|
||||
:class="{ active: isSelected(item) }" @click="handleItemClick(item)">
|
||||
<text class="nbd-select-item-text">{{ item[labelKey] }}</text>
|
||||
<view v-if="multiple" class="nbd-select-checkbox">
|
||||
<u-icon
|
||||
:name="isSelected(item) ? 'checkbox-mark' : 'square'"
|
||||
:size="36"
|
||||
:color="isSelected(item) ? $u.type.primary : '#dcdee0'"
|
||||
></u-icon>
|
||||
<u-icon :name="isSelected(item) ? 'checkbox-mark' : 'square'" :size="36"
|
||||
:color="isSelected(item) ? $u.type.primary : '#dcdee0'"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 底部按钮 -->
|
||||
<view class="nbd-select-footer">
|
||||
<u-button class="nbd-select-btn-cancel" @click="handleCancel">
|
||||
{{ cancelText }}
|
||||
</u-button>
|
||||
<u-button class="nbd-select-btn-confirm" type="primary" @click="handleConfirm">
|
||||
{{ confirmText }}
|
||||
</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
watch
|
||||
} from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
// 弹窗显示状态
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹窗高度
|
||||
height: {
|
||||
type: String,
|
||||
default: '70%'
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题提示
|
||||
tip: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 数据列表
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 选中项(支持数组,多选时使用)
|
||||
model: {
|
||||
type: [Object, Array, String, Number],
|
||||
default: null
|
||||
},
|
||||
// 是否多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示搜索框
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 搜索占位符
|
||||
searchPlaceholder: {
|
||||
type: String,
|
||||
default: '请输入关键词搜索'
|
||||
},
|
||||
// 字段名配置
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
// 自定义搜索字段(默认搜索 labelKey)
|
||||
searchKeyField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 按钮文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确定'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'update:model',
|
||||
'cancel',
|
||||
'confirm',
|
||||
'searchChange',
|
||||
'change'
|
||||
])
|
||||
|
||||
const localVisible = ref(false)
|
||||
const searchKey = ref('')
|
||||
|
||||
// 当前选中的项(多选时为数组)
|
||||
const selectedItems = ref(props.multiple ? (props.model || []) : (props.model || null))
|
||||
|
||||
// 监听外部 model 变化
|
||||
watch(() => props.model, (newVal) => {
|
||||
selectedItems.value = props.multiple ? (newVal || []) : newVal
|
||||
}, { deep: true })
|
||||
|
||||
// 弹窗状态同步
|
||||
watch(() => props.modelValue, (val) => {
|
||||
localVisible.value = val
|
||||
if (val) {
|
||||
// 打开时重置搜索
|
||||
searchKey.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
watch(localVisible, (val) => {
|
||||
emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
// 过滤后的列表
|
||||
const filteredList = computed(() => {
|
||||
if (!searchKey.value) return props.list
|
||||
|
||||
const keyword = searchKey.value.toLowerCase()
|
||||
const searchField = props.searchKeyField || props.labelKey
|
||||
|
||||
return props.list.filter(item => {
|
||||
const value = item[searchField]
|
||||
if (value === null || value === undefined) return false
|
||||
return String(value).toLowerCase().includes(keyword)
|
||||
const props = defineProps({
|
||||
// 弹窗显示状态
|
||||
modelValue: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 弹窗高度
|
||||
height: {
|
||||
type: String,
|
||||
default: '70%'
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 标题提示
|
||||
tip: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 数据列表
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 选中项(支持数组,多选时使用)
|
||||
model: {
|
||||
type: [Object, Array, String, Number],
|
||||
default: null
|
||||
},
|
||||
// 是否多选
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 显示搜索框
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 搜索占位符
|
||||
searchPlaceholder: {
|
||||
type: String,
|
||||
default: '请输入关键词搜索'
|
||||
},
|
||||
// 字段名配置
|
||||
labelKey: {
|
||||
type: String,
|
||||
default: 'label'
|
||||
},
|
||||
valueKey: {
|
||||
type: String,
|
||||
default: 'value'
|
||||
},
|
||||
// 自定义搜索字段(默认搜索 labelKey)
|
||||
searchKeyField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 按钮文字
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确定'
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// 判断是否选中
|
||||
const isSelected = (item) => {
|
||||
if (props.multiple) {
|
||||
return selectedItems.value.some(
|
||||
selected => selected[props.valueKey] === item[props.valueKey]
|
||||
)
|
||||
}
|
||||
if (selectedItems.value && typeof selectedItems.value === 'object') {
|
||||
return selectedItems.value[props.valueKey] === item[props.valueKey]
|
||||
}
|
||||
return selectedItems.value === item[props.valueKey]
|
||||
}
|
||||
const emit = defineEmits([
|
||||
'update:modelValue',
|
||||
'update:model',
|
||||
'cancel',
|
||||
'confirm',
|
||||
'searchChange',
|
||||
'change'
|
||||
])
|
||||
|
||||
// 点击列表项
|
||||
const handleItemClick = (item) => {
|
||||
if (props.multiple) {
|
||||
const index = selectedItems.value.findIndex(
|
||||
selected => selected[props.valueKey] === item[props.valueKey]
|
||||
)
|
||||
if (index > -1) {
|
||||
selectedItems.value.splice(index, 1)
|
||||
const localVisible = ref(false)
|
||||
const searchKey = ref('')
|
||||
|
||||
// 当前选中的项(多选时为数组)
|
||||
const selectedItems = ref(props.multiple ? (props.model || []) : (props.model || null))
|
||||
|
||||
// 监听外部 model 变化
|
||||
watch(() => props.model, (newVal) => {
|
||||
selectedItems.value = props.multiple ? (newVal || []) : newVal
|
||||
}, {
|
||||
deep: true
|
||||
})
|
||||
|
||||
// 弹窗状态同步
|
||||
watch(() => props.modelValue, (val) => {
|
||||
localVisible.value = val
|
||||
if (val) {
|
||||
// 打开时重置搜索
|
||||
searchKey.value = ''
|
||||
}
|
||||
})
|
||||
|
||||
watch(localVisible, (val) => {
|
||||
emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
// 过滤后的列表
|
||||
const filteredList = computed(() => {
|
||||
if (!searchKey.value) return props.list
|
||||
|
||||
const keyword = searchKey.value.toLowerCase()
|
||||
const searchField = props.searchKeyField || props.labelKey
|
||||
|
||||
return props.list.filter(item => {
|
||||
const value = item[searchField]
|
||||
if (value === null || value === undefined) return false
|
||||
return String(value).toLowerCase().includes(keyword)
|
||||
})
|
||||
})
|
||||
|
||||
// 判断是否选中
|
||||
const isSelected = (item) => {
|
||||
if (props.multiple) {
|
||||
return selectedItems.value.some(
|
||||
selected => selected[props.valueKey] === item[props.valueKey]
|
||||
)
|
||||
}
|
||||
if (selectedItems.value && typeof selectedItems.value === 'object') {
|
||||
return selectedItems.value[props.valueKey] === item[props.valueKey]
|
||||
}
|
||||
return selectedItems.value === item[props.valueKey]
|
||||
}
|
||||
|
||||
// 点击列表项
|
||||
const handleItemClick = (item) => {
|
||||
if (props.multiple) {
|
||||
const index = selectedItems.value.findIndex(
|
||||
selected => selected[props.valueKey] === item[props.valueKey]
|
||||
)
|
||||
if (index > -1) {
|
||||
selectedItems.value.splice(index, 1)
|
||||
} else {
|
||||
selectedItems.value.push(item)
|
||||
}
|
||||
} else {
|
||||
selectedItems.value.push(item)
|
||||
}
|
||||
} else {
|
||||
selectedItems.value = item
|
||||
}
|
||||
emit('change', selectedItems.value)
|
||||
}
|
||||
|
||||
// 搜索
|
||||
const handleSearch = (val) => {
|
||||
emit('searchChange', val)
|
||||
}
|
||||
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
emit('cancel')
|
||||
localVisible.value = false
|
||||
}
|
||||
|
||||
// 确认
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', selectedItems.value)
|
||||
emit('update:model', selectedItems.value)
|
||||
localVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.nbd-select {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
border-radius: 24rpx 24rpx 0 0;
|
||||
overflow: hidden;
|
||||
|
||||
&-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24rpx 32rpx 20rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
|
||||
.nbd-select-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 8rpx;
|
||||
}
|
||||
|
||||
.nbd-select-tip {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
selectedItems.value = item
|
||||
}
|
||||
emit('change', selectedItems.value)
|
||||
}
|
||||
|
||||
&-filter {
|
||||
padding: 16rpx 32rpx;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
// 搜索
|
||||
const handleSearch = (val) => {
|
||||
emit('searchChange', val)
|
||||
}
|
||||
|
||||
&-inner {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 0 32rpx;
|
||||
|
||||
&-wrap {
|
||||
padding: 16rpx 0;
|
||||
}
|
||||
// 取消
|
||||
const handleCancel = () => {
|
||||
emit('cancel')
|
||||
localVisible.value = false
|
||||
}
|
||||
|
||||
&-empty {
|
||||
padding: 80rpx 0;
|
||||
// 确认
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', selectedItems.value)
|
||||
emit('update:model', selectedItems.value)
|
||||
localVisible.value = false
|
||||
}
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 28rpx 0;
|
||||
border-bottom: 1rpx solid #f5f6f8;
|
||||
transition: background 0.2s;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&.active .nbd-select-item-text {
|
||||
color: $u-type-primary;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nbd-select-item-text {
|
||||
flex: 1;
|
||||
font-size: 28rpx;
|
||||
color: #333;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.nbd-select-checkbox {
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&-footer {
|
||||
display: flex;
|
||||
gap: 24rpx;
|
||||
padding: 20rpx 32rpx 40rpx;
|
||||
|
||||
.nbd-select-btn-cancel,
|
||||
.nbd-select-btn-confirm {
|
||||
flex: 1;
|
||||
height: 80rpx;
|
||||
font-size: 28rpx;
|
||||
border-radius: 40rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</script>
|
||||
Reference in New Issue
Block a user