index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. <template>
  2. <div class="header-actions" v-if="getActionList.length != 0">
  3. <div class="overflow-box">
  4. <el-button
  5. v-for="(item, index) in getActionList"
  6. :key="index"
  7. :type="item.type || 'primary'"
  8. :plain="item.plain || false"
  9. v-bind="getHeaderActions(item)"
  10. @click="item.action"
  11. :disabled="item.disabled || false"
  12. >
  13. {{ item.text }}
  14. </el-button>
  15. </div>
  16. </div>
  17. <div class="table-list-container by-table">
  18. <!-- v-if="!hideHeader" -->
  19. <header v-if="false" class="header">
  20. <h2>{{ title }}</h2>
  21. </header>
  22. <div class="by-search" v-if="!hideSearch">
  23. <div style="display: flex;">
  24. <div class="by-dropdown" v-for="i in selectConfigCopy" :key="i.prop" style="margin-right:10px">
  25. <div class="by-dropdown-title">
  26. {{ i.label }}<i class="el-icon-caret-bottom el-icon--right"></i>
  27. </div>
  28. <ul class="by-dropdown-lists">
  29. <li @click="searchItemSelct('all',i)">全部</li>
  30. <li v-for="j in i.data" :key="j.value" @click="searchItemSelct(j,i)">{{ j.label }}</li>
  31. </ul>
  32. </div>
  33. </div>
  34. <div style="display: flex">
  35. <el-input
  36. placeholder="请输入关键字"
  37. suffix-icon="search"
  38. size="mini"
  39. v-model="keywrod"
  40. @keyup.enter="searchFn"
  41. >
  42. </el-input>
  43. <el-button
  44. type="primary"
  45. style="margin-left: 10px"
  46. size="default"
  47. @click="searchFn"
  48. >搜索</el-button
  49. >
  50. <div class="more-icon"><i class="el-icon-wind-power"></i></div>
  51. <div class="more-icon">
  52. <i class="el-icon-notebook-2"></i>
  53. </div>
  54. </div>
  55. </div>
  56. <component :is="containerTag">
  57. <div class="filter-form-container">
  58. <slot />
  59. </div>
  60. <el-table
  61. ref="hocElTable"
  62. v-loading="loading"
  63. :data="source"
  64. v-if="!hideTable"
  65. style="width: 100%"
  66. v-bind="$attrs"
  67. v-on="tableEvents"
  68. row-key="id"
  69. :tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
  70. >
  71. <el-table-column
  72. v-for="(item, index) in config"
  73. :key="index"
  74. v-bind="getAttrsValue(item)"
  75. :type="item.type || ''"
  76. >
  77. <template #default="scope" v-if="!item.type">
  78. <slot :name="item.attrs.slot" :item="scope.row" v-if="item.attrs.slot">
  79. 插槽占位符
  80. </slot>
  81. <div v-else-if="isFunction(getValue(scope, item))">
  82. <component
  83. :is="
  84. renderTypeList[getMatchRenderFunction(item)]
  85. .target
  86. "
  87. :cell-list="getValue(scope, item)()"
  88. :row="scope.row"
  89. :parent="getParent"
  90. @click="
  91. ($event) => {
  92. handleNativeClick(
  93. getAttrsValue(item),
  94. $event,
  95. item
  96. )
  97. }
  98. "
  99. />
  100. </div>
  101. <div v-else>
  102. {{ getValue(scope, item) }}
  103. </div>
  104. </template>
  105. </el-table-column>
  106. </el-table>
  107. <el-row
  108. v-if="!hidePagination"
  109. class="table-pagination"
  110. justify="end"
  111. type="flex"
  112. >
  113. <el-pagination
  114. background
  115. layout="total, sizes, prev, pager, next, jumper"
  116. :current-page="getPagination.pageNum"
  117. :page-size="getPagination.pageSize"
  118. :total="getPagination.total"
  119. @size-change="handleSizeChange"
  120. @current-change="handlePageChange"
  121. />
  122. </el-row>
  123. </component>
  124. </div>
  125. </template>
  126. <script>
  127. import { isFunction as isFn, isBoolean } from './type'
  128. import ElementsMapping from './ElementsMapping'
  129. import ComponentsMapping from './ComponentsMapping'
  130. import { computed, defineComponent, getCurrentInstance, ref } from 'vue'
  131. import expand from './expand'
  132. export default defineComponent({
  133. name: 'Table',
  134. components: {
  135. ElementsMapping,
  136. ComponentsMapping,
  137. },
  138. props: {
  139. hideSearch: {
  140. type: Boolean,
  141. default: false,
  142. },
  143. hideTable: {
  144. type: Boolean,
  145. default: false,
  146. },
  147. //顶部搜索下拉配置
  148. selectConfig:{
  149. type: Array,
  150. default() {
  151. return []
  152. },
  153. },
  154. // 获取表格元数据时携带的参数
  155. filterParams: {
  156. type: Object,
  157. default() {
  158. return {}
  159. },
  160. },
  161. // 表格加载 loading
  162. loading: {
  163. type: Boolean,
  164. default: false,
  165. },
  166. // 表格名称
  167. title: {
  168. type: String,
  169. default: '',
  170. },
  171. // 表格元数据
  172. source: {
  173. type: Array,
  174. required: true,
  175. default() {
  176. return []
  177. },
  178. },
  179. searchConfig: {
  180. type: Object,
  181. default() {
  182. return {
  183. keyword: '',
  184. }
  185. },
  186. },
  187. // 指定外层容器的渲染组件
  188. containerTag: {
  189. type: String,
  190. default: 'div',
  191. },
  192. // 是否隐藏表头
  193. hideHeader: {
  194. type: Boolean,
  195. default: false,
  196. },
  197. // 是否隐藏分页
  198. hidePagination: {
  199. type: Boolean,
  200. default: false,
  201. },
  202. // 分页配置
  203. pagination: {
  204. type: Object,
  205. default() {
  206. return {}
  207. },
  208. },
  209. // 表格配置文件
  210. config: {
  211. type: Array,
  212. default() {
  213. return []
  214. },
  215. },
  216. // 表头右上方的按钮组
  217. actionList: {
  218. type: Array,
  219. default() {
  220. return [{ text: '', action: () => {} }]
  221. },
  222. },
  223. // element table 原生事件
  224. tableEvents: {
  225. type: Object,
  226. default() {
  227. return {}
  228. },
  229. },
  230. searchKey: {
  231. type: String,
  232. default: 'keyword',
  233. },
  234. },
  235. setup(props) {
  236. const { proxy } = getCurrentInstance()
  237. const keywrod = ref('')
  238. const selectConfigCopy = computed(()=>{
  239. return props.selectConfig.map((item)=>{
  240. item.labelCopy = item.label
  241. return item
  242. })
  243. })
  244. console.log(selectConfigCopy)
  245. const getAttrsValue = (item) => {
  246. const { attrs } = item
  247. const result = {
  248. ...attrs,
  249. }
  250. delete result.prop
  251. return result
  252. }
  253. const renderTypeList = ref({
  254. render: {},
  255. renderHTML: {
  256. target: 'elements-mapping',
  257. },
  258. renderComponent: {
  259. target: 'components-mapping',
  260. },
  261. })
  262. const getParent = computed(() => {
  263. return proxy.$parent
  264. })
  265. const getPagination = computed(() => {
  266. const params = {
  267. pageNum: 1,
  268. pageSize: 10,
  269. total: 0,
  270. }
  271. return Object.assign({}, params, props.pagination)
  272. })
  273. const getActionList = computed(() => {
  274. return props.actionList
  275. .slice()
  276. .reverse()
  277. .filter((it) => it.text)
  278. })
  279. const getValue = (scope, configItem) => {
  280. const prop = configItem.attrs.prop
  281. const renderName = getMatchRenderFunction(configItem)
  282. const renderObj = renderTypeList.value[renderName]
  283. if (renderObj && isFunction(configItem[renderName])) {
  284. return renderObj.target
  285. ? getRenderValue(scope, configItem, {
  286. name: renderName,
  287. type: 'bind',
  288. })
  289. : getRenderValue(scope, configItem)
  290. }
  291. return scope.row[prop]
  292. }
  293. const getRenderValue = (
  294. scope,
  295. item,
  296. fn = { name: 'render', type: 'call' }
  297. ) => {
  298. const prop = item.attrs.prop
  299. const propValue = prop && scope.row[prop]
  300. scope.row.$index = scope.$index
  301. const args = propValue !== undefined ? propValue : scope.row
  302. return item[fn.name][fn.type](getParent.value, args)
  303. }
  304. // 匹配 render 开头的函数
  305. const getMatchRenderFunction = (obj) => {
  306. return Object.keys(obj).find((key) => {
  307. const matchRender = key.match(/^render.*/)
  308. return matchRender && matchRender[0]
  309. })
  310. }
  311. const isFunction = (fn) => {
  312. return isFn(fn)
  313. }
  314. const searchFn = (val) => {
  315. console.log(props)
  316. proxy.$emit(
  317. 'getList',
  318. Object.assign(props.filterParams, { [props.searchKey]: keywrod.value })
  319. )
  320. }
  321. const handlePageChange = (val) => {
  322. proxy.$emit(
  323. 'getList',
  324. Object.assign(props.filterParams, { pageNum: val })
  325. )
  326. }
  327. const handleSizeChange = (val) => {
  328. proxy.$emit(
  329. 'getList',
  330. Object.assign(props.filterParams, { pageSize: val })
  331. )
  332. }
  333. const getHeaderActions = (item) => {
  334. return {
  335. ...item.attrs,
  336. }
  337. }
  338. const stopBubbles = (e) => {
  339. const event = e || window.event
  340. if (event && event.stopPropagation) {
  341. event.stopPropagation()
  342. } else {
  343. event.cancelBubble = true
  344. }
  345. }
  346. const handleNativeClick = ({ isBubble }, e,item) => {
  347. // 考虑到单元格内渲染了组件,并且组件自身可能含有点击事件,故添加了阻止冒泡机制
  348. // 若指定 isBubble 为 false,则当前单元格恢复冒泡机制
  349. if (isBoolean(isBubble) && !isBubble) return
  350. stopBubbles(e)
  351. }
  352. //下拉搜索相关
  353. const searchItemSelct = ((item,i) => {
  354. if(item == 'all') {
  355. i.label = '全部'
  356. proxy.$emit(
  357. 'getList',
  358. Object.assign(props.filterParams, { [i.prop]: '' })
  359. )
  360. return
  361. }
  362. i.label = item.label
  363. console.log(item,i)
  364. proxy.$emit(
  365. 'getList',
  366. Object.assign(props.filterParams, { [i.prop]: item.value })
  367. )
  368. })
  369. return {
  370. getParent,
  371. getPagination,
  372. renderTypeList,
  373. getActionList,
  374. getAttrsValue,
  375. getValue,
  376. getRenderValue,
  377. getMatchRenderFunction,
  378. isFunction,
  379. handlePageChange,
  380. handleSizeChange,
  381. getHeaderActions,
  382. stopBubbles,
  383. handleNativeClick,
  384. keywrod,
  385. searchFn,
  386. searchItemSelct,
  387. selectConfigCopy
  388. }
  389. },
  390. })
  391. </script>
  392. <style>
  393. .table-list-container th {
  394. color: #333 !important;
  395. }
  396. .by-table td .el-button+.el-button{
  397. margin-left: 0!important;
  398. }
  399. .by-table td .el-button{
  400. background: none!important;
  401. margin: 0!important;
  402. padding:8px 6px!important;
  403. }
  404. </style>
  405. <style lang="scss" scoped>
  406. .by-search {
  407. display: flex;
  408. justify-content: space-between;
  409. margin-bottom: 10px;
  410. }
  411. .by-dropdown {
  412. width: 106px;
  413. position: relative;
  414. text-align: left;
  415. height: 32px;
  416. z-index: 100;
  417. padding:0 10px;
  418. transition: all 0.5s ease;
  419. cursor: pointer;
  420. line-height: 32px;
  421. .by-dropdown-title {
  422. font-size: 14px;
  423. background-color: #fff;
  424. }
  425. ul {
  426. position: absolute;
  427. left: 0;
  428. right: 0;
  429. top: 32px;
  430. padding: 0;
  431. margin: 0;
  432. z-index: 100;
  433. display: none;
  434. li {
  435. list-style: none;
  436. font-size: 12px;
  437. height: 30px;
  438. padding: 0 10px;
  439. }
  440. li:hover {
  441. background-color: #eff6ff;
  442. color: #0084ff;
  443. }
  444. }
  445. }
  446. .by-dropdown::before {
  447. display: block;
  448. width: 1px;
  449. content: ' ';
  450. position: absolute;
  451. height: 14px;
  452. top: 8px;
  453. background-color: #ddd;
  454. right: 0;
  455. z-index: 101;
  456. }
  457. .by-dropdown:hover {
  458. background: #ffffff;
  459. border-radius: 2px 2px 2px 2px;
  460. opacity: 1;
  461. ul {
  462. background: #ffffff;
  463. box-shadow: 0px 2px 16px 1px rgba(0, 0, 0, 0.06);
  464. border-radius: 2px 2px 2px 2px;
  465. opacity: 1;
  466. display: block;
  467. text-align: left;
  468. }
  469. }
  470. .header-actions {
  471. flex: 1;
  472. overflow-x: auto;
  473. padding: 20px;
  474. background: #fff;
  475. margin-bottom: 20px;
  476. .overflow-box {
  477. :deep() .el-button:nth-child(1) {
  478. margin-left: 10px;
  479. }
  480. }
  481. }
  482. .table-list-container {
  483. background: #fff;
  484. padding: 13px 20px 20px;
  485. .table-pagination {
  486. padding-top: 20px;
  487. }
  488. .header {
  489. display: flex;
  490. padding-bottom: 20px;
  491. }
  492. .el-table {
  493. :deep() th {
  494. font-size: 14px;
  495. }
  496. :deep() td {
  497. font-size: 14px;
  498. }
  499. }
  500. }
  501. </style>