Commit f461f24e00113a9ecb146bfcfa535577fc8a0e2f

Authored by 刘汉宸
1 parent 10eba06c

feat: 新增SchemaTransfer组件

examples/router/routes.js
@@ -101,6 +101,12 @@ const _components = [ @@ -101,6 +101,12 @@ const _components = [
101 meta: { title: 'Schema Select 选择器' }, 101 meta: { title: 'Schema Select 选择器' },
102 component: () => import('@/views/docs/component/schema-select.md'), 102 component: () => import('@/views/docs/component/schema-select.md'),
103 }, 103 },
  104 + {
  105 + path: 'schema-transfer',
  106 + name: 'schema-transfer',
  107 + meta: { title: 'Schema Transfer 穿梭框' },
  108 + component: () => import('@/views/docs/component/schema-transfer.md'),
  109 + },
104 ], 110 ],
105 }, 111 },
106 ]; 112 ];
examples/views/docs/component/schema-transfer.md 0 → 100644
@@ -0,0 +1,208 @@ @@ -0,0 +1,208 @@
  1 +# Schema Transfer 方案穿梭框
  2 +
  3 +通过配置JSON Schema的方式快速生成一个穿梭框,与常规穿梭框不同的是,本穿梭框是表格型穿梭框。
  4 +
  5 +## 基础用法
  6 +
  7 +一个展示内容更多的表格形式的下拉选择器,基本配置项与`z-schema-page`中`schema`相同。
  8 +
  9 +::: snippet `source`设置数据源, `titles`设置标题, `value-key`设置绑定值主键,默认为**id**。
  10 +
  11 +```html
  12 +<template>
  13 + <z-schema-transfer v-model="model" :schema="schema" :titles="titles" :source="source" value-key="id" size="small"></z-schema-transfer>
  14 +</template>
  15 +
  16 +<script>
  17 +export default {
  18 + data() {
  19 + return {
  20 + model: [],
  21 + source: [
  22 + { id: '0', name: '姓名0', age: 17 },
  23 + { id: '1', name: '姓名1', age: 26 },
  24 + ],
  25 + titles: ['左边数据源', '右边已选中'],
  26 + schema: {
  27 + table: {
  28 + items: [
  29 + { label: '姓名', prop: 'name', minWidth: 120 },
  30 + { label: '年龄', prop: 'age', minWidth: 120 },
  31 + { label: '地址', prop: 'address', minWidth: 120 },
  32 + ]
  33 + },
  34 + },
  35 + }
  36 + },
  37 +}
  38 +</script>
  39 +```
  40 +
  41 +:::
  42 +
  43 +## 远程搜索
  44 +
  45 +数据源支持远程搜索。
  46 +
  47 +::: snippet `api-search`设置查询方法,返回值格式与`z-schema-page`相同。
  48 +
  49 +```html
  50 +<template>
  51 + <z-schema-transfer v-model="model" :schema="schema" :value-filter.sync="filterModel" value-key="id" size="small" :api-search="searchAPI" auto></z-schema-transfer>
  52 +</template>
  53 +
  54 +<script>
  55 +export default {
  56 + data() {
  57 + return {
  58 + model: [],
  59 + filterModel: { name: '' },
  60 + schema: {
  61 + filter: {
  62 + items: [
  63 + { is: 'el-input', label: '姓名', prop: 'name' },
  64 + ]
  65 + },
  66 + table: {
  67 + items: [
  68 + { label: '姓名', prop: 'name', minWidth: 120 },
  69 + { label: '年龄', prop: 'age', minWidth: 120 },
  70 + { label: '地址', prop: 'address', minWidth: 120 },
  71 + ]
  72 + },
  73 + },
  74 + }
  75 + },
  76 + methods: {
  77 + searchAPI(params) {
  78 + console.log('search', params);
  79 + return new Promise(resolve => {
  80 + setTimeout(() => {
  81 + const list = [
  82 + { id: '0', name: '李饼', age: 32, address: '地址0', status: '正常' },
  83 + { id: '1', name: '陈拾', age: 20, address: '地址1', status: '正常' },
  84 + { id: '3', name: '王七', age: 26, address: '地址3', status: '正常' },
  85 + { id: '4', name: '崔倍', age: 27, address: '地址4', status: '正常' },
  86 + { id: '5', name: '孙豹', age: 38, address: '地址5', status: '正常' },
  87 + ]
  88 + resolve([list, 37]);
  89 + }, 500);
  90 + });
  91 + },
  92 + }
  93 +}
  94 +</script>
  95 +```
  96 +
  97 +:::
  98 +
  99 +## 选中表格配置
  100 +
  101 +可以单独配置已选中的表格
  102 +
  103 +::: snippet `schema`中设置`selected`,格式与`z-schema-table`相同。
  104 +
  105 +```html
  106 +<template>
  107 + <z-schema-transfer v-model="model" :schema="schema" :source="source" value-key="id" size="small"></z-schema-transfer>
  108 +</template>
  109 +
  110 +<script>
  111 +export default {
  112 + data() {
  113 + return {
  114 + model: [],
  115 + source: [
  116 + { id: '0', name: '姓名0', age: 17 },
  117 + { id: '1', name: '姓名1', age: 26 },
  118 + ],
  119 + schema: {
  120 + table: {
  121 + items: [
  122 + { label: '姓名', prop: 'name', minWidth: 120 },
  123 + { label: '年龄', prop: 'age', minWidth: 120 },
  124 + { label: '地址', prop: 'address', minWidth: 120 },
  125 + ]
  126 + },
  127 + selected: {
  128 + props: { border: true, editable: true },
  129 + items: [
  130 + { label: '姓名', prop: 'name', minWidth: 120, editable: false },
  131 + { label: '年龄', prop: 'age', minWidth: 120, editalways: true },
  132 + { label: '地址', prop: 'address', minWidth: 120 },
  133 + ]
  134 + }
  135 + },
  136 + }
  137 + },
  138 +}
  139 +</script>
  140 +```
  141 +
  142 +:::
  143 +
  144 +## 自定义内容
  145 +
  146 +在不满足业务需求的情况下,可以设置插槽自定义左右内容
  147 +
  148 +::: snippet 默认插槽`default`表示数据源,插槽`selected`表示已选中的内容
  149 +
  150 +```html
  151 +<template>
  152 + <z-schema-transfer v-model="model" :schema="schema" :source="source" value-key="id" size="small">
  153 + <template #title-left>未选择 <i class="el-icon-minus"></i></template>
  154 + <template #default="{ onChoose, valueFormatter }">
  155 + <div>
  156 + <el-button size="mini" v-for="item in valueFormatter(source)" @click="onChoose(item)">{{ item.name }}</el-button>
  157 + </div>
  158 + </template>
  159 + <template #title-right>已选择 <i class="el-icon-check"></i></template>
  160 + <template #selected="{ onRemove }">
  161 + <div>
  162 + <el-button size="mini" v-for="item in model" @click="onRemove(item)">{{ item.name }}</el-button>
  163 + </div>
  164 + </template>
  165 + </z-schema-transfer>
  166 +</template>
  167 +
  168 +<script>
  169 +export default {
  170 + data() {
  171 + return {
  172 + model: [],
  173 + source: [
  174 + { id: '0', name: '姓名0', age: 17 },
  175 + { id: '1', name: '姓名1', age: 26 },
  176 + ],
  177 + schema: {
  178 + table: {
  179 + items: [
  180 + { label: '姓名', prop: 'name', minWidth: 120 },
  181 + { label: '年龄', prop: 'age', minWidth: 120 },
  182 + { label: '地址', prop: 'address', minWidth: 120 },
  183 + ]
  184 + },
  185 + selected: {
  186 + props: { border: true, editable: true },
  187 + items: [
  188 + { label: '姓名', prop: 'name', minWidth: 120, editable: false },
  189 + { label: '年龄', prop: 'age', minWidth: 120, editalways: true },
  190 + { label: '地址', prop: 'address', minWidth: 120 },
  191 + ]
  192 + }
  193 + },
  194 + }
  195 + },
  196 +}
  197 +</script>
  198 +```
  199 +
  200 +:::
  201 +
  202 +## API
  203 +
  204 +## Attribute 属性
  205 +
  206 +参数|说明|类型|可选值|默认值
  207 +-|-|-|-|-
  208 +schema | JSON Schema配置项列表 | Array | - | []
0 \ No newline at end of file 209 \ No newline at end of file
packages/schema-transfer/index.vue 0 → 100644
@@ -0,0 +1,229 @@ @@ -0,0 +1,229 @@
  1 +<style lang="scss">
  2 +.z-schema-transfer {
  3 + display: flex;
  4 + justify-content: space-between;
  5 + box-sizing: border-box;
  6 + &__left,
  7 + &__right {
  8 + width: 50%;
  9 + flex: 1;
  10 + border: 1px solid #ebeef5;
  11 + }
  12 + &__left {
  13 + border-right: 0;
  14 + }
  15 + &__header {
  16 + display: flex;
  17 + background: #f5f7fa;
  18 + padding: 8px 10px;
  19 + font-size: 14px;
  20 + }
  21 + &__content {
  22 + padding: 10px;
  23 + }
  24 +}
  25 +</style>
  26 +
  27 +<template>
  28 + <div class="z-schema-transfer">
  29 + <div class="z-schema-transfer__left">
  30 + <div class="z-schema-transfer__header">
  31 + <div class="z-schema-transfer__title">
  32 + <slot name="title-left">{{ titles[0] }}</slot>
  33 + </div>
  34 + </div>
  35 + <div class="z-schema-transfer__content">
  36 + <slot :onChoose="onChoose" :valueFormatter="valueFormatter">
  37 + <z-schema-page
  38 + :size="transferSize"
  39 + :value-filter="valueFilter"
  40 + :value-table="valueTable"
  41 + :schema="schemaLeft"
  42 + :api-search="searchMethod"
  43 + @update:value-filter="e => $emit('update:value-filter', e)"
  44 + :auto="auto"
  45 + >
  46 + <template #operation>
  47 + <el-table-column label="操作" width="80" align="center" fixed="right">
  48 + <template #default="{ row, $index }">
  49 + <div class="z-schema-page__table-operation">
  50 + <el-button type="text" :disabled="_rowDisabled(row, $index)" @click="onChoose(row)">选择</el-button>
  51 + </div>
  52 + </template>
  53 + </el-table-column>
  54 + </template>
  55 + </z-schema-page>
  56 + </slot>
  57 + </div>
  58 + </div>
  59 + <div class="z-schema-transfer__right">
  60 + <div class="z-schema-transfer__header">
  61 + <div class="z-schema-transfer__title">
  62 + <slot name="title-right">{{ titles[1] }}</slot>
  63 + </div>
  64 + </div>
  65 + <div class="z-schema-transfer__content">
  66 + <slot name="selected" :onRemove="onRemove">
  67 + <z-schema-table :size="transferSize" :value="value" :schema="schemaRight" @input="onInput">
  68 + <el-table-column label="操作" width="80" align="center" fixed="right">
  69 + <template #default="{ row, $index }">
  70 + <div class="z-schema-page__table-operation">
  71 + <el-button type="text" :disabled="_rowDisabled(row, $index)" @click="onRemove(row, $index)">移除</el-button>
  72 + </div>
  73 + </template>
  74 + </el-table-column>
  75 + </z-schema-table>
  76 + </slot>
  77 + </div>
  78 + </div>
  79 + </div>
  80 +</template>
  81 +
  82 +<script>
  83 +import { cloneDeep, get } from '../utils';
  84 +
  85 +export default {
  86 + name: 'SchemaTransfer',
  87 + props: {
  88 + value: {
  89 + type: Array,
  90 + default() {
  91 + return [];
  92 + },
  93 + },
  94 + schema: {
  95 + required: true,
  96 + type: Object,
  97 + default() {
  98 + return {};
  99 + },
  100 + },
  101 + titles: {
  102 + type: Array,
  103 + default() {
  104 + return ['未选中', '已选中'];
  105 + },
  106 + },
  107 + source: Array,
  108 + valueKey: {
  109 + type: String,
  110 + default: 'id',
  111 + },
  112 + size: String,
  113 + disabled: Boolean,
  114 + auto: Boolean,
  115 + rowDisabled: Function,
  116 + apiSearch: Function,
  117 + valueFilter: {
  118 + type: Object,
  119 + default() {
  120 + return {};
  121 + },
  122 + },
  123 + },
  124 + inject: {
  125 + elForm: {
  126 + default: undefined,
  127 + },
  128 + elFormItem: {
  129 + default: undefined,
  130 + },
  131 + },
  132 + data() {
  133 + return {
  134 + dataSource: this.source || [],
  135 + };
  136 + },
  137 + watch: {
  138 + source(val) {
  139 + this.dataSource = val || [];
  140 + },
  141 + },
  142 + computed: {
  143 + _elFormItemSize() {
  144 + return (this.elFormItem || {}).elFormItemSize;
  145 + },
  146 + transferSize() {
  147 + return this.size || this._elFormItemSize || (this.elForm || {}).size || (this.$ELEMENT || {}).size;
  148 + },
  149 + transferDisabled() {
  150 + return this.disabled || (this.elForm || {}).disabled;
  151 + },
  152 + schemaFilter() {
  153 + return this.schema.filter || {};
  154 + },
  155 + schemaLeft() {
  156 + return {
  157 + ...this.schema,
  158 + selection: false,
  159 + action: false,
  160 + filter: this.schema.filter
  161 + ? {
  162 + props: {
  163 + span: 12,
  164 + ...(this.schemaFilter.props || {}),
  165 + },
  166 + ...this.schemaFilter,
  167 + }
  168 + : false,
  169 + pagination: !this.apiSearch ? false : get(this.schema, 'pagination'),
  170 + };
  171 + },
  172 + schemaRight() {
  173 + if (this.schema.selected) {
  174 + return this.schema.selected;
  175 + }
  176 + return {
  177 + props: {
  178 + border: true,
  179 + 'highlight-current-row': true,
  180 + ...(get(this.schema, 'table.props') || {}),
  181 + },
  182 + ...this.schema.table,
  183 + };
  184 + },
  185 + valueKeys() {
  186 + return this.value.map(item => item[this.valueKey]);
  187 + },
  188 + valueTable() {
  189 + return this.valueFormatter(this.dataSource || []);
  190 + },
  191 + },
  192 + methods: {
  193 + _rowDisabled(row, index) {
  194 + if (this.transferDisabled) {
  195 + return true;
  196 + }
  197 + if (this.rowDisabled) {
  198 + return this.rowDisabled({ row, index });
  199 + }
  200 + return false;
  201 + },
  202 + valueFormatter(value) {
  203 + return value.filter(item => !this.valueKeys.includes(item[this.valueKey]));
  204 + },
  205 + onChoose(row) {
  206 + this.$emit('input', [...this.value, row]);
  207 + },
  208 + onRemove(row, index) {
  209 + const newValue = cloneDeep(this.value || []);
  210 + newValue.splice(index, 1);
  211 + this.$emit('input', newValue);
  212 + },
  213 + onInput(value) {
  214 + this.$emit('input', value);
  215 + },
  216 + searchMethod(e) {
  217 + if (this.apiSearch) {
  218 + return this.apiSearch(e).then(result => {
  219 + this.dataSource = result[0] || [];
  220 + return [this.valueTable, result[1]];
  221 + });
  222 + }
  223 + return new Promise(resolve => {
  224 + resolve([]);
  225 + });
  226 + },
  227 + },
  228 +};
  229 +</script>