@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.
- package/lib/assets/modules/file-upload-CBUcsUnR.js +170 -0
- package/lib/assets/modules/form-validate-CgX7aR7T.js +297 -0
- package/lib/assets/modules/index-Civhd8xG.js +112 -0
- package/lib/assets/modules/index-DQMdt51R.js +726 -0
- package/lib/assets/modules/{index-BEWJ_qAH.js → index-DmWrkTXX.js} +1 -1
- package/lib/assets/modules/{menuTabs-BXdbFZor.js → menuTabs-BRYvFWA-.js} +131 -121
- package/lib/assets/modules/settingInfo-BZakNKIN.js +999 -0
- package/lib/assets/modules/uploadList-B7XoxGOh.js +278 -0
- package/lib/components/common/icon/index.vue.d.ts +1 -1
- package/lib/components/content/dialog/index.vue.d.ts +1 -1
- package/lib/components/content/drawer/index.vue.d.ts +1 -1
- package/lib/components/content/form/index.vue.d.ts +1 -1
- package/lib/components/content/search/index.vue.d.ts +1 -1
- package/lib/components/content/table/index.vue.d.ts +1 -1
- package/lib/components/content/table/tableOperate.vue.d.ts +1 -1
- package/lib/components/content/toolbar/icontool.vue.d.ts +1 -1
- package/lib/components/content/toolbar/index.vue.d.ts +1 -1
- package/lib/components/content/tree/index.vue.d.ts +1 -1
- package/lib/components/form/transfer/transferTable.vue.d.ts +1 -1
- package/lib/components/form/treeSelect/index.vue.d.ts +1 -1
- package/lib/components/form/upload/uploadList.vue.d.ts +1 -1
- package/lib/const/options.d.ts +32 -0
- package/lib/directives/enter-submit.d.ts +4 -0
- package/lib/directives/index.d.ts +2 -0
- package/lib/directives/permission.d.ts +5 -0
- package/lib/es/AceEditor/index.js +9 -8
- package/lib/es/BasicLayout/index.js +28 -24
- package/lib/es/Error403/index.js +15 -10
- package/lib/es/Error404/index.js +15 -10
- package/lib/es/ExcelForm/index.js +380 -175
- package/lib/es/UploadForm/index.js +23 -20
- package/lib/index.d.ts +42 -2
- package/lib/router/index.d.ts +16 -0
- package/lib/stores/appInfo.d.ts +34 -0
- package/lib/stores/hostInfo.d.ts +9 -0
- package/lib/stores/pageInfo.d.ts +18 -0
- package/lib/stores/pinia.d.ts +3 -0
- package/lib/stores/settingInfo.d.ts +8 -0
- package/lib/stores/userInfo.d.ts +21 -0
- package/lib/typings/data.d.ts +80 -0
- package/lib/typings/form.d.ts +171 -0
- package/lib/typings/menu.d.ts +7 -0
- package/lib/typings/option.d.ts +175 -0
- package/lib/typings/page.d.ts +69 -0
- package/lib/typings/table.d.ts +181 -0
- package/lib/typings/tools.d.ts +130 -0
- package/lib/typings/tree.d.ts +72 -0
- package/lib/typings/upload.d.ts +161 -0
- package/lib/typings/urls.d.ts +69 -0
- package/lib/utils/cache.d.ts +23 -0
- package/lib/utils/data.d.ts +6 -0
- package/lib/utils/download.d.ts +4 -0
- package/lib/utils/eventbus.d.ts +16 -0
- package/lib/utils/export-table.d.ts +12 -0
- package/lib/utils/file-upload.d.ts +15 -0
- package/lib/utils/form-excel.d.ts +30 -0
- package/lib/utils/form-validate.d.ts +29 -0
- package/lib/utils/form.d.ts +9 -0
- package/lib/utils/icon-loader.d.ts +125 -0
- package/lib/utils/isEmpty.d.ts +1 -0
- package/lib/utils/main-openapis.d.ts +9 -0
- package/lib/utils/menu.d.ts +6 -0
- package/lib/utils/options.d.ts +10 -0
- package/lib/utils/page.d.ts +25 -0
- package/lib/utils/table.d.ts +21 -0
- package/lib/utils/tools.d.ts +18 -0
- package/lib/utils/tree.d.ts +3 -0
- package/lib/vite-env.d.ts +8 -0
- package/lib/webui.css +1 -1
- package/lib/webui.es.js +890 -724
- package/package.json +7 -6
- package/src/components/common/icon/appicon.vue +1 -1
- package/src/components/common/icon/fullscreen.vue +2 -1
- package/src/components/common/icon/index.vue +1 -1
- package/src/components/common/icon/layoutIcon.vue +1 -1
- package/src/components/common/icon/projectIcon.vue +1 -1
- package/src/components/common/icon/toolIcon.vue +1 -1
- package/src/components/content/dialog/excelForm.vue +2 -2
- package/src/components/content/dialog/index.vue +1 -1
- package/src/components/content/dialog/uploadForm.vue +7 -6
- package/src/components/content/drawer/index.vue +43 -18
- package/src/components/content/form/formItem.vue +1 -1
- package/src/components/content/form/index.vue +1 -1
- package/src/components/content/search/index.vue +1 -1
- package/src/components/content/search/searchItem.vue +1 -1
- package/src/components/content/table/index.vue +3 -3
- package/src/components/content/table/tableOperate.vue +2 -2
- package/src/components/content/toolbar/icontool.vue +2 -2
- package/src/components/content/toolbar/index.vue +3 -2
- package/src/components/content/tree/index.vue +1 -1
- package/src/components/error/error403.vue +2 -2
- package/src/components/error/error404.vue +2 -2
- package/src/components/form/autoComplete/index.vue +1 -1
- package/src/components/form/cascader/index.vue +1 -2
- package/src/components/form/checkbox/index.vue +11 -5
- package/src/components/form/datePicker/index.vue +1 -1
- package/src/components/form/input/index.vue +1 -1
- package/src/components/form/input/inputNumber.vue +1 -1
- package/src/components/form/input/inputPassword.vue +1 -1
- package/src/components/form/radio/index.vue +1 -1
- package/src/components/form/radio/radioStatus.vue +1 -1
- package/src/components/form/rangePicker/index.vue +1 -1
- package/src/components/form/select/index.vue +1 -1
- package/src/components/form/switch/index.vue +7 -3
- package/src/components/form/textarea/index.vue +1 -1
- package/src/components/form/transfer/index.vue +1 -1
- package/src/components/form/transfer/transferTable.vue +42 -22
- package/src/components/form/treeSelect/index.vue +2 -3
- package/src/components/form/upload/uploadList.vue +1 -1
- package/src/components/layout/breadcrumb/index.vue +1 -1
- package/src/components/layout/header/headerExits.vue +1 -1
- package/src/components/layout/header/index.vue +1 -1
- package/src/components/layout/header/user.vue +2 -1
- package/src/components/layout/menu/index.vue +9 -3
- package/src/components/layout/menu/menuTabs.vue +10 -12
- package/src/components/layout/page/basicLayout.vue +1 -1
- package/src/const/options.ts +114 -0
- package/src/directives/enter-submit.ts +13 -0
- package/src/directives/index.ts +26 -0
- package/src/directives/permission.ts +144 -0
- package/src/index.ts +201 -0
- package/src/router/index.ts +196 -0
- package/src/stores/appInfo.ts +471 -0
- package/src/stores/hostInfo.ts +117 -0
- package/src/stores/pageInfo.ts +131 -0
- package/src/stores/pinia.ts +10 -0
- package/src/stores/settingInfo.ts +53 -0
- package/src/stores/userInfo.ts +392 -0
- package/src/typings/data.d.ts +81 -0
- package/src/typings/form.d.ts +172 -0
- package/src/typings/menu.d.ts +7 -0
- package/src/typings/option.d.ts +177 -0
- package/src/typings/page.d.ts +70 -0
- package/src/typings/table.d.ts +182 -0
- package/src/typings/tools.d.ts +131 -0
- package/src/typings/tree.d.ts +73 -0
- package/src/typings/upload.d.ts +162 -0
- package/src/typings/urls.d.ts +70 -0
- package/src/utils/cache.ts +175 -0
- package/src/utils/data.ts +189 -0
- package/src/utils/download.ts +80 -0
- package/src/utils/eventbus.ts +78 -0
- package/src/utils/export-table.ts +155 -0
- package/src/utils/file-upload.ts +304 -0
- package/src/utils/form-excel.ts +523 -0
- package/src/utils/form-validate.ts +368 -0
- package/src/utils/form.ts +188 -0
- package/src/utils/icon-loader.ts +412 -0
- package/src/utils/isEmpty.ts +18 -0
- package/src/utils/main-openapis.ts +72 -0
- package/src/utils/menu.ts +89 -0
- package/src/utils/options.ts +324 -0
- package/src/utils/page.ts +262 -0
- package/src/utils/table.ts +274 -0
- package/src/utils/tools.ts +362 -0
- package/src/utils/tree.ts +28 -0
- package/tsconfig.json +1 -8
- package/vite.config.ts +7 -4
- package/lib/assets/modules/index-BahGnrAq.js +0 -415
- package/lib/assets/modules/index-BoKIa2sr.js +0 -109
- package/lib/assets/modules/index-D47Ci-T3.js +0 -107
- 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
|
+
// 下载文件或二进制输出文件
|