基于a-select封装的通用选择器,其中的便捷新增和高级搜索属于另外封装的组件,自行清除相关代码可以正常使用
由于本系统基于页面配置从而实现表格、表单动态渲染,集成了很多的功能和交互,删除这部分代码就可以直接使用
比如pageId rowName returnName 便捷获取title之类的可以统统删掉
单选模式/多选模式 绑定的数据都是String类型,逗号隔开(a-select的多选是数组,这个自行修改回去)
<template>
<div class="business-selector">
<!-- style是为了控制高级选择器按钮 删掉 -->
<!-- $txt('')是多语言,改成中文即可 -->
<!-- url是个对象包含list add(用于新增) -->
<a-select
:value="value"
:mode="mode"
allow-clear
show-search
:placeholder="$txt('please_select')"
:dropdown-match-select-width="false"
:filter-option="url.list ? false : filterOption"
:style="{'width': proSelect && url.list && pageId ? 'calc(100% - 40px)' : '100%'}"
:disabled="disabled"
:loading="loading"
@dropdownVisibleChange="dropdownVisibleChange"
@popupScroll="handlePopupScroll"
@search="handleSearch"
@change="val => {
returnSelectedRows({ selectedRowKeys: Array.isArray(val) ? val : [val] })
}"
>
<a-select-option
v-for="item in dataSource"
:key="item[rowKey]"
:value="item[rowKey]"
:disabled="optionDisabled(item)"
>
{{ item[rowName] }}
</a-select-option>
<a-select-option
v-if="loading"
disabled
value="disabled-option"
>
<a-spin :indicator="indicator" />
</a-select-option>
<template
slot="dropdownRender"
slot-scope="menu"
>
<v-nodes :vnodes="menu" />
<!-- 新增功能:1、必须有 url 2、开启该功能 3、控件启用 -->
<template v-if="url.add && created && !disabled">
<a-divider style="margin: 4px 0;" />
<a-button
type="link"
block
@click="handleCreate({}, 'create')"
>
<a-icon type="plus" /> {{ $txt('create') }}
</a-button>
</template>
</template>
</a-select>
<!-- 高级搜索:1、必须有url 2、开启了该功能 3、pageId表格配置 4、控件启用 -->
<a-button
v-if="proSelect && url.list && pageId"
icon="search"
class="search-icon"
:disabled="disabled"
@click="openPop"
/>
<s-modal-create
ref="Create"
:url="url"
:config="config.formConfig || []"
@refresh="refresh"
/>
<s-pop-select
ref="Pop"
:title="title"
:url="url"
:row-key="rowKey"
:config="config"
:params="params"
:option-disabled="optionDisabled"
:mode="mode === 'default' ? 'radio' : 'checkbox'"
@returnSelectedRows="returnSelectedRows"
/>
</div>
</template>
<script>
import PageConfig from '@/utils/pageConfig'
import http from '@/api'
import T from '@/utils/tools'
// 基本覆盖所有需求
// 高级下拉选择器 - 以下功能都有 demo 参考项目 OMS 搜索关键字即可
// 功能:
// 1、数据源: 接口url.list(远程搜索,必须开启分页,否则自行拉取数据传入list) 或者 Array数组list(本地过滤)
// 2、可开启高级功能 A、新增 B、弹窗搜索
// 3、提供选中的数组 selectedRows 方便开发获取 rowKey rowName 以外的数据,事件 returnSelectedRows 接收
// 4、传入的 dataIndex 将会获取 rowKey 的值, returnName 将会获取 rowName 的值 (仅支持一个值)
// 如果要获取多个,请利用第3个功能点自行处理
// 5、提供选项禁用功能 optionDisabled 是个函数
// 6、提供参数过滤功能 param,仅在数据源为 url 时有效,list自行在传入前过滤
// 7.提供滚动加载,滚动加载支持多选
export default {
name: 'SSelect',
components: {
VNodes: {
functional: true,
render: (h, ctx) => ctx.props.vnodes,
},
},
props: {
// 数据源的两种方式
// 1、传接口 url: { list: 'xxx', add: 'xxx' }
// 2、传数组 list
url: {
type: Object,
default: () => ({})
},
list: {
type: Array,
default: () => []
},
rowKey: {
type: String,
required: true,
},
rowName: {
type: String,
required: true,
},
// 数据 key
dataIndex: {
type: String,
required: true
},
// 数据对象(双向)
record: {
type: Object,
required: true
},
// 页面配置 id 用于 create proSelect
pageId: {
type: String,
default: ''
},
// 外部传入的过滤参数
params: {
type: Object,
default: () => ({})
},
// 是否需要新增功能
created: {
type: Boolean,
default: true
},
// 选择器需要返回的名称
returnName: {
type: String,
default: ''
},
// 是否需要高级搜索
proSelect: {
type: Boolean,
default: true
},
// 是否禁用
disabled: {
type: Boolean,
default: false
},
// 单选 、 多选
mode: {
type: String,
default: 'default',
validator: (val) => {
return ['default', 'multiple'].includes(val)
}
},
// 控制 options 是否禁用 一般用于防止重复勾选
optionDisabled: {
type: Function,
default: () => {
return false
}
},
// 是否自动选择(点击下拉进行查询数据,如果只有一条并且未选择时,自动勾选)
autoSelect: {
type: Boolean,
default: true
},
title: {
type: String,
default: '请选择'
}
},
data () {
return {
indicator: <a-icon type="loading" style="font-size: 24px" spin />,
pageNo: 1,
dataSource: [],
config: {},
loading: false,
query: '',
result: {
pages: 1,
current: 1,
total: 1,
},
isInit: true, // 是否属于初始化
open: false,
}
},
computed: {
value () {
const value = this.record[this.dataIndex] || ''
if (this.mode === 'default') return value || undefined // 单选模式的数据 undefined
return value ? value.split(',') : [] // 多选模式的数据类型为 Array
},
// 是否开启滚动加载 true 一次 10条 false 全量加载 1000条 (这里就要求接口必须按规定返回,否则无法判断是否到底了)
// 之前是放到 prop 可选是否开启的 (a-select自己限制了最大数据量,不得不强制开启,同时必须开启远程搜索)
scrollLoad () {
return !!this.url.list
},
},
watch: {
// 本组件采用 watch 代替 handleChange (双向处理联动)
// 外部因为表单联动清空 record[dataindex] 也就是 value 时,该组件也会自动触发 勾选 handle 函数,完成下一级联动
value: {
handler (newVal, oldVal) {
if (!this.isInit && newVal !== undefined) {
this.handleChange(newVal)
}
// 如果已经是展开状态,不需要拉取数据
// 详情页进来回显 || 表单联动带入的值不在datasource里 都需要主动查一遍
if (this.url.list && this.open === false &&
(this.isInit || !this.dataSource.some(item => item[this.rowKey] === newVal))) {
this.refresh({ [this.rowKey]: this.scrollLoad ? this.record[this.dataIndex] : undefined })
}
this.isInit = false
},
immediate: true
},
// 使用外部数据源
list: {
handler () {
this.dataSource = this.list
},
immediate: true
},
// 当外部过滤参数变化时主动进行刷新
params: {
handler (newVal, oldVal) {
if (!this.isInit && JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
this.refresh()
}
},
deep: true
}
},
mounted () {
this.$set(this.record, this.dataIndex, this.record[this.dataIndex])
this.returnName && this.$set(this.record, this.returnName, this.record[this.returnName])
// 如果有 url 则调用接口获取数据
this.pageId && PageConfig.getConfig(this.pageId).then(res => {
this.config = res
})
},
methods: {
async loadData (query = {}) {
if (!this.url.list) return false
const params = {
...query,
...this.params,
pageNo: this.pageNo,
pageSize: this.scrollLoad ? 10 : 1000,
}
this.loading = true
const res = await http.get(this.url.list, { params })
this.result = res.result
this.dataSource = this.dataSource.concat(res.result.records || res.result || [])
this.loading = false
},
// 本地搜索(模糊搜索)
filterOption (input, option) {
return (
option.componentOptions.children[0].text.toLowerCase().indexOf(input.toLowerCase()) >= 0
)
},
// 远程搜索(模糊搜索)
handleSearch: T.throttle(function (input) {
this.query = input
this.dataSource = []
this.pageNo = 1
const param = {
[this.rowName]: this.query ? `*${this.query}*` : this.query
}
this.loadData(param)
}, 800, { trailing: true }),
// 滚动加载
handlePopupScroll (data) {
if (!this.result.pages) return false // 没有页数说明不支持分页
const { scrollHeight, scrollTop, clientHeight } = data.target
if (scrollHeight - scrollTop < clientHeight + 1 && this.loading === false &&
(this.pageNo < this.result.pages || this.dataSource.length < this.result.total)) { // 兼容没返回pages的接口,用数量判断
this.pageNo++
this.loadData()
}
},
// 高级控件的新增
handleCreate () {
this.$refs.Create.handleShow({}, 'create')
},
// 展开时查询数据
dropdownVisibleChange (open) {
this.open = open
if (this.scrollLoad && open) {
this.refresh()
}
},
openPop () {
const selectedRowKeys = this.mode === 'default' ? [this.value] : this.value
const keys = this.record[this.dataIndex]?.split(',') || []
const names = this.record[this.returnName]?.split(',') || []
const selectedRows = keys.map((item, index) => {
return {
[this.rowKey]: keys[index],
[this.rowName || 'none']: names[index], // 没有returnName 随便占个位置
}
})
this.$refs.Pop.handleShow(this.value ? selectedRowKeys : [], selectedRows) // 必须为数组
},
handleChange (val = []) {
const selectedRowKeys = Array.isArray(val) ? val : val.split(',') // 处理 单选 多选数据
this.returnSelectedRows({ selectedRowKeys })
},
// 目前不支持 value 为 number 类型
async returnSelectedRows ({ selectedRowKeys, selectedRows = [] }) {
if (this.loading === true) return false // 也就回显的时候 loading 会为 true, dataSource = [],会错误的清空数据
const tar = this.dataSource.filter(item => selectedRowKeys.includes(item[this.rowKey]))
const arr = tar.length ? tar : selectedRows
this.record[this.dataIndex] = selectedRowKeys.join(',') // 拼接 key
if (this.returnName) {
if (selectedRows.length) {
await this.refresh({ [this.rowKey]: this.scrollLoad ? this.record[this.dataIndex] : undefined })
this.record[this.returnName] = this.dataSource.map(item => item[this.rowName]).join(',') // 拼接 Name
} else {
this.record[this.returnName] = arr.map(item => item[this.rowName]).join(',') // 拼接 Name
}
}
this.$emit('returnSelectedRows', arr, this.record) // 抛出当前选中的 row record 丢出去 表格表单时可以直接给record赋值,不用烦恼到底是哪一行了
},
// 刷新数据
async refresh (query) {
this.pageNo = 1
this.dataSource = []
await this.loadData(query)
},
// 自动勾选 todo
handleAutoSelect () {
// 开启功能 && 只有一条数据 && 未勾选数据 && 没有id(非回显) 情况太多的建议弃用该功能
if (this.autoSelect && this.dataSource.length === 1 &&
this.record[this.dataIndex] && !this.record.id) {
this.handleChange(this.dataSource[0][this.rowKey]) // 自动勾选
}
}
}
}
</script>