/** * 列表页状态保持工具(筛选条件 / 排序 / 页码) * * 解决的问题:列表页 → 编辑/详情页 → 返回,查询条件和页码丢失。 * 两个层次互相配合: * 1. URL query 承载列表状态:进入编辑页时把状态参数带过去,返回时原样带回, * 天然支持刷新、分享、深链(Layout.vue 的 keep-alive 兜不住时也能还原); * 2. Layout.vue 的 :直接保住组件实例, * 表格勾选、滚动位置、弹窗可见性等 DOM 态一起保留,返回零延迟。 * * 页面接入约定(以 ProductManage 为例,见 ListStateMixin): * - data 中必须有 filters / pagination 两个字段(filter 键名即 URL 参数名) * - 混入 SortMixin 时有 sortState 字段,排序状态也会自动同步 */ // 列表页默认分页大小(与各页 el-pagination 默认 page-size 保持一致) export const DEFAULT_PAGE_SIZE = 20 // 允许的每页条数(写进 URL 前校验,避免非法值) export const ALLOWED_PAGE_SIZES = [5, 10, 15, 20, 50, 100] /** * 把列表状态序列化为 URL query 对象(空值 / 默认值省略,保持 URL 简洁) * @param {object} filters 筛选对象,如 { keyword: 'xx', status: 'on_shelf' } * @param {object} pagination 分页对象,如 { page: 3, size: 20 } * @param {Array} sortState SortMixin 的排序状态,如 [{ column: 'name', order: 'asc' }] * @returns {object} 可传给 this.$router.replace / push 的 query */ export function buildListQuery(filters, pagination, sortState) { const query = {} if (filters) { Object.keys(filters).forEach(k => { const v = filters[k] if (v !== undefined && v !== null && v !== '') query[k] = String(v) }) } if (pagination) { if (pagination.page && pagination.page > 1) query.page = String(pagination.page) if (pagination.size && pagination.size !== DEFAULT_PAGE_SIZE) query.size = String(pagination.size) } if (sortState && sortState.length > 0) { query.sort = sortState.map(s => s.column + ':' + s.order).join(',') } return query } /** * 从 URL query 还原列表状态(缺失字段回退到传入的默认值) * @param {object} query this.$route.query * @param {object} defaults { filters: {...}, size: 20 },filters 每键的兜底值取自页面初始值 * @returns {{ filters: object, page: number, size: number, sort: Array }} */ export function parseListQuery(query, defaults) { const d = defaults || {} const baseFilters = d.filters || {} const filters = {} Object.keys(baseFilters).forEach(k => { const v = query[k] filters[k] = (v !== undefined && v !== null && v !== '') ? v : baseFilters[k] }) const sort = [] if (query.sort) { query.sort.split(',').forEach(pair => { const i = pair.indexOf(':') if (i > 0) { const column = pair.slice(0, i) const order = pair.slice(i + 1) if (order === 'asc' || order === 'desc') sort.push({ column, order }) } }) } const page = parseInt(query.page, 10) const size = parseInt(query.size, 10) return { filters, page: page > 0 ? page : 1, size: ALLOWED_PAGE_SIZES.indexOf(size) > -1 ? size : (d.size || DEFAULT_PAGE_SIZE), sort } } /** * 判断两个 query 对象是否等价(键集合与键值都一致), * 用于避免筛选无变化时产生无意义的 router.replace 导航。 */ export function sameQuery(a, b) { const keysA = Object.keys(a) const keysB = Object.keys(b) if (keysA.length !== keysB.length) return false return keysA.every(k => String(a[k] || '') === String(b[k] || '')) }