@peng_kai/kit 0.2.0-beta.3 → 0.2.0-beta.31
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/adminPlugin.ts +11 -1
- package/admin/components/filter/src/FilterParam.vue +1 -1
- package/admin/components/filter/src/FilterReset.vue +12 -9
- package/admin/components/rich-text/index.ts +1 -1
- package/admin/components/rich-text/src/RichText.new.vue +164 -0
- package/admin/components/rich-text/src/RichText.vue +3 -6
- package/admin/components/rich-text/src/editorConfig.ts +126 -0
- package/admin/components/rich-text/src/imageUploader.ts +20 -18
- package/admin/components/scroll-nav/src/ScrollNav.vue +1 -1
- package/admin/components/settings/index.ts +1 -0
- package/admin/components/settings/src/Settings.vue +333 -0
- package/admin/components/text/src/Datetime.vue +1 -1
- package/admin/components/upload/index.ts +1 -0
- package/admin/components/upload/src/PictureCardUpload.vue +1 -1
- package/admin/components/upload/src/helpers.ts +37 -0
- package/admin/defines/route/defineRoute.ts +2 -4
- package/admin/defines/route/getRoutes.ts +4 -4
- package/admin/defines/route/index.ts +1 -1
- package/admin/defines/route-guard/defineRouteGuard.ts +1 -4
- package/admin/defines/route-guard/getRouteGuards.ts +5 -7
- package/admin/defines/startup/defineStartup.ts +1 -3
- package/admin/defines/startup/runStartup.ts +4 -6
- package/admin/layout/large/Breadcrumb.vue +2 -2
- package/admin/layout/large/Content.vue +2 -2
- package/admin/layout/large/Menu.vue +2 -2
- package/admin/layout/large/PageTab.vue +2 -2
- package/admin/permission/routerGuard.ts +1 -1
- package/admin/permission/vuePlugin.ts +1 -0
- package/admin/stores/createUsePageStore.ts +1 -3
- package/admin/styles/classCover.scss +123 -3
- package/admin/styles/index.scss +14 -12
- package/antd/hooks/useAntdForm.ts +1 -1
- package/antd/hooks/useAntdModal.ts +4 -1
- package/antd/hooks/useAntdTable.ts +2 -1
- package/libs/dayjs.ts +7 -0
- package/libs/echarts.ts +1 -1
- package/libs/vue-i18n.ts +21 -0
- package/package.json +36 -35
- package/request/interceptors/returnResultType.ts +3 -3
- package/request/interceptors/toLogin.ts +27 -10
- package/request/request.ts +0 -3
- package/request/type.d.ts +4 -4
- package/utils/LocaleManager.ts +125 -0
- package/utils/date.ts +1 -9
- package/vite/index.mjs +34 -8
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { reactiveComputed } from '@vueuse/core';
|
|
3
|
+
import { cloneDeep, mapKeys, mapValues } from 'lodash-es';
|
|
4
|
+
import dayjs from 'dayjs';
|
|
5
|
+
import { Button, Card, CheckboxGroup, DatePicker, type DatePickerProps, Form, FormItem, Input, InputNumber, RadioGroup, RangePicker, Select, Textarea } from 'ant-design-vue';
|
|
6
|
+
import { computed, ref, toRef } from 'vue';
|
|
7
|
+
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query';
|
|
8
|
+
import { type ItemSchema, useAntdForm } from '../../../../antd';
|
|
9
|
+
|
|
10
|
+
interface IConfigDetail {
|
|
11
|
+
category_id: number
|
|
12
|
+
data_type: string
|
|
13
|
+
form_type: number
|
|
14
|
+
key: string
|
|
15
|
+
label: string
|
|
16
|
+
options: any
|
|
17
|
+
required: number
|
|
18
|
+
sort: number
|
|
19
|
+
status: number
|
|
20
|
+
summary: string
|
|
21
|
+
value: any
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
enum FormTypes {
|
|
25
|
+
/** 数字输入框 */
|
|
26
|
+
NUMBER_INPUT = 0,
|
|
27
|
+
/** 文本输入框 */
|
|
28
|
+
TEXT_INPUT = 1,
|
|
29
|
+
/** 多行文本输入框 */
|
|
30
|
+
TEXTAREA = 2,
|
|
31
|
+
/** 开关 */
|
|
32
|
+
SWITCH = 3,
|
|
33
|
+
/** 单选框 */
|
|
34
|
+
RADIO = 4,
|
|
35
|
+
/** 多选框 */
|
|
36
|
+
CHECKBOX = 5,
|
|
37
|
+
/** 单选下拉选择器 */
|
|
38
|
+
SINGLE_SELECT = 6,
|
|
39
|
+
/** 多选下拉选择器 */
|
|
40
|
+
MULTIPLE_SELECT = 7,
|
|
41
|
+
/** 单一日期选择器 */
|
|
42
|
+
SINGLE_DATE_PICKER = 13,
|
|
43
|
+
/** 日期范围选择器 */
|
|
44
|
+
RANGE_DATE_PICKER = 14,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const antdPropsResolvers: Record<number, (config: IConfigDetail) => IConfigDetail> = {
|
|
48
|
+
[FormTypes.SWITCH](config) {
|
|
49
|
+
const props: any = {};
|
|
50
|
+
|
|
51
|
+
if (config.options) {
|
|
52
|
+
props.options = config.options.split(',').map((item: any) => {
|
|
53
|
+
const [label, value] = item.split(':');
|
|
54
|
+
return { value, label };
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
props.options = [{ value: '1', label: '是' }, { value: '0', label: '否' }];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
config.options = props;
|
|
62
|
+
|
|
63
|
+
return config;
|
|
64
|
+
},
|
|
65
|
+
[FormTypes.RADIO](config) {
|
|
66
|
+
return this[FormTypes.SWITCH](config);
|
|
67
|
+
},
|
|
68
|
+
[FormTypes.CHECKBOX](config) {
|
|
69
|
+
const props: any = {};
|
|
70
|
+
props.options = config.options.split(',').map((item: any) => {
|
|
71
|
+
const [label, value] = item.split(':');
|
|
72
|
+
return { value, label };
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
config.value = config.value.split(',');
|
|
76
|
+
config.options = props;
|
|
77
|
+
|
|
78
|
+
return config;
|
|
79
|
+
},
|
|
80
|
+
[FormTypes.SINGLE_SELECT](config) {
|
|
81
|
+
return this[FormTypes.SWITCH](config);
|
|
82
|
+
},
|
|
83
|
+
[FormTypes.MULTIPLE_SELECT](config) {
|
|
84
|
+
return this[FormTypes.CHECKBOX](config);
|
|
85
|
+
},
|
|
86
|
+
[FormTypes.SINGLE_DATE_PICKER](config) {
|
|
87
|
+
const props: DatePickerProps = {};
|
|
88
|
+
|
|
89
|
+
if (config.options) {
|
|
90
|
+
const [minDate, maxDate] = (config.options || '/').split('/').map(dayjs);
|
|
91
|
+
props.disabledDate = (current) => {
|
|
92
|
+
return current.isBefore(minDate) || current.isAfter(maxDate);
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
config.value = config.value === '' ? undefined : dayjs(config.value);
|
|
97
|
+
config.options = props;
|
|
98
|
+
|
|
99
|
+
return config;
|
|
100
|
+
},
|
|
101
|
+
[FormTypes.RANGE_DATE_PICKER](config) {
|
|
102
|
+
const props: DatePickerProps = {};
|
|
103
|
+
|
|
104
|
+
if (config.options) {
|
|
105
|
+
const [minDate, maxDate] = (config.options || '/').split('/').map(dayjs);
|
|
106
|
+
props.disabledDate = (current) => {
|
|
107
|
+
return current.isBefore(minDate) || current.isAfter(maxDate);
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (config.value === '')
|
|
112
|
+
config.value = [undefined, undefined];
|
|
113
|
+
else
|
|
114
|
+
config.value = (config.value || '/').split('/').map(dayjs);
|
|
115
|
+
|
|
116
|
+
config.options = props;
|
|
117
|
+
|
|
118
|
+
return config;
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
const antdValueResolvers: Record<number, (value: any) => any> = {
|
|
122
|
+
[FormTypes.NUMBER_INPUT](value) {
|
|
123
|
+
return String(value);
|
|
124
|
+
},
|
|
125
|
+
[FormTypes.CHECKBOX](value) {
|
|
126
|
+
if (Array.isArray(value))
|
|
127
|
+
return value.join(',');
|
|
128
|
+
|
|
129
|
+
return value;
|
|
130
|
+
},
|
|
131
|
+
[FormTypes.MULTIPLE_SELECT](value) {
|
|
132
|
+
if (Array.isArray(value))
|
|
133
|
+
return value.join(',');
|
|
134
|
+
|
|
135
|
+
return value;
|
|
136
|
+
},
|
|
137
|
+
[FormTypes.SINGLE_DATE_PICKER](value) {
|
|
138
|
+
if (dayjs.isDayjs(value))
|
|
139
|
+
return value.format('YYYY-MM-DD');
|
|
140
|
+
|
|
141
|
+
return value;
|
|
142
|
+
},
|
|
143
|
+
[FormTypes.RANGE_DATE_PICKER](value) {
|
|
144
|
+
if (dayjs.isDayjs(value[0]) && dayjs.isDayjs(value[1]))
|
|
145
|
+
return [value[0].format('YYYY-MM-DD'), value[1].format('YYYY-MM-DD')].join('/');
|
|
146
|
+
|
|
147
|
+
return value;
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
</script>
|
|
151
|
+
|
|
152
|
+
<script setup lang="ts">
|
|
153
|
+
const props = defineProps<{
|
|
154
|
+
categoryApi: Api.Request
|
|
155
|
+
configApi: Api.Request
|
|
156
|
+
updateApi: Api.Request
|
|
157
|
+
}>();
|
|
158
|
+
|
|
159
|
+
const queryClient = useQueryClient();
|
|
160
|
+
|
|
161
|
+
/* 配置分类 */
|
|
162
|
+
const categoryId = ref(1);
|
|
163
|
+
const categoryQuerier = useQuery({
|
|
164
|
+
queryKey: [props.categoryApi.id],
|
|
165
|
+
queryFn: () => props.categoryApi(undefined),
|
|
166
|
+
});
|
|
167
|
+
const categoryList = computed(() => categoryQuerier.data.value?.map((item: any) => ({ key: String(item.type), tab: item.label })));
|
|
168
|
+
|
|
169
|
+
/* 当前分类下的配置 */
|
|
170
|
+
const configQuerier = useQuery<IConfigDetail[] | undefined>({
|
|
171
|
+
enabled: computed(() => !!categoryQuerier.data.value),
|
|
172
|
+
queryKey: [props.configApi.id, categoryId],
|
|
173
|
+
queryFn: () => props.configApi({ category_id: categoryId.value }),
|
|
174
|
+
});
|
|
175
|
+
const configList = computed(() => {
|
|
176
|
+
const list = cloneDeep(configQuerier.data.value);
|
|
177
|
+
|
|
178
|
+
if (!list?.length)
|
|
179
|
+
return;
|
|
180
|
+
|
|
181
|
+
for (let i = 0; i < list.length; i++) {
|
|
182
|
+
const config = { ...list[i] };
|
|
183
|
+
list[i] = antdPropsResolvers[config.form_type]?.(config) ?? config;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return list;
|
|
187
|
+
});
|
|
188
|
+
const configMap = computed(() => mapKeys(cloneDeep(configQuerier.data.value ?? []), 'key'));
|
|
189
|
+
|
|
190
|
+
/* 配置表单 */
|
|
191
|
+
const settingMutator = useMutation({
|
|
192
|
+
mutationKey: [props.updateApi.id],
|
|
193
|
+
mutationFn: props.updateApi.setDefaultConfig({ successMessage: '更新成功' }),
|
|
194
|
+
onSuccess() {
|
|
195
|
+
configQuerier.refetch();
|
|
196
|
+
queryClient.invalidateQueries({ queryKey: [props.configApi.id], exact: false });
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
const formSchema = reactiveComputed(() => {
|
|
200
|
+
const _configList = configList.value ?? [];
|
|
201
|
+
const entries = _configList.map(config => [
|
|
202
|
+
config.key,
|
|
203
|
+
{
|
|
204
|
+
value: cloneDeep(config.value),
|
|
205
|
+
rules: config.required ? [{ required: true }] : undefined,
|
|
206
|
+
},
|
|
207
|
+
] as [string, ItemSchema]);
|
|
208
|
+
const schema = Object.fromEntries(entries);
|
|
209
|
+
|
|
210
|
+
return schema;
|
|
211
|
+
});
|
|
212
|
+
const settingForm = useAntdForm(formSchema, {
|
|
213
|
+
transform(state) {
|
|
214
|
+
return mapValues(state, (v, k) => {
|
|
215
|
+
const formType = configMap.value[k].form_type;
|
|
216
|
+
const resolver = antdValueResolvers[formType];
|
|
217
|
+
|
|
218
|
+
return resolver ? resolver(v) : v;
|
|
219
|
+
});
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
/** 提交表单内容 */
|
|
224
|
+
async function submitSetting() {
|
|
225
|
+
const body = await settingForm.$form.validate?.().catch(() => {});
|
|
226
|
+
|
|
227
|
+
if (body)
|
|
228
|
+
await settingMutator.mutateAsync({ requestBody: settingForm.stateTF });
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** 重置表单内容 */
|
|
232
|
+
function resetSetting() {
|
|
233
|
+
configList.value?.forEach(config => settingForm.state[config.key] = cloneDeep(config.value));
|
|
234
|
+
settingForm.$form.clearValidate?.();
|
|
235
|
+
}
|
|
236
|
+
</script>
|
|
237
|
+
|
|
238
|
+
<template>
|
|
239
|
+
<Card
|
|
240
|
+
class="antd-cover__actions-right-align"
|
|
241
|
+
:activeTabKey="String(categoryId)"
|
|
242
|
+
:tabList="categoryList"
|
|
243
|
+
:loading="configQuerier.isPending.value"
|
|
244
|
+
@tabChange="key => categoryId = Number(key)"
|
|
245
|
+
>
|
|
246
|
+
<Form
|
|
247
|
+
v-bind="settingForm.props"
|
|
248
|
+
class="ant-cover__col2-form"
|
|
249
|
+
layout="vertical"
|
|
250
|
+
:disabled="settingMutator.isPending.value"
|
|
251
|
+
>
|
|
252
|
+
<template v-for="item of configList" :key="item.key">
|
|
253
|
+
<FormItem v-bind="settingForm.itemProps[item.key]" :label="item.label" :extra="item.summary">
|
|
254
|
+
<slot
|
|
255
|
+
v-if="$slots[item.key]"
|
|
256
|
+
:name="item.key"
|
|
257
|
+
:state="toRef(settingForm.state, item.key)"
|
|
258
|
+
:config="item"
|
|
259
|
+
:orginConfig="configMap[item.key]"
|
|
260
|
+
/>
|
|
261
|
+
<template v-else>
|
|
262
|
+
<slot v-if="item.form_type === FormTypes.NUMBER_INPUT" :name="FormTypes.NUMBER_INPUT">
|
|
263
|
+
<InputNumber v-model:value="settingForm.state[item.key]" class="w-full" />
|
|
264
|
+
</slot>
|
|
265
|
+
|
|
266
|
+
<slot v-else-if="item.form_type === FormTypes.TEXT_INPUT" :name="FormTypes.TEXT_INPUT">
|
|
267
|
+
<Input v-model:value="settingForm.state[item.key]" allowClear />
|
|
268
|
+
</slot>
|
|
269
|
+
|
|
270
|
+
<slot v-else-if="item.form_type === FormTypes.TEXTAREA" :name="FormTypes.TEXTAREA">
|
|
271
|
+
<Textarea v-model:value="settingForm.state[item.key]" :rows="4" />
|
|
272
|
+
</slot>
|
|
273
|
+
|
|
274
|
+
<slot v-else-if="item.form_type === FormTypes.SWITCH" :name="FormTypes.SWITCH">
|
|
275
|
+
<RadioGroup
|
|
276
|
+
v-model:value="settingForm.state[item.key]"
|
|
277
|
+
v-bind="item.options"
|
|
278
|
+
buttonStyle="solid"
|
|
279
|
+
optionType="button"
|
|
280
|
+
/>
|
|
281
|
+
</slot>
|
|
282
|
+
|
|
283
|
+
<slot v-else-if="item.form_type === FormTypes.RADIO" :name="FormTypes.RADIO">
|
|
284
|
+
<RadioGroup
|
|
285
|
+
v-model:value="settingForm.state[item.key]"
|
|
286
|
+
buttonStyle="solid"
|
|
287
|
+
v-bind="item.options"
|
|
288
|
+
/>
|
|
289
|
+
</slot>
|
|
290
|
+
|
|
291
|
+
<slot v-else-if="item.form_type === FormTypes.CHECKBOX" :name="FormTypes.CHECKBOX">
|
|
292
|
+
<CheckboxGroup v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
293
|
+
</slot>
|
|
294
|
+
|
|
295
|
+
<slot v-else-if="item.form_type === FormTypes.SINGLE_SELECT" :name="FormTypes.SINGLE_SELECT">
|
|
296
|
+
<Select v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
297
|
+
</slot>
|
|
298
|
+
|
|
299
|
+
<slot v-else-if="item.form_type === FormTypes.MULTIPLE_SELECT" :name="FormTypes.MULTIPLE_SELECT">
|
|
300
|
+
<Select v-model:value="settingForm.state[item.key]" v-bind="item.options" mode="multiple" />
|
|
301
|
+
</slot>
|
|
302
|
+
|
|
303
|
+
<slot v-else-if="item.form_type === FormTypes.SINGLE_DATE_PICKER" :name="FormTypes.SINGLE_DATE_PICKER">
|
|
304
|
+
<DatePicker v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
305
|
+
</slot>
|
|
306
|
+
|
|
307
|
+
<slot v-else-if="item.form_type === FormTypes.RANGE_DATE_PICKER" :name="FormTypes.RANGE_DATE_PICKER">
|
|
308
|
+
<RangePicker v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
309
|
+
</slot>
|
|
310
|
+
|
|
311
|
+
<span v-else class="text-red">没有找到预设 form_type:{{ item.form_type }},可以通过 {{ item.key }} 插槽自定义</span>
|
|
312
|
+
</template>
|
|
313
|
+
</FormItem>
|
|
314
|
+
</template>
|
|
315
|
+
</Form>
|
|
316
|
+
|
|
317
|
+
<template #actions>
|
|
318
|
+
<Button :disabled="settingMutator.isPending.value" @click="resetSetting()">
|
|
319
|
+
重置
|
|
320
|
+
</Button>
|
|
321
|
+
<Button type="primary" :loading="settingMutator.isPending.value" @click="submitSetting()">
|
|
322
|
+
提交
|
|
323
|
+
</Button>
|
|
324
|
+
</template>
|
|
325
|
+
</Card>
|
|
326
|
+
</template>
|
|
327
|
+
|
|
328
|
+
<style lang="scss" scoped>
|
|
329
|
+
.ant-cover__col2-form :deep(.ant-form-item-label > label) {
|
|
330
|
+
// padding-left: 0.5em;
|
|
331
|
+
// border-left: 2px solid #2361d0;
|
|
332
|
+
}
|
|
333
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { message } from 'ant-design-vue';
|
|
2
|
+
import { Image as AImage, ImagePreviewGroup as AImagePreviewGroup, Upload as AUpload, message } from 'ant-design-vue';
|
|
3
3
|
import type { UploadProps } from 'ant-design-vue';
|
|
4
4
|
import type { PreviewGroupPreview } from 'ant-design-vue/es/vc-image/src/PreviewGroup';
|
|
5
5
|
import { useVModel } from '@vueuse/core';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { UploadFile } from 'ant-design-vue/es/upload';
|
|
2
|
+
import { type Ref, computed } from 'vue';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 获取URL中的文件名
|
|
6
|
+
* @param url - 文件的URL
|
|
7
|
+
* @returns 文件名
|
|
8
|
+
*/
|
|
9
|
+
export function getFileNameByUrl(url: string) {
|
|
10
|
+
return url.split('/').pop();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 判断文件是否全部上传完成
|
|
15
|
+
* @param files - 上传文件的引用
|
|
16
|
+
* @returns 如果所有文件都已完成上传,则返回 true;否则返回 false
|
|
17
|
+
*/
|
|
18
|
+
export function isFilesDoneFn(files: Ref<UploadFile[] | undefined>) {
|
|
19
|
+
return computed(() => files.value ? files.value.every(file => file.status === 'done') : true);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 将URL转换为上传文件对象数组
|
|
24
|
+
* @param urls 要转换的URL数组或URL字符串
|
|
25
|
+
* @returns 上传文件对象数组
|
|
26
|
+
*/
|
|
27
|
+
export function urlToUploadFile(urls?: string[] | string) {
|
|
28
|
+
const _urls = urls ? Array.isArray(urls) ? urls : [urls] : [];
|
|
29
|
+
const files = _urls.filter(url => !!url).map(url => ({
|
|
30
|
+
uid: getFileNameByUrl(url),
|
|
31
|
+
name: getFileNameByUrl(url),
|
|
32
|
+
status: 'done',
|
|
33
|
+
url,
|
|
34
|
+
}));
|
|
35
|
+
|
|
36
|
+
return files;
|
|
37
|
+
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import type { RouteRecordRaw } from 'vue-router';
|
|
2
2
|
import { definePage } from '../page';
|
|
3
3
|
|
|
4
|
-
export { defineRoute
|
|
5
|
-
|
|
6
|
-
const RouteSymbol = Symbol('app-route');
|
|
4
|
+
export { defineRoute };
|
|
7
5
|
|
|
8
6
|
/** 定义路由 */
|
|
9
7
|
function defineRoute(route: (params: { definePage: typeof definePage }) => RouteRecordRaw[]) {
|
|
10
8
|
const routeFn: any = () => route({ definePage });
|
|
11
|
-
routeFn
|
|
9
|
+
routeFn.isRoute = true;
|
|
12
10
|
|
|
13
11
|
return routeFn;
|
|
14
12
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { RouteRecordRaw } from 'vue-router';
|
|
2
2
|
import merge from 'lodash-es/merge';
|
|
3
3
|
import { ENV } from '../../../utils';
|
|
4
|
-
import { RouteSymbol } from './defineRoute';
|
|
5
4
|
|
|
6
5
|
export { getRoutes };
|
|
7
6
|
|
|
@@ -14,10 +13,11 @@ async function getRoutes() {
|
|
|
14
13
|
let routes: RouteRecordRaw[] = [];
|
|
15
14
|
|
|
16
15
|
for (const name in routeFiles) {
|
|
17
|
-
const
|
|
16
|
+
const moduleLoader: any = routeFiles[name];
|
|
17
|
+
const routeDefineFn = (typeof moduleLoader === 'function' ? await moduleLoader() : moduleLoader)?.default;
|
|
18
18
|
|
|
19
|
-
if (
|
|
20
|
-
|
|
19
|
+
if (routeDefineFn?.isRoute)
|
|
20
|
+
routes.push(...routeDefineFn());
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// 处理路由
|
|
@@ -6,12 +6,9 @@ export interface IGuardConfig {
|
|
|
6
6
|
setup: (router: Router) => void
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
// 路由守卫标识
|
|
10
|
-
export const RouteGuardSymbol = Symbol('app-route-guard');
|
|
11
|
-
|
|
12
9
|
// 定义路由守卫
|
|
13
10
|
export function defineRouteGuard(guard: IGuardConfig) {
|
|
14
|
-
(guard as any)
|
|
11
|
+
(guard as any).isRouteGuard = true;
|
|
15
12
|
guard.order ??= 10;
|
|
16
13
|
|
|
17
14
|
return guard;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ENV } from '../../../utils';
|
|
2
|
-
import { RouteGuardSymbol } from './defineRouteGuard';
|
|
3
2
|
import type { IGuardConfig } from './defineRouteGuard';
|
|
4
3
|
|
|
5
4
|
/**
|
|
@@ -14,13 +13,12 @@ export async function getRouteGuards() {
|
|
|
14
13
|
const routeGuardRecord: { file: string, order: number }[] = [];
|
|
15
14
|
|
|
16
15
|
for (const name in routeGuardFiles) {
|
|
17
|
-
const
|
|
16
|
+
const moduleLoader: any = routeGuardFiles[name];
|
|
17
|
+
const routeGuard = (typeof moduleLoader === 'function' ? await moduleLoader() : moduleLoader)?.default;
|
|
18
18
|
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
routeGuards.push(guard);
|
|
23
|
-
routeGuardRecord.push({ file: name, order: guard.order });
|
|
19
|
+
if (routeGuard?.isRouteGuard) {
|
|
20
|
+
routeGuards.push(routeGuard);
|
|
21
|
+
routeGuardRecord.push({ file: name, order: routeGuard.order });
|
|
24
22
|
}
|
|
25
23
|
}
|
|
26
24
|
|
|
@@ -9,10 +9,8 @@ export type StartupFn = (
|
|
|
9
9
|
hooks: IHooks
|
|
10
10
|
) => (void | Promise<void>);
|
|
11
11
|
|
|
12
|
-
export const StartupSymbol = Symbol('app-startup');
|
|
13
|
-
|
|
14
12
|
export function defineStartup(startup: StartupFn) {
|
|
15
|
-
(startup as any)
|
|
13
|
+
(startup as any).isAppStartup = true;
|
|
16
14
|
|
|
17
15
|
return startup;
|
|
18
16
|
}
|
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
import type { App } from 'vue';
|
|
2
2
|
import { ENV } from '../../../utils';
|
|
3
|
-
import { StartupSymbol } from './defineStartup';
|
|
4
3
|
import type { StartupFn } from './defineStartup';
|
|
5
4
|
|
|
6
5
|
export async function runStartup(app: App) {
|
|
7
|
-
const moduleLoaders = runStartup.modules as Record<string,
|
|
6
|
+
const moduleLoaders = runStartup.modules as Record<string, any>;
|
|
8
7
|
const startupPaths: string[] = [];
|
|
9
8
|
const onMountCallbacks: Function[] = [];
|
|
10
9
|
(app as any).deps = {};
|
|
11
10
|
|
|
12
|
-
for (const [path,
|
|
13
|
-
const
|
|
14
|
-
const plugin: StartupFn = module?.default ?? {};
|
|
11
|
+
for (const [path, loader] of Object.entries(moduleLoaders)) {
|
|
12
|
+
const plugin: StartupFn = (typeof loader === 'function' ? await loader() : loader)?.default;
|
|
15
13
|
|
|
16
|
-
if (!plugin?.
|
|
14
|
+
if (!(plugin as any)?.isAppStartup)
|
|
17
15
|
continue;
|
|
18
16
|
|
|
19
17
|
startupPaths.push(path);
|
|
@@ -3,7 +3,7 @@ import { computed } from 'vue';
|
|
|
3
3
|
import type { VNode } from 'vue';
|
|
4
4
|
import { Breadcrumb as ABreadcrumb } from 'ant-design-vue';
|
|
5
5
|
import type { Route as AntdBreadcrumbRoute } from 'ant-design-vue/es/breadcrumb/Breadcrumb';
|
|
6
|
-
import {
|
|
6
|
+
import { injectTTAdmin } from '../../adminPlugin';
|
|
7
7
|
import type { IBreadcrumb } from '../../stores/createUsePageStore';
|
|
8
8
|
|
|
9
9
|
interface IBreadcrumbRoute extends AntdBreadcrumbRoute {
|
|
@@ -23,7 +23,7 @@ function _buildRoute(breadcrumb: IBreadcrumb): IBreadcrumbRoute {
|
|
|
23
23
|
</script>
|
|
24
24
|
|
|
25
25
|
<script setup lang="ts">
|
|
26
|
-
const pageStore =
|
|
26
|
+
const pageStore = injectTTAdmin()!.deps.usePageStore();
|
|
27
27
|
const routes = computed(() => {
|
|
28
28
|
let breadcrumbs: IBreadcrumbRoute[] = [];
|
|
29
29
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { injectTTAdmin } from '../../adminPlugin';
|
|
3
3
|
|
|
4
|
-
const pageStore =
|
|
4
|
+
const pageStore = injectTTAdmin()!.deps.usePageStore();
|
|
5
5
|
</script>
|
|
6
6
|
|
|
7
7
|
<template>
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { computed, ref, watch } from 'vue';
|
|
3
3
|
import { Menu as AMenu } from 'ant-design-vue';
|
|
4
4
|
import type { ItemType } from 'ant-design-vue';
|
|
5
|
-
import {
|
|
5
|
+
import { injectTTAdmin } from '../../adminPlugin';
|
|
6
6
|
import type { TMenu } from '../../stores/createUseMenuStore';
|
|
7
7
|
|
|
8
8
|
function formatMenu(menu: TMenu): ItemType {
|
|
@@ -21,7 +21,7 @@ function formatMenu(menu: TMenu): ItemType {
|
|
|
21
21
|
</script>
|
|
22
22
|
|
|
23
23
|
<script setup lang="ts">
|
|
24
|
-
const { router, useMenuStore } =
|
|
24
|
+
const { router, useMenuStore } = injectTTAdmin()!.deps;
|
|
25
25
|
const menuStore = useMenuStore();
|
|
26
26
|
const items = computed(() => menuStore.menus.map(formatMenu));
|
|
27
27
|
const openKeys = ref<string[]>([]);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { TabPane as ATabPane, Tabs as ATabs } from 'ant-design-vue';
|
|
3
|
-
import {
|
|
3
|
+
import { injectTTAdmin } from '../../adminPlugin';
|
|
4
4
|
|
|
5
|
-
const pageTabStore =
|
|
5
|
+
const pageTabStore = injectTTAdmin()!.deps.usePageTabStore();
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<template>
|
|
@@ -26,7 +26,7 @@ export function setupPermissionRouterGuard(router: Router, rouneNames: { index:
|
|
|
26
26
|
|
|
27
27
|
// 未登录状态进入需要登录权限的页面
|
|
28
28
|
else if (!isLogin && needLogin)
|
|
29
|
-
return next({ name: rouneNames.login, replace: true });
|
|
29
|
+
return next({ name: rouneNames.login, replace: true, query: { redirect: to.fullPath } });
|
|
30
30
|
|
|
31
31
|
// 登录状态进入需要登录权限的页面,有权限直接通行
|
|
32
32
|
else if (isLogin && needLogin && hasPermission)
|
|
@@ -10,6 +10,7 @@ const UNWATCH_NAME = `v-${PLUGIN_NAME}@unwatch`;
|
|
|
10
10
|
export function setupPermissionPlugin(app: App) {
|
|
11
11
|
app.directive<HTMLElement, TCodes>(PLUGIN_NAME, {
|
|
12
12
|
mounted(el, binding) {
|
|
13
|
+
console.log('🤡 / el:', el);
|
|
13
14
|
const permissionStore = adminPlugin.deps.usePermissionStore();
|
|
14
15
|
|
|
15
16
|
function updateVisibility() {
|