@skyfox2000/webui 1.3.4 → 1.3.6
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/.vscode/settings.json +1 -1
- package/lib/assets/modules/{file-upload-BBlFaIXB.js → file-upload-DhPgqGdk.js} +50 -50
- package/lib/assets/modules/index-02J2AYth.js +377 -0
- package/lib/assets/modules/{index-m5rogIyM.js → index-C31q4LHC.js} +2 -2
- package/lib/assets/modules/{index-BG1SqSVl.js → index-CCpTizF9.js} +1 -1
- package/lib/assets/modules/{menuTabs-tPIz4a89.js → menuTabs-DyhSKN9r.js} +2 -2
- package/lib/assets/modules/{toolIcon-DwWoD9TN.js → toolIcon-CqM4gBIc.js} +1 -1
- package/lib/assets/modules/{uploadList-D_Z-Y2tw.js → uploadList-DAVjJkqz.js} +511 -476
- package/lib/assets/modules/{uploadList-Da7mQUNK.js → uploadList-ZajZKqaS.js} +4 -4
- package/lib/components/common/alert/index.vue.d.ts +13 -0
- package/lib/components/common/icon/helper.vue.d.ts +1 -0
- package/lib/components/common/index.d.ts +2 -0
- package/lib/components/content/form/formItem.vue.d.ts +1 -0
- package/lib/components/content/table/index.vue.d.ts +95 -4
- package/lib/components/form/input/index.vue.d.ts +4 -1
- package/lib/components/form/select/index.vue.d.ts +2 -0
- package/lib/components/form/treeSelect/index.vue.d.ts +11 -2
- package/lib/components/index.d.ts +1 -1
- package/lib/es/AceEditor/index.js +3 -3
- package/lib/es/BasicLayout/index.js +3 -3
- package/lib/es/Error403/index.js +1 -1
- package/lib/es/Error404/index.js +1 -1
- package/lib/es/ExcelForm/index.js +317 -274
- package/lib/es/UploadForm/index.js +4 -4
- package/lib/index.d.ts +1 -1
- package/lib/typings/form.d.ts +2 -2
- package/lib/typings/option.d.ts +2 -2
- package/lib/utils/excel-preview.d.ts +24 -0
- package/lib/utils/form-excel.d.ts +17 -4
- package/lib/utils/options.d.ts +2 -2
- package/lib/webui.css +1 -1
- package/lib/webui.es.js +759 -747
- package/package.json +1 -1
- package/src/components/common/alert/index.vue +76 -0
- package/src/components/common/icon/helper.vue +7 -1
- package/src/components/common/index.ts +4 -1
- package/src/components/common/loading/index.vue +1 -1
- package/src/components/content/dialog/excelForm.vue +343 -313
- package/src/components/content/form/formItem.vue +6 -2
- package/src/components/content/table/index.vue +9 -6
- package/src/components/form/autoComplete/index.vue +9 -3
- package/src/components/form/cascader/index.vue +8 -6
- package/src/components/form/input/index.vue +16 -3
- package/src/components/form/select/index.vue +5 -11
- package/src/components/form/treeSelect/index.vue +22 -17
- package/src/components/index.ts +1 -0
- package/src/index.ts +1 -0
- package/src/typings/form.d.ts +2 -2
- package/src/typings/option.d.ts +2 -2
- package/src/utils/excel-preview.ts +188 -0
- package/src/utils/file-upload.ts +0 -2
- package/src/utils/form-excel.ts +132 -126
- package/src/utils/options.ts +80 -22
- package/src/utils/table.ts +15 -2
- package/lib/assets/modules/index-4kDAt8nS.js +0 -333
|
@@ -26,6 +26,10 @@ const props = defineProps<{
|
|
|
26
26
|
* 是否显示在独立行
|
|
27
27
|
*/
|
|
28
28
|
nextLine?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* 底部间距
|
|
31
|
+
*/
|
|
32
|
+
bottomMargin?: string;
|
|
29
33
|
}>();
|
|
30
34
|
|
|
31
35
|
// 关闭自动继承属性到根元素
|
|
@@ -87,13 +91,13 @@ const required = computed(() => {
|
|
|
87
91
|
// });
|
|
88
92
|
</script>
|
|
89
93
|
<template>
|
|
90
|
-
<div :class="['relative mb-1']">
|
|
94
|
+
<div :class="['relative', bottomMargin ? bottomMargin : 'mb-1']">
|
|
91
95
|
<FormItem
|
|
92
96
|
v-if="visible"
|
|
93
97
|
:required="required"
|
|
94
98
|
class="relative"
|
|
95
|
-
v-bind="attrs"
|
|
96
99
|
:class="[nextLine ? 'mb-0' : rule ? '' : 'mb-3']"
|
|
100
|
+
v-bind="attrs"
|
|
97
101
|
>
|
|
98
102
|
<template #label>
|
|
99
103
|
<span :class="[errInfo.errClass ? 'text-[#ff4d4f]' : '', 'w-full']"> {{ label }}</span>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts" setup>
|
|
2
2
|
import { onMounted, Ref, ref, watch, provide, useAttrs, onActivated, computed } from 'vue';
|
|
3
|
-
import { Table,
|
|
3
|
+
import { Table, PaginationProps, TableProps } from 'ant-design-vue';
|
|
4
4
|
import {
|
|
5
5
|
AppRouter,
|
|
6
6
|
gridQueryFind,
|
|
@@ -17,7 +17,7 @@ import Switch from '../../form/switch/index.vue';
|
|
|
17
17
|
import { AnyData } from '@skyfox2000/fapi';
|
|
18
18
|
import { ProviderKeys } from '@/index';
|
|
19
19
|
|
|
20
|
-
const props = defineProps<{
|
|
20
|
+
const props = withDefaults(defineProps<{
|
|
21
21
|
/**
|
|
22
22
|
* 表格数据控制
|
|
23
23
|
*/
|
|
@@ -37,7 +37,7 @@ const props = defineProps<{
|
|
|
37
37
|
/**
|
|
38
38
|
* 自定义分页控制
|
|
39
39
|
*/
|
|
40
|
-
pagination?:
|
|
40
|
+
pagination?: false | PaginationProps;
|
|
41
41
|
/**
|
|
42
42
|
* 表格大小配置
|
|
43
43
|
*/
|
|
@@ -46,7 +46,10 @@ const props = defineProps<{
|
|
|
46
46
|
* 是否禁用启用状态
|
|
47
47
|
*/
|
|
48
48
|
statusDisabled?: Function;
|
|
49
|
-
}>()
|
|
49
|
+
}>(), {
|
|
50
|
+
pagination: undefined,
|
|
51
|
+
});
|
|
52
|
+
|
|
50
53
|
// 关闭自动继承属性到根元素
|
|
51
54
|
defineOptions({
|
|
52
55
|
inheritAttrs: false,
|
|
@@ -67,7 +70,7 @@ const curPageSize = ref(gridCtrl.pageSize.value);
|
|
|
67
70
|
const curPageNo = ref(gridCtrl.pageNo.value);
|
|
68
71
|
|
|
69
72
|
const dataList = ref<Record<string, AnyData>[]>([]);
|
|
70
|
-
const pagination: Ref<false |
|
|
73
|
+
const pagination: Ref<false | PaginationProps> = ref<false | PaginationProps>({
|
|
71
74
|
...{
|
|
72
75
|
total: 0,
|
|
73
76
|
current: 1,
|
|
@@ -89,7 +92,7 @@ const pagination: Ref<false | TablePaginationConfig> = ref<false | TablePaginati
|
|
|
89
92
|
}
|
|
90
93
|
},
|
|
91
94
|
},
|
|
92
|
-
...props.pagination,
|
|
95
|
+
...(props.pagination === false ? {} : props.pagination),
|
|
93
96
|
});
|
|
94
97
|
|
|
95
98
|
if (props.pagination === false) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref, onUnmounted, useAttrs, watch, shallowRef, PropType } from 'vue';
|
|
3
|
-
import { AutoComplete } from 'ant-design-vue';
|
|
3
|
+
import { AutoComplete, Input } from 'ant-design-vue';
|
|
4
4
|
import {
|
|
5
5
|
useInputFactory,
|
|
6
6
|
OptionCommProps,
|
|
@@ -141,11 +141,12 @@ onUnmounted(() => {
|
|
|
141
141
|
v-model:value="innerValue"
|
|
142
142
|
:class="['w-full', errInfo?.errClass]"
|
|
143
143
|
:options="selectOptions"
|
|
144
|
-
:placeholder="'请输入并选择' + labelText"
|
|
145
144
|
@search="onSearch"
|
|
146
145
|
@select="onSelected"
|
|
147
146
|
v-bind="attrs"
|
|
147
|
+
:allow-clear="false"
|
|
148
148
|
>
|
|
149
|
+
<Input allow-clear :placeholder="'请输入并选择' + labelText" />
|
|
149
150
|
<template #option="{ label }">
|
|
150
151
|
{{ label }}
|
|
151
152
|
</template>
|
|
@@ -158,4 +159,9 @@ onUnmounted(() => {
|
|
|
158
159
|
border-color: #ff4d4f80;
|
|
159
160
|
box-shadow: 0 0 3px 0 #ff4d4f;
|
|
160
161
|
}
|
|
161
|
-
|
|
162
|
+
|
|
163
|
+
:deep(input::-webkit-search-cancel-button),
|
|
164
|
+
:deep(input::-webkit-clear-button) {
|
|
165
|
+
display: none !important;
|
|
166
|
+
}
|
|
167
|
+
</style>
|
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
loadOption,
|
|
9
9
|
unloadOption,
|
|
10
10
|
formValidate,
|
|
11
|
+
onOptionChanged,
|
|
12
|
+
getSelectedLabels,
|
|
13
|
+
SelectValue,
|
|
11
14
|
} from '@/index';
|
|
12
15
|
import { Cascader } from 'ant-design-vue';
|
|
13
16
|
import { DefaultOptionType, ValueType } from 'ant-design-vue/es/vc-cascader/Cascader';
|
|
@@ -61,7 +64,7 @@ if (optionCtrl) {
|
|
|
61
64
|
(newOptions) => {
|
|
62
65
|
selectOptions.value = newOptions || [];
|
|
63
66
|
},
|
|
64
|
-
{ immediate: true, deep: true }
|
|
67
|
+
{ immediate: true, deep: true },
|
|
65
68
|
);
|
|
66
69
|
}
|
|
67
70
|
|
|
@@ -71,13 +74,12 @@ const onChanged = (_: ValueType, selected: DefaultOptionType[]) => {
|
|
|
71
74
|
emit('update:value', []);
|
|
72
75
|
return;
|
|
73
76
|
}
|
|
74
|
-
const
|
|
77
|
+
const values = selected.map((item) => item.value);
|
|
78
|
+
const selectedOptions = onOptionChanged(optionCtrl, props, values as unknown as SelectValue);
|
|
79
|
+
const labels: string[] = getSelectedLabels(selectedOptions);
|
|
75
80
|
|
|
76
81
|
emit('update:labels', labels);
|
|
77
|
-
emit(
|
|
78
|
-
'update:value',
|
|
79
|
-
selected.map((item) => item.value),
|
|
80
|
-
);
|
|
82
|
+
emit('update:value', values);
|
|
81
83
|
if (errInfo?.value.errClass && editorCtrl) {
|
|
82
84
|
/// 重新开始验证
|
|
83
85
|
formValidate(editorCtrl);
|
|
@@ -11,9 +11,22 @@ const onBlur = () => {
|
|
|
11
11
|
}
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
const props = defineProps<{
|
|
14
|
+
const props = withDefaults(defineProps<{
|
|
15
15
|
value?: any;
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* 清空时设置的值
|
|
18
|
+
* - false,则清空时设置为 null
|
|
19
|
+
* - true,则清空时设置为 undefined
|
|
20
|
+
*/
|
|
21
|
+
undefValue?: boolean;
|
|
22
|
+
}>(),
|
|
23
|
+
{
|
|
24
|
+
undefValue: false,
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// 如果初始value为undefined,自动设置undefValue为true
|
|
29
|
+
const undefValue = props.value === undefined ? true : props.undefValue;
|
|
17
30
|
const emit = defineEmits(['update:value']);
|
|
18
31
|
// 内部值,拦截外部传入的 value
|
|
19
32
|
const innerValue = ref(props.value);
|
|
@@ -40,7 +53,7 @@ watch(
|
|
|
40
53
|
|
|
41
54
|
const onClear = () => {
|
|
42
55
|
if (innerValue.value === '') {
|
|
43
|
-
innerValue.value = undefined;
|
|
56
|
+
innerValue.value = undefValue ? undefined : null;
|
|
44
57
|
}
|
|
45
58
|
};
|
|
46
59
|
</script>
|
|
@@ -43,7 +43,7 @@ const props = defineProps({
|
|
|
43
43
|
|
|
44
44
|
const inputFactory = useInputFactory();
|
|
45
45
|
const { editorCtrl, errInfo, labelText } = inputFactory;
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
const defaultCtrl = useOptionFactory(props.url, props);
|
|
48
48
|
const optionCtrl = props.optionCtrl ?? defaultCtrl.optionCtrl;
|
|
49
49
|
optionCtrl.inputFactory = shallowRef(inputFactory);
|
|
@@ -67,7 +67,7 @@ const placeholder = ref(attrs.placeholder);
|
|
|
67
67
|
|
|
68
68
|
/// 避免类型错误
|
|
69
69
|
const innerValue = ref<string | number | string[] | number[] | undefined>(optionCtrl?.selected.value || undefined);
|
|
70
|
-
const emit = defineEmits(['change', 'update:value', 'update:labels']);
|
|
70
|
+
const emit = defineEmits(['change', 'update:value', 'update:label', 'update:labels']);
|
|
71
71
|
inputFactory.inputEmit = emit;
|
|
72
72
|
|
|
73
73
|
/**
|
|
@@ -92,6 +92,7 @@ const onChanged = (value: any) => {
|
|
|
92
92
|
|
|
93
93
|
innerValue.value = value;
|
|
94
94
|
emit('update:labels', labels);
|
|
95
|
+
emit('update:label', labels.join(";"));
|
|
95
96
|
emit('change', value);
|
|
96
97
|
|
|
97
98
|
if (errInfo?.value.errClass && editorCtrl) {
|
|
@@ -160,15 +161,8 @@ onUnmounted(() => {
|
|
|
160
161
|
<circleLoading class="text-[#555] mx-[5px] !ml-[10px] !w-4 !h-4" />
|
|
161
162
|
<span>数据加载中...</span>
|
|
162
163
|
</div>
|
|
163
|
-
<Select
|
|
164
|
-
:
|
|
165
|
-
v-model:value="innerValue"
|
|
166
|
-
:allow-clear="true"
|
|
167
|
-
@change="onChanged"
|
|
168
|
-
:placeholder="placeholder"
|
|
169
|
-
:label-in-value="false"
|
|
170
|
-
v-bind="attrs"
|
|
171
|
-
>
|
|
164
|
+
<Select :class="[errInfo?.errClass, 'w-full']" v-model:value="innerValue" :allow-clear="true" @change="onChanged"
|
|
165
|
+
:placeholder="placeholder" :label-in-value="false" v-bind="attrs">
|
|
172
166
|
<template v-for="item in selectOptions" :key="item.value">
|
|
173
167
|
<SelectOption :value="item.value" v-if="item.visible !== false">
|
|
174
168
|
{{ item.label }}
|
|
@@ -16,10 +16,22 @@ const props = defineProps({
|
|
|
16
16
|
},
|
|
17
17
|
value: {
|
|
18
18
|
type: [String, Number, Array] as PropType<SelectValue>,
|
|
19
|
-
required:
|
|
19
|
+
required: false,
|
|
20
|
+
},
|
|
21
|
+
/**
|
|
22
|
+
* 清空时设置的值
|
|
23
|
+
* - false,则清空时设置为 null
|
|
24
|
+
* - true,则清空时设置为 undefined
|
|
25
|
+
*/
|
|
26
|
+
undefValue: {
|
|
27
|
+
type: Boolean,
|
|
28
|
+
default: false,
|
|
20
29
|
},
|
|
21
30
|
});
|
|
22
31
|
|
|
32
|
+
// 如果初始value为undefined,自动设置undefValue为true
|
|
33
|
+
const undefValue = props.value === undefined ? true : props.undefValue;
|
|
34
|
+
|
|
23
35
|
const treeCtrl = props.treeCtrl;
|
|
24
36
|
|
|
25
37
|
const inputFactory = useInputFactory();
|
|
@@ -32,13 +44,13 @@ inputFactory.inputEmit = emit;
|
|
|
32
44
|
// 树选择数据
|
|
33
45
|
const selectTreeData = ref<TreeNode[]>([]);
|
|
34
46
|
// 当前选中值
|
|
35
|
-
const currentValue = ref<SelectValue>();
|
|
47
|
+
const currentValue = ref<SelectValue | null>();
|
|
36
48
|
|
|
37
49
|
// 监听值变化
|
|
38
50
|
watch(
|
|
39
51
|
() => props.value,
|
|
40
52
|
(newVal) => {
|
|
41
|
-
currentValue.value = newVal
|
|
53
|
+
currentValue.value = newVal;
|
|
42
54
|
},
|
|
43
55
|
{ immediate: true },
|
|
44
56
|
);
|
|
@@ -59,7 +71,7 @@ const handleChange = (value: SelectValue) => {
|
|
|
59
71
|
if (props.multiple) {
|
|
60
72
|
currentValue.value = value ?? [];
|
|
61
73
|
} else {
|
|
62
|
-
currentValue.value = value ??
|
|
74
|
+
currentValue.value = value ?? undefValue ? undefined : null;
|
|
63
75
|
}
|
|
64
76
|
emit('change', currentValue.value);
|
|
65
77
|
emit('update:value', currentValue.value);
|
|
@@ -82,22 +94,15 @@ onMounted(() => {
|
|
|
82
94
|
queryTree(props.treeCtrl);
|
|
83
95
|
}
|
|
84
96
|
});
|
|
97
|
+
const onClear = () => {
|
|
98
|
+
currentValue.value = undefValue ? undefined : null;
|
|
99
|
+
};
|
|
85
100
|
</script>
|
|
86
101
|
|
|
87
102
|
<template>
|
|
88
|
-
<TreeSelect
|
|
89
|
-
:class="
|
|
90
|
-
|
|
91
|
-
:multiple="multiple"
|
|
92
|
-
:tree-default-expanded-keys="['-']"
|
|
93
|
-
v-model:value="currentValue"
|
|
94
|
-
:tree-data="selectTreeData"
|
|
95
|
-
:placeholder="'请选择' + labelText"
|
|
96
|
-
:allow-clear="true"
|
|
97
|
-
class="w-full"
|
|
98
|
-
@change="handleChange"
|
|
99
|
-
v-bind="$attrs"
|
|
100
|
-
/>
|
|
103
|
+
<TreeSelect :class="[errInfo?.errClass]" tree-line :multiple="multiple" :tree-default-expanded-keys="['-']"
|
|
104
|
+
v-model:value="currentValue" :tree-data="selectTreeData" :placeholder="'请选择' + labelText" class="w-full"
|
|
105
|
+
@change="handleChange" @clear="onClear" v-bind="$attrs" />
|
|
101
106
|
</template>
|
|
102
107
|
<style scoped>
|
|
103
108
|
.error :deep(.ant-select-selector) {
|
package/src/components/index.ts
CHANGED
package/src/index.ts
CHANGED
package/src/typings/form.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AnyData, IUrlInfo } from '@skyfox2000/fapi';
|
|
1
|
+
import { AnyData, IUrlInfo, ReqParams } from '@skyfox2000/fapi';
|
|
2
2
|
import { PageControl } from './page';
|
|
3
3
|
import Validator, { RuleItem, ValidateError } from 'async-validator';
|
|
4
4
|
import { Ref, DefineProps } from 'vue';
|
|
@@ -71,7 +71,7 @@ export type EditorControl<T> = EditorControlOption & {
|
|
|
71
71
|
* @param editorCtrl 表单配置数据
|
|
72
72
|
* @returns 是否保存
|
|
73
73
|
*/
|
|
74
|
-
beforeSave?: () => boolean | undefined;
|
|
74
|
+
beforeSave?: (params?: ReqParams) => boolean | undefined | void;
|
|
75
75
|
/**
|
|
76
76
|
* 表单保存后处理
|
|
77
77
|
* @param editorCtrl 表单配置数据
|
package/src/typings/option.d.ts
CHANGED
|
@@ -100,8 +100,8 @@ export const OptionCommProps = {
|
|
|
100
100
|
},
|
|
101
101
|
/**
|
|
102
102
|
* 输出字段转换控制
|
|
103
|
-
* - Key
|
|
104
|
-
* - Value:源字段,支持模板 ${}
|
|
103
|
+
* - Key:目的字段,支持 "." 嵌套
|
|
104
|
+
* - Value:源字段,支持模板 ${} 或者 ${index} 或者 ${index}.${key}
|
|
105
105
|
*/
|
|
106
106
|
outFields: {
|
|
107
107
|
type: Object as PropType<Record<string, string>>,
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Excel预览相关工具方法
|
|
3
|
+
* 处理Excel和CSV文件的预览功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { csvToExcelView } from './excel-view';
|
|
7
|
+
import { AnyData, ResStatus, IUrlInfo, httpGet, ApiResponse } from '@skyfox2000/fapi';
|
|
8
|
+
import { doQuery } from '@/index';
|
|
9
|
+
import message from 'vue-m-message';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 判断是否为CSV格式的内容
|
|
13
|
+
* @param data 文件内容
|
|
14
|
+
* @returns 是否为CSV格式
|
|
15
|
+
*/
|
|
16
|
+
export const isCsvContent = (data: string): boolean => {
|
|
17
|
+
return data.includes(',') || data.includes('\n');
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 判断是否为CSV类型
|
|
22
|
+
* @param type MIME类型
|
|
23
|
+
* @param filename 文件名
|
|
24
|
+
* @returns 是否为CSV类型
|
|
25
|
+
*/
|
|
26
|
+
export const isCsvType = (type?: string, filename?: string): boolean => {
|
|
27
|
+
return type === 'text/csv' || filename?.toLowerCase().includes('.csv') || false;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 判断是否为Excel类型
|
|
32
|
+
* @param type MIME类型
|
|
33
|
+
* @param filename 文件名
|
|
34
|
+
* @returns 是否为Excel类型
|
|
35
|
+
*/
|
|
36
|
+
export const isExcelType = (type?: string, filename?: string): boolean => {
|
|
37
|
+
return (
|
|
38
|
+
type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
|
|
39
|
+
type === 'application/vnd.ms-excel' ||
|
|
40
|
+
filename?.toLowerCase().match(/\.(xlsx|xls)$/) !== null
|
|
41
|
+
);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* CSV内容处理的公共方法
|
|
46
|
+
* @param content CSV内容
|
|
47
|
+
* @param filename 文件名
|
|
48
|
+
* @returns 处理结果
|
|
49
|
+
*/
|
|
50
|
+
export const processCsvContent = async (content: string, filename: string = 'preview.csv') => {
|
|
51
|
+
const csvResult = await csvToExcelView(content, filename);
|
|
52
|
+
if (csvResult.success) {
|
|
53
|
+
return {
|
|
54
|
+
success: true,
|
|
55
|
+
blobUrl: csvResult.blobUrl!,
|
|
56
|
+
fileName: csvResult.fileName!,
|
|
57
|
+
};
|
|
58
|
+
} else {
|
|
59
|
+
throw new Error(csvResult.error || 'CSV格式处理失败');
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Excel内容处理的公共方法
|
|
65
|
+
* @param content Excel内容
|
|
66
|
+
* @param mimeType MIME类型
|
|
67
|
+
* @param filename 文件名
|
|
68
|
+
* @returns 处理结果
|
|
69
|
+
*/
|
|
70
|
+
export const processExcelContent = (content: string, mimeType?: string, filename: string = '预览文件.xlsx') => {
|
|
71
|
+
const type = mimeType || 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
|
72
|
+
return {
|
|
73
|
+
success: true,
|
|
74
|
+
blobUrl: `data:${type};base64,${content}`,
|
|
75
|
+
fileName: filename,
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 处理文件数据的统一方法
|
|
81
|
+
* @param data 文件数据
|
|
82
|
+
* @param isRawMode 是否为原始模式
|
|
83
|
+
* @returns 处理结果
|
|
84
|
+
*/
|
|
85
|
+
export const handleFileData = async (data: any, isRawMode: boolean = false) => {
|
|
86
|
+
// 原始模式:直接处理文件内容
|
|
87
|
+
if (isRawMode) {
|
|
88
|
+
if (typeof data === 'string') {
|
|
89
|
+
if (isCsvContent(data)) {
|
|
90
|
+
return await processCsvContent(data);
|
|
91
|
+
} else {
|
|
92
|
+
return processExcelContent(data);
|
|
93
|
+
}
|
|
94
|
+
} else if (data instanceof ArrayBuffer || data instanceof Uint8Array) {
|
|
95
|
+
const blob = new Blob([data], {
|
|
96
|
+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
97
|
+
});
|
|
98
|
+
return {
|
|
99
|
+
success: true,
|
|
100
|
+
blobUrl: URL.createObjectURL(blob),
|
|
101
|
+
fileName: '预览文件.xlsx',
|
|
102
|
+
};
|
|
103
|
+
} else {
|
|
104
|
+
throw new Error('不支持的原始文件格式');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 标准模式:处理结构化数据
|
|
109
|
+
if (data.Content && data.Type) {
|
|
110
|
+
if (isCsvType(data.Type, data.FileName)) {
|
|
111
|
+
return await processCsvContent(data.Content, data.FileName || 'preview.csv');
|
|
112
|
+
} else if (isExcelType(data.Type, data.FileName)) {
|
|
113
|
+
return processExcelContent(data.Content, data.Type, data.FileName || '预览文件.xlsx');
|
|
114
|
+
} else {
|
|
115
|
+
return processExcelContent(data.Content);
|
|
116
|
+
}
|
|
117
|
+
} else if (data.url) {
|
|
118
|
+
return {
|
|
119
|
+
success: true,
|
|
120
|
+
blobUrl: data.url,
|
|
121
|
+
fileName: data.fileName || '预览文件.xlsx',
|
|
122
|
+
};
|
|
123
|
+
} else if (typeof data === 'string') {
|
|
124
|
+
return await processCsvContent(data);
|
|
125
|
+
} else {
|
|
126
|
+
throw new Error('不支持的文件格式');
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* 加载预览文件
|
|
132
|
+
* @param previewUrl 预览URL配置
|
|
133
|
+
* @param excelCtrl Excel控制器
|
|
134
|
+
* @returns 预览文件处理结果
|
|
135
|
+
*/
|
|
136
|
+
export const loadPreviewFile = async (
|
|
137
|
+
previewUrl: IUrlInfo,
|
|
138
|
+
excelCtrl: any,
|
|
139
|
+
): Promise<{ success: boolean; blobUrl?: string; fileName?: string }> => {
|
|
140
|
+
if (!previewUrl || !excelCtrl) {
|
|
141
|
+
throw new Error('预览URL或Excel控制器未配置');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
try {
|
|
145
|
+
let result: ApiResponse<AnyData> | null = null;
|
|
146
|
+
|
|
147
|
+
// 根据请求方法选择不同的处理方式
|
|
148
|
+
if (previewUrl.method === 'GET') {
|
|
149
|
+
// 使用 httpGet 方法处理 GET 请求
|
|
150
|
+
const getUrl: IUrlInfo = {
|
|
151
|
+
...previewUrl,
|
|
152
|
+
method: 'GET' as const,
|
|
153
|
+
api: previewUrl.api || excelCtrl.page.api,
|
|
154
|
+
authorize: previewUrl.authorize ?? excelCtrl.page.authorize,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
result = await httpGet(getUrl);
|
|
158
|
+
} else {
|
|
159
|
+
// 使用 doQuery 处理 POST 请求
|
|
160
|
+
const queryParams = previewUrl.params;
|
|
161
|
+
result = await doQuery(excelCtrl, {
|
|
162
|
+
url: previewUrl,
|
|
163
|
+
urlKey: 'preview',
|
|
164
|
+
params: queryParams,
|
|
165
|
+
hideErrorToast: true,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 原始模式下,result 就是原始数据,不是 ApiResponse 格式
|
|
170
|
+
if (previewUrl.raw) {
|
|
171
|
+
// 原始模式:直接处理返回的数据
|
|
172
|
+
return await handleFileData(result, true);
|
|
173
|
+
} else {
|
|
174
|
+
// 标准模式:检查 ApiResponse 格式
|
|
175
|
+
if (result?.status === ResStatus.SUCCESS && result.data) {
|
|
176
|
+
const data = result.data;
|
|
177
|
+
// 处理返回的文件数据
|
|
178
|
+
return await handleFileData(data, false);
|
|
179
|
+
} else {
|
|
180
|
+
throw new Error(result?.msg || '文件加载失败');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
} catch (error: any) {
|
|
184
|
+
console.error('预览文件加载错误:', error);
|
|
185
|
+
message.error('文件加载失败:' + (error?.message || '未知错误'));
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
};
|
package/src/utils/file-upload.ts
CHANGED
|
@@ -148,7 +148,6 @@ export class AsyncUploader {
|
|
|
148
148
|
// 等待所有上传任务完成
|
|
149
149
|
await Promise.all(activeUploads);
|
|
150
150
|
} catch (error) {
|
|
151
|
-
console.error('上传失败:', error);
|
|
152
151
|
fileList.forEach((file) => {
|
|
153
152
|
file.status = UploadStatus.Error;
|
|
154
153
|
file.error = error instanceof Error ? error : new Error('上传失败');
|
|
@@ -179,7 +178,6 @@ export class AsyncUploader {
|
|
|
179
178
|
});
|
|
180
179
|
} catch (error) {
|
|
181
180
|
file.error = error instanceof Error ? error : new Error('上传失败');
|
|
182
|
-
console.error(file.error);
|
|
183
181
|
} finally {
|
|
184
182
|
// 启动下一个上传任务
|
|
185
183
|
if (pendingFiles.length > 0) {
|