feat(components): 添加数据穿梭组件并优化演示页面

- 新增 CuteTransfer 数据穿梭组件,支持左右两侧数据选择转移
- 更新 CuteOtherDemo 页面,替换 el-divider 为 cute-section-title 组件
- 在演示页面中添加 CuteTransfer 组件的展示和使用示例
- 添加 CuteButton 组件依赖并在相关位置使用
- 配置数据穿梭组件的数据源和基本属性
- 优化页面布局结构,提升组件展示效果
This commit is contained in:
2026-01-30 19:09:50 +08:00
parent a8d4340b55
commit ffc6c88f75
2 changed files with 254 additions and 19 deletions
@@ -0,0 +1,220 @@
<!--
* 数据穿梭组件(不适合大数据量)
* @author odboy
* @email tianjun@odboy.cn
* @created 2026-01-30
-->
<template>
<el-row>
<!-- <el-button @click="onTestValue">测试值</el-button>-->
<el-col :span="11">
<el-table
v-loading="loading"
stripe
border
empty-text="暂无数据"
:row-key="primaryKey"
fit
:data="leftDataSource"
style="width: 100%;"
:height="height"
:max-height="height"
highlight-selection-row
@selection-change="onLeftTableSelectionChange"
>
<el-table-column type="selection" width="55" />
<slot />
</el-table>
</el-col>
<el-col :span="2" style="text-align: center">
<el-row style="margin-top: 130px">
<el-button
type="primary"
icon="el-icon-d-arrow-right"
size="mini"
:disabled="leftSelection.length === 0"
@click="onTransferRight"
/>
</el-row>
<el-row style="margin-top: 20px">
<el-button
type="primary"
icon="el-icon-d-arrow-left"
size="mini"
:disabled="rightSelection.length === 0"
@click="onTransferLeft"
/>
</el-row>
</el-col>
<el-col :span="11">
<el-table
ref="selectedTable"
stripe
border
empty-text="暂无数据"
:row-key="primaryKey"
fit
:data="rightDataSource"
style="width: 100%;"
:height="height"
:max-height="height"
highlight-selection-row
@selection-change="onRightTableSelectionChange"
>
<el-table-column type="selection" width="55" />
<slot />
</el-table>
</el-col>
</el-row>
</template>
<script>
export default {
name: 'CuteTransfer',
props: {
/**
* 已选中的值
*/
value: {
type: Array,
default: function() {
return []
}
},
/**
* 左侧表格数据
*/
dataSource: {
type: Array,
require: true,
default: function() {
return []
}
},
/**
* 取哪个值作为value
*/
primaryKey: {
type: String,
require: true,
default: 'id'
},
/**
* 是否禁用
*/
disabled: {
type: Boolean,
default: false
},
/**
* 是否加载中
*/
loading: {
type: Boolean,
default: false
}
},
data() {
return {
height: 350,
leftDataSource: [],
leftSelection: [],
rightDataSource: [],
rightSelection: [],
rightValues: []
}
},
mounted() {
this.rightValues = this.value
this.leftDataSource = this.dataSource
const key = this.primaryKey
const leftDataSource = this.leftDataSource || []
const rightValues = this.rightValues
if (rightValues && rightValues.length > 0) {
const rightDataSource = []
for (const element of leftDataSource) {
if (rightValues.indexOf(element[key]) !== -1) {
rightDataSource.push(element)
}
}
this.rightDataSource = rightDataSource
this.leftDataSource = leftDataSource.filter(item => !rightValues.includes(item[key]))
} else {
this.leftDataSource = leftDataSource
}
},
methods: {
onLeftTableSelectionChange(selection) {
this.leftSelection = selection
},
onRightTableSelectionChange(selection) {
this.rightSelection = selection
},
/**
* 左侧转右侧
*/
onTransferRight() {
const key = this.primaryKey
const leftDataSource = this.leftDataSource
const leftSelection = this.leftSelection
const rightDataSource = this.rightDataSource
const rightValues = this.rightValues
if (leftSelection && leftSelection.length > 0) {
for (const element of leftSelection) {
rightDataSource.push(element)
}
this.rightDataSource = rightDataSource
for (const element of leftSelection) {
rightValues.push(element[key])
}
this.rightValues = rightValues
// 清空左侧已选中
this.leftSelection = []
// 过滤左侧选中的数据源
this.leftDataSource = leftDataSource.filter(item => !rightValues.includes(item[key]))
// 兼容表单
this.$emit('change', this.rightValues)
this.$emit('input', this.rightValues)
}
},
/**
* 右侧转左侧
*/
onTransferLeft() {
const key = this.primaryKey
const leftDataSource = this.leftDataSource
const rightDataSource = this.rightDataSource
const rightSelection = this.rightSelection
const rightValues = this.rightValues
if (rightSelection && rightSelection.length > 0) {
for (const element of rightSelection) {
leftDataSource.push(element)
}
this.leftDataSource = leftDataSource
// 从右侧数据源中移除选中的项
const selectedKeys = rightSelection.map(item => item[key])
this.rightDataSource = rightDataSource.filter(item => !selectedKeys.includes(item[key]))
this.rightValues = rightValues.filter(value => !selectedKeys.includes(value))
// 清空右侧已选中
this.rightSelection = []
// 兼容表单
this.$emit('change', this.rightValues)
this.$emit('input', this.rightValues)
}
}
// onTestValue() {
// console.error('==============leftDataSource', this.leftDataSource)
// console.error('==============leftSelection', this.leftSelection)
// console.error('==============rightDataSource', this.rightDataSource)
// console.error('==============rightSelection', this.rightSelection)
// console.error('==============rightValues', this.rightValues)
// }
}
}
</script>
@@ -1,6 +1,6 @@
<template> <template>
<div style="padding: 50px"> <div>
<el-divider>CuteSearchForm</el-divider> <cute-section-title title="高级搜索组件 CuteSearchForm" />
<cute-search-form :model="searchFormModel" @submit="handleSearchFormSubmit"> <cute-search-form :model="searchFormModel" @submit="handleSearchFormSubmit">
<template v-slot:suffix> <template v-slot:suffix>
<el-form-item label="模糊查询" prop="blurry"> <el-form-item label="模糊查询" prop="blurry">
@@ -11,23 +11,32 @@
<el-button type="primary">查询后置按钮</el-button> <el-button type="primary">查询后置按钮</el-button>
</template> </template>
</cute-search-form> </cute-search-form>
<el-divider>CuteHorizontalScrollBar</el-divider> <cute-section-title title="横向滚动组件 CuteHorizontalScrollBar" />
<cute-horizontal-scroll-bar> <cute-horizontal-scroll-bar>
<div style="width: 3000px;min-width: 3000px">模拟超长内容</div> <div style="width: 3000px;min-width: 3000px">模拟超长内容</div>
</cute-horizontal-scroll-bar> </cute-horizontal-scroll-bar>
<el-divider>CuteVerticalScrollBar</el-divider> <cute-section-title title="纵向滚动组件 CuteVerticalScrollBar" />
<cute-vertical-scroll-bar> <cute-vertical-scroll-bar>
<div style="height: 3000px;min-height: 3000px">模拟超高内容</div> <div style="height: 3000px;min-height: 3000px">模拟超高内容</div>
</cute-vertical-scroll-bar> </cute-vertical-scroll-bar>
<el-divider>CuteLink</el-divider> <cute-section-title title="数据穿梭组件 CuteTransfer" />
<cute-transfer :data-source="leftDataSource" primary-key="id">
<el-table-column prop="name" label="姓名" />
<el-table-column prop="description" label="备注" />
</cute-transfer>
<cute-section-title title="链接组件 CuteLink" />
<cute-link title="百度一下" href="https://www.baidu.com" /> <cute-link title="百度一下" href="https://www.baidu.com" />
<el-divider>CuteNoData</el-divider> <cute-section-title title="空状态 CuteNoData" />
<cute-no-data desc="自定义提示语" /> <cute-no-data desc="自定义提示语" />
<el-divider>CutePreviewDrawer <el-button type="primary" @click="handleCutePreviewDrawerShow">显示</el-button></el-divider> <cute-section-title title="预览抽屉 CutePreviewDrawer">
<template v-slot:operation>
<cute-button type="primary" @click="handleCutePreviewDrawerShow">显示</cute-button>
</template>
</cute-section-title>
<cute-preview-drawer ref="cpd" title="预览抽屉"> <cute-preview-drawer ref="cpd" title="预览抽屉">
<p>预览抽屉预览抽屉预览抽屉预览抽屉预览抽屉预览抽屉预览抽屉预览抽屉</p> <p>预览抽屉预览抽屉预览抽屉预览抽屉预览抽屉预览抽屉预览抽屉预览抽屉</p>
</cute-preview-drawer> </cute-preview-drawer>
<el-divider>截屏:包裹要截屏的组件</el-divider> <cute-section-title title="截屏组件 CuteScreenShot:包裹要截屏的组件" />
<cute-screen-shot> <cute-screen-shot>
<cute-search-form :model="searchFormModel" @submit="handleSearchFormSubmit" @reset="handleSearchFormReset"> <cute-search-form :model="searchFormModel" @submit="handleSearchFormSubmit" @reset="handleSearchFormReset">
<template v-slot:suffix> <template v-slot:suffix>
@@ -36,20 +45,15 @@
</el-form-item> </el-form-item>
</template> </template>
<template v-slot:operation> <template v-slot:operation>
<el-button type="primary">查询后置按钮</el-button> <cute-button type="primary">查询后置按钮</cute-button>
</template> </template>
</cute-search-form> </cute-search-form>
</cute-screen-shot> </cute-screen-shot>
<el-divider>CuteSectionTitle</el-divider> <cute-section-title title="段落标题 CuteSectionTitle" />
<cute-section-title title="段落标题">
<el-button type="danger" size="mini">操作按钮1</el-button>
<el-button type="danger" size="mini">操作按钮2</el-button>
<el-button type="danger" size="mini">操作按钮3</el-button>
</cute-section-title>
<cute-section-title title="主标题" sub-title="副标题" third-title="三级标题"> <cute-section-title title="主标题" sub-title="副标题" third-title="三级标题">
<el-button type="danger" size="mini">操作按钮1</el-button> <cute-button type="danger" size="mini">操作按钮1</cute-button>
<el-button type="danger" size="mini">操作按钮2</el-button> <cute-button type="danger" size="mini">操作按钮2</cute-button>
<el-button type="danger" size="mini">操作按钮3</el-button> <cute-button type="danger" size="mini">操作按钮3</cute-button>
</cute-section-title> </cute-section-title>
</div> </div>
</template> </template>
@@ -64,10 +68,14 @@ import CuteNoData from '@/views/components/dev/CuteNoData.vue'
import CutePreviewDrawer from '@/views/components/dev/CutePreviewDrawer.vue' import CutePreviewDrawer from '@/views/components/dev/CutePreviewDrawer.vue'
import CuteScreenShot from '@/views/components/dev/CuteScreenShot.vue' import CuteScreenShot from '@/views/components/dev/CuteScreenShot.vue'
import CuteSectionTitle from '@/views/components/dev/CuteSectionTitle.vue' import CuteSectionTitle from '@/views/components/dev/CuteSectionTitle.vue'
import CuteButton from '@/views/components/dev/CuteButton.vue'
import CuteTransfer from '@/views/components/dev/CuteTransfer.vue'
export default { export default {
name: 'CuteOtherDemo', name: 'CuteOtherDemo',
components: { components: {
CuteTransfer,
CuteButton,
CuteSectionTitle, CuteSectionTitle,
CuteScreenShot, CuteScreenShot,
CutePreviewDrawer, CutePreviewDrawer,
@@ -79,7 +87,14 @@ export default {
}, },
data() { data() {
return { return {
searchFormModel: {} searchFormModel: {},
leftDataSource: [
{ id: 1, name: '小明', description: '小明牛P' },
{ id: 2, name: '小明', description: '小明牛P' },
{ id: 3, name: '小明', description: '小明牛P' },
{ id: 4, name: '小明', description: '小明牛P' },
{ id: 5, name: '小明', description: '小明牛P' }
]
} }
}, },
methods: { methods: {