listState.js 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. /**
  2. * 列表页状态保持工具(筛选条件 / 排序 / 页码)
  3. *
  4. * 解决的问题:列表页 → 编辑/详情页 → 返回,查询条件和页码丢失。
  5. * 两个层次互相配合:
  6. * 1. URL query 承载列表状态:进入编辑页时把状态参数带过去,返回时原样带回,
  7. * 天然支持刷新、分享、深链(Layout.vue 的 keep-alive 兜不住时也能还原);
  8. * 2. Layout.vue 的 <keep-alive :include="cachedPages">:直接保住组件实例,
  9. * 表格勾选、滚动位置、弹窗可见性等 DOM 态一起保留,返回零延迟。
  10. *
  11. * 页面接入约定(以 ProductManage 为例,见 ListStateMixin):
  12. * - data 中必须有 filters / pagination 两个字段(filter 键名即 URL 参数名)
  13. * - 混入 SortMixin 时有 sortState 字段,排序状态也会自动同步
  14. */
  15. // 列表页默认分页大小(与各页 el-pagination 默认 page-size 保持一致)
  16. export const DEFAULT_PAGE_SIZE = 20
  17. // 允许的每页条数(写进 URL 前校验,避免非法值)
  18. export const ALLOWED_PAGE_SIZES = [5, 10, 15, 20, 50, 100]
  19. /**
  20. * 把列表状态序列化为 URL query 对象(空值 / 默认值省略,保持 URL 简洁)
  21. * @param {object} filters 筛选对象,如 { keyword: 'xx', status: 'on_shelf' }
  22. * @param {object} pagination 分页对象,如 { page: 3, size: 20 }
  23. * @param {Array} sortState SortMixin 的排序状态,如 [{ column: 'name', order: 'asc' }]
  24. * @returns {object} 可传给 this.$router.replace / push 的 query
  25. */
  26. export function buildListQuery(filters, pagination, sortState) {
  27. const query = {}
  28. if (filters) {
  29. Object.keys(filters).forEach(k => {
  30. const v = filters[k]
  31. if (v !== undefined && v !== null && v !== '') query[k] = String(v)
  32. })
  33. }
  34. if (pagination) {
  35. if (pagination.page && pagination.page > 1) query.page = String(pagination.page)
  36. if (pagination.size && pagination.size !== DEFAULT_PAGE_SIZE) query.size = String(pagination.size)
  37. }
  38. if (sortState && sortState.length > 0) {
  39. query.sort = sortState.map(s => s.column + ':' + s.order).join(',')
  40. }
  41. return query
  42. }
  43. /**
  44. * 从 URL query 还原列表状态(缺失字段回退到传入的默认值)
  45. * @param {object} query this.$route.query
  46. * @param {object} defaults { filters: {...}, size: 20 },filters 每键的兜底值取自页面初始值
  47. * @returns {{ filters: object, page: number, size: number, sort: Array }}
  48. */
  49. export function parseListQuery(query, defaults) {
  50. const d = defaults || {}
  51. const baseFilters = d.filters || {}
  52. const filters = {}
  53. Object.keys(baseFilters).forEach(k => {
  54. const v = query[k]
  55. filters[k] = (v !== undefined && v !== null && v !== '') ? v : baseFilters[k]
  56. })
  57. const sort = []
  58. if (query.sort) {
  59. query.sort.split(',').forEach(pair => {
  60. const i = pair.indexOf(':')
  61. if (i > 0) {
  62. const column = pair.slice(0, i)
  63. const order = pair.slice(i + 1)
  64. if (order === 'asc' || order === 'desc') sort.push({ column, order })
  65. }
  66. })
  67. }
  68. const page = parseInt(query.page, 10)
  69. const size = parseInt(query.size, 10)
  70. return {
  71. filters,
  72. page: page > 0 ? page : 1,
  73. size: ALLOWED_PAGE_SIZES.indexOf(size) > -1 ? size : (d.size || DEFAULT_PAGE_SIZE),
  74. sort
  75. }
  76. }
  77. /**
  78. * 判断两个 query 对象是否等价(键集合与键值都一致),
  79. * 用于避免筛选无变化时产生无意义的 router.replace 导航。
  80. */
  81. export function sameQuery(a, b) {
  82. const keysA = Object.keys(a)
  83. const keysB = Object.keys(b)
  84. if (keysA.length !== keysB.length) return false
  85. return keysA.every(k => String(a[k] || '') === String(b[k] || ''))
  86. }