index.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. <template>
  2. <div class="top-right-btn" :style="style">
  3. <el-row>
  4. <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
  5. <el-button circle icon="Search" @click="toggleSearch()" />
  6. </el-tooltip>
  7. <el-tooltip class="item" effect="dark" content="刷新" placement="top">
  8. <el-button circle icon="Refresh" @click="refresh()" />
  9. </el-tooltip>
  10. <el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="Object.keys(columns).length > 0">
  11. <el-button circle icon="Menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
  12. <el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'">
  13. <el-button circle icon="Menu" />
  14. <template #dropdown>
  15. <el-dropdown-menu>
  16. <!-- 全选/反选 按钮 -->
  17. <el-dropdown-item>
  18. <el-checkbox :indeterminate="isIndeterminate" v-model="isChecked" @change="toggleCheckAll"> 列展示 </el-checkbox>
  19. </el-dropdown-item>
  20. <div class="check-line"></div>
  21. <template v-for="(item, key) in columns" :key="item.key">
  22. <el-dropdown-item>
  23. <el-checkbox v-model="item.visible" @change="checkboxChange($event, key)" :label="item.label" />
  24. </el-dropdown-item>
  25. </template>
  26. </el-dropdown-menu>
  27. </template>
  28. </el-dropdown>
  29. </el-tooltip>
  30. </el-row>
  31. <el-dialog :title="title" v-model="open" append-to-body>
  32. <el-transfer
  33. :titles="['显示', '隐藏']"
  34. v-model="value"
  35. :data="transferData"
  36. @change="dataChange"
  37. ></el-transfer>
  38. </el-dialog>
  39. </div>
  40. </template>
  41. <script setup>
  42. import cache from '@/plugins/cache'
  43. const props = defineProps({
  44. /* 是否显示检索条件 */
  45. showSearch: {
  46. type: Boolean,
  47. default: true
  48. },
  49. /* 显隐列信息(数组格式、对象格式) */
  50. columns: {
  51. type: [Array, Object],
  52. default: () => ({})
  53. },
  54. /* 是否显示检索图标 */
  55. search: {
  56. type: Boolean,
  57. default: true
  58. },
  59. /* 显隐列类型(transfer穿梭框、checkbox复选框) */
  60. showColumnsType: {
  61. type: String,
  62. default: "checkbox"
  63. },
  64. /* 右外边距 */
  65. gutter: {
  66. type: Number,
  67. default: 10
  68. },
  69. /* 列显隐状态记忆的 localStorage key(传入则启用记忆,不传则不记忆) */
  70. storageKey: {
  71. type: String,
  72. default: ""
  73. }
  74. })
  75. const emits = defineEmits(['update:showSearch', 'queryTable'])
  76. // 显隐数据
  77. const value = ref([])
  78. // 弹出层标题
  79. const title = ref("显示/隐藏")
  80. // 是否显示弹出层
  81. const open = ref(false)
  82. const style = computed(() => {
  83. const ret = {}
  84. if (props.gutter) {
  85. ret.marginRight = `${props.gutter / 2}px`
  86. }
  87. return ret
  88. })
  89. // 是否全选/半选 状态
  90. const isChecked = computed({
  91. get: () => Array.isArray(props.columns) ? props.columns.every(col => col.visible) : Object.values(props.columns).every((col) => col.visible),
  92. set: () => {}
  93. })
  94. const isIndeterminate = computed(() => Array.isArray(props.columns) ? props.columns.some((col) => col.visible) && !isChecked.value : Object.values(props.columns).some((col) => col.visible) && !isChecked.value)
  95. const transferData = computed(() => Array.isArray(props.columns) ? props.columns.map((item, index) => ({ key: index, label: item.label })) : Object.keys(props.columns).map((key, index) => ({ key: index, label: props.columns[key].label })))
  96. // 搜索
  97. const { proxy } = getCurrentInstance()
  98. function toggleSearch() {
  99. let el = proxy.$el
  100. let formEl = null
  101. while ((el = el.parentElement) && el !== document.body) {
  102. if ((formEl = el.querySelector('.el-form'))) break
  103. }
  104. if (!formEl) return emits('update:showSearch', !props.showSearch)
  105. animateSearch(formEl, props.showSearch)
  106. }
  107. function animateSearch(el, isHide) {
  108. const DURATION = 260
  109. const TRANSITION = 'max-height 0.25s ease, opacity 0.2s ease'
  110. const clear = () => Object.assign(el.style, { transition: '', maxHeight: '', opacity: '', overflow: '' })
  111. Object.assign(el.style, { overflow: 'hidden', transition: '' })
  112. if (isHide) {
  113. Object.assign(el.style, { maxHeight: el.scrollHeight + 'px', opacity: '1', transition: TRANSITION })
  114. requestAnimationFrame(() => Object.assign(el.style, { maxHeight: '0', opacity: '0' }))
  115. setTimeout(() => { emits('update:showSearch', false); clear() }, DURATION)
  116. } else {
  117. emits('update:showSearch', true)
  118. nextTick(() => {
  119. Object.assign(el.style, { maxHeight: '0', opacity: '0' })
  120. requestAnimationFrame(() => requestAnimationFrame(() => {
  121. Object.assign(el.style, { transition: TRANSITION, maxHeight: el.scrollHeight + 'px', opacity: '1' })
  122. }))
  123. setTimeout(clear, DURATION)
  124. })
  125. }
  126. }
  127. // 刷新
  128. function refresh() {
  129. emits("queryTable")
  130. }
  131. // 右侧列表元素变化
  132. function dataChange(data) {
  133. if (Array.isArray(props.columns)) {
  134. for (let item in props.columns) {
  135. const key = props.columns[item].key
  136. props.columns[item].visible = !data.includes(key)
  137. }
  138. } else {
  139. Object.keys(props.columns).forEach((key, index) => {
  140. props.columns[key].visible = !data.includes(index)
  141. })
  142. }
  143. saveStorage()
  144. }
  145. // 打开显隐列dialog
  146. function showColumn() {
  147. open.value = true
  148. }
  149. // 如果传入了 storageKey,从 localStorage 恢复列显隐状态
  150. if (props.storageKey) {
  151. try {
  152. const saved = cache.local.getJSON(props.storageKey)
  153. if (saved && typeof saved === 'object') {
  154. if (Array.isArray(props.columns)) {
  155. props.columns.forEach((col, index) => {
  156. if (saved[index] !== undefined) col.visible = saved[index]
  157. })
  158. } else {
  159. Object.keys(props.columns).forEach(key => {
  160. if (saved[key] !== undefined) props.columns[key].visible = saved[key]
  161. })
  162. }
  163. }
  164. } catch (e) {}
  165. }
  166. if (props.showColumnsType == "transfer") {
  167. // transfer穿梭显隐列初始默认隐藏列
  168. if (Array.isArray(props.columns)) {
  169. for (let item in props.columns) {
  170. if (props.columns[item].visible === false) {
  171. value.value.push(parseInt(item))
  172. }
  173. }
  174. } else {
  175. Object.keys(props.columns).forEach((key, index) => {
  176. if (props.columns[key].visible === false) {
  177. value.value.push(index)
  178. }
  179. })
  180. }
  181. }
  182. // 单勾选
  183. function checkboxChange(event, key) {
  184. if (Array.isArray(props.columns)) {
  185. props.columns.filter(item => item.key == key)[0].visible = event
  186. } else {
  187. props.columns[key].visible = event
  188. }
  189. saveStorage()
  190. }
  191. // 切换全选/反选
  192. function toggleCheckAll() {
  193. const newValue = !isChecked.value
  194. if (Array.isArray(props.columns)) {
  195. props.columns.forEach((col) => (col.visible = newValue))
  196. } else {
  197. Object.values(props.columns).forEach((col) => (col.visible = newValue))
  198. }
  199. saveStorage()
  200. }
  201. // 将当前列显隐状态持久化到 localStorage
  202. function saveStorage() {
  203. if (!props.storageKey) return
  204. try {
  205. let state = {}
  206. if (Array.isArray(props.columns)) {
  207. props.columns.forEach((col, index) => { state[index] = col.visible })
  208. } else {
  209. Object.keys(props.columns).forEach(key => { state[key] = props.columns[key].visible })
  210. }
  211. cache.local.setJSON(props.storageKey, state)
  212. } catch (e) {}
  213. }
  214. </script>
  215. <style lang='scss' scoped>
  216. :deep(.el-transfer__button) {
  217. border-radius: 50%;
  218. display: block;
  219. margin-left: 0px;
  220. }
  221. :deep(.el-transfer__button:first-child) {
  222. margin-bottom: 10px;
  223. }
  224. :deep(.el-dropdown-menu__item) {
  225. line-height: 30px;
  226. padding: 0 17px;
  227. }
  228. .check-line {
  229. width: 90%;
  230. height: 1px;
  231. background-color: #ccc;
  232. margin: 3px auto;
  233. }
  234. </style>