Skip to content

Commit

Permalink
feat: ✨️新增获取当前实例、下拉加载更多、分页拉取数据钩子,新增列表组件
Browse files Browse the repository at this point in the history
  • Loading branch information
ZRMYDYCG committed Mar 4, 2025
1 parent 781a45a commit c5b17ad
Show file tree
Hide file tree
Showing 18 changed files with 457 additions and 42 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ module.exports = {
rules: {
'vue/multi-word-component-names': 'off', // 禁用vue文件强制多个单词命名
'@typescript-eslint/no-explicit-any': 'off', //允许使用any
'no-unused-vars': 'off', // 禁用未使用变量的检查
'@typescript-eslint/no-unused-vars': 'off', // 禁用 TypeScript 中未使用变量的检查
'@typescript-eslint/no-this-alias': [
'error',
{
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
"@vueuse/core": "^12.7.0",
"axios": "^1.8.1",
"element-plus": "^2.9.5",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"mockjs": "^1.1.0",
"moment": "^2.30.1",
Expand All @@ -48,6 +49,7 @@
"devDependencies": {
"@commitlint/cli": "19.2.0",
"@commitlint/config-conventional": "19.1.0",
"@types/lodash-es": "^4.17.12",
"@types/mockjs": "^1.0.10",
"@types/node": "^22.13.8",
"@typescript-eslint/eslint-plugin": "^6.19.0",
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const api = {}
const apiFiles = import.meta.glob('./modules/*.js', { eager: true })
const apiFiles = import.meta.glob('./modules/*.ts', { eager: true })

Object.entries(apiFiles).forEach(([path, module]) => {
const fileName = (path.match(/\/(\w+)\./) as RegExpMatchArray)[1]
Expand Down
2 changes: 1 addition & 1 deletion src/components/Column/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ withDefaults(
scrollbar: boolean
}>(),
{
scrollbar: false,
scrollbar: true,
},
)
Expand Down
133 changes: 133 additions & 0 deletions src/components/List/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
<!--
* 列表组件
-->
<template>
<div class="gc-list">
<div class="gc-list__wrapper">
<a
v-for="(item, index) in list"
:key="index"
href="javascript:void(0)"
class="gc-list__item is-active:text-[#26224e] is-active:bg-white is-active:shadow-[5px_15px_30px_0px_#f0f0f0] last:border-b-0 hover:bg-white hover:text-[#26224e] hover:shadow-[5px_15px_30px_0px_#f0f0f0]"
:class="{
'is-active': currentIndex === getObjectAttrValue(item, options.key),
}"
:draggable="draggable"
@click="handleChangeItem(item)"
@click.right="handleContextMenu($event, item)"
>
<slot :item="item" :index="index">
{{ getObjectAttrValue(item, options.label) }}
</slot>
</a>
<el-empty v-if="isEmpty" :image-size="60" description="暂无数据" />
</div>
</div>
</template>
<script>
import { useCurrentInstance } from '@/hooks'
import { get as getObjectAttrValue } from 'lodash'
import { computed, nextTick, ref } from 'vue'

export default {
name: 'GcList',
props: {
list: {
type: Array,
default: () => [],
},
options: {
type: Object,
default: () => ({
// 主键key字段
key: 'id',
// 展示内容字段
label: 'name',
}),
},
// item是否可以拖拽
draggable: {
type: Boolean,
default: false,
},
// 切换标签前的回调函数,返回 false 可阻止切换,支持返回 Promise
beforeChange: {
type: Function,
default: null,
},
},
emits: ['click-item', 'change', 'context-menu'],
setup(props, { emit }) {
const { $is } = useCurrentInstance()
const { isFunction, isAsyncFunction } = $is

const currentIndex = ref('')

const isEmpty = computed(() => {
return props.list.length === 0
})

// 切换item选择
const handleChangeItem = async (item) => {
emit('click-item', item)

const index = props.list.findIndex((listItem) => {
return getObjectAttrValue(listItem, props.options.key) === getObjectAttrValue(item, props.options.key)
})

const value = getObjectAttrValue(props.list[index], props.options.key)

if (isFunction(props.beforeChange)) {
let changeConfirm

if (isAsyncFunction(props.beforeChange)) {
await new Promise((resolve) => {
props
.beforeChange(index, item, value)
.then((status) => {
changeConfirm = status
resolve()
})
.catch(() => {
changeConfirm = false
resolve()
})
})
} else {
changeConfirm = props.beforeChange(index, item, value)
}

if (changeConfirm === false) {
return
}

if (index !== -1) {
currentIndex.value = value
emit('change', currentIndex.value, props.list[index])
}
} else {
currentIndex.value = value
emit('change', currentIndex.value, props.list[index])
}
}

// 鼠标右键点击
const handleContextMenu = (event, item) => {
handleChangeItem(item)
nextTick(() => {
emit('context-menu', event, currentIndex.value, item)
})
}

return {
getObjectAttrValue,
currentIndex,
isEmpty,
handleChangeItem,
handleContextMenu,
}
},
}
</script>

<style scoped></style>
3 changes: 3 additions & 0 deletions src/hooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './modules/useCurrentInstance.ts'
export * from './modules/useLoadMore.ts'
export * from './modules/usePageList.ts'
47 changes: 47 additions & 0 deletions src/hooks/modules/useCurrentInstance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* @description: 当前组件实例hooks,用以提供常用工具等
*/

import type { ComponentInternalInstance, ComponentPublicInstance } from 'vue'
import { getCurrentInstance } from 'vue'
import { useRoute, useRouter } from 'vue-router'

// 定义 proxy 上的自定义属性类型
interface CustomProperties {
$api: any
$HTTP: any
$HTTP_CODE: any
$dict: any
$is: any
$date: any
}

export const useCurrentInstance = () => {
const router = useRouter()
const route = useRoute()

const currentInstance = getCurrentInstance()

if (!currentInstance) {
throw new Error('useCurrentInstance must be called within a setup function')
}

const { proxy } = currentInstance as ComponentInternalInstance & { proxy: ComponentPublicInstance & CustomProperties }

const { $api, $HTTP, $HTTP_CODE, $dict, $is, $date } = proxy

// console.log('useCurrentInstance', currentInstance, proxy, $api, $HTTP, $HTTP_CODE, $dict, $is, $date)

return {
router,
route,
currentInstance,
proxy,
$api,
$HTTP,
$HTTP_CODE,
$dict,
$is,
$date,
}
}
67 changes: 67 additions & 0 deletions src/hooks/modules/useLoadMore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @description: 加载更多
*/
import { debounce } from 'lodash-es'
import { onActivated, onBeforeUnmount, onDeactivated } from 'vue'

interface UseLoadMoreOptions {
type?: 'top' | 'bottom' | 'both'
scrollTopCallback?: () => void
scrollBottomCallback?: () => void
container?: HTMLElement
distance?: number
}

export const useLoadMore = (options: UseLoadMoreOptions = {}) => {
const {
type = 'both',
scrollTopCallback,
scrollBottomCallback,
container = document.documentElement,
distance = 0,
} = options

// 判断滚动条是否滚动到顶部
const isScrollTop = (distance: number = 0) => {
const scrollTop = container.scrollTop
return scrollTop - distance <= 0
}

// 判断滚动条是否滚动到底部
const isScrollBottom = (distance: number = 0) => {
const scrollHeight = container.scrollHeight
const scrollTop = container.scrollTop
const clientHeight = container.clientHeight
return scrollHeight - distance <= scrollTop + clientHeight
}

// 滚动监听回调
const handleScroll = debounce(() => {
if (['top', 'both'].includes(type) && isScrollTop(distance)) {
scrollTopCallback?.()
}

if (['bottom', 'both'].includes(type) && isScrollBottom(distance)) {
scrollBottomCallback?.()
}
}, 300)

container.addEventListener('scroll', handleScroll)

onBeforeUnmount(() => {
container.removeEventListener('scroll', handleScroll)
})

onActivated(() => {
container.addEventListener('scroll', handleScroll)
})

onDeactivated(() => {
container.removeEventListener('scroll', handleScroll)
})

return {
isScrollTop,
isScrollBottom,
}
}
Loading

0 comments on commit c5b17ad

Please sign in to comment.