diff --git a/devui/loading/__tests__/loading.spec.ts b/devui/loading/__tests__/loading.spec.ts index bc4ad4faf14cd2746d06d4ca9040c53affc2cc80..3e6030a3b51d5ba4c7def22e864169d0cee6ec42 100644 --- a/devui/loading/__tests__/loading.spec.ts +++ b/devui/loading/__tests__/loading.spec.ts @@ -1,15 +1,12 @@ /* eslint-disable */ import { mount } from '@vue/test-utils'; import { ref, Ref, nextTick, h, shallowReactive } from 'vue'; -import loading from '../index'; - -// 服务方式 -const LoadingService = loading.LoadingService +import { LoadingService, Loading } from '../index'; // 全局属性 const globalOption = { directives: { - dLoading: loading.dLoading + dLoading: Loading } } @@ -55,7 +52,7 @@ describe('Loading as directive', () => { } ) - const loadingPType = wrapper.find('#testLoading') + const loadingPType: any = wrapper.find('#testLoading') expect(loadingPType).toBeTruthy() // @_ts-ignore // 不支持`ts-ignore`,强行修改确保eslint通过。@mrundef-210810 @@ -128,7 +125,6 @@ describe('Loading as directive', () => { }) - // TODO Promise 的单元测试, 需完善 it('loading test Promise', async () => { const wrapper = mount( { @@ -139,7 +135,7 @@ describe('Loading as directive', () => { `, setup() { - const loading: Ref | undefined | boolean> = ref(undefined) + const loading: Ref | undefined | boolean> = ref(false) const click = () => { loading.value = new Promise((res: any) => { @@ -162,10 +158,13 @@ describe('Loading as directive', () => { expect(btn.exists()).toBeTruthy() await btn.trigger('click') - expect(wrapper.find('.devui-loading-wrapper').exists()).toBeFalsy() + expect(wrapper.find('.devui-loading-wrapper').exists()).toBeTruthy() + // TODO 组件移除是在finally内部移除,在微任务队列末尾,这里好像检测不到 + setTimeout(() => { + expect(wrapper.find('.devui-loading-wrapper').exists()).toBeFalsy() + }) }) - // TODO 多个 Promise 的单元测试, 需完善 it('loading test mutiple Promise', async () => { const wrapper = mount( { @@ -205,7 +204,11 @@ describe('Loading as directive', () => { expect(btn.exists()).toBeTruthy() await btn.trigger('click') - expect(wrapper.find('.devui-loading-wrapper').exists()).toBeFalsy() + expect(wrapper.find('.devui-loading-wrapper').exists()).toBeTruthy() + // TODO 组件移除是在finally内部移除,在微任务队列末尾,这里好像检测不到 + setTimeout(() => { + expect(wrapper.find('.devui-loading-wrapper').exists()).toBeFalsy() + }) }) }) diff --git a/devui/pagination/index.ts b/devui/pagination/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..074ec4ab2c71f986b49d2c2037810d24aa8a9a50 --- /dev/null +++ b/devui/pagination/index.ts @@ -0,0 +1,16 @@ +import { App } from 'vue' +import Pagination from './src/pagination' + +Pagination.install = (app: App): void => { + app.component(Pagination.name, Pagination) +} + +export { Pagination } + +export default { + title: 'Pagination 分页', + category: '导航', + install(app: App): void { + app.use(Pagination as any) + } +} diff --git a/devui/pagination/src/components/config-menu.tsx b/devui/pagination/src/components/config-menu.tsx new file mode 100644 index 0000000000000000000000000000000000000000..a08edfad56362273ab0d4ac74997f0dfe00f4001 --- /dev/null +++ b/devui/pagination/src/components/config-menu.tsx @@ -0,0 +1,76 @@ +import { defineComponent, onMounted, onUnmounted, PropType, ref } from 'vue'; +import { on, off } from '../utils' + +import clickoutsideDirective from '../directive' + +export default defineComponent({ + directives: { + clickoutside: clickoutsideDirective + }, + props: { + currentPageSize: Number, + pageSizeChange: Function, + pageSizeOptions: Array as PropType> + } as const, + setup() { + const paginationConfig = ref(null) + const isShowConfig = ref(false) + + onMounted(() => { + on(paginationConfig.value, 'click', closeConfigMenu) + }) + onUnmounted(() => { + off(paginationConfig.value, 'click', closeConfigMenu) + }) + const closeConfigMenu = (e: Event) => { + isShowConfig.value = isShowConfig.value ? false : !!e + } + + return { + paginationConfig, + isShowConfig, + closeConfigMenu + } + }, + render() { + const { + closeConfigMenu, + currentPageSize, + pageSizeChange, + pageSizeOptions, + isShowConfig, + $slots + } = this + + return ( +
+
+ +
+ { + isShowConfig && +
+ {$slots.default?.()} + +
+
每页条数
+
+ { + pageSizeOptions.map((v: number) => { + return ( +
{v}
+ ) + }) + } +
+
+
+ } +
+ ) + } +}) \ No newline at end of file diff --git a/devui/pagination/src/components/jump-page.tsx b/devui/pagination/src/components/jump-page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..096a56c2cea758573acafd05431c0051641a4bf7 --- /dev/null +++ b/devui/pagination/src/components/jump-page.tsx @@ -0,0 +1,53 @@ +import { defineComponent, PropType } from 'vue'; + +export default defineComponent({ + props: { + goToText: String, + size: { + type: String as PropType<'lg' | '' | 'sm'>, + default: '' + }, + inputPageNum: Number, + jump: Function, + jumpPageChange: Function, + showJumpButton: Boolean + } as const, + render() { + const { + goToText, + size, + inputPageNum, + jumpPageChange, + jump, + showJumpButton + } = this + + return ( +
+ {goToText} + + + + { + // TODO 加入国际化后,替换为当前语言为中文的时候加上 '页' + goToText === '跳至' && '页' + } + { + showJumpButton && +
+
+
+ } +
+ ) + } +}) \ No newline at end of file diff --git a/devui/pagination/src/components/page-nums.tsx b/devui/pagination/src/components/page-nums.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8ebb51d1121d2c7ab2683756b27ab058c61da6c6 --- /dev/null +++ b/devui/pagination/src/components/page-nums.tsx @@ -0,0 +1,151 @@ +import { defineComponent, PropType, computed, ExtractPropTypes } from 'vue'; +import { handlePages } from '../utils' + +const pageNumBtnProps = { + size: { + type: String as PropType<'lg' | '' | 'sm'>, + default: '' + }, + preLink: String, + nextLink: String, + lite: Boolean, + cursor: Number, + maxItems: Number, + totalPages: Number, + onChangeCursorEmit: Function, + showTruePageIndex: Boolean +} as const + +type PageNumBtnProps = ExtractPropTypes + +export default defineComponent({ + props: pageNumBtnProps, + emits: ['changeCursorEmit'], + setup(props: PageNumBtnProps, { emit }) { + + // 页码较多时,计算中间的显示页码的起始页码数 + const showPageNum = computed(() => handlePages(props.cursor, props.maxItems, props.totalPages)) + + // 点击页码 + const changeCursor = (pageSize: number) => { + if (isNaN(pageSize)) return + const page = pageSize < 1 ? 1 : pageSize > props.totalPages ? props.totalPages : pageSize | 0 + + emit('changeCursorEmit', page) + } + // 上一页 + const prevChange = (page: number) => { + if (props.cursor > 1) { + const toPage = page === -1 ? props.cursor - 1 : page + + emit('changeCursorEmit', toPage) + } + } + // 下一页 + const nextChange = (page: number) => { + if (props.cursor < props.totalPages) { + const toPage = page === -1 ? props.cursor + 1 : page + + emit('changeCursorEmit', toPage) + } + } + + return { + showPageNum, + changeCursor, + prevChange, + nextChange + } + }, + render() { + const { + size, + preLink, + nextLink, + lite, + changeCursor, + cursor, + showPageNum, + prevChange, + totalPages, + nextChange, + showTruePageIndex + } = this + + return ( +
    + {/* 左侧上一页按钮 */} +
  • + +
  • + { + !lite && + <> + {/* 页码展示 */} + {/* 单独展示第一页 */} +
  • + 1 +
  • + { + // 是否展示第一个 ... + showPageNum[0] > 2 && ( +
  • + ... +
  • + ) + } + { + // 中间显示页码 + (() => { + const list = [] + for(let i = showPageNum[0]; i <= showPageNum[1]; i++) { + list.push( +
  • + {i} +
  • + ) + } + return list + })() + } + { + // 是否展示第二个 ... + showPageNum[1] < totalPages - 1 && ( +
  • + ... +
  • + ) + } + { + // 是否单独展示最后一页 + showPageNum[1] < totalPages && ( +
  • + {totalPages} +
  • + ) + } + { + // 在默认页码超出总页码的时候 + showTruePageIndex && cursor > totalPages && totalPages > 0 && + <> + { + cursor > totalPages + 1 && +
  • + ... +
  • + } +
  • + { cursor } +
  • + + } + + } + {/* 右侧下一页按钮 */} +
  • = totalPages}}> + +
  • +
+ ) + } +}) \ No newline at end of file diff --git a/devui/pagination/src/directive.ts b/devui/pagination/src/directive.ts new file mode 100644 index 0000000000000000000000000000000000000000..43ce9178f36fc0bf46aa42382e35799b42d0c92d --- /dev/null +++ b/devui/pagination/src/directive.ts @@ -0,0 +1,63 @@ +/** + * v-clickoutside + * @desc 点击元素外面才会触发的事件 + * @example + *
+ */ + +import { on } from './utils' + +const ctx = Symbol('@@clickoutside') +const nodeList = new Map() + +let startClick +let nid = 0 + +on(document, 'mousedown', (e: Event) => { + startClick = e +}) +on(document, 'mouseup', (e: Event) => { + for (const [id, node] of nodeList) { + node[ctx].documentHandler(e, startClick) + } +}) + +function createDocumentHandler(el: HTMLElement, binding: Record, vnode: any) { + return function(mousedown: Event, mouseup: Event) { + if ( + !vnode || + !binding.instance || + !mouseup.target || + !mousedown.target || + el.contains(mouseup.target as HTMLElement) || + el.contains(mousedown.target as HTMLElement) || + el === mouseup.target + ) + return + el[ctx].bindingFn && el[ctx].bindingFn() + } +} + +const clickoutsideDirective = { + beforeMount: function (el: HTMLElement, binding: Record, vnode: any) { + nid++ + nodeList.set(nid, el) + el[ctx] = { + nid, + documentHandler: createDocumentHandler(el, binding, vnode), + bindingFn: binding.value + } + }, + + updated: function (el: HTMLElement, binding: Record, vnode: any) { + el[ctx].documentHandler = createDocumentHandler(el, binding, vnode) + el[ctx].bindingFn = binding.value + }, + + unmounted: function (el: HTMLElement) { + nodeList.delete(el[ctx].nid) + delete el[ctx] + } +} + +export default clickoutsideDirective diff --git a/devui/pagination/src/pagination.scss b/devui/pagination/src/pagination.scss new file mode 100644 index 0000000000000000000000000000000000000000..fa28bfe66b7422528df77cd18e214ea120fd3648 --- /dev/null +++ b/devui/pagination/src/pagination.scss @@ -0,0 +1,313 @@ +@import '../../style/theme/color'; +@import '../../style/theme/_corner'; +@import '../../style/core/_font'; + +.devui-pagination { + font-size: var(--devui-font-size, 12px); + + .devui-page-size { + display: inline-block; + max-width: 100px; + line-height: 24px; + margin: 0 12px 0 0; + position: relative; + vertical-align: middle; + + &.devui-page-size-sm { + max-width: 80px; + + .devui-select-input { + height: 24px; + } + } + + &.devui-page-size-lg { + max-width: 100px; + + .devui-select-input { + height: 44px; + } + + .devui-select-item { + height: 44px; + } + } + } + + .devui-select-input { + height: 26px; + } + + .devui-total-size { + display: inline-block; + position: relative; + vertical-align: middle; + margin: 0 12px 0 0; + color: var(--devui-text-weak, #575d6c); + } + + .devui-pagination-list, + .devui-pagination-item { + padding: 0; + margin: 0; + list-style: none; + } + + a { + text-decoration: none; + color: #3eaf7c; + } + + .devui-pagination-list { + vertical-align: middle; + display: inline-flex; + align-items: center; + + li.devui-pagination-item { + cursor: pointer; + + &.active { + a.devui-pagination-link { + text-decoration: none; + background-color: var(--devui-list-item-active-bg, #5e7ce0); + color: var(--devui-list-item-active-text, #ffffff); + } + } + + &.disabled { + a.devui-pagination-link { + cursor: not-allowed; + opacity: 0.5; + background-color: #ffffff; + color: var(--devui-text-weak, #575d6c); + } + } + + .devui-pagination-link { + margin-left: 5px; + padding: 3px 7px; + line-height: 1.5; + border-radius: var(--devui-border-radius, 2px); + color: var(--devui-text-weak, #575d6c); + display: flex; + align-items: center; + transition: background-color var(--devui-animation-duration-slow, 0.3s) var(--devui-animation-ease-in-out-smooth, cubic-bezier(0.645, 0.045, 0.355, 1)); + + &:hover { + text-decoration: none; + background-color: var(--devui-list-item-hover-bg, #f2f5fc); + color: var(--devui-list-item-hover-text, #526ecc); + } + } + } + } + + .devui-pagination-sm > li.devui-pagination-item > a.devui-pagination-link { + padding: 0 5px; + min-width: 18px; + height: 22px; + line-height: 1.5; + font-size: $devui-font-size-sm; + } + + .devui-pagination-lg > li.devui-pagination-item > a.devui-pagination-link { + padding: 0 12px; + height: 38px; + font-size: $devui-font-size-lg; + line-height: 1.5; + } + + .devui-jump-container { + display: inline-flex; + position: relative; + margin: 0 12px; + vertical-align: middle; + align-items: center; + + .devui-input { + display: inline-block; + width: 42px; + height: 28px; + vertical-align: middle; + margin: 0 3px; + + &.devui-input-sm { + height: 24px; + padding: 4px 4px; + font-size: $devui-font-size; + line-height: 1.5; + border-radius: $devui-border-radius; + } + + &.devui-input-lg { + width: 56px; + height: 46px; + } + } + } + + .devui-jump-button { + display: inline-flex; + vertical-align: middle; + width: 24px; + height: 24px; + border-radius: var(--devui-border-radius, 2px); + border: 1px solid var(--devui-line, #adb0b8); + cursor: pointer; + margin-left: 4px; + align-items: center; + justify-content: center; + + .devui-pagination-go { + width: 0; + height: 0; + border-top: 6px solid transparent; + border-left: 6px solid var(--devui-icon-text, #252b3a); + border-bottom: 6px solid transparent; + } + + &:hover { + border-color: $devui-brand-active; + + .devui-pagination-go { + border-left-color: $devui-brand-active; + } + } + } + + .devui-jump-size-default { + width: 28px; + height: 28px; + + .devui-pagination-go { + width: 0; + height: 0; + border-top: 8px solid transparent; + border-left: 10px solid $devui-icon-text; + border-bottom: 8px solid transparent; + } + } + + .devui-jump-size-sm { + width: 24px; + height: 24px; + + .devui-pagination-go { + width: 0; + height: 0; + border-top: 6px solid transparent; + border-left: 6px solid $devui-icon-text; + border-bottom: 6px solid transparent; + } + + .devui-pagination-link { + height: 30px; + line-height: 32px; + } + } + + .devui-jump-size-lg { + width: 46px; + height: 46px; + + .devui-pagination-go { + width: 0; + height: 0; + border-top: 14px solid transparent; + border-left: 14px solid $devui-icon-text; + border-bottom: 14px solid transparent; + } + } + + .devui-pagination-config { + color: var(--devui-text, #252b3a); + position: relative; + display: inline-block; + vertical-align: middle; + margin: 0 4px; + } + + .devui-setup-icon { + line-height: 30px; + cursor: pointer; + display: flex; + } + + .devui-config-container { + padding: 4px 0; + box-shadow: var(--devui-shadow-connected-overlay, 0 2px 8px 0) var(--devui-shadow, rgba(0, 0, 0, 0.2)); + border-radius: var(--devui-border-radius, 2px); + width: 150px; + background-color: var(--devui-connected-overlay-bg, #ffffff); + line-height: 26px; + position: absolute; + left: -136px; + top: 28px; + cursor: auto; + z-index: var(--devui-z-index-dropdown, 1052); + user-select: none; + } + + /* 配置中的每一项,自定义项建议应用此样式或在此基础上修改 */ + .pagination-config-item { + padding-bottom: 8px; + padding-top: 4px; + border-bottom: 1px solid $devui-line; + + &:last-child { + border-bottom: none; + } + } + + /* 配置中每一项的标题样式,自定义项建议应用此样式或在此基础上修改 */ + .config-item-title { + color: $devui-line; + padding-left: 8px; + font-size: $devui-font-size; + line-height: 1.5; + } + + .devui-page-number { + padding-left: 8px; + margin-top: 4px; + display: flex; + + div { + color: var(--devui-text, #252b3a); + cursor: pointer; + border-top: 1px solid var(--devui-line, #adb0b8); + border-bottom: 1px solid var(--devui-line, #adb0b8); + border-right: 1px solid var(--devui-line, #adb0b8); + text-align: center; + height: 26px; + width: 26px; + + &:first-child { + border-left: 1px solid var(--devui-line, #adb0b8); + } + + &:hover { + background-color: var(--devui-list-item-hover-bg, #f2f5fc); + color: var(--devui-list-item-hover-text, #526ecc); + } + + &.choosed { + color: var(--devui-list-item-active-text, #ffffff); + background-color: var(--devui-list-item-active-bg, #5e7ce0) !important; + cursor: auto !important; + } + } + } + + .config-item-words { + color: $devui-text; + padding-left: 8px; + font-size: $devui-font-size; + margin-top: 4px; + } + + .config-item-words:hover { + background-color: $devui-area; + cursor: pointer; + } +} diff --git a/devui/pagination/src/pagination.tsx b/devui/pagination/src/pagination.tsx new file mode 100644 index 0000000000000000000000000000000000000000..d150c80925d49086990f0476a384a5f0f661dcf0 --- /dev/null +++ b/devui/pagination/src/pagination.tsx @@ -0,0 +1,231 @@ +import { defineComponent, computed, ref, nextTick } from 'vue' +import { ComponentProps, componentProps } from './use-pagination' +import { liteSelectOptions } from './utils' + +import clickoutsideDirective from './directive' + +import ConfigMenu from './components/config-menu' +import JumpPage from './components/jump-page' +import PageNumBtn from './components/page-nums' + +import './pagination.scss' + +export default defineComponent({ + name: 'DPagination', + directives: { + clickoutside: clickoutsideDirective + }, + components: { + ConfigMenu, + JumpPage, + PageNumBtn + }, + props: componentProps, + emits: ['pageIndexChange', 'pageSizeChange', 'update:pageSize', 'update:pageIndex'], + setup(props: ComponentProps, { emit }) { + + // 极简模式下,可选的下拉选择页码 + const litePageOptions = computed(() => liteSelectOptions(totalPages.value)) + + // 当前页码 + const cursor = computed({ + get() { + // 是否需要修正错误的pageIndex + if (!props.showTruePageIndex && props.pageIndex > totalPages.value) { + emit('update:pageIndex', totalPages.value || 1) + return totalPages.value || 1 + } + return props.pageIndex || 1 + }, + set(val: number) { + emit('update:pageIndex', val) + } + }) + const changePageNo = ref(props.pageIndex) + // 输入框显示的页码 + const inputPageNum = computed({ + get() { + return props.pageIndex + }, + set(val: number) { + changePageNo.value = val + } + }) + // 每页显示最大条目数量 + const currentPageSize = computed({ + get() { + return props.pageSize + }, + set(val: number) { + emit('update:pageSize', val) + } + }) + // 总页数 + const totalPages = computed(() => Math.ceil(props.total / props.pageSize)) + + const changeCursorEmit = (val: number) => { + cursor.value = val + changePageNo.value = val + emit('pageIndexChange', val) + } + // 输入跳转页码 + const jumpPageChange = (currentPage: string) => { + const curPage = +currentPage + if (isNaN(curPage) || curPage < 1 || curPage > totalPages.value) { + inputPageNum.value = props.pageIndex + return + } + inputPageNum.value = curPage + } + // 跳转指定页码 + const jump = (e: KeyboardEvent | 'btn') => { + if ((e === 'btn' || e.key === 'Enter') && cursor.value !== changePageNo.value) { + cursor.value = changePageNo.value + } + } + // 每页条数改变 + const pageSizeChange = (value: number) => { + currentPageSize.value = value + // 页数改变后,如果当前页码超出最大页码时修正 + if (props.autoFixPageIndex) { + nextTick(() => { + if (cursor.value > totalPages.value) { + changeCursorEmit(totalPages.value) + } + }) + } + emit('pageSizeChange', value) + } + // 极简模式下的跳转页码 + const litePageIndexChange = (page: {name: string; value: number;}) => { + changeCursorEmit(page.value) + } + + return { + cursor, + totalPages, + jump, + changeCursorEmit, + inputPageNum, + jumpPageChange, + currentPageSize, + pageSizeChange, + litePageOptions, + litePageIndexChange + } + }, + render() { + + const { + total, + pageSizeOptions, + // TODO 依赖select组件,待完善 + // pageSizeDirection, + preLink, + nextLink, + size, + canJumpPage, + canChangePageSize, + canViewTotal, + totalItemText, + goToText, + maxItems, + showJumpButton, + showTruePageIndex, + lite, + showPageSelector, + haveConfigMenu, + autoHide, + $slots, + + cursor, + totalPages, + jump, + inputPageNum, + jumpPageChange, + currentPageSize, + pageSizeChange, + changeCursorEmit, + litePageOptions, + litePageIndexChange + } = this + + return ( + // autoHide为 true 并且 pageSizeOptions最小值 > total 不展示分页 + autoHide && Math.min(...pageSizeOptions) > total + ? null + :
+ { + canChangePageSize && !lite && +
+ +
+ } + { + // 总页数显示 + ((!lite || (lite && showPageSelector)) && canViewTotal) && +
{totalItemText}: {total}
+ } + { + // 极简模式下的选择页码下拉框 + lite && showPageSelector && +
+ +
+ } + + {/* 页码展示 */} + + + { + // 跳转页码 + canJumpPage && !lite && + + } + { + // 极简模式下是否显示配置 + lite && haveConfigMenu && + + {$slots.default?.()} + + } +
+ ) + } +}) \ No newline at end of file diff --git a/devui/pagination/src/use-pagination.ts b/devui/pagination/src/use-pagination.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6663598c32a3fd3beaf84043e49fe6fea18b5c8 --- /dev/null +++ b/devui/pagination/src/use-pagination.ts @@ -0,0 +1,120 @@ +import { PropType, ExtractPropTypes } from 'vue'; + +type AppendToBodyDirection = 'rightDown' | 'rightUp' | 'leftUp' | 'leftDown' | 'centerDown' | 'centerUp'; + +interface ConnectedPosition { + originX : 'start' | 'center' | 'end' + originY : 'top' | 'center' | 'bottom' + + overlayX : 'start' | 'center' | 'end' + overlayY : 'top' | 'center' | 'bottom' + + weight ?: number + offsetX ?: number + offsetY ?: number + panelClass ?: string | string[] +} + +type Size = 'lg' | '' | 'sm' + +export const componentProps = { + pageSize: { + type: Number, + default: 10 + }, + total: { + type: Number, + default: 0 + }, + pageSizeOptions: { + type: Array as PropType, + default: () => [5, 10, 20, 50] + }, + pageSizeDirection: { + type: Array as PropType>, + default: () => ['centerDown', 'centerUp'] + }, + pageIndex: { + type: Number, + default: 1 + }, + maxItems: { + type: Number, + default: 10 + }, + preLink: { + type: String, + default: '<' + }, + nextLink: { + type: String, + default: '>' + }, + size: { + type: String as PropType, + default: '' + }, + canJumpPage: { + type: Boolean, + default: false + }, + canChangePageSize: { + type: Boolean, + default: false + }, + canViewTotal: { + type: Boolean, + default: false + }, + totalItemText: { + type: String, + default: '所有条目' + }, + goToText: { + type: String, + default: '跳至' + }, + showJumpButton: { + type: Boolean, + default: false + }, + showTruePageIndex: { + type: Boolean, + default: false + }, + lite: { + type: Boolean, + default: false + }, + showPageSelector: { + type: Boolean, + default: true + }, + haveConfigMenu: { + type: Boolean, + default: false + }, + autoFixPageIndex: { + type: Boolean, + default: true + }, + autoHide: { + type: Boolean, + default: false + }, + 'onUpdate:pageIndex': { + type: Function as PropType<(v: number) => void> + }, + 'onUpdate:pageSize': { + type: Function as PropType<(v: number) => void> + }, + 'onPageIndexChange': { + type: Function as PropType<(v: number) => void> + }, + 'onPageSizeChange': { + type: Function as PropType<(v: number) => void> + } +} as const + +// 组件props +export type ComponentProps = ExtractPropTypes diff --git a/devui/pagination/src/utils.ts b/devui/pagination/src/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..8584c1a57d815a7b672b0c5f966fe48f731dfc9e --- /dev/null +++ b/devui/pagination/src/utils.ts @@ -0,0 +1,66 @@ +/** + * 功能函数 + */ + +// 处理页码显示 +// 1 ... 5 6 7 8 ... 11 +export const handlePages = (cursor: number, maxItems: number, totalPages: number): number[] => { + const currentPage = cursor // 当前页码 + const maxPages = maxItems // 能显示的最大页码 + if (maxPages >= totalPages) { + return [2, totalPages] + } + + const midPages = maxPages - 2 // 中间显示的页码 + // 1 ... 5 6 7 8 ... 11 + // 获取 5 和 8 + let midStart = currentPage - (midPages >> 1) + let midEnd = currentPage + (midPages - 1 >> 1) + + if (midStart < 2) { + midStart = 2 + midEnd = maxPages - 2 + } + + if (midEnd > totalPages) { + midStart = totalPages - maxPages + 3 + midEnd = totalPages + } + + return [midStart, midEnd] +} + +// 处理极简模式下的页码下拉多选显示 +export function liteSelectOptions(total: number): Array<{name: string; value: number;}> { + return new Array(total || 1).fill(0).map((v: number, index: number) => { + return { + name: `${index + 1}/${total}`, + value: index + 1 + } + }) +} + +// 事件处理 +export function on(element: HTMLElement | Document, eventName: string, handler: (this: Element, ev: Event) => any): void { + if (document.addEventListener) { + if (element && eventName && handler) { + element.addEventListener(eventName, handler, false) + } + } else { + if (element && eventName && handler) { + (element as any).attachEvent('on' + eventName, handler) + } + } +} +export function off(element: HTMLElement | Document, eventName: string, handler: (this: Element, ev: Event) => any): void { + if (document.removeEventListener) { + if (element && eventName && handler) { + element.removeEventListener(eventName, handler, false) + } + } else { + if (element && eventName && handler) { + (element as any).detachEvent('on' + eventName, handler) + } + } +} + diff --git a/sites/components/pagination/index.md b/sites/components/pagination/index.md new file mode 100644 index 0000000000000000000000000000000000000000..b21a10e54af279e5c7e04d13d42697304fc5adf4 --- /dev/null +++ b/sites/components/pagination/index.md @@ -0,0 +1,575 @@ +# Pagination 分页 + +分页器。 + +### 何时使用 + +当加载/渲染所有数据将花费很多时间时,可以切换页码浏览数据。 + + +### 基本用法 + +**size = 'sm'** + + + + +**size = 'md'** + + + + +**size = 'lg'** + + + + +**Custom Style** + + + +```html +size = 'sm' + + +size = 'md' + + +size = 'lg' + + +Custom Style + +``` + + +### 极简模式 +极简模式适用于一些有大量信息的页面,可以简化页面的复杂度。 + +**Simple Mode** + + + + +**Super Simple Mode** + + + + +**haveConfigMenu = "true"** + + +
+
show field
+
setting
+
+
+
display method
+
+ + +
+
+
+ +```html +Simple Mode + + +Super Simple Mode + + +haveConfigMenu = "true" + +
+
show field
+
setting
+
+
+
display method
+
+ + +
+
+
+``` +```less +/* 配置中的每一项,自定义项建议应用此样式或在此基础上修改 */ +.pagination-config-item { + padding-bottom: 8px; + padding-top: 4px; + border-bottom: 1px solid $devui-line; +} + +/* 配置中每一项的标题样式,自定义项建议应用此样式或在此基础上修改 */ +.config-item-title { + color: $devui-line; + padding-left: 8px; + font-size: $devui-font-size; + line-height: 1.5; +} + +.config-item-words { + color: $devui-text; + padding-left: 8px; + font-size: $devui-font-size; + margin-top: 4px; +} + +.config-item-words:hover { + background-color: $devui-area; + cursor: pointer; +} +``` + + +### 多种配置 +支持设置输入跳转、显示跳转按钮;设置pageSize等功能。 + +
+ + + +
+ + + +
+ + + +```html + + + + + +``` + + +### 特殊情况 +特殊场景下分页器的显示。 + +
+When the value of pageIndex exceeds the maximum page number, enable showTruePageIndex to display the value of pageIndex +
+ + + + +
+When the value of pageIndex exceeds the maximum page number, the showTruePageIndex function is disabled and only the maximum page number is displayed. +
+ + + + +
Default Mode
+ + + + +
+ total = 0 + total = 5 + total = 15 +
+ +
Simple Mode
+ + + +
+ total = 0 + total = 20 + total = 30000 + total = 100000 + index = 2 + index = 3 +
+ +```html +
+When the value of pageIndex exceeds the maximum page number, enable showTruePageIndex to display the value of pageIndex +
+ + +
+When the value of pageIndex exceeds the maximum page number, the showTruePageIndex function is disabled and only the maximum page number is displayed. +
+ + +
Default Mode
+ +
+ total = 0 + total = 5 + total = 15 +
+ +
Simple Mode
+ +
+ total = 0 + total = 20 + total = 30000 + total = 100000 + index = 2 + index = 3 +
+``` + + \ No newline at end of file