SearchArea.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. <template>
  2. <div class="search-area" ref="root">
  3. <div class="select-root" ref="selectRoot"></div>
  4. <div class="search-item" :key="index" v-for="(col, index) in searchCols">
  5. <div v-if="col.dataType === 'boolean'" :class="['title', {active: col.search.value !== undefined}]">
  6. <template v-if="col.title">
  7. {{col.title}}:
  8. </template>
  9. <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
  10. <a-switch @change="onSwitchChange(col)" class="switch" v-model="col.search.value" size="small"
  11. :checked-children="(col.search.switchOptions && col.search.switchOptions.checkedText) || '是'"
  12. :un-checked-children="(col.search.switchOptions && col.search.switchOptions.uncheckedText) || '否'"
  13. />
  14. <a-icon v-if="col.search.value !== undefined" class="close" @click="e => onCloseClick(e, col)" type="close-circle" theme="filled" />
  15. </div>
  16. <div v-else-if="col.dataType === 'time'" :class="['title', {active: col.search.value}]">
  17. <template v-if="col.title">
  18. {{col.title}}:
  19. </template>
  20. <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
  21. <a-time-picker :format="col.search.format" v-model="col.search.value" placeholder="选择时间" @change="(time, timeStr) => onCalendarChange(time, timeStr, col)" @openChange="open => onCalendarOpenChange(open, col)" class="time-picker" size="small" :get-popup-container="() => $refs.root"/>
  22. </div>
  23. <div v-else-if="col.dataType === 'date'" :class="['title', {active: col.search.value}]">
  24. <template v-if="col.title">
  25. {{col.title}}:
  26. </template>
  27. <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
  28. <a-date-picker :format="col.search.format" v-model="col.search.value" @change="onDateChange(col)" class="date-picker" size="small" :getCalendarContainer="() => $refs.root"/>
  29. </div>
  30. <div v-else-if="col.dataType === 'datetime'" class="title datetime active">
  31. <template v-if="col.title">
  32. {{col.title}}:
  33. </template>
  34. <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
  35. <a-date-picker :format="col.search.format" v-model="col.search.value" @change="(date, dateStr) => onCalendarChange(date, dateStr, col)" @openChange="open => onCalendarOpenChange(open, col)" class="datetime-picker" size="small" show-time :getCalendarContainer="() => $refs.root"/>
  36. </div>
  37. <div v-else-if="col.dataType === 'select'" :class="['title', {active: col.search.value !== undefined}]">
  38. <template v-if="col.title">
  39. {{col.title}}:
  40. </template>
  41. <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
  42. <a-select :allowClear="true" :options="col.search.selectOptions" v-model="col.search.value" placeholder="请选择..." @change="onSelectChange(col)" class="select" slot="content" size="small" :get-popup-container="() => $refs.selectRoot">
  43. </a-select>
  44. </div>
  45. <div v-else :class="['title', {active: col.search.value}]">
  46. <a-popover @visibleChange="onVisibleChange(col, index)" v-model="col.search.visible" placement="bottom" :trigger="['click']" :get-popup-container="() => $refs.root">
  47. <template v-if="col.title">
  48. {{col.title}}
  49. </template>
  50. <slot v-else-if="col.slots && col.slots.title" :name="col.slots.title"></slot>
  51. <div class="value " v-if="col.search.value">:&nbsp;&nbsp;{{col.search.format && typeof col.search.format === 'function' ? col.search.format(col.search.value) : col.search.value}}</div>
  52. <a-icon v-if="!col.search.value" class="icon-down" type="down"/>
  53. <div class="operations" slot="content">
  54. <a-button @click="onCancel(col)" class="btn" size="small" type="link">取消</a-button>
  55. <a-button @click="onConfirm(col)" class="btn" size="small" type="primary">确认</a-button>
  56. </div>
  57. <div class="search-overlay" slot="title">
  58. <a-input :id="`${searchIdPrefix}${index}`" :allow-clear="true" @keyup.esc="onCancel(col)" @keyup.enter="onConfirm(col)" v-model="col.search.value" size="small" />
  59. </div>
  60. </a-popover>
  61. <a-icon v-if="col.search.value" @click="e => onCloseClick(e, col)" class="close" type="close-circle" theme="filled"/>
  62. </div>
  63. </div>
  64. </div>
  65. </template>
  66. <script>
  67. import fastEqual from 'fast-deep-equal'
  68. import moment from 'moment'
  69. export default {
  70. name: 'SearchArea',
  71. props: ['columns', 'formatConditions'],
  72. inject: ['table'],
  73. created() {
  74. this.formatColumns(this.columns)
  75. },
  76. watch: {
  77. columns(newVal, oldVal) {
  78. if (newVal != oldVal) {
  79. this.formatColumns(newVal)
  80. }
  81. },
  82. searchCols(newVal, oldVal) {
  83. if (newVal.length != oldVal.length) {
  84. const newConditions = this.getConditions(newVal)
  85. const newSearchOptions = this.getSearchOptions(newVal)
  86. if (!fastEqual(newConditions, this.conditions)) {
  87. this.conditions = newConditions
  88. this.searchOptions = newSearchOptions
  89. this.$emit('change', this.conditions, this.searchOptions)
  90. }
  91. }
  92. }
  93. },
  94. data() {
  95. return {
  96. conditions: {},
  97. searchOptions: []
  98. }
  99. },
  100. computed: {
  101. searchCols() {
  102. return this.columns.filter(item => item.searchAble)
  103. },
  104. searchIdPrefix() {
  105. return this.table.id + '-ipt-'
  106. }
  107. },
  108. methods: {
  109. onCloseClick(e, col) {
  110. e.preventDefault()
  111. e.stopPropagation()
  112. col.search.value = undefined
  113. const {backup, value} = col.search
  114. if (backup !== value) {
  115. this.backupAndEmitChange(col)
  116. }
  117. },
  118. onCancel(col) {
  119. col.search.value = col.search.backup
  120. col.search.visible = false
  121. },
  122. onConfirm(col) {
  123. const {backup, value} = col.search
  124. col.search.visible = false
  125. if (backup !== value) {
  126. this.backupAndEmitChange(col)
  127. }
  128. },
  129. onSwitchChange(col) {
  130. const {backup, value} = col.search
  131. if (backup !== value) {
  132. this.backupAndEmitChange(col)
  133. }
  134. },
  135. onSelectChange(col) {
  136. this.backupAndEmitChange(col)
  137. },
  138. onCalendarOpenChange(open, col) {
  139. col.search.visible = open
  140. const {momentEqual, backupAndEmitChange} = this
  141. const {value, backup, format} = col.search
  142. if (!open && !momentEqual(value, backup, format)) {
  143. backupAndEmitChange(col, moment(value))
  144. }
  145. },
  146. onCalendarChange(date, dateStr, col) {
  147. const {momentEqual, backupAndEmitChange} = this
  148. const {value, backup, format} = col.search
  149. if (!col.search.visible && !momentEqual(value, backup, format)) {
  150. backupAndEmitChange(col, moment(value))
  151. }
  152. },
  153. onDateChange(col) {
  154. const {momentEqual, backupAndEmitChange} = this
  155. const {value, backup, format} = col.search
  156. if (!momentEqual(value, backup, format)) {
  157. backupAndEmitChange(col, moment(value))
  158. }
  159. },
  160. getFormat(col) {
  161. if (col.search && col.search.format) {
  162. return col.search.format
  163. }
  164. const dataType = col.dataType
  165. switch(dataType) {
  166. case 'time': return 'HH:mm:ss'
  167. case 'date': return 'YYYY-MM-DD'
  168. case 'datetime': return 'YYYY-MM-DD HH:mm:ss'
  169. default: return undefined
  170. }
  171. },
  172. backupAndEmitChange(col, backValue = col.search.value) {
  173. const {getConditions, getSearchOptions} = this
  174. col.search.backup = backValue
  175. this.conditions = getConditions(this.searchCols)
  176. this.searchOptions = getSearchOptions(this.searchCols)
  177. this.$emit('change', this.conditions, this.searchOptions)
  178. },
  179. getConditions(columns) {
  180. const conditions = {}
  181. columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
  182. .forEach(col => {
  183. const {value, format} = col.search
  184. if (this.formatConditions && format) {
  185. if (typeof format === 'function') {
  186. conditions[col.dataIndex] = format(col.search.value)
  187. } else if (typeof format === 'string' && value.constructor.name === 'Moment') {
  188. conditions[col.dataIndex] = value.format(format)
  189. } else {
  190. conditions[col.dataIndex] = value
  191. }
  192. } else {
  193. conditions[col.dataIndex] = value
  194. }
  195. })
  196. return conditions
  197. },
  198. getSearchOptions(columns) {
  199. return columns.filter(item => item.search.value !== undefined && item.search.value !== '' && item.search.value !== null)
  200. .map(({dataIndex, search}) => ({field: dataIndex, value: search.value, format: search.format}))
  201. },
  202. onVisibleChange(col, index) {
  203. if (!col.search.visible) {
  204. col.search.value = col.search.backup
  205. } else {
  206. let input = document.getElementById(`${this.searchIdPrefix}${index}`)
  207. if (input) {
  208. setTimeout(() => {input.focus()}, 0)
  209. } else {
  210. this.$nextTick(() => {
  211. input = document.getElementById(`${this.searchIdPrefix}${index}`)
  212. input.focus()
  213. })
  214. }
  215. }
  216. },
  217. momentEqual(target, source, format) {
  218. if (target === source) {
  219. return true
  220. } else if (target && source && target.format(format) === source.format(format)) {
  221. return true
  222. }
  223. return false
  224. },
  225. formatColumns(columns) {
  226. columns.forEach(item => {
  227. this.$set(item, 'search', {...item.search, visible: false, value: undefined, format: this.getFormat(item)})
  228. })
  229. }
  230. }
  231. }
  232. </script>
  233. <style scoped lang="less">
  234. .search-area{
  235. .select-root{
  236. text-align: left;
  237. }
  238. margin: -4px 0;
  239. .search-item{
  240. margin: 4px 4px;
  241. display: inline-block;
  242. .title{
  243. padding: 4px 8px;
  244. cursor: pointer;
  245. border-radius: 4px;
  246. user-select: none;
  247. display: inline-flex;
  248. align-items: center;
  249. .close{
  250. color: @text-color-second;
  251. margin-left: 4px;
  252. font-size: 12px;
  253. vertical-align: middle;
  254. :hover{
  255. color: @text-color;
  256. }
  257. }
  258. .switch{
  259. margin-left: 4px;
  260. }
  261. .time-picker{
  262. margin-left: 4px;
  263. width: 96px;
  264. }
  265. .date-picker{
  266. margin-left: 4px;
  267. width: 120px;
  268. }
  269. .datetime-picker{
  270. margin-left: 4px;
  271. width: 195px;
  272. }
  273. .value{
  274. display: inline-block;
  275. overflow: hidden;
  276. flex:1;
  277. vertical-align: middle;
  278. max-width: 144px;
  279. text-overflow: ellipsis;
  280. word-break: break-all;
  281. white-space: nowrap;
  282. }
  283. &.active{
  284. background-color: @layout-bg-color;
  285. }
  286. }
  287. .icon-down{
  288. vertical-align: middle;
  289. font-size: 12px;
  290. }
  291. }
  292. .search-overlay{
  293. padding: 8px 0px;
  294. text-align: center;
  295. }
  296. .select{
  297. margin-left: 4px;
  298. max-width: 144px;
  299. min-width: 96px;
  300. text-align: left;
  301. }
  302. .operations{
  303. display: flex;
  304. margin: -6px 0;
  305. justify-content: space-between;
  306. .btn{
  307. }
  308. }
  309. }
  310. </style>