@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.
Files changed (56) hide show
  1. package/README.md +2 -2
  2. package/app/home/home.vue +4 -1
  3. package/app/home/index.ts +2 -1
  4. package/app/home/welcome.vue +9 -26
  5. package/app/index.ts +8 -2
  6. package/app/layout/header.vue +12 -4
  7. package/app/layout/layout.vue +4 -1
  8. package/app/layout/menu-item.vue +3 -1
  9. package/app/settings/index.ts +3 -2
  10. package/app/settings/settings.vue +9 -7
  11. package/app/settings/theme.vue +4 -1
  12. package/app/status/loading.vue +7 -2
  13. package/app/theme/activity/button.vue +4 -1
  14. package/app/theme/activity/index.vue +12 -4
  15. package/app/theme/activity/item.vue +18 -4
  16. package/app/theme/activity/separator.vue +44 -12
  17. package/app/theme/blank.vue +7 -1
  18. package/app/theme/index.ts +7 -2
  19. package/app/theme/index.vue +6 -2
  20. package/app/theme/menu/menu-item.vue +10 -4
  21. package/app/theme/menu/menu.vue +9 -4
  22. package/client/components/chat/overlay.vue +38 -13
  23. package/client/components/common/k-hint.vue +3 -1
  24. package/client/components/dynamic.vue +2 -1
  25. package/client/components/icons/index.ts +7 -2
  26. package/client/components/index.ts +8 -2
  27. package/client/components/link.ts +5 -2
  28. package/client/components/perms.vue +22 -5
  29. package/client/components/slot.ts +19 -4
  30. package/client/context.ts +12 -4
  31. package/client/data.ts +57 -26
  32. package/client/index.ts +2 -1
  33. package/client/locales/de-DE.yml +47 -0
  34. package/client/locales/en-US.yml +47 -0
  35. package/client/locales/fr-FR.yml +47 -0
  36. package/client/locales/ja-JP.yml +47 -0
  37. package/client/locales/ru-RU.yml +47 -0
  38. package/client/locales/zh-CN.yml +47 -0
  39. package/client/locales/zh-TW.yml +47 -0
  40. package/client/plugins/action.ts +46 -13
  41. package/client/plugins/i18n.ts +118 -3
  42. package/client/plugins/loader.ts +67 -45
  43. package/client/plugins/router.ts +34 -11
  44. package/client/plugins/setting.ts +60 -16
  45. package/client/plugins/theme.ts +51 -11
  46. package/client/utils.ts +7 -2
  47. package/lib/bin.mjs +3 -3
  48. package/lib/{client-CaTn_gVG.mjs → client-C97iYPCr.mjs} +1 -1
  49. package/lib/index.d.ts +3 -4
  50. package/lib/index.mjs +1 -1
  51. package/lib/{src-DXaM1Kgh.mjs → src-BOakssV3.mjs} +2 -2
  52. package/package.json +3 -3
  53. package/src/bin.ts +10 -3
  54. package/src/index.ts +59 -15
  55. package/app/home/welcome.en-US.yml +0 -13
  56. 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()`:读取根 package.json 的 workspaces,为每个工作区包名建立指向其 `client/index.ts` 的别名——未被依赖的 workspace 包不会出现在 node_modules 链接里,必须显式映射才能被 bundler 解析。
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">正在连接到 Koishi 服务器……</k-card>
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
@@ -14,7 +14,8 @@ export default function (ctx: Context) {
14
14
  ctx.page({
15
15
  id: "home",
16
16
  path: "/",
17
- name: "欢迎",
17
+ // name 传 getter:渲染层解析(MaybeRefOrGetter),随界面语言切换
18
+ name: () => ctx.$i18n.t("pages.home"),
18
19
  icon: "activity:home",
19
20
  order: 1000,
20
21
  component: Home,
@@ -4,25 +4,25 @@
4
4
 
5
5
  <!--
6
6
  欢迎卡片:文档 / 社区论坛等入口链接。
7
- 自带 zh-CN 与 en-US 两套文案(局部 vue-i18n 实例,独立于全局语言包),
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('action.docs.title') }}</h2>
19
- <p>{{ t('action.docs.description') }}</p>
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('action.forum.title') }}</h2>
25
- <p>{{ t('action.forum.description') }}</p>
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
- // 组件私有的双语文案:不依赖全局 i18n,确保欢迎页在任何语言环境下可用
39
- const { t, setLocaleMessage } = useI18n({
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(global.endpoint, location.origin).toString();
35
- connect(root, () => new WebSocket(endpoint.replace(/^http/, "ws")));
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
  }
@@ -38,7 +38,10 @@
38
38
  </template>
39
39
 
40
40
  <script lang="ts" setup>
41
- import { type ActionContext, useMenu } from "@koishi-ce/client";
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(props.menuKey as keyof ActionContext);
54
-
55
- defineEmits(["update:isLeftAsideOpen", "update:isRightAsideOpen"]);
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>
@@ -54,7 +54,10 @@
54
54
  </template>
55
55
 
56
56
  <script lang="ts" setup>
57
- import { type LegacyMenuItem, useContext } from "@koishi-ce/client";
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";
@@ -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)(scope.value);
57
+ return (getter as (current: typeof scope.value) => T)(
58
+ scope.value,
59
+ );
58
60
  }
59
61
 
60
62
  function trigger() {
@@ -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>(([id, [{ title }]]) => ({
60
- id,
61
- label: title,
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) {
@@ -35,7 +35,10 @@
35
35
 
36
36
  <script setup lang="ts">
37
37
  import { useContext, useI18nText } from "@koishi-ce/client";
38
- import { type Schema, SchemaBase } from "@koishi-ce/components";
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({
@@ -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 states.filter((state) => state.done.value).length / states.length;
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("text/plain", `activity:${props.data.id}`);
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(([a], [b]) => a.order - b.order);
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({ icon: "activity:ellipsis" } as Activity);
91
+ hidden.unshift({
92
+ icon: "activity:ellipsis",
93
+ } as Activity);
90
94
  }
91
95
  // top 区按 order 逆序输出(order 大者靠上),bottom 区保持正序
92
- const top = list.filter(([data]) => data.position !== "bottom").reverse();
93
- const bottom = list.filter(([data]) => data.position === "bottom");
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 { type Activity, useConfig, useMenu } from "@koishi-ce/client";
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(["__proto__", "constructor", "prototype"]);
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(id: string): Record<string, unknown> {
99
+ function ensureOverride(
100
+ id: string,
101
+ ): Record<string, unknown> {
92
102
  const activities = (config.value.activities ??= {});
93
- if (id === "__proto__" || id === "constructor" || id === "prototype") {
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 { type Activity, useConfig, useContext } from "@koishi-ce/client";
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<Record<Position, Activity[][]>>;
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(["__proto__", "constructor", "prototype"]);
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(id: string): Record<string, unknown> {
62
+ function ensureOverride(
63
+ id: string,
64
+ ): Record<string, unknown> {
53
65
  const activities = (config.value.activities ??= {});
54
- if (id === "__proto__" || id === "constructor" || id === "prototype") {
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(([item]) => item);
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) => i < index && item.order === item.options.order,
120
+ (item, i) =>
121
+ i < index && item.order === item.options.order,
103
122
  );
104
123
  const anchorR = list.findIndex(
105
- (item, i) => i > index && item.order === item.options.order,
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 (let index = anchorL + 1; index < list.length; index++) {
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 (let index = anchorL + 1; index < anchorR; index++) {
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)) / (anchorR - anchorL);
159
+ ((orderR - orderL) * (index - anchorL)) /
160
+ (anchorR - anchorL);
132
161
  }
133
162
  }
134
163
  }
135
164
 
136
165
  // 覆盖配置为空对象时删除该键,避免残留无意义的配置项
137
- if (!Object.keys(override).length && !UNSAFE_KEYS.has(id)) {
166
+ if (
167
+ !Object.keys(override).length &&
168
+ !UNSAFE_KEYS.has(id)
169
+ ) {
138
170
  delete config.value.activities[id];
139
171
  }
140
172
  }
@@ -6,10 +6,16 @@
6
6
  <template>
7
7
  <div class="blank">
8
8
  <h1>404</h1>
9
- <p>如果你看到了这个页面,说明控制台插件已经加载成功,但你尚未安装任何控制台扩展。</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 {
@@ -8,7 +8,11 @@
8
8
  * - 注册活动栏右键菜单 "theme.activity" 及其动作(重置活动栏自定义配置)。
9
9
  */
10
10
 
11
- import { type Context, router, useConfig } from "@koishi-ce/client";
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
  }
@@ -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="正在加载数据……"></div>
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((key) => store[key]);
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 { type MaybeGetter, type MenuItem, useContext } from "@koishi-ce/client";
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 (getter as (current: ReturnType<typeof ctx.$action.createScope>) => T)(
59
- ctx.$action.createScope(),
60
- );
62
+ return (
63
+ getter as (
64
+ current: ReturnType<typeof ctx.$action.createScope>,
65
+ ) => T
66
+ )(ctx.$action.createScope());
61
67
  }
62
68
  </script>
@@ -17,7 +17,10 @@
17
17
  </template>
18
18
 
19
19
  <script lang="ts" setup>
20
- import { type ActiveMenu, useContext } from "@koishi-ce/client";
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 } = el.value.getBoundingClientRect();
35
- const style: Partial<Record<"top" | "right" | "bottom" | "left", string>> =
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 {