|
@@ -1,22 +1,33 @@
|
|
|
<script lang="ts">
|
|
|
import { tableSelectProps, tableSelectEmits } from './props'
|
|
|
import { ElPopover } from 'element-plus'
|
|
|
+import type { VxeTableInstance } from 'vxe-table'
|
|
|
export default defineComponent({
|
|
|
name: 'FsTableSelect',
|
|
|
props: tableSelectProps,
|
|
|
emits: tableSelectEmits,
|
|
|
setup(props, { emit }) {
|
|
|
- // 是否显示
|
|
|
- const visible = ref(false)
|
|
|
// popover 实例
|
|
|
const popoverRef = ref<InstanceType<typeof ElPopover>>()
|
|
|
+ // 表格实例
|
|
|
+ const tableRef = ref<VxeTableInstance<any>>()
|
|
|
+ // 是否显示
|
|
|
+ const visible = ref(false)
|
|
|
+ // 选中展示的数据
|
|
|
+ const selectLabel = ref<string | Array<any>>('')
|
|
|
+ // 加载状态
|
|
|
+ const loading = ref(false)
|
|
|
+ // 表格数据
|
|
|
+ const tableData = ref<any[]>([])
|
|
|
+ // 页码
|
|
|
+ const pageIndex = ref<number>(1)
|
|
|
|
|
|
// 是否未选中
|
|
|
const isEmpty = computed<boolean>(() => {
|
|
|
if (!props.multiple) {
|
|
|
- return props.moduleValue == null || props.moduleValue === ''
|
|
|
+ return props.modelValue == null || props.modelValue === ''
|
|
|
}
|
|
|
- return !Array.isArray(props.moduleValue) || !props.moduleValue.length
|
|
|
+ return !Array.isArray(props.modelValue) || !props.modelValue.length
|
|
|
})
|
|
|
|
|
|
// 是否需要清空图标
|
|
@@ -32,12 +43,6 @@ export default defineComponent({
|
|
|
emit('focus', e)
|
|
|
}
|
|
|
|
|
|
- const tableData = computed(() => {
|
|
|
- return props.tableConfig?.datasource.then(res => {
|
|
|
- return res
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
/* 关闭弹窗 */
|
|
|
const onBlur = (e: FocusEvent) => {
|
|
|
emit('blur', e)
|
|
@@ -45,17 +50,122 @@ export default defineComponent({
|
|
|
|
|
|
/* 清除事件 */
|
|
|
const onClear = () => {
|
|
|
+ updateModelValue(props.multiple ? [] : null)
|
|
|
+ selectLabel.value = ''
|
|
|
+ // 取消表格全部选中
|
|
|
+ tableRef.value?.clearCheckboxRow()
|
|
|
emit('clear')
|
|
|
}
|
|
|
|
|
|
+ /* 单个清除事件 */
|
|
|
+ const onItemClear = (item: any) => {
|
|
|
+ const list = [...(selectLabel.value as Array<any>)]
|
|
|
+ const index = list.findIndex(x => x[props.labelKey] === item[props.labelKey])
|
|
|
+ list.splice(index, 1)
|
|
|
+ selectLabel.value = list
|
|
|
+
|
|
|
+ // 取消表格选中数据
|
|
|
+ tableRef.value?.toggleCheckboxRow(item)
|
|
|
+ updateModelValue(list.map(x => x[props.valueKey]))
|
|
|
+ emit('item-clear', { item, list })
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 表格单选事件 */
|
|
|
+ const tableRadioChange = (data: any) => {
|
|
|
+ selectLabel.value = data.row[props.labelKey]
|
|
|
+ visible.value = false
|
|
|
+ // 发出选择事件
|
|
|
+ updateModelValue(data.row[props.valueKey])
|
|
|
+ emit('change', data.row)
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 表格多选择事件 */
|
|
|
+ const tableCheckboxChange = (data: any) => {
|
|
|
+ let result = []
|
|
|
+ if (data.checked) {
|
|
|
+ // 使用 Set 去重
|
|
|
+ const uniqueArray = Array.from(
|
|
|
+ new Set([...selectLabel.value, ...data.records].map((x: any) => JSON.stringify(x)))
|
|
|
+ ).map((str: any) => JSON.parse(str))
|
|
|
+ selectLabel.value = uniqueArray
|
|
|
+ result = selectLabel.value.map(x => x[props.valueKey])
|
|
|
+ } else {
|
|
|
+ const selects = selectLabel.value as Array<any>
|
|
|
+ const index = selects.findIndex(x => x[props.valueKey] === data.row[props.valueKey])
|
|
|
+ selects?.splice(index, 1)
|
|
|
+ result = selects.map(x => x[props.valueKey])
|
|
|
+ }
|
|
|
+ // 发出选择事件
|
|
|
+ updateModelValue(result)
|
|
|
+ emit('change', selectLabel.value)
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 分页改变事件 */
|
|
|
+ const paginationChange = (data: number) => {
|
|
|
+ pageIndex.value = data
|
|
|
+ request()
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 表格请求完成 */
|
|
|
+ const tableDone = () => {
|
|
|
+ if (props.multiple) {
|
|
|
+ nextTick(() => {
|
|
|
+ const newTableData = tableRef.value?.getTableData().tableData
|
|
|
+ newTableData?.forEach(item => {
|
|
|
+ const temp =
|
|
|
+ Array.isArray(selectLabel.value) &&
|
|
|
+ selectLabel.value.find(x => x[props.valueKey] === item[props.valueKey])
|
|
|
+ temp && tableRef.value?.setCheckboxRow(item, true)
|
|
|
+ })
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 请求数据 */
|
|
|
+ const request = () => {
|
|
|
+ if (typeof props.tableConfig?.datasource === 'function') {
|
|
|
+ loading.value = true
|
|
|
+ props.tableConfig
|
|
|
+ .datasource({
|
|
|
+ pageIndex: pageIndex.value,
|
|
|
+ pageSize: props.tableConfig.pageSize
|
|
|
+ })
|
|
|
+ .then((res: any) => {
|
|
|
+ tableData.value = res
|
|
|
+ tableDone()
|
|
|
+ })
|
|
|
+ .catch((e: any) => {
|
|
|
+ console.warn(e)
|
|
|
+ })
|
|
|
+ .finally(() => {
|
|
|
+ loading.value = false
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ console.warn('tableConfig.datasource 必须为 Promise')
|
|
|
+ }
|
|
|
+ }
|
|
|
+ request()
|
|
|
+
|
|
|
+ /* 更新选中值 */
|
|
|
+ const updateModelValue = (value: any) => {
|
|
|
+ emit('update:modelValue', value)
|
|
|
+ }
|
|
|
return {
|
|
|
popoverRef,
|
|
|
+ tableRef,
|
|
|
+ selectLabel,
|
|
|
visible,
|
|
|
+ isEmpty,
|
|
|
+ loading,
|
|
|
tableData,
|
|
|
closeEnable,
|
|
|
onFocus,
|
|
|
onBlur,
|
|
|
- onClear
|
|
|
+ onClear,
|
|
|
+ onItemClear,
|
|
|
+ tableRadioChange,
|
|
|
+ tableCheckboxChange,
|
|
|
+ paginationChange
|
|
|
}
|
|
|
}
|
|
|
})
|
|
@@ -70,6 +180,7 @@ export default defineComponent({
|
|
|
:popper-class="popperClass"
|
|
|
:popper-options="popperOptions"
|
|
|
trigger="click"
|
|
|
+ transition="el-zoom-in-top"
|
|
|
teleported
|
|
|
>
|
|
|
<template #reference>
|
|
@@ -77,11 +188,11 @@ export default defineComponent({
|
|
|
<el-input
|
|
|
:size="size"
|
|
|
:disabled="disabled"
|
|
|
- :placeholder="placeholder"
|
|
|
+ :placeholder="multiple && !isEmpty ? '' : placeholder"
|
|
|
:readonly="true"
|
|
|
:validateEvent="false"
|
|
|
:autocomplete="autocomplete"
|
|
|
- :modelValue="moduleValue"
|
|
|
+ :modelValue="multiple ? '' : selectLabel"
|
|
|
@focus="onFocus"
|
|
|
@blur="onBlur"
|
|
|
>
|
|
@@ -95,24 +206,107 @@ export default defineComponent({
|
|
|
<CircleClose />
|
|
|
</slot>
|
|
|
</ElIcon>
|
|
|
- <ElIcon>
|
|
|
- <slot name="suffixIcon" :visible="visible">
|
|
|
+ <ElIcon class="select-down" :class="{ 'select-down-rotate': visible }">
|
|
|
+ <slot name="suffixIcon">
|
|
|
<ArrowDown />
|
|
|
</slot>
|
|
|
</ElIcon>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-input>
|
|
|
+ <div class="table-select-multiple" v-if="multiple">
|
|
|
+ <el-tag
|
|
|
+ type="info"
|
|
|
+ size="small"
|
|
|
+ disable-transitions
|
|
|
+ closable
|
|
|
+ style="margin-right: 5px"
|
|
|
+ v-for="item in selectLabel"
|
|
|
+ :key="item[valueKey]"
|
|
|
+ @close="onItemClear(item)"
|
|
|
+ >
|
|
|
+ {{ item[labelKey] }}
|
|
|
+ </el-tag>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</template>
|
|
|
- <vxe-table :data="tableData">
|
|
|
- <vxe-column v-for="(item, index) in tableConfig?.column" :key="index" v-bind="item"></vxe-column>
|
|
|
+ <vxe-table
|
|
|
+ :data="tableData"
|
|
|
+ :row-config="{ isCurrent: true, isHover: true }"
|
|
|
+ :radio-config="{ trigger: multiple ? '' : 'row' }"
|
|
|
+ :checkbox-config="{ trigger: multiple ? 'row' : '' }"
|
|
|
+ ref="tableRef"
|
|
|
+ @radio-change="tableRadioChange"
|
|
|
+ @checkbox-all="tableCheckboxChange"
|
|
|
+ @checkbox-change="tableCheckboxChange"
|
|
|
+ v-loading="loading"
|
|
|
+ >
|
|
|
+ <vxe-column type="checkbox" width="60" v-if="multiple"></vxe-column>
|
|
|
+ <vxe-column type="radio" width="40" v-else> </vxe-column>
|
|
|
+ <vxe-column v-for="(item, index) in tableConfig?.column" :key="index" v-bind="item">
|
|
|
+ <template #default="slotProps" v-if="item.slot">
|
|
|
+ <slot :name="item.slot" v-bind="slotProps || {}"></slot>
|
|
|
+ </template>
|
|
|
+ </vxe-column>
|
|
|
</vxe-table>
|
|
|
+ <div class="table-select-pagination" v-if="tableConfig.total">
|
|
|
+ <el-pagination
|
|
|
+ background
|
|
|
+ layout="total, prev, pager, next, jumper"
|
|
|
+ size="small"
|
|
|
+ :pager-count="5"
|
|
|
+ :page-size="tableConfig.pageSize"
|
|
|
+ :total="tableConfig.total"
|
|
|
+ @current-change="paginationChange"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
</el-popover>
|
|
|
</template>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
.table-select-container {
|
|
|
width: 100%;
|
|
|
+ position: relative;
|
|
|
+ .select-clear {
|
|
|
+ position: absolute;
|
|
|
+ right: 10px;
|
|
|
+ top: 49%;
|
|
|
+ transform: translateY(-50%);
|
|
|
+ cursor: pointer;
|
|
|
+ border-radius: 50%;
|
|
|
+ overflow: hidden;
|
|
|
+ z-index: 2;
|
|
|
+ color: #fff;
|
|
|
+ background-color: var(--el-color-info-light-3);
|
|
|
+ opacity: 0;
|
|
|
+ &:hover {
|
|
|
+ background-color: var(--el-color-info);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .select-down {
|
|
|
+ transition: transform 0.2s;
|
|
|
+ }
|
|
|
+ .select-down-rotate {
|
|
|
+ transform: rotate(180deg);
|
|
|
+ }
|
|
|
+ &:hover {
|
|
|
+ .select-clear {
|
|
|
+ opacity: 1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+.table-select-multiple {
|
|
|
+ position: absolute;
|
|
|
+ top: 0px;
|
|
|
+ width: calc(100% - 24px);
|
|
|
+ height: 100%;
|
|
|
+ z-index: 2;
|
|
|
+ padding: 4px 10px;
|
|
|
+ box-sizing: border-box;
|
|
|
+}
|
|
|
+.table-select-pagination {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ margin-top: 12px;
|
|
|
}
|
|
|
</style>
|