添加表头编辑
This commit is contained in:
parent
da0b61b8e2
commit
0ad61c0e69
|
|
@ -115,7 +115,7 @@ const handleBlur = () => {
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.search-container {
|
.search-container {
|
||||||
margin-bottom: 20px;
|
margin-bottom: 4px;
|
||||||
|
|
||||||
.el-form {
|
.el-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,57 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
|
<div class="table-toolbar" v-if="showColumnSetting">
|
||||||
|
<!-- 气泡确认框 -->
|
||||||
|
<el-popover
|
||||||
|
v-model:visible="columnSettingVisible"
|
||||||
|
placement="bottom-end"
|
||||||
|
:width="320"
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-button :icon="Setting" circle />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="column-setting-content">
|
||||||
|
<!-- 操作栏 -->
|
||||||
|
<div class="setting-header">
|
||||||
|
<span class="setting-title">列设置</span>
|
||||||
|
<el-button type="primary" size="small" text @click="handleReset">
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 拖拽列表 -->
|
||||||
|
<div class="column-list">
|
||||||
|
<draggable
|
||||||
|
:list="localColumns"
|
||||||
|
item-key="prop"
|
||||||
|
handle=".drag-handle"
|
||||||
|
:animation="200"
|
||||||
|
ghost-class="ghost"
|
||||||
|
@end="handleDragEnd"
|
||||||
|
>
|
||||||
|
<template #item="{ element }">
|
||||||
|
<div class="column-item">
|
||||||
|
<el-icon class="drag-handle"><Rank /></el-icon>
|
||||||
|
<el-checkbox
|
||||||
|
v-model="element.visible"
|
||||||
|
:disabled="element.fixed"
|
||||||
|
@change="handleVisibleChange"
|
||||||
|
>
|
||||||
|
{{ element.label }}
|
||||||
|
</el-checkbox>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-popover>
|
||||||
|
</div>
|
||||||
<el-table
|
<el-table
|
||||||
ref="tableRef"
|
ref="tableRef"
|
||||||
style="width: 100%; flex: 1"
|
style="width: 100%; flex: 1"
|
||||||
:data="tableData"
|
:data="enableClientFilter ? filteredTableData : tableData"
|
||||||
:row-key="rowKey"
|
:row-key="rowKey"
|
||||||
:tree-props="treeProps"
|
:tree-props="treeProps"
|
||||||
:border="border"
|
:border="border"
|
||||||
|
|
@ -19,7 +67,7 @@
|
||||||
<el-table-column v-if="showSelection" type="selection" width="55" align="center" />
|
<el-table-column v-if="showSelection" type="selection" width="55" align="center" />
|
||||||
|
|
||||||
<!-- 动态表头列 -->
|
<!-- 动态表头列 -->
|
||||||
<template v-for="(column, index) in columns" :key="index">
|
<template v-for="(column, index) in finalColumns" :key="index">
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-if="column.type === 'index'"
|
v-if="column.type === 'index'"
|
||||||
type="index"
|
type="index"
|
||||||
|
|
@ -28,17 +76,59 @@
|
||||||
:align="column.align || 'center'"
|
:align="column.align || 'center'"
|
||||||
/>
|
/>
|
||||||
<el-table-column
|
<el-table-column
|
||||||
v-else-if="!column.slotName"
|
v-else-if="!column.slotName && column.prop"
|
||||||
:prop="column.prop"
|
:prop="column.prop"
|
||||||
:label="column.label"
|
:label="column.label"
|
||||||
:width="column.width"
|
:width="column.width"
|
||||||
:align="column.align || 'left'"
|
:align="column.align || 'left'"
|
||||||
:sortable="column.sortable"
|
:sortable="column.sortable"
|
||||||
|
:column-key="column.prop"
|
||||||
>
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="table-header-cell">
|
||||||
|
<div class="table-header-label">
|
||||||
|
<span>{{ column.label }}</span>
|
||||||
|
<template v-if="column.filters && column.filters.length > 0">
|
||||||
|
<el-dropdown
|
||||||
|
trigger="click"
|
||||||
|
@command="(command: any) => handleFilterCommand(command, column)"
|
||||||
|
>
|
||||||
|
<span class="filter-trigger">
|
||||||
|
<el-icon :class="{ 'is-active': filterStates[column.prop!]?.length }">
|
||||||
|
<Filter />
|
||||||
|
</el-icon>
|
||||||
|
</span>
|
||||||
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item
|
||||||
|
v-for="item in column.filters"
|
||||||
|
:key="item.value"
|
||||||
|
:command="item.value"
|
||||||
|
:divided="filterStates[column.prop!]?.includes(item.value)"
|
||||||
|
>
|
||||||
|
{{ item.text }}
|
||||||
|
</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-if="column.filterable"
|
||||||
|
v-model="filterStates[column.prop!]"
|
||||||
|
size="small"
|
||||||
|
:placeholder="column.filterPlaceholder || '筛选'"
|
||||||
|
clearable
|
||||||
|
@change="handleFilterInput(column)"
|
||||||
|
@clear="handleFilterClear(column)"
|
||||||
|
class="header-filter-input"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template v-if="column.isNumber" #default="{ row }">
|
<template v-if="column.isNumber" #default="{ row }">
|
||||||
<span>{{ formatDecimalTruncate(row[column.prop]) }}</span>
|
<span>{{ formatDecimalTruncate(row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="column.options && column.prop" #default="{ row }">
|
<template v-else-if="column.options" #default="{ row }">
|
||||||
<span>{{ getOptionLabel(column.options, row[column.prop]) }}</span>
|
<span>{{ getOptionLabel(column.options, row[column.prop]) }}</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
@ -60,9 +150,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, watch } from 'vue'
|
import { ref, watch, computed, onMounted, reactive } from 'vue'
|
||||||
|
import { Setting, Rank, Filter } from '@element-plus/icons-vue'
|
||||||
|
import draggable from 'vuedraggable'
|
||||||
import type { TableInstance } from 'element-plus'
|
import type { TableInstance } from 'element-plus'
|
||||||
import { formatDecimalTruncate } from '@/utils/formatDecimal'
|
import { formatDecimalTruncate } from '@/utils/formatDecimal'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { useUserStore } from '@/stores/modules/user'
|
||||||
|
|
||||||
|
// 筛选状态管理
|
||||||
|
const filterStates = reactive<Record<string, any>>({})
|
||||||
|
|
||||||
// 根据选项配置获取标签
|
// 根据选项配置获取标签
|
||||||
const getOptionLabel = (options: { label: string; value: string | number }[], value: any): string => {
|
const getOptionLabel = (options: { label: string; value: string | number }[], value: any): string => {
|
||||||
|
|
@ -78,10 +175,18 @@ interface TableColumn {
|
||||||
label: string
|
label: string
|
||||||
width?: string | number
|
width?: string | number
|
||||||
align?: 'left' | 'center' | 'right'
|
align?: 'left' | 'center' | 'right'
|
||||||
slotName?: string // 具名插槽名称
|
slotName?: string
|
||||||
sortable?: boolean // 是否可排序
|
sortable?: boolean
|
||||||
isNumber?: boolean // 是否为数字类型(自动截取小数位)
|
isNumber?: boolean
|
||||||
options?: { label: string; value: string | number }[] // 选项配置(用于状态映射等)
|
options?: { label: string; value: string | number }[]
|
||||||
|
visible?: boolean
|
||||||
|
fixed?: boolean
|
||||||
|
sortOrder?: number
|
||||||
|
// 表头筛选器相关属性
|
||||||
|
filters?: { text: string; value: any }[]
|
||||||
|
filterMethod?: (value: any, row: any, column: any) => boolean
|
||||||
|
filterable?: boolean
|
||||||
|
filterPlaceholder?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 定义表格组件属性接口
|
// 定义表格组件属性接口
|
||||||
|
|
@ -92,10 +197,13 @@ interface Props {
|
||||||
children: string
|
children: string
|
||||||
}
|
}
|
||||||
columns?: TableColumn[]
|
columns?: TableColumn[]
|
||||||
border?: boolean // 是否显示边框
|
border?: boolean
|
||||||
highlightCurrentRow?: boolean // 是否高亮当前行
|
highlightCurrentRow?: boolean
|
||||||
loading?: boolean // 是否显示加载状态
|
loading?: boolean
|
||||||
showSelection?: boolean // 是否显示勾选列
|
showSelection?: boolean
|
||||||
|
showColumnSetting?: boolean
|
||||||
|
columnSettingStorageKey?: string
|
||||||
|
enableClientFilter?: boolean // 是否启用客户端筛选
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
|
@ -103,16 +211,170 @@ const props = withDefaults(defineProps<Props>(), {
|
||||||
highlightCurrentRow: true,
|
highlightCurrentRow: true,
|
||||||
loading: false,
|
loading: false,
|
||||||
showSelection: false,
|
showSelection: false,
|
||||||
|
showColumnSetting: true, // 默认开启列设置
|
||||||
|
columnSettingStorageKey: '',
|
||||||
|
enableClientFilter: true, // 默认开启客户端筛选
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取过滤后的数据
|
||||||
|
const filteredTableData = computed(() => {
|
||||||
|
if (!props.enableClientFilter || Object.keys(filterStates).length === 0) {
|
||||||
|
return props.tableData
|
||||||
|
}
|
||||||
|
|
||||||
|
return props.tableData.filter((row) => {
|
||||||
|
for (const prop in filterStates) {
|
||||||
|
const filterValue = filterStates[prop]
|
||||||
|
if (Array.isArray(filterValue) && filterValue.length > 0) {
|
||||||
|
if (!filterValue.includes(row[prop])) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else if (filterValue && typeof filterValue === 'string') {
|
||||||
|
const cellValue = String(row[prop] || '').toLowerCase()
|
||||||
|
if (!cellValue.includes(filterValue.toLowerCase())) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// 定义组件抛出的事件
|
// 定义组件抛出的事件
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'row-click', row: any): void // 保留原有行点击事件
|
(e: 'row-click', row: any): void
|
||||||
(e: 'row-contextmenu', row: any, column: any, event: MouseEvent): void
|
(e: 'row-contextmenu', row: any, column: any, event: MouseEvent): void
|
||||||
(e: 'selection-change', rows: any[]): void // 勾选变化事件
|
(e: 'selection-change', rows: any[]): void
|
||||||
(e: 'current-change', row: any): void // 当前行变化事件
|
(e: 'current-change', row: any): void
|
||||||
|
(e: 'filter-change', filters: { [key: string]: any }): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
// 获取路由和用户 Store
|
||||||
|
const route = useRoute()
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 生成缓存 key
|
||||||
|
const getCacheKey = () => {
|
||||||
|
const baseKey = props.columnSettingStorageKey || String(route.name || route.path)
|
||||||
|
const userId = String(userStore.userInfo?.id || 'guest')
|
||||||
|
return `table-column-setting-${baseKey}-${userId}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 气泡确认框显示状态
|
||||||
|
const columnSettingVisible = ref(false)
|
||||||
|
|
||||||
|
// 本地列配置副本
|
||||||
|
const localColumns = ref<TableColumn[]>([])
|
||||||
|
|
||||||
|
// 计算最终使用的列配置
|
||||||
|
const finalColumns = computed(() => {
|
||||||
|
// 当 showColumnSetting 开启时,始终使用 localColumns
|
||||||
|
if (props.showColumnSetting && localColumns.value.length > 0) {
|
||||||
|
return localColumns.value.filter((col) => col.visible !== false)
|
||||||
|
}
|
||||||
|
return props.columns || []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化列配置(从缓存或默认值)
|
||||||
|
const initColumns = () => {
|
||||||
|
if (!props.columns || props.columns.length === 0) {
|
||||||
|
localColumns.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheKey = getCacheKey()
|
||||||
|
const cached = localStorage.getItem(cacheKey)
|
||||||
|
|
||||||
|
if (cached) {
|
||||||
|
try {
|
||||||
|
const cachedConfig = JSON.parse(cached)
|
||||||
|
// 按 sortOrder 排序
|
||||||
|
cachedConfig.sort((a: any, b: any) => (a.sortOrder || 0) - (b.sortOrder || 0))
|
||||||
|
|
||||||
|
// 合并缓存和原始配置,保持缓存的顺序
|
||||||
|
localColumns.value = cachedConfig.map((cached: any) => {
|
||||||
|
const originalCol = props.columns?.find((c) => c.prop === cached.prop)
|
||||||
|
if (originalCol) {
|
||||||
|
return { ...originalCol, ...cached }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}).filter(Boolean)
|
||||||
|
} catch {
|
||||||
|
// 解析失败,使用默认配置
|
||||||
|
localColumns.value = props.columns.map((col) => ({
|
||||||
|
...col,
|
||||||
|
visible: col.visible !== false,
|
||||||
|
fixed: col.fixed || false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 无缓存,使用默认配置
|
||||||
|
localColumns.value = props.columns.map((col) => ({
|
||||||
|
...col,
|
||||||
|
visible: col.visible !== false,
|
||||||
|
fixed: col.fixed || false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存配置到 localStorage
|
||||||
|
const saveToStorage = () => {
|
||||||
|
const config = localColumns.value.map((col, index) => ({
|
||||||
|
prop: col.prop,
|
||||||
|
visible: col.visible,
|
||||||
|
label: col.label,
|
||||||
|
width: col.width,
|
||||||
|
slotName: col.slotName,
|
||||||
|
sortOrder: index,
|
||||||
|
}))
|
||||||
|
localStorage.setItem(getCacheKey(), JSON.stringify(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理可见性变化
|
||||||
|
const handleVisibleChange = () => {
|
||||||
|
saveToStorage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理拖拽结束
|
||||||
|
const handleDragEnd = () => {
|
||||||
|
saveToStorage()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置为默认配置
|
||||||
|
const handleReset = () => {
|
||||||
|
localColumns.value = props.columns.map((col) => ({
|
||||||
|
...col,
|
||||||
|
visible: col.visible !== false,
|
||||||
|
fixed: col.fixed || false,
|
||||||
|
}))
|
||||||
|
localStorage.removeItem(getCacheKey())
|
||||||
|
columnSettingVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 columns 变化
|
||||||
|
watch(
|
||||||
|
() => props.columns,
|
||||||
|
(newColumns) => {
|
||||||
|
if (newColumns && newColumns.length > 0) {
|
||||||
|
// 只有在本地列为空时才初始化(不要覆盖已加载的缓存)
|
||||||
|
if (localColumns.value.length === 0) {
|
||||||
|
initColumns()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// 组件挂载时初始化列配置
|
||||||
|
onMounted(() => {
|
||||||
|
// 延迟初始化,确保 props.columns 已准备好
|
||||||
|
setTimeout(() => {
|
||||||
|
if (props.columns && props.columns.length > 0 && localColumns.value.length === 0) {
|
||||||
|
initColumns()
|
||||||
|
}
|
||||||
|
}, 100)
|
||||||
|
})
|
||||||
|
|
||||||
// 处理勾选变化
|
// 处理勾选变化
|
||||||
const handleSelectionChange = (rows: any[]) => {
|
const handleSelectionChange = (rows: any[]) => {
|
||||||
emit('selection-change', rows)
|
emit('selection-change', rows)
|
||||||
|
|
@ -120,69 +382,72 @@ const handleSelectionChange = (rows: any[]) => {
|
||||||
|
|
||||||
// 处理当前行变化
|
// 处理当前行变化
|
||||||
const handleCurrentChange = (row: any) => {
|
const handleCurrentChange = (row: any) => {
|
||||||
// 添加空值保护,避免传递 null 导致 Vue 响应式系统报错
|
if (row === null || row === undefined) return
|
||||||
if (row === null || row === undefined) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
emit('current-change', row)
|
emit('current-change', row)
|
||||||
}
|
}
|
||||||
|
|
||||||
const tableRef = ref<TableInstance>()
|
const tableRef = ref<TableInstance>()
|
||||||
|
|
||||||
// 维护当前选中行状态,用于二次点击取消选中
|
// 维护当前选中行状态
|
||||||
const selectedRow = ref<any>(null)
|
const selectedRow = ref<any>(null)
|
||||||
|
|
||||||
/**
|
// 处理行点击事件
|
||||||
* 处理行点击事件(支持二次点击取消选中)
|
|
||||||
*/
|
|
||||||
const handleRowClick = (row: any) => {
|
const handleRowClick = (row: any) => {
|
||||||
// 增加空值保护,避免传递 null 给父组件
|
|
||||||
if (!row) return
|
if (!row) return
|
||||||
|
|
||||||
// 判断是否是二次点击同一行
|
|
||||||
if (selectedRow.value && selectedRow.value === row) {
|
if (selectedRow.value && selectedRow.value === row) {
|
||||||
// 二次点击:取消选中
|
|
||||||
selectedRow.value = null
|
selectedRow.value = null
|
||||||
tableRef.value?.setCurrentRow(null) // 传 null 清除选中
|
tableRef.value?.setCurrentRow(null)
|
||||||
} else {
|
} else {
|
||||||
// 首次点击或点击不同行:选中该行
|
|
||||||
selectedRow.value = row
|
selectedRow.value = row
|
||||||
tableRef.value?.setCurrentRow(row)
|
tableRef.value?.setCurrentRow(row)
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('row-click', selectedRow.value)
|
emit('row-click', selectedRow.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// 处理行右键菜单事件
|
||||||
* 处理行右键菜单事件
|
|
||||||
*/
|
|
||||||
const handleRowContextmenu = (row: any, column: any, event: MouseEvent) => {
|
const handleRowContextmenu = (row: any, column: any, event: MouseEvent) => {
|
||||||
console.log('row', row)
|
|
||||||
emit('row-contextmenu', row, column, event)
|
emit('row-contextmenu', row, column, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 监听 tableData 变化,数据更新时取消选中
|
// 监听 tableData 变化
|
||||||
watch(
|
watch(
|
||||||
() => props.tableData,
|
() => props.tableData,
|
||||||
() => {
|
() => {
|
||||||
// 数据更新时,清除选中状态
|
|
||||||
selectedRow.value = null
|
selectedRow.value = null
|
||||||
tableRef.value?.setCurrentRow(null)
|
tableRef.value?.setCurrentRow(null)
|
||||||
},
|
},
|
||||||
{ deep: true }, // 深度监听,防止数组元素变化未触发
|
{ deep: true },
|
||||||
)
|
)
|
||||||
|
|
||||||
// 监听 loading 属性变化,确保状态同步
|
// 处理下拉筛选命令
|
||||||
watch(
|
const handleFilterCommand = (command: any, column: TableColumn) => {
|
||||||
() => props.loading,
|
const prop = column.prop!
|
||||||
(newVal) => {
|
if (!filterStates[prop]) {
|
||||||
// loading 状态变化时可以执行额外的处理
|
filterStates[prop] = []
|
||||||
console.log('Table loading status changed:', newVal)
|
}
|
||||||
},
|
|
||||||
{ flush: 'sync' }, // 使用 sync 模式确保即时响应
|
const index = filterStates[prop].indexOf(command)
|
||||||
)
|
if (index > -1) {
|
||||||
|
filterStates[prop].splice(index, 1)
|
||||||
|
} else {
|
||||||
|
filterStates[prop].push(command)
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('filter-change', { ...filterStates })
|
||||||
|
}
|
||||||
|
|
||||||
// 暴露清除当前选中行的方法
|
// 处理输入筛选
|
||||||
|
const handleFilterInput = (column: TableColumn) => {
|
||||||
|
emit('filter-change', { ...filterStates })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理清除筛选
|
||||||
|
const handleFilterClear = (column: TableColumn) => {
|
||||||
|
filterStates[column.prop!] = ''
|
||||||
|
emit('filter-change', { ...filterStates })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露方法
|
||||||
defineExpose({
|
defineExpose({
|
||||||
clearCurrentRow: () => {
|
clearCurrentRow: () => {
|
||||||
selectedRow.value = null
|
selectedRow.value = null
|
||||||
|
|
@ -203,9 +468,129 @@ defineExpose({
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
.table-toolbar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
:deep(.el-button) {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
:deep(.el-table) {
|
:deep(.el-table) {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.column-setting-content {
|
||||||
|
.setting-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #ebeef5;
|
||||||
|
|
||||||
|
.setting-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-list {
|
||||||
|
max-height: 360px;
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
.column-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drag-handle {
|
||||||
|
margin-right: 10px;
|
||||||
|
cursor: move;
|
||||||
|
color: #909399;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-checkbox) {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ghost {
|
||||||
|
opacity: 0.5;
|
||||||
|
background: #c6e2ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表头单元格样式
|
||||||
|
.table-header-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.table-header-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-trigger {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 2px;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #909399;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
color: #409eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-filter-input {
|
||||||
|
width: 100%;
|
||||||
|
// max-width: 140px;
|
||||||
|
|
||||||
|
:deep(.el-input__wrapper) {
|
||||||
|
padding: 0 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
height: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-input__inner) {
|
||||||
|
height: 24px;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -11,11 +11,11 @@
|
||||||
</header>
|
</header>
|
||||||
<div class="table-container">
|
<div class="table-container">
|
||||||
<div class="table-container-left">
|
<div class="table-container-left">
|
||||||
<Table ref="singleTableRef" :table-data="tableData" :columns="tableColumns" row-key="id" :loading="loading"
|
<Table ref="singleTableRef" :show-column-setting="false" :table-data="tableData" :columns="tableColumns" row-key="id" :loading="loading"
|
||||||
:highlight-current-row="true" @current-change="handleCurrentChange" />
|
:highlight-current-row="true" @current-change="handleCurrentChange" />
|
||||||
</div>
|
</div>
|
||||||
<div class="table-container-right">
|
<div class="table-container-right">
|
||||||
<Table row-key="id" :table-data="tableDataDetail" :columns="tableColumnsDetail" :loading="detailLoading" />
|
<Table :show-column-setting="false" row-key="id" :table-data="tableDataDetail" :columns="tableColumnsDetail" :loading="detailLoading" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-dialog v-model="dialogVisible" :title="dialogTitleType === 1 ? '添加手续费模板' : '编辑手续费模板'" width="500">
|
<el-dialog v-model="dialogVisible" :title="dialogTitleType === 1 ? '添加手续费模板' : '编辑手续费模板'" width="500">
|
||||||
|
|
|
||||||
|
|
@ -8,26 +8,27 @@
|
||||||
v-auth="'riskControl_editRiskControlSetting'">停用</el-button>
|
v-auth="'riskControl_editRiskControlSetting'">停用</el-button>
|
||||||
<el-button type="primary" @click="handleChangeStatus(1)"
|
<el-button type="primary" @click="handleChangeStatus(1)"
|
||||||
v-auth="'riskControl_editRiskControlSetting'">启用</el-button>
|
v-auth="'riskControl_editRiskControlSetting'">启用</el-button>
|
||||||
<el-button type="primary" :loading="syncLoading" @click="handleSyncRiskControl()" v-auth="'riskControl_editRiskControlSetting'">
|
<el-button type="primary" :loading="syncLoading" @click="handleSyncRiskControl()"
|
||||||
|
v-auth="'riskControl_editRiskControlSetting'">
|
||||||
同步风控模板
|
同步风控模板
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<!-- 表格 -->
|
<!-- 表格 -->
|
||||||
<div class="risk-table-content">
|
<div class="risk-table-content">
|
||||||
<RiskTableSwitch :columns="tableColumnsSwitch" :table-data="tableTreeSwitch" class="table-switch"
|
<RiskTableSwitch :columns="tableColumnsSwitch" :show-column-setting="false" :table-data="tableTreeSwitch"
|
||||||
@row-click="handleRowClick" row-key="id" highlight-current-row :current-row-key="selectedRowId"
|
class="table-switch" @row-click="handleRowClick" row-key="id" highlight-current-row
|
||||||
:loading="loading">
|
:current-row-key="selectedRowId" :loading="loading">
|
||||||
<template #status="{ row }">
|
<template #status="{ row }">
|
||||||
<span>{{ row.status === 1 ? '启用' : '停用' }}</span>
|
<span>{{ row.status === 1 ? '启用' : '停用' }}</span>
|
||||||
</template>
|
</template>
|
||||||
</RiskTableSwitch>
|
</RiskTableSwitch>
|
||||||
<RiskTableContent :columns="tableColumnsContent" :table-data="currentContent" class="table-content" row-key="id"
|
<RiskTableContent :show-column-setting="false" :columns="tableColumnsContent" :table-data="currentContent"
|
||||||
:loading="detailLoading">
|
class="table-content" row-key="id" :loading="detailLoading">
|
||||||
</RiskTableContent>
|
</RiskTableContent>
|
||||||
</div>
|
</div>
|
||||||
<!-- dialog -->
|
<!-- dialog -->
|
||||||
<RiskDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" :button-loading="submitLoading" @confirm="handleSubmit"
|
<RiskDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" :button-loading="submitLoading"
|
||||||
@cancel="handleCancel" @close="handleClose">
|
@confirm="handleSubmit" @cancel="handleCancel" @close="handleClose">
|
||||||
<RiskForm :form-data="dialogForm" :form-rules="riskRules" :form-items="RiskFormItems"
|
<RiskForm :form-data="dialogForm" :form-rules="riskRules" :form-items="RiskFormItems"
|
||||||
:options-map="riskFormOptionsMap" :row-gutter="rowGutter" :col-span="colSpan" ref="riskFormRef" />
|
:options-map="riskFormOptionsMap" :row-gutter="rowGutter" :col-span="colSpan" ref="riskFormRef" />
|
||||||
</RiskDialog>
|
</RiskDialog>
|
||||||
|
|
@ -194,7 +195,7 @@ const syncLoading = ref(false)
|
||||||
const handleSyncRiskControl = async () => {
|
const handleSyncRiskControl = async () => {
|
||||||
syncLoading.value = true
|
syncLoading.value = true
|
||||||
try {
|
try {
|
||||||
await syncRiskControlApi()
|
await syncRiskControlApi()
|
||||||
} finally {
|
} finally {
|
||||||
syncLoading.value = false
|
syncLoading.value = false
|
||||||
}
|
}
|
||||||
|
|
@ -251,7 +252,7 @@ const handleModifyRisk = () => {
|
||||||
} as any
|
} as any
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleChangeStatus = async (status:any) => {
|
const handleChangeStatus = async (status: any) => {
|
||||||
dialogTitle.value = '停用'
|
dialogTitle.value = '停用'
|
||||||
dialogType.value = 'status'
|
dialogType.value = 'status'
|
||||||
await editRiskApi({ ...selectedRow.value, status: status })
|
await editRiskApi({ ...selectedRow.value, status: status })
|
||||||
|
|
@ -272,7 +273,7 @@ const handleRowClick = (row: any) => {
|
||||||
}
|
}
|
||||||
selectedRowId.value = row.id
|
selectedRowId.value = row.id
|
||||||
// 找到对应的数据并更新右侧内容
|
// 找到对应的数据并更新右侧内容
|
||||||
const targetItem:any = tableTreeContent.value.find((item: any) => item.id === row.id)
|
const targetItem: any = tableTreeContent.value.find((item: any) => item.id === row.id)
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{ text: '单笔开仓最大数量' + targetItem.maxOpenPosition },
|
{ text: '单笔开仓最大数量' + targetItem.maxOpenPosition },
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@
|
||||||
:table-data="marketTree"
|
:table-data="marketTree"
|
||||||
:columns="marketTableColumnsOptions"
|
:columns="marketTableColumnsOptions"
|
||||||
row-key="id"
|
row-key="id"
|
||||||
|
:show-column-setting="true"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
@row-click="handleRowClick"
|
@row-click="handleRowClick"
|
||||||
@row-contextmenu="handleRowContextMenu"
|
@row-contextmenu="handleRowContextMenu"
|
||||||
|
|
@ -35,7 +36,7 @@
|
||||||
<template #floatProfitLoss="{ row }">
|
<template #floatProfitLoss="{ row }">
|
||||||
<span>{{ getMarketFloatProfit(row.id) }}</span>
|
<span>{{ getMarketFloatProfit(row.id) }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #closeProfitLoss="{ row }">
|
<template #s="{ row }">
|
||||||
<span>{{ getMarketCloseProfit(row.id) }}</span>
|
<span>{{ getMarketCloseProfit(row.id) }}</span>
|
||||||
</template>
|
</template>
|
||||||
<template #pointSpread="{ row }">
|
<template #pointSpread="{ row }">
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,15 @@ export const marketSearch = [
|
||||||
|
|
||||||
export const marketTableColumns = [
|
export const marketTableColumns = [
|
||||||
// id
|
// id
|
||||||
{ label: 'ID', prop: 'id', align: 'center' as const, width: '100px' },
|
{ label: 'ID', prop: 'id', align: 'center' as const, width: '100px',filterable:false },
|
||||||
//代理关系
|
//代理关系
|
||||||
{ label: '代理关系', prop: 'agentRelation', align: 'center' as const, width: '200px' },
|
{ label: '代理关系', prop: 'agentRelation', align: 'center' as const, width: '290' },
|
||||||
//账户名
|
//账户名
|
||||||
{ label: '账户名', prop: 'accountName', align: 'center' as const, width: '120px' },
|
{ label: '账户名', prop: 'accountName', align: 'center' as const, width: '120px' },
|
||||||
//级别
|
//级别
|
||||||
{ label: '级别', prop: 'level', align: 'center' as const, width: '120px' },
|
{ label: '级别', prop: 'level', align: 'center' as const, width: '120px' },
|
||||||
{ label: '邮箱', prop: 'email', align: 'center' as const, width: '160px' },
|
{ label: '邮箱', prop: 'email', align: 'center' as const, width: '160px' },
|
||||||
{ label: '手机号', prop: 'mobile', align: 'center' as const, width: '140px' },
|
{ label: '手机号', prop: 'mobile', align: 'center' as const, width: '180' },
|
||||||
{ label: '风险率', prop: 'ratio', align: 'center' as const, width: '120px' },
|
{ label: '风险率', prop: 'ratio', align: 'center' as const, width: '120px' },
|
||||||
//基本账户
|
//基本账户
|
||||||
{ label: '基本账户', prop: 'basicAccount', align: 'center' as const, isNumber: true , width: '200px' },
|
{ label: '基本账户', prop: 'basicAccount', align: 'center' as const, isNumber: true , width: '200px' },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue