index.vue 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. <template>
  2. <el-form
  3. ref="fForm"
  4. :key="update"
  5. :rules="rules"
  6. class="flex-wrap f-form"
  7. :class="text ? 'text' : ''"
  8. :model="form"
  9. :label-position="labelPosition">
  10. <el-form-item
  11. v-for="(item, index) in configList"
  12. :key="'form-item' + index"
  13. :ref="'formItem'+item.prop"
  14. :label-width="setWidth(item, index)"
  15. :label="item.label"
  16. :prop="item.prop"
  17. :style="{
  18. width: !autoWidth
  19. ? (item.span && colVal * item.span + '%') || colVal + '%'
  20. : 'auto'
  21. }"
  22. class="pb-10 mb-8"
  23. :class="getClass(item,index)"
  24. :rules="item.rules"
  25. :required="item.disabled ? false : item.required"
  26. >
  27. <div :style="{ width: item.width || itemWidth }">
  28. <components
  29. v-if="!item.hasSlot && item.itemType !== 'text' && item.itemType"
  30. :is="'f-' + item.itemType"
  31. v-model="form[item.prop]"
  32. v-bind="{ disabled:formItemDisabled(item),...item.attrs }"
  33. @change="handleEmit($event,item,'change')"
  34. @blur="handleEmit($event,item,'blur')"
  35. @focus="handleEmit($event,item,'focus')"
  36. />
  37. <span v-else-if="!item.hasSlot && item.itemType === 'text'" class="text-break-all" :class="(item.click?'f-cursor--pointer link-type':'') +(item.className || '')" @click="()=>{item.click && item.click(item,form)}">
  38. {{
  39. (item.formatter && item.formatter(form)) ||( formatter(item,form[item.prop]))
  40. }}
  41. </span>
  42. <slot v-else v-bind="item" :name="item.prop" />
  43. <slot v-if="item.orderSlot" :name="item.orderSlot" /></slot>
  44. </div>
  45. <!-- label插槽 -->
  46. <template v-if="item.hasLabelSlot" #label>
  47. <template v-if="item.hasLabelSlot">
  48. <slot v-bind="item" :name="item.prop + 'Label'" />
  49. </template>
  50. </template>
  51. </el-form-item>
  52. <el-form-item
  53. v-if="$slots.default"
  54. label-width="0px"
  55. class="flex-auto slot"
  56. >
  57. <slot />
  58. </el-form-item>
  59. </el-form>
  60. </template>
  61. <script>
  62. import { formatNumber } from '../../utils/index';
  63. import formMixins from'../../mixins/formMixins.js'
  64. export default {
  65. name: 'FForm',
  66. mixins:[formMixins],
  67. props: {
  68. config: { type: Array, default: () => [] }, // 配置文件
  69. form: {
  70. type: Object,
  71. default: () => {}
  72. }, // 传进来的共享的form表单值对象
  73. rules: { type: Object, default: () => ({}) }, // 传进来的共享的form验证规则
  74. column: { type: Number, default: 4 }, // 总的col布局
  75. itemWidth: { type: String, default: '100%' }, // form-item项的宽度
  76. disabled: { type: Boolean, default: false },
  77. clearable: { type: Boolean, default: true },
  78. // label位置
  79. labelPosition: {
  80. type: String,
  81. default: 'top'
  82. },
  83. // 是否自动宽度
  84. autoWidth: {
  85. type: Boolean,
  86. default: false
  87. },
  88. // 是否文字展示
  89. text: {
  90. type: Boolean,
  91. default: false
  92. },
  93. },
  94. data() {
  95. return {
  96. configdata: [],
  97. update: 0
  98. };
  99. },
  100. computed: {
  101. // 组件内部config
  102. configList() {
  103. return this.config;
  104. },
  105. // 整体列数对应的col
  106. colVal() {
  107. return 100 / this.column;
  108. },
  109. labelWidth() {
  110. // rowList每一行的数据,初始化的时候给一个二维数组,并制定第一个参数为new Array()
  111. // columnList每一列的数据
  112. const rowList = [new Array(this.column)];
  113. const columnList = [];
  114. // count用来计算是否换行新增rowList的数组元素空数组,用来填补二维数组空位
  115. // index代表的是rowList的当前操作项的下标
  116. let count = 0;
  117. let index = 0;
  118. this.config.forEach((e, i, arr) => {
  119. count += e.span || 1;
  120. rowList[index][count - (e.span || 1)] = {
  121. name: e.label || '',
  122. prop: e.prop,
  123. index: i
  124. };
  125. if (
  126. count + ((arr[i + 1] || {}).span || 1) > this.column &&
  127. arr[i + 1]
  128. ) {
  129. rowList.push(new Array(this.column));
  130. index++;
  131. count = 0;
  132. }
  133. });
  134. // 行数据转列数据
  135. rowList.forEach((e) => {
  136. for (let i = 0; i < e.length; i++) {
  137. if (!columnList[i]) {
  138. columnList[i] = [];
  139. }
  140. columnList[i].push(e[i]);
  141. }
  142. });
  143. // 取出每一列最大值nameLength
  144. // 取出每一列的index下标
  145. return columnList.map((e) => {
  146. const nameLength = Math.max.apply(
  147. null,
  148. e.map((el) => (el && this.charCode(el.name).length / 4) || 0)
  149. );
  150. let hasRules = false;
  151. e.map((el) => {
  152. if (
  153. el &&
  154. el.name &&
  155. this.rules[el.prop] &&
  156. this.rules[el.prop][0].required
  157. ) {
  158. hasRules = true;
  159. }
  160. });
  161. const indexList = e.map((el) => el && el.index);
  162. return { nameLength, indexList, hasRules };
  163. });
  164. }
  165. },
  166. methods: {
  167. // 表单重置
  168. resetFields(){
  169. this.$refs.fForm.resetFields();
  170. },
  171. // 优先子组件自己的 再表单的
  172. formItemDisabled(item){
  173. if(item.attrs&&item.attrs.hasOwnProperty('disabled')){
  174. return item.attrs.disabled
  175. }
  176. return this.isDisabled
  177. },
  178. // 获取class
  179. getClass(item, ind) {
  180. const { labelPosition, column, configList } = this;
  181. let classStr = ''
  182. // 计算当前配置是不是没行最后一个
  183. let allSpan = 0;
  184. let pr = 'pr-8';
  185. for (let _index = 0; _index <= Number(ind); _index++) {
  186. const newSpan = configList[_index].span || 1;
  187. allSpan += newSpan;
  188. }
  189. if (allSpan % column === 0) {
  190. pr = '';
  191. }
  192. classStr += pr;
  193. if (item.itemClass) {
  194. classStr += item.itemClass;
  195. }
  196. return classStr;
  197. },
  198. // 通过labelWidth获取对应index里面的值
  199. setWidth(item, index) {
  200. // 增加rules为入参,判断当前项是否有必填校验,如果有那么width加上11px;
  201. const val = this.labelWidth.find((e) => e.indexList.includes(index));
  202. const rulesWidth = val.hasRules ? 11 : 0;
  203. return val.nameLength * 14 + rulesWidth + 16 + 'px';
  204. },
  205. // 将字符串转为16进制值,汉子转4位,英文字母和数组转2位
  206. charCode: (str) =>
  207. str
  208. .split('')
  209. .reduce((total, e) => total + e.codePointAt(0).toString(16), ''),
  210. // 格式化
  211. formatter(column, v) {
  212. if (v === null || v === undefined) return '-';
  213. let value = v;
  214. const { dataType } = column;
  215. const type = dataType || '';
  216. if (value !== '') {
  217. if (type === 'money') {
  218. value = formatNumber(value);
  219. } else if (type === 'ten-thousand') {
  220. value = formatNumber(value, 'ten-thousand');
  221. } else if (type === 'number' || type === 'area') {
  222. let decimal = 2;
  223. if (type === 'number') {
  224. decimal = 0;
  225. }
  226. value = formatNumber(value, null, decimal);
  227. } else if (type === 'rate') {
  228. value = formatNumber(value, 'rate');
  229. } else if (type === 'date-d') {
  230. // value = value
  231. }
  232. }
  233. if(value === '0' || value === 0) return value
  234. return value || '-';
  235. },
  236. // 事件传递
  237. handleEmit($event,item,type){
  238. item[type] && item[type]($event);
  239. this.$emit(type,item,this.form)
  240. },
  241. // 校验
  242. validate(callback){
  243. return new Promise(resolve=>{
  244. this.$refs.fForm.validate(resolve);
  245. })
  246. }
  247. }
  248. };
  249. </script>
  250. <style lang="scss" scoped>
  251. .f-form .el-form-item {
  252. margin-bottom: 25px;
  253. // display: flex;
  254. // align-items: center;
  255. .el-change-icon {
  256. transform: rotate(90deg);
  257. }
  258. .el-form-item_custom {
  259. cursor: pointer;
  260. i {
  261. margin-left: 2px;
  262. }
  263. }
  264. }
  265. </style>