index.vue 13 KB

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