@peng_kai/kit 0.2.19 → 0.2.21
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/admin/components/settings/index.ts +1 -0
- package/admin/components/settings/src/SchemaForm.vue +283 -0
- package/admin/components/settings/src/Settings.vue +23 -274
- package/admin/components/text/src/Amount.vue +7 -3
- package/antd/hooks/useAntdForm.ts +1 -1
- package/package.json +1 -1
- package/utils/number.ts +20 -0
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cloneDeep, mapKeys, mapValues } from 'lodash-es';
|
|
3
|
+
import { CheckboxGroup, DatePicker, type DatePickerProps, Form, FormItem, Input, InputNumber, RadioGroup, RangePicker, Select, Textarea } from 'ant-design-vue';
|
|
4
|
+
import { computed, toRef } from 'vue';
|
|
5
|
+
import dayjs from '../../../../libs/dayjs';
|
|
6
|
+
import { type ItemSchema, useAntdForm } from '../../../../antd';
|
|
7
|
+
|
|
8
|
+
export interface IConfigDetail {
|
|
9
|
+
category_id: number
|
|
10
|
+
data_type: string
|
|
11
|
+
form_type: number
|
|
12
|
+
key: string
|
|
13
|
+
label: string
|
|
14
|
+
options: any
|
|
15
|
+
required: number
|
|
16
|
+
sort: number
|
|
17
|
+
status: number
|
|
18
|
+
summary: string
|
|
19
|
+
value: any
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
enum FormTypes {
|
|
23
|
+
/** 数字输入框 */
|
|
24
|
+
NUMBER_INPUT = 0,
|
|
25
|
+
/** 文本输入框 */
|
|
26
|
+
TEXT_INPUT = 1,
|
|
27
|
+
/** 多行文本输入框 */
|
|
28
|
+
TEXTAREA = 2,
|
|
29
|
+
/** 开关 */
|
|
30
|
+
SWITCH = 3,
|
|
31
|
+
/** 单选框 */
|
|
32
|
+
RADIO = 4,
|
|
33
|
+
/** 多选框 */
|
|
34
|
+
CHECKBOX = 5,
|
|
35
|
+
/** 单选下拉选择器 */
|
|
36
|
+
SINGLE_SELECT = 6,
|
|
37
|
+
/** 多选下拉选择器 */
|
|
38
|
+
MULTIPLE_SELECT = 7,
|
|
39
|
+
/** 单一日期选择器 */
|
|
40
|
+
SINGLE_DATE_PICKER = 13,
|
|
41
|
+
/** 日期范围选择器 */
|
|
42
|
+
RANGE_DATE_PICKER = 14,
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const antdPropsResolvers: Record<number, (config: IConfigDetail) => IConfigDetail> = {
|
|
46
|
+
[FormTypes.SWITCH](config) {
|
|
47
|
+
const props: any = {};
|
|
48
|
+
|
|
49
|
+
if (config.options) {
|
|
50
|
+
props.options = config.options.split(',').map((item: any) => {
|
|
51
|
+
const [label, value] = item.split(':');
|
|
52
|
+
return { value, label };
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
props.options = [{ value: '1', label: '是' }, { value: '0', label: '否' }];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
config.options = props;
|
|
60
|
+
|
|
61
|
+
return config;
|
|
62
|
+
},
|
|
63
|
+
[FormTypes.RADIO](config) {
|
|
64
|
+
return this[FormTypes.SWITCH](config);
|
|
65
|
+
},
|
|
66
|
+
[FormTypes.CHECKBOX](config) {
|
|
67
|
+
const props: any = {};
|
|
68
|
+
props.options = config.options.split(',').map((item: any) => {
|
|
69
|
+
const [label, value] = item.split(':');
|
|
70
|
+
return { value, label };
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
config.value = config.value.split(',');
|
|
74
|
+
config.options = props;
|
|
75
|
+
|
|
76
|
+
return config;
|
|
77
|
+
},
|
|
78
|
+
[FormTypes.SINGLE_SELECT](config) {
|
|
79
|
+
return this[FormTypes.SWITCH](config);
|
|
80
|
+
},
|
|
81
|
+
[FormTypes.MULTIPLE_SELECT](config) {
|
|
82
|
+
return this[FormTypes.CHECKBOX](config);
|
|
83
|
+
},
|
|
84
|
+
[FormTypes.SINGLE_DATE_PICKER](config) {
|
|
85
|
+
const props: DatePickerProps = {};
|
|
86
|
+
|
|
87
|
+
if (config.options) {
|
|
88
|
+
const [minDate, maxDate] = (config.options || '/').split('/').map(dayjs);
|
|
89
|
+
props.disabledDate = (current) => {
|
|
90
|
+
return current.isBefore(minDate) || current.isAfter(maxDate);
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
config.value = config.value === '' ? undefined : dayjs(config.value);
|
|
95
|
+
config.options = props;
|
|
96
|
+
|
|
97
|
+
return config;
|
|
98
|
+
},
|
|
99
|
+
[FormTypes.RANGE_DATE_PICKER](config) {
|
|
100
|
+
const props: DatePickerProps = {};
|
|
101
|
+
|
|
102
|
+
if (config.options) {
|
|
103
|
+
const [minDate, maxDate] = (config.options || '/').split('/').map(dayjs);
|
|
104
|
+
props.disabledDate = (current) => {
|
|
105
|
+
return current.isBefore(minDate) || current.isAfter(maxDate);
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (config.value === '')
|
|
110
|
+
config.value = [undefined, undefined];
|
|
111
|
+
else
|
|
112
|
+
config.value = (config.value || '/').split('/').map(dayjs);
|
|
113
|
+
|
|
114
|
+
config.options = props;
|
|
115
|
+
|
|
116
|
+
return config;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const antdValueResolvers: Record<number, (value: any) => any> = {
|
|
121
|
+
[FormTypes.NUMBER_INPUT](value) {
|
|
122
|
+
return String(value);
|
|
123
|
+
},
|
|
124
|
+
[FormTypes.CHECKBOX](value) {
|
|
125
|
+
if (Array.isArray(value))
|
|
126
|
+
return value.join(',');
|
|
127
|
+
|
|
128
|
+
return value;
|
|
129
|
+
},
|
|
130
|
+
[FormTypes.MULTIPLE_SELECT](value) {
|
|
131
|
+
if (Array.isArray(value))
|
|
132
|
+
return value.join(',');
|
|
133
|
+
|
|
134
|
+
return value;
|
|
135
|
+
},
|
|
136
|
+
[FormTypes.SINGLE_DATE_PICKER](value) {
|
|
137
|
+
if (dayjs.isDayjs(value))
|
|
138
|
+
return value.format('YYYY-MM-DD');
|
|
139
|
+
|
|
140
|
+
return value;
|
|
141
|
+
},
|
|
142
|
+
[FormTypes.RANGE_DATE_PICKER](value) {
|
|
143
|
+
if (dayjs.isDayjs(value[0]) && dayjs.isDayjs(value[1]))
|
|
144
|
+
return [value[0].format('YYYY-MM-DD'), value[1].format('YYYY-MM-DD')].join('/');
|
|
145
|
+
|
|
146
|
+
return value;
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
</script>
|
|
150
|
+
|
|
151
|
+
<script setup lang="ts">
|
|
152
|
+
const props = withDefaults(defineProps<{
|
|
153
|
+
formCofnig: IConfigDetail[]
|
|
154
|
+
disabled?: boolean
|
|
155
|
+
}>(), {
|
|
156
|
+
disabled: false,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const configList = computed(() => {
|
|
160
|
+
const list = cloneDeep(props.formCofnig);
|
|
161
|
+
|
|
162
|
+
if (!list?.length)
|
|
163
|
+
return;
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < list.length; i++) {
|
|
166
|
+
const config = { ...list[i] };
|
|
167
|
+
list[i] = antdPropsResolvers[config.form_type]?.(config) ?? config;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return list;
|
|
171
|
+
});
|
|
172
|
+
const configMap = computed(() => mapKeys(cloneDeep(props.formCofnig ?? []), 'key'));
|
|
173
|
+
function formSchema() {
|
|
174
|
+
const _configList = configList.value ?? [];
|
|
175
|
+
const entries = _configList.map(config => [
|
|
176
|
+
config.key,
|
|
177
|
+
{
|
|
178
|
+
value: cloneDeep(config.value),
|
|
179
|
+
rules: config.required ? [{ required: true }] : undefined,
|
|
180
|
+
},
|
|
181
|
+
] as [string, ItemSchema]);
|
|
182
|
+
const schema = Object.fromEntries(entries);
|
|
183
|
+
|
|
184
|
+
return schema;
|
|
185
|
+
}
|
|
186
|
+
const settingForm = useAntdForm(formSchema, {
|
|
187
|
+
transform(state) {
|
|
188
|
+
return mapValues(state, (v, k) => {
|
|
189
|
+
const formType = configMap.value[k].form_type;
|
|
190
|
+
const resolver = antdValueResolvers[formType];
|
|
191
|
+
|
|
192
|
+
return resolver ? resolver(v) : v;
|
|
193
|
+
});
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
function reset() {
|
|
198
|
+
configList.value?.forEach(config => settingForm.state[config.key] = cloneDeep(config.value));
|
|
199
|
+
settingForm.$form.clearValidate?.();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function validate() {
|
|
203
|
+
const data = await settingForm.$form.validate?.();
|
|
204
|
+
return data ? settingForm.stateTF : undefined;
|
|
205
|
+
// return undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
defineExpose({ reset, validate });
|
|
209
|
+
</script>
|
|
210
|
+
|
|
211
|
+
<template>
|
|
212
|
+
<Form
|
|
213
|
+
v-bind="settingForm.props"
|
|
214
|
+
class="ant-cover__col2-form"
|
|
215
|
+
:model="settingForm.state"
|
|
216
|
+
layout="vertical"
|
|
217
|
+
:disabled="props.disabled"
|
|
218
|
+
>
|
|
219
|
+
<template v-for="item of configList" :key="item.key">
|
|
220
|
+
<FormItem v-bind="settingForm.itemProps[item.key]" :label="item.label" :extra="item.summary">
|
|
221
|
+
<slot
|
|
222
|
+
v-if="$slots[item.key]"
|
|
223
|
+
:name="item.key"
|
|
224
|
+
:state="toRef(settingForm.state, item.key)"
|
|
225
|
+
:config="item"
|
|
226
|
+
:orginConfig="configMap[item.key]"
|
|
227
|
+
/>
|
|
228
|
+
<template v-else>
|
|
229
|
+
<slot v-if="item.form_type === FormTypes.NUMBER_INPUT" :name="FormTypes.NUMBER_INPUT">
|
|
230
|
+
<InputNumber v-model:value="settingForm.state[item.key]" class="w-full" />
|
|
231
|
+
</slot>
|
|
232
|
+
|
|
233
|
+
<slot v-else-if="item.form_type === FormTypes.TEXT_INPUT" :name="FormTypes.TEXT_INPUT">
|
|
234
|
+
<Input v-model:value="settingForm.state[item.key]" allowClear />
|
|
235
|
+
</slot>
|
|
236
|
+
|
|
237
|
+
<slot v-else-if="item.form_type === FormTypes.TEXTAREA" :name="FormTypes.TEXTAREA">
|
|
238
|
+
<Textarea v-model:value="settingForm.state[item.key]" :rows="4" />
|
|
239
|
+
</slot>
|
|
240
|
+
|
|
241
|
+
<slot v-else-if="item.form_type === FormTypes.SWITCH" :name="FormTypes.SWITCH">
|
|
242
|
+
<RadioGroup
|
|
243
|
+
v-model:value="settingForm.state[item.key]"
|
|
244
|
+
v-bind="item.options"
|
|
245
|
+
buttonStyle="solid"
|
|
246
|
+
optionType="button"
|
|
247
|
+
/>
|
|
248
|
+
</slot>
|
|
249
|
+
|
|
250
|
+
<slot v-else-if="item.form_type === FormTypes.RADIO" :name="FormTypes.RADIO">
|
|
251
|
+
<RadioGroup
|
|
252
|
+
v-model:value="settingForm.state[item.key]"
|
|
253
|
+
buttonStyle="solid"
|
|
254
|
+
v-bind="item.options"
|
|
255
|
+
/>
|
|
256
|
+
</slot>
|
|
257
|
+
|
|
258
|
+
<slot v-else-if="item.form_type === FormTypes.CHECKBOX" :name="FormTypes.CHECKBOX">
|
|
259
|
+
<CheckboxGroup v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
260
|
+
</slot>
|
|
261
|
+
|
|
262
|
+
<slot v-else-if="item.form_type === FormTypes.SINGLE_SELECT" :name="FormTypes.SINGLE_SELECT">
|
|
263
|
+
<Select v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
264
|
+
</slot>
|
|
265
|
+
|
|
266
|
+
<slot v-else-if="item.form_type === FormTypes.MULTIPLE_SELECT" :name="FormTypes.MULTIPLE_SELECT">
|
|
267
|
+
<Select v-model:value="settingForm.state[item.key]" v-bind="item.options" mode="multiple" />
|
|
268
|
+
</slot>
|
|
269
|
+
|
|
270
|
+
<slot v-else-if="item.form_type === FormTypes.SINGLE_DATE_PICKER" :name="FormTypes.SINGLE_DATE_PICKER">
|
|
271
|
+
<DatePicker v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
272
|
+
</slot>
|
|
273
|
+
|
|
274
|
+
<slot v-else-if="item.form_type === FormTypes.RANGE_DATE_PICKER" :name="FormTypes.RANGE_DATE_PICKER">
|
|
275
|
+
<RangePicker v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
276
|
+
</slot>
|
|
277
|
+
|
|
278
|
+
<span v-else class="text-red">没有找到预设 form_type:{{ item.form_type }},可以通过 {{ item.key }} 插槽自定义</span>
|
|
279
|
+
</template>
|
|
280
|
+
</FormItem>
|
|
281
|
+
</template>
|
|
282
|
+
</Form>
|
|
283
|
+
</template>
|
|
@@ -1,156 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { computed, ref, toRef } from 'vue';
|
|
2
|
+
import { Button, Card } from 'ant-design-vue';
|
|
3
|
+
import { computed, ref } from 'vue';
|
|
5
4
|
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query';
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
interface IConfigDetail {
|
|
10
|
-
category_id: number
|
|
11
|
-
data_type: string
|
|
12
|
-
form_type: number
|
|
13
|
-
key: string
|
|
14
|
-
label: string
|
|
15
|
-
options: any
|
|
16
|
-
required: number
|
|
17
|
-
sort: number
|
|
18
|
-
status: number
|
|
19
|
-
summary: string
|
|
20
|
-
value: any
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
enum FormTypes {
|
|
24
|
-
/** 数字输入框 */
|
|
25
|
-
NUMBER_INPUT = 0,
|
|
26
|
-
/** 文本输入框 */
|
|
27
|
-
TEXT_INPUT = 1,
|
|
28
|
-
/** 多行文本输入框 */
|
|
29
|
-
TEXTAREA = 2,
|
|
30
|
-
/** 开关 */
|
|
31
|
-
SWITCH = 3,
|
|
32
|
-
/** 单选框 */
|
|
33
|
-
RADIO = 4,
|
|
34
|
-
/** 多选框 */
|
|
35
|
-
CHECKBOX = 5,
|
|
36
|
-
/** 单选下拉选择器 */
|
|
37
|
-
SINGLE_SELECT = 6,
|
|
38
|
-
/** 多选下拉选择器 */
|
|
39
|
-
MULTIPLE_SELECT = 7,
|
|
40
|
-
/** 单一日期选择器 */
|
|
41
|
-
SINGLE_DATE_PICKER = 13,
|
|
42
|
-
/** 日期范围选择器 */
|
|
43
|
-
RANGE_DATE_PICKER = 14,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const antdPropsResolvers: Record<number, (config: IConfigDetail) => IConfigDetail> = {
|
|
47
|
-
[FormTypes.SWITCH](config) {
|
|
48
|
-
const props: any = {};
|
|
49
|
-
|
|
50
|
-
if (config.options) {
|
|
51
|
-
props.options = config.options.split(',').map((item: any) => {
|
|
52
|
-
const [label, value] = item.split(':');
|
|
53
|
-
return { value, label };
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
props.options = [{ value: '1', label: '是' }, { value: '0', label: '否' }];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
config.options = props;
|
|
61
|
-
|
|
62
|
-
return config;
|
|
63
|
-
},
|
|
64
|
-
[FormTypes.RADIO](config) {
|
|
65
|
-
return this[FormTypes.SWITCH](config);
|
|
66
|
-
},
|
|
67
|
-
[FormTypes.CHECKBOX](config) {
|
|
68
|
-
const props: any = {};
|
|
69
|
-
props.options = config.options.split(',').map((item: any) => {
|
|
70
|
-
const [label, value] = item.split(':');
|
|
71
|
-
return { value, label };
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
config.value = config.value.split(',');
|
|
75
|
-
config.options = props;
|
|
76
|
-
|
|
77
|
-
return config;
|
|
78
|
-
},
|
|
79
|
-
[FormTypes.SINGLE_SELECT](config) {
|
|
80
|
-
return this[FormTypes.SWITCH](config);
|
|
81
|
-
},
|
|
82
|
-
[FormTypes.MULTIPLE_SELECT](config) {
|
|
83
|
-
return this[FormTypes.CHECKBOX](config);
|
|
84
|
-
},
|
|
85
|
-
[FormTypes.SINGLE_DATE_PICKER](config) {
|
|
86
|
-
const props: DatePickerProps = {};
|
|
87
|
-
|
|
88
|
-
if (config.options) {
|
|
89
|
-
const [minDate, maxDate] = (config.options || '/').split('/').map(dayjs);
|
|
90
|
-
props.disabledDate = (current) => {
|
|
91
|
-
return current.isBefore(minDate) || current.isAfter(maxDate);
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
config.value = config.value === '' ? undefined : dayjs(config.value);
|
|
96
|
-
config.options = props;
|
|
97
|
-
|
|
98
|
-
return config;
|
|
99
|
-
},
|
|
100
|
-
[FormTypes.RANGE_DATE_PICKER](config) {
|
|
101
|
-
const props: DatePickerProps = {};
|
|
102
|
-
|
|
103
|
-
if (config.options) {
|
|
104
|
-
const [minDate, maxDate] = (config.options || '/').split('/').map(dayjs);
|
|
105
|
-
props.disabledDate = (current) => {
|
|
106
|
-
return current.isBefore(minDate) || current.isAfter(maxDate);
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (config.value === '')
|
|
111
|
-
config.value = [undefined, undefined];
|
|
112
|
-
else
|
|
113
|
-
config.value = (config.value || '/').split('/').map(dayjs);
|
|
114
|
-
|
|
115
|
-
config.options = props;
|
|
116
|
-
|
|
117
|
-
return config;
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
const antdValueResolvers: Record<number, (value: any) => any> = {
|
|
121
|
-
[FormTypes.NUMBER_INPUT](value) {
|
|
122
|
-
return String(value);
|
|
123
|
-
},
|
|
124
|
-
[FormTypes.CHECKBOX](value) {
|
|
125
|
-
if (Array.isArray(value))
|
|
126
|
-
return value.join(',');
|
|
127
|
-
|
|
128
|
-
return value;
|
|
129
|
-
},
|
|
130
|
-
[FormTypes.MULTIPLE_SELECT](value) {
|
|
131
|
-
if (Array.isArray(value))
|
|
132
|
-
return value.join(',');
|
|
133
|
-
|
|
134
|
-
return value;
|
|
135
|
-
},
|
|
136
|
-
[FormTypes.SINGLE_DATE_PICKER](value) {
|
|
137
|
-
if (dayjs.isDayjs(value))
|
|
138
|
-
return value.format('YYYY-MM-DD');
|
|
139
|
-
|
|
140
|
-
return value;
|
|
141
|
-
},
|
|
142
|
-
[FormTypes.RANGE_DATE_PICKER](value) {
|
|
143
|
-
if (dayjs.isDayjs(value[0]) && dayjs.isDayjs(value[1]))
|
|
144
|
-
return [value[0].format('YYYY-MM-DD'), value[1].format('YYYY-MM-DD')].join('/');
|
|
145
|
-
|
|
146
|
-
return value;
|
|
147
|
-
},
|
|
148
|
-
};
|
|
5
|
+
import { useTemplateRefs } from '../../../../vue';
|
|
6
|
+
import type SchemaForm from './SchemaForm.vue';
|
|
7
|
+
import type { IConfigDetail } from './SchemaForm.vue';
|
|
149
8
|
</script>
|
|
150
9
|
|
|
151
10
|
<script setup lang="ts">
|
|
152
11
|
const props = withDefaults(defineProps<{
|
|
153
|
-
categoryApi
|
|
12
|
+
categoryApi?: Api.Request
|
|
154
13
|
configApi: Api.Request
|
|
155
14
|
updateApi: Api.Request
|
|
156
15
|
readonly?: boolean
|
|
@@ -159,35 +18,25 @@ const props = withDefaults(defineProps<{
|
|
|
159
18
|
});
|
|
160
19
|
|
|
161
20
|
const queryClient = useQueryClient();
|
|
21
|
+
const [refs, setRefs] = useTemplateRefs<{
|
|
22
|
+
form: typeof SchemaForm
|
|
23
|
+
}>();
|
|
162
24
|
|
|
163
25
|
/* 配置分类 */
|
|
164
26
|
const categoryId = ref(1);
|
|
165
27
|
const categoryQuerier = useQuery({
|
|
166
|
-
queryKey: [props.categoryApi
|
|
167
|
-
queryFn: () => props.categoryApi(undefined),
|
|
28
|
+
queryKey: [props.categoryApi?.id, 'system-setting-category'],
|
|
29
|
+
queryFn: () => props.categoryApi?.(undefined) ?? null,
|
|
168
30
|
});
|
|
169
31
|
const categoryList = computed(() => categoryQuerier.data.value?.map((item: any) => ({ key: String(item.type), tab: item.label })));
|
|
170
32
|
|
|
171
33
|
/* 当前分类下的配置 */
|
|
34
|
+
const configQuerierEnabled = computed(() => categoryQuerier.data.value !== undefined);
|
|
172
35
|
const configQuerier = useQuery<IConfigDetail[] | undefined>({
|
|
173
|
-
enabled:
|
|
36
|
+
enabled: configQuerierEnabled,
|
|
174
37
|
queryKey: [props.configApi.id, categoryId],
|
|
175
38
|
queryFn: () => props.configApi({ category_id: categoryId.value }),
|
|
176
39
|
});
|
|
177
|
-
const configList = computed(() => {
|
|
178
|
-
const list = cloneDeep(configQuerier.data.value);
|
|
179
|
-
|
|
180
|
-
if (!list?.length)
|
|
181
|
-
return;
|
|
182
|
-
|
|
183
|
-
for (let i = 0; i < list.length; i++) {
|
|
184
|
-
const config = { ...list[i] };
|
|
185
|
-
list[i] = antdPropsResolvers[config.form_type]?.(config) ?? config;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
return list;
|
|
189
|
-
});
|
|
190
|
-
const configMap = computed(() => mapKeys(cloneDeep(configQuerier.data.value ?? []), 'key'));
|
|
191
40
|
|
|
192
41
|
/* 配置表单 */
|
|
193
42
|
const settingMutator = useMutation({
|
|
@@ -198,42 +47,12 @@ const settingMutator = useMutation({
|
|
|
198
47
|
queryClient.invalidateQueries({ queryKey: [props.configApi.id], exact: false });
|
|
199
48
|
},
|
|
200
49
|
});
|
|
201
|
-
const formSchema = computed(() => {
|
|
202
|
-
const _configList = configList.value ?? [];
|
|
203
|
-
const entries = _configList.map(config => [
|
|
204
|
-
config.key,
|
|
205
|
-
{
|
|
206
|
-
value: cloneDeep(config.value),
|
|
207
|
-
rules: config.required ? [{ required: true }] : undefined,
|
|
208
|
-
},
|
|
209
|
-
] as [string, ItemSchema]);
|
|
210
|
-
const schema = Object.fromEntries(entries);
|
|
211
|
-
|
|
212
|
-
return schema;
|
|
213
|
-
});
|
|
214
|
-
const settingForm = useAntdForm(formSchema, {
|
|
215
|
-
transform(state) {
|
|
216
|
-
return mapValues(state, (v, k) => {
|
|
217
|
-
const formType = configMap.value[k].form_type;
|
|
218
|
-
const resolver = antdValueResolvers[formType];
|
|
219
|
-
|
|
220
|
-
return resolver ? resolver(v) : v;
|
|
221
|
-
});
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
50
|
|
|
225
51
|
/** 提交表单内容 */
|
|
226
52
|
async function submitSetting() {
|
|
227
|
-
const body = await
|
|
53
|
+
const body = await refs.form?.validate();
|
|
228
54
|
|
|
229
|
-
|
|
230
|
-
await settingMutator.mutateAsync({ requestBody: settingForm.stateTF });
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/** 重置表单内容 */
|
|
234
|
-
function resetSetting() {
|
|
235
|
-
configList.value?.forEach(config => settingForm.state[config.key] = cloneDeep(config.value));
|
|
236
|
-
settingForm.$form.clearValidate?.();
|
|
55
|
+
body && await settingMutator.mutateAsync({ requestBody: body });
|
|
237
56
|
}
|
|
238
57
|
</script>
|
|
239
58
|
|
|
@@ -245,79 +64,16 @@ function resetSetting() {
|
|
|
245
64
|
:loading="configQuerier.isPending.value"
|
|
246
65
|
@tabChange="key => categoryId = Number(key)"
|
|
247
66
|
>
|
|
248
|
-
<
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
<FormItem v-bind="settingForm.itemProps[item.key]" :label="item.label" :extra="item.summary">
|
|
256
|
-
<slot
|
|
257
|
-
v-if="$slots[item.key]"
|
|
258
|
-
:name="item.key"
|
|
259
|
-
:state="toRef(settingForm.state, item.key)"
|
|
260
|
-
:config="item"
|
|
261
|
-
:orginConfig="configMap[item.key]"
|
|
262
|
-
/>
|
|
263
|
-
<template v-else>
|
|
264
|
-
<slot v-if="item.form_type === FormTypes.NUMBER_INPUT" :name="FormTypes.NUMBER_INPUT">
|
|
265
|
-
<InputNumber v-model:value="settingForm.state[item.key]" class="w-full" />
|
|
266
|
-
</slot>
|
|
267
|
-
|
|
268
|
-
<slot v-else-if="item.form_type === FormTypes.TEXT_INPUT" :name="FormTypes.TEXT_INPUT">
|
|
269
|
-
<Input v-model:value="settingForm.state[item.key]" allowClear />
|
|
270
|
-
</slot>
|
|
271
|
-
|
|
272
|
-
<slot v-else-if="item.form_type === FormTypes.TEXTAREA" :name="FormTypes.TEXTAREA">
|
|
273
|
-
<Textarea v-model:value="settingForm.state[item.key]" :rows="4" />
|
|
274
|
-
</slot>
|
|
275
|
-
|
|
276
|
-
<slot v-else-if="item.form_type === FormTypes.SWITCH" :name="FormTypes.SWITCH">
|
|
277
|
-
<RadioGroup
|
|
278
|
-
v-model:value="settingForm.state[item.key]"
|
|
279
|
-
v-bind="item.options"
|
|
280
|
-
buttonStyle="solid"
|
|
281
|
-
optionType="button"
|
|
282
|
-
/>
|
|
283
|
-
</slot>
|
|
284
|
-
|
|
285
|
-
<slot v-else-if="item.form_type === FormTypes.RADIO" :name="FormTypes.RADIO">
|
|
286
|
-
<RadioGroup
|
|
287
|
-
v-model:value="settingForm.state[item.key]"
|
|
288
|
-
buttonStyle="solid"
|
|
289
|
-
v-bind="item.options"
|
|
290
|
-
/>
|
|
291
|
-
</slot>
|
|
292
|
-
|
|
293
|
-
<slot v-else-if="item.form_type === FormTypes.CHECKBOX" :name="FormTypes.CHECKBOX">
|
|
294
|
-
<CheckboxGroup v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
295
|
-
</slot>
|
|
296
|
-
|
|
297
|
-
<slot v-else-if="item.form_type === FormTypes.SINGLE_SELECT" :name="FormTypes.SINGLE_SELECT">
|
|
298
|
-
<Select v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
299
|
-
</slot>
|
|
300
|
-
|
|
301
|
-
<slot v-else-if="item.form_type === FormTypes.MULTIPLE_SELECT" :name="FormTypes.MULTIPLE_SELECT">
|
|
302
|
-
<Select v-model:value="settingForm.state[item.key]" v-bind="item.options" mode="multiple" />
|
|
303
|
-
</slot>
|
|
304
|
-
|
|
305
|
-
<slot v-else-if="item.form_type === FormTypes.SINGLE_DATE_PICKER" :name="FormTypes.SINGLE_DATE_PICKER">
|
|
306
|
-
<DatePicker v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
307
|
-
</slot>
|
|
308
|
-
|
|
309
|
-
<slot v-else-if="item.form_type === FormTypes.RANGE_DATE_PICKER" :name="FormTypes.RANGE_DATE_PICKER">
|
|
310
|
-
<RangePicker v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
311
|
-
</slot>
|
|
312
|
-
|
|
313
|
-
<span v-else class="text-red">没有找到预设 form_type:{{ item.form_type }},可以通过 {{ item.key }} 插槽自定义</span>
|
|
314
|
-
</template>
|
|
315
|
-
</FormItem>
|
|
316
|
-
</template>
|
|
317
|
-
</Form>
|
|
67
|
+
<component
|
|
68
|
+
:is="$slots.default?.({
|
|
69
|
+
formCofnig: configQuerier.data.value ?? [],
|
|
70
|
+
disabled: settingMutator.isPending.value || props.readonly,
|
|
71
|
+
})[0]"
|
|
72
|
+
:ref="setRefs.form"
|
|
73
|
+
/>
|
|
318
74
|
|
|
319
75
|
<template v-if="!props.readonly" #actions>
|
|
320
|
-
<Button :disabled="settingMutator.isPending.value" @click="
|
|
76
|
+
<Button :disabled="settingMutator.isPending.value" @click="refs.form?.reset()">
|
|
321
77
|
重置
|
|
322
78
|
</Button>
|
|
323
79
|
<Button type="primary" :loading="settingMutator.isPending.value" @click="submitSetting()">
|
|
@@ -326,10 +82,3 @@ function resetSetting() {
|
|
|
326
82
|
</template>
|
|
327
83
|
</Card>
|
|
328
84
|
</template>
|
|
329
|
-
|
|
330
|
-
<style lang="scss" scoped>
|
|
331
|
-
.ant-cover__col2-form :deep(.ant-form-item-label > label) {
|
|
332
|
-
// padding-left: 0.5em;
|
|
333
|
-
// border-left: 2px solid #2361d0;
|
|
334
|
-
}
|
|
335
|
-
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { computed } from 'vue';
|
|
2
|
+
import { computed, reactive } from 'vue';
|
|
3
3
|
import bigNumber from 'bignumber.js';
|
|
4
4
|
import isNil from 'lodash-es/isNil';
|
|
5
5
|
|
|
@@ -11,7 +11,7 @@ export const config = {
|
|
|
11
11
|
/**
|
|
12
12
|
* 当 symbol 为以下值时,使用预设的 Logo
|
|
13
13
|
*/
|
|
14
|
-
const presetSymbols: Record<string, string> = {
|
|
14
|
+
const presetSymbols: Record<string, string> = reactive({
|
|
15
15
|
USDT: 'https://api.iconify.design/cryptocurrency-color:usdt.svg',
|
|
16
16
|
TRX: 'https://api.iconify.design/cryptocurrency-color:trx.svg',
|
|
17
17
|
USDC: 'https://api.iconify.design/cryptocurrency-color:usdc.svg',
|
|
@@ -20,7 +20,11 @@ const presetSymbols: Record<string, string> = {
|
|
|
20
20
|
BUSD: 'https://assets.coingecko.com/coins/images/9576/large/BUSD.png',
|
|
21
21
|
MATIC: 'https://api.iconify.design/cryptocurrency-color:matic.svg',
|
|
22
22
|
SOL: 'https://api.iconify.design/cryptocurrency-color:sol.svg',
|
|
23
|
-
};
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export function setConfig(symbols: Record<string, string>) {
|
|
26
|
+
Object.assign(presetSymbols, symbols);
|
|
27
|
+
}
|
|
24
28
|
</script>
|
|
25
29
|
|
|
26
30
|
<script setup lang="ts">
|
|
@@ -87,7 +87,7 @@ function useAntdForm<S extends Record<string, unknown>, TS = S>(schemas: MaybeRe
|
|
|
87
87
|
|
|
88
88
|
for (const k in schemasR.value) {
|
|
89
89
|
const item = schemasR.value[k];
|
|
90
|
-
_state[k] = (k in state.value) ? toRef(state.value, k) :
|
|
90
|
+
_state[k] = (k in state.value) ? toRef(state.value, k) : ref(item.value);
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
state.value = _state;
|
package/package.json
CHANGED
package/utils/number.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { calc } from 'a-calc';
|
|
2
|
+
import type { BigNumber } from 'bignumber.js';
|
|
3
|
+
import bn from 'bignumber.js';
|
|
2
4
|
|
|
3
5
|
export { default as bn } from 'bignumber.js';
|
|
4
6
|
export { calc } from 'a-calc';
|
|
@@ -47,3 +49,21 @@ export function toAmount(amount: any, decimal: string | number = 0) {
|
|
|
47
49
|
|
|
48
50
|
return fmt;
|
|
49
51
|
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 乘以精度,即将小数金额转换为整数金额
|
|
55
|
+
* @param value 数值
|
|
56
|
+
* @param precision 精度
|
|
57
|
+
*/
|
|
58
|
+
export function mulPrecision(value: BigNumber.Value, precision: number) {
|
|
59
|
+
return bn(value).times(bn(10).pow(precision));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 除以精度,即将整数金额转换为小数金额
|
|
64
|
+
* @param value 数值
|
|
65
|
+
* @param precision 精度
|
|
66
|
+
*/
|
|
67
|
+
export function divPrecision(value: BigNumber.Value, precision: number) {
|
|
68
|
+
return bn(value).div(bn(10).pow(precision));
|
|
69
|
+
}
|