@peng_kai/kit 0.3.0-beta.4 → 0.3.0-beta.40
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 +2 -2
- package/admin/components/currency/src/CurrencyIcon.vue +37 -33
- package/admin/components/date/PeriodPicker.vue +122 -0
- package/admin/components/date/TimeFieldSelectForLabel.vue +24 -0
- package/admin/components/date/TtaTimeZone.vue +516 -0
- package/admin/components/date/TtaTimeZoneSimple.vue +104 -0
- package/admin/components/date/helpers.ts +250 -0
- package/admin/components/date/index.ts +6 -0
- package/admin/components/date/presetProps.ts +19 -0
- package/admin/components/filter/src/FilterReset.vue +55 -8
- package/admin/components/filter/src/more/TableSetting.vue +95 -0
- package/admin/components/filter/src/useFilterParams.ts +9 -7
- package/admin/components/provider/Admin.vue +17 -0
- package/admin/components/provider/admin-permission.ts +48 -0
- package/admin/components/provider/admin-router.ts +361 -0
- package/admin/components/provider/index.ts +3 -0
- package/admin/components/rich-text/src/RichText.new.vue +19 -6
- package/admin/components/rich-text/src/editorConfig.ts +76 -1
- package/admin/components/settings/index.ts +1 -1
- package/admin/components/settings/src/SchemaForm.vue +40 -6
- package/admin/components/settings/src/Settings.vue +1 -1
- package/admin/components/text/index.ts +2 -0
- package/admin/components/text/src/Amount.v2.vue +131 -0
- package/admin/components/text/src/Datetime.vue +17 -12
- package/admin/components/text/src/IP.vue +18 -4
- package/admin/components/text/src/Num.vue +192 -0
- package/admin/components/upload/src/PictureCardUpload.vue +56 -20
- package/admin/layout/large/Breadcrumb.vue +10 -23
- package/admin/layout/large/Content.vue +9 -6
- package/admin/layout/large/Layout.vue +129 -0
- package/admin/layout/large/Menu.vue +24 -17
- package/admin/layout/large/Notice.vue +168 -0
- package/admin/layout/large/Tabs.vue +183 -0
- package/admin/layout/large/index.ts +61 -1
- package/admin/layout/large/y682.mp3 +0 -0
- package/admin/permission/routerGuard.ts +24 -11
- package/admin/permission/vuePlugin.ts +5 -10
- package/admin/route-guards/index.ts +0 -1
- package/admin/stores/index.ts +1 -0
- package/admin/styles/classCover.scss +1 -1
- package/admin/styles/index.scss +2 -2
- package/antd/hooks/useAntdModal.ts +27 -12
- package/antd/hooks/useAntdTable.ts +10 -7
- package/antd/hooks/useAntdTheme.ts +7 -0
- package/antd/hooks/useTableColumns.ts +83 -0
- package/antd/index.ts +1 -1
- package/libs/bignumber.ts +1 -1
- package/libs/dayjs.ts +16 -2
- package/libs/fingerprintjs.ts +1 -0
- package/package.json +64 -95
- package/request/interceptors/getDeviceInfo.ts +14 -0
- package/utils/LocaleManager.ts +1 -1
- package/utils/index.ts +1 -4
- package/utils/locale/LocaleManager.ts +2 -1
- package/utils/locale/helpers.ts +9 -0
- package/utils/number.ts +8 -10
- package/utils/storage.ts +31 -0
- package/utils/string.ts +1 -2
- package/utils/upload/AwsS3.ts +12 -4
- package/admin/layout/large/PageTab.vue +0 -70
- package/admin/route-guards/collapseMenu.ts +0 -11
- package/libs/a-calc.ts +0 -1
- package/vue/components/test/KitTest.vue +0 -9
- package/vue/components/test/testStore.ts +0 -11
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { computed, defineComponent, h, reactive, ref, shallowRef, toRef, watch } from 'vue';
|
|
2
|
+
import type { MaybeRef, PropType, Ref, ShallowRef, UnwrapNestedRefs, VNode } from 'vue';
|
|
3
|
+
import type { RouteLocationNormalizedGeneric, RouteLocationNormalizedLoaded, Router } from 'vue-router';
|
|
4
|
+
|
|
5
|
+
interface TOptions {
|
|
6
|
+
excludeTabs?: Array<string>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function createAdminRouter(options: TOptions) {
|
|
10
|
+
const router = shallowRef<Router | null>(null);
|
|
11
|
+
const { menus, breadcrumbs, menuPath, addMenu, generateMenus } = useMenu(router);
|
|
12
|
+
const { tabs, currentTab: tab, tabsCached, addTab, delTab, getTab } = useTab(router, options);
|
|
13
|
+
|
|
14
|
+
function setRouter(newRouter: Router) {
|
|
15
|
+
router.value = newRouter;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return reactive({
|
|
19
|
+
menus,
|
|
20
|
+
menuPath,
|
|
21
|
+
breadcrumbs,
|
|
22
|
+
tabs,
|
|
23
|
+
tabsCached,
|
|
24
|
+
tab,
|
|
25
|
+
setRouter,
|
|
26
|
+
addMenu,
|
|
27
|
+
addTab,
|
|
28
|
+
delTab,
|
|
29
|
+
getTab,
|
|
30
|
+
generateMenus,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const PageRefresh = defineComponent({
|
|
35
|
+
props: {
|
|
36
|
+
router: { type: Object as PropType<Router>, required: true },
|
|
37
|
+
originalMainComp: { type: Object as PropType<any>, required: true },
|
|
38
|
+
},
|
|
39
|
+
mounted() {
|
|
40
|
+
const { router } = this.$props;
|
|
41
|
+
const fullPath = router.currentRoute.value.fullPath;
|
|
42
|
+
router.replace({ ...router.resolve(fullPath), force: true });
|
|
43
|
+
},
|
|
44
|
+
setup() {
|
|
45
|
+
return () => null;
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
function useTab(router: ShallowRef<Router | null>, options: TOptions = {}) {
|
|
50
|
+
const tabs = ref<TTab[]>([]);
|
|
51
|
+
const currentTab = computed(() => [...tabs.value].sort((a, b) => b.lastTime - a.lastTime)[0]);
|
|
52
|
+
const tabsCached = ref<string[]>([]);
|
|
53
|
+
|
|
54
|
+
function addTab(tab: TTabConfig) {
|
|
55
|
+
tabs.value.push(reactive(tab));
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function delTab(path: string) {
|
|
59
|
+
if (path === currentTab.value?.path) {
|
|
60
|
+
const index = tabs.value.findIndex(tab => tab.path === path);
|
|
61
|
+
const nextTab = tabs.value[index + 1] || tabs.value[index - 1];
|
|
62
|
+
|
|
63
|
+
if (nextTab) {
|
|
64
|
+
tabs.value.splice(index, 1);
|
|
65
|
+
router.value?.replace(nextTab.path);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const index = tabs.value.findIndex(tab => tab.path === path);
|
|
70
|
+
if (index >= 0)
|
|
71
|
+
tabs.value.splice(index, 1);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function getTab(path: string) {
|
|
76
|
+
return tabs.value.find(tab => tab.path === path);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function switchTab(to: RouteLocationNormalizedGeneric, from: RouteLocationNormalizedGeneric) {
|
|
80
|
+
const toTab = getTab(to.fullPath);
|
|
81
|
+
const fromTab = getTab(from.fullPath);
|
|
82
|
+
const lastTime = Date.now();
|
|
83
|
+
const isReplaced = history.state?.replaced;
|
|
84
|
+
const tabType = Number(history.state?.tabType ?? 1); // 0:不新增 tab, 1:新增 tab, 2: 固定 tab(不可关闭)
|
|
85
|
+
|
|
86
|
+
// 0. 刷新页面
|
|
87
|
+
if (toTab && toTab?.path === fromTab?.path) {
|
|
88
|
+
const components = to.matched[1].components as Record<string, any>;
|
|
89
|
+
|
|
90
|
+
if (!components?.default || !router.value)
|
|
91
|
+
return;
|
|
92
|
+
|
|
93
|
+
if (components.default.originalMainComp) {
|
|
94
|
+
components.default = components.default.originalMainComp;
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const pageRefreshComp = h(PageRefresh, { router: router.value, originalMainComp:components.default }) as any;
|
|
98
|
+
pageRefreshComp.originalMainComp = components.default;
|
|
99
|
+
components.default = pageRefreshComp;
|
|
100
|
+
toTab.lastTime = 0;
|
|
101
|
+
setTimeout(() => toTab.lastTime = lastTime, 10);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// 1. 在已有的 tab 中互相切换
|
|
105
|
+
else if (toTab && fromTab && !isReplaced) {
|
|
106
|
+
toTab.lastTime = lastTime;
|
|
107
|
+
fromTab.lastTime = lastTime - 1;
|
|
108
|
+
}
|
|
109
|
+
// 2. 仅改变当前 tab 的参数
|
|
110
|
+
else if (!toTab && fromTab && isReplaced) {
|
|
111
|
+
fromTab.lastTime = lastTime;
|
|
112
|
+
fromTab.path = to.fullPath;
|
|
113
|
+
fromTab.routeName = String(to.name);
|
|
114
|
+
}
|
|
115
|
+
// 3. 删除当前 tab
|
|
116
|
+
else if (toTab && !fromTab && isReplaced) {
|
|
117
|
+
toTab.lastTime = lastTime;
|
|
118
|
+
}
|
|
119
|
+
// 4. 新增 tab
|
|
120
|
+
else {
|
|
121
|
+
// 4.1 排除不需要新增 tab 的页面
|
|
122
|
+
const isExcluded = tabType === 0 || options.excludeTabs?.includes(String(to.name)) || getTab(to.fullPath);
|
|
123
|
+
if (isExcluded)
|
|
124
|
+
return;
|
|
125
|
+
|
|
126
|
+
const title = getTitle(to) || String(to.name || to.fullPath);
|
|
127
|
+
addTab({
|
|
128
|
+
lastTime,
|
|
129
|
+
title,
|
|
130
|
+
type: tabType,
|
|
131
|
+
path: to.fullPath,
|
|
132
|
+
routeName: String(to.name),
|
|
133
|
+
routeTitle: title,
|
|
134
|
+
});
|
|
135
|
+
fromTab?.lastTime && (fromTab.lastTime = lastTime - 1);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
watch(tabs, (tabs) => {
|
|
140
|
+
let tabCached = tabs.filter(tab => tab.lastTime > 0);
|
|
141
|
+
|
|
142
|
+
if (tabCached.length > 10)
|
|
143
|
+
tabCached = tabCached.sort((a, b) => a.lastTime - b.lastTime).slice(0, 10);
|
|
144
|
+
|
|
145
|
+
tabsCached.value = tabCached.map(tab => tab.path);
|
|
146
|
+
}, { immediate: true, deep: 2 });
|
|
147
|
+
|
|
148
|
+
watch(router, (router) => {
|
|
149
|
+
router?.afterEach((to, from) => {
|
|
150
|
+
switchTab(to, from);
|
|
151
|
+
|
|
152
|
+
// 使用 to.fullPath 重命名 main 组件
|
|
153
|
+
if (to.matched.length > 1) {
|
|
154
|
+
const mainComp: any = to.matched[1].components?.default;
|
|
155
|
+
const compName = currentTab.value?.path;
|
|
156
|
+
|
|
157
|
+
if (mainComp && compName)
|
|
158
|
+
mainComp.__name = to.fullPath;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
return { tabs, currentTab, tabsCached, addTab, delTab, getTab, switchTab };
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function useMenu(router: ShallowRef<Router | null>) {
|
|
167
|
+
const currentRouteName = computed(() => String(router.value?.currentRoute.value.name || ''));
|
|
168
|
+
const menus = ref<TMenu[]>([]);
|
|
169
|
+
const menuPath = computed(() => getMenuPath(menus.value, currentRouteName.value));
|
|
170
|
+
const breadcrumbs = computed(() => getMenuPath(menus.value, currentRouteName.value).map(menuToBreadcrumb));
|
|
171
|
+
|
|
172
|
+
function addMenu(menuConfig: TMenuConfig, parentKey?: string) {
|
|
173
|
+
const labelGetter = typeof menuConfig.label === 'function' ? menuConfig.label : (() => menuConfig.label) as (() => string);
|
|
174
|
+
const iconGetter = typeof menuConfig.icon === 'function' ? menuConfig.icon : () => null;
|
|
175
|
+
|
|
176
|
+
const _menu = reactive<IMenuReactive>({
|
|
177
|
+
key: menuConfig.key,
|
|
178
|
+
label: toRef(labelGetter),
|
|
179
|
+
icon: toRef(iconGetter),
|
|
180
|
+
trigger: menuConfig.trigger,
|
|
181
|
+
order: menuConfig.order,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
if (parentKey) {
|
|
185
|
+
const parentMenu = findMenu(menus.value, parentKey);
|
|
186
|
+
|
|
187
|
+
if (!parentMenu)
|
|
188
|
+
return;
|
|
189
|
+
|
|
190
|
+
const children = reactive(parentMenu.children ?? []);
|
|
191
|
+
children.push(_menu);
|
|
192
|
+
children.sort((a, b) => b.order - a.order);
|
|
193
|
+
parentMenu.children = children;
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
menus.value.push(_menu);
|
|
197
|
+
menus.value.sort((a, b) => b.order - a.order);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function generateMenus(
|
|
202
|
+
filter?: (menu: ReturnType<typeof getMenusByRouter>[number]) => boolean,
|
|
203
|
+
otherRouter?: Router,
|
|
204
|
+
) {
|
|
205
|
+
if (!router.value)
|
|
206
|
+
return;
|
|
207
|
+
|
|
208
|
+
menus.value.length = 0;
|
|
209
|
+
|
|
210
|
+
const finalFilter = filter ?? (() => true);
|
|
211
|
+
const finalRouter = otherRouter ?? router.value;
|
|
212
|
+
|
|
213
|
+
getMenusByRouter(finalRouter).filter(finalFilter).forEach(({ menu, parentKey }) => addMenu(menu, parentKey));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
watch(router, (router) => {
|
|
217
|
+
generateMenus(undefined, router || undefined);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
return { menus, menuPath, breadcrumbs, addMenu, generateMenus };
|
|
221
|
+
}
|
|
222
|
+
interface TMenuConfig {
|
|
223
|
+
key: string
|
|
224
|
+
label: string | (() => string)
|
|
225
|
+
icon?: () => VNode
|
|
226
|
+
order: number
|
|
227
|
+
trigger: () => void
|
|
228
|
+
children?: TMenuConfig[]
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
interface IMenuReactive {
|
|
232
|
+
key: string
|
|
233
|
+
label: Ref<string>
|
|
234
|
+
icon: Ref<VNode | null>
|
|
235
|
+
order: number
|
|
236
|
+
trigger: () => void
|
|
237
|
+
children?: IMenuReactive[]
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export type TMenu = UnwrapNestedRefs<IMenuReactive>;
|
|
241
|
+
|
|
242
|
+
export interface TBreadcrumb {
|
|
243
|
+
title: string
|
|
244
|
+
icon?: VNode | null
|
|
245
|
+
trigger?: () => void
|
|
246
|
+
children?: TBreadcrumb[]
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
interface TTabConfig {
|
|
250
|
+
title: MaybeRef<string>
|
|
251
|
+
routeTitle: MaybeRef<string>
|
|
252
|
+
path: string
|
|
253
|
+
routeName: string
|
|
254
|
+
lastTime: number
|
|
255
|
+
type: number
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export type TTab = UnwrapNestedRefs<TTabConfig>;
|
|
259
|
+
|
|
260
|
+
export function getTitle(route?: Pick<RouteLocationNormalizedLoaded, 'meta'>) {
|
|
261
|
+
const mTitle = route?.meta?.title;
|
|
262
|
+
|
|
263
|
+
return typeof mTitle === 'function' ? mTitle() : mTitle;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function getMenusByRouter(router: Router) {
|
|
267
|
+
const menuOrderRE = /^(?<key>\w*)@(?<order>\d+)$/;
|
|
268
|
+
const routes = router.getRoutes();
|
|
269
|
+
const menus: Array<{ parentKey?: string, menu: TMenuConfig }> = routes
|
|
270
|
+
.filter(route => menuOrderRE.test((route.meta.menuOrder ?? '')))
|
|
271
|
+
.map((route) => {
|
|
272
|
+
const res = route.meta!.menuOrder!.match(menuOrderRE);
|
|
273
|
+
const parentKey = res?.groups?.key;
|
|
274
|
+
const order = Number(res?.groups?.order);
|
|
275
|
+
const name = route.name as string;
|
|
276
|
+
const menu = {
|
|
277
|
+
key: name,
|
|
278
|
+
label: route.meta.title ?? name,
|
|
279
|
+
icon: route.meta.icon,
|
|
280
|
+
trigger: () => {
|
|
281
|
+
if (router.currentRoute.value.name !== name)
|
|
282
|
+
router.push({ name })
|
|
283
|
+
},
|
|
284
|
+
order,
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
return { parentKey, menu };
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
// 将含有子菜单的菜单的 trigger 设置为空函数
|
|
291
|
+
const hasSubMenus = menus.filter(menu => menus.some(m => m.parentKey === menu.menu.key));
|
|
292
|
+
hasSubMenus.forEach((menu) => {
|
|
293
|
+
menu.menu.trigger = (() => {}) as any;
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
return menus;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
export function printRounesNameInterface(routes: { name: PropertyKey }[]) {
|
|
300
|
+
console.groupCollapsed('路由命名');
|
|
301
|
+
const routesName = new Set();
|
|
302
|
+
routes.forEach((route) => {
|
|
303
|
+
if (typeof route.name === 'string')
|
|
304
|
+
routesName.add(route.name);
|
|
305
|
+
});
|
|
306
|
+
console.log([...routesName.values()].sort().filter(name => !!name).map(name => `${name}: true`).join('\n'));
|
|
307
|
+
console.groupEnd();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function findMenu(menus: TMenu[], key: string) {
|
|
311
|
+
const queue = [...menus];
|
|
312
|
+
|
|
313
|
+
while (queue.length > 0) {
|
|
314
|
+
const menu = queue.shift()!;
|
|
315
|
+
|
|
316
|
+
if (menu.key === key)
|
|
317
|
+
return menu;
|
|
318
|
+
|
|
319
|
+
if (menu.children)
|
|
320
|
+
queue.push(...menu.children);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return undefined;
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
function menuToBreadcrumb(menu: TMenu) {
|
|
327
|
+
const ret: TBreadcrumb = reactive({
|
|
328
|
+
title: menu.label,
|
|
329
|
+
icon: menu.icon,
|
|
330
|
+
trigger: menu.trigger,
|
|
331
|
+
children: menu.children?.map(menuToBreadcrumb),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
return ret;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function getMenuPath(menus: TMenu[], key: string) {
|
|
338
|
+
const path: TMenu[] = [];
|
|
339
|
+
|
|
340
|
+
const _getMenuPath = (menus: TMenu[], key: string) => {
|
|
341
|
+
for (const menu of menus) {
|
|
342
|
+
if (menu.key === key) {
|
|
343
|
+
path.push(menu);
|
|
344
|
+
return true;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (menu.children) {
|
|
348
|
+
path.push(menu);
|
|
349
|
+
if (_getMenuPath(menu.children, key))
|
|
350
|
+
return true;
|
|
351
|
+
path.pop();
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return false;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
_getMenuPath(menus, key);
|
|
359
|
+
|
|
360
|
+
return path as TMenu[];
|
|
361
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useIntervalFn } from '@vueuse/core';
|
|
3
|
-
import
|
|
3
|
+
import { Ckeditor } from '@ckeditor/ckeditor5-vue';
|
|
4
4
|
import { shallowRef } from 'vue';
|
|
5
5
|
import { useInjectDisabled } from 'ant-design-vue/es/config-provider/DisabledContext';
|
|
6
6
|
import { defaultConfig } from './editorConfig';
|
|
7
|
-
import 'https://cdn.ckeditor.com/ckeditor5/41.
|
|
8
|
-
import 'https://cdn.ckeditor.com/ckeditor5/41.
|
|
7
|
+
import 'https://cdn.ckeditor.com/ckeditor5/41.2.0/super-build/ckeditor.js';
|
|
8
|
+
import 'https://cdn.ckeditor.com/ckeditor5/41.2.0/super-build/translations/zh-cn.js';
|
|
9
9
|
|
|
10
10
|
function useClassicEditor() {
|
|
11
11
|
const ClassicEditor = shallowRef();
|
|
@@ -20,7 +20,19 @@ function useClassicEditor() {
|
|
|
20
20
|
</script>
|
|
21
21
|
|
|
22
22
|
<script setup lang="ts">
|
|
23
|
-
const props =
|
|
23
|
+
const props = withDefaults(
|
|
24
|
+
defineProps<{
|
|
25
|
+
modelValue: string;
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
plugins?: string[];
|
|
28
|
+
height?: string;
|
|
29
|
+
}>(),
|
|
30
|
+
{
|
|
31
|
+
disabled: false,
|
|
32
|
+
plugins: () => [],
|
|
33
|
+
height: '500px',
|
|
34
|
+
}
|
|
35
|
+
);
|
|
24
36
|
const emits = defineEmits<{
|
|
25
37
|
(e: 'update:modelValue', value: string): void
|
|
26
38
|
}>();
|
|
@@ -33,7 +45,7 @@ editorConfig.extraPlugins = props.plugins;
|
|
|
33
45
|
|
|
34
46
|
<template>
|
|
35
47
|
<div class="editor-wrapper">
|
|
36
|
-
<
|
|
48
|
+
<Ckeditor
|
|
37
49
|
v-if="ClassicEditor"
|
|
38
50
|
:modelValue="props.modelValue"
|
|
39
51
|
:editor="ClassicEditor"
|
|
@@ -50,9 +62,10 @@ editorConfig.extraPlugins = props.plugins;
|
|
|
50
62
|
<style scoped lang="scss">
|
|
51
63
|
.editor-wrapper {
|
|
52
64
|
--loading-color: var(--antd-colorPrimary);
|
|
65
|
+
min-height: 40px;
|
|
53
66
|
|
|
54
67
|
:deep(.ck-editor__main > .ck-content) {
|
|
55
|
-
height:
|
|
68
|
+
height: v-bind('props.height');
|
|
56
69
|
overflow: auto;
|
|
57
70
|
}
|
|
58
71
|
}
|
|
@@ -3,6 +3,72 @@ export const defaultConfig: Record<string, any> = {
|
|
|
3
3
|
ui: 'zh-cn',
|
|
4
4
|
},
|
|
5
5
|
removePlugins: ['ImportWord', 'ExportWord', 'RevisionHistory', 'RealTimeCollaborativeEditing', 'RealTimeCollaborativeComments', 'RealTimeCollaborativeRevisionHistory', 'RealTimeCollaborativeTrackChanges', 'PresenceList', 'WProofreader', 'DocumentOutline', 'Comments', 'TrackChanges', 'TrackChangesData', 'TableOfContents', 'SlashCommand', 'PasteFromOfficeEnhanced', 'ContentTemplates', 'FormatPainter', 'Mentions', 'PageBreak', 'Pagination', 'AIAssistant', 'CaseChange', 'Template'],
|
|
6
|
+
// plugins: [
|
|
7
|
+
// 'Alignment',
|
|
8
|
+
// 'Autoformat',
|
|
9
|
+
// 'AutoImage',
|
|
10
|
+
// 'AutoLink',
|
|
11
|
+
// 'Autosave',
|
|
12
|
+
// 'BalloonToolbar',
|
|
13
|
+
// 'BlockQuote',
|
|
14
|
+
// 'Bold',
|
|
15
|
+
// 'Code',
|
|
16
|
+
// 'CodeBlock',
|
|
17
|
+
// 'Essentials',
|
|
18
|
+
// 'FindAndReplace',
|
|
19
|
+
// 'FontBackgroundColor',
|
|
20
|
+
// 'FontColor',
|
|
21
|
+
// 'FontFamily',
|
|
22
|
+
// 'FontSize',
|
|
23
|
+
// 'FullPage',
|
|
24
|
+
// 'Fullscreen',
|
|
25
|
+
// 'GeneralHtmlSupport',
|
|
26
|
+
// 'Heading',
|
|
27
|
+
// 'Highlight',
|
|
28
|
+
// 'HorizontalLine',
|
|
29
|
+
// 'HtmlComment',
|
|
30
|
+
// 'HtmlEmbed',
|
|
31
|
+
// 'ImageBlock',
|
|
32
|
+
// 'ImageCaption',
|
|
33
|
+
// 'ImageInline',
|
|
34
|
+
// 'ImageInsert',
|
|
35
|
+
// 'ImageInsertViaUrl',
|
|
36
|
+
// 'ImageResize',
|
|
37
|
+
// 'ImageStyle',
|
|
38
|
+
// 'ImageTextAlternative',
|
|
39
|
+
// 'ImageToolbar',
|
|
40
|
+
// 'ImageUpload',
|
|
41
|
+
// 'Indent',
|
|
42
|
+
// 'IndentBlock',
|
|
43
|
+
// 'Italic',
|
|
44
|
+
// 'Link',
|
|
45
|
+
// 'LinkImage',
|
|
46
|
+
// 'List',
|
|
47
|
+
// 'ListProperties',
|
|
48
|
+
// 'Markdown',
|
|
49
|
+
// 'MediaEmbed',
|
|
50
|
+
// 'Paragraph',
|
|
51
|
+
// 'PasteFromMarkdownExperimental',
|
|
52
|
+
// 'PasteFromOffice',
|
|
53
|
+
// 'PlainTableOutput',
|
|
54
|
+
// 'RemoveFormat',
|
|
55
|
+
// 'ShowBlocks',
|
|
56
|
+
// 'SimpleUploadAdapter',
|
|
57
|
+
// 'SourceEditing',
|
|
58
|
+
// 'Strikethrough',
|
|
59
|
+
// 'Subscript',
|
|
60
|
+
// 'Superscript',
|
|
61
|
+
// 'Table',
|
|
62
|
+
// 'TableCaption',
|
|
63
|
+
// 'TableCellProperties',
|
|
64
|
+
// 'TableColumnResize',
|
|
65
|
+
// 'TableLayout',
|
|
66
|
+
// 'TableProperties',
|
|
67
|
+
// 'TableToolbar',
|
|
68
|
+
// 'TextTransformation',
|
|
69
|
+
// 'Underline',
|
|
70
|
+
// 'WordCount'
|
|
71
|
+
// ],
|
|
6
72
|
toolbar: {
|
|
7
73
|
items: [
|
|
8
74
|
'undo',
|
|
@@ -95,7 +161,7 @@ export const defaultConfig: Record<string, any> = {
|
|
|
95
161
|
},
|
|
96
162
|
},
|
|
97
163
|
link: {
|
|
98
|
-
addTargetToExternalLinks:
|
|
164
|
+
addTargetToExternalLinks: false,
|
|
99
165
|
defaultProtocol: 'https://',
|
|
100
166
|
decorators: {
|
|
101
167
|
toggleDownloadable: {
|
|
@@ -105,6 +171,15 @@ export const defaultConfig: Record<string, any> = {
|
|
|
105
171
|
download: 'file',
|
|
106
172
|
},
|
|
107
173
|
},
|
|
174
|
+
openInNewTab: {
|
|
175
|
+
mode: 'manual',
|
|
176
|
+
label: '打开新标签页',
|
|
177
|
+
defaultValue: true, // This option will be selected by default.
|
|
178
|
+
attributes: {
|
|
179
|
+
target: '_blank',
|
|
180
|
+
rel: 'noopener noreferrer'
|
|
181
|
+
}
|
|
182
|
+
}
|
|
108
183
|
},
|
|
109
184
|
},
|
|
110
185
|
table: {
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { default as Settings } from './src/Settings.vue';
|
|
2
|
-
export { default as SchemaForm, type IConfigDetail } from './src/SchemaForm.vue';
|
|
2
|
+
export { default as SchemaForm, type IConfigDetail, FormTypes } from './src/SchemaForm.vue';
|
|
@@ -4,6 +4,8 @@ import { CheckboxGroup, DatePicker, type DatePickerProps, Form, FormItem, Input,
|
|
|
4
4
|
import { computed, toRef } from 'vue';
|
|
5
5
|
import dayjs from '../../../../libs/dayjs';
|
|
6
6
|
import { type ItemSchema, useAntdForm } from '../../../../antd';
|
|
7
|
+
import { PictureCardUpload } from '../../../components/upload';
|
|
8
|
+
import InputNumberRange from '../../../../antd/components/InputNumberRange.vue';
|
|
7
9
|
|
|
8
10
|
export interface IConfigDetail {
|
|
9
11
|
category_id: number
|
|
@@ -19,7 +21,7 @@ export interface IConfigDetail {
|
|
|
19
21
|
value: any
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
enum FormTypes {
|
|
24
|
+
export enum FormTypes {
|
|
23
25
|
/** 数字输入框 */
|
|
24
26
|
NUMBER_INPUT = 0,
|
|
25
27
|
/** 文本输入框 */
|
|
@@ -36,10 +38,14 @@ enum FormTypes {
|
|
|
36
38
|
SINGLE_SELECT = 6,
|
|
37
39
|
/** 多选下拉选择器 */
|
|
38
40
|
MULTIPLE_SELECT = 7,
|
|
41
|
+
/** 图片上传 */
|
|
42
|
+
IMAGE_UPLOAD = 8,
|
|
39
43
|
/** 单一日期选择器 */
|
|
40
44
|
SINGLE_DATE_PICKER = 13,
|
|
41
45
|
/** 日期范围选择器 */
|
|
42
46
|
RANGE_DATE_PICKER = 14,
|
|
47
|
+
/** 数字范围输入框 */
|
|
48
|
+
NUMBER_RANGE = 15,
|
|
43
49
|
}
|
|
44
50
|
|
|
45
51
|
const antdPropsResolvers: Record<number, (config: IConfigDetail) => IConfigDetail> = {
|
|
@@ -81,6 +87,10 @@ const antdPropsResolvers: Record<number, (config: IConfigDetail) => IConfigDetai
|
|
|
81
87
|
[FormTypes.MULTIPLE_SELECT](config) {
|
|
82
88
|
return this[FormTypes.CHECKBOX](config);
|
|
83
89
|
},
|
|
90
|
+
[FormTypes.IMAGE_UPLOAD](config) {
|
|
91
|
+
config.value = config.value.split(',');
|
|
92
|
+
return config;
|
|
93
|
+
},
|
|
84
94
|
[FormTypes.SINGLE_DATE_PICKER](config) {
|
|
85
95
|
const props: DatePickerProps = {};
|
|
86
96
|
|
|
@@ -115,6 +125,12 @@ const antdPropsResolvers: Record<number, (config: IConfigDetail) => IConfigDetai
|
|
|
115
125
|
|
|
116
126
|
return config;
|
|
117
127
|
},
|
|
128
|
+
[FormTypes.NUMBER_RANGE](config) {
|
|
129
|
+
config.value = config.value === '' ? undefined : config.value.split(',');
|
|
130
|
+
const [min, max] = config.options?.split(',') || [];
|
|
131
|
+
config.options = { min: Number(min), max: Number(max) };
|
|
132
|
+
return config;
|
|
133
|
+
},
|
|
118
134
|
};
|
|
119
135
|
|
|
120
136
|
const antdValueResolvers: Record<number, (value: any) => any> = {
|
|
@@ -130,9 +146,11 @@ const antdValueResolvers: Record<number, (value: any) => any> = {
|
|
|
130
146
|
[FormTypes.MULTIPLE_SELECT](value) {
|
|
131
147
|
if (Array.isArray(value))
|
|
132
148
|
return value.join(',');
|
|
133
|
-
|
|
134
149
|
return value;
|
|
135
150
|
},
|
|
151
|
+
[FormTypes.IMAGE_UPLOAD](value) {
|
|
152
|
+
return value.join(',');
|
|
153
|
+
},
|
|
136
154
|
[FormTypes.SINGLE_DATE_PICKER](value) {
|
|
137
155
|
if (dayjs.isDayjs(value))
|
|
138
156
|
return value.format('YYYY-MM-DD');
|
|
@@ -145,19 +163,26 @@ const antdValueResolvers: Record<number, (value: any) => any> = {
|
|
|
145
163
|
|
|
146
164
|
return value;
|
|
147
165
|
},
|
|
166
|
+
[FormTypes.NUMBER_RANGE](value) {
|
|
167
|
+
if (Array.isArray(value))
|
|
168
|
+
return value.join(',');
|
|
169
|
+
return value;
|
|
170
|
+
},
|
|
148
171
|
};
|
|
149
172
|
</script>
|
|
150
173
|
|
|
151
174
|
<script setup lang="ts">
|
|
152
175
|
const props = withDefaults(defineProps<{
|
|
153
|
-
|
|
176
|
+
formConfig: IConfigDetail[]
|
|
177
|
+
formsOptions?: Partial<Record<FormTypes, any>>;
|
|
154
178
|
disabled?: boolean
|
|
155
179
|
}>(), {
|
|
156
180
|
disabled: false,
|
|
181
|
+
formsOptions: () => ({} as any),
|
|
157
182
|
});
|
|
158
183
|
|
|
159
184
|
const configList = computed(() => {
|
|
160
|
-
const list = cloneDeep(props.
|
|
185
|
+
const list = cloneDeep(props.formConfig);
|
|
161
186
|
|
|
162
187
|
if (!list?.length)
|
|
163
188
|
return;
|
|
@@ -169,7 +194,7 @@ const configList = computed(() => {
|
|
|
169
194
|
|
|
170
195
|
return list;
|
|
171
196
|
});
|
|
172
|
-
const configMap = computed(() => mapKeys(cloneDeep(props.
|
|
197
|
+
const configMap = computed(() => mapKeys(cloneDeep(props.formConfig ?? []), 'key'));
|
|
173
198
|
function formSchema() {
|
|
174
199
|
const _configList = configList.value ?? [];
|
|
175
200
|
const entries = _configList.map(config => [
|
|
@@ -221,9 +246,10 @@ defineExpose({ reset, validate });
|
|
|
221
246
|
<slot
|
|
222
247
|
v-if="$slots[item.key]"
|
|
223
248
|
:name="item.key"
|
|
224
|
-
:state="
|
|
249
|
+
:state="settingForm.state[item.key]"
|
|
225
250
|
:config="item"
|
|
226
251
|
:orginConfig="configMap[item.key]"
|
|
252
|
+
:setState="(v: any) => settingForm.state[item.key] = v"
|
|
227
253
|
/>
|
|
228
254
|
<template v-else>
|
|
229
255
|
<slot v-if="item.form_type === FormTypes.NUMBER_INPUT" :name="FormTypes.NUMBER_INPUT">
|
|
@@ -267,6 +293,10 @@ defineExpose({ reset, validate });
|
|
|
267
293
|
<Select v-model:value="settingForm.state[item.key]" v-bind="item.options" mode="multiple" />
|
|
268
294
|
</slot>
|
|
269
295
|
|
|
296
|
+
<slot v-else-if="item.form_type === FormTypes.IMAGE_UPLOAD" :name="FormTypes.IMAGE_UPLOAD">
|
|
297
|
+
<PictureCardUpload v-model:urls="settingForm.state[item.key]" useUrl v-bind="{ ...formsOptions[item.form_type], ...item.options}" />
|
|
298
|
+
</slot>
|
|
299
|
+
|
|
270
300
|
<slot v-else-if="item.form_type === FormTypes.SINGLE_DATE_PICKER" :name="FormTypes.SINGLE_DATE_PICKER">
|
|
271
301
|
<DatePicker v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
272
302
|
</slot>
|
|
@@ -275,6 +305,10 @@ defineExpose({ reset, validate });
|
|
|
275
305
|
<RangePicker v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
276
306
|
</slot>
|
|
277
307
|
|
|
308
|
+
<slot v-else-if="item.form_type === FormTypes.NUMBER_RANGE" :name="FormTypes.NUMBER_RANGE">
|
|
309
|
+
<InputNumberRange v-model:value="settingForm.state[item.key]" v-bind="item.options" />
|
|
310
|
+
</slot>
|
|
311
|
+
|
|
278
312
|
<span v-else class="text-red">没有找到预设 form_type:{{ item.form_type }},可以通过 {{ item.key }} 插槽自定义</span>
|
|
279
313
|
</template>
|
|
280
314
|
</FormItem>
|
|
@@ -66,7 +66,7 @@ async function submitSetting() {
|
|
|
66
66
|
>
|
|
67
67
|
<component
|
|
68
68
|
:is="$slots.default?.({
|
|
69
|
-
|
|
69
|
+
formConfig: configQuerier.data.value ?? [],
|
|
70
70
|
disabled: settingMutator.isPending.value || props.readonly,
|
|
71
71
|
})[0]"
|
|
72
72
|
:ref="setRefs.form"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import Hash from './src/Hash.vue';
|
|
2
2
|
import Amount from './src/Amount.vue';
|
|
3
|
+
import Amount2 from './src/Amount.v2.vue';
|
|
3
4
|
import Datetime from './src/Datetime.vue';
|
|
4
5
|
import Duration from './src/Duration.vue';
|
|
5
6
|
import IP from './src/IP.vue';
|
|
@@ -10,6 +11,7 @@ export { createDateTypeSwitcher } from './src/createDateTypeSwitcher';
|
|
|
10
11
|
export const Text = {
|
|
11
12
|
Hash,
|
|
12
13
|
Amount,
|
|
14
|
+
Amount2,
|
|
13
15
|
Datetime,
|
|
14
16
|
Duration,
|
|
15
17
|
IP,
|