123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548 |
- <script>
- import { Transfer, Tabs, Icon, Button, Modal } from 'ant-design-vue'
- import uniqueArray from '@/common/services/unique-array'
- import sdDraggableModal from '@/common/components/sd-draggable-modal'
- import pickerMixinInternal from '@/common/components/sd-picker/picker-mixin-internal'
- import pickerListRight from '@/common/components/sd-picker/sd-picker-list-right.vue'
- import SdSelect from '@/common/components/sd-select.vue'
- import components from './_import-components/law-case-value-picker-import'
- /**
- * input框,通过弹出对话框选择数据,支持列表、树等多种数据源
- * @displayName SdValuePicker 值选择器
- */
- export default {
- name: 'LawCaseValuePicker',
- components,
- mixins: [pickerMixinInternal],
- props: {
- selectclick: {
- type: Function,
- default: null,
- },
- /**
- * 根节点{code:'200000',name:'西安分公司'}
- */
- rootNode: {
- type: [Object, Array],
- default: undefined,
- },
- },
- data() {
- return {
- activeTab: 0, // 当前激活的tab页
- pickerSources: [], // 各数据源的属性,如标题、图标等
- modalVisible: false,
- targetKeys: [], // 已经选到右边的key,
- dataSourceInTargetKeys: [], // 已经选到右边的key,每一项都保存一份数据源信息
- selectedKeys: [],
- dataSource: {},
- showSearch: {},
- searchValue: {},
- }
- },
- computed: {
- singleMode() {
- return this.single || this.singleColumn
- },
- keysToInput() {
- // 不管什么模式,最终将要写入Input框的keys
- return this.singleMode ? this.selectedKeys : this.targetKeys
- },
- dblClickMode() {
- // 单列模式不能使用点击移动
- return this.dblClickTransfer || this.singleMode
- },
- allDataSourceOriginal() {
- return this.allDataSource.map((item) => item.originalValue)
- },
- allDataSource() {
- const currentTabDataSource = this.dataSource[this.activeTab] || []
- // 当前tab页的数据源+已选中到右边的数据源
- return this.dataSourceInTargetKeys
- .filter(
- // 过滤掉已经在数据源里面的
- (item) =>
- !currentTabDataSource.find(
- (item2) => item[this.optionValue] === item2[this.optionValue]
- )
- )
- .concat(
- currentTabDataSource.filter(
- // 把disabledKeys中的数据源去掉
- (item) => this.disabledKeys.indexOf(item[this.optionValue]) === -1
- )
- )
- .map((item) => {
- return {
- key: item[this.optionValue],
- title: item[this.optionLabel],
- disabled: item.disabled === true,
- originalValue: item,
- }
- })
- },
- dataSourceRight() {
- // 已选中部分的数据源
- return this.targetKeys.map(
- (key) => this.allDataSource.find((item) => item.key === key).originalValue
- )
- },
- },
- watch: {
- targetKeys(keys) {
- // 已经选到右边的key,每一项都保存一份数据源信息
- const dataSource = keys.map((key) =>
- this.allDataSourceOriginal.find((data) => key === data[this.optionValue])
- )
- this.dataSourceInTargetKeys = dataSource
- },
- },
- mounted() {
- // 计算有几个数据源
- this.pickerSources = this.$scopedSlots
- .default()
- .filter((vnode) => !!vnode.componentOptions)
- .map((vnode) => {
- return {
- title: vnode.componentOptions.propsData.title,
- icon: vnode.componentOptions.propsData.icon,
- }
- })
- },
- methods: {
- change(keys, direction, moveKeys) {
- if (direction === 'right') {
- // this.targetKeys = this.targetKeys.concat(moveKeys)
- this.targetKeys = moveKeys
- } else {
- this.targetKeys = keys
- }
- },
- selectChange(keys) {
- // 单选模式,始终只选中一个
- if (this.single) {
- if (keys.length > 0) this.selectedKeys = [keys[keys.length - 1]]
- } else if (this.singleColumn) {
- this.selectedKeys = keys
- }
- },
- closeModal() {
- this.modalVisible = false
- },
- inputClick() {
- // 只读模式不弹窗,直接返回
- if (this.readOnly) return
- if (this.selectclick !== null) {
- this.selectclick()
- } else {
- this.$refs.select.blur()
- // 初始化各种选择器相关的数据
- this.activeTab = 0
- this.dataSource = {}
- this.dataSourceInTargetKeys = this.value
- this.showSearch = {}
- this.targetKeys = this.selectedKeys = this.keyInValue()
- if (this.rootNode !== undefined) {
- if (this.rootNode.id !== '') {
- this.modalVisible = true
- } else {
- let lx = ''
- if (this.rootNode.name.indexOf('内控') > -1) {
- lx = '内控'
- } else if (this.rootNode.name.indexOf('风控') > -1) {
- lx = '风控'
- }
- Modal.info({
- content: '请配置组织对应的' + lx + '机构',
- })
- }
- } else {
- this.modalVisible = true
- }
- }
- },
- handleOk() {
- const keys = this.keysToInput
- const value = keys.map((key) => {
- return this.allDataSource.find((item) => item.key === key).originalValue
- })
- this.$emit('change', value)
- this.closeModal()
- },
- updateDataSource(data) {
- // 先把数组去重处理
- this.dataSource[this.activeTab] = uniqueArray(data, (item) => item[this.optionValue])
- this.dataSource = { ...this.dataSource }
- },
- updateShowSearch(data) {
- this.showSearch[this.activeTab] = data
- this.showSearch = { ...this.showSearch }
- },
- itemDblClick(evt, key, direction) {
- if (direction !== 'right') {
- if (this.single) {
- this.handleOk()
- } else {
- if (this.singleColumn) return
- this.targetKeys.splice(this.targetKeys.indexOf(key), 1)
- this.targetKeys.push(key)
- }
- } else {
- this.targetKeys.splice(this.targetKeys.indexOf(key), 1)
- }
- },
- removeTargetKeyItem(key) {
- this.targetKeys.splice(this.targetKeys.indexOf(key), 1)
- },
- keyInValue() {
- return this.value ? this.value.map((v) => v[this.optionValue]) : []
- },
- },
- /**
- * input框最后的图标
- * @slot suffixIcon
- */
- /**
- * 选择器的数据源,可以是多个
- * @slot default
- * @binding scope {object} 各种值和工具函数,无需解析直接传递给内部的数据源即可
- */
- /**
- * 右侧列表顶部
- * <p>8.0.7 新增</p>
- * @slot targetListHeader
- * @binding targetKeys {array} 右侧列表所有项目的 key 值
- * @binding targetValues {array} 右侧列表所有项目的完整 Object 值
- */
- render(h) {
- const renderTransfer = (index) => {
- return (
- <div
- class={{ [this.$style.wrapper]: true, [this.$style.singleClickMode]: !this.dblClickMode }}
- >
- {this.singleMode || index !== this.activeTab ? (
- undefined
- ) : (
- <div class={this.$style.targetListHeader}>
- {this.$scopedSlots.targetListHeader?.({
- targetKeys: this.targetKeys,
- targetValues: this.dataSourceInTargetKeys,
- })}
- <Button
- icon='sd-trash2'
- type='link'
- title='全部删除'
- on={{
- click: () => {
- this.targetKeys = []
- },
- }}
- ></Button>
- </div>
- )}
- {this.singleMode ? (
- undefined
- ) : (
- <div class={this.$style.sourceListHeader} style='display:none'>
- <Button
- icon='double-right'
- type='link'
- title='全部增加'
- on={{
- click: () =>
- this.allDataSource.forEach((item) => {
- if (!this.targetKeys.includes(item.key)) {
- this.targetKeys.push(item.key)
- }
- }),
- }}
- ></Button>
- </div>
- )}
- <Transfer
- class={[
- this.$style.transfer,
- {
- [this.$style.single]: this.singleMode,
- [this.$style.singleColumn]: this.singleColumn,
- [this.$style.showSearch]: !this.singleColumn && this.showSearch[this.activeTab],
- },
- ]}
- showSelectAll={!this.single}
- dataSource={this.allDataSource}
- targetKeys={this.singleMode ? undefined : this.targetKeys}
- selectedKeys={!this.singleMode ? undefined : this.selectedKeys}
- lazy={false}
- showSearch={true} // 用css隐藏搜索框,改prop的值会导致内部picker-source组件重新创建
- filterOption={() => true} // 不过滤任何数据,只是为了显示出搜索框来
- on={{
- change: this.change,
- search: (direction, value) => {
- if (direction === 'left') this.searchValue[index] = value
- },
- selectChange: this.selectChange,
- }}
- scopedSlots={{
- children: (scope) => {
- if (scope.props.direction !== 'left')
- return (
- <pickerListRight
- itemSelect={
- this.dblClickMode
- ? scope.on.itemSelect
- : (key, checked) => this.itemDblClick({}, key, 'right')
- }
- dataSource={this.dataSourceRight}
- selectedKeys={scope.props.selectedKeys}
- targetKeys={this.targetKeys}
- itemDblClick={this.dblClickTransfer ? this.itemDblClick : () => {}}
- render={this.render}
- optionValue={this.optionValue}
- optionLabel={this.optionLabel}
- on={{
- updateTargetKeys: (keys) => {
- this.targetKeys = keys
- },
- }}
- />
- )
- return [
- this.$scopedSlots
- .default({
- render: this.render,
- single: this.single,
- selectedKeys: scope.props.selectedKeys,
- targetKeys: this.singleMode ? [] : this.targetKeys,
- disabledKeys: this.disabledKeys,
- itemSelect: this.dblClickMode
- ? this.singleColumn
- ? (...args) => {
- // 单列多选模式时,对于selectedKeys的处理有些问题,加个timeout
- setTimeout(() => scope.on.itemSelect(...args))
- }
- : scope.on.itemSelect
- : (key, checked) => this.itemDblClick({}, key, 'left'),
- updateDataSource: this.updateDataSource,
- dataSource: index === this.activeTab ? this.allDataSourceOriginal : [],
- itemDblClick: this.dblClickTransfer ? this.itemDblClick : () => {},
- optionValue: this.optionValue,
- optionLabel: this.optionLabel,
- updateShowSearch: this.updateShowSearch,
- searchValue: this.searchValue[index],
- })
- .filter((vnode) => !!vnode.componentOptions)[index],
- ]
- },
- }}
- ></Transfer>
- </div>
- )
- }
- return (
- <span
- class={{
- [this.$style.allowClear]: this.value?.length > 1,
- [this.$style.selectWrapper]: true,
- }}
- >
- <SdSelect
- ref='select'
- value={this.value}
- optionLabel={this.optionLabel}
- optionValue={this.optionValue}
- options={[]}
- open={false}
- readOnly={this.readOnly}
- mode='multiple'
- showArrow={false}
- on={{
- ...this.$listeners,
- }}
- nativeOnClick={this.inputClick}
- />
- {this.$scopedSlots.suffixIcon && !this.readOnly ? (
- <span class={this.$style.icon}>{this.$scopedSlots.suffixIcon()}</span>
- ) : (
- undefined
- )}
- <sdDraggableModal
- title={this.title}
- visible={this.modalVisible}
- destroyOnClose={true}
- width={
- this.modalProps.width ||
- (this.single ? 600 : 800) + (this.pickerSources.length > 1 ? 100 : 0)
- }
- okButtonProps={
- this.required && this.keysToInput.length === 0 ? { props: { disabled: true } } : {}
- }
- attrs={this.modalProps}
- on={{
- cancel: () => {
- this.closeModal()
- this.$emit('cancel')
- },
- ok: this.handleOk,
- }}
- >
- {this.pickerSources.length > 1 ? (
- <Tabs
- class={this.$style.tab}
- tabPosition='left'
- on={{ change: (tab) => (this.activeTab = tab) }}
- >
- {this.pickerSources.map((source, index) => {
- return (
- <Tabs.TabPane key={index}>
- <div slot='tab' class={this.$style.tabTitle}>
- {source.icon ? <Icon type={source.icon} /> : undefined}
- {source.title ? <div>{source.title}</div> : undefined}
- </div>
- {renderTransfer(index)}
- </Tabs.TabPane>
- )
- })}
- </Tabs>
- ) : (
- renderTransfer(0)
- )}
- </sdDraggableModal>
- </span>
- )
- },
- }
- </script>
- <style module lang="scss">
- @use '@/common/design' as *;
- .wrapper {
- position: relative;
- }
- .select-wrapper {
- position: relative;
- display: inline-block;
- width: 100%;
- :global .ant-select {
- padding: 4px 0; // 保持上下边距相等,目前还没弄清为什么必须要加padding
- vertical-align: middle;
- .ant-select-selection {
- max-height: 86px; // 最多显示三行
- overflow-y: auto;
- /* stylelint-disable-next-line */
- .ant-select-selection__clear {
- top: 50%;
- }
- }
- }
- }
- .allow-clear:hover {
- .icon {
- opacity: 0;
- }
- }
- .icon {
- position: absolute;
- top: 50%;
- right: 11px;
- margin-top: -$font-size-base / 2;
- line-height: $font-size-base;
- color: $disabled-color;
- pointer-events: none;
- transition: opacity 0.15s ease;
- }
- .transfer {
- display: flex;
- :global .ant-transfer-list {
- flex: 1;
- height: 500px;
- .ant-transfer-list-body {
- display: flex;
- flex-direction: column;
- }
- .ant-transfer-list-body-search-wrapper {
- padding-bottom: 12px;
- }
- .ant-transfer-list-content-item {
- white-space: normal;
- user-select: none;
- }
- .ant-radio-wrapper {
- margin-right: 0;
- }
- }
- // 右侧待选区,不显示搜索框
- :global .ant-transfer-list:last-child {
- .ant-transfer-list-body-search-wrapper {
- display: none;
- }
- }
- &:not(.show-search) {
- :global .ant-transfer-list-body-search-wrapper {
- // display: none;
- }
- }
- }
- .tab {
- &:global(.ant-tabs .ant-tabs-left-bar .ant-tabs-tab) {
- padding: 8px 15px 8px 0;
- .tab-title {
- text-align: center;
- :global(.anticon) {
- margin-right: 0;
- font-size: $font-size-base * 1.5;
- }
- }
- }
- }
- .single {
- :global .ant-transfer-operation,
- :global .ant-transfer-list:last-child {
- display: none;
- }
- &:not(.single-column) {
- :global(.ant-transfer-list-header) {
- display: none;
- }
- :global .ant-transfer-list {
- padding-top: 0;
- }
- }
- }
- .list-text {
- display: inline-block;
- min-width: 200px;
- }
- .target-list-header {
- position: absolute;
- right: 0;
- z-index: 1;
- padding: 3px 0;
- }
- .source-list-header {
- position: absolute;
- left: 50%;
- z-index: 1;
- padding: 3px 0;
- margin-left: -52px;
- }
- .single-click-mode {
- :global(.ant-transfer-list-content .ant-checkbox-wrapper),
- :global(.ant-transfer-list-header .ant-checkbox-wrapper) {
- width: 0;
- margin-right: -8px;
- visibility: hidden;
- }
- }
- </style>
|