@skyfox2000/webui 1.4.16 → 1.4.18

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyfox2000/webui",
3
- "version": "1.4.16",
3
+ "version": "1.4.18",
4
4
  "description": "后台前端通用组件定义",
5
5
  "type": "module",
6
6
  "keywords": [],
@@ -1,9 +1,10 @@
1
1
  <script setup lang="ts">
2
- import { EditorControl, ProviderKeys, useFormItemFactory, ValidateRuleItem } from '@/index';
2
+ import { EditorControl, ProviderKeys, useFormItemFactory } from '@/index';
3
3
  import { FormItem, message } from 'ant-design-vue';
4
4
  import { Helper } from '../../common';
5
5
  import { computed, inject, ref, useAttrs } from 'vue';
6
6
  import { AnyData } from '@skyfox2000/fapi';
7
+ import { getRule } from '@/utils/form-validate';
7
8
 
8
9
  const props = defineProps<{
9
10
  /**
@@ -48,21 +49,6 @@ setTimeout(() => {
48
49
  visible.value = true;
49
50
  }, 30);
50
51
 
51
- /**
52
- * 递归获取规则
53
- * - async-validator的语法规范
54
- */
55
- const getRule = (rule: Array<string>, ruleObj: Record<string, any> | undefined): ValidateRuleItem | undefined => {
56
- if (!ruleObj) {
57
- return undefined;
58
- }
59
- const [key, ...rest] = rule;
60
- if (rule.length === 1) {
61
- return ruleObj[key];
62
- }
63
- if (!ruleObj[key]) return undefined;
64
- return getRule(rest, ruleObj[key].fields as Record<string, any>);
65
- };
66
52
  /**
67
53
  * 是否必填
68
54
  * - 如果rule为空,则不显示必填标记
@@ -1,8 +1,10 @@
1
1
  <script setup lang="ts">
2
- import { inject, useAttrs } from 'vue';
2
+ import { computed, inject, useAttrs } from 'vue';
3
3
  import { ProviderKeys, EditorControl, useFormItemFactory } from '@/index';
4
4
  import { FormItem } from 'ant-design-vue';
5
5
  import { AnyData } from '@skyfox2000/fapi';
6
+ import { getRule } from '@/utils/form-validate';
7
+ import message from 'vue-m-message';
6
8
 
7
9
  const props = defineProps<{
8
10
  /**
@@ -26,16 +28,38 @@ const attrs = useAttrs(); // 手动获取 $attrs
26
28
 
27
29
  const editorCtrl = inject(ProviderKeys.EditorControl, undefined) as EditorControl<AnyData> | undefined;
28
30
  const errInfo = useFormItemFactory(props, editorCtrl);
31
+
32
+ /**
33
+ * 是否必填
34
+ * - 如果rule为空,则不显示必填标记
35
+ * - 如果rule不为空,则根据formRules中的required属性判断是否必填
36
+ */
37
+ const required = computed(() => {
38
+ if (!props.rule) {
39
+ // 如果rule为空,则不显示必填标记
40
+ return false;
41
+ }
42
+ // 如果rule包含.,则表示是对象属性
43
+ const rule = getRule(props.rule.split('.'), editorCtrl?.formRules?.value);
44
+ if (!rule) {
45
+ message.error(`"${props.label}" 的验证规则 \`${props.rule}\` 不存在`);
46
+ errInfo.value.errClass = 'text-[#ff4d4f]';
47
+ errInfo.value.msg = `规则 \`${props.rule}\` 不存在,请检查代码!`;
48
+ return true;
49
+ }
50
+ if (!rule.required) {
51
+ // 如果rule.required为false,则表示该字段是可选字段,不需要验证
52
+ errInfo.value.errClass = '';
53
+ errInfo.value.msg = '';
54
+ }
55
+ return rule.required ?? true;
56
+ });
57
+
29
58
  </script>
30
59
  <template>
31
60
  <div class="w-1/3 relative mb-1">
32
- <FormItem
33
- :required="rule !== undefined"
34
- class="w-[90%] relative"
35
- v-bind="attrs"
36
- :class="[rule ? '' : 'mb-3', width]"
37
- :labelCol="{ span: 6 }"
38
- >
61
+ <FormItem :required="required" class="w-[90%] relative" v-bind="attrs" :class="[rule ? '' : 'mb-3', width]"
62
+ :labelCol="{ span: 6 }">
39
63
  <template #label v-if="label">
40
64
  <span :class="[errInfo.errClass ? 'text-[#ff4d4f]' : '', 'w-full']"> {{ label }}</span>
41
65
  </template>
@@ -11,13 +11,92 @@ import { LoginExpiredError, useUserInfo } from '@/stores/userInfo';
11
11
  // 表格列类型定义 (适配 ant-design-vue)
12
12
  export interface TableColumn {
13
13
  title: string;
14
- dataIndex?: string;
14
+ dataIndex?: string | string[];
15
15
  key?: string;
16
16
  visible?: boolean; // 是否显示列
17
17
  export?: boolean; // 是否导出列
18
- customRender?: (value: any, record: Record<string, any>) => any;
18
+ customRender?: (options: { text: any; record: Record<string, any>; index: number; column: TableColumn }) => any;
19
19
  }
20
20
 
21
+ /**
22
+ * 将 dataIndex 转换为字符串形式
23
+ * 如果是数组则转换为点号连接的字符串,否则直接返回原值或空字符串
24
+ * @param dataIndex 列的 dataIndex
25
+ */
26
+ const toString = (dataIndex: string | string[] | undefined): string => {
27
+ if (!dataIndex) return '';
28
+ if (Array.isArray(dataIndex)) {
29
+ return dataIndex.join('.');
30
+ }
31
+ return dataIndex;
32
+ };
33
+
34
+ /**
35
+ * 准备 CSV 字段配置
36
+ * @param columns 需要导出的列配置
37
+ */
38
+ const getFields = (columns: TableColumn[]) => {
39
+ return columns.map((col) => ({
40
+ label: col.title,
41
+ value: toString(col.dataIndex) || col.key || '',
42
+ }));
43
+ };
44
+
45
+ /**
46
+ * 处理数据行,应用自定义渲染
47
+ * @param data 原始数据
48
+ * @param columns 列配置
49
+ */
50
+ const processData = <T extends Record<string, any>>(data: T[], columns: TableColumn[]) => {
51
+ return data.map((row, index) => {
52
+ const newRow: Record<string, any> = { ...row };
53
+
54
+ columns.forEach((col) => {
55
+ const field = toString(col.dataIndex) || col.key;
56
+ if (!field) return;
57
+
58
+ // 应用自定义渲染
59
+ if (col.customRender) {
60
+ newRow[field] = col.customRender({
61
+ text: row[field],
62
+ record: row,
63
+ index: index,
64
+ column: col,
65
+ });
66
+ }
67
+ });
68
+ return newRow;
69
+ });
70
+ };
71
+
72
+ /**
73
+ * 生成并下载 CSV 文件
74
+ * @param fileName 文件名
75
+ * @param fields CSV 字段配置
76
+ * @param data 处理后的数据
77
+ */
78
+ const downloadCSV = async <T extends Record<string, any>>(
79
+ fileName: string,
80
+ fields: Array<{ label: string; value: string }>,
81
+ data: T[],
82
+ ) => {
83
+ try {
84
+ const mod = await import('@json2csv/plainjs');
85
+ const JSON2CSVParser = mod.Parser;
86
+
87
+ // 生成 CSV 内容
88
+ const parser = new JSON2CSVParser({ fields });
89
+ const csvContent = parser.parse(data);
90
+
91
+ // 创建并下载文件
92
+ const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv' });
93
+ downloadBlob(blob, fileName);
94
+ } catch (error) {
95
+ console.error('导出失败:', error);
96
+ message.error('文件导出失败,请稍后重试');
97
+ }
98
+ };
99
+
21
100
  /**
22
101
  * 导出选中行数据为 CSV 文件
23
102
  * @param fileName 文件名(支持 {YYYY}、{YY}、{MM}、{DD}、{HH}、{mm}、{ss} 时间格式)
@@ -30,8 +109,6 @@ export const exportSelectedRows = async <T extends Record<string, any>>(
30
109
  selectRows: T[],
31
110
  ) => {
32
111
  try {
33
- const mod = await import('@json2csv/plainjs');
34
- const JSON2CSVParser = mod.Parser;
35
112
  // 1. 处理文件名中的日期格式
36
113
  const processedFileName = formatFileName(fileName);
37
114
 
@@ -39,37 +116,15 @@ export const exportSelectedRows = async <T extends Record<string, any>>(
39
116
  const exportColumns = columns.filter((col) => col.visible !== false && col.export !== false);
40
117
 
41
118
  // 3. 准备 CSV 字段配置
42
- const fields = exportColumns.map((col) => ({
43
- label: col.title,
44
- value: col.dataIndex || col.key || '',
45
- }));
119
+ const fields = getFields(exportColumns);
46
120
 
47
121
  // 4. 处理数据行
48
- const processedData = selectRows.map((row) => {
49
- const newRow: Record<string, any> = { ...row };
50
-
51
- exportColumns.forEach((col) => {
52
- const field = col.dataIndex || col.key;
53
- if (!field) return;
54
-
55
- // 应用自定义渲染
56
- if (col.customRender) {
57
- newRow[field] = col.customRender(row[field], row);
58
- }
59
- });
60
- return newRow;
61
- });
122
+ const processedData = processData(selectRows, exportColumns);
62
123
 
63
- // 5. 生成 CSV 内容
64
- const parser = new JSON2CSVParser({ fields });
65
- const csvContent = parser.parse(processedData);
66
-
67
- // 6. 创建并下载文件
68
- const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv' });
69
- downloadBlob(blob, processedFileName);
124
+ // 5. 生成 CSV 内容并下载
125
+ await downloadCSV(processedFileName, fields, processedData);
70
126
  } catch (error) {
71
127
  console.error('导出失败:', error);
72
- // throw new Error('文件导出失败,请稍后重试');
73
128
  message.error('文件导出失败,请稍后重试');
74
129
  }
75
130
  };
@@ -91,8 +146,6 @@ export const exportResults = async <T extends Record<string, any>>(
91
146
  url: IUrlInfo,
92
147
  ) => {
93
148
  try {
94
- const mod = await import('@json2csv/plainjs');
95
- const JSON2CSVParser = mod.Parser;
96
149
  // 1. 处理文件名中的日期格式
97
150
  const processedFileName = formatFileName(fileName);
98
151
 
@@ -100,10 +153,7 @@ export const exportResults = async <T extends Record<string, any>>(
100
153
  const exportColumns = columns.filter((col) => col.visible !== false);
101
154
 
102
155
  // 3. 准备 CSV 字段配置
103
- const fields = exportColumns.map((col) => ({
104
- label: col.title,
105
- value: col.dataIndex || col.key || '',
106
- }));
156
+ const fields = getFields(exportColumns);
107
157
 
108
158
  // 4. 获取数据
109
159
  let pageCtrl = gridCtrl.page;
@@ -113,34 +163,16 @@ export const exportResults = async <T extends Record<string, any>>(
113
163
  if (url.authorize === undefined) url.authorize = pageCtrl.authorize;
114
164
 
115
165
  gridCtrl.isGridLoading.value = true;
116
- return httpPost<T>(url, newParams).then((result: ApiResponse<T> | null) => {
166
+ return httpPost<T>(url, newParams).then(async (result: ApiResponse<T> | null) => {
117
167
  gridCtrl.isGridLoading.value = false;
118
168
  if (result?.status === ResStatus.SUCCESS) {
119
169
  if (result.data) {
120
170
  // 5. 处理数据行
121
171
  let results = result.data as unknown as T[];
122
- const processedData = results.map((row) => {
123
- const newRow: Record<string, any> = { ...row };
124
-
125
- exportColumns.forEach((col) => {
126
- const field = col.dataIndex || col.key;
127
- if (!field) return;
128
-
129
- // 应用自定义渲染
130
- if (col.customRender) {
131
- newRow[field] = col.customRender(row[field], row);
132
- }
133
- });
134
- return newRow;
135
- });
136
-
137
- // 5. 生成 CSV 内容
138
- const parser = new JSON2CSVParser({ fields });
139
- const csvContent = parser.parse(processedData);
140
-
141
- // 6. 创建并下载文件
142
- const blob = new Blob([`\uFEFF${csvContent}`], { type: 'text/csv' });
143
- downloadBlob(blob, processedFileName);
172
+ const processedData = processData(results, exportColumns);
173
+
174
+ // 6. 生成 CSV 内容并下载
175
+ await downloadCSV(processedFileName, fields, processedData);
144
176
  }
145
177
  } else if (result?.errno == LoginExpiredError) {
146
178
  const userInfoStore = useUserInfo();
@@ -151,10 +183,9 @@ export const exportResults = async <T extends Record<string, any>>(
151
183
  });
152
184
  } catch (error) {
153
185
  console.error('导出失败:', error);
154
- // throw new Error('文件导出失败,请稍后重试');
155
186
  message.error('文件导出失败,请稍后重试');
156
187
  }
157
188
  };
158
189
 
159
190
  // 后端处理
160
- // 下载文件或二进制输出文件
191
+ // 下载文件或二进制输出文件
@@ -1,6 +1,6 @@
1
1
  import { inject, toRaw, ref, provide, watch, Ref } from 'vue';
2
2
  import Validator from 'async-validator';
3
- import { EditorControl, InputFactoryItems, ValidateError, ValidateRule } from '@/typings/form.d';
3
+ import { EditorControl, InputFactoryItems, ValidateError, ValidateRule, ValidateRuleItem } from '@/typings/form.d';
4
4
  import { ProviderKeys } from '@/typings/page.d';
5
5
  import { AnyData } from '@skyfox2000/fapi';
6
6
  import { isEmpty } from './isEmpty';
@@ -383,3 +383,22 @@ export const useFormItemFactory = (options: RuleFactoryOptions, editorCtrl?: Edi
383
383
  provide(ProviderKeys.ErrInfo, errInfo);
384
384
  return errInfo;
385
385
  };
386
+
387
+ /**
388
+ * 递归获取规则
389
+ * - async-validator的语法规范
390
+ */
391
+ export const getRule = (
392
+ rule: Array<string>,
393
+ ruleObj: Record<string, any> | undefined,
394
+ ): ValidateRuleItem | undefined => {
395
+ if (!ruleObj) {
396
+ return undefined;
397
+ }
398
+ const [key, ...rest] = rule;
399
+ if (rule.length === 1) {
400
+ return ruleObj[key];
401
+ }
402
+ if (!ruleObj[key]) return undefined;
403
+ return getRule(rest, ruleObj[key].fields as Record<string, any>);
404
+ };