SearchForm.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <script lang="tsx">
  2. import { defineComponent, watch, ref, computed, PropType, CSSProperties } from 'vue'
  3. import { Refresh, Search } from '@element-plus/icons-vue'
  4. import { LeFormItem, ObjectOpts, FormConfigOpts, FormItemSlots, SlotOption } from '@/components/FormConfig/formConfig.types'
  5. import InputNumber from './InputNumber'
  6. import InputNumberRange from './InputNumberRange'
  7. import CustomRender from './CustomRender'
  8. import { useI18n } from 'vue-i18n'
  9. import { getOptions, renderOption, get_formSlots } from '@/components/FormConfig/utils.ts'
  10. import { OptionItemProps } from '@/components/Select/select.types.ts'
  11. const emits = ['update:searchData']
  12. export type SearchFormItem = LeFormItem
  13. export const SearchFormProps = {
  14. forms: {
  15. // SearchForm: 与FormConfig不同的是 change的第一个参数的params, 去掉了原来的value 选项
  16. type: Array as PropType<SearchFormItem[]>,
  17. required: true
  18. },
  19. // 后台传递的初始值 以及 双向绑定 对象
  20. searchData: {
  21. required: true,
  22. type: Object as PropType<ObjectOpts>
  23. },
  24. // item 修改后 自动触发搜索
  25. triggerSearchAuto: {
  26. type: Boolean,
  27. default: false
  28. // default: true
  29. },
  30. // 自动触发 Automatic trigger
  31. // formModal的配置项对象
  32. formConfig: {
  33. type: Object as PropType<FormConfigOpts>,
  34. default: () => ({})
  35. },
  36. loading: {
  37. type: Boolean,
  38. default: false
  39. },
  40. reset: {
  41. type: Function as PropType<((initSearchData: Record<string, any>) => any)>
  42. }
  43. }
  44. export const SearchForm = defineComponent({
  45. name: 'LeSearchForm',
  46. emits,
  47. props: SearchFormProps,
  48. setup(props, ctx) {
  49. const { t } = useI18n()
  50. const formRef = ref(/*formRef*/)
  51. let initSearchData = undefined
  52. watch(
  53. () => props.searchData,
  54. (newValue, oldValue) => {
  55. if (!initSearchData && newValue) {
  56. initSearchData = { ...newValue }
  57. }
  58. },
  59. {
  60. immediate: true
  61. }
  62. )
  63. // 重置搜索
  64. const local_resetHandler = () => {
  65. // 若有reset 将不会触发默认重置的操作
  66. const emitReset = props.reset
  67. const _initSearchData = { ...(initSearchData || {}) }
  68. if (emitReset) {
  69. emitReset(_initSearchData)
  70. } else {
  71. // formRef.value!.resetFields()
  72. // 撤回为初始化状态
  73. ctx.emit('update:searchData', _initSearchData)
  74. }
  75. }
  76. // 搜索
  77. const searchHandler = () => {
  78. ctx.emit('update:searchData', { ...props.searchData })
  79. }
  80. // 强行修改初始化数据(用于 重置方法进行数据重置)
  81. const forceUpdateInitParams = (searchData = props.searchData) => {
  82. initSearchData = { ...searchData }
  83. }
  84. ctx.expose({
  85. formRef,
  86. forceUpdateInitParams
  87. })
  88. const vSlots = ctx.slots
  89. const realForms = computed(() => {
  90. return (props.forms || []).map((form) => {
  91. return {
  92. ...form,
  93. le_slots: get_formSlots(vSlots, form.slots)
  94. }
  95. })
  96. })
  97. // const gutter = 8
  98. // const colStyle = computed(() => {
  99. // const styles: CSSProperties = {}
  100. // if (gutter) {
  101. // styles.paddingLeft = styles.paddingRight = `${gutter / 2}px`
  102. // }
  103. // return styles
  104. // })
  105. // render渲染
  106. return () => {
  107. const { searchData, formConfig = {}, triggerSearchAuto } = props
  108. let warpClass = 'le-search-form-container' // labelStyle
  109. const getItemStyle = (itemStyle, defaultWidth) => {
  110. return itemStyle + ((/width\:/g).test(itemStyle) ? '' : `;width:${defaultWidth}`)
  111. }
  112. const itemRender = (form, _label) => {
  113. // 申明: onChange 会导致(类input) change后触发两次(组件定义一次,原生change一次) 对组件定义进行过滤,仅留原生触发,组件触发onChange 用change 替代
  114. const { prop, itemType, itemWidth, options, change, onChange, itemStyle = '', placeholder, t_placeholder, le_slots, ...formOthers } = form
  115. const _options = options || []
  116. const _itemStyle = itemStyle + (itemWidth ? `;width: ${itemWidth}` : '')
  117. let disabled = form.disabled
  118. const _placeholder: string = (t_placeholder ? t(t_placeholder) : placeholder) || _label
  119. // 优化后的 change事件
  120. let formatterChange = async () => {
  121. if (typeof change === 'function') {
  122. return change(searchData[prop], _options, searchData)
  123. }
  124. }
  125. let bindInputEvents = {}
  126. let changeAndSearch = formatterChange
  127. if (triggerSearchAuto) {
  128. changeAndSearch = () => formatterChange().then(searchHandler)
  129. bindInputEvents = {
  130. onBlur: searchHandler,
  131. // 回车触发搜索
  132. onKeydown: (e: KeyboardEvent) => {
  133. // console.error(e, 'onKeyDown', e.key)
  134. if (e.key === 'Enter') {
  135. searchHandler()
  136. }
  137. }
  138. }
  139. }
  140. switch (itemType) {
  141. case 'leSelect':
  142. // leSelect: 基于 element-plus el-select-v2扩展
  143. const slots_leSelect = {
  144. default: le_slots.option as SlotOption<OptionItemProps>
  145. }
  146. return (
  147. <LeSelect
  148. {...formOthers}
  149. options={_options}
  150. v-model={searchData[prop]}
  151. // 通过teleport插入到body (popper-append-to-body popperAppendToBody已弃用)
  152. teleported={formOthers.teleported ?? true}
  153. // '@update:selected_label' todo
  154. onChange={changeAndSearch}
  155. disabled={disabled}
  156. placeholder={_placeholder}
  157. style={getItemStyle(_itemStyle, '200px')}
  158. v-slots={slots_leSelect}
  159. />
  160. )
  161. // 自定义render
  162. case 'render':
  163. return <CustomRender
  164. form={form}
  165. params={searchData}
  166. />
  167. // 下拉框
  168. case 'select':
  169. return (
  170. <el-select
  171. {...formOthers}
  172. v-model={searchData[prop]}
  173. onChange={changeAndSearch}
  174. style={getItemStyle(_itemStyle, '200px')}
  175. disabled={disabled}
  176. clearable={form.clearable ?? true}
  177. placeholder={_placeholder}
  178. >
  179. {getOptions(_options, form).map((option, i) => {
  180. const { label, value, disabled } = option
  181. return (
  182. <el-option key={`${value}_${i}`} value={value} label={label} disabled={disabled} title={label}>
  183. {/*renderOption(ctx.slots, form.slots?.option, option)*/}
  184. {renderOption(le_slots.option, option)}
  185. </el-option>
  186. )
  187. })}
  188. </el-select>
  189. )
  190. // 单选框
  191. case 'radio':
  192. return (
  193. <el-radio-group
  194. {...formOthers}
  195. v-model={searchData[prop]}
  196. disabled={disabled}
  197. onChange={changeAndSearch}
  198. style={getItemStyle(_itemStyle, 'auto')}
  199. >
  200. {getOptions(_options, form).map((option, i) => {
  201. const { label, value, disabled } = option
  202. return (
  203. <el-radio-button key={`${value}_${i}`} label={value} disabled={disabled} title={label}>
  204. {/*renderOption(ctx.slots, form.slots?.option, option)*/}
  205. {renderOption(le_slots.option, option)}
  206. </el-radio-button>
  207. )
  208. })}
  209. </el-radio-group>
  210. )
  211. // 级联
  212. case 'cascader':
  213. const slots_cascader = {
  214. default: le_slots.option as SlotOption<{ data: any; node: any }>
  215. }
  216. return (
  217. <el-cascader
  218. {...formOthers}
  219. v-model={searchData[prop]}
  220. onChange={changeAndSearch}
  221. style={getItemStyle(_itemStyle, '200px')}
  222. disabled={disabled}
  223. clearable={form.clearable ?? true}
  224. filterable={form.filterable ?? true}
  225. options={_options}
  226. placeholder={_placeholder}
  227. v-slots={slots_cascader}
  228. />
  229. )
  230. // 数字
  231. case 'inputNumber':
  232. return (
  233. <InputNumber
  234. class="rate100"
  235. {...bindInputEvents}
  236. {...formOthers}
  237. slots={le_slots}
  238. v-model={searchData[prop]}
  239. onChange={formatterChange}
  240. style={getItemStyle(_itemStyle, '130px')}
  241. disabled={disabled}
  242. placeholder={_placeholder}
  243. precision={form.precision || 0}
  244. />
  245. )
  246. // 数字区间
  247. case 'inputNumberRange':
  248. const numberChange = (e, propKey) => {
  249. change && change(searchData[propKey], _options, searchData, propKey)
  250. }
  251. return (
  252. <InputNumberRange
  253. {...bindInputEvents}
  254. prop={prop}
  255. {...formOthers}
  256. v-model={searchData[prop]}
  257. isValueArray
  258. // modelValue={searchData}
  259. onChange={numberChange}
  260. itemStyle={getItemStyle(_itemStyle, '230px')}
  261. disabled={disabled}
  262. placeholder={_placeholder}
  263. precision={form.precision || 0}
  264. v-slots={le_slots}
  265. />
  266. )
  267. // 日期选择 (单日期 || 日期区间 ...) year/month/date/datetime/ week/datetimerange/daterange
  268. case 'datePicker':
  269. let dateWidthDefault = '160px'
  270. let dateOpts: any = {}
  271. dateOpts.valueFormat = form.valueFormat || 'MM/DD/YYYY'
  272. dateOpts.format = form.format || dateOpts.valueFormat
  273. // 区间类型
  274. if (/range$/.test(form.type || '')) {
  275. dateWidthDefault = '220px'
  276. const startPlaceholder = form.t_startPlaceholder ? t(form.t_startPlaceholder) : form.startPlaceholder
  277. const endPlaceholder = form.t_endPlaceholder ? t(form.t_endPlaceholder) : form.endPlaceholder
  278. dateOpts = Object.assign(dateOpts, {
  279. startPlaceholder: startPlaceholder ?? t('le.filter.startDate'),
  280. endPlaceholder: endPlaceholder ?? t('le.filter.endDate'),
  281. unlinkPanels: form.unlinkPanels ?? true // 解除联动
  282. })
  283. } else {
  284. dateOpts.placeholder = _placeholder || t('le.filter.selectDate')
  285. }
  286. return (
  287. <el-date-picker
  288. {...formOthers}
  289. {...dateOpts}
  290. v-model={searchData[prop]}
  291. onChange={changeAndSearch}
  292. style={getItemStyle(_itemStyle, dateWidthDefault)}
  293. disabled={disabled}
  294. />
  295. )
  296. // switch
  297. case 'switch':
  298. return <el-switch
  299. {...formOthers}
  300. v-model={searchData[prop]}
  301. onChange={changeAndSearch}
  302. style={_itemStyle}
  303. disabled={disabled}
  304. />
  305. case 'input':
  306. default:
  307. return (
  308. <el-input
  309. {...bindInputEvents}
  310. {...formOthers}
  311. v-model={searchData[prop]}
  312. onChange={formatterChange}
  313. disabled={disabled}
  314. placeholder={_placeholder}
  315. style={getItemStyle(_itemStyle, '160px')}
  316. />
  317. )
  318. }
  319. }
  320. return (
  321. <div class={warpClass}>
  322. <div class="le-search-form-flex">
  323. <el-form ref={formRef} inline={true} size="default" class="le-search-form-flex-wrap" model={searchData} {...formConfig}>
  324. <el-row class="form_wrap" gutter={8}>
  325. {realForms.value.map((form: SearchFormItem & { le_slots: ObjectOpts }, idx) => {
  326. // 通过 form.visible 控制 是否展示
  327. const _label = form.t_label ? t(form.t_label) : form.label
  328. const slots = {
  329. label: form.le_slots.label
  330. }
  331. return (
  332. <el-col v-show={form.visible !== false} key={idx} span={form.span ?? 1024}>
  333. <el-form-item
  334. // class={`${form.showLabel === false ? 'hideLabel' : ''} el-col el-col-${form.span}`}
  335. // v-show={form.visible !== false}
  336. // style={colStyle.value}
  337. // key={idx}
  338. class={form.showLabel === false ? 'hideLabel' : ''}
  339. {...form}
  340. label={_label}
  341. v-slots={slots}
  342. >
  343. {itemRender(form, _label)}
  344. </el-form-item>
  345. </el-col>
  346. )
  347. })}
  348. </el-row>
  349. </el-form>
  350. <div class="action-wrap">
  351. <el-button
  352. size="default"
  353. plain
  354. icon={Refresh}
  355. disabled={props.loading}
  356. onClick={local_resetHandler}>
  357. { t('le.btn.reset') }
  358. </el-button>
  359. <el-button
  360. size="default"
  361. type="primary"
  362. loading={props.loading}
  363. icon={Search}
  364. onClick={searchHandler}>
  365. { t('le.btn.search') }
  366. </el-button>
  367. </div>
  368. </div>
  369. </div>
  370. )
  371. }
  372. }
  373. })
  374. export default SearchForm
  375. </script>