├── .circleci └── config.yml ├── .eslintrc.cjs ├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── .vscode └── extensions.json ├── CHANGELOG-CN.md ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── App.vue ├── ExamplesIndex.vue └── main.js ├── favicon.ico ├── index.html ├── jsconfig.json ├── package.json ├── pnpm-lock.yaml ├── src ├── PaginationBar.jsx ├── PaginationCore.jsx ├── __test__ │ ├── PaginationComponents.jsx │ └── v-page.spec.js ├── constants.js ├── helper.js ├── index.js ├── language.js └── page.sass ├── tsconfig.json ├── types └── index.d.ts └── vite.config.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Javascript Node CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-javascript/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | branches: 9 | only: master 10 | 11 | docker: 12 | # specify the version you desire here 13 | # - image: circleci/node:12.22-browsers 14 | - image: cimg/node:16.17.0-browsers 15 | 16 | # Specify service dependencies here if necessary 17 | # CircleCI maintains a library of pre-built images 18 | # documented at https://circleci.com/docs/2.0/circleci-images/ 19 | # - image: circleci/mongo:3.4.4 20 | 21 | working_directory: ~/repo 22 | 23 | steps: 24 | - checkout 25 | 26 | # Download and cache dependencies 27 | - restore_cache: 28 | keys: 29 | - v1-dependencies-{{ checksum "package.json" }} 30 | # fallback to using the latest cache if no exact match is found 31 | - v1-dependencies- 32 | 33 | - run: npm install 34 | # - run: npm rebuild node-sass 35 | - run: sudo npm install -g codecov 36 | 37 | - save_cache: 38 | paths: 39 | - node_modules 40 | key: v1-dependencies-{{ checksum "package.json" }} 41 | 42 | # run tests! 43 | - run: npm run coverage 44 | - run: codecov 45 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | const path = require('node:path') 5 | const createAliasSetting = require('@vue/eslint-config-standard/createAliasSetting') 6 | 7 | module.exports = { 8 | root: true, 9 | extends: [ 10 | 'plugin:vue/vue3-strongly-recommended', 11 | '@vue/eslint-config-standard' 12 | ], 13 | parserOptions: { 14 | ecmaVersion: 'latest', 15 | ecmaFeatures: { 16 | jsx: true 17 | } 18 | }, 19 | ignorePatterns: [ 20 | 'types/' 21 | ], 22 | settings: { 23 | ...createAliasSetting({ 24 | '@': `${path.resolve(__dirname, './src')}` 25 | }) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | - release-test 11 | # release: 12 | # types: [published] 13 | 14 | jobs: 15 | build: 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: pnpm/action-setup@v4 20 | - uses: actions/setup-node@v4 21 | with: 22 | node-version: '20.x' 23 | cache: pnpm 24 | 25 | - run: pnpm i --frozen-lockfile 26 | - run: pnpm lint 27 | - run: pnpm coverage 28 | 29 | - name: Upload coverage to Codecov 30 | uses: codecov/codecov-action@v5 31 | with: 32 | fail_ci_if_error: true # optional (default = false) 33 | files: ./coverage1.xml,./coverage2.xml # optional 34 | flags: unittests # optional 35 | name: codecov-umbrella # optional 36 | token: ${{ secrets.CODECOV_TOKEN }} 37 | verbose: true # optional (default = false) 38 | 39 | # publish-npm: 40 | # needs: build 41 | # runs-on: ubuntu-latest 42 | # steps: 43 | # - uses: actions/checkout@v4 44 | # - uses: actions/setup-node@v4 45 | # with: 46 | # node-version: 20 47 | # registry-url: https://registry.npmjs.org/ 48 | # - run: npm ci 49 | # - run: npm publish --provenance --access public 50 | # env: 51 | # NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | .nyc_output/ 16 | coverage/ 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["Vue.volar"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG-CN.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | 英文 changelog 内容请访问 [CHANGELOG](CHANGELOG.md) 4 | 5 | ## [3.3.0](https://github.com/TerryZ/v-page/compare/v3.2.1...v3.3.0) (2024-12-25) 6 | 7 | ### 新特性 8 | 9 | - 分页栏面板均以独立组件形式提供,可进行自由组合排列 10 | - 移除 `pageSizeOptions`、`info`、`first` 与 `last` props 11 | 12 | ## [3.2.1](https://github.com/TerryZ/v-page/compare/v3.2.0...v3.2.1) (2024-10-27) 13 | 14 | ### 问题修复 15 | 16 | - 每页记录数列表变更选中项目,有概率出现选中项目与 `pageSize` 值不一致 17 | - 圆形按钮问题修正 18 | - 样式避免被污染 19 | 20 | ## [3.2.0](https://github.com/TerryZ/v-page/compare/v3.1.0...v3.2.0) (2024-10-24) 21 | 22 | - 新增 `circle` prop,用于设置是否应用圆形按钮样式 23 | - `pageSize` 值添加至每页记录数列表 24 | - 每页记录数列表中的项目匹配 `pageSize` 值时,自动选中该项目 25 | - 精简多语言文本内容 26 | - 移除暴露的数据项与函数 27 | 28 | ## [3.1.0](https://github.com/TerryZ/v-page/compare/v3.0.0...v3.1.0) (2024-06-23) 29 | 30 | - 新增 `pageSizeOptions` prop,用于设置是否显示页数列表 31 | - `pageSizeMenu` 移除 `boolean` 类型,仅作为页数列表设置项 32 | - 新增 `pageSize` prop,指定每页项目个数 33 | - 新增 `hideOnSinglePage` prop,用于设置是否在只有一页时隐藏分页栏 34 | - `change` 事件输出数据中新增 `totalPage` 总页数属性 35 | 36 | ## [3.0.0](https://github.com/TerryZ/v-page/compare/v3.0.0-beta.4...v3.0.0) (2023-09-11) 37 | 38 | - 完善 `.d.ts` 文档 `default slot` 内容 39 | - 优化组件样式 40 | - 组件默认语言从 `cn` 修改为 `en` 41 | - 组件导出的模块名称从 `Page` 修改为 `PaginationBar` 42 | 43 | ## [3.0.0-beta.4](https://github.com/TerryZ/v-page/compare/v3.0.0-beta.3...v3.0.0-beta.4) (2023-09-10) 44 | 45 | - 更新 `.d.ts` 文档 46 | - 更新依赖库版本 47 | 48 | ## [3.0.0-beta.3](https://github.com/TerryZ/v-page/compare/v3.0.0-beta.2...v3.0.0-beta.3) (2023-09-09) 49 | 50 | - 更新 package.json 的 `.d.ts` 相关设置 51 | 52 | ## [3.0.0-beta.2](https://github.com/TerryZ/v-page/compare/v3.0.0-beta.1...v3.0.0-beta.2) (2023-01-25) 53 | 54 | - 升级依赖库 55 | 56 | ## [3.0.0-beta.1](https://github.com/TerryZ/v-page/compare/v2.1.0...v3.0.0-beta.1) (2022-08-26) 57 | 58 | ### 新特性 59 | 60 | - 使用 vue3 **composition api** 重构 `v-page` 61 | - 工具链从 `webpack` 更换为 `vite` 62 | - 单元测试库从 `mocha` 更换为 `vitest` 63 | - `page-change` 事件更名为 `change` 64 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Please refer to [CHANGELOG-CN](CHANGELOG-CN.md) for Chinese changelog 4 | 5 | ## [3.3.0](https://github.com/TerryZ/v-page/compare/v3.2.1...v3.3.0) (2024-12-25) 6 | 7 | ### Features 8 | 9 | - The pagination panels are provided as independent components and can be freely combined and arranged 10 | - Removed `pageSizeOptions`, `info`, `first` and `last` props 11 | 12 | ## [3.2.1](https://github.com/TerryZ/v-page/compare/v3.2.0...v3.2.1) (2024-10-27) 13 | 14 | ### Bug Fixes 15 | 16 | - When changing the selected item in the list of records per page, there is a chance that the selected item will be inconsistent with the `pageSize` value 17 | - Fixed round page number button style issue 18 | - Avoid component style contamination 19 | 20 | ## [3.2.0](https://github.com/TerryZ/v-page/compare/v3.1.0...v3.2.0) (2024-10-24) 21 | 22 | - Added `circle` prop to set whether to apply round button style 23 | - Added `pageSize` value to the number of records per page list 24 | - Automatically select an item in the number of records per page list when it matches the `pageSize` value 25 | - Streamlined multilingual text content 26 | - Removed exposed data items and functions 27 | 28 | ## [3.1.0](https://github.com/TerryZ/v-page/compare/v3.0.0...v3.1.0) (2024-06-23) 29 | 30 | - Add `pageSizeOptions` prop, used to show page size list 31 | - `pageSizeMenu` prop remove `boolean` type, only use for set page size list items 32 | - Add `pageSize` prop, set the item number of per page 33 | - Add `hideOnSinglePage` prop, hide pagination when have only one page 34 | - `change` event output data add `totalPage` property for the number of total pages 35 | 36 | ## [3.0.0](https://github.com/TerryZ/v-page/compare/v3.0.0-beta.4...v3.0.0) (2023-09-11) 37 | 38 | - Complete `.d.ts` document `default slot` content 39 | - Optimize component styles 40 | - The component default language is changed from `cn` to `en` 41 | - The module name exported by the component is changed from `Page` to `PaginationBar` 42 | 43 | ## [3.0.0-beta.4](https://github.com/TerryZ/v-page/compare/v3.0.0-beta.3...v3.0.0-beta.4) (2023-09-10) 44 | 45 | - Update `.d.ts` document 46 | - Upgrade dependency libraries 47 | 48 | ## [3.0.0-beta.3](https://github.com/TerryZ/v-page/compare/v3.0.0-beta.2...v3.0.0-beta.3) (2023-09-09) 49 | 50 | - Update the `.d.ts` related settings of package.json 51 | 52 | ## [3.0.0-beta.2](https://github.com/TerryZ/v-page/compare/v3.0.0-beta.1...v3.0.0-beta.2) (2023-01-25) 53 | 54 | - Upgrade dependency libraries 55 | 56 | ## [3.0.0-beta.1](https://github.com/TerryZ/v-page/compare/v2.1.0...v3.0.0-beta.1) (2022-08-26) 57 | 58 | ### Features 59 | 60 | - Refactor `v-page` with vue3 **composition api** 61 | - Change module bundler library from `webpack` to `vite` 62 | - Change unit test library from `mocha` to `vitest` 63 | - `page-change` event rename to `change` 64 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Terry Zeng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [v-page](https://terryz.github.io/vue/#/page) · [![CircleCI](https://dl.circleci.com/status-badge/img/gh/TerryZ/v-page/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/TerryZ/v-page/tree/master) [![code coverage](https://codecov.io/gh/TerryZ/v-page/branch/master/graph/badge.svg)](https://codecov.io/gh/TerryZ/v-page) [![npm version](https://img.shields.io/npm/v/v-page.svg)](https://www.npmjs.com/package/v-page) [![license](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://mit-license.org/) [![npm download](https://img.shields.io/npm/dy/v-page.svg)](https://www.npmjs.com/package/v-page) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 2 | 3 | A simple pagination bar for vue3, including size Menu, i18n support features 4 | 5 | v-page 6 | 7 | If you are using vue `2.x` version, please use [v-page 2.x](https://github.com/TerryZ/v-page/tree/dev-vue-2) version instead 8 | 9 | 15 | 16 | ## Examples and Documentation 17 | 18 | Documentation and examples and please visit below sites 19 | 20 | - [github-pages](https://terryz.github.io/docs-vue3/page/) 21 | 22 | ## Installation 23 | 24 | [![https://nodei.co/npm/v-page.png?downloads=true&downloadRank=true&stars=true](https://nodei.co/npm/v-page.png?downloads=true&downloadRank=true&stars=true)](https://www.npmjs.com/package/v-page) 25 | 26 | ```sh 27 | # npm 28 | npm i v-page 29 | # yarn 30 | yarn add v-page 31 | # pnpm 32 | pnpm add v-page 33 | ``` 34 | 35 | Include and install plugin in your `main.js` file 36 | 37 | ```js 38 | import { createApp } from 'vue' 39 | import App from './app.vue' 40 | import { PaginationBar } from 'v-page' 41 | 42 | const app = createApp(App) 43 | // install component globally 44 | app.use(PaginationBar, { 45 | // globally config options 46 | }) 47 | app.mount('#app') 48 | ``` 49 | 50 | Use `v-page` as a locally component 51 | 52 | ```vue 53 | 56 | 57 | 60 | ``` 61 | 62 | ## Usage 63 | 64 | ```vue 65 | 80 | 81 | 103 | ``` 104 | -------------------------------------------------------------------------------- /examples/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 8 | -------------------------------------------------------------------------------- /examples/ExamplesIndex.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 465 | 466 | 473 | -------------------------------------------------------------------------------- /examples/main.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import App from './App.vue' 3 | import 'bootstrap/dist/css/bootstrap.min.css' 4 | 5 | // import Page from '@' 6 | 7 | // const app = createApp(App) 8 | 9 | // app.use(Page, { 10 | // align: 'left', 11 | // language: 'en' 12 | // }) 13 | 14 | // app.mount('#app') 15 | 16 | createApp(App).mount('#app') 17 | -------------------------------------------------------------------------------- /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TerryZ/v-page/ca4871dc925aa74dea081ff0ceed2ce5cd58485b/favicon.ico -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | v-page examples 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": { 5 | "@/*": ["src/*"] 6 | }, 7 | "target": "ESNext", 8 | "module": "commonjs", 9 | "allowSyntheticDefaultImports": true, 10 | "jsx": "preserve" 11 | }, 12 | "include": ["src/**/*"], 13 | "exclude": ["node_modules", "dist"] 14 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v-page", 3 | "description": "A simple pagination bar", 4 | "version": "3.3.0", 5 | "author": "TerryZ ", 6 | "type": "module", 7 | "files": [ 8 | "./dist", 9 | "./types" 10 | ], 11 | "main": "./dist/v-page.umd.cjs", 12 | "module": "./dist/v-page.js", 13 | "exports": { 14 | ".": { 15 | "import": { 16 | "types": "./types/index.d.ts", 17 | "default": "./dist/v-page.js" 18 | }, 19 | "require": "./dist/v-page.umd.cjs" 20 | }, 21 | "./types": { 22 | "import": { 23 | "types": "./types/index.d.ts" 24 | } 25 | } 26 | }, 27 | "typings": "./types/index.d.ts", 28 | "license": "MIT", 29 | "packageManager": "pnpm@9.15.9", 30 | "scripts": { 31 | "dev": "vite", 32 | "build": "vite build", 33 | "preview": "vite preview --port 4173", 34 | "test:unit": "vitest", 35 | "coverage": "vitest run --coverage", 36 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/TerryZ/v-page.git" 41 | }, 42 | "keywords": [ 43 | "front-end", 44 | "web", 45 | "vue", 46 | "vuejs", 47 | "page", 48 | "pagination" 49 | ], 50 | "peerDependencies": { 51 | "vue": "^3.4.0" 52 | }, 53 | "dependencies": { 54 | "vue": "^3.5.13" 55 | }, 56 | "devDependencies": { 57 | "@rushstack/eslint-patch": "^1.10.4", 58 | "@vitejs/plugin-vue": "^5.2.1", 59 | "@vitejs/plugin-vue-jsx": "^4.1.1", 60 | "@vitest/coverage-v8": "^2.1.8", 61 | "@vue/eslint-config-standard": "^8.0.1", 62 | "@vue/test-utils": "^2.4.6", 63 | "bootstrap": "^5.3.3", 64 | "eslint": "^8.50.0", 65 | "eslint-plugin-vue": "^9.32.0", 66 | "postcss": "^8.4.49", 67 | "autoprefixer": "^10.4.20", 68 | "jsdom": "^25.0.1", 69 | "sass": "^1.83.0", 70 | "typescript": "^5.7.2", 71 | "vite": "^6.0.5", 72 | "vite-plugin-css-injected-by-js": "^3.5.2", 73 | "vitest": "^2.1.8" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/PaginationBar.jsx: -------------------------------------------------------------------------------- 1 | import './page.sass' 2 | 3 | import { ref, computed, watch, toRefs, onMounted, defineComponent, provide } from 'vue' 4 | 5 | import { 6 | FIRST, 7 | ALIGN_RIGHT, 8 | DEFAULT_PAGE_SIZE, 9 | DEFAULT_PAGE_SIZE_MENU, 10 | DEFAULT_PAGE_NUMBER_SIZE, 11 | ALL_RECORD_PAGE_SIZE, 12 | injectPagination 13 | } from './constants' 14 | import { EN } from './language' 15 | import { getLanguages, getPageNumbers } from './helper' 16 | 17 | export default defineComponent({ 18 | name: 'PaginationBar', 19 | props: { 20 | modelValue: { type: Number, default: 0 }, 21 | pageSize: { type: Number, default: DEFAULT_PAGE_SIZE }, 22 | totalRow: { type: Number, default: 0 }, 23 | language: { type: String, default: EN }, 24 | /** 25 | * Pagination alignment direction 26 | * `left`, `center` and `right`(default) 27 | */ 28 | align: { type: String, default: ALIGN_RIGHT }, 29 | /** Page size list */ 30 | pageSizeMenu: { type: [Array], default: () => DEFAULT_PAGE_SIZE_MENU }, 31 | disabled: { type: Boolean, default: false }, 32 | /** Round style page number button */ 33 | circle: { type: Boolean, default: false }, 34 | border: { type: Boolean, default: false }, 35 | /** 36 | * Display all records 37 | * 38 | * will add `all` option in page size list 39 | * and the page size will be 0 40 | */ 41 | displayAll: { type: Boolean, default: false }, 42 | /** Hide pagination when only have one page */ 43 | hideOnSinglePage: { type: Boolean, default: false } 44 | }, 45 | emits: ['update:modelValue', 'update:pageSize', 'change'], 46 | setup (props, { emit, slots }) { 47 | const { pageSizeMenu, totalRow } = toRefs(props) 48 | const current = ref(0) 49 | const pageNumberSize = ref(DEFAULT_PAGE_NUMBER_SIZE) 50 | const pageSize = ref(props.pageSize ?? DEFAULT_PAGE_SIZE) 51 | 52 | const sizeList = computed(() => { 53 | const sizes = Array.from( 54 | Array.isArray(pageSizeMenu.value) && pageSizeMenu.value.length > 0 55 | ? pageSizeMenu.value 56 | : DEFAULT_PAGE_SIZE_MENU 57 | ) 58 | // filter duplicate items 59 | if (pageSize.value !== 0 && !sizes.includes(pageSize.value)) { // display all 60 | sizes.push(pageSize.value) 61 | } 62 | 63 | return sizes.sort((a, b) => a - b) 64 | }) 65 | const totalPage = computed(() => { 66 | // when display all records, the `totalPage` value always be 1 67 | if (pageSize.value === ALL_RECORD_PAGE_SIZE) return FIRST 68 | return Math.ceil(totalRow.value / pageSize.value) 69 | }) 70 | const pageNumbers = computed(() => getPageNumbers( 71 | current.value, 72 | totalPage.value, 73 | pageNumberSize.value 74 | )) 75 | const containerClasses = computed(() => ({ 76 | 'v-pagination': true, 77 | 'v-pagination--right': props.align === 'right', 78 | 'v-pagination--center': props.align === 'center', 79 | 'v-pagination--disabled': props.disabled, 80 | 'v-pagination--border': props.border, 81 | 'v-pagination--circle': !props.border && props.circle 82 | })) 83 | const isFirst = computed(() => current.value === FIRST) 84 | const isLast = computed(() => current.value === totalPage.value) 85 | 86 | watch(() => props.modelValue, changePageNumber) 87 | watch(() => props.pageSize, changePageSize) 88 | 89 | function changePageNumber (pNumber = FIRST) { 90 | if (props.disabled) return 91 | if (typeof pNumber !== 'number') return 92 | 93 | let num = pNumber < FIRST ? FIRST : pNumber 94 | if (pNumber > totalPage.value && totalPage.value > 0) { 95 | num = totalPage.value 96 | } 97 | if (num === current.value) return 98 | 99 | current.value = num 100 | emit('update:modelValue', current.value) 101 | change() 102 | } 103 | function changePageSize (val) { 104 | if (typeof val !== 'number') return 105 | if (val < 0) return 106 | if (val === pageSize.value) return 107 | 108 | pageSize.value = val 109 | emit('update:pageSize', pageSize.value) 110 | if (current.value === FIRST) { 111 | return change() 112 | } 113 | changePageNumber(FIRST) 114 | } 115 | function change () { 116 | emit('change', { 117 | pageNumber: current.value, 118 | pageSize: Number(pageSize.value), 119 | totalPage: totalPage.value 120 | }) 121 | } 122 | 123 | onMounted(() => changePageNumber(props.modelValue || FIRST)) 124 | 125 | provide(injectPagination, { 126 | lang: getLanguages(props.language), 127 | pageSize, 128 | sizeList, 129 | pageNumbers, 130 | isFirst, 131 | isLast, 132 | current, 133 | totalPage, 134 | changePageNumber, 135 | changePageSize, 136 | ...toRefs(props) 137 | }) 138 | 139 | return () => { 140 | if (props.hideOnSinglePage && totalPage.value <= 1) return null 141 | 142 | const slotData = { 143 | pageNumber: current.value, 144 | pageSize: pageSize.value, 145 | totalPage: totalPage.value, 146 | totalRow: totalRow.value, 147 | isFirst: isFirst.value, 148 | isLast: isLast.value 149 | } 150 | 151 | return ( 152 |
153 | 156 |
157 | ) 158 | } 159 | } 160 | }) 161 | -------------------------------------------------------------------------------- /src/PaginationCore.jsx: -------------------------------------------------------------------------------- 1 | import { inject } from 'vue' 2 | 3 | import { FIRST, ALL_RECORD_PAGE_SIZE, injectPagination } from './constants' 4 | 5 | function Link (props, { slots }) { 6 | return {slots?.default?.()} 7 | } 8 | function PageItem ({ classes, pageNumberValue, name }) { 9 | const { changePageNumber } = inject(injectPagination) 10 | return ( 11 |
  • 12 | changePageNumber(pageNumberValue)} > 13 | {name} 14 | 15 |
  • 16 | ) 17 | } 18 | 19 | export function PaginationPageSizeOptions () { 20 | const { 21 | lang, sizeList, pageSize, disabled, displayAll, changePageSize 22 | } = inject(injectPagination) 23 | 24 | const SizeOptions = () => sizeList.value.map(val => 25 | 30 | ) 31 | const DisplayAllOption = () => { 32 | if (!displayAll.value) return null 33 | return ( 34 | 38 | ) 39 | } 40 | 41 | return ( 42 |
  • 43 | 44 | {lang.pageLength} 45 | 52 | 53 |
  • 54 | ) 55 | } 56 | export function PaginationInfo () { 57 | const { lang, current, totalPage, totalRow } = inject(injectPagination) 58 | const content = lang.pageInfo 59 | .replace('#pageNumber#', current.value) 60 | .replace('#totalPage#', totalPage.value) 61 | .replace('#totalRow#', totalRow.value) 62 | return ( 63 |
  • 64 | {content} 65 |
  • 66 | ) 67 | } 68 | export function PaginationPanel (props, { slots }) { 69 | return ( 70 |
  • 71 | {slots?.default?.()} 72 |
  • 73 | ) 74 | } 75 | export function PaginationPageNumbers () { 76 | const { pageNumbers, current } = inject(injectPagination) 77 | return pageNumbers.value.map(val => ( 78 | 84 | )) 85 | } 86 | export function PaginationFirstPage () { 87 | const { isFirst, lang } = inject(injectPagination) 88 | return ( 89 | 94 | ) 95 | } 96 | export function PaginationPreviousPage () { 97 | const { isFirst, current } = inject(injectPagination) 98 | return ( 99 | 104 | ) 105 | } 106 | export function PaginationNextPage () { 107 | const { isLast, current } = inject(injectPagination) 108 | return ( 109 | 114 | ) 115 | } 116 | export function PaginationLastPage () { 117 | const { isLast, totalPage, lang } = inject(injectPagination) 118 | return ( 119 | 124 | ) 125 | } 126 | -------------------------------------------------------------------------------- /src/__test__/PaginationComponents.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | PaginationBar, 3 | PaginationPageSizeOptions, 4 | PaginationInfo, 5 | PaginationPanel, 6 | PaginationPageNumbers, 7 | PaginationFirstPage, 8 | PaginationPreviousPage, 9 | PaginationNextPage, 10 | PaginationLastPage 11 | } from '../' 12 | 13 | export function PaginationComplete (props, { attrs }) { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ) 25 | } 26 | export function PaginationSlot (props, { attrs }) { 27 | return ( 28 | {{ 29 | default: (data) => ( 30 | <> 31 | 32 | 33 | 34 |
    {data.pageNumber}
    35 |
    {data.pageSize}
    36 |
    {data.totalPage}
    37 |
    {data.totalRow}
    38 |
    {data.isFirst.toString()}
    39 |
    {data.isLast.toString()}
    40 |
    41 | 42 | 43 | 44 | 45 | 46 | 47 | ) 48 | }}
    49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /src/__test__/v-page.spec.js: -------------------------------------------------------------------------------- 1 | import { describe, it, expect, vi } from 'vitest' 2 | import { mount } from '@vue/test-utils' 3 | import { PaginationBar } from '@/' 4 | import { getPageNumbers } from '@/helper' 5 | import { 6 | PaginationComplete, PaginationSlot 7 | } from './PaginationComponents' 8 | 9 | describe('v-page', function () { 10 | describe('page-numbers', function () { 11 | it('should be [2,3,4,5,6] when current page number is 4', function () { 12 | const values = getPageNumbers(4, 10, 5) 13 | expect(values.sort().join('')).to.equal([2, 3, 4, 5, 6].sort().join('')) 14 | }) 15 | it('should be [1,2,3,4,5] when page number is less than 1(0)', function () { 16 | const values = getPageNumbers(0, 10, 5) 17 | expect(values.sort().join('')).to.equal([1, 2, 3, 4, 5].sort().join('')) 18 | }) 19 | it('should be [16,17,18,19,20] when page number is greater than total page number(22)', function () { 20 | const values = getPageNumbers(22, 20, 5) 21 | expect(values.sort().join('')).to.equal( 22 | [16, 17, 18, 19, 20].sort().join('') 23 | ) 24 | }) 25 | }) 26 | 27 | describe('dom operation', () => { 28 | const vModelFn = vi.fn() 29 | const wrapper = mount(PaginationComplete, { 30 | props: { 31 | modelValue: 3, 32 | totalRow: 101, 33 | language: 'cn', 34 | border: true, 35 | 'onUpdate:modelValue': vModelFn 36 | } 37 | }) 38 | 39 | it('分页栏应用了边框线风格', () => { 40 | expect(wrapper.classes('v-pagination--border')).toBeTruthy() 41 | }) 42 | it('click page number `5`, the page 5
  • item class name should be `active`', async () => { 43 | await wrapper.findAll('a').at(8).trigger('click') 44 | expect(wrapper.findAll('li').at(6).classes('active')).to.equal(true) 45 | }) 46 | it('the number of current page should be 5', () => { 47 | expect(vModelFn).toHaveBeenCalledWith(5) 48 | }) 49 | it('click `last page` button, the number of current page should be 11', async () => { 50 | // click `last page` button 51 | await wrapper.find('li.v-pagination__last').find('a').trigger('click') 52 | expect(vModelFn).toHaveBeenCalledWith(11) 53 | }) 54 | it('the `next page` and `last page` buttons should be disabled', () => { 55 | expect( 56 | wrapper.find('li.v-pagination__next').classes('disabled') 57 | ).to.equal(true) 58 | expect( 59 | wrapper.find('li.v-pagination__last').classes('disabled') 60 | ).to.equal(true) 61 | }) 62 | it('the page info bar content should be `第 11/11 页(共101条记录)`', () => { 63 | const expectInfoString = '第 11/11 页(共101条记录)' 64 | expect(wrapper.find('li.v-pagination__info a').text()).to.equal( 65 | expectInfoString 66 | ) 67 | }) 68 | it('设置大于总页数的页码,页码应被强制设置为总页数值', async () => { 69 | await wrapper.setProps({ modelValue: 20 }) 70 | // 总页码没有发生变化,相应事件也应没有被触发 71 | expect(vModelFn).toHaveBeenCalledTimes(3) 72 | }) 73 | }) 74 | 75 | describe('switch page size', () => { 76 | const vModelFn = vi.fn() 77 | const changeFn = vi.fn() 78 | const wrapper = mount(PaginationComplete, { 79 | props: { 80 | modelValue: 5, 81 | totalRow: 100, 82 | language: 'cn', 83 | circle: true, 84 | onChange: changeFn, 85 | 'onUpdate:modelValue': vModelFn 86 | } 87 | }) 88 | const bar = wrapper.getComponent(PaginationBar) 89 | it('分页栏应用了圆形按钮风格', () => { 90 | expect(wrapper.classes('v-pagination--circle')).toBeTruthy() 91 | }) 92 | it('initialize v-page with props: { modelValue: 5, totalRow: 100 }', () => { 93 | expect(vModelFn).toHaveBeenCalledWith(5) 94 | expect(bar.props('totalRow')).to.equal(100) 95 | expect(changeFn).toHaveBeenCalledWith({ pageNumber: 5, pageSize: 10, totalPage: 10 }) 96 | }) 97 | it('switch to the third item of list(50), the number of current page should be 1', () => { 98 | // select the third page size(50) in the dropdown menu 99 | wrapper.find('select').findAll('option').at(2).setSelected() 100 | expect(vModelFn).toHaveBeenCalledWith(1) 101 | }) 102 | it('the number of total page should be 2', () => { 103 | expect(bar.emitted('change')[1][0].totalPage).to.equal(2) 104 | }) 105 | it('`displayAll` prop set to true, page length list should add `全部` option', async () => { 106 | await wrapper.setProps({ displayAll: true }) 107 | expect( 108 | wrapper 109 | .find('li.v-pagination__list select') 110 | .find('option:last-child') 111 | .text() 112 | ).to.equal('全部') 113 | }) 114 | it('switch page length to all, the `change` event should return { pageNumber: 1, pageSize: 0 }', () => { 115 | wrapper 116 | .find('li.v-pagination__list select') 117 | .find('option:last-child') 118 | .setSelected() 119 | expect(changeFn).toHaveBeenCalledWith({ pageNumber: 1, pageSize: 0, totalPage: 1 }) 120 | }) 121 | }) 122 | 123 | describe('每页记录数列表与 `pageSize` 联动应用', () => { 124 | const vModelPageSizeFn = vi.fn() 125 | const wrapper = mount(PaginationComplete, { 126 | props: { 127 | totalRow: 100, 128 | language: 'cn', 129 | pageSize: 25, 130 | 'onUpdate:pageSize': vModelPageSizeFn 131 | } 132 | }) 133 | it('每页记录数列表中应包含 `25` 选项,且该项目应被选中', () => { 134 | expect(wrapper.find('select').element.value).toBe('25') 135 | expect(wrapper.find('select option:checked').text()).toBe('25') 136 | }) 137 | it('在记录数列表中选择 `50` 选项,每页记录数应被设置为 `50`', async () => { 138 | await wrapper.find('select').setValue('50') 139 | expect(wrapper.find('select').element.value).toBe('50') 140 | expect(wrapper.find('select option:checked').text()).toBe('50') 141 | }) 142 | it('列表中应不再存在 `25` 的项目', () => { 143 | expect(wrapper.find('select option[value="25"]').exists()).toBeFalsy() 144 | }) 145 | it('设置 `pageSize` prop 为 `15`,应在列表中添加该项目并选中', async () => { 146 | await wrapper.setProps({ pageSize: 15 }) 147 | expect(vModelPageSizeFn).toHaveBeenCalledWith(15) 148 | expect(wrapper.find('select option[selected]').text()).toBe('15') 149 | }) 150 | it('在记录数列表中选择 `20` 选项,每页记录数应被设置为 `20`', async () => { 151 | await wrapper.find('select').setValue('20') 152 | expect(wrapper.find('select').element.value).toBe('20') 153 | expect(wrapper.find('select option:checked').text()).toBe('20') 154 | }) 155 | it('列表中应不再存在 `15` 的项目', () => { 156 | expect(wrapper.find('select option[value="15"]').exists()).toBeFalsy() 157 | }) 158 | }) 159 | 160 | describe('props', () => { 161 | const wrapper = mount(PaginationBar, { 162 | props: { 163 | modelValue: 5, 164 | totalRow: 100, 165 | align: 'center', 166 | language: 'cn' 167 | } 168 | }) 169 | it('set `align` prop to `center`, pagination should align to the center', () => { 170 | expect(wrapper.classes().includes('v-pagination--center')).toBeTruthy() 171 | }) 172 | it('set `disabled` prop to true, pagination should be disabled', async () => { 173 | await wrapper.setProps({ disabled: true }) 174 | expect(wrapper.classes().includes('v-pagination--disabled')).toBeTruthy() 175 | }) 176 | it('set `border` prop to true, pagination should have border style', async () => { 177 | await wrapper.setProps({ border: true }) 178 | expect(wrapper.classes().includes('v-pagination--border')).toBeTruthy() 179 | }) 180 | it('set `hideOnSinglePage` to true, the pagination should be hidden when the total page is 1', async () => { 181 | await wrapper.setProps({ hideOnSinglePage: true, pageSize: 100 }) 182 | expect(wrapper.isVisible()).toBeFalsy() 183 | }) 184 | }) 185 | 186 | describe('slot', () => { 187 | const wrapper = mount(PaginationSlot, { 188 | props: { 189 | totalRow: 100, 190 | pageSize: 25, 191 | border: true, 192 | circle: true 193 | } 194 | }) 195 | // console.log(wrapper.html()) 196 | 197 | it('scoped slot should output 6 page current states', () => { 198 | const slot = wrapper.find('li.v-pagination__slot') 199 | expect(slot.find('.slot-page-number').text()).to.equal('1') 200 | expect(slot.find('.slot-page-size').text()).to.equal('25') 201 | expect(slot.find('.slot-total-page').text()).to.equal('4') 202 | expect(slot.find('.slot-total-row').text()).to.equal('100') 203 | expect(slot.find('.slot-is-first').text()).to.equal('true') 204 | expect(slot.find('.slot-is-last').text()).to.equal('false') 205 | }) 206 | it('go to last page, scoped slot states should be updated', async () => { 207 | await wrapper.find('li.v-pagination__last a').trigger('click') 208 | 209 | const slot = wrapper.find('li.v-pagination__slot') 210 | expect(slot.find('.slot-page-number').text()).to.equal('4') 211 | expect(slot.find('.slot-page-size').text()).to.equal('25') 212 | expect(slot.find('.slot-total-page').text()).to.equal('4') 213 | expect(slot.find('.slot-total-row').text()).to.equal('100') 214 | expect(slot.find('.slot-is-first').text()).to.equal('false') 215 | expect(slot.find('.slot-is-last').text()).to.equal('true') 216 | }) 217 | it('同时启用了圆形按钮风格与边框线样式时,应只有边框线样式生效', () => { 218 | expect(wrapper.classes().includes('v-pagination--border')).toBeTruthy() 219 | expect(wrapper.classes().includes('v-pagination--circle')).toBeFalsy() 220 | }) 221 | it('每页记录数列表当前选中项目应为 `25`', () => { 222 | expect(wrapper.find('.v-pagination__list select').element.value).toBe('25') 223 | }) 224 | }) 225 | }) 226 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | export const FIRST = 1 2 | export const DEFAULT_PAGE_NUMBER_SIZE = 5 3 | export const DEFAULT_PAGE_SIZE = 10 4 | export const DEFAULT_PAGE_SIZE_MENU = [DEFAULT_PAGE_SIZE, 20, 50, 100] 5 | 6 | export const ALL_RECORD_PAGE_SIZE = 0 7 | 8 | export const [ALIGN_LEFT, ALIGN_CENTER, ALIGN_RIGHT] = ['left', 'center', 'right'] 9 | 10 | export const injectPagination = Symbol('pagination') 11 | -------------------------------------------------------------------------------- /src/helper.js: -------------------------------------------------------------------------------- 1 | import { FIRST } from './constants' 2 | import languages, { EN } from './language' 3 | 4 | function getPageNumberStart (current, totalPage, pageNumberSize) { 5 | if (totalPage <= pageNumberSize) return FIRST 6 | 7 | const half = Math.floor(pageNumberSize / 2) 8 | const lastRangeStart = totalPage - pageNumberSize + 1 9 | const start = current - half 10 | 11 | if (start < FIRST) return FIRST 12 | if (start > lastRangeStart) return lastRangeStart 13 | 14 | return start 15 | } 16 | 17 | export function getPageNumbers (current, totalPage, pageNumberSize) { 18 | const start = getPageNumberStart(current, totalPage, pageNumberSize) 19 | return Array.from({ length: pageNumberSize }) 20 | .map((val, index) => start + index) 21 | .filter(val => val >= FIRST && val <= totalPage) 22 | } 23 | 24 | export function getLanguages (lang = EN) { 25 | const key = String(lang).toLowerCase() 26 | return languages[Object.hasOwn(languages, key) ? key : EN] 27 | } 28 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import PaginationBar from './PaginationBar' 2 | export * from './PaginationCore' 3 | 4 | PaginationBar.install = (app, options = {}) => { 5 | if (Object.keys(options).length) { 6 | const { props } = PaginationBar 7 | const { 8 | language, 9 | align, 10 | info, 11 | border, 12 | pageNumber, 13 | first, 14 | last, 15 | pageSizeMenu 16 | } = options 17 | 18 | if (language) props.language.default = language 19 | if (align) props.align.default = align 20 | if (typeof info === 'boolean') props.info.default = info 21 | if (typeof border === 'boolean') props.border.default = border 22 | if (typeof pageNumber === 'boolean') props.pageNumber.default = pageNumber 23 | if (typeof first === 'boolean') props.first.default = first 24 | if (typeof last === 'boolean') props.last.default = last 25 | if (typeof pageSizeMenu !== 'undefined') { 26 | props.pageSizeMenu.default = pageSizeMenu 27 | } 28 | } 29 | app.component(PaginationBar.name, PaginationBar) 30 | } 31 | 32 | export { PaginationBar } 33 | export default PaginationBar 34 | -------------------------------------------------------------------------------- /src/language.js: -------------------------------------------------------------------------------- 1 | export const [ 2 | CN, 3 | EN, 4 | DE, 5 | JP, 6 | PT 7 | ] = ['cn', 'en', 'de', 'jp', 'pt'] 8 | 9 | export default { 10 | [CN]: { 11 | pageLength: '每页记录数', 12 | pageInfo: '第 #pageNumber#/#totalPage# 页(共#totalRow#条记录)', 13 | first: '首页', 14 | last: '尾页', 15 | all: '全部' 16 | }, 17 | [EN]: { 18 | pageLength: 'Per page', 19 | pageInfo: 'Page #pageNumber#/#totalPage# (total #totalRow# records)', 20 | first: 'First', 21 | last: 'Last', 22 | all: 'All' 23 | }, 24 | [DE]: { 25 | pageLength: 'Seitenlänge', 26 | pageInfo: 'Aktuell #pageNumber#/#totalPage# (gesamt #totalRow# Aufzeichnungen)', 27 | first: 'Zuerst', 28 | last: 'Letzte', 29 | all: 'Alle' 30 | }, 31 | [JP]: { 32 | pageLength: 'ページごとの記録数', 33 | pageInfo: '現在の第 #pageNumber#/#totalPage# ページ(全部で #totalRow# 条の記録)', 34 | first: 'トップページ', 35 | last: '尾のページ', 36 | all: 'すべて' 37 | }, 38 | [PT]: { 39 | pageLength: 'Resultados por página', 40 | pageInfo: '#pageNumber#/#totalPage# (total de #totalRow#)', 41 | first: 'Início', 42 | last: 'Fim', 43 | all: 'Todos' 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/page.sass: -------------------------------------------------------------------------------- 1 | $border-radius: .5rem 2 | $color-text: #333 3 | $color-muted: #ddd 4 | 5 | .v-pagination 6 | display: flex 7 | justify-content: flex-start 8 | box-sizing: border-box 9 | &--right 10 | justify-content: flex-end 11 | &--center 12 | justify-content: center 13 | &#{&}--disabled 14 | a, 15 | select 16 | color: $color-muted !important 17 | .v-pagination__item a 18 | cursor: default !important 19 | &:hover 20 | background-color: transparent 21 | &#{&}--disabled#{&}--border ul 22 | background-color: #fafafa 23 | &#{&}--circle 24 | .v-pagination__item 25 | a 26 | border-radius: 50rem 27 | border: 1px solid transparent 28 | min-height: 30px 29 | min-width: 30px 30 | padding: 0 .6rem 31 | display: flex 32 | align-items: center 33 | justify-content: center 34 | &:hover 35 | border: 1px solid #f7f7f7 36 | &.disabled a:hover 37 | border: 1px solid transparent 38 | &.active a 39 | border: 1px solid #ddd !important 40 | background-color: transparent !important 41 | box-shadow: 0 1px 3px rgb(0,0,0,.1) 42 | ul 43 | margin: 0 44 | padding: 0 45 | display: flex 46 | li 47 | list-style: none 48 | display: flex 49 | a 50 | padding: .3rem .6rem 51 | text-decoration: none 52 | line-height: 1.3 53 | font-size: 14px 54 | margin: 0 55 | outline: 0 56 | color: $color-text 57 | border-radius: $border-radius 58 | display: inline-flex 59 | align-items: center 60 | cursor: default 61 | // transition: all .3s 62 | &.v-pagination__list, 63 | &.v-pagination__info 64 | a 65 | color: #888 66 | &.active, 67 | &.disabled 68 | a 69 | cursor: default !important 70 | &.v-pagination__item a:hover 71 | background-color: #f7f7f7 72 | color: black 73 | cursor: pointer 74 | &.active a 75 | background-color: #eee !important 76 | color: #aaa 77 | &:hover 78 | color: #aaa 79 | &.disabled a 80 | color: $color-muted !important 81 | &:hover 82 | background-color: transparent 83 | select 84 | width: auto !important 85 | font-size: 12px 86 | padding: 0 87 | outline: 0 88 | margin: 0 0 0 5px 89 | border: 1px solid #ccc 90 | color: $color-text 91 | border-radius: .3rem 92 | 93 | &#{&}--border 94 | ul 95 | box-shadow: 0 1px 2px rgba(0, 0, 0, .05) 96 | border-radius: $border-radius 97 | li 98 | a 99 | border: 1px solid #DEE2E6 100 | border-left: 0 101 | border-radius: 0 102 | &:first-child > a 103 | border-bottom-left-radius: $border-radius 104 | border-top-left-radius: $border-radius 105 | border-left: 1px solid #DEE2E6 106 | &:last-child > a 107 | border-top-right-radius: $border-radius 108 | border-bottom-right-radius: $border-radius 109 | &.active a 110 | color: #aaa 111 | background-color: #eee 112 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "strict": true, 5 | "module": "ESNext", 6 | "moduleResolution": "node", 7 | "allowJs": true, 8 | "jsx": "preserve", 9 | "jsxImportSource": "vue", 10 | "paths": { 11 | "@/*": ["./src/*"] 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AllowedComponentProps, 3 | ComponentCustomProps, 4 | VNodeProps, 5 | VNode 6 | } from 'vue' 7 | 8 | export declare interface PageInfo { 9 | pageNumber: number 10 | pageSize: number 11 | totalPage: number 12 | } 13 | 14 | export declare interface PageSlotData { 15 | pageNumber: number 16 | pageSize: number 17 | totalPage: number 18 | totalRow: number 19 | isFirst: boolean 20 | isLast: boolean 21 | } 22 | 23 | /** 24 | * Pagination plugin for Vue 25 | */ 26 | declare interface PaginationProps { 27 | /** 28 | * The number of current page 29 | */ 30 | modelValue?: number 31 | /** 32 | * The number of total record 33 | */ 34 | totalRow: number 35 | /** 36 | * The number of page size 37 | * @default 10 38 | */ 39 | pageSize?: number 40 | /** 41 | * v-page language 42 | * @default `en` 43 | */ 44 | language?: 'cn' | 'en' | 'de' | 'jp' | 'pt' 45 | /** 46 | * Page size list 47 | * @default [10, 20, 50, 100] 48 | */ 49 | pageSizeMenu?: number[] 50 | /** 51 | * Alignment direction 52 | * @default `right` 53 | */ 54 | align?: 'left' | 'right' | 'center' 55 | /** 56 | * Disabled the pagination 57 | * @default false 58 | */ 59 | disabled?: boolean 60 | /** 61 | * Whether to display the border 62 | * @default true 63 | */ 64 | border?: boolean 65 | /** 66 | * Round style page number button 67 | * @default false 68 | */ 69 | circle?: boolean 70 | /** 71 | * Whether add `All` item in page length list 72 | * @default false 73 | */ 74 | displayAll?: boolean 75 | /** 76 | * Hide pagination when only have one page 77 | * @default false 78 | */ 79 | hideOnSinglePage?: boolean 80 | } 81 | 82 | /** Update pageNumber value */ 83 | type EmitUpdateModelValue = (event: "update:modelValue", value: number) => void 84 | /** Update pageSize value */ 85 | type EmitUpdatePageSize = (event: "update:pageSize", value: number) => void 86 | /** The event respond pageNumber or pageSize change */ 87 | type EmitChange = (event: "change", value: PageInfo) => void 88 | 89 | type ComponentProps = AllowedComponentProps & ComponentCustomProps & VNodeProps 90 | 91 | declare interface PaginationBar { 92 | new (): { 93 | $props: ComponentProps & PaginationProps 94 | $emit: EmitUpdateModelValue & EmitUpdatePageSize & EmitChange 95 | $slots: { 96 | default?: (defaultSlotData: PageSlotData) => VNode[] 97 | } 98 | } 99 | } 100 | declare interface PaginationComponent { 101 | new (): { 102 | $props: ComponentProps 103 | } 104 | } 105 | declare interface PaginationPanel { 106 | new (): { 107 | $props: ComponentProps 108 | $slots: { 109 | default?: () => VNode[] 110 | } 111 | } 112 | } 113 | declare const PaginationBar: PaginationBar 114 | declare const PaginationPageSizeOptions: PaginationComponent 115 | declare const PaginationInfo: PaginationComponent 116 | declare const PaginationPageNumbers: PaginationComponent 117 | declare const PaginationFirstPage: PaginationComponent 118 | declare const PaginationPreviousPage: PaginationComponent 119 | declare const PaginationNextPage: PaginationComponent 120 | declare const PaginationLastPage: PaginationComponent 121 | declare const PaginationPanel: PaginationPanel 122 | 123 | export { 124 | PaginationBar, 125 | PaginationPageSizeOptions, 126 | PaginationInfo, 127 | PaginationPageNumbers, 128 | PaginationFirstPage, 129 | PaginationPreviousPage, 130 | PaginationNextPage, 131 | PaginationLastPage, 132 | PaginationPanel 133 | } 134 | 135 | export default PaginationBar -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | import { resolve } from 'path' 3 | // import { defineConfig } from 'vite' 4 | import { defineConfig } from 'vitest/config' 5 | import vue from '@vitejs/plugin-vue' 6 | import vueJsx from '@vitejs/plugin-vue-jsx' 7 | import cssInJs from 'vite-plugin-css-injected-by-js' 8 | 9 | // https://vitejs.dev/config/ 10 | export default defineConfig({ 11 | resolve: { 12 | alias: { 13 | '@': fileURLToPath(new URL('./src', import.meta.url)) 14 | } 15 | }, 16 | build: { 17 | lib: { 18 | entry: resolve(__dirname, 'src/index.js'), 19 | name: 'VPage', 20 | formats: ['es', 'umd'], 21 | fileName: 'v-page' 22 | }, 23 | rollupOptions: { 24 | external: ['vue'], 25 | output: { 26 | exports: 'named', 27 | globals: { 28 | vue: 'Vue' 29 | } 30 | } 31 | } 32 | }, 33 | test: { 34 | environment: 'jsdom', 35 | reporters: 'verbose', 36 | coverage: { 37 | provider: 'v8', 38 | reporter: ['text', 'json', 'html'], 39 | include: ['src/**'] 40 | } 41 | }, 42 | plugins: [ 43 | vue(), 44 | vueJsx(), 45 | cssInJs() 46 | ] 47 | }) 48 | --------------------------------------------------------------------------------