@skyfox2000/webui 0.1.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 (213) hide show
  1. package/.eslintrc.js +23 -0
  2. package/.prettierrc +11 -0
  3. package/.vscode/settings.json +25 -0
  4. package/README.md +104 -0
  5. package/env.d.ts +11 -0
  6. package/index.html +19 -0
  7. package/lib/AceEditor.d.ts +4 -0
  8. package/lib/BasicLayout.d.ts +4 -0
  9. package/lib/Error403.d.ts +4 -0
  10. package/lib/Error404.d.ts +4 -0
  11. package/lib/ExcelForm.d.ts +4 -0
  12. package/lib/UploadForm.d.ts +4 -0
  13. package/lib/assets/modules/basicLayout-YP_-EySb.js +726 -0
  14. package/lib/assets/modules/error403-Bi0E2twj.js +33 -0
  15. package/lib/assets/modules/error404-BF7vasR_.js +33 -0
  16. package/lib/assets/modules/excelForm-Dzndz-SG.js +109 -0
  17. package/lib/assets/modules/excelForm-WJVQmaDT.js +317 -0
  18. package/lib/assets/modules/index-FzWSvscZ.js +107 -0
  19. package/lib/assets/modules/index-ekkaExvB.js +49 -0
  20. package/lib/assets/modules/uploadForm-BahGnrAq.js +415 -0
  21. package/lib/assets/modules/uploadForm-DEnOjhwc.js +308 -0
  22. package/lib/components/common/button/index.vue.d.ts +42 -0
  23. package/lib/components/common/button/index.vue.d.ts.map +1 -0
  24. package/lib/components/common/icon/appicon.vue.d.ts +12 -0
  25. package/lib/components/common/icon/appicon.vue.d.ts.map +1 -0
  26. package/lib/components/common/icon/fullscreen.vue.d.ts +4 -0
  27. package/lib/components/common/icon/fullscreen.vue.d.ts.map +1 -0
  28. package/lib/components/common/icon/helper.vue.d.ts +23 -0
  29. package/lib/components/common/icon/helper.vue.d.ts.map +1 -0
  30. package/lib/components/common/icon/index.vue.d.ts +244 -0
  31. package/lib/components/common/icon/index.vue.d.ts.map +1 -0
  32. package/lib/components/common/icon/layoutIcon.vue.d.ts +44 -0
  33. package/lib/components/common/icon/layoutIcon.vue.d.ts.map +1 -0
  34. package/lib/components/common/icon/projectIcon.vue.d.ts +60 -0
  35. package/lib/components/common/icon/projectIcon.vue.d.ts.map +1 -0
  36. package/lib/components/common/icon/toolIcon.vue.d.ts +44 -0
  37. package/lib/components/common/icon/toolIcon.vue.d.ts.map +1 -0
  38. package/lib/components/common/index.d.ts +19 -0
  39. package/lib/components/common/index.d.ts.map +1 -0
  40. package/lib/components/common/tooltip/index.vue.d.ts +22 -0
  41. package/lib/components/common/tooltip/index.vue.d.ts.map +1 -0
  42. package/lib/components/content/dialog/excelForm.vue.d.ts +31 -0
  43. package/lib/components/content/dialog/excelForm.vue.d.ts.map +1 -0
  44. package/lib/components/content/dialog/index.vue.d.ts +35 -0
  45. package/lib/components/content/dialog/index.vue.d.ts.map +1 -0
  46. package/lib/components/content/dialog/uploadForm.vue.d.ts +25 -0
  47. package/lib/components/content/dialog/uploadForm.vue.d.ts.map +1 -0
  48. package/lib/components/content/drawer/index.vue.d.ts +27 -0
  49. package/lib/components/content/drawer/index.vue.d.ts.map +1 -0
  50. package/lib/components/content/form/formItem.vue.d.ts +26 -0
  51. package/lib/components/content/form/formItem.vue.d.ts.map +1 -0
  52. package/lib/components/content/form/index.vue.d.ts +26 -0
  53. package/lib/components/content/form/index.vue.d.ts.map +1 -0
  54. package/lib/components/content/index.d.ts +27 -0
  55. package/lib/components/content/index.d.ts.map +1 -0
  56. package/lib/components/content/search/index.vue.d.ts +30 -0
  57. package/lib/components/content/search/index.vue.d.ts.map +1 -0
  58. package/lib/components/content/search/searchItem.vue.d.ts +24 -0
  59. package/lib/components/content/search/searchItem.vue.d.ts.map +1 -0
  60. package/lib/components/content/table/index.vue.d.ts +37 -0
  61. package/lib/components/content/table/index.vue.d.ts.map +1 -0
  62. package/lib/components/content/table/tableOperate.vue.d.ts +19 -0
  63. package/lib/components/content/table/tableOperate.vue.d.ts.map +1 -0
  64. package/lib/components/content/toolbar/icontool.vue.d.ts +8 -0
  65. package/lib/components/content/toolbar/icontool.vue.d.ts.map +1 -0
  66. package/lib/components/content/toolbar/index.vue.d.ts +19 -0
  67. package/lib/components/content/toolbar/index.vue.d.ts.map +1 -0
  68. package/lib/components/content/tree/index.vue.d.ts +47 -0
  69. package/lib/components/content/tree/index.vue.d.ts.map +1 -0
  70. package/lib/components/error/error403.vue.d.ts +4 -0
  71. package/lib/components/error/error403.vue.d.ts.map +1 -0
  72. package/lib/components/error/error404.vue.d.ts +4 -0
  73. package/lib/components/error/error404.vue.d.ts.map +1 -0
  74. package/lib/components/form/aceEditor/aceConfig.d.ts +9 -0
  75. package/lib/components/form/aceEditor/aceConfig.d.ts.map +1 -0
  76. package/lib/components/form/aceEditor/index.vue.d.ts +13 -0
  77. package/lib/components/form/aceEditor/index.vue.d.ts.map +1 -0
  78. package/lib/components/form/autoComplete/index.vue.d.ts +140 -0
  79. package/lib/components/form/autoComplete/index.vue.d.ts.map +1 -0
  80. package/lib/components/form/cascader/index.vue.d.ts +110 -0
  81. package/lib/components/form/cascader/index.vue.d.ts.map +1 -0
  82. package/lib/components/form/checkbox/index.vue.d.ts +129 -0
  83. package/lib/components/form/checkbox/index.vue.d.ts.map +1 -0
  84. package/lib/components/form/datePicker/index.vue.d.ts +7 -0
  85. package/lib/components/form/datePicker/index.vue.d.ts.map +1 -0
  86. package/lib/components/form/index.d.ts +41 -0
  87. package/lib/components/form/index.d.ts.map +1 -0
  88. package/lib/components/form/input/index.vue.d.ts +27 -0
  89. package/lib/components/form/input/index.vue.d.ts.map +1 -0
  90. package/lib/components/form/input/inputIcon.vue.d.ts +11 -0
  91. package/lib/components/form/input/inputIcon.vue.d.ts.map +1 -0
  92. package/lib/components/form/input/inputNumber.vue.d.ts +4 -0
  93. package/lib/components/form/input/inputNumber.vue.d.ts.map +1 -0
  94. package/lib/components/form/input/inputPassword.vue.d.ts +4 -0
  95. package/lib/components/form/input/inputPassword.vue.d.ts.map +1 -0
  96. package/lib/components/form/propEditor/index.vue.d.ts +13 -0
  97. package/lib/components/form/propEditor/index.vue.d.ts.map +1 -0
  98. package/lib/components/form/radio/index.vue.d.ts +134 -0
  99. package/lib/components/form/radio/index.vue.d.ts.map +1 -0
  100. package/lib/components/form/radio/radioStatus.vue.d.ts +32 -0
  101. package/lib/components/form/radio/radioStatus.vue.d.ts.map +1 -0
  102. package/lib/components/form/rangePicker/index.vue.d.ts +17 -0
  103. package/lib/components/form/rangePicker/index.vue.d.ts.map +1 -0
  104. package/lib/components/form/select/index.vue.d.ts +143 -0
  105. package/lib/components/form/select/index.vue.d.ts.map +1 -0
  106. package/lib/components/form/switch/index.vue.d.ts +44 -0
  107. package/lib/components/form/switch/index.vue.d.ts.map +1 -0
  108. package/lib/components/form/textarea/index.vue.d.ts +4 -0
  109. package/lib/components/form/textarea/index.vue.d.ts.map +1 -0
  110. package/lib/components/form/transfer/index.vue.d.ts +39 -0
  111. package/lib/components/form/transfer/index.vue.d.ts.map +1 -0
  112. package/lib/components/form/transfer/transferTable.vue.d.ts +39 -0
  113. package/lib/components/form/transfer/transferTable.vue.d.ts.map +1 -0
  114. package/lib/components/form/treeSelect/index.vue.d.ts +39 -0
  115. package/lib/components/form/treeSelect/index.vue.d.ts.map +1 -0
  116. package/lib/components/form/upload/uploadList.vue.d.ts +477 -0
  117. package/lib/components/form/upload/uploadList.vue.d.ts.map +1 -0
  118. package/lib/components/index.d.ts +9 -0
  119. package/lib/components/index.d.ts.map +1 -0
  120. package/lib/components/layout/breadcrumb/index.vue.d.ts +4 -0
  121. package/lib/components/layout/breadcrumb/index.vue.d.ts.map +1 -0
  122. package/lib/components/layout/content/index.vue.d.ts +23 -0
  123. package/lib/components/layout/content/index.vue.d.ts.map +1 -0
  124. package/lib/components/layout/datetime/index.vue.d.ts +4 -0
  125. package/lib/components/layout/datetime/index.vue.d.ts.map +1 -0
  126. package/lib/components/layout/header/headerExits.vue.d.ts +4 -0
  127. package/lib/components/layout/header/headerExits.vue.d.ts.map +1 -0
  128. package/lib/components/layout/header/index.vue.d.ts +4 -0
  129. package/lib/components/layout/header/index.vue.d.ts.map +1 -0
  130. package/lib/components/layout/index.d.ts +17 -0
  131. package/lib/components/layout/index.d.ts.map +1 -0
  132. package/lib/components/layout/menu/index.vue.d.ts +7 -0
  133. package/lib/components/layout/menu/index.vue.d.ts.map +1 -0
  134. package/lib/components/layout/menu/menuTabs.vue.d.ts +4 -0
  135. package/lib/components/layout/menu/menuTabs.vue.d.ts.map +1 -0
  136. package/lib/components/layout/page/basicLayout.vue.d.ts +7 -0
  137. package/lib/components/layout/page/basicLayout.vue.d.ts.map +1 -0
  138. package/lib/es/AceEditor/index.js +168 -0
  139. package/lib/es/BasicLayout/index.js +4 -0
  140. package/lib/es/Error403/index.js +4 -0
  141. package/lib/es/Error404/index.js +4 -0
  142. package/lib/es/ExcelForm/index.js +5 -0
  143. package/lib/es/UploadForm/index.js +5 -0
  144. package/lib/index.d.ts +2 -0
  145. package/lib/webui.css +1 -0
  146. package/lib/webui.es.js +3349 -0
  147. package/package.json +66 -0
  148. package/plugins/vite-plugin-auto-generate-vue.ts +105 -0
  149. package/postcss.config.ts +6 -0
  150. package/src/assets/global.css +9 -0
  151. package/src/components/common/button/index.vue +126 -0
  152. package/src/components/common/icon/appicon.vue +28 -0
  153. package/src/components/common/icon/fullscreen.vue +13 -0
  154. package/src/components/common/icon/helper.vue +30 -0
  155. package/src/components/common/icon/index.vue +426 -0
  156. package/src/components/common/icon/layoutIcon.vue +33 -0
  157. package/src/components/common/icon/projectIcon.vue +41 -0
  158. package/src/components/common/icon/toolIcon.vue +33 -0
  159. package/src/components/common/index.ts +19 -0
  160. package/src/components/common/tooltip/index.vue +25 -0
  161. package/src/components/content/dialog/excelForm.vue +479 -0
  162. package/src/components/content/dialog/index.vue +149 -0
  163. package/src/components/content/dialog/uploadForm.vue +228 -0
  164. package/src/components/content/drawer/index.vue +93 -0
  165. package/src/components/content/form/formItem.vue +76 -0
  166. package/src/components/content/form/index.vue +48 -0
  167. package/src/components/content/index.ts +32 -0
  168. package/src/components/content/search/index.vue +135 -0
  169. package/src/components/content/search/searchItem.vue +52 -0
  170. package/src/components/content/table/index.vue +215 -0
  171. package/src/components/content/table/tableOperate.vue +131 -0
  172. package/src/components/content/toolbar/icontool.vue +151 -0
  173. package/src/components/content/toolbar/index.vue +107 -0
  174. package/src/components/content/tree/index.vue +140 -0
  175. package/src/components/error/error403.vue +14 -0
  176. package/src/components/error/error404.vue +14 -0
  177. package/src/components/form/aceEditor/aceConfig.ts +90 -0
  178. package/src/components/form/aceEditor/index.vue +175 -0
  179. package/src/components/form/autoComplete/index.vue +171 -0
  180. package/src/components/form/cascader/index.vue +110 -0
  181. package/src/components/form/checkbox/index.vue +108 -0
  182. package/src/components/form/datePicker/index.vue +29 -0
  183. package/src/components/form/index.ts +54 -0
  184. package/src/components/form/input/index.vue +70 -0
  185. package/src/components/form/input/inputIcon.vue +39 -0
  186. package/src/components/form/input/inputNumber.vue +23 -0
  187. package/src/components/form/input/inputPassword.vue +22 -0
  188. package/src/components/form/propEditor/index.vue +81 -0
  189. package/src/components/form/radio/index.vue +132 -0
  190. package/src/components/form/radio/radioStatus.vue +42 -0
  191. package/src/components/form/rangePicker/index.vue +64 -0
  192. package/src/components/form/select/index.vue +186 -0
  193. package/src/components/form/switch/index.vue +58 -0
  194. package/src/components/form/textarea/index.vue +23 -0
  195. package/src/components/form/transfer/index.vue +95 -0
  196. package/src/components/form/transfer/transferTable.vue +124 -0
  197. package/src/components/form/treeSelect/index.vue +108 -0
  198. package/src/components/form/upload/uploadList.vue +235 -0
  199. package/src/components/index.ts +97 -0
  200. package/src/components/layout/breadcrumb/index.vue +38 -0
  201. package/src/components/layout/content/index.vue +28 -0
  202. package/src/components/layout/datetime/index.vue +16 -0
  203. package/src/components/layout/header/headerExits.vue +28 -0
  204. package/src/components/layout/header/index.vue +43 -0
  205. package/src/components/layout/index.ts +16 -0
  206. package/src/components/layout/menu/index.vue +64 -0
  207. package/src/components/layout/menu/menuTabs.vue +56 -0
  208. package/src/components/layout/page/basicLayout.vue +67 -0
  209. package/src/vite-env.d.ts +8 -0
  210. package/tailwind.config.ts +11 -0
  211. package/tsconfig.json +53 -0
  212. package/vite.config.ts +117 -0
  213. package//344/273/243/347/240/201/350/247/204/350/214/203/345/217/212/351/243/216/346/240/274/346/214/207/345/215/227.md +116 -0
@@ -0,0 +1,479 @@
1
+ <script setup lang="ts">
2
+ import { watch, ref, onMounted } from 'vue';
3
+ import { Button } from '../../common';
4
+ import { Modal, Space, Upload, Alert } from 'ant-design-vue';
5
+ import {
6
+ EditorControl,
7
+ GridControl,
8
+ validateExcel,
9
+ getRuleTexts,
10
+ checkExcelDuplicates,
11
+ AsyncUploader,
12
+ UploadStatus,
13
+ doSave,
14
+ ExcelFileParams,
15
+ processExcelFile,
16
+ path,
17
+ } from '@skyfox2000/webbase';
18
+ import { AnyData, ResStatus } from '@skyfox2000/fapi';
19
+ import message from 'vue-m-message';
20
+ //引入相关样式
21
+ import type { UploadProps } from 'ant-design-vue';
22
+ import VueOfficeExcel from '@vue-office/excel';
23
+ import '@vue-office/excel/lib/index.css';
24
+ import { UploadFile } from '@skyfox2000/webbase';
25
+
26
+ type AlertType = 'success' | 'info' | 'warning' | 'error';
27
+
28
+ const props = defineProps<{
29
+ /**
30
+ * 标题
31
+ */
32
+ title: String;
33
+ /**
34
+ * 表格控制器
35
+ */
36
+ gridCtrl: GridControl<AnyData>;
37
+ /**
38
+ * 表格控制器
39
+ */
40
+ excelCtrl: EditorControl<AnyData>;
41
+ /**
42
+ * 文件上传参数
43
+ */
44
+ uploadParams: ExcelFileParams;
45
+ /**
46
+ * 表格字段映射
47
+ * - 表头映射字段
48
+ */
49
+ excelFieldMap: Record<string, string>;
50
+ /**
51
+ * Excel文件信息字段
52
+ */
53
+ fileField?: string;
54
+ /**
55
+ * Excel批量数据字段
56
+ * - Excel数据列表转换后的上传字段
57
+ */
58
+ excelBatchField?: string;
59
+ /**
60
+ * 确认按钮文字,空字符串则不显示
61
+ */
62
+ saveText?: string;
63
+ }>();
64
+
65
+ const excelCtrl = props.excelCtrl;
66
+ const open = ref<boolean>(false);
67
+ const excelUrl = ref('');
68
+ const fileList = ref<any[]>([]);
69
+ const fileName = ref('');
70
+ const validating = ref(true); // 表示正在验证状态
71
+
72
+ // Alert状态变量
73
+ const validationMsg = ref('待验证数据规则');
74
+ const validationType = ref<AlertType>('warning');
75
+ const duplicateMsg = ref('待验证重复数据');
76
+ const duplicateType = ref<AlertType>('warning');
77
+
78
+ watch(
79
+ () => excelCtrl.visible.value,
80
+ () => {
81
+ open.value = excelCtrl.visible.value;
82
+ },
83
+ );
84
+ watch(
85
+ () => open.value,
86
+ () => {
87
+ excelCtrl.visible.value = open.value;
88
+ },
89
+ );
90
+
91
+ const uploadParams = ref(props.uploadParams);
92
+ const uploadUrl = ref(uploadParams.value.uploadUrl);
93
+ const duplicateRules = ref(uploadParams.value.duplicateRules);
94
+ const duplicateUrl = ref(uploadParams.value.duplicateUrl);
95
+
96
+ const dialogUpload = async () => {
97
+ const url = uploadUrl.value;
98
+ if (!url) {
99
+ message.error('未配置文件上传地址!');
100
+ return;
101
+ }
102
+
103
+ if (excelError.value) {
104
+ message.error('表格数据存在验证错误,请修正后再上传!');
105
+ return;
106
+ }
107
+
108
+ excelCtrl.isFormLoading.value = true;
109
+ try {
110
+ if (!excelBuffer.value || !fileName.value) {
111
+ message.warning('请先选择Excel文件!');
112
+ excelCtrl.isFormLoading.value = false;
113
+ return;
114
+ }
115
+
116
+ // 创建文件对象和上传器
117
+ const uploader = new AsyncUploader(url, 1);
118
+
119
+ // 创建上传文件对象
120
+ const file = new File([excelBuffer.value], fileName.value, {
121
+ type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
122
+ });
123
+
124
+ const fileKey = path.join(uploadParams.value.basePath, fileName.value);
125
+ const uploadFile: UploadFile = {
126
+ uid: '1',
127
+ name: fileName.value,
128
+ originFileObj: file,
129
+ status: UploadStatus.Uploading,
130
+ percent: 0,
131
+ params: {
132
+ FileKey: fileKey,
133
+ },
134
+ };
135
+
136
+ // 使用上传器的 uploadFile 方法上传文件
137
+ const abortController = new AbortController();
138
+ try {
139
+ await uploader.uploadFile(uploadFile, abortController.signal, (percent) => {
140
+ uploadFile.percent = percent;
141
+ });
142
+
143
+ // 上传成功后调用 dialogSave 方法
144
+ message.success('文件上传成功,开始业务处理!');
145
+ dialogSave(uploadFile);
146
+ } catch (uploadError: any) {
147
+ console.error('文件上传错误:', uploadError);
148
+ message.error(uploadError?.message || '文件上传失败,请稍后再试!');
149
+ throw uploadError; // 向外层抛出错误
150
+ }
151
+ } catch (error: any) {
152
+ console.error('上传处理错误:', error);
153
+ message.error('上传处理失败:' + (error?.message || '未知错误'));
154
+ } finally {
155
+ excelCtrl.isFormLoading.value = false;
156
+ }
157
+ };
158
+
159
+ const dialogSave = async (uploadFile: UploadFile) => {
160
+ if (excelCtrl.formData.value) {
161
+ // 使用 doSave 方法保存数据
162
+ if (props.excelBatchField) {
163
+ // 获取Excel数据,并转换成指定数据对象结构
164
+ const excelFileData = await processExcelFile(excelBuffer.value!);
165
+ if (!excelFileData) return null;
166
+ const { excelData } = excelFileData;
167
+ const excelDataList = excelData.map((row: Record<string, AnyData>) => {
168
+ const result: Record<string, AnyData> = {};
169
+ for (const key in row) {
170
+ const field = props.excelFieldMap[key];
171
+ if (field) {
172
+ result[field] = row[key];
173
+ }
174
+ }
175
+ return result;
176
+ });
177
+ excelCtrl.formData.value[props.excelBatchField] = excelDataList;
178
+ }
179
+
180
+ const fileField = props.fileField ?? 'FileInfo';
181
+ excelCtrl.formData.value[fileField] = uploadFile;
182
+
183
+ if (excelCtrl.beforeSave) {
184
+ excelCtrl.beforeSave();
185
+ }
186
+
187
+ const postData = {
188
+ Data: {
189
+ ...excelCtrl.formData.value,
190
+ },
191
+ Query: {
192
+ [props.excelCtrl.primaryKey]: excelCtrl.formData.value[props.excelCtrl.primaryKey],
193
+ },
194
+ };
195
+
196
+ excelCtrl.isFormLoading.value = true;
197
+ try {
198
+ const result = await doSave(props.excelCtrl, {
199
+ params: postData,
200
+ urlKey: 'save',
201
+ url: props.excelCtrl.saveUrl,
202
+ });
203
+
204
+ if (result?.status === ResStatus.SUCCESS) {
205
+ message.success('数据保存成功!');
206
+ if (excelCtrl.afterSave) {
207
+ excelCtrl.afterSave();
208
+ }
209
+ excelCtrl.visible.value = false;
210
+ } else {
211
+ message.error('保存失败:' + (result?.msg || '未知错误'));
212
+ }
213
+ } catch (error) {
214
+ console.error('保存错误:', error);
215
+ message.error('数据保存失败,请稍后再试!');
216
+ } finally {
217
+ excelCtrl.isFormLoading.value = false;
218
+ }
219
+ }
220
+ };
221
+
222
+ const excelError = ref(false);
223
+ const duplicateError = ref(false);
224
+ const excelBuffer = ref<ArrayBuffer | null>(null);
225
+
226
+ // 上传前处理函数
227
+ const beforeUpload: UploadProps['beforeUpload'] = async (file) => {
228
+ const isExcel =
229
+ file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
230
+ file.type === 'application/vnd.ms-excel';
231
+ if (!isExcel) {
232
+ message.error('只能上传Excel文件!');
233
+ return Upload.LIST_IGNORE;
234
+ }
235
+
236
+ try {
237
+ // 设置文件名
238
+ fileName.value = file.name;
239
+
240
+ // 清除之前的错误状态并设置为验证中
241
+ excelError.value = false;
242
+ duplicateError.value = false;
243
+ validating.value = true;
244
+ validationMsg.value = '待验证数据规则';
245
+ validationType.value = 'warning';
246
+ duplicateMsg.value = '待验证重复数据';
247
+ duplicateType.value = 'warning';
248
+
249
+ // 使用ArrayBuffer读取文件
250
+ const buffer = await file.arrayBuffer();
251
+ excelBuffer.value = buffer;
252
+
253
+ const reader = new FileReader();
254
+ reader.readAsDataURL(file);
255
+ reader.onload = async (loadEvent) => {
256
+ if (loadEvent.target) {
257
+ // 读取完成后先进行普通验证
258
+ const { hasError, errBlob } = await validateExcel(buffer, excelCtrl.formRules.value);
259
+
260
+ // 有验证错误
261
+ if (hasError) {
262
+ if (errBlob) {
263
+ excelError.value = true;
264
+ validating.value = false;
265
+ validationMsg.value = '数据验证失败';
266
+ validationType.value = 'error';
267
+ const blobUrl = URL.createObjectURL(errBlob);
268
+ excelUrl.value = blobUrl;
269
+ }
270
+ return; // 验证失败则结束
271
+ } else {
272
+ // 验证成功
273
+ validationMsg.value = '数据验证成功';
274
+ validationType.value = 'success';
275
+ }
276
+
277
+ // 无验证错误,继续验证重复数据
278
+ if (duplicateRules.value && duplicateRules.value.length > 0 && duplicateUrl.value) {
279
+ try {
280
+ // 检测重复数据
281
+ if (!duplicateUrl.value.api) duplicateUrl.value.api = props.gridCtrl.page.api;
282
+ if (duplicateUrl.value.authorize === undefined)
283
+ duplicateUrl.value.authorize = props.gridCtrl.page.authorize;
284
+
285
+ const { hasError, errBlob } = await checkExcelDuplicates(
286
+ buffer,
287
+ duplicateRules.value,
288
+ duplicateUrl.value,
289
+ );
290
+ if (hasError) {
291
+ // 有重复数据
292
+ if (errBlob) {
293
+ duplicateError.value = true;
294
+ duplicateMsg.value = '检测到重复数据';
295
+ duplicateType.value = 'error';
296
+ const blobUrl = URL.createObjectURL(errBlob);
297
+ excelUrl.value = blobUrl;
298
+ }
299
+ return;
300
+ } else {
301
+ // 无重复数据
302
+ duplicateError.value = false;
303
+ duplicateMsg.value = '数据验证通过';
304
+ duplicateType.value = 'success';
305
+ }
306
+ } catch (error) {
307
+ duplicateMsg.value = '重复检测异常';
308
+ duplicateType.value = 'error';
309
+ }
310
+ }
311
+ excelUrl.value = loadEvent.target.result as string;
312
+
313
+ // 验证完成
314
+ validating.value = false;
315
+ } else {
316
+ message.error('加载Excel文件失败,请检查文件格式!');
317
+ }
318
+ };
319
+ } catch (error) {
320
+ console.error('Excel处理错误:', error);
321
+ message.error('Excel文件处理失败,请检查文件格式!');
322
+ validating.value = false;
323
+ validationMsg.value = 'Excel处理错误';
324
+ validationType.value = 'error';
325
+ }
326
+
327
+ return false;
328
+ };
329
+
330
+ // 计算验证规则显示文本
331
+ const validationRules: { field: string; rules: string[] }[] = getRuleTexts(excelCtrl.formRules.value);
332
+
333
+ onMounted(() => {
334
+ const pageCtrl = props.gridCtrl.page;
335
+ uploadUrl.value = uploadUrl.value ?? pageCtrl.urls.upload;
336
+ if (!uploadUrl.value) {
337
+ message.error('未配置文件上传地址!');
338
+ return;
339
+ }
340
+
341
+ if (!uploadUrl.value.api) uploadUrl.value.api = pageCtrl.api;
342
+ if (uploadUrl.value.authorize === undefined) uploadUrl.value.authorize = pageCtrl.authorize;
343
+
344
+ for (const key in excelCtrl.formData.value) {
345
+ if (props.gridCtrl.rowData.value) excelCtrl.formData.value[key] = props.gridCtrl.rowData.value[key];
346
+ }
347
+
348
+ open.value = excelCtrl.visible.value;
349
+ });
350
+
351
+ const dialogClose = () => {
352
+ excelCtrl.visible.value = false;
353
+ };
354
+ const handleError = () => {
355
+ console.error('渲染失败');
356
+ };
357
+ </script>
358
+ <template>
359
+ <Modal
360
+ :title="title ?? 'Excel文件上传'"
361
+ v-model:open="open"
362
+ :wrapClassName="['modal', 'mx-auto', $attrs.width ? 'w-[' + $attrs.width + ']' : ''].join(' ')"
363
+ :width="940"
364
+ @close="dialogClose"
365
+ >
366
+ <slot></slot>
367
+ <div class="mb-4 flex items-center">
368
+ <Upload :file-list="fileList" :before-upload="beforeUpload" accept=".xlsx,.xls" :showUploadList="true">
369
+ <Button type="primary">选择Excel文件</Button>
370
+ </Upload>
371
+ <div v-if="excelUrl && fileName" class="ml-3 text-gray-600">
372
+ <span>{{ fileName }}</span>
373
+ </div>
374
+ </div>
375
+
376
+ <div class="flex gap-4">
377
+ <!-- 左侧Excel显示区域 -->
378
+ <div
379
+ class="flex-shrink-0 excel-container"
380
+ :class="[validationRules.length === 0 ? 'w-[100%]' : 'w-[80%]']"
381
+ style="height: 430px"
382
+ >
383
+ <VueOfficeExcel
384
+ :src="excelUrl"
385
+ @error="handleError"
386
+ style="width: 100%; height: 100%"
387
+ :options="{
388
+ styles: true,
389
+ formatCells: true,
390
+ skipStyles: false,
391
+ autoStyle: true,
392
+ keepOriginalFormat: true,
393
+ renderingStyle: 'svg',
394
+ }"
395
+ />
396
+ </div>
397
+
398
+ <!-- 右侧验证条件和错误信息 -->
399
+ <div class="w-[22%] flex-grow overflow-hidden flex flex-col" v-if="validationRules.length > 0">
400
+ <div class="bg-gray-50 p-2 rounded border flex-grow flex flex-col">
401
+ <div v-if="validationRules.length === 0" class="text-gray-500">没有设置验证规则</div>
402
+ <div v-else class="flex flex-col">
403
+ <!-- 行验证规则 -->
404
+ <Alert :message="validationMsg" :type="validationType" show-icon class="mb-2" />
405
+ <div class="overflow-y-auto">
406
+ <div v-for="(rule, index) in validationRules" :key="index" class="border-b pb-0">
407
+ <div class="text-[13px] text-gray-600">{{ rule.field }}</div>
408
+ <ul class="pl-5 mt-1 mb-0">
409
+ <li v-for="(text, i) in rule.rules" :key="i" class="text-[12px] text-gray-600">
410
+ {{ text }}
411
+ </li>
412
+ </ul>
413
+ </div>
414
+ </div>
415
+
416
+ <!-- 重复数据检测规则 -->
417
+ <div v-if="duplicateRules && duplicateRules.length > 0" class="mt-3">
418
+ <Alert :message="duplicateMsg" :type="duplicateType" show-icon class="mt-2 mb-2" />
419
+ <div class="text-[12px] text-gray-600 p-1">
420
+ <div class="text-[13px] text-gray-600">
421
+ 以下{{ duplicateRules.length > 1 ? '组合' : '字段' }}必须唯一
422
+ </div>
423
+ <ul class="pl-5 mt-1 mb-0">
424
+ <li class="text-[12px] text-gray-600">
425
+ {{ duplicateRules.join(', ') }}
426
+ </li>
427
+ </ul>
428
+ </div>
429
+ </div>
430
+ </div>
431
+ </div>
432
+ </div>
433
+ </div>
434
+
435
+ <template #footer>
436
+ <Space>
437
+ <Button @click="dialogClose">取消</Button>
438
+ <Button
439
+ @click="dialogUpload"
440
+ type="primary"
441
+ :loading="excelCtrl.isFormSaving.value"
442
+ :disabled="!excelUrl || excelError || duplicateError || validating"
443
+ >
444
+ {{ saveText ?? '上传文件' }}
445
+ </Button>
446
+ </Space>
447
+ </template>
448
+ </Modal>
449
+ </template>
450
+ <style>
451
+ .modal .ant-modal-content {
452
+ padding: 16px;
453
+ }
454
+
455
+ .full-modal .ant-modal {
456
+ width: 100% !important;
457
+ max-width: 100%;
458
+ top: 0;
459
+ padding-bottom: 0;
460
+ margin: 0;
461
+ }
462
+
463
+ .full-modal .ant-modal-content {
464
+ display: flex;
465
+ flex-direction: column;
466
+ height: calc(100vh);
467
+ }
468
+
469
+ .full-modal .ant-modal-body {
470
+ flex: 1;
471
+ }
472
+
473
+ .excel-container {
474
+ position: relative;
475
+ border: 1px solid #f0f0f0;
476
+ border-radius: 4px;
477
+ overflow: hidden;
478
+ }
479
+ </style>
@@ -0,0 +1,149 @@
1
+ <script setup lang="ts">
2
+ import { watch, ref, onMounted, provide } from 'vue';
3
+ import { Button } from '../../common';
4
+ import { Modal, Space } from 'ant-design-vue';
5
+ import { onFormSave, onFormSaveAs, onFormClose, EditorControl, ProviderKeys } from '@skyfox2000/webbase';
6
+ import { AnyData } from '@skyfox2000/fapi';
7
+
8
+ const props = defineProps<{
9
+ /**
10
+ * 确认按钮文字,空字符串则不显示
11
+ */
12
+ saveText?: string;
13
+ /**
14
+ * 另存为按钮文字,空字符串则不显示
15
+ */
16
+ saveAsText?: string;
17
+ /**
18
+ * 取消按钮文字,空字符则不显示
19
+ */
20
+ cancelText?: string;
21
+ /**
22
+ * 保存数据请求配置
23
+ */
24
+ editorCtrl?: EditorControl<AnyData>;
25
+ /**
26
+ * 自定义存储方法
27
+ */
28
+ dialogSave?: () => void;
29
+ /**
30
+ * 宽度
31
+ */
32
+ width?: number;
33
+ /**
34
+ * 全屏模式
35
+ */
36
+ full?: boolean;
37
+ /**
38
+ * 是否显示
39
+ */
40
+ open?: boolean;
41
+ }>();
42
+
43
+ const editorCtrl = props.editorCtrl;
44
+ provide(ProviderKeys.EditorControl, editorCtrl);
45
+
46
+ const open = ref<boolean>(props.open ?? false);
47
+ const emit = defineEmits(['update:open']);
48
+ const width = ref(props.width ?? 430);
49
+ watch(
50
+ () => editorCtrl?.visible.value,
51
+ (newVal) => {
52
+ open.value = newVal ?? false;
53
+ },
54
+ );
55
+
56
+ watch(
57
+ () => props.open,
58
+ (newVal) => {
59
+ open.value = newVal;
60
+ },
61
+ );
62
+
63
+ watch(
64
+ () => open.value,
65
+ (newVal) => {
66
+ emit('update:open', newVal);
67
+ if (!newVal) {
68
+ dialogClose();
69
+ }
70
+ },
71
+ );
72
+
73
+ onMounted(() => {
74
+ open.value = editorCtrl?.visible.value ?? false;
75
+ });
76
+
77
+ const dialogSave = () => {
78
+ if (props.dialogSave) {
79
+ } else if (editorCtrl) onFormSave(editorCtrl);
80
+ };
81
+ const dialogSaveAs = () => {
82
+ if (editorCtrl) onFormSaveAs(editorCtrl);
83
+ };
84
+ const dialogClose = () => {
85
+ if (editorCtrl) onFormClose(editorCtrl);
86
+ else open.value = false;
87
+ };
88
+ </script>
89
+ <template>
90
+ <Modal
91
+ v-model:open="open"
92
+ :wrapClassName="'modal mx-auto min-w-[430px] ' + (full ? 'full-modal w-full' : '')"
93
+ :width="width"
94
+ >
95
+ <div class="overflow-y-auto w-full h-full">
96
+ <slot></slot>
97
+ </div>
98
+ <template #footer>
99
+ <Space>
100
+ <Button @click="dialogClose" v-if="cancelText !== ''">
101
+ {{ cancelText ?? '取消' }}
102
+ </Button>
103
+ <Button
104
+ @click="dialogSaveAs"
105
+ v-if="saveAsText !== '' && editorCtrl?.saveAsBtnVisible !== false"
106
+ type="primary"
107
+ :loading="editorCtrl?.isFormSaving.value"
108
+ >
109
+ {{ saveAsText ?? '另存为' }}
110
+ </Button>
111
+ <Button
112
+ @click="dialogSave"
113
+ v-if="saveText !== '' && editorCtrl?.saveBtnVisible !== false"
114
+ type="primary"
115
+ :loading="editorCtrl?.isFormSaving.value"
116
+ >
117
+ {{ saveText ?? '保存' }}
118
+ </Button>
119
+ </Space>
120
+ </template>
121
+ </Modal>
122
+ </template>
123
+ <style>
124
+ .modal {
125
+ .ant-modal-content {
126
+ padding: 16px;
127
+ }
128
+ }
129
+
130
+ .full-modal {
131
+ .ant-modal {
132
+ width: 100% !important;
133
+ max-width: 100%;
134
+ top: 0;
135
+ padding-bottom: 0;
136
+ margin: 0;
137
+ }
138
+
139
+ .ant-modal-content {
140
+ display: flex;
141
+ flex-direction: column;
142
+ height: calc(100vh);
143
+ }
144
+
145
+ .ant-modal-body {
146
+ flex: 1;
147
+ }
148
+ }
149
+ </style>