Commit 8516388e by User

添加本地代码开发示例和table组件

parent c560ab0d
{
"Offset": 0,
"Total": 10,
"Results": [
{
"OwnerName": "222",
"Name": "333",
"SerialNumber": "1111",
"Type": "Pos",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "EF1D786D-06F7-43F5-A685-8E13AE0F33BF",
"CreateTime": "2025-05-07T14:30:22.0000000+08:00",
"UpdateTime": "2025-05-13T16:59:38.0000000+08:00",
"Status": 0
},
{
"OwnerName": "xxx",
"Name": "xxx",
"SerialNumber": "xxxx",
"Type": "Cash",
"CreditLine": 0,
"Amount": 0,
"Currency": "USD",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "66B88D4E-A1B9-4E3C-922B-DD5211BA3FB5",
"CreateTime": "2025-05-07T14:26:34.0000000+08:00",
"UpdateTime": "2025-05-13T16:57:05.0000000+08:00",
"Status": 0
},
{
"OwnerName": "1111",
"Name": "222",
"SerialNumber": "333",
"Type": "Pos",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "97F8DA3B-06AC-4C50-ABEB-430F58B3DD64",
"CreateTime": "2025-05-07T14:30:08.0000000+08:00",
"UpdateTime": "2025-05-07T14:30:08.0000000+08:00",
"Status": 0
},
{
"OwnerName": "11",
"Name": "222",
"SerialNumber": "3",
"Type": "Cash",
"CreditLine": 0,
"Amount": 0,
"Currency": "USD",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "CC255F3E-9F80-4002-B68C-53C5FF86AE52",
"CreateTime": "2025-05-07T14:29:49.0000000+08:00",
"UpdateTime": "2025-05-07T14:29:49.0000000+08:00",
"Status": 0
},
{
"OwnerName": "22",
"Name": "222",
"SerialNumber": "33",
"Type": "AliPay",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "99C366A3-5867-43D5-B22D-B71E970D9A3C",
"CreateTime": "2025-05-07T14:29:33.0000000+08:00",
"UpdateTime": "2025-05-07T14:29:33.0000000+08:00",
"Status": 0
},
{
"OwnerName": "xxx",
"Name": "111",
"SerialNumber": "2222",
"Type": "Bank",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "F2475ECC-55BD-49FE-BC00-8822DC846739",
"CreateTime": "2025-05-07T14:29:00.0000000+08:00",
"UpdateTime": "2025-05-07T14:29:00.0000000+08:00",
"Status": 0
},
{
"OwnerName": "7275275",
"Name": "4274272",
"SerialNumber": "41042277",
"Type": "Cash",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "0C38277C-7BA6-4E9D-BEF5-D43DB49B5EA1",
"CreateTime": "2025-03-27T14:47:54.0000000+08:00",
"UpdateTime": "2025-03-27T14:47:54.0000000+08:00",
"Status": 0
},
{
"OwnerName": "12312322",
"Name": "32131",
"SerialNumber": "2231123",
"Type": "Pos",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "2A42BDFE-CDED-4300-ADF3-020D1B3E0138",
"CreateTime": "2025-03-27T14:33:13.0000000+08:00",
"UpdateTime": "2025-03-27T14:33:13.0000000+08:00",
"Status": 0
},
{
"OwnerName": "123123",
"Name": "321321",
"SerialNumber": "123132312312",
"Type": "Cash",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "095872A0-513C-4834-A0C5-BA173F5189A4",
"CreateTime": "2025-03-27T14:29:36.0000000+08:00",
"UpdateTime": "2025-03-27T14:29:36.0000000+08:00",
"Status": 0
},
{
"OwnerName": "12",
"Name": "1212",
"SerialNumber": "12121211",
"Type": "Cash",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "40D1968B-70A1-49D5-89FD-39943A080D0A",
"CreateTime": "2025-03-27T14:17:12.0000000+08:00",
"UpdateTime": "2025-03-27T14:17:12.0000000+08:00",
"Status": 0
}
]
}
\ No newline at end of file
......@@ -156,6 +156,7 @@ const local: App.I18n.Schema = {
'iframe-page': '外链页面',
home: '首页',
chat: 'AI助手',
tablecomponent: '表格组件',
exception: '异常页',
exception_403: '403',
exception_404: '404',
......
......@@ -4,6 +4,9 @@ import '@wangeditor/editor/dist/css/style.css';
import VueGridLayout from 'vue-grid-layout';
import MateChat from '@matechat/core';
import '@devui-design/icons/icomoon/devui-icon.css';
import axios from 'axios';
import * as echarts from 'echarts/core';
import WangEditor from 'wangeditor';
import './plugins/assets';
import { localStg } from '@/utils/storage';
// main.js or main.ts
......@@ -62,6 +65,11 @@ async function setupApp() {
setupIconifyOffline();
setupDayjs();
// 全局注册工具库到 window 对象,供外部组件使用
(window as any).$axios = axios;
(window as any).$echarts = echarts;
(window as any).$WangEditor = WangEditor;
const app = createApp(App);
setupStore(app);
await setupRouter(app);
......
......@@ -22,4 +22,5 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
login: () => import("@/views/_builtin/login/index.vue"),
chat: () => import("@/views/chat/index.vue"),
home: () => import("@/views/home/index.vue"),
tablecomponent: () => import("@/views/tableComponent/index.vue"),
};
......@@ -84,5 +84,14 @@ export const generatedRoutes: GeneratedRoute[] = [
constant: true,
hideInMenu: true
}
},
{
name: 'tablecomponent',
path: '/tablecomponent',
component: 'layout.base$view.tablecomponent',
meta: {
title: 'tablecomponent',
i18nKey: 'route.tablecomponent'
}
}
];
......@@ -182,7 +182,8 @@ const routeMap: RouteMap = {
"chat": "/chat",
"home": "/home",
"iframe-page": "/iframe-page/:url",
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?"
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
"tablecomponent": "/tablecomponent"
};
/**
......
......@@ -7,11 +7,17 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AddFieldDrawer: typeof import('./../components/AddFieldDrawer.vue')['default']
AppProvider: typeof import('./../components/common/app-provider.vue')['default']
BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
ColumnEditDrawer: typeof import('../views/table/codet/components/ColumnEditDrawer.vue')['default']
ColumnSettings: typeof import('../views/table/codet/components/ColumnSettings.vue')['default']
CountTo: typeof import('./../components/custom/count-to.vue')['default']
CreateDialog: typeof import('../views/table/codet/components/CreateDialog.vue')['default']
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
DataTable: typeof import('../views/table/codet/components/DataTable.vue')['default']
DataTransform: typeof import('../views/table/codet/components/DataTransform.vue')['default']
ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
FullScreen: typeof import('./../components/common/full-screen.vue')['default']
IconAntDesignEnterOutlined: typeof import('~icons/ant-design/enter-outlined')['default']
......@@ -27,12 +33,16 @@ declare module 'vue' {
LangSwitch: typeof import('./../components/common/lang-switch.vue')['default']
LookForward: typeof import('./../components/custom/look-forward.vue')['default']
MenuToggler: typeof import('./../components/common/menu-toggler.vue')['default']
NAlert: typeof import('naive-ui')['NAlert']
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
NButton: typeof import('naive-ui')['NButton']
NCard: typeof import('naive-ui')['NCard']
NCheckbox: typeof import('naive-ui')['NCheckbox']
NCheckboxGroup: typeof import('naive-ui')['NCheckboxGroup']
NColorPicker: typeof import('naive-ui')['NColorPicker']
NDataTable: typeof import('naive-ui')['NDataTable']
NDatePicker: typeof import('naive-ui')['NDatePicker']
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
NDivider: typeof import('naive-ui')['NDivider']
NDrawer: typeof import('naive-ui')['NDrawer']
......@@ -43,6 +53,8 @@ declare module 'vue' {
NFormItem: typeof import('naive-ui')['NFormItem']
NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid']
NGridItem: typeof import('naive-ui')['NGridItem']
NIcon: typeof import('naive-ui')['NIcon']
NInput: typeof import('naive-ui')['NInput']
NInputGroup: typeof import('naive-ui')['NInputGroup']
NInputNumber: typeof import('naive-ui')['NInputNumber']
......@@ -51,24 +63,34 @@ declare module 'vue' {
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
NModal: typeof import('naive-ui')['NModal']
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
NRadioButton: typeof import('naive-ui')['NRadioButton']
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
NScrollbar: typeof import('naive-ui')['NScrollbar']
NSelect: typeof import('naive-ui')['NSelect']
NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin']
NStatistic: typeof import('naive-ui')['NStatistic']
NSwitch: typeof import('naive-ui')['NSwitch']
NTab: typeof import('naive-ui')['NTab']
NTabPane: typeof import('naive-ui')['NTabPane']
NTabs: typeof import('naive-ui')['NTabs']
NText: typeof import('naive-ui')['NText']
NTooltip: typeof import('naive-ui')['NTooltip']
NWatermark: typeof import('naive-ui')['NWatermark']
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SearchFieldEditDrawer: typeof import('../views/table/codet/components/SearchFieldEditDrawer.vue')['default']
SearchFieldSettings: typeof import('../views/table/codet/components/SearchFieldSettings.vue')['default']
SearchForm: typeof import('../views/table/codet/components/SearchForm.vue')['default']
SoybeanAvatar: typeof import('./../components/custom/soybean-avatar.vue')['default']
SvgIcon: typeof import('./../components/custom/svg-icon.vue')['default']
SystemLogo: typeof import('./../components/common/system-logo.vue')['default']
TableBasicSettings: typeof import('../views/table/codet/components/TableBasicSettings.vue')['default']
TableColumnSetting: typeof import('./../components/advanced/table-column-setting.vue')['default']
TableHeaderOperation: typeof import('./../components/advanced/table-header-operation.vue')['default']
TableSettingsDrawer: typeof import('../views/table/codet/components/TableSettingsDrawer.vue')['default']
ThemeSchemaSwitch: typeof import('./../components/common/theme-schema-switch.vue')['default']
WaveBg: typeof import('./../components/custom/wave-bg.vue')['default']
}
......
......@@ -37,6 +37,7 @@ declare module "@elegant-router/types" {
"home": "/home";
"iframe-page": "/iframe-page/:url";
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?";
"tablecomponent": "/tablecomponent";
};
/**
......@@ -88,6 +89,7 @@ declare module "@elegant-router/types" {
| "home"
| "iframe-page"
| "login"
| "tablecomponent"
>;
/**
......@@ -113,6 +115,7 @@ declare module "@elegant-router/types" {
| "login"
| "chat"
| "home"
| "tablecomponent"
>;
/**
......
......@@ -11,9 +11,7 @@ import {
} from 'vue';
import { NConfigProvider, darkTheme } from 'naive-ui';
import * as naive from 'naive-ui';
import axios from 'axios';
import * as echarts from 'echarts/core';
import WangEditor from 'wangeditor';
// 这些库已在 main.ts 中全局注册,这里不需要重复导入
import { useThemeStore } from '@/store/modules/theme';
// 引入echarts相关
import { useEcharts } from '@/hooks/common/echarts';
......@@ -44,23 +42,18 @@ Object.keys(naive).forEach(key => {
// 添加类型声明
declare global {
interface Window {
$axios: typeof axios;
$echarts: typeof echarts;
$axios: any;
$echarts: any;
$useEcharts: typeof useEcharts;
$themeStore: typeof themeStore;
$WangEditor: typeof WangEditor;
$WangEditor: any;
}
}
// 全局注入axios
(window as any).$axios = axios;
// 全局注入echarts和useEcharts
(window as any).$echarts = echarts;
// axios, echarts, WangEditor 已在 main.ts 中全局注册
// 这里只需要注册组件特有的工具
(window as any).$useEcharts = useEcharts;
// 全局注入主题存储
(window as any).$themeStore = themeStore;
// 全局注入WangEditor
(window as any).$WangEditor = WangEditor;
// 清理函数 - 完全销毁Vue组件
function cleanup() {
......@@ -75,8 +68,8 @@ function cleanup() {
}
// 清理编辑器实例
const editor = (window as any).$WangEditor?.getInstance();
if (editor) {
const editor = (window as any).$WangEditor?.getInstance?.();
if (editor && typeof editor.destroy === 'function') {
editor.destroy();
}
} catch (e) {
......
<script setup lang="ts">
import { inject } from 'vue';
// 从主组件注入状态和方法
const tableState: any = inject('tableState');
const tableMethods: any = inject('tableMethods');
const { showAddFieldDrawer, selectedColumns, availableColumns } = tableState;
const { confirmAddFields } = tableMethods;
defineOptions({
name: 'AddFieldDrawer'
});
</script>
<template>
<NDrawer v-model:show="showAddFieldDrawer" :width="400" placement="right">
<NDrawerContent title="添加搜索字段">
<div class="add-field-content">
<NAlert type="info" :show-icon="true" class="mb-4">请选择要添加为搜索条件的字段</NAlert>
<div class="field-select-list">
<NScrollbar style="max-height: calc(100vh - 250px)">
<NCheckboxGroup v-model:value="selectedColumns">
<div v-for="column in availableColumns" :key="column.key" class="field-select-item">
<NCheckbox :value="column.key">
<span class="field-select-label">{{ column.title }}</span>
<span class="field-select-key">({{ column.key }})</span>
</NCheckbox>
</div>
</NCheckboxGroup>
</NScrollbar>
</div>
</div>
<template #footer>
<NSpace justify="end">
<NButton @click="showAddFieldDrawer = false">取消</NButton>
<NButton type="primary" :disabled="selectedColumns.length === 0" @click="confirmAddFields">确定</NButton>
</NSpace>
</template>
</NDrawerContent>
</NDrawer>
</template>
<style scoped>
.add-field-content {
padding: 16px;
}
.field-select-list {
margin-top: 16px;
}
.field-select-item {
padding: 8px 0;
border-bottom: 1px solid var(--n-border-color);
}
.field-select-item:last-child {
border-bottom: none;
}
.field-select-label {
font-size: 14px;
color: var(--n-text-color);
}
.field-select-key {
font-size: 12px;
color: var(--n-text-color-3);
margin-left: 8px;
}
.mb-4 {
margin-bottom: 16px;
}
</style>
<script setup lang="ts">
import { inject } from 'vue';
// 从主组件注入状态和方法
const tableState: any = inject('tableState');
const tableMethods: any = inject('tableMethods');
const { showEditDrawer, currentEditColumn, isIndexColumn, newFilterLabel, newFilterValue } = tableState;
const { saveColumnEdit, addFilterOption, removeFilterOption, generateFilterOptions } = tableMethods;
defineOptions({
name: 'ColumnEditDrawer'
});
</script>
<template>
<NDrawer v-model:show="showEditDrawer" :width="400" placement="right">
<NDrawerContent title="编辑列">
<NForm label-placement="left" label-width="100" label-align="left">
<NFormItem label="显示名称">
<NInput
:value="currentEditColumn?.title"
:disabled="!currentEditColumn"
@update:value="val => currentEditColumn && (currentEditColumn.title = val)"
></NInput>
</NFormItem>
<NFormItem label="字段名">
<NInput :value="currentEditColumn?.key" disabled></NInput>
</NFormItem>
<NFormItem label="表头对齐">
<NRadioGroup
:value="currentEditColumn?.titleAlign"
@update:value="val => currentEditColumn && (currentEditColumn.titleAlign = val)"
>
<NRadioButton value="left">左对齐</NRadioButton>
<NRadioButton value="center">居中</NRadioButton>
<NRadioButton value="right">右对齐</NRadioButton>
</NRadioGroup>
</NFormItem>
<NFormItem label="内容对齐">
<NRadioGroup
:value="currentEditColumn?.align"
@update:value="val => currentEditColumn && (currentEditColumn.align = val)"
>
<NRadioButton value="left">左对齐</NRadioButton>
<NRadioButton value="center">居中</NRadioButton>
<NRadioButton value="right">右对齐</NRadioButton>
</NRadioGroup>
</NFormItem>
<!-- 只在非 index 字段时显示排序开关 -->
<NFormItem v-if="!isIndexColumn" label="开启排序">
<NSwitch
:value="currentEditColumn?.sortable"
@update:value="val => currentEditColumn && (currentEditColumn.sortable = val)"
></NSwitch>
</NFormItem>
<!-- 添加筛选功能相关配置 -->
<NFormItem v-if="!isIndexColumn" label="开启筛选">
<NSwitch
:value="!!currentEditColumn?.filter"
@update:value="
val => {
if (currentEditColumn) {
if (val) {
// 启用筛选时,初始化筛选相关属性
currentEditColumn.filter = true;
currentEditColumn.filterOptions = currentEditColumn.filterOptions || [];
} else {
// 禁用筛选时,清除筛选相关属性
delete currentEditColumn.filter;
delete currentEditColumn.filterOptions;
}
}
}
"
></NSwitch>
</NFormItem>
<!-- 筛选选项管理,仅在开启筛选时显示 -->
<template v-if="currentEditColumn?.filter">
<NDivider>筛选选项管理</NDivider>
<NFormItem label="自动获取选项">
<NButton size="small" @click="generateFilterOptions(currentEditColumn)">从当前数据生成选项</NButton>
</NFormItem>
<!-- 添加新筛选选项 -->
<NSpace vertical>
<NInputGroup>
<NInput v-model:value="newFilterLabel" placeholder="显示文本"></NInput>
<NInput v-model:value="newFilterValue" placeholder="筛选值"></NInput>
<NButton type="primary" :disabled="!newFilterLabel || !newFilterValue" @click="addFilterOption">
添加
</NButton>
</NInputGroup>
</NSpace>
<!-- 筛选选项列表 -->
<NSpace vertical style="margin-top: 12px; max-height: 200px; overflow-y: auto">
<div
v-for="(option, index) in currentEditColumn.filterOptions"
:key="index"
style="display: flex; justify-content: space-between; align-items: center; padding: 4px 0"
>
<span>{{ option.label }}</span>
<NButton text type="error" @click="removeFilterOption(index)">
<template #icon>
<NIcon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
fill="currentColor"
/>
</svg>
</NIcon>
</template>
</NButton>
</div>
</NSpace>
</template>
<!-- 添加 ellipsis 开关 -->
<NFormItem v-if="!isIndexColumn" label="文本省略">
<NSwitch
:value="!!currentEditColumn?.ellipsis"
@update:value="
val => {
if (currentEditColumn) {
if (val) {
currentEditColumn.ellipsis = { tooltip: true };
} else {
delete currentEditColumn.ellipsis;
}
}
}
"
></NSwitch>
</NFormItem>
<!-- 只在启用 ellipsis 时显示 tooltip 开关 -->
<NFormItem v-if="currentEditColumn?.ellipsis" label="显示tooltip">
<NSwitch
:value="currentEditColumn?.ellipsis?.tooltip"
@update:value="
val => {
if (currentEditColumn?.ellipsis) {
currentEditColumn.ellipsis.tooltip = val;
}
}
"
></NSwitch>
</NFormItem>
</NForm>
<template #footer>
<NSpace justify="end">
<NButton @click="showEditDrawer = false">取消</NButton>
<NButton type="primary" :disabled="!currentEditColumn" @click="saveColumnEdit">保存</NButton>
</NSpace>
</template>
</NDrawerContent>
</NDrawer>
</template>
<style scoped>
:deep(.n-drawer .n-form-item) {
margin-bottom: 16px;
}
:deep(.n-drawer .n-radio-group) {
width: 100%;
display: flex;
gap: 8px;
}
:deep(.n-drawer .n-radio-button) {
flex: 1;
text-align: center;
}
.n-input-group {
display: flex;
gap: 8px;
}
.n-input-group .n-input {
flex: 1;
}
.n-divider {
margin: 16px 0;
}
</style>
<script setup lang="ts">
import { inject } from 'vue';
// 从主组件注入状态和方法
const tableState: any = inject('tableState');
const tableMethods: any = inject('tableMethods');
const { filteredColumns, visibleColumns, columnSearchText } = tableState;
const {
handleColumnVisibleChange,
clearColumnSearch,
handleEditColumn,
handleDragStart,
handleDragOver,
handleDrop,
handleSetSettings
} = tableMethods;
// 添加拖拽完成后的保存处理
const handleDragEnd = () => {
// 触发配置保存
if (handleSetSettings) {
handleSetSettings();
}
};
defineOptions({
name: 'ColumnSettings'
});
</script>
<template>
<div>
<div class="columns-header">
<span>显示字段</span>
<span>显示 {{ visibleColumns.length }}</span>
</div>
<div class="columns-search">
<NInput v-model:value="columnSearchText" placeholder="搜索列" clearable @clear="clearColumnSearch">
<template #prefix>
<NIcon>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
<path
d="M15.5 14h-.79l-.28-.27a6.5 6.5 0 1 0-.7.7l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0A4.5 4.5 0 1 1 14 9.5A4.5 4.5 0 0 1 9.5 14z"
fill="currentColor"
/>
</svg>
</NIcon>
</template>
</NInput>
</div>
<div class="columns-list">
<NScrollbar style="max-height: calc(100vh - 200px)">
<div class="columns-container">
<div
v-for="(column, index) in filteredColumns"
:key="column.key"
class="column-item"
draggable="true"
@dragstart="handleDragStart($event, index)"
@dragover="handleDragOver"
@drop="handleDrop($event, index)"
@dragend="handleDragEnd"
>
<div class="column-item-left">
<NIcon class="column-drag-handle">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">
<path d="M8 18h8v-2H8v2zm0-4h8v-2H8v2zm0-4h8V8H8v2zm0-4h8V4H8v2z" fill="currentColor" />
</svg>
</NIcon>
<NSwitch
:value="visibleColumns.includes(column.key)"
size="small"
@update:value="checked => handleColumnVisibleChange(checked, column)"
></NSwitch>
</div>
<div class="column-item-content">
<span class="column-title">{{ column.title }}</span>
<span class="column-key">({{ column.key }})</span>
</div>
<div class="column-item-right">
<NButton text @click="handleEditColumn(column)">
<template #icon>
<NIcon>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
>
<g fill="none">
<path
d="M3 17.75A3.25 3.25 0 0 0 6.25 21h4.915l.356-1.423l.02-.077H6.25a1.75 1.75 0 0 1-1.75-1.75V11h3.25l.184-.005A3.25 3.25 0 0 0 11 7.75V4.5h6.75c.966 0 1.75.784 1.75 1.75v4.982c.479-.19.994-.263 1.5-.22V6.25A3.25 3.25 0 0 0 17.75 3h-6.879a2.25 2.25 0 0 0-1.59.659L3.658 9.28A2.25 2.25 0 0 0 3 10.871v6.879zM7.75 9.5H5.561L9.5 5.561V7.75l-.006.144A1.75 1.75 0 0 1 7.75 9.5zm11.35 3.17l-5.903 5.902a2.686 2.686 0 0 0-.706 1.247l-.458 1.831a1.087 1.087 0 0 0 1.319 1.318l1.83-.457a2.685 2.685 0 0 0 1.248-.707l5.902-5.902A2.286 2.286 0 0 0 19.1 12.67z"
fill="currentColor"
></path>
</g>
</svg>
</NIcon>
</template>
</NButton>
</div>
</div>
</div>
</NScrollbar>
</div>
</div>
</template>
<style scoped>
.columns-header {
padding: 0 16px 12px;
margin: 0;
flex-shrink: 0;
display: flex;
justify-content: space-between;
}
.columns-search {
padding: 0 16px 12px;
flex-shrink: 0;
}
.columns-list {
flex: 1;
overflow: hidden;
}
.columns-container {
display: flex;
flex-direction: column;
}
.column-item {
padding: 8px 16px;
min-height: 40px;
box-sizing: border-box;
display: flex;
border-bottom: 1px solid var(--n-border-color);
align-items: center;
}
.column-item-left {
display: flex;
align-items: center;
gap: 12px;
}
.column-drag-handle {
cursor: move;
color: var(--n-text-color-3);
}
.column-item-content {
display: flex;
align-items: center;
gap: 8px;
flex: 1;
margin-left: 12px;
}
.column-title {
font-size: 12px;
color: var(--n-text-color);
}
.column-key {
font-size: 12px;
color: var(--n-text-color-3);
}
.column-item-right {
opacity: 0;
transition: opacity 0.3s;
margin-left: auto;
}
.column-item:hover .column-item-right {
opacity: 1;
}
.columns-search :deep(.n-input) {
--n-height: 32px;
}
.columns-search :deep(.n-input-wrapper) {
background-color: var(--n-card-color);
}
.columns-search :deep(.n-input__prefix) {
margin-right: 8px;
}
.columns-search :deep(.n-icon) {
font-size: 16px;
color: var(--n-text-color-3);
}
</style>
<script setup lang="ts">
import { defineAsyncComponent, inject, ref } from 'vue';
// 从主组件注入状态
const tableState: any = inject('tableState');
const tableMethods: any = inject('tableMethods');
const { uiConfig } = tableState;
const showDialog = ref(false);
// 添加当前编辑的行数据
const currentRow = ref<any>(null);
const isEditMode = ref(false);
// 动态加载组件
const DynamicComponent = defineAsyncComponent(() => import(uiConfig.CreateVueUrl));
// 处理子组件关闭事件
const handleClose = () => {
// console.log('关闭弹窗');
showDialog.value = false;
// 重置状态
currentRow.value = null;
isEditMode.value = false;
// 刷新表格数据
tableMethods.fetchData();
};
// 处理子组件提交事件
const handleSubmit = () => {
// console.log('提交表单');
showDialog.value = false;
// 重置状态
currentRow.value = null;
isEditMode.value = false;
// 刷新表格数据
tableMethods.fetchData();
};
// 打开弹窗的方法
const open = (row?: any) => {
// 如果传入了row参数,则表示是编辑模式
if (row) {
currentRow.value = row;
isEditMode.value = true;
// console.log('打开编辑模式', row);
} else {
currentRow.value = null;
isEditMode.value = false;
// console.log('打开新建模式');
}
showDialog.value = true;
};
// 暴露方法给父组件
defineExpose({
open
});
defineOptions({
name: 'CreateDialog'
});
</script>
<template>
<NModal
:show="showDialog"
preset="card"
style="width: 90%; max-width: 1200px"
:mask-closable="false"
@update:show="showDialog = $event"
>
<template #header>
<div class="dialog-header">
<span>{{ isEditMode ? '编辑' : '新建' }}</span>
</div>
</template>
<div class="dialog-content">
<DynamicComponent
v-if="showDialog"
:edit-data="currentRow"
:is-edit="isEditMode"
@close="handleClose"
@submit="handleSubmit"
/>
</div>
</NModal>
</template>
<style scoped>
.dialog-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.dialog-content {
width: 100%;
height: 100%;
}
</style>
<script setup lang="ts">
import { computed, h, inject, onMounted, watch } from 'vue';
// 从主组件注入状态和方法
const tableState: any = inject('tableState');
const tableMethods: any = inject('tableMethods');
// 通过window对象使用axios和dialog
const axios = (window as any).$axios;
const dialog = (window as any).$dialog;
const message = (window as any).$message;
// 获取主题状态
const themeStore = (window as any).$themeStore;
const isDarkMode = computed(() => themeStore?.darkMode || false);
const borderColor = computed(() => (isDarkMode.value ? '#303030' : '#ffffff'));
const {
tableData,
columns,
loading,
pagination,
tableSize,
showBorder,
showStripe,
tableHeight,
tableScrollWidth,
uiConfig
} = tableState;
const { handlePageChange, handlePageSizeChange, handleSorterChange, handleFiltersChange, fetchData, handleCreate } =
tableMethods;
// const handleDeleteRefresh = tableMethods.handleDeleteRefresh;
// 监听数据变化
watch(
() => tableData.value,
() => {
// console.log('表格数据已更新');
},
{ deep: true }
);
// 组件挂载后检查数据
onMounted(() => {
// 初始化完成
});
// 处理列配置,为表格列添加标签渲染支持
const processedColumns = computed(() => {
// 处理原始列配置
const baseColumns = columns.value.map((col: any) => {
// 如果已经有自定义render函数则保留
if (col.render) return col;
// 添加通用的render函数检查是否是标签类型
const newCol = { ...col };
// 为列添加渲染函数
newCol.render = (row: any) => {
const value = row[col.key];
// 检查是否是标签类型
// eslint-disable-next-line no-underscore-dangle
if (value && typeof value === 'object' && (value as any).__isTag) {
// console.log(`渲染标签:列=${col.key}, 值=${value.value}, 标签=${value.label}, 颜色=${value.color}`);
return h('div', { class: 'tag-container' }, [
h(
'div',
{
class: `table-tag ${value.color}`,
style: {
display: 'inline-block',
padding: '0 7px',
fontSize: '12px',
lineHeight: '20px',
borderRadius: '3px',
margin: 0,
backgroundColor: getTagColor(value.color),
color: '#fff'
}
},
value.label
)
]);
}
// 普通值直接返回
return value;
};
return newCol;
});
// 添加操作列
const actionColumn = {
title: '操作',
key: 'actions',
fixed: 'right',
width: 120,
render: (row: any) => {
return h(
'div',
{
style: `display: flex; align-items: center; justify-content: center; gap: 4px; background-color: ${
borderColor.value
}; padding: 4px; flex-direction: row; flex-wrap: nowrap; width: 70px;`
},
[
// 编辑按钮 - 使用用户提供的 SVG 图标
h(
'div',
{
style:
'display: inline-flex; align-items: center; justify-content: center; cursor: pointer; margin-right: 8px; transition: transform 0.2s ease; flex-shrink: 0;',
onClick: () => {
console.log('编辑', row);
// 使用handleCreate方法处理编辑操作
if (handleCreate) {
handleCreate(row);
} else {
console.warn('handleCreate方法未定义');
}
}
},
h(
'svg',
{
xmlns: 'http://www.w3.org/2000/svg',
width: '16',
height: '16',
viewBox: '0 0 32 32',
fill: 'none',
stroke: 'currentColor'
},
[
h('path', { d: 'M2 26h28v2H2z', fill: 'currentColor' }),
h('path', {
d: 'M25.4 9c.8-.8.8-2 0-2.8l-3.6-3.6c-.8-.8-2-.8-2.8 0l-15 15V24h6.4l15-15zm-5-5L24 7.6l-3 3L17.4 7l3-3zM6 22v-3.6l10-10l3.6 3.6l-10 10H6z',
fill: 'currentColor'
})
]
)
),
// 删除按钮 - 使用 SVG 图标
h(
'div',
{
style:
'display: inline-flex; align-items: center; justify-content: center; cursor: pointer; transition: transform 0.2s ease; flex-shrink: 0;',
onClick: () => {
// 创建确认对话框
dialog?.warning({
title: '确认删除',
content: '确定要删除这条数据吗?',
positiveText: '确定',
negativeText: '取消',
onPositiveClick: async () => {
try {
// 准备删除请求的参数
const Kvids = [row.Kvid];
// 发送删除请求
const response = await axios.post(uiConfig.InitDelete, { Kvids });
if (response.data) {
message?.success('删除成功');
// 重新加载数据
fetchData();
} else {
message?.error('删除失败');
}
} catch (error: any) {
// console.error('删除失败:', error);
message?.error(`删除失败: ${error.message || '未知错误'}`);
}
}
});
}
},
h(
'svg',
{
xmlns: 'http://www.w3.org/2000/svg',
width: '16',
height: '16',
viewBox: '0 0 20 20',
fill: 'none',
stroke: 'currentColor'
},
[
h('path', {
d: 'M11.5 4a1.5 1.5 0 0 0-3 0h-1a2.5 2.5 0 0 1 5 0H17a.5.5 0 0 1 0 1h-.554L15.15 16.23A2 2 0 0 1 13.163 18H6.837a2 2 0 0 1-1.987-1.77L3.553 5H3a.5.5 0 0 1-.492-.41L2.5 4.5A.5.5 0 0 1 3 4h8.5zm3.938 1H4.561l1.282 11.115a1 1 0 0 0 .994.885h6.326a1 1 0 0 0 .993-.885L15.438 5zM8.5 7.5c.245 0 .45.155.492.359L9 7.938v6.125c0 .241-.224.437-.5.437c-.245 0-.45-.155-.492-.359L8 14.062V7.939c0-.242.224-.438.5-.438zm3 0c.245 0 .45.155.492.359l.008.079v6.125c0 .241-.224.437-.5.437c-.245 0-.45-.155-.492-.359L11 14.062V7.939c0-.242.224-.438.5-.438z',
fill: 'currentColor'
})
]
)
)
]
);
}
};
return [...baseColumns, actionColumn];
});
// 根据标签类型获取颜色
function getTagColor(type: string) {
switch (type) {
case 'default':
return '#f0f0f0';
case 'primary':
return '#2080f0';
case 'info':
return '#909399';
case 'success':
return '#18a058';
case 'warning':
return '#f0a020';
case 'error':
return '#d03050';
default:
return '#f0f0f0';
}
}
</script>
<template>
<NCard class="search-card" :bordered="false" content-style="padding: 0 12px 8px;margin-top:10px">
<NDataTable
ref="table"
remote
:columns="processedColumns"
:data="tableData"
:loading="loading"
:pagination="{
...pagination,
onChange: handlePageChange,
onUpdatePageSize: handlePageSizeChange
}"
:row-key="row => row.Kvid"
:max-height="tableHeight"
:scroll-x="tableScrollWidth"
:single-line="!showBorder"
:striped="showStripe"
:size="tableSize"
@update:sorter="handleSorterChange"
@update:filters="handleFiltersChange"
>
<template #loading>
<NSpace vertical justify="center" class="py-4">
<NSpin size="medium"></NSpin>
<NText depth="3">正在加载数据...</NText>
</NSpace>
</template>
<template #empty>
<NEmpty description="暂无数据">
<template #extra>
<NButton size="small" @click="fetchData">重新加载</NButton>
</template>
</NEmpty>
</template>
</NDataTable>
</NCard>
</template>
<style scoped>
.search-card {
margin-bottom: 8px;
}
.tag-container {
display: flex;
align-items: center;
justify-content: flex-start;
}
:deep(.n-data-table) {
--n-merged-th-color: var(--n-table-color);
--n-merged-td-color: var(--n-table-color);
}
:deep(.n-data-table-base-table-body) {
overflow: auto !important;
}
:deep(.n-data-table-table) {
min-width: 100%;
}
/*:deep(.n-data-table__pagination) {*/
/* position: fixed;*/
/* bottom: 12px;*/
/* right: 12px;*/
/*}*/
/* 优化分页器页码选择器的样式 */
:deep(.n-pagination .n-pagination-size-picker) {
width: 84px !important;
}
:deep(.n-pagination .n-pagination-size-picker .n-base-selection) {
width: 84px !important;
}
/* 优化分页器整体布局 */
:deep(.n-pagination) {
display: flex;
align-items: center;
gap: 8px;
}
/* 确保总条数显示正常 */
:deep(.n-pagination-prefix) {
margin-right: auto;
white-space: nowrap;
}
/* 优化表格滚动相关样式 */
:deep(.n-data-table-base-table) {
width: 100%;
}
:deep(.n-data-table-base-table-body) {
overflow-x: auto !important;
}
:deep(.n-scrollbar-container) {
min-width: 100%;
}
/* 图标按钮样式 */
.icon-button {
display: inline-flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: transform 0.2s ease;
flex-shrink: 0;
}
.icon-button:hover {
transform: scale(1.2);
}
:deep(.n-data-table .action-buttons .n-button) {
min-width: 60px;
cursor: pointer !important;
}
/* 确保操作列固定在右侧 */
:deep(.n-data-table-td--fixed-right) {
background-color: v-bind(borderColor) !important;
right: 0px;
}
:deep(.n-data-table-th--fixed-right) {
background-color: v-bind(borderColor) !important;
right: 0px;
}
/* 直接针对最后一列的单元格设置样式 */
:deep(.n-data-table-td--last-col) {
background-color: v-bind(borderColor) !important;
}
</style>
<script setup lang="ts">
import { inject } from 'vue';
// 从主组件注入状态和方法
const tableState: any = inject('tableState');
const tableMethods: any = inject('tableMethods');
// 获取表格数据和列
const { tagMappings, transformRules, selectedField, availableFields } = tableState;
// 颜色选项
const colorOptions = [
{ label: '默认', value: 'default' },
{ label: '主要', value: 'primary' },
{ label: '信息', value: 'info' },
{ label: '成功', value: 'success' },
{ label: '警告', value: 'warning' },
{ label: '错误', value: 'error' }
];
// 添加/删除映射规则
const addTagMapping = () => {
tableMethods.addTagMapping();
};
const removeTagMapping = (index: number) => {
tableMethods.removeTagMapping(index);
};
// 添加转换规则
const addTransformRule = () => {
if (!selectedField.value) return;
// 过滤掉空值映射
const validMappings = tagMappings.value.filter((mapping: any) => mapping.value !== '' && mapping.label !== '');
if (validMappings.length === 0) {
window.$message?.error('请添加至少一个有效的标记映射');
return;
}
tableMethods.addTransformRule({
field: selectedField.value,
fieldLabel: availableFields.value.find((f: any) => f.value === selectedField.value)?.label || selectedField.value,
type: 'tag',
typeLabel: '转换标记',
params: { mappings: validMappings }
});
// 重置输入
selectedField.value = '';
tagMappings.value = [{ value: '', label: '', color: '' }];
};
// 移除转换规则
const removeTransformRule = (index: number) => {
tableMethods.removeTransformRule(index);
};
// 应用转换规则
const applyTransforms = () => {
tableMethods.applyTransformations();
};
// 重置数据转换
const resetData = () => {
// 清除所有转换规则
transformRules.value = [];
// 重新获取数据
tableMethods.fetchData();
window.$message?.success('数据已重置');
};
// 暴露方法给父组件
defineExpose({
transformRules,
applyTransforms,
resetData
});
defineOptions({
name: 'DataTransform'
});
</script>
<template>
<div class="data-transform-container">
<h3>数据转换设置</h3>
<div class="transform-form">
<div class="form-row">
<div class="form-item">
<label>字段名</label>
<NSelect v-model:value="selectedField" :options="availableFields" placeholder="请选择字段" />
</div>
</div>
<!-- 标记转换参数 -->
<div class="param-section">
<h4>转换内容</h4>
<div v-for="(mapping, index) in tagMappings" :key="index" class="mapping-item">
<div class="mapping-row">
<div class="mapping-field">
<label>Name</label>
<NInput v-model:value="mapping.value" placeholder="表格值" />
</div>
<div class="mapping-field">
<label>Value</label>
<NInput v-model:value="mapping.label" placeholder="显示文本" />
</div>
<div class="mapping-field">
<label>颜色</label>
<NSelect v-model:value="mapping.color" :options="colorOptions" placeholder="选择颜色" />
</div>
<NButton
circle
type="error"
size="small"
class="remove-btn"
:disabled="tagMappings.length === 1"
@click="removeTagMapping(index)"
>
<span>-</span>
</NButton>
</div>
</div>
<div class="add-mapping">
<NButton circle type="primary" size="small" class="add-btn" @click="addTagMapping">
<span>+</span>
</NButton>
</div>
</div>
<div class="button-row">
<NButton type="primary" :disabled="!selectedField" @click="addTransformRule">添加规则</NButton>
</div>
<div v-if="transformRules.length > 0" class="rules-list">
<h4>转换规则列表</h4>
<div v-for="(rule, index) in transformRules" :key="index" class="rule-item">
<div class="rule-content">
<span class="field-name">{{ rule.fieldLabel }}</span>
<span class="transform-arrow"></span>
<span class="transform-type">{{ rule.typeLabel }}</span>
</div>
<NButton size="small" type="error" @click="removeTransformRule(index)">删除</NButton>
</div>
<div class="action-buttons">
<NButton type="primary" :disabled="transformRules.length === 0" @click="applyTransforms">应用转换</NButton>
<NButton @click="resetData">重置数据</NButton>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.data-transform-container {
padding: 16px;
height: 100%;
overflow-y: auto;
}
.transform-form {
margin-top: 16px;
display: flex;
flex-direction: column;
gap: 16px;
}
.form-row {
display: flex;
gap: 12px;
}
.form-item {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.param-section {
border: 1px solid #eee;
border-radius: 4px;
padding: 12px;
}
.param-section h4 {
margin-top: 0;
margin-bottom: 12px;
color: #666;
}
.button-row {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
.rules-list {
margin-top: 24px;
border-top: 1px solid #eee;
padding-top: 16px;
}
.rule-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 12px;
background-color: #f8f8f8;
border-radius: 4px;
margin-bottom: 8px;
}
.rule-content {
display: flex;
align-items: center;
gap: 8px;
}
.field-name {
font-weight: 500;
}
.transform-arrow {
color: #666;
}
.transform-type {
color: #2080f0;
}
.action-buttons {
display: flex;
gap: 12px;
margin-top: 16px;
}
.mapping-item {
margin-bottom: 12px;
padding-bottom: 12px;
border-bottom: 1px dashed #eee;
}
.mapping-row {
display: flex;
gap: 8px;
align-items: flex-end;
}
.mapping-field {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.remove-btn {
margin-bottom: 4px;
}
.add-mapping {
display: flex;
justify-content: center;
margin-top: 8px;
}
</style>
<script setup lang="ts">
import { inject } from 'vue';
// 从主组件注入状态和方法
const tableState: any = inject('tableState');
const tableMethods: any = inject('tableMethods');
const { showSearchFieldDrawer, currentSearchField, newOptionKey, newOptionValue } = tableState;
const { saveSearchField, addOption, removeOption } = tableMethods;
defineOptions({
name: 'SearchFieldEditDrawer'
});
</script>
<template>
<NDrawer v-model:show="showSearchFieldDrawer" :width="400" placement="right">
<NDrawerContent title="编辑搜索字段">
<NForm ref="searchFieldForm" :model="currentSearchField" label-placement="left" label-width="100">
<NFormItem label="显示名称" path="label">
<NInput v-model:value="currentSearchField.label" placeholder="请输入显示名称"></NInput>
</NFormItem>
<NFormItem label="字段名" path="key">
<NInput v-model:value="currentSearchField.key" disabled></NInput>
</NFormItem>
<NFormItem label="控件类型" path="type">
<NSelect
v-model:value="currentSearchField.type"
:options="[
{ label: '输入框', value: 'input' },
{ label: '下拉选择', value: 'select' },
{ label: '日期选择', value: 'date' },
{ label: '数字范围', value: 'number-range' }
]"
></NSelect>
</NFormItem>
<NFormItem label="占位提示" path="placeholder">
<NInput v-model:value="currentSearchField.placeholder" placeholder="请输入占位提示"></NInput>
</NFormItem>
<NFormItem label="默认值" path="defaultValue">
<NInput v-model:value="currentSearchField.defaultValue" placeholder="请输入默认值"></NInput>
</NFormItem>
<!-- 当类型为 select 时显示选项配置 -->
<template v-if="currentSearchField.type === 'select'">
<NDivider>选项配置</NDivider>
<!-- 添加新选项的输入框 -->
<NSpace vertical>
<NInputGroup>
<NInput v-model:value="newOptionKey" placeholder="选项名称"></NInput>
<NInput v-model:value="newOptionValue" placeholder="选项值"></NInput>
<NButton type="primary" :disabled="!newOptionKey || !newOptionValue" @click="addOption">添加</NButton>
</NInputGroup>
</NSpace>
<!-- 已添加选项列表 -->
<NSpace vertical style="margin-top: 12px">
<div
v-for="(option, index) in currentSearchField.options"
:key="index"
style="display: flex; justify-content: space-between; align-items: center"
>
<span>{{ option.label }} - {{ option.value }}</span>
<NButton text type="error" @click="removeOption(index)">
<template #icon>
<NIcon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
fill="currentColor"
/>
</svg>
</NIcon>
</template>
</NButton>
</div>
</NSpace>
</template>
</NForm>
<template #footer>
<NSpace justify="end">
<NButton @click="showSearchFieldDrawer = false">取消</NButton>
<NButton type="primary" @click="saveSearchField">保存</NButton>
</NSpace>
</template>
</NDrawerContent>
</NDrawer>
</template>
<style scoped>
.n-input-group {
display: flex;
gap: 8px;
}
.n-input-group .n-input {
flex: 1;
}
.n-divider {
margin: 16px 0;
}
</style>
<script setup lang="ts">
import { inject } from 'vue';
// 从主组件注入状态和方法
const tableState: any = inject('tableState');
const tableMethods: any = inject('tableMethods');
const { searchFields } = tableState;
defineOptions({
name: 'SearchFieldSettings'
});
const {
addSearchField,
editSearchField,
removeSearchField,
updateFieldVisibility,
handleSearchFieldDragStart,
handleSearchFieldDragOver,
handleSearchFieldDrop
} = tableMethods;
</script>
<template>
<div>
<div class="search-config-header">
<span>搜索字段配置</span>
<NButton size="small" type="primary" @click="addSearchField">添加字段</NButton>
</div>
<div class="search-fields-list">
<NScrollbar style="max-height: calc(100vh - 200px)">
<div class="search-fields-container">
<div
v-for="field in searchFields"
:key="field.key"
class="search-field-item"
draggable="true"
@dragstart="handleSearchFieldDragStart($event, field)"
@dragover="handleSearchFieldDragOver"
@drop="handleSearchFieldDrop($event, field)"
>
<div class="field-item-left">
<NIcon class="field-drag-handle">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M8 18h8v-2H8v2zm0-4h8v-2H8v2zm0-4h8V8H8v2zm0-4h8V4H8v2z" fill="currentColor" />
</svg>
</NIcon>
<NSwitch
:value="field.visible"
size="small"
@update:value="val => updateFieldVisibility(field, val)"
></NSwitch>
</div>
<div class="field-item-content">
<span class="field-label">{{ field.label }}</span>
<span class="field-key">({{ field.key }})</span>
</div>
<div class="field-item-right">
<NButton text @click="editSearchField(field)">
<template #icon>
<NIcon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a.996.996 0 0 0 0-1.41l-2.34-2.34a.996.996 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z"
fill="currentColor"
/>
</svg>
</NIcon>
</template>
</NButton>
<NButton text @click="removeSearchField(field)">
<template #icon>
<NIcon>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z"
fill="currentColor"
/>
</svg>
</NIcon>
</template>
</NButton>
</div>
</div>
</div>
</NScrollbar>
</div>
</div>
</template>
<style scoped>
.search-config-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 16px 12px;
border-bottom: 1px solid var(--n-border-color);
}
.search-fields-container {
padding: 8px 0;
}
.search-field-item {
display: flex;
align-items: center;
padding: 8px 16px;
border-bottom: 1px solid var(--n-border-color);
cursor: move;
}
.field-item-left {
display: flex;
align-items: center;
gap: 12px;
}
.field-drag-handle {
cursor: move;
color: var(--n-text-color-3);
}
.field-item-content {
flex: 1;
margin: 0 12px;
}
.field-label {
font-size: 14px;
color: var(--n-text-color);
}
.field-key {
font-size: 12px;
color: var(--n-text-color-3);
margin-left: 8px;
}
.field-item-right {
display: flex;
gap: 4px;
opacity: 0;
transition: opacity 0.3s;
}
.search-field-item:hover .field-item-right {
opacity: 1;
}
</style>
<script setup lang="ts">
import { inject } from 'vue';
defineOptions({
name: 'SearchForm'
});
// 从主组件注入状态和方法
const tableState: any = inject('tableState');
const tableMethods: any = inject('tableMethods');
const { searchForm, formItems, isExpanded } = tableState;
const { handleSearch, resetSearch, fetchData, handleCreate } = tableMethods;
const toggleExpand = () => {
tableState.isExpanded.value = !tableState.isExpanded.value;
};
const openSettings = () => {
tableState.showSettingDrawer.value = true;
};
</script>
<template>
<NCard class="search-card" :bordered="false" content-style="padding: 0 12px 8px;margin-top:10px">
<NGrid :cols="6" x-gap="8" y-gap="8">
<!-- 默认显示前5个表单项 -->
<NGridItem v-for="item in formItems.slice(0, 5)" :key="item.key">
<NFormItem :label="item.label" label-placement="left">
<NInput
v-if="item.type === 'input'"
v-model:value="searchForm[item.key]"
:placeholder="item.placeholder"
></NInput>
<NSelect
v-if="item.type === 'select'"
v-model:value="searchForm[item.key]"
:options="item.options"
:placeholder="item.placeholder"
></NSelect>
<NDatePicker
v-if="item.type === 'date'"
v-model:value="searchForm[item.key]"
type="daterange"
:placeholder="item.placeholder"
clearable
></NDatePicker>
</NFormItem>
</NGridItem>
<!-- 未展开时的操作按钮 -->
<NGridItem v-if="!isExpanded">
<div class="operation-buttons">
<NSpace :size="8" align="center" :wrap="false">
<NButton type="primary" size="small" @click="() => handleCreate(null)">新建</NButton>
<NButton type="primary" size="small" @click="handleSearch">查询</NButton>
<NButton size="small" @click="resetSearch">重置</NButton>
<NButton size="small" @click="toggleExpand">
<template #icon>
<NIcon>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
>
<path d="M18 6.41L16.59 5L12 9.58L7.41 5L6 6.41l6 6z" fill="currentColor"></path>
<path d="M18 13l-1.41-1.41L12 16.17l-4.59-4.58L6 13l6 6z" fill="currentColor"></path>
</svg>
</NIcon>
</template>
</NButton>
<NButton size="small" title="刷新数据" @click="fetchData">
<template #icon>
<NIcon>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
>
<path
d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"
fill="currentColor"
></path>
</svg>
</NIcon>
</template>
</NButton>
<NButton size="small" @click="openSettings">
<template #icon>
<NIcon>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
>
<path
opacity=".3"
d="M19.28 8.6l-.7-1.21l-1.27.51l-1.06.43l-.91-.7c-.39-.3-.8-.54-1.23-.71l-1.06-.43l-.16-1.13L12.7 4h-1.4l-.19 1.35l-.16 1.13l-1.06.44c-.41.17-.82.41-1.25.73l-.9.68l-1.05-.42l-1.27-.52l-.7 1.21l1.08.84l.89.7l-.14 1.13c-.03.3-.05.53-.05.73s.02.43.05.73l.14 1.13l-.89.7l-1.08.84l.7 1.21l1.27-.51l1.06-.43l.91.7c.39.3.8.54 1.23.71l1.06.43l.16 1.13l.19 1.36h1.39l.19-1.35l.16-1.13l1.06-.43c.41-.17.82-.41 1.25-.73l.9-.68l1.04.42l1.27.51l.7-1.21l-1.08-.84l-.89-.7l.14-1.13c.04-.31.05-.52.05-.73c0-.21-.02-.43-.05-.73l-.14-1.13l.89-.7l1.1-.84zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4s4 1.79 4 4s-1.79 4-4 4z"
fill="currentColor"
></path>
<path
d="M19.43 12.98c.04-.32.07-.64.07-.98c0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46a.5.5 0 0 0-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65A.488.488 0 0 0 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1a.566.566 0 0 0-.18-.03c-.17 0-.34.09-.43.25l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46a.5.5 0 0 0 .61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.42.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.06.02.12.03.18.03c.17 0 .34-.09.43-.25l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zm-1.98-1.71c.04.31.05.52.05.73c0 .21-.02.43-.05.73l-.14 1.13l.89.7l1.08.84l-.7 1.21l-1.27-.51l-1.04-.42l-.9.68c-.43.32-.84.56-1.25.73l-1.06.43l-.16 1.13l-.2 1.35h-1.4l-.19-1.35l-.16-1.13l-1.06-.43c-.43-.18-.83-.41-1.23-.71l-.91-.7l-1.06.43l-1.27.51l-.7-1.21l1.08-.84l.89-.7l-.14-1.13c-.03-.31-.05-.54-.05-.74s.02-.43.05-.73l.14-1.13l-.89-.7l-1.08-.84l.7-1.21l1.27.51l1.04.42l.9-.68c.43-.32.84-.56 1.25-.73l1.06-.43l.16-1.13l.2-1.35h1.39l.19 1.35l.16 1.13l1.06.43c.43.18.83.41 1.23.71l.91.7l1.06-.43l1.27-.51l.7 1.21l-1.07.85l-.89.7l.14 1.13zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4s4-1.79 4-4s-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2s2 .9 2 2s-.9 2-2 2z"
fill="currentColor"
></path>
</svg>
</NIcon>
</template>
</NButton>
</NSpace>
</div>
</NGridItem>
<!-- 展开后的额外表单项 -->
<template v-if="isExpanded">
<NGridItem v-for="item in formItems.slice(5)" :key="item.key">
<NFormItem :label="item.label" label-placement="left">
<NInput
v-if="item.type === 'input'"
v-model:value="searchForm[item.key]"
:placeholder="item.placeholder"
></NInput>
<NSelect
v-if="item.type === 'select'"
v-model:value="searchForm[item.key]"
:options="item.options"
:placeholder="item.placeholder"
></NSelect>
<NDatePicker
v-if="item.type === 'date'"
v-model:value="searchForm[item.key]"
type="daterange"
:placeholder="item.placeholder"
clearable
></NDatePicker>
</NFormItem>
</NGridItem>
<!-- 展开后的操作按钮,独占一行并靠左对齐 -->
<NGridItem :span="6">
<div class="operation-buttons-expanded">
<NSpace :size="8" align="center" :wrap="false">
<NButton size="small" @click="() => handleCreate(null)">新建</NButton>
<NButton size="small" @click="handleSearch">查询</NButton>
<NButton size="small" @click="resetSearch">重置</NButton>
<NButton size="small" @click="toggleExpand">
<template #icon>
<NIcon>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 16 16"
>
<g fill="none">
<path
d="M4.26 8.3a.75.75 0 1 1-1.02-1.1l4.25-4a.75.75 0 0 1 1.02 0l4.25 4a.75.75 0 1 1-1.02 1.1L8 4.773L4.26 8.3zm0 4a.75.75 0 0 1-1.02-1.1l4.25-4a.75.75 0 0 1 1.02 0l4.25 4a.75.75 0 1 1-1.02 1.1L8 8.773L4.26 12.3z"
fill="currentColor"
></path>
</g>
</svg>
</NIcon>
</template>
</NButton>
<NButton size="small" title="刷新数据" @click="fetchData">
<template #icon>
<NIcon>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
>
<path
d="M17.65 6.35A7.958 7.958 0 0 0 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0 1 12 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"
fill="currentColor"
></path>
</svg>
</NIcon>
</template>
</NButton>
<NButton size="small" @click="openSettings">
<template #icon>
<NIcon>
<svg
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
viewBox="0 0 24 24"
>
<path
opacity=".3"
d="M19.28 8.6l-.7-1.21l-1.27.51l-1.06.43l-.91-.7c-.39-.3-.8-.54-1.23-.71l-1.06-.43l-.16-1.13L12.7 4h-1.4l-.19 1.35l-.16 1.13l-1.06.44c-.41.17-.82.41-1.25.73l-.9.68l-1.05-.42l-1.27-.52l-.7 1.21l1.08.84l.89.7l-.14 1.13c-.03.3-.05.53-.05.73s.02.43.05.73l.14 1.13l-.89.7l-1.08.84l.7 1.21l1.27-.51l1.06-.43l.91.7c.39.3.8.54 1.23.71l1.06.43l.16 1.13l.19 1.36h1.39l.19-1.35l.16-1.13l1.06-.43c.41-.17.82-.41 1.25-.73l.9-.68l1.04.42l1.27.51l.7-1.21l-1.08-.84l-.89-.7l.14-1.13c.04-.31.05-.52.05-.73c0-.21-.02-.43-.05-.73l-.14-1.13l.89-.7l1.1-.84zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4s4 1.79 4 4s-1.79 4-4 4z"
fill="currentColor"
></path>
<path
d="M19.43 12.98c.04-.32.07-.64.07-.98c0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46a.5.5 0 0 0-.61-.22l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65A.488.488 0 0 0 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1a.566.566 0 0 0-.18-.03c-.17 0-.34.09-.43.25l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98s.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46a.5.5 0 0 0 .61.22l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.42.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.06.02.12.03.18.03c.17 0 .34-.09.43-.25l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zm-1.98-1.71c.04.31.05.52.05.73c0 .21-.02.43-.05.73l-.14 1.13l.89.7l1.08.84l-.7 1.21l-1.27-.51l-1.04-.42l-.9.68c-.43.32-.84.56-1.25.73l-1.06.43l-.16 1.13l-.2 1.35h-1.4l-.19-1.35l-.16-1.13l-1.06-.43c-.43-.18-.83-.41-1.23-.71l-.91-.7l-1.06.43l-1.27.51l-.7-1.21l1.08-.84l.89-.7l-.14-1.13c-.03-.31-.05-.54-.05-.74s.02-.43.05-.73l.14-1.13l-.89-.7l-1.08-.84l.7-1.21l1.27.51l1.04.42l.9-.68c.43-.32.84-.56 1.25-.73l1.06-.43l.16-1.13l.2-1.35h1.39l.19 1.35l.16 1.13l1.06.43c.43.18.83.41 1.23.71l.91.7l1.06-.43l1.27-.51l.7 1.21l-1.07.85l-.89.7l.14 1.13zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4s4-1.79 4-4s-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2s2 .9 2 2s-.9 2-2 2z"
fill="currentColor"
></path>
</svg>
</NIcon>
</template>
</NButton>
</NSpace>
</div>
</NGridItem>
</template>
</NGrid>
</NCard>
</template>
<style scoped>
.search-card {
margin-bottom: 8px;
}
.operation-buttons {
display: flex;
align-items: center;
height: 100%;
padding-bottom: 2px;
}
.operation-buttons-expanded {
display: flex;
align-items: center;
padding: 8px 0 0 0;
justify-content: flex-end;
}
:deep(.n-space) {
flex-wrap: nowrap;
}
:deep(.n-form-item) {
margin-bottom: 0;
}
:deep(.n-form-item-label) {
width: 120px;
justify-content: flex-end;
padding: 0;
margin-right: 4px;
text-align: right;
white-space: normal;
}
:deep(.n-form-item-label label) {
display: inline-block;
text-align: right;
width: 100%;
}
:deep(.n-form-item-label label:after) {
content: '';
display: block;
text-align: left;
}
:deep(.n-form-item-blank) {
min-height: 28px;
justify-content: flex-end;
}
:deep(.n-input) {
--n-height: 28px;
}
:deep(.n-select) {
--n-height: 28px;
}
:deep(.n-date-picker) {
--n-height: 28px;
}
:deep(.n-form-item-feedback-wrapper) {
display: none;
}
</style>
<script setup lang="ts">
import { inject } from 'vue';
// 从主组件注入状态和方法
const tableState: any = inject('tableState');
const { tableSize, showBorder, showStripe, enableSingleSelect, pagination, tableScrollWidth, tableHeight } = tableState;
</script>
<template>
<div>
<!-- 表格大小设置 -->
<div class="setting-item">
<div class="setting-label">表格的大小</div>
<div class="setting-control">
<NRadioGroup v-model:value="tableSize" name="tableSize" class="size-group" size="small">
<NRadioButton value="small">紧凑</NRadioButton>
<NRadioButton value="medium">默认</NRadioButton>
<NRadioButton value="large">宽松</NRadioButton>
</NRadioGroup>
</div>
</div>
<!-- 表格纵向边框 -->
<div class="setting-item">
<div class="setting-label">表格纵向边框</div>
<div class="setting-control">
<NSwitch v-model:value="showBorder"></NSwitch>
</div>
</div>
<!-- 表格隔行换色 -->
<div class="setting-item">
<div class="setting-label">表格条纹</div>
<div class="setting-control">
<NSpace align="center">
<NSwitch v-model:value="showStripe"></NSwitch>
</NSpace>
</div>
</div>
<!-- 是否开启单选 -->
<div class="setting-item">
<div class="setting-label">是否开启序号</div>
<div class="setting-control">
<NSwitch v-model:value="enableSingleSelect"></NSwitch>
</div>
</div>
<!-- 每页显示条数 -->
<div class="setting-item">
<div class="setting-label">每页显示条数</div>
<div class="setting-control">
<NInputNumber v-model:value="pagination.pageSize"></NInputNumber>
</div>
</div>
<!-- 表格宽度 -->
<div class="setting-item">
<div class="setting-label">表格宽度</div>
<div class="setting-control">
<NInputNumber v-model:value="tableScrollWidth"></NInputNumber>
</div>
</div>
<!-- 表格高度 -->
<div class="setting-item">
<div class="setting-label">表格高度</div>
<div class="setting-control">
<NInputNumber v-model:value="tableHeight"></NInputNumber>
</div>
</div>
</div>
</template>
<style scoped>
.setting-item {
display: flex;
align-items: center;
justify-content: space-between;
font-size: 13px;
padding: 10px 0;
border-bottom: 1px solid var(--n-border-color);
}
.setting-item:last-child {
border-bottom: none;
}
.setting-label {
flex: 0 0 120px;
color: var(--n-text-color-2);
font-size: 14px;
}
.setting-control {
flex: 1;
display: flex;
align-items: center;
justify-content: flex-end;
}
.size-group {
width: 100%;
}
:deep(.n-radio-group) {
display: flex;
gap: 8px;
}
:deep(.n-radio-button) {
flex: 1;
text-align: center;
}
:deep(.n-select),
:deep(.n-input),
:deep(.n-input-number) {
width: 100%;
}
</style>
<script setup lang="ts">
import { defineExpose, inject, ref } from 'vue';
import TableBasicSettings from './TableBasicSettings.vue';
import ColumnSettings from './ColumnSettings.vue';
import SearchFieldSettings from './SearchFieldSettings.vue';
import DataTransform from './DataTransform.vue';
defineOptions({
name: 'TableSettingsDrawer'
});
// 通过window对象使用axios
const axios = (window as any).$axios; // 使用全局注入的axios实例
// 从主组件注入状态和方法
const tableState: any = inject('tableState');
const tableMethods: any = inject('tableMethods');
const {
showSettingDrawer,
activeTab,
tableSize,
showBorder,
showStripe,
enableSingleSelect,
tableHeight,
tableScrollWidth,
visibleColumns,
searchFields,
formItems,
uiConfig
} = tableState;
// 获取所有设置信息
const getAllSettings = () => {
// 获取列编辑配置
const columnConfigs = tableState.rawColumns.value.map((column: any, index: number) => ({
key: column.key,
title: column.title,
titleAlign: column.titleAlign,
align: column.align,
sortable: column.sortable,
filter: column.filter,
filterOptions: column.filterOptions,
ellipsis: column.ellipsis,
width: column.width,
minWidth: column.minWidth,
maxWidth: column.maxWidth,
fixed: column.fixed,
resizable: column.resizable,
order: index // 保存列的排序顺序
}));
// 获取搜索字段配置
const searchFieldConfigs = tableState.searchFields.value.map((field: any) => ({
key: field.key,
label: field.label,
type: field.type,
visible: field.visible,
defaultValue: field.defaultValue,
placeholder: field.placeholder,
options: field.options,
order: field.order
}));
// 获取数据转换配置
const transformConfigs = {
// 保存转换规则
transformRules: tableState.transformRules.value,
// 保存标签映射
tagMappings: tableState.tagMappings.value,
// 保存字段转换配置
transformConfigurations: tableState.transformConfigurations.value,
// 保存列的自定义渲染函数配置
columnRenderConfigs: tableState.rawColumns.value
// eslint-disable-next-line no-underscore-dangle
.filter((column: any) => column.__originalRender)
.map((column: any) => ({
key: column.key,
hasCustomRender: true
}))
};
return {
// 表格基本设置
tableSettings: {
tableSize: tableSize.value,
showBorder: showBorder.value,
showStripe: showStripe.value,
enableSingleSelect: enableSingleSelect.value,
tableHeight: tableHeight.value,
tableScrollWidth: tableScrollWidth.value
},
// 列设置
columnSettings: {
visibleColumns: visibleColumns.value,
columnConfigs,
columnOrder: tableState.rawColumns.value.map((col: any) => col.key) // 保存列的顺序
},
// 搜索设置
searchSettings: {
searchFields: searchFields.value,
formItems: formItems.value,
searchFieldConfigs
},
// 数据转换设置
transformSettings: transformConfigs
};
};
// 添加应用配置的方法
const applySettings = (settings: any) => {
// 应用表格基本设置
if (settings.tableSettings) {
tableSize.value = settings.tableSettings.tableSize;
showBorder.value = settings.tableSettings.showBorder;
showStripe.value = settings.tableSettings.showStripe;
enableSingleSelect.value = settings.tableSettings.enableSingleSelect;
tableHeight.value = settings.tableSettings.tableHeight;
tableScrollWidth.value = settings.tableSettings.tableScrollWidth;
}
// 应用列设置
if (settings.columnSettings) {
visibleColumns.value = settings.columnSettings.visibleColumns;
// 应用列排序
if (settings.columnSettings.columnOrder) {
// 根据保存的顺序重新排列列
const orderedColumns: any[] = [];
settings.columnSettings.columnOrder.forEach((key: any) => {
const column = tableState.rawColumns.value.find((col: any) => col.key === key);
if (column) {
orderedColumns.push(column);
}
});
// 添加可能新增的列(不在保存的顺序中的列)
tableState.rawColumns.value.forEach((column: any) => {
if (!settings.columnSettings.columnOrder.includes(column.key)) {
orderedColumns.push(column);
}
});
// 更新列配置
tableState.rawColumns.value = orderedColumns;
}
// 应用列配置
if (settings.columnSettings.columnConfigs) {
tableState.rawColumns.value = tableState.rawColumns.value.map((column: any) => {
const config = settings.columnSettings.columnConfigs.find((c: any) => c.key === column.key);
if (config) {
return {
...column,
...config
};
}
return column;
});
}
}
// 应用搜索设置
if (settings.searchSettings) {
searchFields.value = settings.searchSettings.searchFields;
formItems.value = settings.searchSettings.formItems;
// 应用搜索字段配置
if (settings.searchSettings.searchFieldConfigs) {
tableState.searchFields.value = settings.searchSettings.searchFieldConfigs;
}
}
// 应用数据转换设置
if (settings.transformSettings) {
// 恢复转换规则
if (settings.transformSettings.transformRules) {
tableState.transformRules.value = settings.transformSettings.transformRules;
}
// 恢复标签映射
if (settings.transformSettings.tagMappings) {
tableState.tagMappings.value = settings.transformSettings.tagMappings;
}
// 恢复字段转换配置
if (settings.transformSettings.transformConfigurations) {
tableState.transformConfigurations.value = settings.transformSettings.transformConfigurations;
}
// 恢复列的自定义渲染配置
if (settings.transformSettings.columnRenderConfigs) {
settings.transformSettings.columnRenderConfigs.forEach((config: any) => {
const column = tableState.rawColumns.value.find((col: any) => col.key === config.key);
if (column && config.hasCustomRender) {
// 使用 tableMethods 中的方法更新列渲染函数
tableMethods.updateTransformedColumnRenders();
}
});
}
// 如果有转换规则,自动应用转换
if (settings.transformSettings.transformRules?.length > 0) {
tableMethods.applyTransformations();
}
}
};
const settings = ref({});
// 获取配置信息的方法
const handleGetSettings = async () => {
try {
// 在请求前检查必要的配置参数
if (!uiConfig.GetUrl || !uiConfig.Type) {
// console.warn('缺少必要的配置参数,无法获取配置');
return;
}
const response = await axios.post(uiConfig.GetUrl, {
Type: uiConfig.Type,
InternalCode: uiConfig.InternalCode,
IsDefault: uiConfig.IsDefault
});
if (response.data?.Parameters) {
settings.value = JSON.parse(response.data.Parameters);
// console.log('获取到的配置信息:', settings.value);
applySettings(settings.value);
// window.$message?.success('配置信息获取成功');
} else {
// console.log('没有获取到配置参数,使用默认配置');
}
} catch (error) {
// console.error('获取配置失败:', error);
window.$message?.error('获取配置失败');
}
};
// 保存配置信息的方法
const handleSetSettings = async () => {
try {
const response = await axios.post(uiConfig.SetUrl, {
Type: uiConfig.Type,
InternalCode: uiConfig.InternalCode,
IsDefault: uiConfig.IsDefault,
Parameters: JSON.stringify(getAllSettings())
});
if (response.data) {
// 应用配置
applySettings(settings.value);
window.$message?.success('配置信息保存成功');
handleGetSettings();
}
} catch (error) {
// console.error('保存配置失败:', error);
window.$message?.error('保存配置失败');
}
};
// 导出方法供外部使用
defineExpose({
getAllSettings,
applySettings,
handleGetSettings
});
</script>
<template>
<NDrawer v-model:show="showSettingDrawer" :width="550" placement="right">
<NDrawerContent title="表格设置">
<div class="settings-container">
<NTabs v-model:value="activeTab" placement="left" type="line" class="settings-tabs">
<NTabPane name="table" tab="表格设置">
<div class="tab-content">
<TableBasicSettings />
</div>
</NTabPane>
<NTabPane name="columns" tab="列设置">
<div class="tab-content">
<ColumnSettings />
</div>
</NTabPane>
<NTabPane name="buttons" tab="搜索配置">
<div class="tab-content">
<SearchFieldSettings />
</div>
</NTabPane>
<NTabPane name="transform" tab="数据转换">
<div class="tab-content">
<DataTransform />
</div>
</NTabPane>
</NTabs>
</div>
<div style="position: fixed; top: 5px; right: 5px">
<NButton type="primary" style="margin-right: 5px" @click="handleGetSettings">获取配置信息</NButton>
<NButton type="primary" @click="handleSetSettings">应用配置信息</NButton>
</div>
</NDrawerContent>
</NDrawer>
</template>
<style scoped>
.settings-container {
height: 80%;
display: flex;
}
.settings-tabs {
flex: 1;
height: 100%;
}
.tab-content {
padding: 16px 0;
height: 100%;
display: flex;
flex-direction: column;
}
:deep(.n-tabs) {
height: 100%;
}
:deep(.n-tabs-nav) {
width: 100px;
border-right: 1px solid var(--n-border-color);
}
:deep(.n-tabs-nav__scroll-container) {
padding: 8px 0;
}
:deep(.n-tabs-tab) {
justify-content: flex-start;
padding: 12px 16px;
}
:deep(.n-tabs-tab-wrapper) {
padding: 0;
}
:deep(.n-tabs-wrapper) {
flex: 1;
height: 100%;
}
:deep(.n-tab-pane) {
padding: 0;
height: 100%;
}
:deep(.n-drawer-content) {
padding: 0;
}
:deep(.n-drawer-header) {
padding: 16px;
border-bottom: 1px solid var(--n-border-color);
}
:deep(.n-drawer-header__main) {
font-size: 16px;
font-weight: 500;
}
</style>
<script setup lang="ts">
/* eslint-disable no-underscore-dangle */
import { computed, defineProps, h, nextTick, onActivated, onMounted, provide, ref, watch, withDefaults } from 'vue';
import SearchForm from './codet/components/SearchForm.vue';
import DataTable from './codet/components/DataTable.vue';
import TableSettingsDrawer from './codet/components/TableSettingsDrawer.vue';
import ColumnEditDrawer from './codet/components/ColumnEditDrawer.vue';
import SearchFieldEditDrawer from './codet/components/SearchFieldEditDrawer.vue';
import AddFieldDrawer from './codet/components/AddFieldDrawer.vue';
import DataTransform from './codet/components/DataTransform.vue';
import CreateDialog from './codet/components/CreateDialog.vue';
// 通过window对象使用axios - 已在main.ts中全局注册
const axios = (window as any).$axios;
// 定义组件名称,避免与HTML元素冲突
defineOptions({
name: 'TableView'
});
const tableData = ref([]);
const loading = ref(false);
// 在 script setup 中添加响应式变量
const tableSize = ref('medium'); // 表格大小
const showBorder = ref(true); // 表格纵向边框
const showStripe = ref(true); // 表格隔行换色
const enableSingleSelect = ref(false); // 是否开启序号
const activeTab = ref('table'); // 当前激活的标签页
// 定义props
interface Props {
uiConfig?: Record<string, any>;
}
const props = withDefaults(defineProps<Props>(), {
uiConfig: () => ({})
});
// 分页配置
const pagination = ref({
page: 1,
pageSize: 20,
itemCount: 0,
showSizePicker: true,
pageSizes: [20, 30, 40, 50],
prefix: ({ itemCount }: { itemCount: number }) => `共 ${itemCount} 条`
});
// 定义列的类型
interface TableColumn {
title: string;
key: string;
width: number;
minWidth?: number;
render?: (row: any, index?: number) => any; // 修改render函数类型定义
ellipsis?: {
tooltip: boolean;
};
resizable?: boolean;
align?: 'left' | 'center' | 'right';
titleAlign?: 'left' | 'center' | 'right';
sortable?: boolean;
sorter?: (row1: any, row2: any) => number;
filter?: boolean;
filterOptions?: { label: string; value: string }[];
filterOptionValues?: string[] | null; // 用于存储当前选中的筛选值
__originalRender?: (row: any, index?: number) => any; // 备份原始渲染函数
}
// 接口类型定义
interface SearchFieldOption {
label: string;
value: string;
}
interface SearchField {
label: string;
key: string;
type: string;
visible: boolean;
defaultValue: string;
placeholder: string;
options: SearchFieldOption[];
order: number;
}
interface TagMapping {
value: string;
label: string;
color: string;
}
interface TransformRule {
field: string;
fieldLabel: string;
type: string;
typeLabel: string;
params: {
mappings: TagMapping[];
};
}
// 添加日期格式化函数
const formatDate = (dateStr: string) => {
if (!dateStr) return '';
const date = new Date(dateStr);
if (Number.isNaN(date.getTime())) return dateStr;
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// 原始列配置
const rawColumns = ref<TableColumn[]>([]);
// 添加列配置相关的响应式变量
const visibleColumns = ref<string[]>([]);
// 当前筛选状态
const currentFilters = ref<Record<string, any>>({});
// 获取当前所有列的筛选状态
const getFiltersState = () => {
const filters: Record<string, string[]> = {};
rawColumns.value.forEach(col => {
if (col.filter && col.filterOptionValues && col.filterOptionValues.length) {
filters[col.key] = col.filterOptionValues;
}
});
return filters;
};
// 筛选改变
const handleFiltersChange = (filters: any) => {
// 保存当前筛选状态到currentFilters
// 格式化筛选参数,确保数组标识在冒号后面,值在数组里
const formattedFilters: Record<string, any> = {};
for (const key in filters) {
if (filters[key] && filters[key].length) {
// 移除可能存在的数组标记
const cleanKey = key.replace('[]', '');
// 将值作为数组存储
formattedFilters[cleanKey] = filters[key];
}
}
currentFilters.value = formattedFilters;
pagination.value.page = 1;
fetchData();
};
// 修改 generateColumns 函数
const generateColumns = (data: any): TableColumn[] => {
if (!data) return [];
const columns: TableColumn[] = [];
// 添加序号列
if (enableSingleSelect.value) {
columns.push({
title: '序号',
key: 'index',
width: 80,
minWidth: 50,
render: (_, index = 0) => index + 1 + (pagination.value.page - 1) * pagination.value.pageSize
});
}
const processField = (obj: any, prefix = '') => {
Object.entries(obj).forEach(([key, value]) => {
// 处理数组类型字段
if (Array.isArray(value)) {
const columnKey = prefix + key;
// 查找原有列的配置
const existingColumn = rawColumns.value.find(col => col.key === columnKey);
columns.push({
title: key,
key: columnKey,
width: 200,
minWidth: 150,
ellipsis: existingColumn?.ellipsis ?? {
tooltip: true
},
resizable: true,
sortable: false, // 数组类型字段不支持排序
render: (row: any) => {
const arrayValue = row[key];
if (Array.isArray(arrayValue)) {
return arrayValue
.map(item => {
if (typeof item === 'object') {
return Object.entries(item)
.map(([k, v]) => `${k}: ${v}`)
.join(', ');
}
return String(item);
})
.join('; ');
}
return '';
}
});
}
// 处理嵌套对象
else if (value && typeof value === 'object' && !Array.isArray(value)) {
Object.entries(value as object).forEach(([nestedKey, _]) => {
const columnKey = `${key}.${nestedKey}`;
// 查找原有列的配置
const existingColumn = rawColumns.value.find(col => col.key === columnKey);
columns.push({
title: nestedKey,
key: columnKey,
width: key.includes('Time') ? 200 : 150,
minWidth: 100,
ellipsis: existingColumn?.ellipsis ?? {
tooltip: true
},
resizable: true,
sortable: existingColumn?.sortable ?? false,
...(existingColumn?.filter
? {
filter: existingColumn.filter,
filterOptions: existingColumn.filterOptions,
filterOptionValues: existingColumn.filterOptionValues
}
: {}),
render:
key.includes('Time') || key.includes('Date')
? (row: any) => formatDate(row[key]?.[nestedKey])
: undefined,
sorter: (row1: any, row2: any) => {
const val1 = row1[key]?.[nestedKey];
const val2 = row2[key]?.[nestedKey];
if (val1 > val2) return 1;
if (val1 < val2) return -1;
return 0;
}
});
});
}
// 处理普通字段
else {
const columnKey = prefix + key;
// 查找原有列的配置
const existingColumn = rawColumns.value.find(col => col.key === columnKey);
columns.push({
title: key,
key: columnKey,
width: key.includes('Time') ? 200 : 150,
minWidth: 100,
ellipsis: existingColumn?.ellipsis ?? {
tooltip: true
},
resizable: true,
sortable: existingColumn?.sortable ?? false,
...(existingColumn?.filter
? {
filter: existingColumn.filter,
filterOptions: existingColumn.filterOptions,
filterOptionValues: existingColumn.filterOptionValues
}
: {}),
render: key.includes('Time') || key.includes('Date') ? (row: any) => formatDate(row[key]) : undefined,
sorter: (row1: any, row2: any) => {
const val1 = row1[key];
const val2 = row2[key];
if (val1 > val2) return 1;
if (val1 < val2) return -1;
return 0;
}
});
}
});
};
processField(data);
// 更新 rawColumns,但保留原有的排序设置
rawColumns.value = columns;
// 初始化 visibleColumns
if (visibleColumns.value.length === 0) {
visibleColumns.value = columns.map(col => col.key);
}
return columns;
};
// 修改计算属性,根据 visibleColumns 过滤显示的列
const columns = computed(() => {
return rawColumns.value
.filter(col => visibleColumns.value.includes(col.key))
.map(col => ({
...col,
title: col.title,
width: col.width,
sortable: col.sortable,
sorter: col.sortable
? (row1: any, row2: any) => {
const val1 = col.key.includes('.') ? row1[col.key.split('.')[0]]?.[col.key.split('.')[1]] : row1[col.key];
const val2 = col.key.includes('.') ? row2[col.key.split('.')[0]]?.[col.key.split('.')[1]] : row2[col.key];
// 处理数字类型
if (!Number.isNaN(Number(val1)) && !Number.isNaN(Number(val2))) {
return Number(val1) - Number(val2);
}
// 处理日期类型
if (col.key.includes('Time') || col.key.includes('Date')) {
// 使用formatDate函数格式化日期,然后进行比较
const formattedDate1 = formatDate(val1);
const formattedDate2 = formatDate(val2);
// 如果格式化后的日期相同,则使用原始时间戳比较
if (formattedDate1 === formattedDate2) {
return new Date(val1).getTime() - new Date(val2).getTime();
}
// 否则比较格式化后的日期字符串
return formattedDate1.localeCompare(formattedDate2);
}
// 处理字符串类型
return String(val1).localeCompare(String(val2));
}
: undefined,
// 处理筛选相关属性
...(col.filter
? {
filter: true,
filterOptions: col.filterOptions,
filterOptionValues: col.filterOptionValues,
// 添加筛选变化处理函数
onFilterChange: (values: string[]) => {
const columnIndex = rawColumns.value.findIndex(c => c.key === col.key);
if (columnIndex !== -1) {
// 保存筛选值到列配置
rawColumns.value[columnIndex].filterOptionValues = values.length ? values : null;
// 生成筛选状态并触发表格刷新
handleFiltersChange(getFiltersState());
}
}
}
: {})
}));
});
// 添加 watch 以在 enableSingleSelect 变化时重新生成列
watch(enableSingleSelect, () => {
if (tableData.value.length > 0) {
rawColumns.value = generateColumns(tableData.value[0]);
}
});
// 当前排序状态
const currentSorter = ref();
// 添加搜索表单的展开状态
const isExpanded = ref(true);
// 在 script setup 部分添加以下代码
const searchFields = ref<SearchField[]>([]);
// 在 script setup 部分添加以下代码
const searchForm = ref<Record<string, any>>({});
// 修改 watch searchFields 的处理
watch(
searchFields,
newFields => {
// 更新 searchForm 结构
const newSearchForm: Record<string, any> = {};
newFields.forEach(field => {
// 根据不同类型设置不同的初始值,优先使用默认值
if (field.type === 'date') {
newSearchForm[field.key] = field.defaultValue || null;
} else if (field.type === 'select') {
newSearchForm[field.key] = field.defaultValue || undefined;
} else {
newSearchForm[field.key] = field.defaultValue || '';
}
});
searchForm.value = newSearchForm;
},
{ deep: true }
);
// 处理搜索
const handleSearch = () => {
pagination.value.page = 1;
fetchData();
};
// 重置搜索表单
const resetSearch = () => {
// 只保留分页参数,清除其他所有搜索条件
const newSearchForm = {
Skip: (pagination.value.page - 1) * pagination.value.pageSize,
Take: pagination.value.pageSize
};
searchForm.value = newSearchForm;
handleSearch();
};
// 添加 CreateDialog 的引用
const createDialogRef = ref();
// 添加一个标志来追踪配置是否已加载
const isConfigLoaded = ref(false);
// 添加 TableSettingsDrawer 的引用
const tableSettingsDrawerRef = ref();
// 添加筛选功能相关配置
const newFilterLabel = ref('');
const newFilterValue = ref('');
// 添加获取所有设置的方法
const getAllTableSettings = async () => {
if (tableSettingsDrawerRef.value) {
await tableSettingsDrawerRef.value.handleGetSettings();
}
};
// 处理新建
const handleCreate = (row?: any) => {
if (row) {
// 编辑模式
// console.log('进入编辑模式', row);
// 打开编辑弹窗并传递当前行数据
createDialogRef.value?.open(row);
} else {
// 新建模式
// console.log('进入新建模式');
// 打开新建弹窗
createDialogRef.value?.open();
}
};
// 处理搜索参数
const buildSearchParams = () => {
const searchParams: any = {
...searchForm.value,
Skip: (pagination.value.page - 1) * pagination.value.pageSize,
Take: pagination.value.pageSize,
OrderBy: currentSorter.value
? (currentSorter.value.order === 'descend' ? '-' : '') + currentSorter.value.columnKey
: undefined
};
// 处理筛选参数
if (Object.keys(currentFilters.value).length) {
for (const key in currentFilters.value) {
if (
currentFilters.value[key] &&
Array.isArray(currentFilters.value[key]) &&
currentFilters.value[key].length > 0
) {
searchParams[key] = currentFilters.value[key];
}
}
}
// 处理表单中的值为空或undefined的情况,创建新对象而不是删除属性
const cleanedParams: any = {};
for (const key in searchParams) {
if (searchParams[key] !== '' && searchParams[key] !== null && searchParams[key] !== undefined) {
cleanedParams[key] = searchParams[key];
}
}
Object.assign(searchParams, cleanedParams);
// 清空原对象的其他属性
for (const key in searchParams) {
if (!(key in cleanedParams)) {
searchParams[key] = undefined;
}
}
// 处理日期范围
if (searchForm.value.下单时间) {
searchParams.下单时间开始 = searchForm.value.下单时间[0];
searchParams.下单时间结束 = searchForm.value.下单时间[1];
delete searchParams.下单时间;
}
return searchParams;
};
// 获取数据
async function fetchData() {
console.log('🚀 fetchData 被调用,axios 可用:', Boolean(axios));
loading.value = true;
try {
const searchParams = buildSearchParams();
// 自定义axios请求配置,使用自定义参数序列化函数
console.log('📡 发送请求到 /table.json');
const response = await axios.get('/table.json', {
params: searchParams,
paramsSerializer: (params: any) => {
// 自定义参数序列化,避免数组被转换为key[]=value格式
const queryParams: string[] = [];
for (const key in params) {
if (params[key] !== undefined && params[key] !== null) {
if (Array.isArray(params[key])) {
// 只在数组不为空时添加参数
if (params[key].length > 0) {
// 数组类型使用JSON.stringify处理,并将参数名变为复数形式
queryParams.push(`${encodeURIComponent(`${key}s`)}=${encodeURIComponent(JSON.stringify(params[key]))}`);
}
} else {
// 非数组类型参数保持原样
queryParams.push(`${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`);
}
}
}
return queryParams.join('&');
}
});
console.log('✅ 请求成功,响应:', response.status, response.data?.Results?.length || 0, '条数据');
if (response.data?.Results?.length) {
// 保存当前的filter状态
const currentFilterState: Record<
string,
{
filter: boolean;
filterOptions?: { label: string; value: string }[];
filterOptionValues?: string[] | null;
}
> = rawColumns.value.reduce((acc: Record<string, any>, col) => {
if (col.filter && col.filterOptionValues) {
acc[col.key] = {
filter: col.filter,
filterOptions: col.filterOptions,
filterOptionValues: col.filterOptionValues
};
}
return acc;
}, {});
// 先获取字段配置
let fieldConfigs = [];
try {
const entityResponse = await axios.get(`/Server/Entity/${props.uiConfig.Type}`);
if (entityResponse.data?.Results) {
fieldConfigs = entityResponse.data.Results;
}
} catch (error) {
// console.error('获取字段配置失败:', error);
}
// 生成列配置时传入字段配置
const generateColumnsWithConfig = (data: any, configs: any[]): TableColumn[] => {
if (!data) return [];
const newColumns: TableColumn[] = [];
// 添加序号列
if (enableSingleSelect.value) {
newColumns.push({
title: '序号',
key: 'index',
width: 60,
minWidth: 30,
render: (_, index = 0) => index + 1 + (pagination.value.page - 1) * pagination.value.pageSize
});
}
const processField = (obj: any, prefix = '') => {
Object.entries(obj).forEach(([key, value]) => {
// 查找字段配置
const fieldConfig = configs.find(field => field.Name === key);
const displayName = fieldConfig?.DisplayName || key;
// 处理数组类型字段
if (Array.isArray(value)) {
const columnKey = prefix + key;
const existingColumn = rawColumns.value.find(col => col.key === columnKey);
newColumns.push({
title: displayName,
key: columnKey,
width: 200,
minWidth: 150,
ellipsis: existingColumn?.ellipsis ?? {
tooltip: true
},
resizable: true,
sortable: false,
render: (row: any) => {
const arrayValue = row[key];
if (Array.isArray(arrayValue)) {
return arrayValue
.map(item => {
if (typeof item === 'object') {
return Object.entries(item)
.map(([k, v]) => `${k}: ${v}`)
.join(', ');
}
return String(item);
})
.join('; ');
}
return '';
}
});
}
// 处理嵌套对象
else if (value && typeof value === 'object' && !Array.isArray(value)) {
Object.entries(value as object).forEach(([nestedKey, _]) => {
const columnKey = `${key}.${nestedKey}`;
const existingColumn = rawColumns.value.find(col => col.key === columnKey);
const nestedFieldConfig = configs.find(field => field.Name === nestedKey);
const nestedDisplayName = nestedFieldConfig?.DisplayName || nestedKey;
newColumns.push({
title: nestedDisplayName,
key: columnKey,
width: key.includes('Time') ? 200 : 150,
minWidth: 100,
ellipsis: existingColumn?.ellipsis ?? {
tooltip: true
},
resizable: true,
sortable: existingColumn?.sortable ?? false,
...(existingColumn?.filter
? {
filter: existingColumn.filter,
filterOptions: existingColumn.filterOptions,
filterOptionValues: existingColumn.filterOptionValues
}
: {}),
render:
key.includes('Time') || key.includes('Date')
? (row: any) => formatDate(row[key]?.[nestedKey])
: undefined,
sorter: (row1: any, row2: any) => {
const val1 = row1[key]?.[nestedKey];
const val2 = row2[key]?.[nestedKey];
if (val1 > val2) return 1;
if (val1 < val2) return -1;
return 0;
}
});
});
}
// 处理普通字段
else {
const columnKey = prefix + key;
const existingColumn = rawColumns.value.find(col => col.key === columnKey);
newColumns.push({
title: displayName,
key: columnKey,
width: key.includes('Time') ? 200 : 150,
minWidth: 100,
ellipsis: existingColumn?.ellipsis ?? {
tooltip: true
},
resizable: true,
sortable: existingColumn?.sortable ?? false,
...(existingColumn?.filter
? {
filter: existingColumn.filter,
filterOptions: existingColumn.filterOptions,
filterOptionValues: existingColumn.filterOptionValues
}
: {}),
render: key.includes('Time') || key.includes('Date') ? (row: any) => formatDate(row[key]) : undefined,
sorter: (row1: any, row2: any) => {
const val1 = row1[key];
const val2 = row2[key];
if (val1 > val2) return 1;
if (val1 < val2) return -1;
return 0;
}
});
}
});
};
processField(data);
return newColumns;
};
// 使用新的生成函数生成列配置
rawColumns.value = generateColumnsWithConfig(response.data.Results[0], fieldConfigs);
// 确保所有列在rawColumns更新后默认可见
visibleColumns.value = rawColumns.value.map(col => col.key);
// 恢复之前的筛选状态
if (Object.keys(currentFilterState).length > 0) {
rawColumns.value.forEach(col => {
const savedState = currentFilterState[col.key];
if (savedState) {
col.filter = savedState.filter;
col.filterOptions = savedState.filterOptions;
col.filterOptionValues = savedState.filterOptionValues;
}
});
}
tableData.value = response.data.Results;
pagination.value.itemCount = response.data.Total || 0;
// 移除多余的判断,简化配置加载逻辑
if (!isConfigLoaded.value) {
// console.log('首次加载配置');
isConfigLoaded.value = true;
nextTick(() => {
getAllTableSettings();
});
}
} else {
// 如果没有数据,清空表格数据并设置总数为0
tableData.value = [];
pagination.value.itemCount = response.data?.Total || 0;
}
} catch (error) {
console.error('❌ 请求失败:', error);
tableData.value = [];
pagination.value.itemCount = 0;
} finally {
loading.value = false;
}
}
// 添加删除后的数据刷新处理方法
const handleDeleteRefresh = async () => {
// 记录删除前的数据条数
const beforeDeleteCount = tableData.value.length;
const currentPage = pagination.value.page;
// 先刷新数据获取最新的总数
await fetchData();
// 如果当前页没有数据了,且不是第一页,则跳转到前一页
if (tableData.value.length === 0 && currentPage > 1) {
// console.log('当前页无数据,跳转到前一页');
pagination.value.page = currentPage - 1;
await fetchData();
}
// 如果删除后当前页还有数据,但数据条数减少了,说明删除成功
else if (beforeDeleteCount > tableData.value.length || tableData.value.length === 0) {
// console.log('删除成功,数据已更新');
}
};
// 页码改变
const handlePageChange = (page: number) => {
pagination.value.page = page;
fetchData();
};
// 每页条数改变
const handlePageSizeChange = (pageSize: number) => {
pagination.value.pageSize = pageSize;
pagination.value.page = 1;
fetchData();
};
// 排序改变
const handleSorterChange = (sorter: { columnKey: string; order: 'ascend' | 'descend' | false }) => {
currentSorter.value = sorter;
fetchData();
};
const showSettingDrawer = ref(false);
const tableHeight = ref(600);
const tableScrollWidth = ref(0); // 设置一个默认值
// 添加列配置相关的响应式变量
const showEditDrawer = ref(false);
const currentEditColumn = ref<TableColumn | null>(null);
// 修改拖拽排序相关的方法
const handleDragStart = (e: DragEvent, index: number) => {
e.dataTransfer?.setData('text/plain', index.toString());
};
const handleDragOver = (e: DragEvent) => {
e.preventDefault();
};
const handleDrop = (e: DragEvent, toIndex: number) => {
e.preventDefault();
const fromIndex = Number(e.dataTransfer?.getData('text/plain'));
if (!Number.isNaN(fromIndex)) {
const reorderedColumns = [...rawColumns.value];
const [movedColumn] = reorderedColumns.splice(fromIndex, 1);
reorderedColumns.splice(toIndex, 0, movedColumn);
rawColumns.value = reorderedColumns;
}
};
// 添加计算属性判断是否为 index 字段
const isIndexColumn = computed(() => {
if (!currentEditColumn.value) return false;
return currentEditColumn.value.key === 'index' || currentEditColumn.value.key.toLowerCase().includes('index');
});
// 修改编辑列的方法
const handleEditColumn = (column: TableColumn) => {
currentEditColumn.value = {
...column,
align: (column as any).align || 'left',
titleAlign: (column as any).titleAlign || 'left',
sortable: isIndexColumn.value ? false : column.sortable || false,
// 保持原有的 ellipsis 配置,如果没有则不设置
...(column.ellipsis ? { ellipsis: { ...column.ellipsis } } : {}),
// 保持原有的筛选配置
...(column.filter
? {
filter: true,
filterOptions: [...(column.filterOptions || [])]
}
: {})
};
showEditDrawer.value = true;
// 重置添加筛选选项输入框
newFilterLabel.value = '';
newFilterValue.value = '';
};
// 修改 saveColumnEdit 方法
const saveColumnEdit = () => {
if (!currentEditColumn.value) return;
const index = rawColumns.value.findIndex(col => col.key === currentEditColumn.value?.key);
if (index > -1) {
const updatedColumn = {
...rawColumns.value[index],
title: currentEditColumn.value.title,
align: currentEditColumn.value.align,
titleAlign: currentEditColumn.value.titleAlign,
sortable: currentEditColumn.value.sortable
};
// 只在存在 ellipsis 时添加该属性
if (currentEditColumn.value.ellipsis) {
updatedColumn.ellipsis = {
tooltip: currentEditColumn.value.ellipsis.tooltip
};
} else {
// 确保删除 ellipsis 属性
delete updatedColumn.ellipsis;
}
// 保存筛选相关设置
if (currentEditColumn.value.filter) {
updatedColumn.filter = true;
updatedColumn.filterOptions = currentEditColumn.value.filterOptions || [];
// 添加筛选处理函数
updatedColumn.filterOptionValues = null; // 初始时没有选中的筛选值
} else {
// 删除筛选相关属性
delete updatedColumn.filter;
delete updatedColumn.filterOptions;
delete updatedColumn.filterOptionValues;
}
rawColumns.value[index] = updatedColumn;
}
showEditDrawer.value = false;
currentEditColumn.value = null;
};
// 添加列显示状态变化的处理方法
const handleColumnVisibleChange = (checked: boolean, column: TableColumn) => {
if (checked) {
if (!visibleColumns.value.includes(column.key)) {
visibleColumns.value = [...visibleColumns.value, column.key];
}
} else {
visibleColumns.value = visibleColumns.value.filter(key => key !== column.key);
}
};
// 添加列搜索相关的响应式变量
const columnSearchText = ref('');
// 添加列搜索的计算属性
const filteredColumns = computed(() => {
const searchText = columnSearchText.value.toLowerCase().trim();
if (!searchText) return rawColumns.value;
return rawColumns.value.filter(column => {
const title = column.title.toLowerCase();
const key = column.key.toLowerCase();
return title.includes(searchText) || key.includes(searchText);
});
});
// 添加搜索框清空方法
const clearColumnSearch = () => {
columnSearchText.value = '';
};
// 在 script setup 部分添加以下代码
const formItems = computed(() => {
const items = searchFields.value
.filter(field => field.visible)
.sort((a, b) => a.order - b.order)
.map(field => ({
label: field.label,
key: field.key,
type: field.type,
// 使用字段自身的 placeholder,如果没有则使用默认值
placeholder: field.placeholder || (field.type === 'select' ? `请选择${field.label}` : `请输入${field.label}`),
defaultValue: field.defaultValue, // 添加默认值
options: field.options
}));
return items;
});
// 修改添加搜索字段的方法
const showAddFieldDrawer = ref(false);
const selectedColumns = ref<string[]>([]);
const confirmAddFields = () => {
const newFields = selectedColumns.value.map(key => {
const column = rawColumns.value.find(col => col.key === key);
const isDateField = column?.key.toLowerCase().includes('time') || column?.key.toLowerCase().includes('date');
return {
label: column?.title || '',
key: column?.key || '',
type: isDateField ? 'date' : 'input',
visible: true,
defaultValue: '',
placeholder: isDateField ? `请选择${column?.title || ''}` : `请输入${column?.title || ''}`,
order: searchFields.value.length + 1,
options: []
};
});
searchFields.value = [...searchFields.value, ...newFields];
showAddFieldDrawer.value = false;
};
// 添加搜索字段编辑抽屉
const showSearchFieldDrawer = ref(false);
const currentSearchField = ref<SearchField>({
label: '',
key: '',
type: 'input',
visible: true,
defaultValue: '',
placeholder: '请输入占位提示',
options: [],
order: 0
});
// 修改 saveSearchField 方法
const saveSearchField = () => {
const index = searchFields.value.findIndex(field => field.key === currentSearchField.value.key);
if (index > -1) {
const oldType = searchFields.value[index].type;
const newType = currentSearchField.value.type;
searchFields.value[index] = {
...searchFields.value[index],
...currentSearchField.value,
placeholder: currentSearchField.value.placeholder,
options: currentSearchField.value.type === 'select' ? currentSearchField.value.options : []
};
// 如果类型改变为 select,重置对应的表单值为 undefined
if (oldType !== 'select' && newType === 'select') {
searchForm.value[currentSearchField.value.key] = undefined;
}
}
showSearchFieldDrawer.value = false;
};
// 添加搜索字段编辑方法
const addSearchField = () => {
selectedColumns.value = [];
showAddFieldDrawer.value = true;
};
// 编辑搜索字段
const editSearchField = (field: any) => {
currentSearchField.value = {
...field,
options: field.options || [] // 确保 options 属性存在
};
showSearchFieldDrawer.value = true;
};
// 移除搜索字段
const removeSearchField = (field: any) => {
searchFields.value = searchFields.value.filter(f => f.key !== field.key);
};
// 更新字段可见性
const updateFieldVisibility = (field: any, visible: boolean) => {
field.visible = visible;
};
// 获取可选择的列
const availableColumns = computed(() => {
const existingKeys = searchFields.value.map(f => f.key);
return rawColumns.value.filter(col => !existingKeys.includes(col.key));
});
// 在 script setup 部分添加以下响应式变量
const newOptionKey = ref('');
const newOptionValue = ref('');
// 添加一个方法来处理添加新选项
const addOption = () => {
if (!currentSearchField.value.options) {
currentSearchField.value.options = [];
}
if (newOptionKey.value && newOptionValue.value) {
currentSearchField.value.options.push({
label: newOptionKey.value,
value: newOptionValue.value
});
// 清空输入
newOptionKey.value = '';
newOptionValue.value = '';
}
};
// 添加删除选项的方法
const removeOption = (index: number) => {
if (currentSearchField.value.options) {
currentSearchField.value.options.splice(index, 1);
}
};
// 添加拖拽排序相关方法
const handleSearchFieldDragStart = (e: DragEvent, field: any) => {
e.dataTransfer?.setData('text/plain', field.key);
};
const handleSearchFieldDragOver = (e: DragEvent) => {
e.preventDefault();
};
const handleSearchFieldDrop = (e: DragEvent, targetField: any) => {
e.preventDefault();
const sourceKey = e.dataTransfer?.getData('text/plain');
if (!sourceKey) return;
const sourceField = searchFields.value.find(f => f.key === sourceKey);
const targetOrder = targetField.order;
if (sourceField) {
// 交换order值
const sourceOrder = sourceField.order;
sourceField.order = targetOrder;
targetField.order = sourceOrder;
// 重新排序数组
searchFields.value.sort((a, b) => a.order - b.order);
}
};
// 添加筛选选项
const addFilterOption = () => {
if (!currentEditColumn.value) return;
if (!currentEditColumn.value.filterOptions) {
currentEditColumn.value.filterOptions = [];
}
// 检查是否已存在相同的值,避免重复
const isDuplicate = currentEditColumn.value.filterOptions.some(option => option.value === newFilterValue.value);
if (!isDuplicate) {
currentEditColumn.value.filterOptions.push({
label: newFilterLabel.value,
value: newFilterValue.value
});
// 清空输入
newFilterLabel.value = '';
newFilterValue.value = '';
} else {
// 可以在这里添加重复选项的提示
// alert('该筛选值已存在,请勿重复添加');
}
};
// 移除筛选选项
const removeFilterOption = (index: number) => {
if (currentEditColumn.value?.filterOptions) {
currentEditColumn.value.filterOptions.splice(index, 1);
}
};
// 从当前数据生成选项
const generateFilterOptions = (column: TableColumn) => {
if (!column || !tableData.value.length) return;
// 获取当前列的所有唯一值
const uniqueValues = new Set<string>();
const key = column.key;
tableData.value.forEach(row => {
let value;
// 处理嵌套属性 (如 'user.name')
if (key.includes('.')) {
const [parentKey, childKey] = key.split('.');
value = row[parentKey]?.[childKey];
} else {
value = row[key];
}
// 值存在时添加到集合中
if (value !== undefined && value !== null) {
uniqueValues.add(String(value));
}
});
// 将唯一值转换为筛选选项
const options = Array.from(uniqueValues).map(value => ({
label: value,
value
}));
// 更新当前编辑列的筛选选项
if (currentEditColumn.value) {
currentEditColumn.value.filterOptions = options;
}
// 提供用户反馈
if (options.length > 0) {
// alert(`成功生成 ${options.length} 个筛选选项!`);
} else {
// alert('未能从当前数据中找到任何值,请检查数据是否已加载。');
}
};
// 数据转换相关的状态
const selectedField = ref('');
const availableFields = computed(() => {
return rawColumns.value.map((column: TableColumn) => ({
label: column.title,
value: column.key
}));
});
const tagMappings = ref<TagMapping[]>([{ value: '', label: '', color: '' }]);
const transformRules = ref<TransformRule[]>([]);
const transformedData = ref<any[]>([]);
// 添加转换配置存储
const transformConfigurations = ref<Record<string, any>>({});
// 添加获取标签颜色的辅助函数
const getTagColor = (color: string) => {
const colorMap: Record<string, string> = {
default: '#909399',
primary: '#409EFF',
success: '#67C23A',
warning: '#E6A23C',
error: '#F56C6C',
info: '#909399'
};
return colorMap[color] || colorMap.default;
};
// 添加更新列渲染函数的方法
const updateTransformedColumnRenders = () => {
// 遍历所有列,为有转换配置的列添加自定义渲染函数
rawColumns.value.forEach(column => {
const config = transformConfigurations.value[column.key];
if (config && config.type === 'tag') {
// 备份原有的render函数(如果有)
if (!column.__originalRender && column.render) {
column.__originalRender = column.render;
}
// 添加新的render函数,应用标签转换
column.render = (row, index) => {
let value;
// 处理嵌套字段
if (column.key.includes('.')) {
const [parentKey, childKey] = column.key.split('.');
value = row[parentKey]?.[childKey];
} else {
value = row[column.key];
}
if (value !== undefined && value !== null) {
const stringValue = String(value);
const mapping = config.mappings.find((m: any) => String(m.value) === stringValue);
if (mapping) {
// 返回标签组件
return h(
'span',
{
class: `tag tag-${mapping.color || 'default'}`,
style: {
padding: '2px 6px',
borderRadius: '4px',
backgroundColor: getTagColor(mapping.color),
color: '#fff',
fontSize: '12px'
}
},
mapping.label
);
}
}
// 如果没有匹配的映射或值不存在,使用原始渲染或值
if (column.__originalRender) {
return column.__originalRender(row, index);
}
return value;
};
} else if (!config && column.__originalRender) {
// 如果列没有转换配置但有原始渲染函数,恢复原始渲染
column.render = column.__originalRender;
column.__originalRender = undefined;
}
});
};
const applyTransformations = () => {
if (!tableData.value || tableData.value.length === 0) {
window.$message?.error('没有可转换的数据');
return;
}
try {
// 不修改原始数据,只提供渲染函数
// console.log('应用数据转换配置');
// console.log('转换配置:', transformConfigurations.value);
// 刷新表格视图
updateTransformedColumnRenders();
// window.$message?.success('数据转换成功应用到显示');
} catch (error: any) {
// console.error('转换过程中出错:', error);
window.$message?.error(`转换过程中出错: ${error.message}`);
}
};
// 数据转换相关的方法
const addTagMapping = () => {
tagMappings.value.push({ value: '', label: '', color: '' });
};
const removeTagMapping = (index: number) => {
tagMappings.value.splice(index, 1);
};
const addTransformRule = () => {
if (!selectedField.value) return;
// 过滤掉空值映射
const validMappings = tagMappings.value.filter(mapping => mapping.value !== '' && mapping.label !== '');
if (validMappings.length === 0) {
window.$message?.error('请添加至少一个有效的标记映射');
return;
}
// 将转换规则添加到规则列表
transformRules.value.push({
field: selectedField.value,
fieldLabel: availableFields.value.find(f => f.value === selectedField.value)?.label || selectedField.value,
type: 'tag',
typeLabel: '转换标记',
params: { mappings: validMappings }
});
// 同时更新转换配置存储
transformConfigurations.value[selectedField.value] = {
type: 'tag',
mappings: validMappings
};
// 重置输入
selectedField.value = '';
tagMappings.value = [{ value: '', label: '', color: '' }];
};
const removeTransformRule = (index: number) => {
// 获取要删除的规则
const removedRule = transformRules.value[index];
// 从规则列表中删除
transformRules.value.splice(index, 1);
// 从转换配置中删除
if (removedRule && removedRule.field) {
transformConfigurations.value[removedRule.field] = undefined;
}
// 刷新表格以移除转换效果
applyTransformations();
};
// 添加恢复原始数据的方法
const restoreOriginalData = () => {
// 恢复所有列的原始渲染函数
rawColumns.value.forEach(column => {
if ((column as any).__originalRender) {
column.render = (column as any).__originalRender;
(column as any).__originalRender = undefined;
}
});
// 清空转换配置
transformConfigurations.value = {};
transformRules.value = [];
window.$message?.success('已恢复原始数据显示');
};
// 添加一个标志来追踪数据是否已经准备好
const isDataReady = ref(false);
// 监听数据变化
watch([tableData, rawColumns], ([newTableData, newRawColumns]) => {
if (newTableData.length > 0 && newRawColumns.length > 0) {
// console.log('数据已准备好');
isDataReady.value = true;
}
});
// 监听数据准备状态 - 不再自动加载配置
watch(isDataReady, newValue => {
if (newValue) {
// console.log('数据已准备就绪');
// 移除这里的配置加载,避免重复加载
// loadTableSettings();
}
});
onMounted(() => {
// console.log(props.uiConfig);
console.log('🔧 检查全局工具库:', {
axios: Boolean((window as any).$axios),
echarts: Boolean((window as any).$echarts),
WangEditor: Boolean((window as any).$WangEditor)
});
// 在组件挂载时就确保配置加载完成
fetchData();
Promise.resolve()
.then(() => fetchData())
.then(() => {
nextTick(() => {
// console.log('组件挂载完成,初始化配置');
// getAllTableSettings();
});
});
});
onActivated(() => {
// console.log('组件被激活');
fetchData();
});
// 提供共享状态和方法给子组件
provide('tableState', {
tableData,
loading,
columns,
pagination,
rawColumns,
visibleColumns,
tableSize,
showBorder,
showStripe,
tableHeight,
tableScrollWidth,
searchForm,
searchFields,
formItems,
isExpanded,
enableSingleSelect,
filteredColumns,
showSettingDrawer,
showEditDrawer,
showSearchFieldDrawer,
showAddFieldDrawer,
currentEditColumn,
currentSearchField,
activeTab,
columnSearchText,
selectedColumns,
newFilterLabel,
newFilterValue,
newOptionKey,
newOptionValue,
isIndexColumn,
availableColumns,
uiConfig: props.uiConfig,
tagMappings,
transformRules,
transformedData,
selectedField,
availableFields,
transformConfigurations
});
// 提供方法
provide('tableMethods', {
fetchData,
handleSearch,
resetSearch,
handlePageChange,
handlePageSizeChange,
handleSorterChange,
handleFiltersChange,
handleEditColumn,
saveColumnEdit,
handleColumnVisibleChange,
clearColumnSearch,
handleDragStart,
handleDragOver,
handleDrop,
addSearchField,
editSearchField,
removeSearchField,
updateFieldVisibility,
confirmAddFields,
saveSearchField,
addOption,
removeOption,
handleSearchFieldDragStart,
handleSearchFieldDragOver,
handleSearchFieldDrop,
addFilterOption,
removeFilterOption,
generateFilterOptions,
formatDate,
addTagMapping,
removeTagMapping,
addTransformRule,
removeTransformRule,
applyTransformations,
restoreOriginalData,
updateTransformedColumnRenders,
handleCreate,
handleDeleteRefresh
});
</script>
<template>
<div class="table-container">
<!-- 搜索表单组件 -->
<SearchForm></SearchForm>
<!-- 数据表格组件 -->
<DataTable></DataTable>
<!-- 表格设置抽屉组件 -->
<TableSettingsDrawer ref="tableSettingsDrawerRef"></TableSettingsDrawer>
<!-- 列编辑抽屉 -->
<ColumnEditDrawer></ColumnEditDrawer>
<!-- 搜索字段编辑抽屉 -->
<SearchFieldEditDrawer></SearchFieldEditDrawer>
<!-- 添加字段抽屉 -->
<AddFieldDrawer></AddFieldDrawer>
<!-- 新建弹窗 -->
<CreateDialog ref="createDialogRef"></CreateDialog>
</div>
</template>
<style scoped>
.table-container {
width: 100%;
position: relative;
}
</style>
{
"Offset": 0,
"Total": 10,
"Results": [
{
"OwnerName": "222",
"Name": "333",
"SerialNumber": "1111",
"Type": "Pos",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "EF1D786D-06F7-43F5-A685-8E13AE0F33BF",
"CreateTime": "2025-05-07T14:30:22.0000000+08:00",
"UpdateTime": "2025-05-13T16:59:38.0000000+08:00",
"Status": 0
},
{
"OwnerName": "xxx",
"Name": "xxx",
"SerialNumber": "xxxx",
"Type": "Cash",
"CreditLine": 0,
"Amount": 0,
"Currency": "USD",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "66B88D4E-A1B9-4E3C-922B-DD5211BA3FB5",
"CreateTime": "2025-05-07T14:26:34.0000000+08:00",
"UpdateTime": "2025-05-13T16:57:05.0000000+08:00",
"Status": 0
},
{
"OwnerName": "1111",
"Name": "222",
"SerialNumber": "333",
"Type": "Pos",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "97F8DA3B-06AC-4C50-ABEB-430F58B3DD64",
"CreateTime": "2025-05-07T14:30:08.0000000+08:00",
"UpdateTime": "2025-05-07T14:30:08.0000000+08:00",
"Status": 0
},
{
"OwnerName": "11",
"Name": "222",
"SerialNumber": "3",
"Type": "Cash",
"CreditLine": 0,
"Amount": 0,
"Currency": "USD",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "CC255F3E-9F80-4002-B68C-53C5FF86AE52",
"CreateTime": "2025-05-07T14:29:49.0000000+08:00",
"UpdateTime": "2025-05-07T14:29:49.0000000+08:00",
"Status": 0
},
{
"OwnerName": "22",
"Name": "222",
"SerialNumber": "33",
"Type": "AliPay",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "99C366A3-5867-43D5-B22D-B71E970D9A3C",
"CreateTime": "2025-05-07T14:29:33.0000000+08:00",
"UpdateTime": "2025-05-07T14:29:33.0000000+08:00",
"Status": 0
},
{
"OwnerName": "xxx",
"Name": "111",
"SerialNumber": "2222",
"Type": "Bank",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "F2475ECC-55BD-49FE-BC00-8822DC846739",
"CreateTime": "2025-05-07T14:29:00.0000000+08:00",
"UpdateTime": "2025-05-07T14:29:00.0000000+08:00",
"Status": 0
},
{
"OwnerName": "7275275",
"Name": "4274272",
"SerialNumber": "41042277",
"Type": "Cash",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "0C38277C-7BA6-4E9D-BEF5-D43DB49B5EA1",
"CreateTime": "2025-03-27T14:47:54.0000000+08:00",
"UpdateTime": "2025-03-27T14:47:54.0000000+08:00",
"Status": 0
},
{
"OwnerName": "12312322",
"Name": "32131",
"SerialNumber": "2231123",
"Type": "Pos",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "2A42BDFE-CDED-4300-ADF3-020D1B3E0138",
"CreateTime": "2025-03-27T14:33:13.0000000+08:00",
"UpdateTime": "2025-03-27T14:33:13.0000000+08:00",
"Status": 0
},
{
"OwnerName": "123123",
"Name": "321321",
"SerialNumber": "123132312312",
"Type": "Cash",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "095872A0-513C-4834-A0C5-BA173F5189A4",
"CreateTime": "2025-03-27T14:29:36.0000000+08:00",
"UpdateTime": "2025-03-27T14:29:36.0000000+08:00",
"Status": 0
},
{
"OwnerName": "12",
"Name": "1212",
"SerialNumber": "12121211",
"Type": "Cash",
"CreditLine": 0,
"Amount": 0,
"Currency": "CNY",
"Summary": "",
"Remark": "",
"CreatorKvid": "7F5918CC-E15F-4D7A-B636-1B40A905B744",
"CreatorName": "高源",
"OrganizationKvid": "CC7B3DF9-1B95-46DF-83C5-150587E29612",
"Metadata": {},
"Kvid": "40D1968B-70A1-49D5-89FD-39943A080D0A",
"CreateTime": "2025-03-27T14:17:12.0000000+08:00",
"UpdateTime": "2025-03-27T14:17:12.0000000+08:00",
"Status": 0
}
]
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment