mirror of
https://gitee.com/wonderful-code/buildadmin
synced 2024-11-23 07:40:18 +00:00
refactor(BaTable):单元格渲染器拆分为独立组件并改用易于扩展的方式加载
This commit is contained in:
parent
a61d313f5a
commit
cbdb8d21bf
@ -4,7 +4,7 @@
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --force",
|
||||
"dev": "esno ./src/utils/build.ts && vite --force",
|
||||
"build": "vite build && esno ./src/utils/build.ts",
|
||||
"lint": "eslint .",
|
||||
"lint-fix": "eslint --fix .",
|
||||
|
144
web/src/components/table/fieldRender/buttons.vue
Normal file
144
web/src/components/table/fieldRender/buttons.vue
Normal file
@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<div v-memo="[field]">
|
||||
<template v-for="(btn, idx) in field.buttons" :key="idx">
|
||||
<template v-if="btn.display ? btn.display(row, field) : true">
|
||||
<!-- 常规按钮 -->
|
||||
<el-button
|
||||
v-if="btn.render == 'basicButton'"
|
||||
v-blur
|
||||
@click="onButtonClick(btn)"
|
||||
:class="btn.class"
|
||||
class="ba-table-render-buttons-item"
|
||||
:type="btn.type"
|
||||
:disabled="btn.disabled && btn.disabled(row, field)"
|
||||
v-bind="btn.attr"
|
||||
>
|
||||
<Icon v-if="btn.icon" :name="btn.icon" />
|
||||
<div v-if="btn.text" class="text">{{ getTranslation(btn.text) }}</div>
|
||||
</el-button>
|
||||
|
||||
<!-- 带提示信息的按钮 -->
|
||||
<el-tooltip
|
||||
v-if="btn.render == 'tipButton' && ((btn.name == 'edit' && baTable.auth('edit')) || btn.name != 'edit')"
|
||||
:disabled="btn.title && !btn.disabledTip ? false : true"
|
||||
:content="getTranslation(btn.title)"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
v-blur
|
||||
@click="onButtonClick(btn)"
|
||||
:class="btn.class"
|
||||
class="ba-table-render-buttons-item"
|
||||
:type="btn.type"
|
||||
:disabled="btn.disabled && btn.disabled(row, field)"
|
||||
v-bind="btn.attr"
|
||||
>
|
||||
<Icon v-if="btn.icon" :name="btn.icon" />
|
||||
<div v-if="btn.text" class="text">{{ getTranslation(btn.text) }}</div>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 带确认框的按钮 -->
|
||||
<el-popconfirm
|
||||
v-if="btn.render == 'confirmButton' && ((btn.name == 'delete' && baTable.auth('del')) || btn.name != 'delete')"
|
||||
:disabled="btn.disabled && btn.disabled(row, field)"
|
||||
v-bind="btn.popconfirm"
|
||||
@confirm="onButtonClick(btn)"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="ml-6">
|
||||
<el-tooltip :disabled="btn.title ? false : true" :content="getTranslation(btn.title)" placement="top">
|
||||
<el-button
|
||||
v-blur
|
||||
:class="btn.class"
|
||||
class="ba-table-render-buttons-item"
|
||||
:type="btn.type"
|
||||
:disabled="btn.disabled && btn.disabled(row, field)"
|
||||
v-bind="btn.attr"
|
||||
>
|
||||
<Icon v-if="btn.icon" :name="btn.icon" />
|
||||
<div v-if="btn.text" class="text">{{ getTranslation(btn.text) }}</div>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
|
||||
<!-- 带提示的可拖拽按钮 -->
|
||||
<el-tooltip
|
||||
v-if="btn.render == 'moveButton' && ((btn.name == 'weigh-sort' && baTable.auth('sortable')) || btn.name != 'weigh-sort')"
|
||||
:disabled="btn.title && !btn.disabledTip ? false : true"
|
||||
:content="getTranslation(btn.title)"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
:class="btn.class"
|
||||
class="ba-table-render-buttons-item move-button"
|
||||
:type="btn.type"
|
||||
:disabled="btn.disabled && btn.disabled(row, field)"
|
||||
v-bind="btn.attr"
|
||||
>
|
||||
<Icon v-if="btn.icon" :name="btn.icon" />
|
||||
<div v-if="btn.text" class="text">{{ getTranslation(btn.text) }}</div>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx } from 'element-plus'
|
||||
import { inject } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const { t, te } = useI18n()
|
||||
const props = defineProps<Props>()
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
|
||||
const onButtonClick = (btn: OptButton) => {
|
||||
if (typeof btn.click === 'function') {
|
||||
btn.click(props.row, props.field)
|
||||
return
|
||||
}
|
||||
baTable.onTableAction(btn.name, props)
|
||||
}
|
||||
|
||||
const getTranslation = (key?: string) => {
|
||||
if (!key) return ''
|
||||
return te(key) ? t(key) : key
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ba-table-render-buttons-item {
|
||||
padding: 4px 5px;
|
||||
height: auto;
|
||||
.icon {
|
||||
font-size: 14px !important;
|
||||
color: var(--ba-bg-color-overlay) !important;
|
||||
}
|
||||
.text {
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
.ba-table-render-buttons-move {
|
||||
cursor: move;
|
||||
}
|
||||
.ml-6 {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.ml-6 + .el-button {
|
||||
margin-left: 6px;
|
||||
}
|
||||
</style>
|
28
web/src/components/table/fieldRender/color.vue
Normal file
28
web/src/components/table/fieldRender/color.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<div :style="{ background: cellValue }" class="ba-table-render-color"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx } from 'element-plus'
|
||||
import { getCellValue } from '/@/components/table/index'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const cellValue = getCellValue(props.row, props.field, props.column, props.index)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ba-table-render-color {
|
||||
height: 25px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
28
web/src/components/table/fieldRender/customRender.vue
Normal file
28
web/src/components/table/fieldRender/customRender.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<component
|
||||
:is="field.customRender"
|
||||
:renderRow="row"
|
||||
:renderField="field"
|
||||
:renderValue="cellValue"
|
||||
:renderColumn="column"
|
||||
:renderIndex="index"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx } from 'element-plus'
|
||||
import { getCellValue } from '/@/components/table/index'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const cellValue = getCellValue(props.row, props.field, props.column, props.index)
|
||||
</script>
|
21
web/src/components/table/fieldRender/customTemplate.vue
Normal file
21
web/src/components/table/fieldRender/customTemplate.vue
Normal file
@ -0,0 +1,21 @@
|
||||
<template>
|
||||
<div>
|
||||
<div v-html="field.customTemplate ? field.customTemplate(row, field, cellValue, column, index) : ''"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx } from 'element-plus'
|
||||
import { getCellValue } from '/@/components/table/index'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const cellValue = getCellValue(props.row, props.field, props.column, props.index)
|
||||
</script>
|
22
web/src/components/table/fieldRender/datetime.vue
Normal file
22
web/src/components/table/fieldRender/datetime.vue
Normal file
@ -0,0 +1,22 @@
|
||||
<template>
|
||||
<div>
|
||||
{{ !cellValue ? '-' : timeFormat(cellValue, field.timeFormat ?? 'yyyy-mm-dd hh:MM:ss') }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx } from 'element-plus'
|
||||
import { getCellValue } from '/@/components/table/index'
|
||||
import { timeFormat } from '/@/utils/common'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const cellValue = getCellValue(props.row, props.field, props.column, props.index)
|
||||
</script>
|
5
web/src/components/table/fieldRender/default.vue
Normal file
5
web/src/components/table/fieldRender/default.vue
Normal file
@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-tag effect="dark" type="danger">Field renderer not found</el-tag>
|
||||
</div>
|
||||
</template>
|
19
web/src/components/table/fieldRender/icon.vue
Normal file
19
web/src/components/table/fieldRender/icon.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<div>
|
||||
<Icon color="var(--el-text-color-primary)" :name="getCellValue(props.row, props.field, props.column, props.index)" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx } from 'element-plus'
|
||||
import { getCellValue } from '/@/components/table/index'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
</script>
|
35
web/src/components/table/fieldRender/image.vue
Normal file
35
web/src/components/table/fieldRender/image.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-image
|
||||
:hide-on-click-modal="true"
|
||||
:preview-teleported="true"
|
||||
:preview-src-list="[fullUrl(cellValue)]"
|
||||
:src="fullUrl(cellValue)"
|
||||
class="ba-table-render-image"
|
||||
></el-image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx } from 'element-plus'
|
||||
import { getCellValue } from '/@/components/table/index'
|
||||
import { fullUrl } from '/@/utils/common'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const cellValue = getCellValue(props.row, props.field, props.column, props.index)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ba-table-render-image {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
}
|
||||
</style>
|
42
web/src/components/table/fieldRender/images.vue
Normal file
42
web/src/components/table/fieldRender/images.vue
Normal file
@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="isArray(cellValue) && cellValue.length">
|
||||
<el-image
|
||||
v-for="(item, idx) in cellValue"
|
||||
:key="idx"
|
||||
:initial-index="idx"
|
||||
:preview-teleported="true"
|
||||
:preview-src-list="arrayFullUrl(cellValue)"
|
||||
class="ba-table-render-images-item"
|
||||
:src="fullUrl(item)"
|
||||
:hide-on-click-modal="true"
|
||||
></el-image>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx } from 'element-plus'
|
||||
import { isArray } from 'lodash-es'
|
||||
import { getCellValue } from '/@/components/table/index'
|
||||
import { arrayFullUrl, fullUrl } from '/@/utils/common'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const cellValue = getCellValue(props.row, props.field, props.column, props.index)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.ba-table-render-images-item {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
</style>
|
@ -1,299 +0,0 @@
|
||||
<template>
|
||||
<!-- Icon -->
|
||||
<Icon class="ba-icon-dark" v-if="field.render == 'icon'" :name="fieldValue ? fieldValue : field.default ?? ''" />
|
||||
|
||||
<!-- switch -->
|
||||
<el-switch
|
||||
v-if="field.render == 'switch'"
|
||||
@change="onChangeField"
|
||||
:model-value="fieldValue.toString()"
|
||||
:loading="row.loading"
|
||||
active-value="1"
|
||||
inactive-value="0"
|
||||
/>
|
||||
|
||||
<!-- image -->
|
||||
<div v-if="field.render == 'image' && fieldValue" class="ba-render-image">
|
||||
<el-image
|
||||
:hide-on-click-modal="true"
|
||||
:preview-teleported="true"
|
||||
:preview-src-list="[fullUrl(fieldValue)]"
|
||||
:src="fullUrl(fieldValue)"
|
||||
></el-image>
|
||||
</div>
|
||||
|
||||
<!-- images -->
|
||||
<div v-if="field.render == 'images'" class="ba-render-image">
|
||||
<template v-if="Array.isArray(fieldValue) && fieldValue.length">
|
||||
<el-image
|
||||
v-for="(item, idx) in fieldValue"
|
||||
:key="idx"
|
||||
:initial-index="idx"
|
||||
:preview-teleported="true"
|
||||
:preview-src-list="arrayFullUrl(fieldValue)"
|
||||
class="images-item"
|
||||
:src="fullUrl(item)"
|
||||
:hide-on-click-modal="true"
|
||||
></el-image>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- tag -->
|
||||
<div v-if="field.render == 'tag' && fieldValue !== ''">
|
||||
<el-tag :type="getTagType(fieldValue, field.custom)" :effect="field.effect ?? 'light'" :size="field.size ?? 'default'">
|
||||
{{ field.replaceValue ? field.replaceValue[fieldValue] ?? fieldValue : fieldValue }}
|
||||
</el-tag>
|
||||
</div>
|
||||
|
||||
<!-- tags -->
|
||||
<div v-if="field.render == 'tags'">
|
||||
<template v-if="Array.isArray(fieldValue)">
|
||||
<template v-for="(tag, idx) in fieldValue" :key="idx">
|
||||
<el-tag
|
||||
v-if="tag"
|
||||
class="m-10"
|
||||
:type="getTagType(tag, field.custom)"
|
||||
:effect="field.effect ?? 'light'"
|
||||
:size="field.size ?? 'default'"
|
||||
>
|
||||
{{ field.replaceValue ? field.replaceValue[tag] ?? tag : tag }}</el-tag
|
||||
>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag
|
||||
class="m-10"
|
||||
v-if="fieldValue !== ''"
|
||||
:type="getTagType(fieldValue, field.custom)"
|
||||
:effect="field.effect ?? 'light'"
|
||||
:size="field.size ?? 'default'"
|
||||
>
|
||||
{{ field.replaceValue ? field.replaceValue[fieldValue] ?? fieldValue : fieldValue }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- url -->
|
||||
<div v-if="field.render == 'url' && fieldValue">
|
||||
<el-input :model-value="fieldValue" :placeholder="t('Link address')">
|
||||
<template #append>
|
||||
<el-button
|
||||
@click="typeof field.click == 'function' ? field.click(row, field, fieldValue, column, index) : openUrl(fieldValue, field)"
|
||||
>
|
||||
<Icon :color="'#606266'" name="el-icon-Position" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<!-- datetime -->
|
||||
<div v-if="field.render == 'datetime'">
|
||||
{{ !fieldValue ? '-' : timeFormat(fieldValue, field.timeFormat ?? undefined) }}
|
||||
</div>
|
||||
|
||||
<!-- color -->
|
||||
<div v-if="field.render == 'color'">
|
||||
<div :style="{ background: fieldValue }" class="ba-render-color"></div>
|
||||
</div>
|
||||
|
||||
<!-- customTemplate 自定义模板 -->
|
||||
<div
|
||||
v-if="field.render == 'customTemplate'"
|
||||
v-html="field.customTemplate ? field.customTemplate(row, field, fieldValue, column, index) : ''"
|
||||
></div>
|
||||
|
||||
<!-- 自定义组件/函数渲染 -->
|
||||
<component
|
||||
v-if="field.render == 'customRender'"
|
||||
:is="field.customRender"
|
||||
:renderRow="row"
|
||||
:renderField="field"
|
||||
:renderValue="fieldValue"
|
||||
:renderColumn="column"
|
||||
:renderIndex="index"
|
||||
/>
|
||||
|
||||
<!-- 按钮组 -->
|
||||
<!-- 只对默认的编辑、删除、排序按钮进行鉴权,其他按钮请通过 display 属性控制按钮是否显示 -->
|
||||
<div v-if="field.render == 'buttons' && field.buttons" v-memo="[field]">
|
||||
<template v-for="(btn, idx) in field.buttons" :key="idx">
|
||||
<template v-if="btn.display ? btn.display(row, field) : true">
|
||||
<!-- 常规按钮 -->
|
||||
<el-button
|
||||
v-if="btn.render == 'basicButton'"
|
||||
v-blur
|
||||
@click="onButtonClick(btn)"
|
||||
:class="btn.class"
|
||||
class="table-operate"
|
||||
:type="btn.type"
|
||||
:disabled="btn.disabled && btn.disabled(row, field)"
|
||||
v-bind="btn.attr"
|
||||
>
|
||||
<Icon :name="btn.icon" />
|
||||
<div v-if="btn.text" class="table-operate-text">{{ btn.text }}</div>
|
||||
</el-button>
|
||||
|
||||
<!-- 带提示信息的按钮 -->
|
||||
<el-tooltip
|
||||
v-if="btn.render == 'tipButton' && ((btn.name == 'edit' && baTable.auth('edit')) || btn.name != 'edit')"
|
||||
:disabled="btn.title && !btn.disabledTip ? false : true"
|
||||
:content="btn.title ? t(btn.title) : ''"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
v-blur
|
||||
@click="onButtonClick(btn)"
|
||||
:class="btn.class"
|
||||
class="table-operate"
|
||||
:type="btn.type"
|
||||
:disabled="btn.disabled && btn.disabled(row, field)"
|
||||
v-bind="btn.attr"
|
||||
>
|
||||
<Icon :name="btn.icon" />
|
||||
<div v-if="btn.text" class="table-operate-text">{{ btn.text }}</div>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<!-- 带确认框的按钮 -->
|
||||
<el-popconfirm
|
||||
v-if="btn.render == 'confirmButton' && ((btn.name == 'delete' && baTable.auth('del')) || btn.name != 'delete')"
|
||||
:disabled="btn.disabled && btn.disabled(row, field)"
|
||||
v-bind="btn.popconfirm"
|
||||
@confirm="onButtonClick(btn)"
|
||||
>
|
||||
<template #reference>
|
||||
<div class="ml-6">
|
||||
<el-tooltip :disabled="btn.title ? false : true" :content="btn.title ? t(btn.title) : ''" placement="top">
|
||||
<el-button
|
||||
v-blur
|
||||
:class="btn.class"
|
||||
class="table-operate"
|
||||
:type="btn.type"
|
||||
:disabled="btn.disabled && btn.disabled(row, field)"
|
||||
v-bind="btn.attr"
|
||||
>
|
||||
<Icon :name="btn.icon" />
|
||||
<div v-if="btn.text" class="table-operate-text">{{ btn.text }}</div>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
|
||||
<!-- 带提示的可拖拽按钮 -->
|
||||
<el-tooltip
|
||||
v-if="btn.render == 'moveButton' && ((btn.name == 'weigh-sort' && baTable.auth('sortable')) || btn.name != 'weigh-sort')"
|
||||
:disabled="btn.title && !btn.disabledTip ? false : true"
|
||||
:content="btn.title ? t(btn.title) : ''"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
:class="btn.class"
|
||||
class="table-operate move-button"
|
||||
:type="btn.type"
|
||||
:disabled="btn.disabled && btn.disabled(row, field)"
|
||||
v-bind="btn.attr"
|
||||
>
|
||||
<Icon :name="btn.icon" />
|
||||
<div v-if="btn.text" class="table-operate-text">{{ btn.text }}</div>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, inject } from 'vue'
|
||||
import type { TagProps, TableColumnCtx } from 'element-plus'
|
||||
import { openUrl } from '/@/components/table'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { fullUrl, arrayFullUrl, timeFormat } from '/@/utils/common'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
|
||||
const { t } = useI18n()
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
const props = defineProps<Props>()
|
||||
|
||||
// 字段值(单元格值)
|
||||
const fieldName = ref(props.field.prop)
|
||||
const fieldValue = ref(fieldName.value ? props.row[fieldName.value] : '')
|
||||
if (fieldName.value && fieldName.value.indexOf('.') > -1) {
|
||||
let fieldNameArr = fieldName.value.split('.')
|
||||
let val: any = ref(props.row[fieldNameArr[0]])
|
||||
for (let index = 1; index < fieldNameArr.length; index++) {
|
||||
val.value = val.value ? val.value[fieldNameArr[index]] ?? '' : ''
|
||||
}
|
||||
fieldValue.value = val.value
|
||||
}
|
||||
|
||||
if (props.field.renderFormatter && typeof props.field.renderFormatter == 'function') {
|
||||
fieldValue.value = props.field.renderFormatter(props.row, props.field, fieldValue.value, props.column, props.index)
|
||||
}
|
||||
|
||||
const onChangeField = (value: any) => {
|
||||
baTable.onTableAction('field-change', { value: value, ...props })
|
||||
}
|
||||
|
||||
const onButtonClick = (btn: OptButton) => {
|
||||
if (typeof btn.click === 'function') {
|
||||
btn.click(props.row, props.field)
|
||||
return
|
||||
}
|
||||
baTable.onTableAction(btn.name, props)
|
||||
}
|
||||
|
||||
const getTagType = (value: string, custom: any): TagProps['type'] => {
|
||||
return custom && custom[value] ? custom[value] : 'primary'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.m-10 {
|
||||
margin: 4px;
|
||||
}
|
||||
.ba-render-image {
|
||||
text-align: center;
|
||||
}
|
||||
.images-item {
|
||||
width: 50px;
|
||||
margin: 0 5px;
|
||||
}
|
||||
.el-image {
|
||||
height: 36px;
|
||||
width: 36px;
|
||||
}
|
||||
.table-operate-text {
|
||||
padding-left: 5px;
|
||||
}
|
||||
.table-operate {
|
||||
padding: 4px 5px;
|
||||
height: auto;
|
||||
}
|
||||
.table-operate .icon {
|
||||
font-size: 14px !important;
|
||||
color: var(--ba-bg-color-overlay) !important;
|
||||
}
|
||||
.move-button {
|
||||
cursor: move;
|
||||
}
|
||||
.ml-6 {
|
||||
display: inline-flex;
|
||||
vertical-align: middle;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.ml-6 + .el-button {
|
||||
margin-left: 6px;
|
||||
}
|
||||
.ba-render-color {
|
||||
height: 25px;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
44
web/src/components/table/fieldRender/switch.vue
Normal file
44
web/src/components/table/fieldRender/switch.vue
Normal file
@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-switch v-if="field.prop" @change="onChange" :model-value="cellValue" :loading="loading" active-value="1" inactive-value="0" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx } from 'element-plus'
|
||||
import { inject, ref } from 'vue'
|
||||
import { getCellValue } from '/@/components/table/index'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const loading = ref(false)
|
||||
const props = defineProps<Props>()
|
||||
const baTable = inject('baTable') as baTableClass
|
||||
const cellValue = ref(getCellValue(props.row, props.field, props.column, props.index))
|
||||
|
||||
if (typeof cellValue.value === 'number') {
|
||||
cellValue.value = cellValue.value.toString()
|
||||
}
|
||||
|
||||
const onChange = (value: string | number | boolean) => {
|
||||
loading.value = true
|
||||
baTable.api
|
||||
.postData('edit', {
|
||||
[baTable.table.pk!]: props.row[baTable.table.pk!],
|
||||
[props.field.prop!]: value,
|
||||
})
|
||||
.then(() => {
|
||||
cellValue.value = value
|
||||
baTable.onTableAction('field-change', { value: value, ...props })
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false
|
||||
})
|
||||
}
|
||||
</script>
|
28
web/src/components/table/fieldRender/tag.vue
Normal file
28
web/src/components/table/fieldRender/tag.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-tag v-if="cellValue != ''" :type="getTagType(cellValue, field.custom)" :effect="field.effect ?? 'light'" :size="field.size ?? 'default'">
|
||||
{{ !isEmpty(field.replaceValue) ? field.replaceValue[cellValue] ?? cellValue : cellValue }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx, TagProps } from 'element-plus'
|
||||
import { isEmpty } from 'lodash-es'
|
||||
import { getCellValue } from '/@/components/table/index'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const cellValue = getCellValue(props.row, props.field, props.column, props.index)
|
||||
|
||||
const getTagType = (value: string, custom: any): TagProps['type'] => {
|
||||
return !isEmpty(custom) && custom[value] ? custom[value] : 'primary'
|
||||
}
|
||||
</script>
|
54
web/src/components/table/fieldRender/tags.vue
Normal file
54
web/src/components/table/fieldRender/tags.vue
Normal file
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div>
|
||||
<template v-if="isArray(cellValue)">
|
||||
<template v-for="(tag, idx) in cellValue" :key="idx">
|
||||
<el-tag
|
||||
v-if="tag != ''"
|
||||
class="m-4"
|
||||
:type="getTagType(tag, field.custom)"
|
||||
:effect="field.effect ?? 'light'"
|
||||
:size="field.size ?? 'default'"
|
||||
>
|
||||
{{ !isEmpty(field.replaceValue) ? field.replaceValue[tag] ?? tag : tag }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</template>
|
||||
<template v-else>
|
||||
<el-tag
|
||||
v-if="cellValue != ''"
|
||||
:type="getTagType(cellValue, field.custom)"
|
||||
:effect="field.effect ?? 'light'"
|
||||
:size="field.size ?? 'default'"
|
||||
>
|
||||
{{ !isEmpty(field.replaceValue) ? field.replaceValue[cellValue] ?? cellValue : cellValue }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx, TagProps } from 'element-plus'
|
||||
import { isArray, isEmpty } from 'lodash-es'
|
||||
import { getCellValue } from '/@/components/table/index'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
const cellValue = getCellValue(props.row, props.field, props.column, props.index)
|
||||
|
||||
const getTagType = (value: string, custom: any): TagProps['type'] => {
|
||||
return !isEmpty(custom) && custom[value] ? custom[value] : 'primary'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.m-4 {
|
||||
margin: 4px;
|
||||
}
|
||||
</style>
|
39
web/src/components/table/fieldRender/url.vue
Normal file
39
web/src/components/table/fieldRender/url.vue
Normal file
@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-input :model-value="cellValue" :placeholder="$t('Link address')">
|
||||
<template #append>
|
||||
<el-button @click="openUrl(cellValue, field)">
|
||||
<Icon color="#606266" name="el-icon-Position" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { TableColumnCtx } from 'element-plus'
|
||||
import { getCellValue } from '/@/components/table/index'
|
||||
|
||||
interface Props {
|
||||
row: TableRow
|
||||
field: TableColumn
|
||||
column: TableColumnCtx<TableRow>
|
||||
index: number
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
|
||||
if (props.field.click) {
|
||||
console.warn('baTable.table.column.click 即将废弃,请使用 el-table 的 @cell-click 或单元格自定义渲染代替')
|
||||
}
|
||||
|
||||
const cellValue = getCellValue(props.row, props.field, props.column, props.index)
|
||||
|
||||
const openUrl = (url: string, field: TableColumn) => {
|
||||
if (field.target == '_blank') {
|
||||
window.open(url)
|
||||
} else {
|
||||
window.location.href = url
|
||||
}
|
||||
}
|
||||
</script>
|
@ -1,14 +1,40 @@
|
||||
import { TableColumnCtx } from 'element-plus'
|
||||
import { i18n } from '/@/lang/index'
|
||||
|
||||
/*
|
||||
* 默认Url点击事件处理
|
||||
/**
|
||||
* 获取单元格值
|
||||
*/
|
||||
export const openUrl = (url: string, field: TableColumn) => {
|
||||
if (field.target == '_blank') {
|
||||
window.open(url)
|
||||
} else {
|
||||
window.location.href = url
|
||||
export const getCellValue = (row: TableRow, field: TableColumn, column: TableColumnCtx<TableRow>, index: number) => {
|
||||
if (!field.prop) return ''
|
||||
|
||||
const prop = field.prop
|
||||
let cellValue: any = row[prop]
|
||||
|
||||
// 字段 prop 带 . 比如 user.nickname
|
||||
if (prop.indexOf('.') > -1) {
|
||||
const fieldNameArr = prop.split('.')
|
||||
cellValue = row[fieldNameArr[0]]
|
||||
for (let index = 1; index < fieldNameArr.length; index++) {
|
||||
cellValue = cellValue ? cellValue[fieldNameArr[index]] ?? '' : ''
|
||||
}
|
||||
return cellValue
|
||||
}
|
||||
|
||||
// 若无值,尝试取默认值
|
||||
if (cellValue === undefined || cellValue === null) {
|
||||
cellValue = field.default
|
||||
}
|
||||
|
||||
// 渲染前格式化
|
||||
if (field.renderFormatter && typeof field.renderFormatter == 'function') {
|
||||
cellValue = field.renderFormatter(row, field, cellValue, column, index)
|
||||
console.warn('baTable.table.column.renderFormatter 即将废弃,请直接使用兼容 el-table 的 baTable.table.column.formatter 代替')
|
||||
}
|
||||
if (field.formatter && typeof field.formatter == 'function') {
|
||||
cellValue = field.formatter(row, column, cellValue, index)
|
||||
}
|
||||
|
||||
return cellValue
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -30,22 +30,15 @@
|
||||
v-bind="item"
|
||||
:column-key="(item['columnKey'] ? item['columnKey'] : `table-column-${item.prop}`) || shortUuid()"
|
||||
>
|
||||
<!-- baTable 预设的列 render 方案 -->
|
||||
<!-- ./fieldRender/ 文件夹内的每个组件为一种字段渲染器,组件名称为渲染器名称 -->
|
||||
<template v-if="item.render" #default="scope">
|
||||
<FieldRender
|
||||
:field="item"
|
||||
<component
|
||||
:row="scope.row"
|
||||
:field="item"
|
||||
:column="scope.column"
|
||||
:index="scope.$index"
|
||||
:key="
|
||||
key +
|
||||
'-' +
|
||||
scope.$index +
|
||||
'-' +
|
||||
item.render +
|
||||
'-' +
|
||||
(item.prop ? '-' + item.prop + '-' + scope.row[item.prop] : '')
|
||||
"
|
||||
:is="fieldRenderer[item.render] ?? fieldRenderer['default']"
|
||||
:key="getRenderKey(key, item, scope)"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@ -71,8 +64,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { ElTable, TableInstance } from 'element-plus'
|
||||
import type { Component } from 'vue'
|
||||
import { computed, inject, nextTick, ref } from 'vue'
|
||||
import FieldRender from '/@/components/table/fieldRender/index.vue'
|
||||
import { useConfig } from '/@/stores/config'
|
||||
import type baTableClass from '/@/utils/baTable'
|
||||
import { shortUuid } from '/@/utils/random'
|
||||
@ -89,6 +82,23 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
pagination: true,
|
||||
})
|
||||
|
||||
const fieldRenderer: Record<string, Component> = {}
|
||||
const fieldRendererComponents: Record<string, any> = import.meta.glob('./fieldRender/**.vue', { eager: true })
|
||||
for (const key in fieldRendererComponents) {
|
||||
const fileName = key.replace('./fieldRender/', '').replace('.vue', '')
|
||||
fieldRenderer[fileName] = fieldRendererComponents[key].default
|
||||
}
|
||||
|
||||
const getRenderKey = (key: number, item: TableColumn, scope: any) => {
|
||||
if (item.getRenderKey && typeof item.getRenderKey == 'function') {
|
||||
return item.getRenderKey(scope.row, item, scope.column, scope.$index)
|
||||
}
|
||||
if (item.render == 'switch') {
|
||||
return item.render + item.prop
|
||||
}
|
||||
return key + scope.$index + '-' + item.render + '-' + (item.prop ? '-' + item.prop + '-' + scope.row[item.prop] : '')
|
||||
}
|
||||
|
||||
const onTableSizeChange = (val: number) => {
|
||||
baTable.onTableAction('page-size-change', { size: val })
|
||||
}
|
||||
|
@ -291,24 +291,7 @@ export default class baTable {
|
||||
this.postDel([data.row[this.table.pk!]])
|
||||
},
|
||||
],
|
||||
[
|
||||
'field-change',
|
||||
() => {
|
||||
if (data.field.render == 'switch') {
|
||||
if (!data.field || !data.field.prop) return
|
||||
data.row.loading = true
|
||||
this.api
|
||||
.postData('edit', { [this.table.pk!]: data.row[this.table.pk!], [data.field.prop]: data.value })
|
||||
.then(() => {
|
||||
data.row.loading = false
|
||||
data.row[data.field.prop] = data.value
|
||||
})
|
||||
.catch(() => {
|
||||
data.row.loading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
],
|
||||
['field-change', () => {}],
|
||||
[
|
||||
'com-search',
|
||||
() => {
|
||||
|
@ -1,5 +1,37 @@
|
||||
import pkg from '../../package.json'
|
||||
export const run = () => {
|
||||
console.log(`✨ ${pkg.name} - build successfully!`)
|
||||
import { readdirSync, writeFile } from 'fs'
|
||||
import { trimEnd } from 'lodash-es'
|
||||
|
||||
function getFileNames(dir: string) {
|
||||
const dirents = readdirSync(dir, {
|
||||
withFileTypes: true,
|
||||
})
|
||||
const fileNames: string[] = []
|
||||
for (const dirent of dirents) {
|
||||
if (!dirent.isDirectory()) fileNames.push(dirent.name.replace('.vue', ''))
|
||||
}
|
||||
return fileNames
|
||||
}
|
||||
run()
|
||||
|
||||
/**
|
||||
* 生成 ./types/tableRenderer.d.ts 文件
|
||||
*/
|
||||
const buildTableRendererType = () => {
|
||||
let tableRenderer = getFileNames('./src/components/table/fieldRender/')
|
||||
|
||||
// 增加 slot,去除 default
|
||||
tableRenderer.push('slot')
|
||||
tableRenderer = tableRenderer.filter((item) => item !== 'default')
|
||||
|
||||
let tableRendererContent =
|
||||
'// 可用的表格单元格渲染器,本文件内容以 ./src/components/table/fieldRender/ 目录中的文件名自动生成\ntype tableRenderer =\n | '
|
||||
for (const key in tableRenderer) {
|
||||
tableRendererContent += `'${tableRenderer[key]}'\n | `
|
||||
}
|
||||
tableRendererContent = trimEnd(tableRendererContent, ' | ')
|
||||
|
||||
writeFile('./types/tableRenderer.d.ts', tableRendererContent, 'utf-8', (err) => {
|
||||
if (err) throw err
|
||||
})
|
||||
}
|
||||
|
||||
buildTableRendererType()
|
||||
|
19
web/types/table.d.ts
vendored
19
web/types/table.d.ts
vendored
@ -135,21 +135,8 @@ declare global {
|
||||
show?: boolean
|
||||
// 是否在下拉菜单的复选框显示 默认为true显示
|
||||
enableColumnDisplayControl?: boolean
|
||||
// 渲染为:icon|switch|image|images|tag|url|datetime|buttons|customTemplate|customRender|slot
|
||||
render?:
|
||||
| 'icon'
|
||||
| 'switch'
|
||||
| 'image'
|
||||
| 'images'
|
||||
| 'tag'
|
||||
| 'tags'
|
||||
| 'url'
|
||||
| 'datetime'
|
||||
| 'color'
|
||||
| 'buttons'
|
||||
| 'customTemplate'
|
||||
| 'customRender'
|
||||
| 'slot'
|
||||
// 渲染为 tableRenderer 之一
|
||||
render?: tableRenderer
|
||||
// 操作按钮组
|
||||
buttons?: OptButton[]
|
||||
// 渲染为Tag时:el-tag 组件的主题
|
||||
@ -198,6 +185,8 @@ declare global {
|
||||
multiple?: boolean
|
||||
remoteUrl: string
|
||||
}
|
||||
// 单元格渲染组件的 key,此 key 值改变时单元格将自动重新渲染,默认将根据列配置等属性自动生成
|
||||
getRenderKey?: (row: TableRow, field: TableColumn, column: TableColumnCtx<TableRow>, index: number) => string
|
||||
}
|
||||
|
||||
/* 表格右侧操作按钮 */
|
||||
|
15
web/types/tableRenderer.d.ts
vendored
Normal file
15
web/types/tableRenderer.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
// 可用的表格单元格渲染器,本文件内容以 ./src/components/table/fieldRender/ 目录中的文件名自动生成
|
||||
type tableRenderer =
|
||||
| 'buttons'
|
||||
| 'color'
|
||||
| 'customRender'
|
||||
| 'customTemplate'
|
||||
| 'datetime'
|
||||
| 'icon'
|
||||
| 'image'
|
||||
| 'images'
|
||||
| 'switch'
|
||||
| 'tag'
|
||||
| 'tags'
|
||||
| 'url'
|
||||
| 'slot'
|
Loading…
Reference in New Issue
Block a user