@koishi-ce/client 1.0.3 → 1.1.0
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/README.md +2 -2
- package/app/home/home.vue +4 -1
- package/app/home/index.ts +2 -1
- package/app/home/welcome.vue +9 -26
- package/app/index.ts +8 -2
- package/app/layout/header.vue +12 -4
- package/app/layout/layout.vue +4 -1
- package/app/layout/menu-item.vue +3 -1
- package/app/settings/index.ts +3 -2
- package/app/settings/settings.vue +9 -7
- package/app/settings/theme.vue +4 -1
- package/app/status/loading.vue +7 -2
- package/app/theme/activity/button.vue +4 -1
- package/app/theme/activity/index.vue +12 -4
- package/app/theme/activity/item.vue +18 -4
- package/app/theme/activity/separator.vue +44 -12
- package/app/theme/blank.vue +7 -1
- package/app/theme/index.ts +7 -2
- package/app/theme/index.vue +6 -2
- package/app/theme/menu/menu-item.vue +10 -4
- package/app/theme/menu/menu.vue +9 -4
- package/client/components/chat/overlay.vue +38 -13
- package/client/components/common/k-hint.vue +3 -1
- package/client/components/dynamic.vue +2 -1
- package/client/components/icons/index.ts +7 -2
- package/client/components/index.ts +8 -2
- package/client/components/link.ts +5 -2
- package/client/components/perms.vue +22 -5
- package/client/components/slot.ts +19 -4
- package/client/context.ts +12 -4
- package/client/data.ts +57 -26
- package/client/index.ts +2 -1
- package/client/locales/de-DE.yml +47 -0
- package/client/locales/en-US.yml +47 -0
- package/client/locales/fr-FR.yml +47 -0
- package/client/locales/ja-JP.yml +47 -0
- package/client/locales/ru-RU.yml +47 -0
- package/client/locales/zh-CN.yml +47 -0
- package/client/locales/zh-TW.yml +47 -0
- package/client/plugins/action.ts +46 -13
- package/client/plugins/i18n.ts +118 -3
- package/client/plugins/loader.ts +67 -45
- package/client/plugins/router.ts +34 -11
- package/client/plugins/setting.ts +60 -16
- package/client/plugins/theme.ts +51 -11
- package/client/utils.ts +7 -2
- package/lib/bin.mjs +3 -3
- package/lib/{client-CaTn_gVG.mjs → client-C97iYPCr.mjs} +1 -1
- package/lib/index.d.ts +3 -4
- package/lib/index.mjs +1 -1
- package/lib/{src-DXaM1Kgh.mjs → src-BOakssV3.mjs} +2 -2
- package/package.json +3 -3
- package/src/bin.ts +10 -3
- package/src/index.ts +59 -15
- package/app/home/welcome.en-US.yml +0 -13
- package/app/home/welcome.zh-CN.yml +0 -13
package/README.md
CHANGED
|
@@ -25,7 +25,7 @@ Koishi 控制台的浏览器端运行时与组件基座,移植自上游 [koish
|
|
|
25
25
|
|
|
26
26
|
- CLI:`koishi-console build [root]`(`src/bin.ts`)——带 root 时构建单个插件的前端,否则执行宿主控制台总装(产物硬编码到 `plugins/webui/console/dist`,含共享 chunk 与样式);
|
|
27
27
|
- 编程式:`build(root, config)` 构建单个插件前端,`createServer(baseDir)` 创建 Vite 开发中间件;
|
|
28
|
-
- `collectWorkspaceAliases()
|
|
28
|
+
- `collectWorkspaceAliases()`(内部机制,未从包导出):读取根 package.json 的 workspaces,为每个工作区包名建立指向其 `client/index.ts` 的别名——未被依赖的 workspace 包不会出现在 node_modules 链接里,必须显式映射才能被 bundler 解析。
|
|
29
29
|
|
|
30
30
|
## 许可证
|
|
31
31
|
|
|
@@ -49,7 +49,7 @@ The package is consumed as source code rather than shipped as a standalone runti
|
|
|
49
49
|
|
|
50
50
|
- CLI: `koishi-console build [root]` — builds a single plugin frontend when a root is given, otherwise performs the host console assembly (output hardcoded to `plugins/webui/console/dist`).
|
|
51
51
|
- Programmatic: `build(root, config)` and `createServer(baseDir)` (a Vite dev middleware).
|
|
52
|
-
- `collectWorkspaceAliases()` — maps every workspace package to its `client/index.ts`; workspace packages absent from node_modules links can only be resolved through this explicit alias map.
|
|
52
|
+
- `collectWorkspaceAliases()` (an internal mechanism, not exported from the package) — maps every workspace package to its `client/index.ts`; workspace packages absent from node_modules links can only be resolved through this explicit alias map.
|
|
53
53
|
|
|
54
54
|
## License
|
|
55
55
|
|
package/app/home/home.vue
CHANGED
|
@@ -16,14 +16,17 @@
|
|
|
16
16
|
</k-slot>
|
|
17
17
|
</el-scrollbar>
|
|
18
18
|
<div v-else>
|
|
19
|
-
<k-card class="connect"
|
|
19
|
+
<k-card class="connect">{{ t('home.connecting') }}</k-card>
|
|
20
20
|
</div>
|
|
21
21
|
</k-layout>
|
|
22
22
|
</template>
|
|
23
23
|
|
|
24
24
|
<script lang="ts" setup>
|
|
25
25
|
import { global, socket } from "@koishi-ce/client";
|
|
26
|
+
import { useI18n } from "vue-i18n";
|
|
26
27
|
import Welcome from "./welcome.vue";
|
|
28
|
+
|
|
29
|
+
const { t } = useI18n();
|
|
27
30
|
</script>
|
|
28
31
|
|
|
29
32
|
<style lang="scss">
|
package/app/home/index.ts
CHANGED
package/app/home/welcome.vue
CHANGED
|
@@ -4,25 +4,25 @@
|
|
|
4
4
|
|
|
5
5
|
<!--
|
|
6
6
|
欢迎卡片:文档 / 社区论坛等入口链接。
|
|
7
|
-
|
|
7
|
+
文案走宿主全局词典(client/locales/,7 语种),
|
|
8
8
|
其余扩展可通过 "welcome-choice" 插槽追加自定义入口。
|
|
9
9
|
-->
|
|
10
10
|
<template>
|
|
11
11
|
<div class="k-card welcome">
|
|
12
|
-
<h1>{{ t('title') }}</h1>
|
|
13
|
-
<p>{{ t('description') }}</p>
|
|
12
|
+
<h1>{{ t('welcome.title') }}</h1>
|
|
13
|
+
<p>{{ t('welcome.description') }}</p>
|
|
14
14
|
<div class="choices">
|
|
15
15
|
<k-slot name="welcome-choice">
|
|
16
16
|
<k-slot-item :order="1000">
|
|
17
17
|
<a class="choice" href="https://koishi.chat" rel="noopener noreferer" target="_blank">
|
|
18
|
-
<h2>{{ t('
|
|
19
|
-
<p>{{ t('
|
|
18
|
+
<h2>{{ t('welcome.docs.title') }}</h2>
|
|
19
|
+
<p>{{ t('welcome.docs.description') }}</p>
|
|
20
20
|
</a>
|
|
21
21
|
</k-slot-item>
|
|
22
22
|
<k-slot-item :order="500">
|
|
23
23
|
<a class="choice" href="https://k.ilharp.cc" rel="noopener noreferer" target="_blank">
|
|
24
|
-
<h2>{{ t('
|
|
25
|
-
<p>{{ t('
|
|
24
|
+
<h2>{{ t('welcome.forum.title') }}</h2>
|
|
25
|
+
<p>{{ t('welcome.forum.description') }}</p>
|
|
26
26
|
</a>
|
|
27
27
|
</k-slot-item>
|
|
28
28
|
</k-slot>
|
|
@@ -32,26 +32,9 @@
|
|
|
32
32
|
|
|
33
33
|
<script lang="ts" setup>
|
|
34
34
|
import { useI18n } from "vue-i18n";
|
|
35
|
-
import enUS from "./welcome.en-US.yml";
|
|
36
|
-
import zhCN from "./welcome.zh-CN.yml";
|
|
37
35
|
|
|
38
|
-
//
|
|
39
|
-
const { t
|
|
40
|
-
messages: {
|
|
41
|
-
"zh-CN": zhCN,
|
|
42
|
-
"en-US": enUS,
|
|
43
|
-
},
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
// 开发模式下 yml 文案改动即时热替换,无需整页刷新
|
|
47
|
-
if (import.meta.hot) {
|
|
48
|
-
import.meta.hot.accept("./welcome.zh-CN.yml", (module) => {
|
|
49
|
-
setLocaleMessage("zh-CN", module.default);
|
|
50
|
-
});
|
|
51
|
-
import.meta.hot.accept("./welcome.en-US.yml", (module) => {
|
|
52
|
-
setLocaleMessage("en-US", module.default);
|
|
53
|
-
});
|
|
54
|
-
}
|
|
36
|
+
// 全局 composer:词典由宿主 $i18n 服务在挂载前注入
|
|
37
|
+
const { t } = useI18n();
|
|
55
38
|
</script>
|
|
56
39
|
|
|
57
40
|
<style lang="scss">
|
package/app/index.ts
CHANGED
|
@@ -31,6 +31,12 @@ root.start();
|
|
|
31
31
|
|
|
32
32
|
if (!global.static) {
|
|
33
33
|
// global.static 为真表示纯静态构建(无后端),跳过连接
|
|
34
|
-
const endpoint = new URL(
|
|
35
|
-
|
|
34
|
+
const endpoint = new URL(
|
|
35
|
+
global.endpoint,
|
|
36
|
+
location.origin,
|
|
37
|
+
).toString();
|
|
38
|
+
connect(
|
|
39
|
+
root,
|
|
40
|
+
() => new WebSocket(endpoint.replace(/^http/, "ws")),
|
|
41
|
+
);
|
|
36
42
|
}
|
package/app/layout/header.vue
CHANGED
|
@@ -38,7 +38,10 @@
|
|
|
38
38
|
</template>
|
|
39
39
|
|
|
40
40
|
<script lang="ts" setup>
|
|
41
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
type ActionContext,
|
|
43
|
+
useMenu,
|
|
44
|
+
} from "@koishi-ce/client";
|
|
42
45
|
import { useRoute } from "vue-router";
|
|
43
46
|
|
|
44
47
|
const props = defineProps<{
|
|
@@ -50,9 +53,14 @@ const props = defineProps<{
|
|
|
50
53
|
|
|
51
54
|
// 溢出菜单按钮的点击触发器:打开 menuKey 对应的上下文菜单
|
|
52
55
|
// (menuKey 为运行期字符串,这里断言为菜单键类型以复用字面量重载)
|
|
53
|
-
const trigger = useMenu(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
const trigger = useMenu(
|
|
57
|
+
props.menuKey as keyof ActionContext,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
defineEmits([
|
|
61
|
+
"update:isLeftAsideOpen",
|
|
62
|
+
"update:isRightAsideOpen",
|
|
63
|
+
]);
|
|
56
64
|
|
|
57
65
|
const route = useRoute();
|
|
58
66
|
</script>
|
package/app/layout/layout.vue
CHANGED
|
@@ -54,7 +54,10 @@
|
|
|
54
54
|
</template>
|
|
55
55
|
|
|
56
56
|
<script lang="ts" setup>
|
|
57
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
type LegacyMenuItem,
|
|
59
|
+
useContext,
|
|
60
|
+
} from "@koishi-ce/client";
|
|
58
61
|
import { computed, ref, useSlots } from "vue";
|
|
59
62
|
import { useRoute } from "vue-router";
|
|
60
63
|
import LayoutHeader from "./header.vue";
|
package/app/layout/menu-item.vue
CHANGED
|
@@ -54,7 +54,9 @@ const scope = computed(() =>
|
|
|
54
54
|
function toValue<T>(getter: MaybeGetter<T>): T {
|
|
55
55
|
if (typeof getter !== "function") return getter;
|
|
56
56
|
// 收窄到 MaybeGetter 的函数分支(以作用域为参的 getter)
|
|
57
|
-
return (getter as (current: typeof scope.value) => T)(
|
|
57
|
+
return (getter as (current: typeof scope.value) => T)(
|
|
58
|
+
scope.value,
|
|
59
|
+
);
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
function trigger() {
|
package/app/settings/index.ts
CHANGED
|
@@ -16,7 +16,8 @@ import Theme from "./theme.vue";
|
|
|
16
16
|
export default function (ctx: Context) {
|
|
17
17
|
ctx.page({
|
|
18
18
|
path: "/settings/:name*",
|
|
19
|
-
name
|
|
19
|
+
// name / title 传 getter:渲染层解析,随界面语言切换
|
|
20
|
+
name: () => ctx.$i18n.t("pages.settings"),
|
|
20
21
|
icon: "activity:settings",
|
|
21
22
|
position: "bottom",
|
|
22
23
|
order: -100,
|
|
@@ -31,7 +32,7 @@ export default function (ctx: Context) {
|
|
|
31
32
|
|
|
32
33
|
ctx.settings({
|
|
33
34
|
id: "status",
|
|
34
|
-
title: "
|
|
35
|
+
title: () => ctx.$i18n.t("settings.status"),
|
|
35
36
|
order: 800,
|
|
36
37
|
});
|
|
37
38
|
|
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
<!--
|
|
6
6
|
用户设置页:左侧 el-tree 分组导航 + 右侧当前分组的内容。
|
|
7
7
|
分组内的条目三种形态二选一渲染:组件型(component)、表单型(schema),
|
|
8
|
-
disabled
|
|
8
|
+
disabled 的条目直接跳过。分区标题支持 getter(随界面语言实时切换)。
|
|
9
9
|
-->
|
|
10
10
|
<template>
|
|
11
11
|
<k-layout main="page-settings">
|
|
12
12
|
<template #header>
|
|
13
|
-
{{ ctx.internal.settings[path][0]?.title }}
|
|
13
|
+
{{ toValue(ctx.internal.settings[path][0]?.title) }}
|
|
14
14
|
</template>
|
|
15
15
|
|
|
16
16
|
<template #left>
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
|
|
39
39
|
<script lang="ts" setup>
|
|
40
40
|
import { useConfig, useContext } from "@koishi-ce/client";
|
|
41
|
-
import { computed } from "vue";
|
|
41
|
+
import { computed, toValue } from "vue";
|
|
42
42
|
import { useRoute, useRouter } from "vue-router";
|
|
43
43
|
|
|
44
44
|
const route = useRoute();
|
|
@@ -56,10 +56,12 @@ interface Tree {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
const data = computed(() =>
|
|
59
|
-
Object.entries(ctx.internal.settings).map<Tree>(
|
|
60
|
-
id,
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
Object.entries(ctx.internal.settings).map<Tree>(
|
|
60
|
+
([id, [{ title }]]) => ({
|
|
61
|
+
id,
|
|
62
|
+
label: toValue(title),
|
|
63
|
+
}),
|
|
64
|
+
),
|
|
63
65
|
);
|
|
64
66
|
|
|
65
67
|
function handleClick(tree: Tree) {
|
package/app/settings/theme.vue
CHANGED
|
@@ -35,7 +35,10 @@
|
|
|
35
35
|
|
|
36
36
|
<script setup lang="ts">
|
|
37
37
|
import { useContext, useI18nText } from "@koishi-ce/client";
|
|
38
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
type Schema,
|
|
40
|
+
SchemaBase,
|
|
41
|
+
} from "@koishi-ce/components";
|
|
39
42
|
import { computed, type PropType } from "vue";
|
|
40
43
|
|
|
41
44
|
defineProps({
|
package/app/status/loading.vue
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<template>
|
|
10
10
|
<k-status v-if="progress < 1">
|
|
11
11
|
<el-progress :indeterminate="!store.entry" :percentage="progress * 100">
|
|
12
|
-
|
|
12
|
+
{{ t('loading.page') }}
|
|
13
13
|
</el-progress>
|
|
14
14
|
</k-status>
|
|
15
15
|
</template>
|
|
@@ -17,11 +17,16 @@
|
|
|
17
17
|
<script lang="ts" setup>
|
|
18
18
|
import { store, useContext } from "@koishi-ce/client";
|
|
19
19
|
import { computed } from "vue";
|
|
20
|
+
import { useI18n } from "vue-i18n";
|
|
20
21
|
|
|
21
22
|
const ctx = useContext();
|
|
23
|
+
const { t } = useI18n();
|
|
22
24
|
|
|
23
25
|
const progress = computed(() => {
|
|
24
26
|
const states = Object.values(ctx.$loader.extensions);
|
|
25
|
-
return
|
|
27
|
+
return (
|
|
28
|
+
states.filter((state) => state.done.value).length /
|
|
29
|
+
states.length
|
|
30
|
+
);
|
|
26
31
|
});
|
|
27
32
|
</script>
|
|
@@ -33,7 +33,10 @@ const isDragging = ref(false);
|
|
|
33
33
|
function handleDragStart(event: DragEvent) {
|
|
34
34
|
isDragging.value = true;
|
|
35
35
|
// 约定的拖拽协议:以 "activity:" 前缀 + 活动 id 标识被拖动的活动项
|
|
36
|
-
event.dataTransfer.setData(
|
|
36
|
+
event.dataTransfer.setData(
|
|
37
|
+
"text/plain",
|
|
38
|
+
`activity:${props.data.id}`,
|
|
39
|
+
);
|
|
37
40
|
}
|
|
38
41
|
|
|
39
42
|
function handleDragEnd(event: DragEvent) {
|
|
@@ -72,7 +72,9 @@ const groups = computed(() => {
|
|
|
72
72
|
delete available[id];
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
|
-
const list = Object.values(available).sort(
|
|
75
|
+
const list = Object.values(available).sort(
|
|
76
|
+
([a], [b]) => a.order - b.order,
|
|
77
|
+
);
|
|
76
78
|
// 放不下时:从头部取出溢出项折叠成一个组,top 位优先、同位再按 order 排,
|
|
77
79
|
// 并以 "..." 图标作为该组的展示入口
|
|
78
80
|
if (list.length * unit > total) {
|
|
@@ -86,11 +88,17 @@ const groups = computed(() => {
|
|
|
86
88
|
return scale;
|
|
87
89
|
})
|
|
88
90
|
.flat();
|
|
89
|
-
hidden.unshift({
|
|
91
|
+
hidden.unshift({
|
|
92
|
+
icon: "activity:ellipsis",
|
|
93
|
+
} as Activity);
|
|
90
94
|
}
|
|
91
95
|
// top 区按 order 逆序输出(order 大者靠上),bottom 区保持正序
|
|
92
|
-
const top = list
|
|
93
|
-
|
|
96
|
+
const top = list
|
|
97
|
+
.filter(([data]) => data.position !== "bottom")
|
|
98
|
+
.reverse();
|
|
99
|
+
const bottom = list.filter(
|
|
100
|
+
([data]) => data.position === "bottom",
|
|
101
|
+
);
|
|
94
102
|
return { top, bottom, hidden };
|
|
95
103
|
});
|
|
96
104
|
|
|
@@ -38,7 +38,11 @@
|
|
|
38
38
|
</template>
|
|
39
39
|
|
|
40
40
|
<script lang="ts" setup>
|
|
41
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
type Activity,
|
|
43
|
+
useConfig,
|
|
44
|
+
useMenu,
|
|
45
|
+
} from "@koishi-ce/client";
|
|
42
46
|
import type { Placement } from "element-plus";
|
|
43
47
|
import { computed, ref, watch } from "vue";
|
|
44
48
|
import { useRoute } from "vue-router";
|
|
@@ -84,13 +88,23 @@ const config = useConfig();
|
|
|
84
88
|
|
|
85
89
|
// 原型链保留键:这类键在普通对象上会触发原型链存取器,禁止作为配置键读写
|
|
86
90
|
// (id 来自拖拽事件的 dataTransfer 文本,属外部输入)
|
|
87
|
-
const UNSAFE_KEYS = new Set([
|
|
91
|
+
const UNSAFE_KEYS = new Set([
|
|
92
|
+
"__proto__",
|
|
93
|
+
"constructor",
|
|
94
|
+
"prototype",
|
|
95
|
+
]);
|
|
88
96
|
|
|
89
97
|
/** 取某活动的覆盖配置(不存在则创建);保留键返回一次性空对象,防原型污染。
|
|
90
98
|
* 守卫须用显式字符串比较(Set.has 形式 CodeQL 无法识别为阻断) */
|
|
91
|
-
function ensureOverride(
|
|
99
|
+
function ensureOverride(
|
|
100
|
+
id: string,
|
|
101
|
+
): Record<string, unknown> {
|
|
92
102
|
const activities = (config.value.activities ??= {});
|
|
93
|
-
if (
|
|
103
|
+
if (
|
|
104
|
+
id === "__proto__" ||
|
|
105
|
+
id === "constructor" ||
|
|
106
|
+
id === "prototype"
|
|
107
|
+
) {
|
|
94
108
|
return {};
|
|
95
109
|
}
|
|
96
110
|
return (activities[id] ??= {});
|
|
@@ -18,7 +18,11 @@
|
|
|
18
18
|
</template>
|
|
19
19
|
|
|
20
20
|
<script lang="ts" setup>
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
type Activity,
|
|
23
|
+
useConfig,
|
|
24
|
+
useContext,
|
|
25
|
+
} from "@koishi-ce/client";
|
|
22
26
|
import { type ComputedRef, inject, ref } from "vue";
|
|
23
27
|
|
|
24
28
|
type Position = "top" | "bottom";
|
|
@@ -28,7 +32,9 @@ const props = defineProps<{
|
|
|
28
32
|
position: Position;
|
|
29
33
|
}>();
|
|
30
34
|
|
|
31
|
-
const groups = inject("groups") as ComputedRef<
|
|
35
|
+
const groups = inject("groups") as ComputedRef<
|
|
36
|
+
Record<Position, Activity[][]>
|
|
37
|
+
>;
|
|
32
38
|
|
|
33
39
|
const hasDragOver = ref(false);
|
|
34
40
|
|
|
@@ -45,13 +51,23 @@ const ctx = useContext();
|
|
|
45
51
|
|
|
46
52
|
// 原型链保留键:这类键在普通对象上会触发原型链存取器,禁止作为配置键读写
|
|
47
53
|
// (id 来自拖拽事件的 dataTransfer 文本,属外部输入)
|
|
48
|
-
const UNSAFE_KEYS = new Set([
|
|
54
|
+
const UNSAFE_KEYS = new Set([
|
|
55
|
+
"__proto__",
|
|
56
|
+
"constructor",
|
|
57
|
+
"prototype",
|
|
58
|
+
]);
|
|
49
59
|
|
|
50
60
|
/** 取某活动的覆盖配置(不存在则创建);保留键返回一次性空对象,防原型污染。
|
|
51
61
|
* 守卫须用显式字符串比较(Set.has 形式 CodeQL 无法识别为阻断) */
|
|
52
|
-
function ensureOverride(
|
|
62
|
+
function ensureOverride(
|
|
63
|
+
id: string,
|
|
64
|
+
): Record<string, unknown> {
|
|
53
65
|
const activities = (config.value.activities ??= {});
|
|
54
|
-
if (
|
|
66
|
+
if (
|
|
67
|
+
id === "__proto__" ||
|
|
68
|
+
id === "constructor" ||
|
|
69
|
+
id === "prototype"
|
|
70
|
+
) {
|
|
55
71
|
return {};
|
|
56
72
|
}
|
|
57
73
|
return (activities[id] ??= {});
|
|
@@ -63,7 +79,9 @@ function handleDrop(event: DragEvent) {
|
|
|
63
79
|
// 只响应活动栏自身的拖拽协议
|
|
64
80
|
if (!text.startsWith("activity:")) return;
|
|
65
81
|
const id = text.slice(9);
|
|
66
|
-
const list = groups.value[props.position].map(
|
|
82
|
+
const list = groups.value[props.position].map(
|
|
83
|
+
([item]) => item,
|
|
84
|
+
);
|
|
67
85
|
const oldIndex = list.findIndex((item) => item.id === id);
|
|
68
86
|
// 落点即原位(含紧邻原位的前一格)时无需移动
|
|
69
87
|
if (
|
|
@@ -99,10 +117,12 @@ function handleDrop(event: DragEvent) {
|
|
|
99
117
|
// 介于两锚点之间的项按线性插值重算 order;
|
|
100
118
|
// 只有一侧锚点时按步长 100 单向递增 / 递减;两侧都没有则恢复默认
|
|
101
119
|
const anchorL = list.findLastIndex(
|
|
102
|
-
(item, i) =>
|
|
120
|
+
(item, i) =>
|
|
121
|
+
i < index && item.order === item.options.order,
|
|
103
122
|
);
|
|
104
123
|
const anchorR = list.findIndex(
|
|
105
|
-
(item, i) =>
|
|
124
|
+
(item, i) =>
|
|
125
|
+
i > index && item.order === item.options.order,
|
|
106
126
|
);
|
|
107
127
|
if (anchorL === -1) {
|
|
108
128
|
if (anchorR === -1) {
|
|
@@ -117,24 +137,36 @@ function handleDrop(event: DragEvent) {
|
|
|
117
137
|
} else {
|
|
118
138
|
if (anchorR === -1) {
|
|
119
139
|
let order = list[anchorL].options.order;
|
|
120
|
-
for (
|
|
140
|
+
for (
|
|
141
|
+
let index = anchorL + 1;
|
|
142
|
+
index < list.length;
|
|
143
|
+
index++
|
|
144
|
+
) {
|
|
121
145
|
const override = ensureOverride(list[index].id);
|
|
122
146
|
override.order = order -= 100;
|
|
123
147
|
}
|
|
124
148
|
} else {
|
|
125
149
|
let orderL = list[anchorL].options.order;
|
|
126
150
|
let orderR = list[anchorR].options.order;
|
|
127
|
-
for (
|
|
151
|
+
for (
|
|
152
|
+
let index = anchorL + 1;
|
|
153
|
+
index < anchorR;
|
|
154
|
+
index++
|
|
155
|
+
) {
|
|
128
156
|
const override = ensureOverride(list[index].id);
|
|
129
157
|
override.order =
|
|
130
158
|
orderL +
|
|
131
|
-
((orderR - orderL) * (index - anchorL)) /
|
|
159
|
+
((orderR - orderL) * (index - anchorL)) /
|
|
160
|
+
(anchorR - anchorL);
|
|
132
161
|
}
|
|
133
162
|
}
|
|
134
163
|
}
|
|
135
164
|
|
|
136
165
|
// 覆盖配置为空对象时删除该键,避免残留无意义的配置项
|
|
137
|
-
if (
|
|
166
|
+
if (
|
|
167
|
+
!Object.keys(override).length &&
|
|
168
|
+
!UNSAFE_KEYS.has(id)
|
|
169
|
+
) {
|
|
138
170
|
delete config.value.activities[id];
|
|
139
171
|
}
|
|
140
172
|
}
|
package/app/theme/blank.vue
CHANGED
|
@@ -6,10 +6,16 @@
|
|
|
6
6
|
<template>
|
|
7
7
|
<div class="blank">
|
|
8
8
|
<h1>404</h1>
|
|
9
|
-
<p
|
|
9
|
+
<p>{{ t('blank.hint') }}</p>
|
|
10
10
|
</div>
|
|
11
11
|
</template>
|
|
12
12
|
|
|
13
|
+
<script lang="ts" setup>
|
|
14
|
+
import { useI18n } from "vue-i18n";
|
|
15
|
+
|
|
16
|
+
const { t } = useI18n();
|
|
17
|
+
</script>
|
|
18
|
+
|
|
13
19
|
<style lang="scss" scoped>
|
|
14
20
|
|
|
15
21
|
.blank {
|
package/app/theme/index.ts
CHANGED
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
* - 注册活动栏右键菜单 "theme.activity" 及其动作(重置活动栏自定义配置)。
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
type Context,
|
|
13
|
+
router,
|
|
14
|
+
useConfig,
|
|
15
|
+
} from "@koishi-ce/client";
|
|
12
16
|
import App from "./index.vue";
|
|
13
17
|
|
|
14
18
|
export default function (ctx: Context) {
|
|
@@ -35,7 +39,8 @@ export default function (ctx: Context) {
|
|
|
35
39
|
// label: '活动栏设置',
|
|
36
40
|
// }, {
|
|
37
41
|
id: ".reset",
|
|
38
|
-
label
|
|
42
|
+
// label 支持 getter(menu-item 渲染层 toValue 解析),随界面语言切换
|
|
43
|
+
label: () => ctx.$i18n.t("menu.resetActivity"),
|
|
39
44
|
},
|
|
40
45
|
]);
|
|
41
46
|
}
|
package/app/theme/index.vue
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
<component :is="Component"></component>
|
|
15
15
|
</keep-alive>
|
|
16
16
|
</router-view>
|
|
17
|
-
<div class="loading" v-else v-loading="true" element-loading-text="
|
|
17
|
+
<div class="loading" v-else v-loading="true" :element-loading-text="t('loading.data')"></div>
|
|
18
18
|
<status-bar></status-bar>
|
|
19
19
|
<menu-list></menu-list>
|
|
20
20
|
</template>
|
|
@@ -22,17 +22,21 @@
|
|
|
22
22
|
<script lang="ts" setup>
|
|
23
23
|
import { store } from "@koishi-ce/client";
|
|
24
24
|
import { computed } from "vue";
|
|
25
|
+
import { useI18n } from "vue-i18n";
|
|
25
26
|
import { useRoute } from "vue-router";
|
|
26
27
|
import ActivityBar from "./activity/index.vue";
|
|
27
28
|
import MenuList from "./menu/index.vue";
|
|
28
29
|
import StatusBar from "./status.vue";
|
|
29
30
|
|
|
31
|
+
const { t } = useI18n();
|
|
30
32
|
const route = useRoute();
|
|
31
33
|
|
|
32
34
|
// 页面就绪判定:未声明所需字段的页面恒为已就绪;
|
|
33
35
|
// 否则要求 meta.activity.fields 中每个 store 字段都有数据
|
|
34
36
|
const loaded = computed(() => {
|
|
35
37
|
if (!route.meta.activity?.fields) return true;
|
|
36
|
-
return route.meta.activity.fields.every(
|
|
38
|
+
return route.meta.activity.fields.every(
|
|
39
|
+
(key) => store[key],
|
|
40
|
+
);
|
|
37
41
|
});
|
|
38
42
|
</script>
|
|
@@ -21,7 +21,11 @@
|
|
|
21
21
|
</template>
|
|
22
22
|
|
|
23
23
|
<script lang="ts" setup>
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
type MaybeGetter,
|
|
26
|
+
type MenuItem,
|
|
27
|
+
useContext,
|
|
28
|
+
} from "@koishi-ce/client";
|
|
25
29
|
import { computed } from "vue";
|
|
26
30
|
|
|
27
31
|
const props = defineProps<MenuItem & { prefix: string }>();
|
|
@@ -55,8 +59,10 @@ const icon = computed(() => toValue(props.icon));
|
|
|
55
59
|
function toValue<T>(getter: MaybeGetter<T>): T {
|
|
56
60
|
if (typeof getter !== "function") return getter;
|
|
57
61
|
// 收窄到 MaybeGetter 的函数分支(以作用域为参的 getter)
|
|
58
|
-
return (
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
return (
|
|
63
|
+
getter as (
|
|
64
|
+
current: ReturnType<typeof ctx.$action.createScope>,
|
|
65
|
+
) => T
|
|
66
|
+
)(ctx.$action.createScope());
|
|
61
67
|
}
|
|
62
68
|
</script>
|
package/app/theme/menu/menu.vue
CHANGED
|
@@ -17,7 +17,10 @@
|
|
|
17
17
|
</template>
|
|
18
18
|
|
|
19
19
|
<script lang="ts" setup>
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
type ActiveMenu,
|
|
22
|
+
useContext,
|
|
23
|
+
} from "@koishi-ce/client";
|
|
21
24
|
import { ref } from "vue";
|
|
22
25
|
import MenuItem from "./menu-item.vue";
|
|
23
26
|
|
|
@@ -31,9 +34,11 @@ const el = ref<HTMLElement>();
|
|
|
31
34
|
// 结合菜单自身渲染后的实际宽高计算定位;尚未挂载时先隐藏避免闪烁
|
|
32
35
|
const getStyle = () => {
|
|
33
36
|
if (!el.value) return { visibility: "hidden" };
|
|
34
|
-
const { height, width } =
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
const { height, width } =
|
|
38
|
+
el.value.getBoundingClientRect();
|
|
39
|
+
const style: Partial<
|
|
40
|
+
Record<"top" | "right" | "bottom" | "left", string>
|
|
41
|
+
> = {};
|
|
37
42
|
if (props.relative.right + width > window.innerWidth) {
|
|
38
43
|
style.right = `${window.innerWidth - props.relative.left}px`;
|
|
39
44
|
} else {
|