├── .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) · [](https://dl.circleci.com/status-badge/redirect/gh/TerryZ/v-page/tree/master) [](https://codecov.io/gh/TerryZ/v-page) [](https://www.npmjs.com/package/v-page) [](https://mit-license.org/) [](https://www.npmjs.com/package/v-page) [](https://standardjs.com)
2 |
3 | A simple pagination bar for vue3, including size Menu, i18n support features
4 |
5 |
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://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 |
54 |
55 |
56 |
57 |
60 | ```
61 |
62 | ## Usage
63 |
64 | ```vue
65 |
66 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
103 | ```
104 |
--------------------------------------------------------------------------------
/examples/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/examples/ExamplesIndex.vue:
--------------------------------------------------------------------------------
1 |
51 |
52 |
53 |
54 |
55 | v-page examples
56 |
57 |
58 |
59 |
60 |
照片墙实例
61 |
62 |
68 | {{ num }}
69 |
70 |
71 |
72 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | 完整分页栏
90 |
91 |
92 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
114 |
115 |
122 |
129 |
130 |
131 |
132 |
133 | 自定义 page size {{ size }}
134 |
135 |
136 |
146 |
147 |
148 |
149 |
150 | pageSize:
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
167 |
168 |
169 | pageSize:
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
185 |
186 |
193 |
200 |
201 |
202 |
203 |
204 | 对齐方向
205 |
206 |
207 |
208 |
方向:
209 |
210 |
224 |
225 |
226 |
227 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 | 无页数选择列表
244 |
245 |
246 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 | 无分页信息栏
262 |
263 |
264 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 | 无首页、尾页
278 |
279 |
280 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 | 无分页码
292 |
293 |
294 |
298 |
299 |
300 |
301 |
302 |
303 |
304 | 禁用
305 |
306 |
353 |
354 |
355 | 圆形按钮风格
356 |
357 |
358 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 | 边框
377 |
378 |
379 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 | 插槽
399 |
400 |
401 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
page:
417 |
pageSize:
418 |
totalPage:
419 |
totalRow:
420 |
isFirst:
421 |
isLast:
422 |
423 |
424 |
425 |
426 |
427 |
428 | 显示全部数据
429 |
430 |
431 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 | 仅一页时隐藏分页栏
448 |
449 |
450 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
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 |
154 | {slots?.default?.(slotData)}
155 |
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 |
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 |
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 |
66 | )
67 | }
68 | export function PaginationPanel (props, { slots }) {
69 | return (
70 |
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 |
--------------------------------------------------------------------------------