XTable.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. <template>
  2. <VxeGrid v-bind="getProps" ref="xGrid" :class="`${prefixCls}`" class="xtable-scrollbar">
  3. <template #[item]="data" v-for="item in Object.keys($slots)" :key="item">
  4. <slot :name="item" v-bind="data || {}"></slot>
  5. </template>
  6. </VxeGrid>
  7. </template>
  8. <script setup lang="ts" name="XTable">
  9. import { PropType } from 'vue'
  10. import { SizeType, VxeGridInstance } from 'vxe-table'
  11. import { useAppStore } from '@/store/modules/app'
  12. import { useDesign } from '@/hooks/web/useDesign'
  13. import { XTableProps } from './type'
  14. import { isBoolean, isFunction } from '@/utils/is'
  15. import styleCss from './style/dark.scss?inline'
  16. import download from '@/utils/download'
  17. const { t } = useI18n()
  18. const message = useMessage() // 消息弹窗
  19. const appStore = useAppStore()
  20. const { getPrefixCls } = useDesign()
  21. const prefixCls = getPrefixCls('x-vxe-table')
  22. const attrs = useAttrs()
  23. const emit = defineEmits(['register'])
  24. const removeStyles = () => {
  25. const filename = 'cssTheme'
  26. //移除引入的文件名
  27. const targetelement = 'style'
  28. const targetattr = 'id'
  29. let allsuspects = document.getElementsByTagName(targetelement)
  30. for (let i = allsuspects.length; i >= 0; i--) {
  31. if (
  32. allsuspects[i] &&
  33. allsuspects[i].getAttribute(targetattr) != null &&
  34. allsuspects[i].getAttribute(targetattr)?.indexOf(filename) != -1
  35. ) {
  36. console.log(allsuspects[i], 'node')
  37. allsuspects[i].parentNode?.removeChild(allsuspects[i])
  38. }
  39. }
  40. }
  41. const reImport = () => {
  42. let head = document.getElementsByTagName('head')[0]
  43. let style = document.createElement('style')
  44. style.innerText = styleCss
  45. style.id = 'cssTheme'
  46. head.appendChild(style)
  47. }
  48. watch(
  49. () => appStore.getIsDark,
  50. () => {
  51. if (appStore.getIsDark == true) {
  52. reImport()
  53. }
  54. if (appStore.getIsDark == false) {
  55. removeStyles()
  56. }
  57. },
  58. { immediate: true }
  59. )
  60. const currentSize = computed(() => {
  61. let resSize: SizeType = 'small'
  62. const appsize = appStore.getCurrentSize
  63. switch (appsize) {
  64. case 'large':
  65. resSize = 'medium'
  66. break
  67. case 'default':
  68. resSize = 'small'
  69. break
  70. case 'small':
  71. resSize = 'mini'
  72. break
  73. }
  74. return resSize
  75. })
  76. const props = defineProps({
  77. options: {
  78. type: Object as PropType<XTableProps>,
  79. default: () => {}
  80. }
  81. })
  82. const innerProps = ref<Partial<XTableProps>>()
  83. const getProps = computed(() => {
  84. const options = innerProps.value || props.options
  85. options.size = currentSize as any
  86. options.height = 700
  87. getOptionInitConfig(options)
  88. getColumnsConfig(options)
  89. getProxyConfig(options)
  90. getPageConfig(options)
  91. getToolBarConfig(options)
  92. // console.log(options);
  93. return {
  94. ...options,
  95. ...attrs
  96. }
  97. })
  98. const xGrid = ref<VxeGridInstance>() // 列表 Grid Ref
  99. let proxyForm = false
  100. const getOptionInitConfig = (options: XTableProps) => {
  101. options.size = currentSize as any
  102. options.rowConfig = {
  103. isCurrent: true, // 当鼠标点击行时,是否要高亮当前行
  104. isHover: true // 当鼠标移到行时,是否要高亮当前行
  105. }
  106. }
  107. // columns
  108. const getColumnsConfig = (options: XTableProps) => {
  109. const { allSchemas } = options
  110. if (!allSchemas) return
  111. if (allSchemas.printSchema) {
  112. options.printConfig = {
  113. columns: allSchemas.printSchema
  114. }
  115. }
  116. if (allSchemas.formSchema) {
  117. proxyForm = true
  118. options.formConfig = {
  119. enabled: true,
  120. titleWidth: 100,
  121. titleAlign: 'right',
  122. items: allSchemas.searchSchema
  123. }
  124. }
  125. if (allSchemas.tableSchema) {
  126. options.columns = allSchemas.tableSchema
  127. }
  128. }
  129. // 动态请求
  130. const getProxyConfig = (options: XTableProps) => {
  131. const { getListApi, proxyConfig, data, isList } = options
  132. if (proxyConfig || data) return
  133. if (getListApi && isFunction(getListApi)) {
  134. if (!isList) {
  135. options.proxyConfig = {
  136. seq: true, // 启用动态序号代理(分页之后索引自动计算为当前页的起始序号)
  137. form: proxyForm, // 启用表单代理,当点击表单提交按钮时会自动触发 reload 行为
  138. props: { result: 'list', total: 'total' },
  139. ajax: {
  140. query: async ({ page, form }) => {
  141. let queryParams: any = Object.assign({}, JSON.parse(JSON.stringify(form)))
  142. if (options.params) {
  143. queryParams = Object.assign(queryParams, options.params)
  144. }
  145. if (!options?.treeConfig) {
  146. queryParams.pageSize = page.pageSize
  147. queryParams.pageNo = page.currentPage
  148. }
  149. return new Promise(async (resolve) => {
  150. resolve(await getListApi(queryParams))
  151. })
  152. },
  153. delete: ({ body }) => {
  154. return new Promise(async (resolve) => {
  155. if (options.deleteApi) {
  156. resolve(await options.deleteApi(JSON.stringify(body)))
  157. } else {
  158. Promise.reject('未设置deleteApi')
  159. }
  160. })
  161. },
  162. queryAll: ({ form }) => {
  163. const queryParams = Object.assign({}, JSON.parse(JSON.stringify(form)))
  164. return new Promise(async (resolve) => {
  165. if (options.getAllListApi) {
  166. resolve(await options.getAllListApi(queryParams))
  167. } else {
  168. resolve(await getListApi(queryParams))
  169. }
  170. })
  171. }
  172. }
  173. }
  174. } else {
  175. options.proxyConfig = {
  176. seq: true, // 启用动态序号代理(分页之后索引自动计算为当前页的起始序号)
  177. form: true, // 启用表单代理,当点击表单提交按钮时会自动触发 reload 行为
  178. props: { result: 'data' },
  179. ajax: {
  180. query: ({ form }) => {
  181. let queryParams: any = Object.assign({}, JSON.parse(JSON.stringify(form)))
  182. if (options?.params) {
  183. queryParams = Object.assign(queryParams, options.params)
  184. }
  185. return new Promise(async (resolve) => {
  186. resolve(await getListApi(queryParams))
  187. })
  188. }
  189. }
  190. }
  191. }
  192. }
  193. if (options.exportListApi) {
  194. options.exportConfig = {
  195. filename: options?.exportName,
  196. // 默认选中类型
  197. type: 'csv',
  198. // 自定义数据量列表
  199. modes: options?.getAllListApi ? ['current', 'all'] : ['current'],
  200. columns: options?.allSchemas?.printSchema
  201. }
  202. }
  203. }
  204. // 分页
  205. const getPageConfig = (options: XTableProps) => {
  206. const { pagination, pagerConfig, treeConfig, isList } = options
  207. if (isList) return
  208. if (treeConfig) {
  209. options.treeConfig = options.treeConfig
  210. return
  211. }
  212. if (pagerConfig) return
  213. if (pagination) {
  214. if (isBoolean(pagination)) {
  215. options.pagerConfig = {
  216. border: false, // 带边框
  217. background: false, // 带背景颜色
  218. perfect: false, // 配套的样式
  219. pageSize: 10, // 每页大小
  220. pagerCount: 7, // 显示页码按钮的数量
  221. autoHidden: false, // 当只有一页时自动隐藏
  222. pageSizes: [5, 10, 20, 30, 50, 100], // 每页大小选项列表
  223. layouts: [
  224. 'PrevJump',
  225. 'PrevPage',
  226. 'JumpNumber',
  227. 'NextPage',
  228. 'NextJump',
  229. 'Sizes',
  230. 'FullJump',
  231. 'Total'
  232. ]
  233. }
  234. return
  235. }
  236. options.pagerConfig = pagination
  237. } else {
  238. if (pagination != false) {
  239. options.pagerConfig = {
  240. border: false, // 带边框
  241. background: false, // 带背景颜色
  242. perfect: false, // 配套的样式
  243. pageSize: 10, // 每页大小
  244. pagerCount: 7, // 显示页码按钮的数量
  245. autoHidden: false, // 当只有一页时自动隐藏
  246. pageSizes: [5, 10, 20, 30, 50, 100], // 每页大小选项列表
  247. layouts: [
  248. 'Sizes',
  249. 'PrevJump',
  250. 'PrevPage',
  251. 'Number',
  252. 'NextPage',
  253. 'NextJump',
  254. 'FullJump',
  255. 'Total'
  256. ]
  257. }
  258. }
  259. }
  260. }
  261. // tool bar
  262. const getToolBarConfig = (options: XTableProps) => {
  263. const { toolBar, toolbarConfig, topActionSlots } = options
  264. if (toolbarConfig) return
  265. if (toolBar) {
  266. if (!isBoolean(toolBar)) {
  267. console.info(2)
  268. options.toolbarConfig = toolBar
  269. return
  270. }
  271. } else if (topActionSlots != false) {
  272. options.toolbarConfig = {
  273. slots: { buttons: 'toolbar_buttons' }
  274. }
  275. } else {
  276. options.toolbarConfig = {
  277. enabled: true
  278. }
  279. }
  280. }
  281. // 刷新列表
  282. const reload = () => {
  283. const g = unref(xGrid)
  284. if (!g) {
  285. return
  286. }
  287. g.commitProxy('query')
  288. }
  289. // 删除
  290. const deleteData = async (id: string | number) => {
  291. const g = unref(xGrid)
  292. if (!g) {
  293. return
  294. }
  295. const options = innerProps.value || props.options
  296. if (!options.deleteApi) {
  297. console.error('未传入delListApi')
  298. return
  299. }
  300. return new Promise(async () => {
  301. message.delConfirm().then(async () => {
  302. await (options?.deleteApi && options?.deleteApi(id))
  303. message.success(t('common.delSuccess'))
  304. // 刷新列表
  305. reload()
  306. })
  307. })
  308. }
  309. // 批量删除
  310. const deleteBatch = async () => {
  311. const g = unref(xGrid)
  312. if (!g) {
  313. return
  314. }
  315. const rows = g.getCheckboxRecords() || g.getRadioRecord()
  316. let ids: any[] = []
  317. if (rows.length == 0) {
  318. message.error('请选择数据')
  319. return
  320. } else {
  321. rows.forEach((row) => {
  322. ids.push(row.id)
  323. })
  324. }
  325. const options = innerProps.value || props.options
  326. if (options.deleteListApi) {
  327. return new Promise(async () => {
  328. message.delConfirm().then(async () => {
  329. await (options?.deleteListApi && options?.deleteListApi(ids))
  330. message.success(t('common.delSuccess'))
  331. // 刷新列表
  332. reload()
  333. })
  334. })
  335. } else if (options.deleteApi) {
  336. return new Promise(async () => {
  337. message.delConfirm().then(async () => {
  338. ids.forEach(async (id) => {
  339. await (options?.deleteApi && options?.deleteApi(id))
  340. })
  341. message.success(t('common.delSuccess'))
  342. // 刷新列表
  343. reload()
  344. })
  345. })
  346. } else {
  347. console.error('未传入delListApi')
  348. return
  349. }
  350. }
  351. // 导出
  352. const exportList = async (fileName?: string) => {
  353. const g = unref(xGrid)
  354. if (!g) {
  355. return
  356. }
  357. const options = innerProps.value || props.options
  358. if (!options?.exportListApi) {
  359. console.error('未传入exportListApi')
  360. return
  361. }
  362. const queryParams = Object.assign({}, JSON.parse(JSON.stringify(g.getProxyInfo()?.form)))
  363. message.exportConfirm().then(async () => {
  364. const res = await (options?.exportListApi && options?.exportListApi(queryParams))
  365. download.excel(res as unknown as Blob, fileName ? fileName : 'excel.xls')
  366. })
  367. }
  368. // 获取查询参数
  369. const getSearchData = () => {
  370. const g = unref(xGrid)
  371. if (!g) {
  372. return
  373. }
  374. const queryParams = Object.assign({}, JSON.parse(JSON.stringify(g.getProxyInfo()?.form)))
  375. return queryParams
  376. }
  377. // 获取当前列
  378. const getCurrentColumn = () => {
  379. const g = unref(xGrid)
  380. if (!g) {
  381. return
  382. }
  383. return g.getCurrentColumn()
  384. }
  385. // 获取当前选中列,redio
  386. const getRadioRecord = () => {
  387. const g = unref(xGrid)
  388. if (!g) {
  389. return
  390. }
  391. return g.getRadioRecord(false)
  392. }
  393. // 获取当前选中列,checkbox
  394. const getCheckboxRecords = () => {
  395. const g = unref(xGrid)
  396. if (!g) {
  397. return
  398. }
  399. return g.getCheckboxRecords(false)
  400. }
  401. const setProps = (prop: Partial<XTableProps>) => {
  402. innerProps.value = { ...unref(innerProps), ...prop }
  403. }
  404. defineExpose({ reload, Ref: xGrid, getSearchData, deleteData, exportList })
  405. emit('register', {
  406. reload,
  407. getSearchData,
  408. setProps,
  409. deleteData,
  410. deleteBatch,
  411. exportList,
  412. getCurrentColumn,
  413. getRadioRecord,
  414. getCheckboxRecords
  415. })
  416. </script>
  417. <style lang="scss">
  418. @import './style/index.scss';
  419. </style>