index.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. <template>
  2. <div class="header-actions">
  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">
  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. hideTable: {
  140. type: Boolean,
  141. default: false,
  142. },
  143. //顶部搜索下拉配置
  144. selectConfig:{
  145. type: Array,
  146. default() {
  147. return []
  148. },
  149. },
  150. // 获取表格元数据时携带的参数
  151. filterParams: {
  152. type: Object,
  153. default() {
  154. return {}
  155. },
  156. },
  157. // 表格加载 loading
  158. loading: {
  159. type: Boolean,
  160. default: false,
  161. },
  162. // 表格名称
  163. title: {
  164. type: String,
  165. default: '',
  166. },
  167. // 表格元数据
  168. source: {
  169. type: Array,
  170. required: true,
  171. default() {
  172. return []
  173. },
  174. },
  175. searchConfig: {
  176. type: Object,
  177. default() {
  178. return {
  179. keyword: '',
  180. }
  181. },
  182. },
  183. // 指定外层容器的渲染组件
  184. containerTag: {
  185. type: String,
  186. default: 'div',
  187. },
  188. // 是否隐藏表头
  189. hideHeader: {
  190. type: Boolean,
  191. default: false,
  192. },
  193. // 是否隐藏分页
  194. hidePagination: {
  195. type: Boolean,
  196. default: false,
  197. },
  198. // 分页配置
  199. pagination: {
  200. type: Object,
  201. default() {
  202. return {}
  203. },
  204. },
  205. // 表格配置文件
  206. config: {
  207. type: Array,
  208. default() {
  209. return []
  210. },
  211. },
  212. // 表头右上方的按钮组
  213. actionList: {
  214. type: Array,
  215. default() {
  216. return [{ text: '', action: () => {} }]
  217. },
  218. },
  219. // element table 原生事件
  220. tableEvents: {
  221. type: Object,
  222. default() {
  223. return {}
  224. },
  225. }
  226. },
  227. setup(props) {
  228. const { proxy } = getCurrentInstance()
  229. const keywrod = ref('')
  230. const selectConfigCopy = computed(()=>{
  231. return props.selectConfig.map((item)=>{
  232. item.labelCopy = item.label
  233. return item
  234. })
  235. })
  236. console.log(selectConfigCopy)
  237. const getAttrsValue = (item) => {
  238. const { attrs } = item
  239. const result = {
  240. ...attrs,
  241. }
  242. delete result.prop
  243. return result
  244. }
  245. const renderTypeList = ref({
  246. render: {},
  247. renderHTML: {
  248. target: 'elements-mapping',
  249. },
  250. renderComponent: {
  251. target: 'components-mapping',
  252. },
  253. })
  254. const getParent = computed(() => {
  255. return proxy.$parent
  256. })
  257. const getPagination = computed(() => {
  258. const params = {
  259. pageNum: 1,
  260. pageSize: 10,
  261. total: 0,
  262. }
  263. return Object.assign({}, params, props.pagination)
  264. })
  265. const getActionList = computed(() => {
  266. return props.actionList
  267. .slice()
  268. .reverse()
  269. .filter((it) => it.text)
  270. })
  271. const getValue = (scope, configItem) => {
  272. const prop = configItem.attrs.prop
  273. const renderName = getMatchRenderFunction(configItem)
  274. const renderObj = renderTypeList.value[renderName]
  275. if (renderObj && isFunction(configItem[renderName])) {
  276. return renderObj.target
  277. ? getRenderValue(scope, configItem, {
  278. name: renderName,
  279. type: 'bind',
  280. })
  281. : getRenderValue(scope, configItem)
  282. }
  283. return scope.row[prop]
  284. }
  285. const getRenderValue = (
  286. scope,
  287. item,
  288. fn = { name: 'render', type: 'call' }
  289. ) => {
  290. const prop = item.attrs.prop
  291. const propValue = prop && scope.row[prop]
  292. scope.row.$index = scope.$index
  293. const args = propValue !== undefined ? propValue : scope.row
  294. return item[fn.name][fn.type](getParent.value, args)
  295. }
  296. // 匹配 render 开头的函数
  297. const getMatchRenderFunction = (obj) => {
  298. return Object.keys(obj).find((key) => {
  299. const matchRender = key.match(/^render.*/)
  300. return matchRender && matchRender[0]
  301. })
  302. }
  303. const isFunction = (fn) => {
  304. return isFn(fn)
  305. }
  306. const searchFn = (val) => {
  307. console.log(keywrod)
  308. proxy.$emit(
  309. 'getList',
  310. Object.assign(props.filterParams, { keyword: keywrod.value })
  311. )
  312. }
  313. const handlePageChange = (val) => {
  314. proxy.$emit(
  315. 'getList',
  316. Object.assign(props.filterParams, { pageNum: val })
  317. )
  318. }
  319. const handleSizeChange = (val) => {
  320. proxy.$emit(
  321. 'getList',
  322. Object.assign(props.filterParams, { pageSize: val })
  323. )
  324. }
  325. const getHeaderActions = (item) => {
  326. return {
  327. ...item.attrs,
  328. }
  329. }
  330. const stopBubbles = (e) => {
  331. const event = e || window.event
  332. if (event && event.stopPropagation) {
  333. event.stopPropagation()
  334. } else {
  335. event.cancelBubble = true
  336. }
  337. }
  338. const handleNativeClick = ({ isBubble }, e,item) => {
  339. // 考虑到单元格内渲染了组件,并且组件自身可能含有点击事件,故添加了阻止冒泡机制
  340. // 若指定 isBubble 为 false,则当前单元格恢复冒泡机制
  341. if (isBoolean(isBubble) && !isBubble) return
  342. stopBubbles(e)
  343. }
  344. //下拉搜索相关
  345. const searchItemSelct = ((item,i) => {
  346. if(item == 'all') {
  347. i.label = '全部'
  348. proxy.$emit(
  349. 'getList',
  350. Object.assign(props.filterParams, { [i.prop]: '' })
  351. )
  352. return
  353. }
  354. i.label = item.label
  355. console.log(item,i)
  356. proxy.$emit(
  357. 'getList',
  358. Object.assign(props.filterParams, { [i.prop]: item.value })
  359. )
  360. })
  361. return {
  362. getParent,
  363. getPagination,
  364. renderTypeList,
  365. getActionList,
  366. getAttrsValue,
  367. getValue,
  368. getRenderValue,
  369. getMatchRenderFunction,
  370. isFunction,
  371. handlePageChange,
  372. handleSizeChange,
  373. getHeaderActions,
  374. stopBubbles,
  375. handleNativeClick,
  376. keywrod,
  377. searchFn,
  378. searchItemSelct,
  379. selectConfigCopy
  380. }
  381. },
  382. })
  383. </script>
  384. <style>
  385. .table-list-container th {
  386. color: #333 !important;
  387. }
  388. .by-table td .el-button+.el-button{
  389. margin-left: 0!important;
  390. }
  391. .by-table td .el-button{
  392. background: none!important;
  393. margin: 0!important;
  394. padding:8px 6px!important;
  395. }
  396. </style>
  397. <style lang="scss" scoped>
  398. .by-search {
  399. display: flex;
  400. justify-content: space-between;
  401. margin-bottom: 10px;
  402. }
  403. .by-dropdown {
  404. width: 106px;
  405. position: relative;
  406. text-align: left;
  407. height: 32px;
  408. z-index: 100;
  409. padding:0 10px;
  410. transition: all 0.5s ease;
  411. cursor: pointer;
  412. line-height: 32px;
  413. .by-dropdown-title {
  414. font-size: 14px;
  415. background-color: #fff;
  416. }
  417. ul {
  418. position: absolute;
  419. left: 0;
  420. right: 0;
  421. top: 32px;
  422. padding: 0;
  423. margin: 0;
  424. z-index: 100;
  425. display: none;
  426. li {
  427. list-style: none;
  428. font-size: 12px;
  429. height: 30px;
  430. padding: 0 10px;
  431. }
  432. li:hover {
  433. background-color: #eff6ff;
  434. color: #0084ff;
  435. }
  436. }
  437. }
  438. .by-dropdown::before {
  439. display: block;
  440. width: 1px;
  441. content: ' ';
  442. position: absolute;
  443. height: 14px;
  444. top: 8px;
  445. background-color: #ddd;
  446. right: 0;
  447. z-index: 101;
  448. }
  449. .by-dropdown:hover {
  450. background: #ffffff;
  451. border-radius: 2px 2px 2px 2px;
  452. opacity: 1;
  453. ul {
  454. background: #ffffff;
  455. box-shadow: 0px 2px 16px 1px rgba(0, 0, 0, 0.06);
  456. border-radius: 2px 2px 2px 2px;
  457. opacity: 1;
  458. display: block;
  459. text-align: left;
  460. }
  461. }
  462. .header-actions {
  463. flex: 1;
  464. overflow-x: auto;
  465. padding: 20px;
  466. background: #fff;
  467. margin-bottom: 20px;
  468. .overflow-box {
  469. :deep() .el-button:nth-child(1) {
  470. margin-left: 10px;
  471. }
  472. }
  473. }
  474. .table-list-container {
  475. background: #fff;
  476. padding: 13px 20px 20px;
  477. .table-pagination {
  478. padding-top: 20px;
  479. }
  480. .header {
  481. display: flex;
  482. padding-bottom: 20px;
  483. }
  484. .el-table {
  485. :deep() th {
  486. font-size: 14px;
  487. }
  488. :deep() td {
  489. font-size: 14px;
  490. }
  491. }
  492. }
  493. </style>