feat: 自定义table开发中

master
xjs 2025-03-13 11:29:14 +08:00
parent bc778e01cd
commit 7a3e90e9db
15 changed files with 488 additions and 18 deletions

View File

@ -4,8 +4,7 @@
<view
v-for="(column, index) in columns"
:key="index"
class="table-cell"
:class="[`text-${column.align || 'center'}`]"
:class="[`text-${column.align || 'center'}`, 'table-cell']"
:style="{ width: column.width }"
>
<text>{{ column.label }}</text>
@ -15,8 +14,7 @@
<view
v-for="(column, colIndex) in columns"
:key="colIndex"
class="table-cell"
:class="[`text-${column.align || 'center'}`]"
:class="[`text-${column.align || 'center'}`, column.colClass, 'table-cell']"
:style="{ width: column.width }"
>
<text>{{ item[column.prop] }}</text>
@ -55,7 +53,7 @@ const { columns } = useTable()
display: flex;
align-items: center;
justify-content: center;
padding: 8rpx 0;
font-size: 24rpx;
color: #333333;
box-sizing: border-box;

View File

@ -13,6 +13,7 @@ const props = defineProps<{
label: string
width?: string
align?: 'left' | 'center' | 'right'
colClass?: string
}>()
const { addColumn } = useTableInject()
@ -24,6 +25,7 @@ onMounted(() => {
label: props.label,
width: props.width,
align: props.align,
colClass: props.colClass,
slot: slot,
})
})

View File

@ -0,0 +1,118 @@
<template>
<view class="table">
<view class="table-header">
<view
v-for="(column, index) in children"
:key="index"
class="table-cell"
:class="[`text-${column.align || 'center'}`]"
:style="{ width: column.width }"
>
<text>{{ column.label }}</text>
</view>
</view>
<view class="table-row">
<view class="table-cell" :class="[`text-${indexColumn.align || 'center'} w-full`]">
<TableCol
:prop="indexColumn.prop"
:label="indexColumn.label"
:width="indexColumn.width"
:align="indexColumn.align"
class="w-full"
>
<template #value="{ index }">
<text>{{ index + 1 }}</text>
</template>
</TableCol>
<slot class="w-full"></slot>
</view>
</view>
<view class="text-center p-[32rpx]" v-show="data.length === 0"></view>
</view>
</template>
<script lang="ts" setup>
import { useChildren } from './useChildren'
import { TABLE_KEY } from './types'
import { isObj, uuid } from './utils'
import TableCol from './WXXTableCol.vue'
const props = defineProps<{
data: any[]
index?: boolean
}>()
const { children, linkChildren } = useChildren(TABLE_KEY)
linkChildren({ props })
const indexUUID = uuid()
const indexColumn = ref({
prop: indexUUID,
label: '序号',
width: '100rpx',
sortable: false,
fixed: false,
align: 'left',
...(isObj(props.index) ? props.index : {}),
})
</script>
<style scoped>
.table {
overflow: hidden;
width: 100%;
border: 2rpx solid #eee;
box-sizing: border-box;
}
.table-header {
display: flex;
background-color: #f5f5f5;
font-weight: bold;
min-height: 60rpx;
}
.table-cell {
display: flex;
align-items: center;
justify-content: center;
padding: 16rpx;
font-size: 24rpx;
width: 100%;
color: #333333;
box-sizing: border-box;
}
.table-header .table-cell {
text-align: center;
}
.table-row {
display: flex;
border-top: 1px solid #ddd;
}
.table-row .table-cell {
text-align: center;
}
.table-row:first-child {
border-top: none;
}
.table-cell:not(:last-child) {
border-right: 1px solid #ddd;
}
.text-left {
justify-content: flex-start;
}
.text-center {
justify-content: center;
}
.text-right {
justify-content: flex-end;
}
</style>

View File

@ -0,0 +1,103 @@
<template>
<view :class="`w-full`">
<view
:class="`table-cell`"
v-for="(row, index) in column"
:key="index"
@click="handleRowClick(index)"
:style="cellStyle"
>
<slot name="value" v-if="$slots.value" :row="getScope(index)" :index="index"></slot>
<text :class="`table-value`" v-else>{{ row }}</text>
</view>
</view>
</template>
<script lang="ts" setup>
import { useParent } from './useParent'
import { TABLE_KEY } from './types'
import { isDef, isFunction } from './utils'
const props = defineProps<{
prop?: string
label?: string
width?: string
align?: string
}>()
const { parent: table, index: columnIndex } = useParent(TABLE_KEY)
const column = computed(() => {
if (!isDef(table)) {
return []
}
const column: any[] = table.props.data.map((item) => {
return item[props.prop]
})
return column
})
/**
* 行点击事件
* @param index 行下标
*/
function handleRowClick(index: number) {
if (!isDef(table)) {
return
}
isFunction(table.rowClick) && table.rowClick(index)
}
//
function getScope(index: number) {
if (!isDef(table)) {
return {}
}
return table.props.data[index] || {}
}
const cellStyle = computed(() => {
let style: {
width: string
textAlign: 'left' | 'center' | 'right'
} = { width: '', textAlign: 'center' }
let _currentStyle = table.internalChildren[columnIndex.value].props
if (_currentStyle.width) {
style.width = _currentStyle.width
}
if (_currentStyle.align) {
style.textAlign = _currentStyle.align
}
return style
})
</script>
<style scoped>
.table-cell {
display: flex;
align-items: center;
justify-content: center;
/* padding: 16rpx; */
font-size: 20rpx;
color: #333333;
border-right: 1px solid #ddd;
}
.table-cell:last-child {
border-right: none;
}
.text-left {
justify-content: flex-start;
}
.text-center {
justify-content: center;
}
.text-right {
justify-content: flex-end;
}
</style>

View File

@ -0,0 +1,21 @@
import type { CSSProperties, ExtractPropTypes, InjectionKey } from 'vue'
import type { PropType } from 'vue'
export const tableProps = {
data: [],
}
export type TableProps = ExtractPropTypes<typeof tableProps>
export type TableProvide = {
props: Omit<TableProps, 'index' | 'customStyle' | 'customClass'>
state: {
scrollLeft: number
}
rowClick: (index: number) => void
getIsLastFixed: (column: { fixed: boolean; prop: string }) => boolean
getFixedStyle: (index: number, style: CSSProperties) => CSSProperties
}
export const TABLE_KEY: InjectionKey<TableProvide> = Symbol('wd-table')

View File

@ -0,0 +1,122 @@
import {
provide,
reactive,
getCurrentInstance,
type VNode,
type InjectionKey,
type VNodeNormalizedChildren,
type ComponentPublicInstance,
type ComponentInternalInstance,
} from 'vue'
// 小程序端不支持从vue导出的isVNode方法参考uni-mp-vue的实现
function isVNode(value: any): value is VNode {
return value ? value.__v_isVNode === true : false
}
export function flattenVNodes(children: VNodeNormalizedChildren) {
const result: VNode[] = []
const traverse = (children: VNodeNormalizedChildren) => {
if (Array.isArray(children)) {
children.forEach((child) => {
if (isVNode(child)) {
result.push(child)
if (child.component?.subTree) {
result.push(child.component.subTree)
traverse(child.component.subTree.children)
}
if (child.children) {
traverse(child.children)
}
}
})
}
}
traverse(children)
return result
}
const findVNodeIndex = (vnodes: VNode[], vnode: VNode) => {
const index = vnodes.indexOf(vnode)
if (index === -1) {
return vnodes.findIndex(
(item) =>
vnode.key !== undefined &&
vnode.key !== null &&
item.type === vnode.type &&
item.key === vnode.key,
)
}
return index
}
// sort children instances by vnodes order
export function sortChildren(
parent: ComponentInternalInstance,
publicChildren: ComponentPublicInstance[],
internalChildren: ComponentInternalInstance[],
) {
const vnodes =
parent && parent.subTree && parent.subTree.children
? flattenVNodes(parent.subTree.children)
: []
internalChildren.sort((a, b) => findVNodeIndex(vnodes, a.vnode) - findVNodeIndex(vnodes, b.vnode))
const orderedPublicChildren = internalChildren.map((item) => item.proxy!)
publicChildren.sort((a, b) => {
const indexA = orderedPublicChildren.indexOf(a)
const indexB = orderedPublicChildren.indexOf(b)
return indexA - indexB
})
}
export function useChildren<
// eslint-disable-next-line
Child extends ComponentPublicInstance = ComponentPublicInstance<{}, any>,
ProvideValue = never,
>(key: InjectionKey<ProvideValue>) {
const publicChildren: Child[] = reactive([])
const internalChildren: ComponentInternalInstance[] = reactive([])
const parent = getCurrentInstance()!
const linkChildren = (value?: any) => {
const link = (child: ComponentInternalInstance) => {
if (child.proxy) {
internalChildren.push(child)
publicChildren.push(child.proxy as Child)
sortChildren(parent, publicChildren, internalChildren)
}
}
const unlink = (child: ComponentInternalInstance) => {
const index = internalChildren.indexOf(child)
publicChildren.splice(index, 1)
internalChildren.splice(index, 1)
}
provide(
key,
Object.assign(
{
link,
unlink,
children: publicChildren,
internalChildren,
},
value,
),
)
}
return {
children: publicChildren,
linkChildren,
}
}

View File

@ -0,0 +1,40 @@
import {
ref,
inject,
computed,
onUnmounted,
type InjectionKey,
getCurrentInstance,
type ComponentPublicInstance,
type ComponentInternalInstance,
} from 'vue'
type ParentProvide<T> = T & {
link(child: ComponentInternalInstance): void
unlink(child: ComponentInternalInstance): void
children: ComponentPublicInstance[]
internalChildren: ComponentInternalInstance[]
}
export function useParent<T>(key: InjectionKey<ParentProvide<T>>) {
const parent = inject(key, null)
if (parent) {
const instance = getCurrentInstance()!
const { link, unlink, internalChildren } = parent
link(instance)
onUnmounted(() => unlink(instance))
const index = computed(() => internalChildren.indexOf(instance))
return {
parent,
index,
}
}
return {
parent: null,
index: ref(-1),
}
}

View File

@ -6,6 +6,7 @@ export type Column = {
width?: string
align?: 'left' | 'center' | 'right'
slot?: any
colClass?: string
}
type TableContext = {

View File

@ -0,0 +1,42 @@
/**
*
* @param target
* @returns {string} type
*/
export function getType(target: unknown): string {
// 得到原生类型
const typeStr = Object.prototype.toString.call(target)
// 拿到类型值
const match = typeStr.match(/\[object (\w+)\]/)
const type = match && match.length ? match[1].toLowerCase() : ''
// 类型值转小写并返回
return type
}
export function isFunction<T extends Function>(value: any): value is T {
return getType(value) === 'function' || getType(value) === 'asyncfunction'
}
export const isDef = <T>(value: T): value is NonNullable<T> => value !== undefined && value !== null
/**
* uuid
* @returns string
*/
export function uuid() {
return s4() + s4() + s4() + s4() + s4() + s4() + s4() + s4()
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1)
}
/**
* @description target
* @param value
* @return {boolean}
*/
export function isObj(value: any): value is object {
return Object.prototype.toString.call(value) === '[object Object]' || typeof value === 'object'
}

View File

@ -57,8 +57,19 @@ const getUserLocation = () => {
type: 'wgs84',
success: (res) => {
const { latitude, longitude } = res
console.log('当前位置的经度:' + res.longitude)
console.log('当前位置的纬度:' + res.latitude)
getUserCity({ lat: latitude, lng: longitude }).then((resp) => {
if (resp.code === 200) {
let _locationCode = (resp.result as { locationCode: string }).locationCode
cities.map((item) => {
item.provinces.map((city) => {
if (city.code === _locationCode) {
chooseCity(city)
}
})
})
}
})
},
fail: () => {
uni.showToast({
@ -69,9 +80,10 @@ const getUserLocation = () => {
})
}
//
onMounted(() => {
getUserLocation()
onLoad(() => {
if (!userStore.userInfo.estimatedAchievement.provinceCode) {
getUserLocation()
}
})
</script>

View File

@ -45,9 +45,14 @@
<view class="h-[16rpx] bg-[#F8F8F8] my-[40rpx]"></view>
<scroll-view scroll-y class="flex flex-col flex-1 pb-safe px-[32rpx] w-auto">
<scroll-view scroll-y class="flex flex-col flex-1 pb-safe px-[32rpx] w-auto" enable-flex>
<WXXTable :data="tableData">
<WXXTableCol prop="universityName" label="院校名称" width="37%">123</WXXTableCol>
<WXXTableCol
prop="universityName"
label="院校名称"
width="37%"
colClass="py-[24rpx]!"
></WXXTableCol>
<WXXTableCol prop="locationName" label="办学地点" width="35%"></WXXTableCol>
<WXXTableCol prop="type" label="院校鉴别" width="28%"></WXXTableCol>
</WXXTable>
@ -116,7 +121,12 @@ const getUniversityList = () => {
}).then((res) => {
if (res.code === 200) {
let result = res.result as any[]
tableData.value = result
tableData.value = result.map((item) => {
return {
...item,
type: item.type === 1 ? '虚假大学' : '真实大学',
}
})
}
})
}

View File

@ -4,6 +4,7 @@
style: {
navigationStyle: 'custom',
},
needLogin: true,
}
</route>
<template>

View File

@ -212,7 +212,8 @@
"layout": "page",
"style": {
"navigationStyle": "custom"
}
},
"needLogin": true
},
{
"path": "home/line/index",

View File

@ -196,9 +196,8 @@ export const verifyUniversity = (params: { keyword: string; provinceName: string
return http.get('/api/zhiYuan/fackUniversitys', params)
}
export const getUserCity = (params: { lat: number; lng: number; username: string }) => {
return http.get('https://secure.geonames.org/findNearbyPlaceNameJSON', {
haPrefix: true,
export const getUserCity = (params: { lat: number; lng: number }) => {
return http.get('/api/location/getcity', {
...params,
})
}

View File

@ -21,7 +21,7 @@ export type City = {
years: number
}
export type IUserCity = {
cities: Array<{ letter: string; provinces: { id: number; provincename: string } }>
cities: Array<{ letter: string; provinces: City[] }>
}
export type ExtraUserInfo = {