@koishi-ce/client 1.0.2 → 1.0.4
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 +56 -0
- package/app/home/home.vue +4 -0
- package/app/home/index.ts +4 -0
- package/app/home/welcome.en-US.yml +4 -0
- package/app/home/welcome.vue +16 -6
- package/app/home/welcome.zh-CN.yml +4 -0
- package/app/index.scss +4 -0
- package/app/index.ts +12 -2
- package/app/layout/header.vue +16 -4
- package/app/layout/index.ts +4 -0
- package/app/layout/layout.vue +8 -1
- package/app/layout/menu-item.vue +7 -1
- package/app/settings/index.ts +4 -0
- package/app/settings/settings.vue +10 -4
- package/app/settings/theme.vue +8 -1
- package/app/shims.d.ts +3 -0
- package/app/status/index.ts +4 -0
- package/app/status/loading.vue +8 -1
- package/app/status/status.vue +4 -0
- package/app/styles/element.scss +4 -0
- package/app/styles/hc.scss +4 -0
- package/app/styles/index.scss +4 -0
- package/app/styles/index.ts +4 -0
- package/app/styles/layout.scss +4 -0
- package/app/theme/activity/button.vue +8 -1
- package/app/theme/activity/index.vue +16 -4
- package/app/theme/activity/item.vue +22 -4
- package/app/theme/activity/separator.vue +48 -12
- package/app/theme/activity/utils.ts +4 -0
- package/app/theme/blank.vue +4 -0
- package/app/theme/index.ts +9 -1
- package/app/theme/index.vue +7 -1
- package/app/theme/menu/index.vue +4 -0
- package/app/theme/menu/menu-item.vue +14 -4
- package/app/theme/menu/menu.vue +13 -4
- package/app/theme/status.vue +4 -0
- package/client/components/chat/image.vue +4 -0
- package/client/components/chat/overlay.vue +34 -8
- package/client/components/chat/utils.ts +4 -0
- package/client/components/common/index.ts +4 -0
- package/client/components/common/k-button.vue +4 -0
- package/client/components/common/k-hint.vue +7 -1
- package/client/components/common/k-tab.vue +4 -0
- package/client/components/dynamic.vue +6 -1
- package/client/components/icons/activity/default.vue +4 -0
- package/client/components/icons/activity/ellipsis.vue +4 -0
- package/client/components/icons/activity/home.vue +4 -0
- package/client/components/icons/activity/moon.vue +4 -0
- package/client/components/icons/activity/settings.vue +4 -0
- package/client/components/icons/activity/sun.vue +4 -0
- package/client/components/icons/index.ts +11 -2
- package/client/components/icons/style.scss +4 -0
- package/client/components/icons/svg/arrow-left.vue +4 -0
- package/client/components/icons/svg/arrow-right.vue +4 -0
- package/client/components/icons/svg/box-open.vue +4 -0
- package/client/components/icons/svg/check-full.vue +4 -0
- package/client/components/icons/svg/chevron-down.vue +4 -0
- package/client/components/icons/svg/chevron-left.vue +4 -0
- package/client/components/icons/svg/chevron-right.vue +4 -0
- package/client/components/icons/svg/chevron-up.vue +4 -0
- package/client/components/icons/svg/clipboard-list.vue +4 -0
- package/client/components/icons/svg/edit.vue +4 -0
- package/client/components/icons/svg/exclamation-full.vue +4 -0
- package/client/components/icons/svg/expand.vue +4 -0
- package/client/components/icons/svg/file-archive.vue +4 -0
- package/client/components/icons/svg/filter.vue +4 -0
- package/client/components/icons/svg/github.vue +4 -0
- package/client/components/icons/svg/gitlab.vue +4 -0
- package/client/components/icons/svg/info-full.vue +4 -0
- package/client/components/icons/svg/koishi.vue +4 -0
- package/client/components/icons/svg/link.vue +4 -0
- package/client/components/icons/svg/paper-plane.vue +4 -0
- package/client/components/icons/svg/question-empty.vue +4 -0
- package/client/components/icons/svg/redo.vue +4 -0
- package/client/components/icons/svg/search-minus.vue +4 -0
- package/client/components/icons/svg/search-plus.vue +4 -0
- package/client/components/icons/svg/search.vue +4 -0
- package/client/components/icons/svg/star-empty.vue +4 -0
- package/client/components/icons/svg/star-full.vue +4 -0
- package/client/components/icons/svg/start.vue +4 -0
- package/client/components/icons/svg/tag.vue +4 -0
- package/client/components/icons/svg/times-full.vue +4 -0
- package/client/components/icons/svg/tools.vue +4 -0
- package/client/components/icons/svg/undo.vue +4 -0
- package/client/components/icons/svg/user.vue +4 -0
- package/client/components/index.ts +12 -2
- package/client/components/layout/card.vue +4 -0
- package/client/components/layout/content.vue +4 -0
- package/client/components/layout/empty.vue +4 -0
- package/client/components/layout/index.ts +4 -0
- package/client/components/layout/tab-group.vue +4 -0
- package/client/components/layout/tab-item.vue +4 -0
- package/client/components/link.ts +9 -2
- package/client/components/perms.vue +26 -5
- package/client/components/slot.ts +23 -4
- package/client/context.ts +16 -4
- package/client/data.ts +61 -26
- package/client/index.ts +6 -1
- package/client/plugins/action.ts +50 -13
- package/client/plugins/i18n.ts +4 -0
- package/client/plugins/loader.ts +71 -45
- package/client/plugins/router.ts +38 -11
- package/client/plugins/setting.ts +49 -12
- package/client/plugins/theme.ts +25 -6
- package/client/shims.d.ts +3 -0
- package/client/utils.ts +11 -2
- package/global.d.ts +4 -0
- package/lib/bin.mjs +7 -3
- package/lib/{client-CaTn_gVG.mjs → client-C97iYPCr.mjs} +1 -1
- package/lib/index.mjs +1 -1
- package/lib/{src-lTfcJ1xA.mjs → src-BOakssV3.mjs} +8 -2
- package/package.json +6 -2
- package/src/bin.ts +13 -3
- package/src/index.ts +72 -14
- package/src/yaml.ts +3 -0
package/README.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# @koishi-ce/client
|
|
2
|
+
|
|
3
|
+
**简体中文** | [English](#english)
|
|
4
|
+
|
|
5
|
+
Koishi 控制台的浏览器端运行时与组件基座,移植自上游 [koishijs/webui](https://github.com/koishijs/webui) 的 `packages/client`。它承载控制台前端的全部基础设施——根 Context、服务与插件系统、路由与活动栏、状态管理、主题、动作与菜单——同时提供把各控制台插件的前端组装为可部署产物的编程式构建器。
|
|
6
|
+
|
|
7
|
+
与 [`@koishi-ce/plugin-console`](https://github.com/Koishi-CE/koishi/tree/main/plugins/webui/console)(node 侧宿主)的分工:本包运行在浏览器,是前端基座;plugin-console 运行在 node,负责 WebSocket 通道与静态资源托管。宿主总装产物直接写入 plugin-console 的 `dist` 目录。
|
|
8
|
+
|
|
9
|
+
## 主要导出
|
|
10
|
+
|
|
11
|
+
- `root` 与 `Context`:唯一根上下文实例与其类型;
|
|
12
|
+
- `defineExtension`:控制台插件前端扩展的定义入口;
|
|
13
|
+
- `Service`:前端侧服务基类;
|
|
14
|
+
- 路由与活动:`RouterService`、`Activity`、`redirectTo`;
|
|
15
|
+
- 状态:`store`(reactive 全局状态)及 `socket` / `send` / `receive` / `connect`;
|
|
16
|
+
- 设置与主题:`createStorage`、`useConfig`、`SettingService`、`useColorMode`、`ThemeService`;
|
|
17
|
+
- 动作与菜单:`useMenu` 与 ActionService(动作面板、菜单注册、全局快捷键分发);
|
|
18
|
+
- 国际化:I18nService;
|
|
19
|
+
- element-plus 的 `loading` / `message` / `messageBox` 服务封装;
|
|
20
|
+
- `Satori` / `Universal` 命名空间与 `ScopeStatus` 常量。
|
|
21
|
+
|
|
22
|
+
## 构建器
|
|
23
|
+
|
|
24
|
+
本包没有独立的运行时发布形态,而是作为源码被各前端工程消费:
|
|
25
|
+
|
|
26
|
+
- CLI:`koishi-console build [root]`(`src/bin.ts`)——带 root 时构建单个插件的前端,否则执行宿主控制台总装(产物硬编码到 `plugins/webui/console/dist`,含共享 chunk 与样式);
|
|
27
|
+
- 编程式:`build(root, config)` 构建单个插件前端,`createServer(baseDir)` 创建 Vite 开发中间件;
|
|
28
|
+
- `collectWorkspaceAliases()`:读取根 package.json 的 workspaces,为每个工作区包名建立指向其 `client/index.ts` 的别名——未被依赖的 workspace 包不会出现在 node_modules 链接里,必须显式映射才能被 bundler 解析。
|
|
29
|
+
|
|
30
|
+
## 许可证
|
|
31
|
+
|
|
32
|
+
[AGPL-3.0](https://github.com/Koishi-CE/koishi/blob/main/LICENSES/AGPL-3.0.txt)。本包是上游 koishijs/webui 的社区再分发,版权归 Shigma 及 Koishijs 贡献者(上游)与 Koishi-CE 贡献者,见 [NOTICE](https://github.com/Koishi-CE/koishi/blob/main/NOTICE)。
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## English
|
|
37
|
+
|
|
38
|
+
The browser-side runtime and component foundation of the Koishi console, ported from `packages/client` of the upstream [koishijs/webui](https://github.com/koishijs/webui). It hosts the entire frontend infrastructure — the root Context, services and plugin system, router and activity bar, state management, themes, actions and menus — and ships a programmatic builder that assembles console plugin frontends into deployable bundles.
|
|
39
|
+
|
|
40
|
+
Division of labor with [`@koishi-ce/plugin-console`](https://github.com/Koishi-CE/koishi/tree/main/plugins/webui/console) (the node-side host): this package runs in the browser as the frontend foundation, while plugin-console runs on node serving the WebSocket channel and static assets. Host assembly output is written straight into plugin-console's `dist` directory.
|
|
41
|
+
|
|
42
|
+
## Key exports
|
|
43
|
+
|
|
44
|
+
`root` and `Context` (the single root context), `defineExtension` (entry for console plugin frontends), the frontend `Service` base, `RouterService` / `Activity` / `redirectTo`, the reactive `store` with `socket` / `send` / `receive` / `connect`, settings and theming (`createStorage`, `useConfig`, `SettingService`, `useColorMode`, `ThemeService`), actions and menus (`useMenu`, ActionService with global shortcut dispatch), I18nService, element-plus service wrappers (`loading` / `message` / `messageBox`), plus the `Satori` / `Universal` namespaces and `ScopeStatus`.
|
|
45
|
+
|
|
46
|
+
## Builder
|
|
47
|
+
|
|
48
|
+
The package is consumed as source code rather than shipped as a standalone runtime:
|
|
49
|
+
|
|
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
|
+
- 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.
|
|
53
|
+
|
|
54
|
+
## License
|
|
55
|
+
|
|
56
|
+
[AGPL-3.0](https://github.com/Koishi-CE/koishi/blob/main/LICENSES/AGPL-3.0.txt). Community redistribution of upstream koishijs/webui; copyright Shigma and Koishijs contributors (upstream) and Koishi-CE contributors — see [NOTICE](https://github.com/Koishi-CE/koishi/blob/main/NOTICE).
|
package/app/home/home.vue
CHANGED
package/app/home/index.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
# SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
# Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
# Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
1
5
|
title: Welcome to Koishi!
|
|
2
6
|
description: Your chatbot is ready to go. 点击下面的任一选项,开启你的 Koishi 之旅吧。
|
|
3
7
|
action:
|
package/app/home/welcome.vue
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
欢迎卡片:文档 / 社区论坛等入口链接。
|
|
3
7
|
自带 zh-CN 与 en-US 两套文案(局部 vue-i18n 实例,独立于全局语言包),
|
|
@@ -41,12 +45,18 @@ const { t, setLocaleMessage } = useI18n({
|
|
|
41
45
|
|
|
42
46
|
// 开发模式下 yml 文案改动即时热替换,无需整页刷新
|
|
43
47
|
if (import.meta.hot) {
|
|
44
|
-
import.meta.hot.accept(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
48
|
+
import.meta.hot.accept(
|
|
49
|
+
"./welcome.zh-CN.yml",
|
|
50
|
+
(module) => {
|
|
51
|
+
setLocaleMessage("zh-CN", module.default);
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
import.meta.hot.accept(
|
|
55
|
+
"./welcome.en-US.yml",
|
|
56
|
+
(module) => {
|
|
57
|
+
setLocaleMessage("en-US", module.default);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
50
60
|
}
|
|
51
61
|
</script>
|
|
52
62
|
|
package/app/index.scss
CHANGED
package/app/index.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-only
|
|
2
|
+
// Copyright (c) 2019-present Shigma and Koishijs contributors.
|
|
3
|
+
// Copyright (c) 2026-present Koishi-CE contributors.
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* 控制台前端宿主应用入口(scripts/client.ts 总装构建的主入口)。
|
|
3
7
|
*
|
|
@@ -27,6 +31,12 @@ root.start();
|
|
|
27
31
|
|
|
28
32
|
if (!global.static) {
|
|
29
33
|
// global.static 为真表示纯静态构建(无后端),跳过连接
|
|
30
|
-
const endpoint = new URL(
|
|
31
|
-
|
|
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
|
+
);
|
|
32
42
|
}
|
package/app/layout/header.vue
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
布局顶栏:左侧标题(默认取当前路由名)、右侧菜单区,
|
|
3
7
|
移动端额外提供侧栏开关按钮与溢出菜单("...")按钮。
|
|
@@ -34,7 +38,10 @@
|
|
|
34
38
|
</template>
|
|
35
39
|
|
|
36
40
|
<script lang="ts" setup>
|
|
37
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
type ActionContext,
|
|
43
|
+
useMenu,
|
|
44
|
+
} from "@koishi-ce/client";
|
|
38
45
|
import { useRoute } from "vue-router";
|
|
39
46
|
|
|
40
47
|
const props = defineProps<{
|
|
@@ -46,9 +53,14 @@ const props = defineProps<{
|
|
|
46
53
|
|
|
47
54
|
// 溢出菜单按钮的点击触发器:打开 menuKey 对应的上下文菜单
|
|
48
55
|
// (menuKey 为运行期字符串,这里断言为菜单键类型以复用字面量重载)
|
|
49
|
-
const trigger = useMenu(
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
const trigger = useMenu(
|
|
57
|
+
props.menuKey as keyof ActionContext,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
defineEmits([
|
|
61
|
+
"update:isLeftAsideOpen",
|
|
62
|
+
"update:isRightAsideOpen",
|
|
63
|
+
]);
|
|
52
64
|
|
|
53
65
|
const route = useRoute();
|
|
54
66
|
</script>
|
package/app/layout/index.ts
CHANGED
package/app/layout/layout.vue
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
三栏布局组件(页面模板中 <k-layout> 的内置实现):
|
|
3
7
|
可选的左右侧栏 + 顶部 header + 主区域。
|
|
@@ -50,7 +54,10 @@
|
|
|
50
54
|
</template>
|
|
51
55
|
|
|
52
56
|
<script lang="ts" setup>
|
|
53
|
-
import {
|
|
57
|
+
import {
|
|
58
|
+
type LegacyMenuItem,
|
|
59
|
+
useContext,
|
|
60
|
+
} from "@koishi-ce/client";
|
|
54
61
|
import { computed, ref, useSlots } from "vue";
|
|
55
62
|
import { useRoute } from "vue-router";
|
|
56
63
|
import LayoutHeader from "./header.vue";
|
package/app/layout/menu-item.vue
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
顶栏菜单按钮(图标型):hidden/disabled/label/icon 均支持 getter,
|
|
3
7
|
点击时以 action scope 为参调用对应 action。
|
|
@@ -50,7 +54,9 @@ const scope = computed(() =>
|
|
|
50
54
|
function toValue<T>(getter: MaybeGetter<T>): T {
|
|
51
55
|
if (typeof getter !== "function") return getter;
|
|
52
56
|
// 收窄到 MaybeGetter 的函数分支(以作用域为参的 getter)
|
|
53
|
-
return (getter as (current: typeof scope.value) => T)(
|
|
57
|
+
return (getter as (current: typeof scope.value) => T)(
|
|
58
|
+
scope.value,
|
|
59
|
+
);
|
|
54
60
|
}
|
|
55
61
|
|
|
56
62
|
function trigger() {
|
package/app/settings/index.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
用户设置页:左侧 el-tree 分组导航 + 右侧当前分组的内容。
|
|
3
7
|
分组内的条目三种形态二选一渲染:组件型(component)、表单型(schema),
|
|
@@ -52,10 +56,12 @@ interface Tree {
|
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
const data = computed(() =>
|
|
55
|
-
Object.entries(ctx.internal.settings).map<Tree>(
|
|
56
|
-
id,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
Object.entries(ctx.internal.settings).map<Tree>(
|
|
60
|
+
([id, [{ title }]]) => ({
|
|
61
|
+
id,
|
|
62
|
+
label: title,
|
|
63
|
+
}),
|
|
64
|
+
),
|
|
59
65
|
);
|
|
60
66
|
|
|
61
67
|
function handleClick(tree: Tree) {
|
package/app/settings/theme.vue
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
主题选择器:role 为 "theme" 的字符串 schema 的专用渲染组件。
|
|
3
7
|
下拉选项按明 / 暗模式过滤(主题 id 以 -light / -dark 结尾),
|
|
@@ -31,7 +35,10 @@
|
|
|
31
35
|
|
|
32
36
|
<script setup lang="ts">
|
|
33
37
|
import { useContext, useI18nText } from "@koishi-ce/client";
|
|
34
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
type Schema,
|
|
40
|
+
SchemaBase,
|
|
41
|
+
} from "@koishi-ce/components";
|
|
35
42
|
import { computed, type PropType } from "vue";
|
|
36
43
|
|
|
37
44
|
defineProps({
|
package/app/shims.d.ts
CHANGED
package/app/status/index.ts
CHANGED
package/app/status/loading.vue
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
页面组件按需加载进度条:按当前路由所需扩展(ctx.$loader.extensions)
|
|
3
7
|
的完成比例显示进度;store.entry 尚未就绪时以不确定进度(indeterminate)展示。
|
|
@@ -18,6 +22,9 @@ const ctx = useContext();
|
|
|
18
22
|
|
|
19
23
|
const progress = computed(() => {
|
|
20
24
|
const states = Object.values(ctx.$loader.extensions);
|
|
21
|
-
return
|
|
25
|
+
return (
|
|
26
|
+
states.filter((state) => state.done.value).length /
|
|
27
|
+
states.length
|
|
28
|
+
);
|
|
22
29
|
});
|
|
23
30
|
</script>
|
package/app/status/status.vue
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
通用状态项容器(页面模板中 <k-status> 的内置实现):
|
|
3
7
|
内容区 + 悬停时的 tooltip(内容经 el-scrollbar 限高滚动)。
|
package/app/styles/element.scss
CHANGED
package/app/styles/hc.scss
CHANGED
package/app/styles/index.scss
CHANGED
package/app/styles/index.ts
CHANGED
package/app/styles/layout.scss
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
活动栏单个图标按钮,也是拖拽排序的拖拽源:
|
|
3
7
|
拖起时向 dataTransfer 写入 "activity:<id>" 文本供落点组件识别。
|
|
@@ -29,7 +33,10 @@ const isDragging = ref(false);
|
|
|
29
33
|
function handleDragStart(event: DragEvent) {
|
|
30
34
|
isDragging.value = true;
|
|
31
35
|
// 约定的拖拽协议:以 "activity:" 前缀 + 活动 id 标识被拖动的活动项
|
|
32
|
-
event.dataTransfer.setData(
|
|
36
|
+
event.dataTransfer.setData(
|
|
37
|
+
"text/plain",
|
|
38
|
+
`activity:${props.data.id}`,
|
|
39
|
+
);
|
|
33
40
|
}
|
|
34
41
|
|
|
35
42
|
function handleDragEnd(event: DragEvent) {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
活动栏(最左侧图标栏):分 top / bottom 两区渲染活动项,
|
|
3
7
|
项与项之间的分隔槽(activity-separator)同时充当拖拽排序的落点。
|
|
@@ -68,7 +72,9 @@ const groups = computed(() => {
|
|
|
68
72
|
delete available[id];
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
|
-
const list = Object.values(available).sort(
|
|
75
|
+
const list = Object.values(available).sort(
|
|
76
|
+
([a], [b]) => a.order - b.order,
|
|
77
|
+
);
|
|
72
78
|
// 放不下时:从头部取出溢出项折叠成一个组,top 位优先、同位再按 order 排,
|
|
73
79
|
// 并以 "..." 图标作为该组的展示入口
|
|
74
80
|
if (list.length * unit > total) {
|
|
@@ -82,11 +88,17 @@ const groups = computed(() => {
|
|
|
82
88
|
return scale;
|
|
83
89
|
})
|
|
84
90
|
.flat();
|
|
85
|
-
hidden.unshift({
|
|
91
|
+
hidden.unshift({
|
|
92
|
+
icon: "activity:ellipsis",
|
|
93
|
+
} as Activity);
|
|
86
94
|
}
|
|
87
95
|
// top 区按 order 逆序输出(order 大者靠上),bottom 区保持正序
|
|
88
|
-
const top = list
|
|
89
|
-
|
|
96
|
+
const top = list
|
|
97
|
+
.filter(([data]) => data.position !== "bottom")
|
|
98
|
+
.reverse();
|
|
99
|
+
const bottom = list.filter(
|
|
100
|
+
([data]) => data.position === "bottom",
|
|
101
|
+
);
|
|
90
102
|
return { top, bottom, hidden };
|
|
91
103
|
});
|
|
92
104
|
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
活动栏条目:children[0] 为组的主图标,其余为并入该组的子活动,
|
|
3
7
|
悬停 tooltip 中列出组内成员。条目本身也是拖拽落点——
|
|
@@ -34,7 +38,11 @@
|
|
|
34
38
|
</template>
|
|
35
39
|
|
|
36
40
|
<script lang="ts" setup>
|
|
37
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
type Activity,
|
|
43
|
+
useConfig,
|
|
44
|
+
useMenu,
|
|
45
|
+
} from "@koishi-ce/client";
|
|
38
46
|
import type { Placement } from "element-plus";
|
|
39
47
|
import { computed, ref, watch } from "vue";
|
|
40
48
|
import { useRoute } from "vue-router";
|
|
@@ -80,13 +88,23 @@ const config = useConfig();
|
|
|
80
88
|
|
|
81
89
|
// 原型链保留键:这类键在普通对象上会触发原型链存取器,禁止作为配置键读写
|
|
82
90
|
// (id 来自拖拽事件的 dataTransfer 文本,属外部输入)
|
|
83
|
-
const UNSAFE_KEYS = new Set([
|
|
91
|
+
const UNSAFE_KEYS = new Set([
|
|
92
|
+
"__proto__",
|
|
93
|
+
"constructor",
|
|
94
|
+
"prototype",
|
|
95
|
+
]);
|
|
84
96
|
|
|
85
97
|
/** 取某活动的覆盖配置(不存在则创建);保留键返回一次性空对象,防原型污染。
|
|
86
98
|
* 守卫须用显式字符串比较(Set.has 形式 CodeQL 无法识别为阻断) */
|
|
87
|
-
function ensureOverride(
|
|
99
|
+
function ensureOverride(
|
|
100
|
+
id: string,
|
|
101
|
+
): Record<string, unknown> {
|
|
88
102
|
const activities = (config.value.activities ??= {});
|
|
89
|
-
if (
|
|
103
|
+
if (
|
|
104
|
+
id === "__proto__" ||
|
|
105
|
+
id === "constructor" ||
|
|
106
|
+
id === "prototype"
|
|
107
|
+
) {
|
|
90
108
|
return {};
|
|
91
109
|
}
|
|
92
110
|
return (activities[id] ??= {});
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
<!-- SPDX-License-Identifier: AGPL-3.0-only -->
|
|
2
|
+
<!-- Copyright (c) 2019-present Shigma and Koishijs contributors. -->
|
|
3
|
+
<!-- Copyright (c) 2026-present Koishi-CE contributors. -->
|
|
4
|
+
|
|
1
5
|
<!--
|
|
2
6
|
活动栏条目间的分隔槽:平时不可见,拖拽悬停时显示横线提示,
|
|
3
7
|
松手即把被拖项移动到该位置(拖拽排序的核心落点组件)。
|
|
@@ -14,7 +18,11 @@
|
|
|
14
18
|
</template>
|
|
15
19
|
|
|
16
20
|
<script lang="ts" setup>
|
|
17
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
type Activity,
|
|
23
|
+
useConfig,
|
|
24
|
+
useContext,
|
|
25
|
+
} from "@koishi-ce/client";
|
|
18
26
|
import { type ComputedRef, inject, ref } from "vue";
|
|
19
27
|
|
|
20
28
|
type Position = "top" | "bottom";
|
|
@@ -24,7 +32,9 @@ const props = defineProps<{
|
|
|
24
32
|
position: Position;
|
|
25
33
|
}>();
|
|
26
34
|
|
|
27
|
-
const groups = inject("groups") as ComputedRef<
|
|
35
|
+
const groups = inject("groups") as ComputedRef<
|
|
36
|
+
Record<Position, Activity[][]>
|
|
37
|
+
>;
|
|
28
38
|
|
|
29
39
|
const hasDragOver = ref(false);
|
|
30
40
|
|
|
@@ -41,13 +51,23 @@ const ctx = useContext();
|
|
|
41
51
|
|
|
42
52
|
// 原型链保留键:这类键在普通对象上会触发原型链存取器,禁止作为配置键读写
|
|
43
53
|
// (id 来自拖拽事件的 dataTransfer 文本,属外部输入)
|
|
44
|
-
const UNSAFE_KEYS = new Set([
|
|
54
|
+
const UNSAFE_KEYS = new Set([
|
|
55
|
+
"__proto__",
|
|
56
|
+
"constructor",
|
|
57
|
+
"prototype",
|
|
58
|
+
]);
|
|
45
59
|
|
|
46
60
|
/** 取某活动的覆盖配置(不存在则创建);保留键返回一次性空对象,防原型污染。
|
|
47
61
|
* 守卫须用显式字符串比较(Set.has 形式 CodeQL 无法识别为阻断) */
|
|
48
|
-
function ensureOverride(
|
|
62
|
+
function ensureOverride(
|
|
63
|
+
id: string,
|
|
64
|
+
): Record<string, unknown> {
|
|
49
65
|
const activities = (config.value.activities ??= {});
|
|
50
|
-
if (
|
|
66
|
+
if (
|
|
67
|
+
id === "__proto__" ||
|
|
68
|
+
id === "constructor" ||
|
|
69
|
+
id === "prototype"
|
|
70
|
+
) {
|
|
51
71
|
return {};
|
|
52
72
|
}
|
|
53
73
|
return (activities[id] ??= {});
|
|
@@ -59,7 +79,9 @@ function handleDrop(event: DragEvent) {
|
|
|
59
79
|
// 只响应活动栏自身的拖拽协议
|
|
60
80
|
if (!text.startsWith("activity:")) return;
|
|
61
81
|
const id = text.slice(9);
|
|
62
|
-
const list = groups.value[props.position].map(
|
|
82
|
+
const list = groups.value[props.position].map(
|
|
83
|
+
([item]) => item,
|
|
84
|
+
);
|
|
63
85
|
const oldIndex = list.findIndex((item) => item.id === id);
|
|
64
86
|
// 落点即原位(含紧邻原位的前一格)时无需移动
|
|
65
87
|
if (
|
|
@@ -95,10 +117,12 @@ function handleDrop(event: DragEvent) {
|
|
|
95
117
|
// 介于两锚点之间的项按线性插值重算 order;
|
|
96
118
|
// 只有一侧锚点时按步长 100 单向递增 / 递减;两侧都没有则恢复默认
|
|
97
119
|
const anchorL = list.findLastIndex(
|
|
98
|
-
(item, i) =>
|
|
120
|
+
(item, i) =>
|
|
121
|
+
i < index && item.order === item.options.order,
|
|
99
122
|
);
|
|
100
123
|
const anchorR = list.findIndex(
|
|
101
|
-
(item, i) =>
|
|
124
|
+
(item, i) =>
|
|
125
|
+
i > index && item.order === item.options.order,
|
|
102
126
|
);
|
|
103
127
|
if (anchorL === -1) {
|
|
104
128
|
if (anchorR === -1) {
|
|
@@ -113,24 +137,36 @@ function handleDrop(event: DragEvent) {
|
|
|
113
137
|
} else {
|
|
114
138
|
if (anchorR === -1) {
|
|
115
139
|
let order = list[anchorL].options.order;
|
|
116
|
-
for (
|
|
140
|
+
for (
|
|
141
|
+
let index = anchorL + 1;
|
|
142
|
+
index < list.length;
|
|
143
|
+
index++
|
|
144
|
+
) {
|
|
117
145
|
const override = ensureOverride(list[index].id);
|
|
118
146
|
override.order = order -= 100;
|
|
119
147
|
}
|
|
120
148
|
} else {
|
|
121
149
|
let orderL = list[anchorL].options.order;
|
|
122
150
|
let orderR = list[anchorR].options.order;
|
|
123
|
-
for (
|
|
151
|
+
for (
|
|
152
|
+
let index = anchorL + 1;
|
|
153
|
+
index < anchorR;
|
|
154
|
+
index++
|
|
155
|
+
) {
|
|
124
156
|
const override = ensureOverride(list[index].id);
|
|
125
157
|
override.order =
|
|
126
158
|
orderL +
|
|
127
|
-
((orderR - orderL) * (index - anchorL)) /
|
|
159
|
+
((orderR - orderL) * (index - anchorL)) /
|
|
160
|
+
(anchorR - anchorL);
|
|
128
161
|
}
|
|
129
162
|
}
|
|
130
163
|
}
|
|
131
164
|
|
|
132
165
|
// 覆盖配置为空对象时删除该键,避免残留无意义的配置项
|
|
133
|
-
if (
|
|
166
|
+
if (
|
|
167
|
+
!Object.keys(override).length &&
|
|
168
|
+
!UNSAFE_KEYS.has(id)
|
|
169
|
+
) {
|
|
134
170
|
delete config.value.activities[id];
|
|
135
171
|
}
|
|
136
172
|
}
|
package/app/theme/blank.vue
CHANGED