index.vue 14 KB

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