CusFormColumn.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. <template>
  2. <el-col class="cus-form-column" :class="{
  3. span1: filterSpan == 1,
  4. span2: filterSpan == 2,
  5. span3: filterSpan == 3,
  6. span4: filterSpan == 4,
  7. span5: filterSpan == 5,
  8. }" :span="span" :offset="offset" ref="ref_cusFormColumn">
  9. <el-form-item :label="label" :label-width="labelWidth" :class="{ required: required !== false }"
  10. :error="errorMessage" :required="required">
  11. <slot name="cus" :handleValidate="handleValidate">
  12. <template v-if="link === 'input'">
  13. <InputCom
  14. v-bind="$attrs"
  15. :label="labelCpt"
  16. :param="param"
  17. @emitParam="val => {
  18. $emit('update:param', val), handleValidate(val)
  19. }"
  20. @emitEnter="handleEnter"
  21. >
  22. <template v-if="$slots.prefix" #prefix>
  23. <slot name="prefix"/>
  24. </template>
  25. <template v-if="$slots.suffix" #suffix>
  26. <slot name="suffix"/>
  27. </template>
  28. <template v-if="$slots.prepend" #prepend>
  29. <slot name="prepend"/>
  30. </template>
  31. <template v-if="$slots.append" #append>
  32. <slot name="append"/>
  33. </template>
  34. </InputCom>
  35. </template>
  36. <template v-else-if="link === 'select'">
  37. <SelectCom
  38. v-bind="$attrs"
  39. :label="labelCpt"
  40. :param="param"
  41. @emitParam="val => {
  42. $emit('update:param', val), handleValidate(val)
  43. }"
  44. >
  45. <slot/>
  46. </SelectCom>
  47. </template>
  48. <template v-else-if="link === 'date'">
  49. <DateCom
  50. v-bind="$attrs"
  51. :label="labelCpt"
  52. :param="param"
  53. @emitParam="val => {
  54. $emit('update:param', val), handleValidate(val)
  55. }"
  56. >
  57. <template #default="{cell}">
  58. <slot v-bind="cell"/>
  59. </template>
  60. </DateCom>
  61. </template>
  62. <template v-else-if="link === 'datetime'">
  63. <DateTimeCom
  64. v-bind="$attrs"
  65. :label="labelCpt"
  66. :param="param"
  67. @emitParam="val => {
  68. $emit('update:param', val), handleValidate(val)
  69. }"
  70. >
  71. <template #default="{cell}">
  72. <slot v-bind="cell"/>
  73. </template>
  74. </DateTimeCom>
  75. </template>
  76. <template v-else-if="link === 'time'">
  77. <TimeCom
  78. v-bind="$attrs"
  79. :label="labelCpt"
  80. :param="param"
  81. @emitParam="val => {
  82. $emit('update:param', val), handleValidate(val)
  83. }"
  84. >
  85. </TimeCom>
  86. </template>
  87. <template v-else-if="link === 'cascader'">
  88. <CascaderCom
  89. v-bind="$attrs"
  90. :label="labelCpt"
  91. :param="param"
  92. @emitParam="val => {
  93. $emit('update:param', val), handleValidate(val)
  94. }"
  95. >
  96. <template v-if="$slots.default" #default="{node, data}">
  97. <slot v-bind="{node, data}"/>
  98. </template>
  99. </CascaderCom>
  100. </template>
  101. <template v-else-if="link === 'switch'">
  102. <SwitchCom
  103. v-bind="$attrs"
  104. :label="labelCpt"
  105. :param="param"
  106. @emitParam="val => {
  107. $emit('update:param', val), handleValidate(val)
  108. }"
  109. >
  110. </SwitchCom>
  111. </template>
  112. <template v-else-if="link === 'radio'">
  113. <RadioCom
  114. v-bind="$attrs"
  115. :label="labelCpt"
  116. :param="param"
  117. @emitParam="val => {
  118. $emit('update:param', val), handleValidate(val)
  119. }"
  120. >
  121. </RadioCom>
  122. </template>
  123. <template v-else-if="link === 'checkbox'">
  124. <CheckboxCom
  125. v-bind="$attrs"
  126. :label="labelCpt"
  127. :param="param"
  128. @emitParam="val => {
  129. $emit('update:param', val), handleValidate(val)
  130. }"
  131. >
  132. </CheckboxCom>
  133. </template>
  134. <template v-else-if="link === 'portOfRegistry'">
  135. <PortOfRegistryCom
  136. v-bind="$attrs"
  137. :label="labelCpt"
  138. :paramOne="paramOne"
  139. :paramTwo="paramTwo"
  140. @emitParam="(pOne, pTwo) => {
  141. $emit('update:paramOne', pOne), $emit('update:paramTwo', pTwo), handleValidate(pOne, pTwo)
  142. }"
  143. />
  144. </template>
  145. <template v-else-if="link === 'residentMooringPoint'">
  146. <ResidentMooringPointCom
  147. v-bind="$attrs"
  148. :label="labelCpt"
  149. :paramOne="paramOne"
  150. :paramTwo="paramTwo"
  151. @emitParam="(pOne, pTwo) => {
  152. $emit('update:paramOne', pOne), $emit('update:paramTwo', pTwo), handleValidate(pOne, pTwo)
  153. }"
  154. />
  155. </template>
  156. <template v-else-if="link === 'dept'">
  157. <DeptCom
  158. v-bind="$attrs"
  159. :label="labelCpt"
  160. :param="param"
  161. @emitParam="val => {
  162. $emit('update:param', val), handleValidate(val)
  163. }"
  164. @emitDefault="val => $emit('getDefault', val)"
  165. />
  166. </template>
  167. <template v-else-if="link === 'upload'">
  168. <UploadCom
  169. v-bind="$attrs"
  170. :label="labelCpt"
  171. :param="param"
  172. @emitParam="val => {
  173. $emit('update:param', val), handleValidate(val)
  174. }"
  175. />
  176. </template>
  177. <template v-else-if="link === 'number'">
  178. <NumberCom
  179. v-bind="$attrs"
  180. :label="labelCpt"
  181. :param="param"
  182. @emitParam="val => {
  183. $emit('update:param', val), handleValidate(val)
  184. }"
  185. @emitEnter="handleEnter"
  186. >
  187. <template v-if="$slots.prefix" #prefix>
  188. <slot name="prefix"/>
  189. </template>
  190. <template v-if="$slots.suffix" #suffix>
  191. <slot name="suffix"/>
  192. </template>
  193. <template v-if="$slots.prepend" #prepend>
  194. <slot name="prepend"/>
  195. </template>
  196. <template v-if="$slots.append" #append>
  197. <slot name="append"/>
  198. </template>
  199. </NumberCom>
  200. </template>
  201. <template v-else-if="link === 'input-number'">
  202. <InputNumberCom
  203. v-bind="$attrs"
  204. :label="labelCpt"
  205. :param="param"
  206. @emitParam="val => {
  207. $emit('update:param', val), handleValidate(val)
  208. }"
  209. @emitEnter="handleEnter"
  210. >
  211. </InputNumberCom>
  212. </template>
  213. <template v-else-if="link === 'tree-select'">
  214. <TreeSelectCom
  215. v-bind="$attrs"
  216. :label="labelCpt"
  217. :param="param"
  218. @emitParam="val => {
  219. $emit('update:param', val), handleValidate(val)
  220. }"
  221. >
  222. </TreeSelectCom>
  223. </template>
  224. </slot>
  225. <div v-if="unit" class="unit">{{unit}}</div>
  226. </el-form-item>
  227. </el-col>
  228. </template>
  229. <script lang="ts">
  230. import {
  231. defineComponent,
  232. computed,
  233. onMounted,
  234. ref,
  235. reactive,
  236. watch,
  237. getCurrentInstance,
  238. ComponentInternalInstance,
  239. toRefs,
  240. nextTick,
  241. inject, onUnmounted, onBeforeUnmount
  242. } from 'vue'
  243. import {useStore} from 'vuex'
  244. import {useRouter, useRoute} from 'vue-router'
  245. import InputCom from './cus-form-link/input.vue'
  246. import InputNumberCom from './cus-form-link/input-number.vue'
  247. import SelectCom from './cus-form-link/select.vue'
  248. import DateCom from './cus-form-link/date.vue'
  249. import DateTimeCom from './cus-form-link/datetime.vue'
  250. import TimeCom from './cus-form-link/time.vue'
  251. import CascaderCom from './cus-form-link/cascader.vue'
  252. import SwitchCom from './cus-form-link/switch.vue'
  253. import RadioCom from './cus-form-link/radio.vue'
  254. import CheckboxCom from './cus-form-link/checkbox.vue'
  255. import PortOfRegistryCom from './cus-form-link/portOfRegistry.vue'
  256. import ResidentMooringPointCom from './cus-form-link/residentMooringPoint.vue'
  257. import DeptCom from './cus-form-link/dept.vue'
  258. import UploadCom from './cus-form-link/upload.vue'
  259. import NumberCom from './cus-form-link/number.vue'
  260. import TreeSelectCom from './cus-form-link/tree-select.vue'
  261. import { v4 } from "uuid";
  262. export default defineComponent({
  263. name: 'CusFormColumn',
  264. components: {
  265. InputCom,
  266. InputNumberCom,
  267. SelectCom,
  268. DateCom,
  269. DateTimeCom,
  270. TimeCom,
  271. CascaderCom,
  272. SwitchCom,
  273. RadioCom,
  274. CheckboxCom,
  275. PortOfRegistryCom,
  276. ResidentMooringPointCom,
  277. DeptCom,
  278. UploadCom,
  279. NumberCom,
  280. TreeSelectCom
  281. },
  282. props: {
  283. span: {type: Number, default: 6},
  284. filterSpan: { default: null },
  285. offset: {type: Number, default: 0},
  286. param: {},
  287. paramOne: {},
  288. paramTwo: {},
  289. label: {type: String, default: ''},
  290. required: {default: false},
  291. labelWidth: {type: String, default: ''},
  292. link: {type: String, default: 'input', validator(val: string) {
  293. return ['cascader', 'checkbox', 'date', 'datetime', 'input', 'radio', 'select', 'switch', 'portOfRegistry', 'residentMooringPoint', 'dept', 'time', 'upload', 'number', 'input-number', 'tree-select'].includes(val)
  294. }},
  295. rules: {type: Array, default: () => []},
  296. maxLength: {type: Number, default: null},
  297. minLength: {type: Number, default: null},
  298. defaultErrorMsg: {default: null},
  299. unit: {default: '', type: String}
  300. },
  301. setup(props) {
  302. const store = useStore();
  303. const router = useRouter();
  304. const route = useRoute();
  305. const that = (getCurrentInstance() as ComponentInternalInstance).appContext.config.globalProperties
  306. const state = reactive({
  307. errorMessage: null,
  308. uuid: ''
  309. })
  310. const ref_cusFormColumn: any = ref(null)
  311. const isValue = (val: any) => {
  312. if (val === null || val === undefined || (typeof val === 'string' && val.trim() === '') || (typeof val === 'object' && val?.length === 0)) {
  313. return false
  314. }
  315. return true
  316. }
  317. const labelCpt = computed(() => {
  318. return props.label.replace(/[::]([^::]*)$/, "")
  319. })
  320. const rulesCpt = computed(() => {
  321. const r = [...props.rules]
  322. if (isValue(props.minLength)) {
  323. r.unshift({
  324. handle: (val: any) => !isValue(val) || (isValue(val) && String(val).length >= props.minLength),
  325. message: `内容过短,字数需大于等于${props.minLength}`
  326. })
  327. }
  328. if (isValue(props.maxLength)) {
  329. r.unshift({
  330. handle: (val: any) => !isValue(val) || (isValue(val) && String(val).length <= props.maxLength),
  331. message: `内容过长,字数需小于等于${props.maxLength}`
  332. })
  333. }
  334. const doStr = ['input', 'number', 'input-number'].includes(props.link) ? '输入' : '选择'
  335. if (props.required !== false && !props.rules.some((v: any) => v.type === 'default')) {
  336. if (['portOfRegistry', 'residentMooringPoint'].includes(props.link)) {
  337. r.unshift({
  338. handle: (pOne: any, pTwo: any) => isValue(pOne) && isValue(pTwo),
  339. message: props.defaultErrorMsg ?? `请${doStr}${labelCpt.value}`
  340. })
  341. } else {
  342. r.unshift({
  343. handle: (val: any) => isValue(val),
  344. message: props.defaultErrorMsg ?? `请${doStr}${labelCpt.value}`
  345. })
  346. }
  347. }
  348. return r
  349. })
  350. const handleValidate = (val: any = undefined, val2: any = undefined) => {
  351. state.errorMessage = null
  352. for (let i = 0; i < rulesCpt.value.length; i++) {
  353. const item: any = rulesCpt.value[i]
  354. if (['portOfRegistry', 'residentMooringPoint'].includes(props.link)) {
  355. if (!item.handle(val === undefined ? props.paramOne : val, val2 === undefined ? props.paramTwo : val2)) {
  356. state.errorMessage = item.message
  357. break
  358. }
  359. } else {
  360. if (!item.handle(val === undefined ? props.param : val)) {
  361. state.errorMessage = item.message
  362. break
  363. }
  364. }
  365. }
  366. return state.errorMessage
  367. }
  368. const handleEnterFunc = inject('handle-enter', () => {})
  369. const handleEnter = () => {
  370. handleEnterFunc?.()
  371. }
  372. const reset = () => {
  373. state.errorMessage = null
  374. }
  375. watch(() => state.errorMessage, (n) => {
  376. const p = ref_cusFormColumn.value?.$el?.getElementsByClassName('el-form-item')?.[0]
  377. if (n) {
  378. setTimeout(() => {
  379. const e = p?.getElementsByClassName('el-form-item__content')?.[0]?.getElementsByClassName('el-form-item__error')?.[0]
  380. if (e?.clientHeight) {
  381. p.style.marginBottom = `${e.clientHeight + 4}px`
  382. }
  383. }, 200)
  384. } else {
  385. p.style.marginBottom = '18px'
  386. }
  387. })
  388. onMounted(() => {
  389. state.uuid = v4()
  390. const formChildrenMap: any = inject('cus-form-children-map', new Map())
  391. let flag = true
  392. const deep = (dom) => {
  393. if (dom.className === 'hidden-columns') {
  394. flag = false
  395. }
  396. if (dom.parentElement) {
  397. deep(dom.parentElement)
  398. }
  399. }
  400. deep(ref_cusFormColumn.value.$parent.$el)
  401. if (flag) {
  402. formChildrenMap.set(state.uuid, ref_cusFormColumn.value.$parent)
  403. }
  404. })
  405. onBeforeUnmount(() => {
  406. const formChildrenMap: any = inject('cus-form-children-map', new Map())
  407. formChildrenMap.delete(state.uuid)
  408. })
  409. return {
  410. ...toRefs(state),
  411. handleValidate,
  412. handleEnter,
  413. ref_cusFormColumn,
  414. reset,
  415. labelCpt
  416. }
  417. },
  418. })
  419. </script>
  420. <style scoped lang="scss">
  421. .cus-form-column {
  422. &.span1 {
  423. width: 20%;
  424. max-width: 20%;
  425. flex: unset;
  426. }
  427. &.span2 {
  428. width: 40%;
  429. max-width: 40%;
  430. flex: unset;
  431. }
  432. &.span3 {
  433. width: 60%;
  434. max-width: 60%;
  435. flex: unset;
  436. }
  437. &.span4 {
  438. width: 80%;
  439. max-width: 80%;
  440. flex: unset;
  441. }
  442. &.span5 {
  443. width: 100%;
  444. max-width: 100%;
  445. flex: unset;
  446. }
  447. :deep(.el-form-item) {
  448. .el-form-item__label {
  449. word-break: keep-all;
  450. line-height: 1;
  451. text-align: right;
  452. display: flex;
  453. align-items: center;
  454. padding-left: 10px;
  455. font-size: 14px;
  456. font-family: Microsoft YaHei;
  457. font-weight: 400;
  458. color: #434343;
  459. height: 36px;
  460. }
  461. .el-form-item__content {
  462. height: 36px;
  463. flex-wrap: unset;
  464. >div:first-child {
  465. flex: 1;
  466. height: 100%;
  467. }
  468. .unit {
  469. margin-left: 6px;
  470. font-size: 14px;
  471. font-family: PingFang SC-Regular, PingFang SC;
  472. font-weight: 400;
  473. color: #606266;
  474. }
  475. .el-input {
  476. height: 100%;
  477. .el-input__wrapper {
  478. border-radius: 2px;
  479. }
  480. }
  481. .el-select {
  482. height: 100%;
  483. .select-trigger {
  484. height: 100%;
  485. }
  486. }
  487. }
  488. }
  489. }
  490. </style>