feat: 自定义table开发中
parent
bc778e01cd
commit
7a3e90e9db
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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')
|
||||
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ export type Column = {
|
|||
width?: string
|
||||
align?: 'left' | 'center' | 'right'
|
||||
slot?: any
|
||||
colClass?: string
|
||||
}
|
||||
|
||||
type TableContext = {
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ? '虚假大学' : '真实大学',
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
style: {
|
||||
navigationStyle: 'custom',
|
||||
},
|
||||
needLogin: true,
|
||||
}
|
||||
</route>
|
||||
<template>
|
||||
|
|
|
|||
|
|
@ -212,7 +212,8 @@
|
|||
"layout": "page",
|
||||
"style": {
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
"needLogin": true
|
||||
},
|
||||
{
|
||||
"path": "home/line/index",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
Loading…
Reference in New Issue