@koishi-ce/client 1.0.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/app/assets/logo.png +0 -0
- package/app/home/home.vue +51 -0
- package/app/home/index.ts +18 -0
- package/app/home/welcome.en-US.yml +9 -0
- package/app/home/welcome.vue +111 -0
- package/app/home/welcome.zh-CN.yml +9 -0
- package/app/index.html +13 -0
- package/app/index.scss +64 -0
- package/app/index.ts +32 -0
- package/app/layout/header.vue +187 -0
- package/app/layout/index.ts +15 -0
- package/app/layout/layout.vue +194 -0
- package/app/layout/menu-item.vue +60 -0
- package/app/settings/index.ts +39 -0
- package/app/settings/settings.vue +82 -0
- package/app/settings/theme.vue +123 -0
- package/app/shims.d.ts +1 -0
- package/app/status/index.ts +21 -0
- package/app/status/loading.vue +23 -0
- package/app/status/status.vue +56 -0
- package/app/styles/element.scss +217 -0
- package/app/styles/hc.scss +35 -0
- package/app/styles/index.scss +140 -0
- package/app/styles/index.ts +30 -0
- package/app/styles/layout.scss +92 -0
- package/app/theme/activity/button.vue +97 -0
- package/app/theme/activity/index.vue +119 -0
- package/app/theme/activity/item.vue +185 -0
- package/app/theme/activity/separator.vue +163 -0
- package/app/theme/activity/utils.ts +27 -0
- package/app/theme/blank.vue +24 -0
- package/app/theme/index.ts +37 -0
- package/app/theme/index.vue +34 -0
- package/app/theme/menu/index.vue +26 -0
- package/app/theme/menu/menu-item.vue +58 -0
- package/app/theme/menu/menu.vue +45 -0
- package/app/theme/status.vue +45 -0
- package/app/tsconfig.json +15 -0
- package/client/components/chat/image.vue +20 -0
- package/client/components/chat/overlay.vue +231 -0
- package/client/components/chat/utils.ts +6 -0
- package/client/components/common/index.ts +11 -0
- package/client/components/common/k-button.vue +127 -0
- package/client/components/common/k-hint.vue +62 -0
- package/client/components/common/k-tab.vue +50 -0
- package/client/components/dynamic.vue +61 -0
- package/client/components/icons/activity/default.vue +6 -0
- package/client/components/icons/activity/ellipsis.vue +6 -0
- package/client/components/icons/activity/home.vue +13 -0
- package/client/components/icons/activity/moon.vue +6 -0
- package/client/components/icons/activity/settings.vue +6 -0
- package/client/components/icons/activity/sun.vue +6 -0
- package/client/components/icons/index.ts +133 -0
- package/client/components/icons/style.scss +3 -0
- package/client/components/icons/svg/arrow-left.vue +6 -0
- package/client/components/icons/svg/arrow-right.vue +6 -0
- package/client/components/icons/svg/box-open.vue +6 -0
- package/client/components/icons/svg/check-full.vue +6 -0
- package/client/components/icons/svg/chevron-down.vue +6 -0
- package/client/components/icons/svg/chevron-left.vue +6 -0
- package/client/components/icons/svg/chevron-right.vue +6 -0
- package/client/components/icons/svg/chevron-up.vue +6 -0
- package/client/components/icons/svg/clipboard-list.vue +6 -0
- package/client/components/icons/svg/edit.vue +6 -0
- package/client/components/icons/svg/exclamation-full.vue +6 -0
- package/client/components/icons/svg/expand.vue +6 -0
- package/client/components/icons/svg/file-archive.vue +6 -0
- package/client/components/icons/svg/filter.vue +6 -0
- package/client/components/icons/svg/github.vue +6 -0
- package/client/components/icons/svg/gitlab.vue +6 -0
- package/client/components/icons/svg/info-full.vue +6 -0
- package/client/components/icons/svg/koishi.vue +6 -0
- package/client/components/icons/svg/link.vue +6 -0
- package/client/components/icons/svg/paper-plane.vue +6 -0
- package/client/components/icons/svg/question-empty.vue +6 -0
- package/client/components/icons/svg/redo.vue +6 -0
- package/client/components/icons/svg/search-minus.vue +6 -0
- package/client/components/icons/svg/search-plus.vue +6 -0
- package/client/components/icons/svg/search.vue +6 -0
- package/client/components/icons/svg/star-empty.vue +6 -0
- package/client/components/icons/svg/star-full.vue +6 -0
- package/client/components/icons/svg/start.vue +6 -0
- package/client/components/icons/svg/tag.vue +6 -0
- package/client/components/icons/svg/times-full.vue +6 -0
- package/client/components/icons/svg/tools.vue +6 -0
- package/client/components/icons/svg/undo.vue +6 -0
- package/client/components/icons/svg/user.vue +6 -0
- package/client/components/index.ts +75 -0
- package/client/components/layout/card.vue +69 -0
- package/client/components/layout/content.vue +37 -0
- package/client/components/layout/empty.vue +41 -0
- package/client/components/layout/index.ts +15 -0
- package/client/components/layout/tab-group.vue +45 -0
- package/client/components/layout/tab-item.vue +35 -0
- package/client/components/link.ts +39 -0
- package/client/components/perms.vue +73 -0
- package/client/components/slot.ts +95 -0
- package/client/context.ts +143 -0
- package/client/data.ts +178 -0
- package/client/index.ts +76 -0
- package/client/plugins/action.ts +251 -0
- package/client/plugins/i18n.ts +43 -0
- package/client/plugins/loader.ts +154 -0
- package/client/plugins/router.ts +278 -0
- package/client/plugins/setting.ts +217 -0
- package/client/plugins/theme.ts +134 -0
- package/client/shims.d.ts +55 -0
- package/client/tsconfig.json +15 -0
- package/client/utils.ts +36 -0
- package/global.d.ts +24 -0
- package/lib/bin.d.ts +1 -0
- package/lib/bin.mjs +112 -0
- package/lib/client-CaTn_gVG.mjs +125 -0
- package/lib/index.d.ts +20 -0
- package/lib/index.mjs +2 -0
- package/lib/src-Bxzzi1Rx.mjs +160 -0
- package/lib/yaml-UzQbvquH.mjs +23 -0
- package/package.json +66 -0
- package/src/bin.ts +61 -0
- package/src/index.ts +280 -0
- package/src/yaml.ts +22 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
三栏布局组件(页面模板中 <k-layout> 的内置实现):
|
|
3
|
+
可选的左右侧栏 + 顶部 header + 主区域。
|
|
4
|
+
移动端(<=768px)下侧栏转为抽屉式展开,由半透明遮罩(aside-mask)点击收起。
|
|
5
|
+
-->
|
|
6
|
+
<template>
|
|
7
|
+
<div class="layout-container" :class="[container, styles]">
|
|
8
|
+
<aside class="layout-aside layout-left" :class="left" v-if="$slots.left">
|
|
9
|
+
<slot name="left"></slot>
|
|
10
|
+
</aside>
|
|
11
|
+
|
|
12
|
+
<div class="main-container">
|
|
13
|
+
<div class="aside-mask" @click="isLeftAsideOpen = !isLeftAsideOpen"></div>
|
|
14
|
+
<layout-header
|
|
15
|
+
v-model:isLeftAsideOpen="isLeftAsideOpen"
|
|
16
|
+
v-model:isRightAsideOpen="isRightAsideOpen"
|
|
17
|
+
:menu-key="typeof menu === 'string' ? menu : null"
|
|
18
|
+
:menu-data="menuData">
|
|
19
|
+
<template #left>
|
|
20
|
+
<slot name="header">{{ route.meta.activity?.name }}</slot>
|
|
21
|
+
</template>
|
|
22
|
+
<template #right>
|
|
23
|
+
<slot name="menu">
|
|
24
|
+
<!-- menu 为字符串时,渲染 ctx.internal.menus 中注册的同名菜单 -->
|
|
25
|
+
<template v-if="typeof menu === 'string'">
|
|
26
|
+
<template v-for="item in ctx.internal.menus[menu]" :key="menu">
|
|
27
|
+
<!-- 以 "." 开头的是相对菜单 id,需拼上所属菜单前缀再查 action -->
|
|
28
|
+
<layout-menu-item
|
|
29
|
+
v-if="item.id !== '@separator'"
|
|
30
|
+
:item="{ ...item, ...ctx.internal.actions[item.id.startsWith('.') ? menu + item.id : item.id] }"
|
|
31
|
+
:menu-key="menu" :menu-data="menuData"
|
|
32
|
+
/>
|
|
33
|
+
</template>
|
|
34
|
+
</template>
|
|
35
|
+
<template v-else>
|
|
36
|
+
<layout-menu-item v-for="item in menu" :item="item" />
|
|
37
|
+
</template>
|
|
38
|
+
</slot>
|
|
39
|
+
</template>
|
|
40
|
+
</layout-header>
|
|
41
|
+
<main class="layout-main" :class="main">
|
|
42
|
+
<slot></slot>
|
|
43
|
+
</main>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<aside class="layout-aside layout-right" :class="right" v-if="$slots.right">
|
|
47
|
+
<slot name="right"></slot>
|
|
48
|
+
</aside>
|
|
49
|
+
</div>
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<script lang="ts" setup>
|
|
53
|
+
import { type LegacyMenuItem, useContext } from "@koishi-ce/client";
|
|
54
|
+
import { computed, ref, useSlots } from "vue";
|
|
55
|
+
import { useRoute } from "vue-router";
|
|
56
|
+
import LayoutHeader from "./header.vue";
|
|
57
|
+
import LayoutMenuItem from "./menu-item.vue";
|
|
58
|
+
|
|
59
|
+
// 各 class prop 用于向对应区域追加自定义类名;menu 传数组时作为
|
|
60
|
+
// 菜单项列表直接渲染,menuData 则作为菜单触发时的附加数据传入
|
|
61
|
+
defineProps<{
|
|
62
|
+
main?: string;
|
|
63
|
+
left?: string;
|
|
64
|
+
right?: string;
|
|
65
|
+
container?: string;
|
|
66
|
+
menu?: string | LegacyMenuItem[];
|
|
67
|
+
menuData?: unknown;
|
|
68
|
+
}>();
|
|
69
|
+
|
|
70
|
+
const slots = useSlots();
|
|
71
|
+
const route = useRoute();
|
|
72
|
+
const ctx = useContext();
|
|
73
|
+
|
|
74
|
+
// 移动端左右侧栏(抽屉)的展开状态
|
|
75
|
+
const isLeftAsideOpen = ref(false);
|
|
76
|
+
const isRightAsideOpen = ref(false);
|
|
77
|
+
|
|
78
|
+
// 布局类名:左右插槽是否存在决定骨架形态,开合状态配合移动端抽屉样式
|
|
79
|
+
const styles = computed(() => ({
|
|
80
|
+
"has-left-aside": slots.left,
|
|
81
|
+
"has-right-aside": slots.right,
|
|
82
|
+
"is-left-aside-open": isLeftAsideOpen.value,
|
|
83
|
+
"is-right-aside-open": isRightAsideOpen.value,
|
|
84
|
+
}));
|
|
85
|
+
</script>
|
|
86
|
+
|
|
87
|
+
<style lang="scss">
|
|
88
|
+
|
|
89
|
+
.layout-container {
|
|
90
|
+
position: fixed;
|
|
91
|
+
box-sizing: border-box;
|
|
92
|
+
z-index: 100;
|
|
93
|
+
top: 0;
|
|
94
|
+
left: var(--activity-width);
|
|
95
|
+
bottom: var(--footer-height);
|
|
96
|
+
right: 0;
|
|
97
|
+
background-color: var(--k-card-bg);
|
|
98
|
+
transition: var(--color-transition);
|
|
99
|
+
display: flex;
|
|
100
|
+
flex-direction: row;
|
|
101
|
+
|
|
102
|
+
.layout-aside {
|
|
103
|
+
top: 0;
|
|
104
|
+
bottom: 0;
|
|
105
|
+
flex: 0 0 auto;
|
|
106
|
+
position: relative;
|
|
107
|
+
width: var(--aside-width);
|
|
108
|
+
transition: var(--color-transition);
|
|
109
|
+
background-color: var(--k-side-bg);
|
|
110
|
+
box-sizing: border-box;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.layout-right {
|
|
114
|
+
border-left: var(--k-color-divider-dark) 1px solid;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.layout-left {
|
|
118
|
+
border-right: var(--k-color-divider-dark) 1px solid;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.main-container {
|
|
122
|
+
position: relative;
|
|
123
|
+
flex: 1;
|
|
124
|
+
transition: left 0.3s ease;
|
|
125
|
+
background-color: var(--k-main-bg);
|
|
126
|
+
min-height: 100%;
|
|
127
|
+
display: flex;
|
|
128
|
+
flex-flow: column;
|
|
129
|
+
overflow: hidden;
|
|
130
|
+
|
|
131
|
+
&.darker {
|
|
132
|
+
background-color: var(--k-side-bg);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.layout-main {
|
|
136
|
+
flex: 1 1 auto;
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
position: relative;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.aside-mask {
|
|
142
|
+
pointer-events: none;
|
|
143
|
+
position: absolute;
|
|
144
|
+
top: 0;
|
|
145
|
+
left: 0;
|
|
146
|
+
right: 0;
|
|
147
|
+
bottom: 0;
|
|
148
|
+
background-color: #000;
|
|
149
|
+
opacity: 0;
|
|
150
|
+
z-index: 300;
|
|
151
|
+
transition: opacity 0.3s ease;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
@media screen and (max-width: 768px) {
|
|
157
|
+
.layout-container {
|
|
158
|
+
.layout-left {
|
|
159
|
+
position: fixed;
|
|
160
|
+
top: 0;
|
|
161
|
+
left: var(--activity-width);
|
|
162
|
+
bottom: 0;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.main-container {
|
|
166
|
+
position: fixed;
|
|
167
|
+
left: 0;
|
|
168
|
+
top: 0;
|
|
169
|
+
width: 100vw;
|
|
170
|
+
bottom: 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
&.is-left-aside-open {
|
|
174
|
+
&.has-left-aside {
|
|
175
|
+
.main-container {
|
|
176
|
+
left: calc(var(--aside-width) + var(--activity-width));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
&:not(.has-left-aside) {
|
|
181
|
+
.main-container {
|
|
182
|
+
left: var(--activity-width);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.aside-mask {
|
|
187
|
+
opacity: 0.25;
|
|
188
|
+
pointer-events: auto;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
</style>
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
顶栏菜单按钮(图标型):hidden/disabled/label/icon 均支持 getter,
|
|
3
|
+
点击时以 action scope 为参调用对应 action。
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<el-tooltip v-if="!hidden" :disabled="disabled" :content="toValue(item.label)" placement="bottom">
|
|
7
|
+
<span class="menu-item" :class="[toValue(item.type), { disabled }]" @click="trigger">
|
|
8
|
+
<k-icon class="menu-icon" :name="toValue(item.icon)"></k-icon>
|
|
9
|
+
</span>
|
|
10
|
+
</el-tooltip>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script lang="ts" setup>
|
|
14
|
+
import {
|
|
15
|
+
type LegacyMenuItem,
|
|
16
|
+
type MaybeGetter,
|
|
17
|
+
useContext,
|
|
18
|
+
} from "@koishi-ce/client";
|
|
19
|
+
import { computed } from "vue";
|
|
20
|
+
|
|
21
|
+
const props = defineProps<{
|
|
22
|
+
item: LegacyMenuItem;
|
|
23
|
+
menuKey?: string;
|
|
24
|
+
menuData?: unknown;
|
|
25
|
+
}>();
|
|
26
|
+
|
|
27
|
+
const ctx = useContext();
|
|
28
|
+
|
|
29
|
+
// 未声明 hidden 时直接可见;声明为 getter 的按求值结果判断
|
|
30
|
+
const hidden = computed(() => {
|
|
31
|
+
if (!props.item.hidden) return false;
|
|
32
|
+
return toValue(props.item.hidden);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// 没有对应 action 的按钮视为禁用(仅作展示)
|
|
36
|
+
const disabled = computed(() => {
|
|
37
|
+
if (!props.item.action) return true;
|
|
38
|
+
if (!props.item.disabled) return false;
|
|
39
|
+
return toValue(props.item.disabled);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// action scope:以所属菜单 id -> menuData 的映射作为求值上下文
|
|
43
|
+
const scope = computed(() =>
|
|
44
|
+
ctx.$action.createScope({
|
|
45
|
+
[props.menuKey]: props.menuData,
|
|
46
|
+
}),
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// label / icon 等字段可能是静态值或以 scope 为参的 getter,这里统一解包
|
|
50
|
+
function toValue<T>(getter: MaybeGetter<T>): T {
|
|
51
|
+
if (typeof getter !== "function") return getter;
|
|
52
|
+
// 收窄到 MaybeGetter 的函数分支(以作用域为参的 getter)
|
|
53
|
+
return (getter as (current: typeof scope.value) => T)(scope.value);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function trigger() {
|
|
57
|
+
// disabled 计算属性已保证无 action 的按钮不可点击,这里做空值防护
|
|
58
|
+
return props.item.action?.(scope.value);
|
|
59
|
+
}
|
|
60
|
+
</script>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 设置插件:
|
|
3
|
+
* - 注册用户设置页(/settings/:name*,活动栏底部);
|
|
4
|
+
* - 注册 role 为 "theme" 的字符串 schema 渲染组件(主题选择器);
|
|
5
|
+
* - 声明 "status"(状态栏设置)设置分组,供其它扩展挂载配置项。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Context } from "@koishi-ce/client";
|
|
9
|
+
import Settings from "./settings.vue";
|
|
10
|
+
import Theme from "./theme.vue";
|
|
11
|
+
|
|
12
|
+
export default function (ctx: Context) {
|
|
13
|
+
ctx.page({
|
|
14
|
+
path: "/settings/:name*",
|
|
15
|
+
name: "用户设置",
|
|
16
|
+
icon: "activity:settings",
|
|
17
|
+
position: "bottom",
|
|
18
|
+
order: -100,
|
|
19
|
+
component: Settings,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
ctx.schema({
|
|
23
|
+
type: "string",
|
|
24
|
+
role: "theme",
|
|
25
|
+
component: Theme,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
ctx.settings({
|
|
29
|
+
id: "status",
|
|
30
|
+
title: "状态栏设置",
|
|
31
|
+
order: 800,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// ctx.settings({
|
|
35
|
+
// id: 'activity',
|
|
36
|
+
// title: '活动栏设置',
|
|
37
|
+
// order: 800,
|
|
38
|
+
// })
|
|
39
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
用户设置页:左侧 el-tree 分组导航 + 右侧当前分组的内容。
|
|
3
|
+
分组内的条目三种形态二选一渲染:组件型(component)、表单型(schema),
|
|
4
|
+
disabled 的条目直接跳过。
|
|
5
|
+
-->
|
|
6
|
+
<template>
|
|
7
|
+
<k-layout main="page-settings">
|
|
8
|
+
<template #header>
|
|
9
|
+
{{ ctx.internal.settings[path][0]?.title }}
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<template #left>
|
|
13
|
+
<el-scrollbar>
|
|
14
|
+
<el-tree
|
|
15
|
+
ref="tree"
|
|
16
|
+
:data="data"
|
|
17
|
+
:default-expand-all="true"
|
|
18
|
+
@node-click="handleClick"
|
|
19
|
+
></el-tree>
|
|
20
|
+
</el-scrollbar>
|
|
21
|
+
</template>
|
|
22
|
+
|
|
23
|
+
<keep-alive>
|
|
24
|
+
<k-content :key="path">
|
|
25
|
+
<template v-for="item of ctx.internal.settings[path]">
|
|
26
|
+
<template v-if="item.disabled?.()"></template>
|
|
27
|
+
<component v-else-if="item.component" :is="item.component" />
|
|
28
|
+
<k-form v-else-if="item.schema" :schema="item.schema" v-model="config" :initial="config" />
|
|
29
|
+
</template>
|
|
30
|
+
</k-content>
|
|
31
|
+
</keep-alive>
|
|
32
|
+
</k-layout>
|
|
33
|
+
</template>
|
|
34
|
+
|
|
35
|
+
<script lang="ts" setup>
|
|
36
|
+
import { useConfig, useContext } from "@koishi-ce/client";
|
|
37
|
+
import { computed } from "vue";
|
|
38
|
+
import { useRoute, useRouter } from "vue-router";
|
|
39
|
+
|
|
40
|
+
const route = useRoute();
|
|
41
|
+
const router = useRouter();
|
|
42
|
+
|
|
43
|
+
// 全局控制台配置(双向可写),表单型条目直接绑定到此对象
|
|
44
|
+
const config = useConfig(true);
|
|
45
|
+
const ctx = useContext();
|
|
46
|
+
|
|
47
|
+
// el-tree 的数据源:由 internal.settings 中各分组的首个条目标题生成
|
|
48
|
+
interface Tree {
|
|
49
|
+
id: string;
|
|
50
|
+
label: string;
|
|
51
|
+
children?: Tree[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const data = computed(() =>
|
|
55
|
+
Object.entries(ctx.internal.settings).map<Tree>(([id, [{ title }]]) => ({
|
|
56
|
+
id,
|
|
57
|
+
label: title,
|
|
58
|
+
})),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
function handleClick(tree: Tree) {
|
|
62
|
+
// 仅叶子节点(无 children)可切换分组
|
|
63
|
+
if (tree.children) return;
|
|
64
|
+
path.value = tree.id;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 当前分组与路由参数双向同步;无效分组名回退为空串
|
|
68
|
+
const path = computed({
|
|
69
|
+
get() {
|
|
70
|
+
const name = route.params.name?.toString();
|
|
71
|
+
return name in ctx.internal.settings ? name : "";
|
|
72
|
+
},
|
|
73
|
+
set(value) {
|
|
74
|
+
if (!(value in ctx.internal.settings)) value = "";
|
|
75
|
+
void router.replace(`/settings/${value}`);
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<style lang="scss">
|
|
81
|
+
|
|
82
|
+
</style>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
主题选择器:role 为 "theme" 的字符串 schema 的专用渲染组件。
|
|
3
|
+
下拉选项按明 / 暗模式过滤(主题 id 以 -light / -dark 结尾),
|
|
4
|
+
每个选项以三色块 + 标题的形式预览主题配色。
|
|
5
|
+
-->
|
|
6
|
+
<template>
|
|
7
|
+
<schema-base>
|
|
8
|
+
<template #title><slot name="title"></slot></template>
|
|
9
|
+
<template #desc><slot name="desc"></slot></template>
|
|
10
|
+
<template #menu><slot name="menu"></slot></template>
|
|
11
|
+
<template #prefix><slot name="prefix"></slot></template>
|
|
12
|
+
<template #suffix><slot name="suffix"></slot></template>
|
|
13
|
+
<template #control>
|
|
14
|
+
<el-select popper-class="theme-select" v-model="model">
|
|
15
|
+
<template v-for="(_, key) in ctx.internal.themes" :key="key">
|
|
16
|
+
<el-option :value="key" v-if="key.endsWith('-' + schema.meta.extra.mode)">
|
|
17
|
+
<div class="theme-root" :class="key.endsWith('-dark') ? 'dark' : 'light'" :theme="key">
|
|
18
|
+
<div class="theme-block-1"></div>
|
|
19
|
+
<div class="theme-block-2"></div>
|
|
20
|
+
<div class="theme-block-3"></div>
|
|
21
|
+
<div class="theme-title">
|
|
22
|
+
{{ tt(ctx.internal.themes[key].name) }}
|
|
23
|
+
</div>
|
|
24
|
+
</div>
|
|
25
|
+
</el-option>
|
|
26
|
+
</template>
|
|
27
|
+
</el-select>
|
|
28
|
+
</template>
|
|
29
|
+
</schema-base>
|
|
30
|
+
</template>
|
|
31
|
+
|
|
32
|
+
<script setup lang="ts">
|
|
33
|
+
import { useContext, useI18nText } from "@koishi-ce/client";
|
|
34
|
+
import { type Schema, SchemaBase } from "@koishi-ce/components";
|
|
35
|
+
import { computed, type PropType } from "vue";
|
|
36
|
+
|
|
37
|
+
defineProps({
|
|
38
|
+
schema: {} as PropType<Schema>,
|
|
39
|
+
modelValue: {} as PropType<unknown[]>,
|
|
40
|
+
disabled: {} as PropType<boolean>,
|
|
41
|
+
prefix: {} as PropType<string>,
|
|
42
|
+
initial: {} as PropType<Record<never, never>>,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const emit = defineEmits(["update:modelValue"]);
|
|
46
|
+
|
|
47
|
+
const ctx = useContext();
|
|
48
|
+
|
|
49
|
+
const tt = useI18nText();
|
|
50
|
+
|
|
51
|
+
const config = SchemaBase.useModel();
|
|
52
|
+
|
|
53
|
+
// el-select 的双向绑定:get 返回当前主题的显示名(供下拉框回显),
|
|
54
|
+
// set 把选中的主题 id(即 el-option 的 value)透传给父级表单
|
|
55
|
+
const model = computed({
|
|
56
|
+
get() {
|
|
57
|
+
return tt(ctx.internal.themes[config.value]?.name);
|
|
58
|
+
},
|
|
59
|
+
set(value) {
|
|
60
|
+
emit("update:modelValue", value);
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<style lang="scss">
|
|
66
|
+
|
|
67
|
+
.el-select-dropdown.theme-select {
|
|
68
|
+
overflow: hidden;
|
|
69
|
+
|
|
70
|
+
.el-select-dropdown__list {
|
|
71
|
+
margin: 0 !important;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.el-select-dropdown__item {
|
|
75
|
+
padding: 0;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.theme-root {
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 100%;
|
|
81
|
+
position: relative;
|
|
82
|
+
|
|
83
|
+
.theme-block-1 {
|
|
84
|
+
position: absolute;
|
|
85
|
+
width: 33%;
|
|
86
|
+
height: 100%;
|
|
87
|
+
background-color: var(--bg1);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.theme-block-2 {
|
|
91
|
+
position: absolute;
|
|
92
|
+
left: 33%;
|
|
93
|
+
width: 34%;
|
|
94
|
+
height: 100%;
|
|
95
|
+
background-color: var(--bg2);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.theme-block-3 {
|
|
99
|
+
position: absolute;
|
|
100
|
+
left: 67%;
|
|
101
|
+
width: 33%;
|
|
102
|
+
height: 100%;
|
|
103
|
+
background-color: var(--bg3);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.theme-title {
|
|
107
|
+
position: absolute;
|
|
108
|
+
left: 0;
|
|
109
|
+
width: 100%;
|
|
110
|
+
height: 100%;
|
|
111
|
+
display: flex;
|
|
112
|
+
flex-direction: row;
|
|
113
|
+
align-items: center;
|
|
114
|
+
justify-content: center;
|
|
115
|
+
font-size: 1em;
|
|
116
|
+
z-index: 100;
|
|
117
|
+
font-family: var(--font-family);
|
|
118
|
+
color: var(--k-color-primary);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
</style>
|
package/app/shims.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference path="../client/shims.d.ts" />
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 状态栏插件:向全局 "status" 插槽注册状态栏主体(可悬停出 tooltip 的
|
|
3
|
+
* 状态项容器),并向 "status-right" 插槽注册页面组件加载进度条。
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Context } from "@koishi-ce/client";
|
|
7
|
+
import Loading from "./loading.vue";
|
|
8
|
+
import Status from "./status.vue";
|
|
9
|
+
|
|
10
|
+
export default function (ctx: Context) {
|
|
11
|
+
ctx.slot({
|
|
12
|
+
type: "status",
|
|
13
|
+
component: Status,
|
|
14
|
+
order: -1000,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
ctx.slot({
|
|
18
|
+
type: "status-right",
|
|
19
|
+
component: Loading,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
页面组件按需加载进度条:按当前路由所需扩展(ctx.$loader.extensions)
|
|
3
|
+
的完成比例显示进度;store.entry 尚未就绪时以不确定进度(indeterminate)展示。
|
|
4
|
+
-->
|
|
5
|
+
<template>
|
|
6
|
+
<k-status v-if="progress < 1">
|
|
7
|
+
<el-progress :indeterminate="!store.entry" :percentage="progress * 100">
|
|
8
|
+
正在加载页面组件
|
|
9
|
+
</el-progress>
|
|
10
|
+
</k-status>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script lang="ts" setup>
|
|
14
|
+
import { store, useContext } from "@koishi-ce/client";
|
|
15
|
+
import { computed } from "vue";
|
|
16
|
+
|
|
17
|
+
const ctx = useContext();
|
|
18
|
+
|
|
19
|
+
const progress = computed(() => {
|
|
20
|
+
const states = Object.values(ctx.$loader.extensions);
|
|
21
|
+
return states.filter((state) => state.done.value).length / states.length;
|
|
22
|
+
});
|
|
23
|
+
</script>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
通用状态项容器(页面模板中 <k-status> 的内置实现):
|
|
3
|
+
内容区 + 悬停时的 tooltip(内容经 el-scrollbar 限高滚动)。
|
|
4
|
+
inheritAttrs 关闭,让外部透传的属性落到内层 div 而非根组件。
|
|
5
|
+
-->
|
|
6
|
+
<template>
|
|
7
|
+
<el-tooltip placement="top" effect="light" popper-class="k-status-tooltip">
|
|
8
|
+
<template #content>
|
|
9
|
+
<el-scrollbar max-height="calc(100vh - 4rem)">
|
|
10
|
+
<slot name="tooltip">
|
|
11
|
+
<span class="el-popper__empty"></span>
|
|
12
|
+
</slot>
|
|
13
|
+
</el-scrollbar>
|
|
14
|
+
</template>
|
|
15
|
+
<div class="k-status" v-bind="$attrs">
|
|
16
|
+
<slot></slot>
|
|
17
|
+
</div>
|
|
18
|
+
</el-tooltip>
|
|
19
|
+
</template>
|
|
20
|
+
|
|
21
|
+
<script lang="ts" setup>
|
|
22
|
+
defineOptions({
|
|
23
|
+
inheritAttrs: false,
|
|
24
|
+
});
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<style lang="scss" scoped>
|
|
28
|
+
|
|
29
|
+
.k-status {
|
|
30
|
+
cursor: default;
|
|
31
|
+
padding: 0 0.5rem;
|
|
32
|
+
display: inline-flex;
|
|
33
|
+
align-items: center;
|
|
34
|
+
transition: var(--color-transition);
|
|
35
|
+
user-select: none;
|
|
36
|
+
cursor: pointer;
|
|
37
|
+
|
|
38
|
+
&:hover {
|
|
39
|
+
background-color: var(--k-hover-bg);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
</style>
|
|
44
|
+
|
|
45
|
+
<style lang="scss">
|
|
46
|
+
|
|
47
|
+
.el-popper.k-status-tooltip {
|
|
48
|
+
padding: 0 0;
|
|
49
|
+
border-radius: 8px;
|
|
50
|
+
|
|
51
|
+
.el-popper__empty + .el-popper__arrow {
|
|
52
|
+
display: none;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
</style>
|