Commit 1c0a50268dbe76754e88f9a6d95ff3c7d6bb59ec
1 parent
bfe60bf3
Exists in
master
and in
1 other branch
新增搜索表单
Showing
6 changed files
with
408 additions
and
0 deletions
Show diff stats
examples/router/routes.js
| ... | ... | @@ -25,12 +25,29 @@ const _components = [ |
| 25 | 25 | component: () => import('@/views/docs/form.md'), |
| 26 | 26 | }, |
| 27 | 27 | { |
| 28 | + path: 'search', | |
| 29 | + name: 'search', | |
| 30 | + meta: { title: 'Search 搜索' }, | |
| 31 | + component: () => import('@/views/docs/search.md'), | |
| 32 | + }, | |
| 33 | + { | |
| 28 | 34 | path: 'table', |
| 29 | 35 | name: 'table', |
| 30 | 36 | meta: { title: 'Table 表格' }, |
| 31 | 37 | component: () => import('@/views/docs/table.md'), |
| 32 | 38 | }, |
| 33 | 39 | ] |
| 40 | + }, | |
| 41 | + { | |
| 42 | + group: '业务', | |
| 43 | + children: [ | |
| 44 | + { | |
| 45 | + path: 'scheme', | |
| 46 | + name: 'scheme', | |
| 47 | + meta: { title: 'Scheme 方案' }, | |
| 48 | + component: () => import('@/views/docs/scheme.md'), | |
| 49 | + }, | |
| 50 | + ] | |
| 34 | 51 | } |
| 35 | 52 | ] |
| 36 | 53 | ... | ... |
| ... | ... | @@ -0,0 +1,38 @@ |
| 1 | +# Scheme 方案 | |
| 2 | + | |
| 3 | +Scheme是一个数据驱动的解决方案,通过既定的业务配置参数,生成可模块化编辑的`CURD`业务视图 | |
| 4 | + | |
| 5 | +## 基础用法 | |
| 6 | + | |
| 7 | +配置项list中通过type可以配置任意组件,不受框架限制 | |
| 8 | + | |
| 9 | +:::snippet 使用`list`属性设置数据源,列表项中的`type`指定组件类型,每一项都已设置为**el-form-item**的子组件,通过`rules`配置校验规则 | |
| 10 | + | |
| 11 | +```html | |
| 12 | +<template> | |
| 13 | + <eagle-scheme :list="schemeList"></eagle-scheme> | |
| 14 | +</template> | |
| 15 | + | |
| 16 | +<script> | |
| 17 | +export default { | |
| 18 | + data() { | |
| 19 | + return { | |
| 20 | + schemeList: [ | |
| 21 | + { type: 'el-input', key: 'name', label: '名称', rules: [{ required: true, message: '请输入名称' }] }, | |
| 22 | + { type: 'eagle-select', key: 'address', label: '住址', props: { dataSource: [{ label: '大街上', value: 'S' }, { label: '小区里', value: 'H' }] } }, | |
| 23 | + { type: 'el-input', key: 'postcode', label: '邮编', span: 12, tip: { content: '邮政编码', placement: "left" } }, | |
| 24 | + { type: 'el-input', key: 'number', label: '楼栋号', span: 12, visible: (model) => model.address === 'H' }, | |
| 25 | + ] | |
| 26 | + } | |
| 27 | + }, | |
| 28 | +} | |
| 29 | +</script> | |
| 30 | +``` | |
| 31 | + | |
| 32 | +::: | |
| 33 | + | |
| 34 | +## Attribute 属性 | |
| 35 | + | |
| 36 | +参数|说明|类型|可选值|默认值 | |
| 37 | +-|-|-|-|- | |
| 38 | +list | 表单项配置列表 | Array | - | [] | |
| 0 | 39 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,130 @@ |
| 1 | +# Search 搜索 | |
| 2 | + | |
| 3 | +Search 搜索组件是一个使用`list`来配置生成的搜索表单 | |
| 4 | + | |
| 5 | +## 基础用法 | |
| 6 | + | |
| 7 | +配置项list中通过type可以配置任意组件,不受框架限制 | |
| 8 | + | |
| 9 | +:::snippet 使用`list`属性设置数据源,列表项中的`type`指定组件类型,支持通过`rules`配置校验规则 | |
| 10 | + | |
| 11 | +```html | |
| 12 | +<template> | |
| 13 | + <eagle-search :list="searchList" @search="handleSearch" @reset="handleReset" :span="8" :searching="searching"></eagle-search> | |
| 14 | +</template> | |
| 15 | + | |
| 16 | +<script> | |
| 17 | +export default { | |
| 18 | + data() { | |
| 19 | + return { | |
| 20 | + searching: false, | |
| 21 | + searchList: [ | |
| 22 | + { type: 'el-input', key: 'name', label: '名称', rules: [{ required: true, message: '请输入名称' }] }, | |
| 23 | + { type: 'eagle-select', key: 'type', label: '类型', props: { dataSource: [{ label: '呆萌', value: '1' }, { label: '二货', value: '2' }] } }, | |
| 24 | + { type: 'el-input', key: 'postcode', label: '邮编', tip: { content: '邮政编码', placement: "left" } }, | |
| 25 | + { type: 'el-input', key: 'number', label: '楼栋号' }, | |
| 26 | + { type: 'el-input', key: 'not', label: '我不是', visible: (model) => model.type === '2' }, | |
| 27 | + { type: 'el-input', key: 'no', label: '我没有', visible: (model) => model.type === '2' }, | |
| 28 | + { type: 'el-input', key: 'never', label: '别瞎说啊', visible: (model) => model.type === '2' }, | |
| 29 | + ] | |
| 30 | + } | |
| 31 | + }, | |
| 32 | + mounted() { | |
| 33 | + this.searching = true; | |
| 34 | + setTimeout(() => { | |
| 35 | + this.searching = false; | |
| 36 | + }, 3000); | |
| 37 | + }, | |
| 38 | + methods: { | |
| 39 | + handleSearch(value) { | |
| 40 | + console.log(value); | |
| 41 | + }, | |
| 42 | + handleReset() { | |
| 43 | + console.log('reset'); | |
| 44 | + } | |
| 45 | + } | |
| 46 | +} | |
| 47 | +</script> | |
| 48 | +``` | |
| 49 | + | |
| 50 | +::: | |
| 51 | + | |
| 52 | +## 自定义组件 | |
| 53 | + | |
| 54 | +在使用`list`的同时,也支持通过`slot`传入组件,以满足不同的业务需求 | |
| 55 | + | |
| 56 | +:::snippet 使用`list`属性设置数据源,列表项中的`type`指定组件类型,支持通过`rules`配置校验规则 | |
| 57 | + | |
| 58 | +```html | |
| 59 | +<template> | |
| 60 | + <eagle-search :list="searchList"> | |
| 61 | + <template #type="{ model }"> | |
| 62 | + <eagle-select v-model="model.type" :dataSource="dataSource"></eagle-select> | |
| 63 | + </template> | |
| 64 | + <template #button-group="{ model, collapse, doSearch, doReset, doCollapse }"> | |
| 65 | + <el-button size="mini" type="primary" round @click="handleQuery(model, doSearch)">搜索</el-button> | |
| 66 | + <el-button size="mini" type="success" round @click="doReset">恢复</el-button> | |
| 67 | + <el-button size="mini" type="info" round @click="doCollapse">{{ collapse ? '打开' : '关闭' }}</el-button> | |
| 68 | + </template> | |
| 69 | + </eagle-search> | |
| 70 | +</template> | |
| 71 | + | |
| 72 | +<script> | |
| 73 | +export default { | |
| 74 | + data() { | |
| 75 | + return { | |
| 76 | + searchList: [ | |
| 77 | + { type: 'el-input', key: 'name', label: '名称' }, | |
| 78 | + { key: 'type', label: '类型' }, | |
| 79 | + { type: 'el-input', key: 'yes', label: '是的' }, | |
| 80 | + { type: 'el-input', key: 'yeah', label: '没错' }, | |
| 81 | + { type: 'el-input', key: 'absolutely', label: '就这样' }, | |
| 82 | + ], | |
| 83 | + dataSource: [ | |
| 84 | + { label: '选项A', value: 'A' }, | |
| 85 | + { label: '选项B', value: 'B' }, | |
| 86 | + ] | |
| 87 | + } | |
| 88 | + }, | |
| 89 | + methods: { | |
| 90 | + handleQuery(model, action) { | |
| 91 | + if (action) { | |
| 92 | + action(); | |
| 93 | + console.log(model); | |
| 94 | + } | |
| 95 | + }, | |
| 96 | + } | |
| 97 | +} | |
| 98 | +</script> | |
| 99 | +``` | |
| 100 | + | |
| 101 | +::: | |
| 102 | + | |
| 103 | +## Attribute 属性 | |
| 104 | + | |
| 105 | +参数|说明|类型|可选值|默认值 | |
| 106 | +-|-|-|-|- | |
| 107 | +value / v-model | 绑定值 | Object | - | - | |
| 108 | +list | 表单项配置列表 | Array | - | [] | |
| 109 | + | |
| 110 | +## Events 事件 | |
| 111 | + | |
| 112 | +事件名称|说明|回调参数 | |
| 113 | +-|-|- | |
| 114 | +change | 表单model发生变化时触发 | model对象 | |
| 115 | +search | 点击查询按钮时触发 | model对象 | |
| 116 | +reset | 点击重置按钮时触发 | - | |
| 117 | + | |
| 118 | +## List 表单项配置列表 | |
| 119 | + | |
| 120 | +参数|说明|类型|可选值|默认值 | |
| 121 | +-|-|-|-|- | |
| 122 | +type | 组件类型 | String | - | el-input | |
| 123 | +key | 参数名 | String | - | - | |
| 124 | +label | 参数标签 | String | - | - | |
| 125 | +props | 组件参数 | Object,Function(model: object)) | - | {} | |
| 126 | +style | 组件样式 | Object | - | { width: "100%" } | |
| 127 | +on | 组件事件 | Object,Function(model: object) | - | {} | |
| 128 | +visible | 组件v-if状态 | Boolean,Function(model: object) | - | true | |
| 129 | +rules | 组件校验规则 | Object,Array | - | - | |
| 130 | +tip | 组件提示框 | Object,String | - | {} | |
| 0 | 131 | \ No newline at end of file | ... | ... |
packages/index.js
| ... | ... | @@ -8,6 +8,8 @@ import ImageUpload from './Image-upload' |
| 8 | 8 | import ImageUploadMultiple from './Image-upload/multiple' |
| 9 | 9 | import ImageView from './image-view' |
| 10 | 10 | import RadioGroup from './radio-group' |
| 11 | +import Scheme from './scheme' | |
| 12 | +import Search from './search' | |
| 11 | 13 | import Select from './select' |
| 12 | 14 | import StatusIndicator from './status-indicator' |
| 13 | 15 | import SwitchButton from './switch-button' |
| ... | ... | @@ -25,6 +27,8 @@ const components = { |
| 25 | 27 | ImageUploadMultiple, |
| 26 | 28 | ImageView, |
| 27 | 29 | RadioGroup, |
| 30 | + Scheme, | |
| 31 | + Search, | |
| 28 | 32 | Select, |
| 29 | 33 | StatusIndicator, |
| 30 | 34 | SwitchButton, | ... | ... |
| ... | ... | @@ -0,0 +1,30 @@ |
| 1 | +<style> | |
| 2 | +.eagle-scheme { | |
| 3 | + padding: 0px; | |
| 4 | +} | |
| 5 | +</style> | |
| 6 | + | |
| 7 | +<template> | |
| 8 | + <div class="eagle-scheme"> | |
| 9 | + Scheme | |
| 10 | + </div> | |
| 11 | +</template> | |
| 12 | + | |
| 13 | +<script> | |
| 14 | +export default { | |
| 15 | + name: 'Scheme', | |
| 16 | + props: { | |
| 17 | + // 配置列表 | |
| 18 | + list: { | |
| 19 | + type: Array, | |
| 20 | + required: true | |
| 21 | + }, | |
| 22 | + }, | |
| 23 | + data() { | |
| 24 | + return { | |
| 25 | + // 编辑器表单模型 | |
| 26 | + model: {} | |
| 27 | + }; | |
| 28 | + }, | |
| 29 | +}; | |
| 30 | +</script> | |
| 0 | 31 | \ No newline at end of file | ... | ... |
| ... | ... | @@ -0,0 +1,189 @@ |
| 1 | +<style> | |
| 2 | +.eagle-search { | |
| 3 | + padding: 0px; | |
| 4 | +} | |
| 5 | +.eagle-search__btn-col { | |
| 6 | + text-align: right; | |
| 7 | +} | |
| 8 | +</style> | |
| 9 | + | |
| 10 | +<template> | |
| 11 | + <el-form class="eagle-search" ref="search" :model="model" v-bind="formProps"> | |
| 12 | + <el-row :gutter="15"> | |
| 13 | + <template v-for="(item, index) in list"> | |
| 14 | + <el-col v-if="bindItemVisible(item.visible)" v-show="!(collapse && index > visibleColNum - 2)" :key="index + 'data'" :span="!item.span ? span : item.span"> | |
| 15 | + <el-form-item :label="item.label" :label-width="item.label ? undefined : item.labelWidth || '0px'" :prop="item.key" :rules="item.rules"> | |
| 16 | + <el-tooltip :disabled="!item.tip" v-bind="bindItemTip(item.tip)"> | |
| 17 | + <slot v-if="$scopedSlots[item.key] || $slots[item.key]" :name="item.key" :model="model" v-bind="item"></slot> | |
| 18 | + <component v-else :is="item.type || 'el-input'" v-model="model[item.key]" v-bind="bindItemProps(item)" v-on="bindItemEvent(item)" :style="bindItemStyle(item.style)"></component> | |
| 19 | + </el-tooltip> | |
| 20 | + </el-form-item> | |
| 21 | + </el-col> | |
| 22 | + </template> | |
| 23 | + <el-col :span="list.length > visibleColNum ? collapse ? span : 24 : span" class="eagle-search__btn-col"> | |
| 24 | + <slot v-if="$scopedSlots['button-group'] || $slots['button-group']" name="button-group" | |
| 25 | + :model="model" :collapse="collapse" :doSearch="handleSearch" :doReset="handleReset" :doCollapse="handleCollapse" | |
| 26 | + ></slot> | |
| 27 | + <el-button-group v-else> | |
| 28 | + <el-button size="small" type="primary" :loading="searching" @click="handleSearch" icon="el-icon-search">查询</el-button> | |
| 29 | + <el-button size="small" @click="handleReset">重置</el-button> | |
| 30 | + <el-button size="small" v-if="list.length > visibleColNum" :icon="collapse ? 'ios-arrow-down' : 'ios-arrow-up'" @click="handleCollapse"> | |
| 31 | + {{ collapse ? '展开' : '收起' }} | |
| 32 | + </el-button> | |
| 33 | + </el-button-group> | |
| 34 | + </el-col> | |
| 35 | + </el-row> | |
| 36 | + </el-form> | |
| 37 | +</template> | |
| 38 | + | |
| 39 | +<script> | |
| 40 | +export default { | |
| 41 | + name: 'Search', | |
| 42 | + props: { | |
| 43 | + // 用于实例化本组件绑定v-model的值 | |
| 44 | + value: { | |
| 45 | + type: Object, | |
| 46 | + default: () => { | |
| 47 | + return {}; | |
| 48 | + } | |
| 49 | + }, | |
| 50 | + // 配置列表 | |
| 51 | + list: { | |
| 52 | + type: Array, | |
| 53 | + required: true | |
| 54 | + }, | |
| 55 | + // 提交加载状态 | |
| 56 | + searching: Boolean, | |
| 57 | + // 表单参数 | |
| 58 | + formProps: { | |
| 59 | + type: Object, | |
| 60 | + default() { | |
| 61 | + return { | |
| 62 | + size: 'small', | |
| 63 | + 'label-width': '70px' | |
| 64 | + } | |
| 65 | + } | |
| 66 | + }, | |
| 67 | + // 表单项占位 | |
| 68 | + span: { | |
| 69 | + type: Number, | |
| 70 | + default: 6 | |
| 71 | + } | |
| 72 | + }, | |
| 73 | + data() { | |
| 74 | + return { | |
| 75 | + // 编辑器表单模型 | |
| 76 | + model: {}, | |
| 77 | + // 表单折叠状态 | |
| 78 | + collapse: false, | |
| 79 | + }; | |
| 80 | + }, | |
| 81 | + computed: { | |
| 82 | + visibleColNum() { | |
| 83 | + return 24 / this.span; | |
| 84 | + } | |
| 85 | + }, | |
| 86 | + watch: { | |
| 87 | + // 组件外部v-model值更新后同步刷新model | |
| 88 | + value(val) { | |
| 89 | + Object.keys(this.model).forEach(key => { | |
| 90 | + this.model[key] = val ? val[key] : undefined; | |
| 91 | + }); | |
| 92 | + }, | |
| 93 | + // 配置列表有改动时初始化表单模型 | |
| 94 | + list(value) { | |
| 95 | + this.initModel(value); | |
| 96 | + }, | |
| 97 | + model: { | |
| 98 | + handler(val) { | |
| 99 | + this.$emit("input", val); | |
| 100 | + this.$emit("change", val); | |
| 101 | + }, | |
| 102 | + deep: true | |
| 103 | + } | |
| 104 | + }, | |
| 105 | + created() { | |
| 106 | + // 初始化表单模型 | |
| 107 | + this.initModel(this.list); | |
| 108 | + }, | |
| 109 | + methods: { | |
| 110 | + // 绑定提示组件参数 | |
| 111 | + bindItemTip(tip) { | |
| 112 | + if (typeof tip === 'string') { | |
| 113 | + return { content: tip, effect: 'light' }; | |
| 114 | + } else if (typeof tip === 'object') { | |
| 115 | + return tip; | |
| 116 | + } else { | |
| 117 | + return {}; | |
| 118 | + } | |
| 119 | + }, | |
| 120 | + // 绑定组件事件 | |
| 121 | + bindItemEvent(item) { | |
| 122 | + if (item.on) { | |
| 123 | + if (typeof item.on === 'function') { | |
| 124 | + return item.on(this.model); | |
| 125 | + } else { | |
| 126 | + return item.on | |
| 127 | + } | |
| 128 | + } else { | |
| 129 | + return undefined | |
| 130 | + } | |
| 131 | + }, | |
| 132 | + // 初始化表单模型 | |
| 133 | + initModel(list) { | |
| 134 | + list.forEach(item => { | |
| 135 | + this.$set(this.model, item.key, item.default || undefined) | |
| 136 | + }); | |
| 137 | + }, | |
| 138 | + // 绑定组件v-if状态 | |
| 139 | + bindItemVisible(visible = true) { | |
| 140 | + let result = visible; | |
| 141 | + if (typeof visible === 'function') { | |
| 142 | + result = visible(this.model); | |
| 143 | + } | |
| 144 | + return result; | |
| 145 | + }, | |
| 146 | + // 绑定组件参数 | |
| 147 | + bindItemProps(item) { | |
| 148 | + const { props = {} } = item; | |
| 149 | + let result = { ...props }; | |
| 150 | + Object.keys(result).forEach(key => { | |
| 151 | + if (typeof result[key] === 'function') { | |
| 152 | + result[key] = result[key](this.model); | |
| 153 | + } | |
| 154 | + }); | |
| 155 | + return result; | |
| 156 | + }, | |
| 157 | + // 绑定组件样式 | |
| 158 | + bindItemStyle(style = {}) { | |
| 159 | + return { | |
| 160 | + width: "100%", | |
| 161 | + ...style | |
| 162 | + }; | |
| 163 | + }, | |
| 164 | + // 点击确定提交表单的操作 | |
| 165 | + handleSearch() { | |
| 166 | + this.$refs.search.validate(valid => { | |
| 167 | + if (valid) { | |
| 168 | + const result = JSON.parse(JSON.stringify(this.model)); | |
| 169 | + this.$emit("search", result); | |
| 170 | + } | |
| 171 | + }); | |
| 172 | + }, | |
| 173 | + // 重置表单 | |
| 174 | + handleReset() { | |
| 175 | + Object.keys(this.model).forEach(key => { | |
| 176 | + this.model[key] = this.list[key] ? this.list[key].default : undefined; | |
| 177 | + }); | |
| 178 | + this.$nextTick(() => { | |
| 179 | + this.$refs.search.clearValidate(); | |
| 180 | + }); | |
| 181 | + this.$emit('reset'); | |
| 182 | + }, | |
| 183 | + // 折叠表单 | |
| 184 | + handleCollapse() { | |
| 185 | + this.collapse = !this.collapse; | |
| 186 | + } | |
| 187 | + } | |
| 188 | +}; | |
| 189 | +</script> | |
| 0 | 190 | \ No newline at end of file | ... | ... |