@skyfox2000/webui 1.0.14 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/lib/assets/modules/file-upload-CBUcsUnR.js +170 -0
  2. package/lib/assets/modules/form-validate-CgX7aR7T.js +297 -0
  3. package/lib/assets/modules/index-Civhd8xG.js +112 -0
  4. package/lib/assets/modules/index-DQMdt51R.js +726 -0
  5. package/lib/assets/modules/{index-BEWJ_qAH.js → index-DmWrkTXX.js} +1 -1
  6. package/lib/assets/modules/{menuTabs-BXdbFZor.js → menuTabs-BRYvFWA-.js} +131 -121
  7. package/lib/assets/modules/settingInfo-BZakNKIN.js +999 -0
  8. package/lib/assets/modules/uploadList-B7XoxGOh.js +278 -0
  9. package/lib/components/common/icon/index.vue.d.ts +1 -1
  10. package/lib/components/content/dialog/index.vue.d.ts +1 -1
  11. package/lib/components/content/drawer/index.vue.d.ts +1 -1
  12. package/lib/components/content/form/index.vue.d.ts +1 -1
  13. package/lib/components/content/search/index.vue.d.ts +1 -1
  14. package/lib/components/content/table/index.vue.d.ts +1 -1
  15. package/lib/components/content/table/tableOperate.vue.d.ts +1 -1
  16. package/lib/components/content/toolbar/icontool.vue.d.ts +1 -1
  17. package/lib/components/content/toolbar/index.vue.d.ts +1 -1
  18. package/lib/components/content/tree/index.vue.d.ts +1 -1
  19. package/lib/components/form/transfer/transferTable.vue.d.ts +1 -1
  20. package/lib/components/form/treeSelect/index.vue.d.ts +1 -1
  21. package/lib/components/form/upload/uploadList.vue.d.ts +1 -1
  22. package/lib/const/options.d.ts +32 -0
  23. package/lib/directives/enter-submit.d.ts +4 -0
  24. package/lib/directives/index.d.ts +2 -0
  25. package/lib/directives/permission.d.ts +5 -0
  26. package/lib/es/AceEditor/index.js +9 -8
  27. package/lib/es/BasicLayout/index.js +28 -24
  28. package/lib/es/Error403/index.js +15 -10
  29. package/lib/es/Error404/index.js +15 -10
  30. package/lib/es/ExcelForm/index.js +380 -175
  31. package/lib/es/UploadForm/index.js +23 -20
  32. package/lib/index.d.ts +42 -2
  33. package/lib/router/index.d.ts +16 -0
  34. package/lib/stores/appInfo.d.ts +34 -0
  35. package/lib/stores/hostInfo.d.ts +9 -0
  36. package/lib/stores/pageInfo.d.ts +18 -0
  37. package/lib/stores/pinia.d.ts +3 -0
  38. package/lib/stores/settingInfo.d.ts +8 -0
  39. package/lib/stores/userInfo.d.ts +21 -0
  40. package/lib/typings/data.d.ts +80 -0
  41. package/lib/typings/form.d.ts +171 -0
  42. package/lib/typings/menu.d.ts +7 -0
  43. package/lib/typings/option.d.ts +175 -0
  44. package/lib/typings/page.d.ts +69 -0
  45. package/lib/typings/table.d.ts +181 -0
  46. package/lib/typings/tools.d.ts +130 -0
  47. package/lib/typings/tree.d.ts +72 -0
  48. package/lib/typings/upload.d.ts +161 -0
  49. package/lib/typings/urls.d.ts +69 -0
  50. package/lib/utils/cache.d.ts +23 -0
  51. package/lib/utils/data.d.ts +6 -0
  52. package/lib/utils/download.d.ts +4 -0
  53. package/lib/utils/eventbus.d.ts +16 -0
  54. package/lib/utils/export-table.d.ts +12 -0
  55. package/lib/utils/file-upload.d.ts +15 -0
  56. package/lib/utils/form-excel.d.ts +30 -0
  57. package/lib/utils/form-validate.d.ts +29 -0
  58. package/lib/utils/form.d.ts +9 -0
  59. package/lib/utils/icon-loader.d.ts +125 -0
  60. package/lib/utils/isEmpty.d.ts +1 -0
  61. package/lib/utils/main-openapis.d.ts +9 -0
  62. package/lib/utils/menu.d.ts +6 -0
  63. package/lib/utils/options.d.ts +10 -0
  64. package/lib/utils/page.d.ts +25 -0
  65. package/lib/utils/table.d.ts +21 -0
  66. package/lib/utils/tools.d.ts +18 -0
  67. package/lib/utils/tree.d.ts +3 -0
  68. package/lib/vite-env.d.ts +8 -0
  69. package/lib/webui.css +1 -1
  70. package/lib/webui.es.js +890 -724
  71. package/package.json +7 -6
  72. package/src/components/common/icon/appicon.vue +1 -1
  73. package/src/components/common/icon/fullscreen.vue +2 -1
  74. package/src/components/common/icon/index.vue +1 -1
  75. package/src/components/common/icon/layoutIcon.vue +1 -1
  76. package/src/components/common/icon/projectIcon.vue +1 -1
  77. package/src/components/common/icon/toolIcon.vue +1 -1
  78. package/src/components/content/dialog/excelForm.vue +2 -2
  79. package/src/components/content/dialog/index.vue +1 -1
  80. package/src/components/content/dialog/uploadForm.vue +7 -6
  81. package/src/components/content/drawer/index.vue +43 -18
  82. package/src/components/content/form/formItem.vue +1 -1
  83. package/src/components/content/form/index.vue +1 -1
  84. package/src/components/content/search/index.vue +1 -1
  85. package/src/components/content/search/searchItem.vue +1 -1
  86. package/src/components/content/table/index.vue +3 -3
  87. package/src/components/content/table/tableOperate.vue +2 -2
  88. package/src/components/content/toolbar/icontool.vue +2 -2
  89. package/src/components/content/toolbar/index.vue +3 -2
  90. package/src/components/content/tree/index.vue +1 -1
  91. package/src/components/error/error403.vue +2 -2
  92. package/src/components/error/error404.vue +2 -2
  93. package/src/components/form/autoComplete/index.vue +1 -1
  94. package/src/components/form/cascader/index.vue +1 -2
  95. package/src/components/form/checkbox/index.vue +11 -5
  96. package/src/components/form/datePicker/index.vue +1 -1
  97. package/src/components/form/input/index.vue +1 -1
  98. package/src/components/form/input/inputNumber.vue +1 -1
  99. package/src/components/form/input/inputPassword.vue +1 -1
  100. package/src/components/form/radio/index.vue +1 -1
  101. package/src/components/form/radio/radioStatus.vue +1 -1
  102. package/src/components/form/rangePicker/index.vue +1 -1
  103. package/src/components/form/select/index.vue +1 -1
  104. package/src/components/form/switch/index.vue +7 -3
  105. package/src/components/form/textarea/index.vue +1 -1
  106. package/src/components/form/transfer/index.vue +1 -1
  107. package/src/components/form/transfer/transferTable.vue +42 -22
  108. package/src/components/form/treeSelect/index.vue +2 -3
  109. package/src/components/form/upload/uploadList.vue +1 -1
  110. package/src/components/layout/breadcrumb/index.vue +1 -1
  111. package/src/components/layout/header/headerExits.vue +1 -1
  112. package/src/components/layout/header/index.vue +1 -1
  113. package/src/components/layout/header/user.vue +2 -1
  114. package/src/components/layout/menu/index.vue +9 -3
  115. package/src/components/layout/menu/menuTabs.vue +10 -12
  116. package/src/components/layout/page/basicLayout.vue +1 -1
  117. package/src/const/options.ts +114 -0
  118. package/src/directives/enter-submit.ts +13 -0
  119. package/src/directives/index.ts +26 -0
  120. package/src/directives/permission.ts +144 -0
  121. package/src/index.ts +201 -0
  122. package/src/router/index.ts +196 -0
  123. package/src/stores/appInfo.ts +471 -0
  124. package/src/stores/hostInfo.ts +117 -0
  125. package/src/stores/pageInfo.ts +131 -0
  126. package/src/stores/pinia.ts +10 -0
  127. package/src/stores/settingInfo.ts +53 -0
  128. package/src/stores/userInfo.ts +392 -0
  129. package/src/typings/data.d.ts +81 -0
  130. package/src/typings/form.d.ts +172 -0
  131. package/src/typings/menu.d.ts +7 -0
  132. package/src/typings/option.d.ts +177 -0
  133. package/src/typings/page.d.ts +70 -0
  134. package/src/typings/table.d.ts +182 -0
  135. package/src/typings/tools.d.ts +131 -0
  136. package/src/typings/tree.d.ts +73 -0
  137. package/src/typings/upload.d.ts +162 -0
  138. package/src/typings/urls.d.ts +70 -0
  139. package/src/utils/cache.ts +175 -0
  140. package/src/utils/data.ts +189 -0
  141. package/src/utils/download.ts +80 -0
  142. package/src/utils/eventbus.ts +78 -0
  143. package/src/utils/export-table.ts +155 -0
  144. package/src/utils/file-upload.ts +304 -0
  145. package/src/utils/form-excel.ts +523 -0
  146. package/src/utils/form-validate.ts +368 -0
  147. package/src/utils/form.ts +188 -0
  148. package/src/utils/icon-loader.ts +412 -0
  149. package/src/utils/isEmpty.ts +18 -0
  150. package/src/utils/main-openapis.ts +72 -0
  151. package/src/utils/menu.ts +89 -0
  152. package/src/utils/options.ts +324 -0
  153. package/src/utils/page.ts +262 -0
  154. package/src/utils/table.ts +274 -0
  155. package/src/utils/tools.ts +362 -0
  156. package/src/utils/tree.ts +28 -0
  157. package/tsconfig.json +1 -8
  158. package/vite.config.ts +7 -4
  159. package/lib/assets/modules/index-BahGnrAq.js +0 -415
  160. package/lib/assets/modules/index-BoKIa2sr.js +0 -109
  161. package/lib/assets/modules/index-D47Ci-T3.js +0 -107
  162. package/lib/assets/modules/uploadList-Dzlg47V0.js +0 -182
@@ -0,0 +1,189 @@
1
+ import { AnyControl, ExecuteOptions, PostOptions } from '@/typings/data.d';
2
+ import { ApiResponse, httpPost, IUrlInfo, ResStatus } from '@skyfox2000/fapi';
3
+ import message from 'vue-m-message';
4
+ import { isEmpty } from './isEmpty';
5
+
6
+ const combineUrl = (control: AnyControl, options: ExecuteOptions<any>) => {
7
+ const pageCtrl = control.page;
8
+ const url: IUrlInfo | undefined = {
9
+ url: '',
10
+ ...pageCtrl.urls?.[options.urlKey],
11
+ ...control.url,
12
+ ...options.url,
13
+ loadingText: options.loadingText,
14
+ };
15
+ return url;
16
+ };
17
+
18
+ /**
19
+ * 合并调用方法
20
+ */
21
+ const doPost = <T>(control: AnyControl, options: PostOptions<T>): Promise<ApiResponse<T> | null> => {
22
+ // 获取页面控制器和URL
23
+ const pageCtrl = control.page;
24
+ const url = options.url;
25
+ // 验证URL
26
+ if (!url || !url.url) {
27
+ const errorMsg = `URL未设置: ${options.urlKey}`;
28
+ console.warn(errorMsg);
29
+ return Promise.reject(new Error(errorMsg));
30
+ }
31
+
32
+ // 配置URL参数
33
+ if (!url.api) url.api = pageCtrl.api;
34
+ if (url.authorize === undefined) url.authorize = pageCtrl.authorize;
35
+
36
+ // 合并请求参数
37
+ let newParams = JSON.parse(JSON.stringify(options.params));
38
+
39
+ // 处理特殊参数
40
+ if (options.processParams) {
41
+ newParams = options.processParams(newParams);
42
+ }
43
+
44
+ // 标记为加载中
45
+ if (options.loadingState) {
46
+ options.loadingState.value = true;
47
+ }
48
+ if (options.hideErrorToast) {
49
+ url.hideErrorToast = true;
50
+ }
51
+ // 发起请求
52
+ return httpPost<T>(url, newParams)
53
+ .then((result: ApiResponse<T> | null) => {
54
+ // 更新加载状态
55
+ if (options.loadingState) {
56
+ options.loadingState.value = false;
57
+ }
58
+
59
+ return result;
60
+ })
61
+ .catch((_) => {
62
+ // 更新加载状态
63
+ if (options.loadingState) {
64
+ options.loadingState.value = false;
65
+ }
66
+
67
+ return null;
68
+ });
69
+ };
70
+
71
+ /**
72
+ * 通用数据查询方法
73
+ * @param control 控制对象 (TreeControl, GridControl等)
74
+ * @param options 查询选项
75
+ * @returns Promise<ApiResponse<T> | null>
76
+ */
77
+ export const doQuery = <T>(control: AnyControl, options: PostOptions<T>): Promise<ApiResponse<T> | null> => {
78
+ // 设置默认参数
79
+ if (!options.params) options.params = {};
80
+ if (!options.params!.Query) options.params!.Query = {};
81
+
82
+ const url = combineUrl(control, options);
83
+ options.url = url;
84
+
85
+ return doPost<T>(control, options);
86
+ };
87
+
88
+ /**
89
+ * 通用数据执行方法(自定义执行方法)
90
+ * @param control 控制对象
91
+ * @param options 执行选项
92
+ * @returns Promise<ApiResponse<T> | null>
93
+ */
94
+ export const doExecute = <T>(control: AnyControl, options: ExecuteOptions<T>): Promise<ApiResponse<T> | null> => {
95
+ if (!options.params) options.params = {};
96
+ if (!options.params!.Query) options.params!.Query = {};
97
+
98
+ options.loadingText = options.loadingText === false ? false : options.loadingText || '开始执行...';
99
+ options.url = combineUrl(control, options);
100
+
101
+ return doPost<T>(control, options).then((result) => {
102
+ if (result?.status === ResStatus.SUCCESS) {
103
+ if (!options.hideErrorToast) message.success('执行成功!');
104
+ }
105
+ return result as ApiResponse<T> | null;
106
+ });
107
+ };
108
+
109
+ /**
110
+ * 保存数据的简便方法(新增、更新、保存等)
111
+ * @param control 控制对象
112
+ * @param options 额外的执行选项
113
+ * @returns Promise<ApiResponse<T> | null>
114
+ */
115
+ export const doSave = <T>(
116
+ control: AnyControl,
117
+ options: Partial<ExecuteOptions<T>> = {},
118
+ ): Promise<ApiResponse<T> | null> => {
119
+ const primaryKey = options.primaryKey || 'Id';
120
+ if (!options.params) options.params = {};
121
+ if (!options.params!.Query) options.params!.Query = {};
122
+ if (!options.params!.Data) {
123
+ message.error('错误!无保存数据!');
124
+ return Promise.resolve(null);
125
+ }
126
+
127
+ const execOptions: ExecuteOptions<T> = {
128
+ ...(options as ExecuteOptions<T>),
129
+ urlKey: options.urlKey || 'save',
130
+ loadingText: options.loadingText === false ? false : options.loadingText || '数据保存中...',
131
+ };
132
+
133
+ // 如果有主键,添加到查询条件中
134
+ execOptions.params!.Query![primaryKey] = execOptions.params!.Data[primaryKey];
135
+
136
+ const url = combineUrl(control, execOptions);
137
+ execOptions.url = url;
138
+
139
+ return doPost(control, execOptions).then((result) => {
140
+ if (result?.status === ResStatus.SUCCESS) {
141
+ if (!options.hideErrorToast) message.success('保存成功!');
142
+ }
143
+ return result as ApiResponse<T> | null;
144
+ });
145
+ };
146
+
147
+ /**
148
+ * 删除数据方法
149
+ * @param control 控制对象
150
+ * @param record 需要删除的记录或ID数组
151
+ * @param options 额外的执行选项
152
+ * @returns Promise<ApiResponse<T> | null>
153
+ */
154
+ export const doDelete = <T>(
155
+ control: AnyControl,
156
+ record: string[] | Record<string, any>,
157
+ options: Partial<ExecuteOptions<T>> = {},
158
+ ): Promise<ApiResponse<T> | null> => {
159
+ const primaryKey = options.primaryKey || 'Id';
160
+ if (!options.params) options.params = {};
161
+ if (!options.params!.Query) options.params!.Query = {};
162
+
163
+ const execOptions: ExecuteOptions<T> = {
164
+ ...(options as ExecuteOptions<T>),
165
+ urlKey: 'delete',
166
+ loadingText: options.loadingText === false ? false : options.loadingText || '数据删除中...',
167
+ };
168
+
169
+ // 处理记录
170
+ if (Array.isArray(record)) {
171
+ execOptions.params!.Query![primaryKey] = record;
172
+ } else {
173
+ execOptions.params!.Query![primaryKey] = record[primaryKey];
174
+ }
175
+
176
+ if (isEmpty(execOptions.params!.Query) || isEmpty(execOptions.params!.Query![primaryKey])) {
177
+ message.error('错误!禁止无条件删除数据!');
178
+ return Promise.resolve(null);
179
+ }
180
+ const url = combineUrl(control, execOptions);
181
+ execOptions.url = url;
182
+
183
+ return doPost(control, execOptions).then((result) => {
184
+ if (result?.status === ResStatus.SUCCESS) {
185
+ if (!options.hideErrorToast) message.success('删除成功!');
186
+ }
187
+ return result as ApiResponse<T> | null;
188
+ });
189
+ };
@@ -0,0 +1,80 @@
1
+ import { MinioFile } from '@/typings/upload.d';
2
+ import { PageControl } from '@/typings/page.d';
3
+ import { ApiResponse, httpPost, IUrlInfo, ReqParams, ResStatus } from '@skyfox2000/fapi';
4
+ import message from 'vue-m-message';
5
+ import { combineParams } from '@skyfox2000/microbase';
6
+
7
+ /** 通用 Blob 下载方法
8
+ *
9
+ * @param blob 文件Blob对象
10
+ * @param fileName 文件名
11
+ */
12
+ export const downloadBlob = (blob: Blob, fileName: string) => {
13
+ // // IE 兼容处理
14
+ // if (window.navigator.msSaveOrOpenBlob) {
15
+ // window.navigator.msSaveBlob(blob, fileName);
16
+ // return;
17
+ // }
18
+
19
+ // 现代浏览器处理方式
20
+ const link = document.createElement('a');
21
+ const url = URL.createObjectURL(blob);
22
+
23
+ link.href = url;
24
+ link.download = fileName;
25
+ link.style.display = 'none';
26
+
27
+ document.body.appendChild(link);
28
+ link.click();
29
+
30
+ // 清理资源
31
+ URL.revokeObjectURL(url);
32
+ document.body.removeChild(link);
33
+ };
34
+
35
+ /** 从 Minio 下载文件
36
+ *
37
+ * @param url 请求地址
38
+ * @param pageCtrl 页面控制器
39
+ * @returns
40
+ */
41
+ export const donwloadFromMinio = <T>(url: IUrlInfo, params?: ReqParams, pageCtrl?: PageControl<T>) => {
42
+ try {
43
+ if (pageCtrl) {
44
+ if (!url.api) url.api = pageCtrl.api;
45
+ if (url.authorize === undefined) url.authorize = pageCtrl.authorize;
46
+ }
47
+ const newParams = combineParams(url.params, params);
48
+
49
+ return httpPost<T>(url, newParams).then((result: ApiResponse<T> | null) => {
50
+ if (result?.status === ResStatus.SUCCESS && result.data) {
51
+ const minioFile: MinioFile = result.data as unknown as MinioFile;
52
+ // 提取内容和文件名
53
+ const base64String = minioFile.Content!;
54
+ const fileName = minioFile.FileName!;
55
+
56
+ // 解码 Base64 字符串
57
+ const byteCharacters = atob(base64String);
58
+ const byteArrays = [];
59
+ for (let b = 0; b < byteCharacters.length; b++) {
60
+ byteArrays.push(byteCharacters.charCodeAt(b));
61
+ }
62
+
63
+ // 创建 Blob
64
+ const blob = new Blob([new Uint8Array(byteArrays)], {
65
+ type: minioFile.Type!,
66
+ });
67
+
68
+ // 调用下载方法
69
+ downloadBlob(blob, fileName);
70
+ } else {
71
+ message.error('下载文件失败!');
72
+ }
73
+ return undefined;
74
+ });
75
+ } catch (error) {
76
+ console.error('下载失败:', error);
77
+ // throw new Error('文件导出失败,请稍后重试');
78
+ message.error('文件下载失败,请稍后重试');
79
+ }
80
+ };
@@ -0,0 +1,78 @@
1
+ /**
2
+ * 设置事件前缀,防止滥用事件总线
3
+ */
4
+ export enum EventPrefix {
5
+ SelectReload = 'Select.Reload',
6
+ RadioReload = 'Radio.Reload',
7
+ CheckboxReload = 'Checkbox.Reload',
8
+ TreeSelectReload = 'TreeSelect.Reload',
9
+ TreeCheckReload = 'TreeCheck.Reload',
10
+ // 可以根据需要添加更多前缀
11
+ }
12
+
13
+ class EventBus {
14
+ private listeners: Record<string, ((...args: any[]) => void)[]> = {};
15
+
16
+ // 检查事件名是否符合前缀
17
+ private isValidPrefix(event: string): boolean {
18
+ return Object.values(EventPrefix).some((prefix) => event.startsWith(prefix + '.'));
19
+ }
20
+
21
+ /**
22
+ * 事件订阅
23
+ * @param event 事件名
24
+ * @param listener 处理器
25
+ */
26
+ on(event: string, listener: (...args: any[]) => void) {
27
+ if (!this.isValidPrefix(event)) {
28
+ console.error(`事件前缀不正确: ${event}`);
29
+ return;
30
+ }
31
+
32
+ if (!this.listeners[event]) {
33
+ this.listeners[event] = [];
34
+ }
35
+ this.listeners[event].push(listener);
36
+ }
37
+
38
+ /**
39
+ * 取消订阅事件
40
+ * @param event 事件名
41
+ * @param listener 处理器
42
+ */
43
+ off(event: string, listener: (...args: any[]) => void) {
44
+ if (!this.isValidPrefix(event)) {
45
+ console.error(`事件前缀不正确: ${event}`);
46
+ return;
47
+ }
48
+
49
+ if (!this.listeners[event]) return;
50
+ this.listeners[event] = this.listeners[event].filter((l) => l !== listener);
51
+ }
52
+
53
+ //
54
+ /**
55
+ * 触发事件
56
+ * @param event 事件名
57
+ * @param args 参数
58
+ */
59
+ emit(event: string, ...args: any[]) {
60
+ if (!this.isValidPrefix(event)) {
61
+ console.error(`事件前缀不正确: ${event}`);
62
+ return;
63
+ }
64
+
65
+ if (!this.listeners[event]) return;
66
+ this.listeners[event].forEach((listener) => listener(event, ...args));
67
+ }
68
+ }
69
+
70
+ /**
71
+ * 全局的 EventBus 实例
72
+ */
73
+ const eventBus = new EventBus();
74
+
75
+ /**
76
+ * 全局的 EventBus 实例
77
+ */
78
+ export default eventBus;
@@ -0,0 +1,155 @@
1
+ import { GridControl } from '@/typings/table.d';
2
+ import { ApiResponse, httpPost, IUrlInfo, ResStatus } from '@skyfox2000/fapi';
3
+ import dayjs from 'dayjs';
4
+ import message from 'vue-m-message';
5
+ import { downloadBlob } from './download';
6
+
7
+ // 下载当前选择行
8
+ // 表头相同
9
+
10
+ // 表格列类型定义 (适配 ant-design-vue)
11
+ export interface TableColumn {
12
+ title: string;
13
+ dataIndex?: string;
14
+ key?: string;
15
+ visible?: boolean; // 是否显示列
16
+ export?: boolean; // 是否导出列
17
+ customRender?: (value: any, record: Record<string, any>) => any;
18
+ }
19
+
20
+ /**
21
+ * 导出选中行数据为 CSV 文件
22
+ * @param fileName 文件名(支持 {YYYY}、{YY}、{MM}、{DD}、{HH}、{mm}、{ss} 时间格式)
23
+ * @param columns 表格列配置
24
+ * @param selectRows 选中的数据行
25
+ */
26
+ export const exportSelectedRows = async <T extends Record<string, any>>(
27
+ fileName: string,
28
+ columns: TableColumn[],
29
+ selectRows: T[],
30
+ ) => {
31
+ try {
32
+ const mod = await import('@json2csv/plainjs');
33
+ const JSON2CSVParser = mod.Parser;
34
+ // 1. 处理文件名中的日期格式
35
+ const processedFileName = formatFileName(fileName);
36
+
37
+ // 2. 过滤需要导出的列
38
+ const exportColumns = columns.filter((col) => col.visible !== false && col.export !== false);
39
+
40
+ // 3. 准备 CSV 字段配置
41
+ const fields = exportColumns.map((col) => ({
42
+ label: col.title,
43
+ value: col.dataIndex || col.key || '',
44
+ }));
45
+
46
+ // 4. 处理数据行
47
+ const processedData = selectRows.map((row) => {
48
+ const newRow: Record<string, any> = { ...row };
49
+
50
+ exportColumns.forEach((col) => {
51
+ const field = col.dataIndex || col.key;
52
+ if (!field) return;
53
+
54
+ // 应用自定义渲染
55
+ if (col.customRender) {
56
+ newRow[field] = col.customRender(row[field], row);
57
+ }
58
+ });
59
+ return newRow;
60
+ });
61
+
62
+ // 5. 生成 CSV 内容
63
+ const parser = new JSON2CSVParser({ fields });
64
+ const csvContent = parser.parse(processedData);
65
+
66
+ // 6. 创建并下载文件
67
+ const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv' });
68
+ downloadBlob(blob, processedFileName);
69
+ } catch (error) {
70
+ console.error('导出失败:', error);
71
+ // throw new Error('文件导出失败,请稍后重试');
72
+ message.error('文件导出失败,请稍后重试');
73
+ }
74
+ };
75
+
76
+ /** 格式化文件名 */
77
+ const formatFileName = (fileName: string) => {
78
+ const datePattern = /\{([^}]+)\}/g;
79
+ return fileName.replace(datePattern, (_, formatStr) => {
80
+ return dayjs().format(formatStr);
81
+ });
82
+ };
83
+ // 下载当前条件全部数据
84
+ // 表头相同
85
+
86
+ export const exportResults = async <T extends Record<string, any>>(
87
+ fileName: string,
88
+ columns: TableColumn[],
89
+ gridCtrl: GridControl<T>,
90
+ url: IUrlInfo,
91
+ ) => {
92
+ try {
93
+ const mod = await import('@json2csv/plainjs');
94
+ const JSON2CSVParser = mod.Parser;
95
+ // 1. 处理文件名中的日期格式
96
+ const processedFileName = formatFileName(fileName);
97
+
98
+ // 2. 过滤需要导出的列
99
+ const exportColumns = columns.filter((col) => col.visible !== false);
100
+
101
+ // 3. 准备 CSV 字段配置
102
+ const fields = exportColumns.map((col) => ({
103
+ label: col.title,
104
+ value: col.dataIndex || col.key || '',
105
+ }));
106
+
107
+ // 4. 获取数据
108
+ let pageCtrl = gridCtrl.page;
109
+ let newParams = gridCtrl.gridQuery!;
110
+
111
+ if (!url.api) url.api = pageCtrl.api;
112
+ if (url.authorize === undefined) url.authorize = pageCtrl.authorize;
113
+
114
+ gridCtrl.isGridLoading.value = true;
115
+ return httpPost<T>(url, newParams).then((result: ApiResponse<T> | null) => {
116
+ gridCtrl.isGridLoading.value = false;
117
+ if (result?.status === ResStatus.SUCCESS) {
118
+ if (result.data) {
119
+ // 5. 处理数据行
120
+ let results = result.data as unknown as T[];
121
+ const processedData = results.map((row) => {
122
+ const newRow: Record<string, any> = { ...row };
123
+
124
+ exportColumns.forEach((col) => {
125
+ const field = col.dataIndex || col.key;
126
+ if (!field) return;
127
+
128
+ // 应用自定义渲染
129
+ if (col.customRender) {
130
+ newRow[field] = col.customRender(row[field], row);
131
+ }
132
+ });
133
+ return newRow;
134
+ });
135
+
136
+ // 5. 生成 CSV 内容
137
+ const parser = new JSON2CSVParser({ fields });
138
+ const csvContent = parser.parse(processedData);
139
+
140
+ // 6. 创建并下载文件
141
+ const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv' });
142
+ downloadBlob(blob, processedFileName);
143
+ }
144
+ }
145
+ return undefined;
146
+ });
147
+ } catch (error) {
148
+ console.error('导出失败:', error);
149
+ // throw new Error('文件导出失败,请稍后重试');
150
+ message.error('文件导出失败,请稍后重试');
151
+ }
152
+ };
153
+
154
+ // 后端处理
155
+ // 下载文件或二进制输出文件