Commit 61296e41448f7fd3697f2645977d114fe6566dd1

Authored by Aaron.Liu
1 parent 70f019f6
Exists in master and in 1 other branch legacy

重构form组件

examples/views/index.vue
... ... @@ -36,7 +36,7 @@
36 36 </div>
37 37 <div class="cpt-title">下拉选择 eagle-select</div>
38 38 <div class="cpt-content">
39   - <eagle-select v-model="selectValue" :dataSource="[{ label: '选项A', value: 'A' }, { label: '选项B', value: 'B' }]"></eagle-select>
  39 + <!-- <eagle-select v-model="selectValue" :dataSource="[{ label: '选项A', value: 'A' }, { label: '选项B', value: 'B' }]"></eagle-select> -->
40 40 </div>
41 41 <div class="cpt-title">状态指示点 eagle-status-indicator</div>
42 42 <div class="cpt-content">
... ... @@ -62,7 +62,21 @@
62 62 </div>
63 63 <div class="cpt-title">表单生成器 eagle-form</div>
64 64 <div class="cpt-content">
65   - <eagle-cform v-model="formValue" :list="formList"></eagle-cform>
  65 + {{ formValue }}
  66 + <eagle-form ref="form" v-model="formValue" :list="formList" :form-props="{ size: 'large', 'label-width': '80px' }" submit-pure>
  67 + <template #group-not-bad="{ label, list }">
  68 + <div style="background: deepskyblue">哎哟 - {{ label }} - 标题 [{{ list.length }} 项]</div>
  69 + </template>
  70 + <template #group-default="{ label }">
  71 + <div style="background: deeppink">{{ label }} - 默认标题</div>
  72 + </template>
  73 + <template #item-name="{ model, key }">
  74 + <el-select v-model="model[key]">
  75 + <el-option value="A">A</el-option>
  76 + <el-option value="B">B</el-option>
  77 + </el-select>
  78 + </template>
  79 + </eagle-form>
66 80 </div>
67 81 </div>
68 82 </template>
... ... @@ -79,19 +93,48 @@ export default {
79 93 { id: 0, label: '一级菜单', children: [{ id: 1, label: '二级菜单-1' }, { id: 2, label: '二级菜单-2', children: [{ id: 3, label: '三级菜单' }] }] }
80 94 ],
81 95 formValue: {},
  96 + // formList: [
  97 + // { type: 'el-input', key: 'name', label: '名称', group: '基本信息', span: 12 },
  98 + // { type: 'el-input', key: 'gender', label: '性别', group: '基本信息', span: 12 },
  99 + // { type: 'el-input', key: 'address', label: '住址', group: '家庭住址' },
  100 + // { type: 'el-input', key: 'postcode', label: '邮编', group: '家庭住址' },
  101 + // { type: 'el-input', key: 'political', label: '政治面貌', group: { key: 'pol', label: '政治审查' } },
  102 + // ]
82 103 formList: [
83   - { type: 'el-input' }
  104 + { type: 'el-input', key: 'name', label: '名称', group: '那啥' },
  105 + { type: 'el-input', key: 'gender', label: '性别', group: { label: '不错哦', key: 'not-bad' }, tip: '周某人说的', props: { disabled: true } },
  106 + { type: 'eagle-select', key: 'address', label: '住址', default: '123', props: { dataSource: [{ label: '第一个', value: 'No.1' }, { label: '第二个', value: 'No.2' }] } },
  107 + { type: 'el-input', key: 'postcode', label: '邮编', tip: { content: '随便挑', placement: "left" } },
  108 + { type: 'el-input', key: 'political', label: '政治面貌', visible: (model) => model.name === 'B' },
84 109 ]
85 110 }
86 111 },
  112 + mounted() {
  113 + setTimeout(() => {
  114 + this.formValue.name = "B"
  115 + this.formValue.name2 = "B"
  116 + this.formValue.name3 = "B"
  117 + this.formValue.name4 = "B"
  118 + this.formValue.name5 = "B"
  119 + this.formValue.name6 = "B"
  120 + this.formValue.political = "B11111111111111111111111"
  121 + }, 5000)
  122 +
  123 + // setTimeout(() => {
  124 + // this.$refs.form.reset()
  125 + // }, 8000)
  126 + },
87 127 methods: {
  128 + handleText(val) {
  129 + console.log(val)
  130 + },
88 131 handleConfirm() {
89 132 alert('确认')
90 133 },
91 134 handleCancel() {
92 135 alert('取消')
93 136 }
94   - }
  137 + },
95 138 }
96 139 </script>
97 140  
... ...
packages/cform/index.vue
... ... @@ -1,271 +0,0 @@
1   -<style scoped>
2   -.edit-form {
3   - padding-right: 30px;
4   -}
5   -.group-title {
6   - font-weight: bold;
7   - padding: 15px 5px;
8   - border-bottom: 1px solid #d9d9d9;
9   - margin-bottom: 30px;
10   - /* margin-top: 15px;
11   - margin-bottom: 30px; */
12   -}
13   -/* .group-title:first-child {
14   - margin-top: 0px;
15   -} */
16   -
17   -.group-content {
18   - margin: 15px 0px;
19   -}
20   -</style>
21   -
22   -<template>
23   - <el-form class="edit-form" size="small" ref="form" :model="model" :label-width="labelWidth">
24   - <template v-if="groupList.group">
25   - <!-- 这里可以合为一个代码段判断 -->
26   - <template v-for="(data, index) in groupList.data">
27   - <template v-if="data.list.length > 0">
28   - <!-- 这里可以判断是否满足分组条件决定显示title,另外下面content的class也可以判断,div可以换满span的col代替 -->
29   - <!-- 加上选项tips提示判断,类似avue -->
30   - <div class="group-title" :key="index + 'title'">{{ data.title }}</div>
31   - <el-row class="group-content" :key="index + 'list'" :gutter="15">
32   - <template v-for="(item, index) in data.list">
33   - <el-col v-if="componentVisible(item.visible)" v-show="componentShow(item.show)" :key="index + 'data'" :span="!item.span ? 24 : item.span">
34   - <el-form-item :label="item.label" :label-width="item.label ? undefined : item.labelWidth || '0px'" :prop="item.key" :rules="item.rules">
35   - <component @input="(e) => handleComponentInput(e, item)" :is="item.type" @change="(e) => handleComponentChange(e, item)" v-on="handleComponentOn(item)" :style="componentStyle(item.style)" v-model="model[item.key]" v-bind="componentProps(item)"></component>
36   - </el-form-item>
37   - </el-col>
38   - </template>
39   - </el-row>
40   - </template>
41   - </template>
42   - </template>
43   - <template v-else>
44   - <el-row :gutter="15">
45   - <template v-for="(item, index) in list">
46   - <el-col v-if="componentVisible(item.visible)" v-show="componentShow(item.show)" :key="index" :span="!item.span ? 24 : item.span">
47   - <el-form-item :label="item.label" :label-width="item.label ? undefined : item.labelWidth || '0px'" :prop="item.key" :rules="item.rules">
48   - <component @input="(e) => handleComponentInput(e, item)" :is="item.type" @change="(e) => handleComponentChange(e, item)" v-on="handleComponentOn(item)" :style="componentStyle(item.style)" v-model="model[item.key]" v-bind="componentProps(item)"></component>
49   - </el-form-item>
50   - </el-col>
51   - </template>
52   - </el-row>
53   - </template>
54   - <!-- 使用slot,不用slot时可定义对齐位置 -->
55   - <div style="text-align: right">
56   - <el-button type="primary" size="small" :loading="submitLoading" @click="handleSubmit">确定</el-button>
57   - <el-button plain size="small" @click="handleCancel" style="margin-left: 8px">取消</el-button>
58   - </div>
59   - </el-form>
60   -</template>
61   -
62   -<script>
63   -export default { // ------------------------------------ 解决外部的v-model绑定值失效问题
64   - name: 'Cform',
65   - props: {
66   - // 用于实例化本组件绑定v-model的值
67   - value: {
68   - type: Object,
69   - default: () => {
70   - return {};
71   - }
72   - },
73   - // 配置列表 --------------------------------------------可换成对象,传list的同时传其它参数?是否跟formProps重复???
74   - list: {
75   - type: Array,
76   - required: true
77   - },
78   - // 提交加载状态
79   - submitLoading: Boolean, // --------------------------------- isLoading 或者loading,考虑骨架屏?
80   - // 表单模式,一般结合外部模态框判断新增或修改使用 ------------------------------尽量去掉mode,在外部隐藏时销毁组件,避免历史值遗留问题
81   - mode: String,
82   - // 标签宽度 ------------------------------- form的props可以用单独的字段如formProps表示,动态传入,使用v-bind绑定
83   - labelWidth: {
84   - type: String,
85   - default: '90px'
86   - }
87   - },
88   - data() {
89   - return {
90   - // 编辑器表单模型
91   - model: {}
92   - };
93   - },
94   - watch: {
95   - value: function(val) {
96   - if (val) {
97   - Object.keys(this.model).forEach(key => {
98   - this.model[key] = val[key];
99   - });
100   - }
101   - },
102   - // 配置列表又改动时初始化表单模型
103   - list: function(value) {
104   - this.initModel(value);
105   - const obj = {};
106   - this.list.forEach(data => {
107   - if (data.group) {
108   - obj[data.group] = true;
109   - }
110   - });
111   - }
112   - },
113   - computed: {
114   - groupList() {
115   - const obj = {};
116   - this.list.forEach(data => {
117   - if (data.group) {
118   - if (!obj[data.group]) {
119   - obj[data.group] = [];
120   - }
121   - obj[data.group].push(data);
122   - }
123   - });
124   - return {
125   - group: Object.keys(obj).length > 0,
126   - data: Object.keys(obj).map(key => {
127   - return { title: key, list: obj[key] };
128   - })
129   - };
130   - }
131   - },
132   - created() {
133   - // 初始化表单模型
134   - this.initModel(this.list);
135   - },
136   - methods: {
137   - handleComponentInput(e, item) {
138   - if (item.oninput) {
139   - item.oninput(e, this.model, this.mode);
140   - }
141   - this.emitModel();
142   - },
143   - handleComponentChange(e, item) {
144   - if (item.onchange) {
145   - item.onchange(e, this.model, this.mode);
146   - }
147   - },
148   - handleComponentOn(item) {
149   - if (item.on) {
150   - if (typeof item.on === 'function') {
151   - return item.on(this.model, this.mode);
152   - } else {
153   - return item.on
154   - }
155   - } else {
156   - return undefined
157   - }
158   - },
159   - // 向上推送表单值到实例化的组件的v-model中
160   - emitModel() {
161   - const result = {};
162   - Object.keys(this.model).forEach(key => {
163   - if (this.componentIsVisible(key)) {
164   - let value = this.model[key];
165   - if (this.model[key] === null) {
166   - value = undefined;
167   - }
168   - result[key] = value;
169   - }
170   - });
171   - this.$emit("input", result);
172   - },
173   - // 初始化表单模型
174   - initModel(list) {
175   - const model = {};
176   - list.forEach(item => {
177   - model[item.key] = item.default;
178   - });
179   - this.model = model;
180   - this.emitModel();
181   - },
182   - // 设置表单模型值
183   - setValue(value) {
184   - this.reset();
185   - Object.keys(value).forEach(key => {
186   - this.model[key] = value[key];
187   - });
188   - this.emitModel();
189   - },
190   - // 判断名称为key的组件v-if是否为true
191   - componentIsVisible(key) {
192   - let visible = true;
193   - this.list.forEach((item) => {
194   - if (item.key === key) {
195   - if (typeof item.visible === 'function') {
196   - visible = item.visible(this.model, this.mode);
197   - } else {
198   - visible = item.visible === undefined ? true : item.visible;
199   - }
200   - }
201   - });
202   - return visible;
203   - },
204   - // 判断组件v-if状态
205   - componentVisible(visible = true) {
206   - let componentVisible = visible;
207   - if (typeof visible === 'function') {
208   - componentVisible = visible(this.model, this.mode);
209   - }
210   - return componentVisible;
211   - },
212   - // 判断组件v-show状态
213   - componentShow(show = true) {
214   - let componentShow = show;
215   - if (typeof show === 'function') {
216   - componentShow = show(this.model, this.mode);
217   - }
218   - return componentShow;
219   - },
220   - // 判断组件参数
221   - componentProps(item) {
222   - const { props = {}, propsEditor = {} } = item;
223   - let connectedProps = { ...props, ...propsEditor };
224   - Object.keys(connectedProps).forEach(key => {
225   - if (typeof connectedProps[key] === 'function') {
226   - connectedProps[key] = connectedProps[key](this.model, this.mode);
227   - }
228   - });
229   - return connectedProps;
230   - },
231   - // 判断组件样式
232   - componentStyle(style = {}) {
233   - return {
234   - width: "100%",
235   - ...style
236   - };
237   - },
238   - // 点击确定提交表单的操作
239   - handleSubmit(name) {
240   - this.$refs.form.validate(valid => {
241   - if (valid) {
242   - const result = {};
243   - Object.keys(this.model).forEach(key => {
244   - if (this.componentIsVisible(key)) {
245   - let value = this.model[key];
246   - if (this.model[key] === null) {
247   - value = undefined;
248   - }
249   - result[key] = value;
250   - }
251   - });
252   - this.$emit("submit", result);
253   - }
254   - });
255   - },
256   - // 重置表单
257   - reset() {
258   - Object.keys(this.model).forEach(key => {
259   - this.model[key] = (this.list.find(item => item.key === key) || {}).default;
260   - });
261   - this.$nextTick(() => {
262   - this.$refs.form.clearValidate();
263   - });
264   - },
265   - // 点击取消的操作
266   - handleCancel() {
267   - this.$emit("cancel");
268   - }
269   - }
270   -};
271   -</script>
272 0 \ No newline at end of file
packages/form/index.vue 0 → 100644
... ... @@ -0,0 +1,271 @@
  1 +<style scoped>
  2 +.eagle-form {
  3 + padding: 0px;
  4 +}
  5 +.eagle-form__group-title {
  6 + font-weight: bold;
  7 + padding: 15px 5px;
  8 + border-bottom: 1px solid #d9d9d9;
  9 + margin-bottom: 30px;
  10 +}
  11 +.eagle-form__group-content {
  12 + margin: 15px 0px;
  13 +}
  14 +</style>
  15 +
  16 +<template>
  17 + <el-form class="eagle-form" ref="form" :model="model" v-bind="formProps">
  18 + <el-row :gutter="15">
  19 + <!-- 这里可以合为一个代码段判断 -->
  20 + <template v-for="(data, index) in listOption.dataList">
  21 + <!-- 这里可以判断是否满足分组条件决定显示title,另外下面content的class也可以判断,div可以换满span的col代替 -->
  22 + <!-- 加上选项tips提示判断,类似avue -->
  23 + <template v-if="listOption.isGroup">
  24 + <slot v-if="$scopedSlots[data.key] || $slots[data.key]" :name="data.key" v-bind="{...data}"></slot>
  25 + <el-col v-else class="eagle-form__group-title" :key="data.key" :span="24">{{ data.label }}</el-col>
  26 + </template>
  27 + <el-row :class="{ 'eagle-form__group-content': listOption.isGroup }" :key="'group-content-' + index" :gutter="15">
  28 + <template v-for="(item, index) in data.list">
  29 + <el-col v-if="bindItemVisible(item.visible)" v-show="bindItemShow(item.show)" :key="index + 'data'" :span="!item.span ? 24 : item.span">
  30 + <el-form-item :label="item.label" :label-width="item.label ? undefined : item.labelWidth || '0px'" :prop="item.key" :rules="item.rules">
  31 + <el-tooltip :disabled="!item.tip" v-bind="bindItemTip(item.tip)">
  32 + <slot v-if="$scopedSlots[`item-${item.key}`] || $slots[`item-${item.key}`]" :name="`item-${item.key}`" :model="model" v-bind="item"></slot>
  33 + <component v-else :is="item.type" v-model="model[item.key]" v-bind="bindItemProps(item)" v-on="bindItemEvent(item)" :style="bindItemStyle(item.style)"></component>
  34 + </el-tooltip>
  35 + </el-form-item>
  36 + </el-col>
  37 + </template>
  38 + </el-row>
  39 + </template>
  40 + </el-row>
  41 + <!-- 使用slot,不用slot时可定义对齐位置 -->
  42 + <div style="text-align: right">
  43 + <el-button type="primary" size="small" :loading="submitting" @click="handleSubmit">确定</el-button>
  44 + <el-button plain size="small" @click="handleCancel" style="margin-left: 8px">取消</el-button>
  45 + </div>
  46 + </el-form>
  47 +</template>
  48 +
  49 +<script>
  50 +export default {
  51 + name: 'Form',
  52 + props: {
  53 + // 用于实例化本组件绑定v-model的值
  54 + value: {
  55 + type: Object,
  56 + default: () => {
  57 + return {};
  58 + }
  59 + },
  60 + // 配置列表
  61 + list: {
  62 + type: Array,
  63 + required: true
  64 + },
  65 + // 提交加载状态
  66 + submitting: Boolean,
  67 + // 表单参数
  68 + formProps: {
  69 + type: Object,
  70 + default() {
  71 + return {
  72 + size: 'small',
  73 + 'label-width': '70px'
  74 + }
  75 + }
  76 + },
  77 + // 纯净提交
  78 + submitPure: {
  79 + type: Boolean,
  80 + default: false
  81 + }
  82 + },
  83 + data() {
  84 + return {
  85 + // 编辑器表单模型
  86 + model: {}
  87 + };
  88 + },
  89 + watch: {
  90 + // 组件外部v-model值更新后同步刷新model
  91 + value(val) {
  92 + Object.keys(this.model).forEach(key => {
  93 + this.model[key] = val ? val[key] : undefined;
  94 + });
  95 + },
  96 + // 配置列表又改动时初始化表单模型
  97 + list(value) {
  98 + this.initModel(value);
  99 + },
  100 + model: {
  101 + handler(val) {
  102 + this.$emit("input", val);
  103 + },
  104 + deep: true
  105 + }
  106 + },
  107 + computed: {
  108 + // 配置列表键值对形式
  109 + listKeySet() {
  110 + let result = {};
  111 + this.list.forEach(item => {
  112 + result[item.key] = item;
  113 + });
  114 + return result;
  115 + },
  116 + // 配置列表解析为渲染配置项
  117 + listOption() {
  118 + let groupSet = {};
  119 + this.list.forEach(data => {
  120 + if (data.group) {
  121 + if (typeof data.group === 'object') {
  122 + if (!groupSet[`group-${data.group.key}`]) {
  123 + groupSet[`group-${data.group.key}`] = {
  124 + label: data.group.label,
  125 + list: []
  126 + };
  127 + }
  128 + groupSet[`group-${data.group.key}`].list.push(data);
  129 + } else if (typeof data.group === 'string') {
  130 + if (!groupSet[data.group]) {
  131 + groupSet[data.group] = {
  132 + label: data.group,
  133 + list: []
  134 + };
  135 + }
  136 + groupSet[data.group].list.push(data);
  137 + }
  138 + } else {
  139 + if (!groupSet['group-default']) {
  140 + groupSet['group-default'] = {
  141 + label: '基本信息',
  142 + list: []
  143 + };
  144 + }
  145 + groupSet['group-default'].list.push(data);
  146 + }
  147 + });
  148 + const isGroup = Object.keys(groupSet).length > 1;
  149 + const dataList = Object.keys(groupSet).map(key => {
  150 + return { key, ...groupSet[key] };
  151 + })
  152 + return { isGroup, dataList };
  153 + }
  154 + },
  155 + created() {
  156 + // 初始化表单模型
  157 + this.initModel(this.list);
  158 + },
  159 + methods: {
  160 + // 绑定提示组件参数
  161 + bindItemTip(tip) {
  162 + if (typeof tip === 'string') {
  163 + return { content: tip, effect: 'light' };
  164 + } else if (typeof tip === 'object') {
  165 + return tip;
  166 + } else {
  167 + return {};
  168 + }
  169 + },
  170 + // 绑定组件事件
  171 + bindItemEvent(item) {
  172 + if (item.on) {
  173 + if (typeof item.on === 'function') {
  174 + return item.on(this.model);
  175 + } else {
  176 + return item.on
  177 + }
  178 + } else {
  179 + return undefined
  180 + }
  181 + },
  182 + // 初始化表单模型
  183 + initModel(list) {
  184 + list.forEach(item => {
  185 + this.$set(this.model, item.key, item.default || undefined)
  186 + });
  187 + },
  188 + // 判断列表项是否存在
  189 + isItemVisible(key) {
  190 + let visible = true;
  191 + const item = this.listKeySet[key] || {};
  192 + if (typeof item.visible === 'function') {
  193 + visible = item.visible({ ...this.model }); // 返回model的复制结果,判断类属性禁止改变model,防止循环导致内存溢出
  194 + } else {
  195 + visible = item.visible === undefined ? true : item.visible; // 没有定义visible时返回true,否则返回visible定义的值。【注意:不可写成“ !item.visible ”】
  196 + }
  197 + return visible;
  198 + },
  199 + // 绑定组件v-if状态
  200 + bindItemVisible(visible = true) {
  201 + let result = visible;
  202 + if (typeof visible === 'function') {
  203 + result = visible(this.model);
  204 + }
  205 + return result;
  206 + },
  207 + // 绑定组件v-show状态
  208 + bindItemShow(show = true) {
  209 + let result = show;
  210 + if (typeof show === 'function') {
  211 + result = show(this.model);
  212 + }
  213 + return result;
  214 + },
  215 + // 绑定组件参数
  216 + bindItemProps(item) {
  217 + const { props = {} } = item;
  218 + let result = { ...props };
  219 + Object.keys(result).forEach(key => {
  220 + if (typeof result[key] === 'function') {
  221 + result[key] = result[key](this.model);
  222 + }
  223 + });
  224 + return result;
  225 + },
  226 + // 绑定组件样式
  227 + bindItemStyle(style = {}) {
  228 + return {
  229 + width: "100%",
  230 + ...style
  231 + };
  232 + },
  233 + // 点击确定提交表单的操作
  234 + handleSubmit(name) {
  235 + this.$refs.form.validate(valid => {
  236 + if (valid) {
  237 + const result = this.submitPure ? this.getPureModel() : JSON.parse(JSON.stringify(this.model));
  238 + this.$emit("submit", result);
  239 + }
  240 + });
  241 + },
  242 + // 重置表单
  243 + reset() {
  244 + Object.keys(this.model).forEach(key => {
  245 + this.model[key] = this.listKeySet[key] ? this.listKeySet[key].default : undefined;
  246 + });
  247 + this.$nextTick(() => {
  248 + this.$refs.form.clearValidate();
  249 + });
  250 + },
  251 + // 获取以初始list为准的纯净model值
  252 + getPureModel() {
  253 + const result = {};
  254 + Object.keys(this.listKeySet).forEach(key => {
  255 + if (this.isItemVisible(key)) {
  256 + let value = this.model[key];
  257 + if (this.model[key] === null) {
  258 + value = undefined;
  259 + }
  260 + result[key] = value;
  261 + }
  262 + });
  263 + return result;
  264 + },
  265 + // 点击取消的操作
  266 + handleCancel() {
  267 + this.$emit("cancel");
  268 + }
  269 + }
  270 +};
  271 +</script>
0 272 \ No newline at end of file
... ...
packages/index.js
... ... @@ -3,7 +3,7 @@ import Confirm from &#39;./confirm&#39;
3 3 import DistPicker from './dist-picker'
4 4 import Editor from './editor'
5 5 import FileUpload from './file-upload'
6   -import Cform from './cform'
  6 +import Form from './form'
7 7 import ImageUpload from './Image-upload'
8 8 import ImageUploadMultiple from './Image-upload/multiple'
9 9 import ImageView from './image-view'
... ... @@ -14,12 +14,12 @@ import SwitchButton from &#39;./switch-button&#39;
14 14 import TreeSelect from './tree-select'
15 15  
16 16 const components = {
17   - Cform,
18 17 Code,
19 18 Confirm,
20 19 DistPicker,
21 20 Editor,
22 21 FileUpload,
  22 + Form,
23 23 ImageUpload,
24 24 ImageUploadMultiple,
25 25 ImageView,
... ... @@ -37,6 +37,7 @@ const install = function (Vue, opts = {}) {
37 37 const prefix = opts.name || 'eagle'
38 38 // 配置组件名称
39 39 const name = prefix + component.name
  40 + component.name = name
40 41 // 给每个子组件配置install方法
41 42 component.install = function (Vue) {
42 43 Vue.component(name, component)
... ...