index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. <template>
  2. <div id="ym-table-search" class="ym-table-search">
  3. <div class="main-content">
  4. <el-form
  5. ref="form"
  6. :model="form"
  7. label-width="78px"
  8. :inline="false"
  9. size="small"
  10. >
  11. <div style="display: flex; flex-wrap: wrap">
  12. <!-- 操作栏 -->
  13. <!-- 常用表单渲染 -->
  14. <div
  15. v-for="(item, index) in renderSearchForm"
  16. :key="index"
  17. >
  18. <el-button
  19. v-if="item.type === 'button'"
  20. :type="item.danger ? 'danger' : 'primary'"
  21. size="small"
  22. @click="item.method"
  23. >{{ item.text }}</el-button>
  24. <el-form-item
  25. :label-width="item.labelWidth"
  26. :label="item.label"
  27. :prop="item.prop"
  28. :required="item.required"
  29. v-show="!item.isHide"
  30. >
  31. <el-input
  32. v-if="item.type === 'input'"
  33. v-model="form[item.prop]"
  34. style="width: 150px;margin-right: 16px;"
  35. :placeholder="item.placeholder || `请输入${item.label}`"
  36. :clearable="clearable(item)"
  37. />
  38. <el-select
  39. v-if="item.type === 'select'"
  40. v-model="form[item.prop]"
  41. :placeholder="item.placeholder || `请选择${item.label}`"
  42. :clearable="clearable(item)"
  43. @change="item.method?item.method(form[item.prop]):''"
  44. style="width: 150px;margin-right: 16px;"
  45. >
  46. <el-option
  47. v-for="(option, index) in item.options"
  48. :key="option.value + '' + index"
  49. :label="option.label"
  50. :value="option.value"
  51. />
  52. </el-select>
  53. <div style="margin-right: 16px;">
  54. <el-date-picker
  55. v-if="isDateType(item.type)"
  56. v-model="form[item.prop]"
  57. :value-format="item.valueFormat || dateValueFormat(item.type)"
  58. :format="item.format"
  59. :type="item.type"
  60. :placeholder="item.placeholder || `请选择${item.label}`"
  61. :picker-options="datePickerOptions(item)"
  62. :style="{
  63. width: (item.type === 'datetimerange' || item.type === 'daterange') ? '230px' : '150px'
  64. }"
  65. :start-placeholder="item.startPlaceholder || '开始日期'"
  66. :end-placeholder="item.endPlaceholder || '结束日期'"
  67. :clearable="clearable(item)"
  68. />
  69. </div>
  70. <el-cascader
  71. v-if="item.type === 'cascader'"
  72. ref="areaCascader"
  73. v-model="form[item.prop]"
  74. style="width: 150px;margin-right: 16px;"
  75. :options="item.options || areaOptions"
  76. :clearable="clearable(item)"
  77. filterable
  78. :props="
  79. Object.assign(
  80. {
  81. expandTrigger: 'hover',
  82. checkStrictly: true,
  83. label: 'label',
  84. value: 'value',
  85. children: 'children',
  86. },
  87. item.props
  88. )
  89. "
  90. @change="onAreaChange($event, item.prop)"
  91. />
  92. <slot :name="item.slotName" :form="form" :formItem="form[item.prop]" v-if="item.slotName && item.type === 'slot'"></slot>
  93. </el-form-item>
  94. </div>
  95. <!-- 查询操作 -->
  96. <el-col
  97. v-if="renderSearchForm.length"
  98. class="opts-item"
  99. :span="2"
  100. style="margin-left: auto;"
  101. >
  102. <el-form-item
  103. class="opts-item-btns"
  104. :class="{ 'flex-start': renderSearchForm.length === 1 }"
  105. >
  106. <el-button
  107. type="primary"
  108. :loading="loading"
  109. @click="onSubmit"
  110. >查询</el-button>
  111. <el-button
  112. v-if="renderSearchForm.length > 1"
  113. @click="onReset"
  114. >重置</el-button>
  115. <!-- <el-button
  116. v-if="searchForm.length > 5"
  117. class="dropdown-arrow"
  118. type="text"
  119. @click="dropdownAction = !dropdownAction"
  120. >
  121. {{ dropdownAction ? "收起" : "展开" }}
  122. <i
  123. class="el-icon-d-arrow-right"
  124. :class="{ active: dropdownAction }"
  125. />
  126. </el-button> -->
  127. </el-form-item>
  128. </el-col>
  129. <el-col v-else style="padding-bottom: 20px" :span="24">
  130. <el-alert
  131. title="无表单数据"
  132. type="warning"
  133. center
  134. :closable="false"
  135. show-icon
  136. />
  137. </el-col>
  138. </div>
  139. </el-form>
  140. </div>
  141. <div class="extra-content">
  142. <slot name="extra" />
  143. </div>
  144. </div>
  145. </template>
  146. <script>
  147. import request from '@/utils/request'
  148. export default {
  149. name: 'YMTableSearch',
  150. // inject: ["reload"],
  151. props: {
  152. searchForm: {
  153. type: Array,
  154. default: () => []
  155. },
  156. loading: {
  157. type: Boolean,
  158. default: false
  159. }
  160. },
  161. data() {
  162. return {
  163. initFlag: true,
  164. dropdownAction: false,
  165. form: {},
  166. areaOptions: [],
  167. areaListProp: '',
  168. searchHeight: 0,
  169. optsCols: 24,
  170. cancalDebounce: null
  171. }
  172. },
  173. computed: {
  174. clearable() {
  175. return item => {
  176. return item.clearable !== undefined ? item.clearable : true
  177. }
  178. },
  179. // 操作空间占用列数
  180. actionCols() {
  181. return this.optsCols
  182. },
  183. // 日期类型
  184. isDateType() {
  185. return (type) => {
  186. return (
  187. 'year/month/date/dates/months/years/week/datetime/datetimerange/daterange/monthrange'.indexOf(
  188. type
  189. ) !== -1
  190. )
  191. }
  192. },
  193. dateValueFormat() {
  194. return (type) => {
  195. const valueFormat = {
  196. year: 'yyyy',
  197. month: 'yyyy-MM',
  198. date: 'yyyy-MM-dd',
  199. dates: 'yyyy-MM-dd',
  200. months: 'yyyy-MM',
  201. years: 'yyyy',
  202. week: 'yyyy-ww',
  203. datetime: 'yyyy-MM-dd HH:mm:ss',
  204. datetimerange: 'yyyy-MM-dd HH:mm:ss',
  205. daterange: 'yyyy-MM-dd'
  206. }
  207. return valueFormat[type]
  208. }
  209. },
  210. // 日期选择器配置:默认禁止选择未来日期(当天整天可选),可用 item.pickerOptions 覆盖
  211. datePickerOptions() {
  212. return (item) => {
  213. const defaultDisabled = (time) => {
  214. const endOfToday = new Date()
  215. endOfToday.setHours(23, 59, 59, 999)
  216. return time.getTime() > endOfToday.getTime()
  217. }
  218. const itemOptions = item.pickerOptions || {}
  219. const itemDisabled = itemOptions.disabledDate
  220. return {
  221. ...itemOptions,
  222. disabledDate: itemDisabled || defaultDisabled
  223. }
  224. }
  225. },
  226. // 查询表单个数
  227. searchFormNum() {
  228. return this.renderSearchForm.length
  229. },
  230. // 操作表单列数
  231. colSpan() {
  232. if (!this.renderSearchForm) {
  233. this.$message.warning('请配置查询表单')
  234. return 24
  235. }
  236. const remainNum = 3 - (this.searchFormNum % 3)
  237. return remainNum * 8
  238. },
  239. /**
  240. * 渲染查询表单
  241. * 收起展开判断
  242. * @returns {Array}
  243. */
  244. renderSearchForm() {
  245. return this.searchForm.filter((item, index) => {
  246. return { ...item }
  247. // if (this.dropdownAction) {
  248. // return {
  249. // ...item
  250. // }
  251. // } else {
  252. // if (index < 5) {
  253. // return {
  254. // ...item
  255. // }
  256. // }
  257. // }
  258. })
  259. }
  260. },
  261. // 监听this.form
  262. watch: {
  263. form: {
  264. handler(val) {
  265. this.$emit('onValueChange', val)
  266. },
  267. deep: true
  268. }
  269. },
  270. mounted() {
  271. this.getWindowInfo()
  272. window.addEventListener('resize', this.cancalDebounce)
  273. },
  274. beforeDestroy() {
  275. window.removeEventListener('resize', this.cancalDebounce)
  276. },
  277. created() {
  278. this.initData()
  279. // 是否是区域展示
  280. const existCasader = this.renderSearchForm.find((item) => item.isArea)
  281. if (existCasader) {
  282. console.log('🚀 获取区域数据……')
  283. this.areaList(existCasader)
  284. }
  285. this.cancalDebounce = this.debounce(this.getWindowInfo, 500)
  286. window.addEventListener('resize', this.cancalDebounce)
  287. this.getWindowInfo()
  288. },
  289. methods: {
  290. /**
  291. * 监听元素的变化,刷新布局
  292. */
  293. getWindowInfo() {
  294. const { width } = document.body.getBoundingClientRect()
  295. // 两列,奇数则12,偶数则24
  296. if (width < 768) {
  297. this.optsCols = this.freeSpace(2)
  298. } else if (width >= 768 && width < 1920) {
  299. this.optsCols = this.freeSpace(3)
  300. } else {
  301. this.optsCols = this.freeSpace(4)
  302. }
  303. if (!this.initFlag) {
  304. console.log('刷新')
  305. // window.removeEventListener('resize', this.ancalDebounce)
  306. // this.reload();
  307. } else {
  308. this.initFlag = false
  309. }
  310. },
  311. // TODO: 添加显示下拉的判断
  312. freeSpace(cols) {
  313. const formLength = this.renderSearchForm.length
  314. const remainder = formLength % cols
  315. const eachColNum = 24 / cols
  316. const remianColNum = 24 - remainder * eachColNum
  317. return remianColNum
  318. },
  319. // 防抖
  320. debounce(fn, delay) {
  321. let timer
  322. return function() {
  323. if (timer) {
  324. clearTimeout(timer)
  325. }
  326. timer = setTimeout(() => {
  327. fn()
  328. }, delay)
  329. }
  330. },
  331. initData() {
  332. this.form = Object.assign(
  333. {},
  334. ...this.searchForm.map((item) => {
  335. return { [item.prop]: item.default || '' }
  336. })
  337. )
  338. console.table('🚀 初始化查询参数:', this.searchForm, this.form)
  339. },
  340. onSubmit() {
  341. const params = Object.assign({}, this.form, { page: 1 })
  342. this.$emit('submit', params)
  343. },
  344. // 只重置不查询
  345. resetFields() {
  346. console.log('🚀 重置查询')
  347. this.$refs.form.resetFields()
  348. },
  349. onReset() {
  350. this.resetFields()
  351. this.onSubmit()
  352. },
  353. areaList(existCasader) {
  354. const type = existCasader.isArea
  355. if (![true, 1, 2, 3, 4, 5].includes(type)) {
  356. this.$message.error('请设置isArea为1/2/3/4/5/true')
  357. return
  358. }
  359. request({
  360. url: `/lv/area_list`,
  361. method: 'get',
  362. params: {
  363. type: type === true ? 4 : type
  364. }
  365. }).then((res) => {
  366. // 由于获取出来的数据结构不一样,所以需要根据type来获取数据
  367. if ([true, 4].includes(type)) {
  368. this.areaOptions = this.areaOptions.concat(res.data[0].children)
  369. } else if ([1, 2, 3].includes(type)) {
  370. this.areaOptions = this.areaOptions.concat(res.data)
  371. if ([1, 3].includes(type)) {
  372. this.cascaderProps = Object.assign({}, this.cascaderProps, {
  373. label: 'name',
  374. value: 'id'
  375. })
  376. }
  377. }
  378. if (existCasader?.areaOptions) {
  379. this.areaOptions = [
  380. ...existCasader?.areaOptions,
  381. ...this.areaOptions
  382. ]
  383. }
  384. })
  385. },
  386. onAreaChange(value, prop) {
  387. console.log('🚀 区域变化值:', value, prop)
  388. this.form[prop] = value
  389. },
  390. // 获取地区选中对象
  391. getAreaValue() {
  392. if (!this.$refs.areaCascader) return
  393. return this.$refs.areaCascader[0].getCheckedNodes()
  394. }
  395. }
  396. }
  397. </script>
  398. <style lang="scss" scoped>
  399. .ym-table-search {
  400. flex: 1;
  401. margin: 0px 16px 16px 16px;
  402. padding: 12px 20px 0;
  403. border-radius: 8px;
  404. background-color: #fff;
  405. .el-form-item--mini.el-form-item, .el-form-item--small.el-form-item{
  406. margin-bottom: 16px;
  407. }
  408. .el-icon-d-arrow-right {
  409. transform: rotate(90deg);
  410. &.active {
  411. transform: rotate(-90deg);
  412. }
  413. }
  414. .el-row {
  415. display: flex;
  416. flex-wrap: wrap;
  417. // justify-content: space-between;
  418. }
  419. // [class*="el-col-"]{
  420. // flex: 1/3;
  421. // }
  422. .el-col {
  423. padding-right: 24px;
  424. }
  425. ::v-deep.el-form-item__label {
  426. text-align-last: justify;
  427. }
  428. .el-form {
  429. .el-form-item:last-child {
  430. // margin-bottom: 0;
  431. }
  432. }
  433. ::v-deep .el-form-item__label{
  434. color: #000;
  435. font-weight: 500;
  436. font-size: 16px;
  437. }
  438. ::v-deep .opts-item-btns {
  439. .el-button:first-child {
  440. margin-left: 24px;
  441. }
  442. .el-form-item__content {
  443. display: flex;
  444. justify-content: flex-end;
  445. }
  446. &.flex-start {
  447. .el-form-item__content {
  448. justify-content: flex-start;
  449. margin-left: 0 !important;
  450. }
  451. }
  452. }
  453. }
  454. </style>