@lingfan-engine/create 0.0.0-stage → 0.1.5
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/LICENSE +201 -0
- package/bin/create.mjs +18 -0
- package/package.json +44 -3
- package/src/cli/cli.ts +302 -0
- package/src/cli/prompts.ts +115 -0
- package/src/generate/dotfiles.ts +27 -0
- package/src/generate/index.ts +20 -0
- package/src/generate/project.ts +279 -0
- package/src/generate/replace.ts +91 -0
- package/src/index.ts +46 -0
- package/src/overlay/index.ts +10 -0
- package/src/overlay/merge.ts +143 -0
- package/src/registry/index.ts +15 -0
- package/src/registry/template-meta.ts +107 -0
- package/src/registry/versions.ts +109 -0
- package/src/shared/guards.ts +85 -0
- package/src/shared/index.ts +7 -0
- package/src/shared/texts.ts +55 -0
- package/template/README.md +138 -0
- package/template/__PROJECT__/Resources/Audio/.gitkeep +0 -0
- package/template/__PROJECT__/Resources/Fonts/README.md +10 -0
- package/template/__PROJECT__/Resources/Images/.gitkeep +0 -0
- package/template/__PROJECT__/Resources/Lang/.gitkeep +0 -0
- package/template/__PROJECT__/Resources/Stories/title/title_main.story +24 -0
- package/template/__PROJECT__/Resources/Video/.gitkeep +0 -0
- package/template/__PROJECT__/Resources/project.json +7 -0
- package/template/__PROJECT__/gitignore +8 -0
- package/template/__PROJECT__/index.html +34 -0
- package/template/__PROJECT__/package.json +22 -0
- package/template/__PROJECT__/pnpm-workspace.yaml +8 -0
- package/template/__PROJECT__/src/host/boot.ts +181 -0
- package/template/__PROJECT__/src/host/dialogue-view.ts +235 -0
- package/template/__PROJECT__/src/host/effects.ts +68 -0
- package/template/__PROJECT__/src/host/engine-wiring.ts +206 -0
- package/template/__PROJECT__/src/host/frame-loop.ts +42 -0
- package/template/__PROJECT__/src/host/index.ts +27 -0
- package/template/__PROJECT__/src/host/input-map.ts +51 -0
- package/template/__PROJECT__/src/host/layer-z.ts +50 -0
- package/template/__PROJECT__/src/host/save-slots.ts +38 -0
- package/template/__PROJECT__/src/host/stage-dom.ts +74 -0
- package/template/__PROJECT__/src/host/theme-fonts.ts +60 -0
- package/template/__PROJECT__/src/host/toast.ts +29 -0
- package/template/__PROJECT__/src/main.ts +61 -0
- package/template/__PROJECT__/src/styles/stage.css +183 -0
- package/template/__PROJECT__/src/vite-env.d.ts +14 -0
- package/template/__PROJECT__/tsconfig.json +19 -0
- package/template/__PROJECT__/tsconfig.node.json +14 -0
- package/template/__PROJECT__/vite.config.ts +64 -0
- package/template/overlays/README.md +70 -0
- package/template/overlays/miniprogram/README.md +104 -0
- package/template/overlays/miniprogram/index.html +15 -0
- package/template/overlays/miniprogram/package.patch.json +40 -0
- package/template/overlays/miniprogram/scripts/serve-project.mjs +273 -0
- package/template/overlays/miniprogram/src/App.vue +23 -0
- package/template/overlays/miniprogram/src/components/ChoiceLayer.vue +208 -0
- package/template/overlays/miniprogram/src/components/DialogueLayer.vue +183 -0
- package/template/overlays/miniprogram/src/components/NodeTree.vue +44 -0
- package/template/overlays/miniprogram/src/components/NoticeLayer.vue +87 -0
- package/template/overlays/miniprogram/src/components/SaveLoadBar.vue +168 -0
- package/template/overlays/miniprogram/src/components/StageView.vue +50 -0
- package/template/overlays/miniprogram/src/env.d.ts +1 -0
- package/template/overlays/miniprogram/src/main.ts +12 -0
- package/template/overlays/miniprogram/src/manifest.json +21 -0
- package/template/overlays/miniprogram/src/mp/frame.ts +51 -0
- package/template/overlays/miniprogram/src/mp/guards.ts +28 -0
- package/template/overlays/miniprogram/src/mp/host.ts +252 -0
- package/template/overlays/miniprogram/src/mp/index.ts +9 -0
- package/template/overlays/miniprogram/src/mp/platform.ts +210 -0
- package/template/overlays/miniprogram/src/mp/ports.ts +84 -0
- package/template/overlays/miniprogram/src/mp/project-source.ts +77 -0
- package/template/overlays/miniprogram/src/mp/slots.ts +64 -0
- package/template/overlays/miniprogram/src/mp/surface.ts +239 -0
- package/template/overlays/miniprogram/src/mp/video-surface.ts +104 -0
- package/template/overlays/miniprogram/src/mp/view.ts +91 -0
- package/template/overlays/miniprogram/src/pages/index/index.vue +299 -0
- package/template/overlays/miniprogram/src/pages.json +18 -0
- package/template/overlays/miniprogram/tsconfig.json +20 -0
- package/template/overlays/miniprogram/vite.config.ts +87 -0
- package/template/overlays/react/index.html +16 -0
- package/template/overlays/react/package.patch.json +18 -0
- package/template/overlays/react/src/App.tsx +32 -0
- package/template/overlays/react/src/main.tsx +74 -0
- package/template/overlays/react/src/vite-env.d.ts +14 -0
- package/template/overlays/react/tsconfig.json +20 -0
- package/template/overlays/react/vite.config.ts +67 -0
- package/template/overlays/tauri/README.md +59 -0
- package/template/overlays/tauri/package.patch.json +14 -0
- package/template/overlays/tauri/src/main.ts +58 -0
- package/template/overlays/tauri/src-tauri/Cargo.toml +16 -0
- package/template/overlays/tauri/src-tauri/build.rs +6 -0
- package/template/overlays/tauri/src-tauri/capabilities/default.json +7 -0
- package/template/overlays/tauri/src-tauri/icons/128x128.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/128x128@2x.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/32x32.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square107x107Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square142x142Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square150x150Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square284x284Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square30x30Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square310x310Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square44x44Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square71x71Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/Square89x89Logo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/StoreLogo.png +0 -0
- package/template/overlays/tauri/src-tauri/icons/icon.icns +0 -0
- package/template/overlays/tauri/src-tauri/icons/icon.ico +0 -0
- package/template/overlays/tauri/src-tauri/icons/icon.png +0 -0
- package/template/overlays/tauri/src-tauri/src/lib.rs +26 -0
- package/template/overlays/tauri/src-tauri/src/main.rs +7 -0
- package/template/overlays/tauri/src-tauri/src/preferences.rs +50 -0
- package/template/overlays/tauri/src-tauri/src/save.rs +178 -0
- package/template/overlays/tauri/src-tauri/tauri.conf.json +35 -0
- package/template/overlays/vue/index.html +16 -0
- package/template/overlays/vue/package.patch.json +16 -0
- package/template/overlays/vue/src/App.vue +30 -0
- package/template/overlays/vue/src/main.ts +68 -0
- package/template/overlays/vue/src/vite-env.d.ts +24 -0
- package/template/overlays/vue/tsconfig.json +19 -0
- package/template/overlays/vue/vite.config.ts +67 -0
- package/template/pack-template.ps1 +65 -0
- package/template/template-meta.json +40 -0
- package/README.md +0 -3
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* 选择层:菜单与输入两形态互斥(同一张卡片,`choices.options` 或 `choices.input` 二选一)。
|
|
4
|
+
*
|
|
5
|
+
* 层根拦住点击:选项点完不该再穿到舞台层推进一次对话。输入框的值暂存在页面状态里,
|
|
6
|
+
* 提交时一次性交回核心——这样「打字中的字」不会污染叙事状态。
|
|
7
|
+
*/
|
|
8
|
+
import type { MiniprogramChoicesData } from "../mp/index.ts";
|
|
9
|
+
|
|
10
|
+
defineOptions({ name: "ChoiceLayer" });
|
|
11
|
+
|
|
12
|
+
defineProps<{
|
|
13
|
+
/** 选择层数据:菜单、输入或两者皆空的空壳 */
|
|
14
|
+
choices: MiniprogramChoicesData;
|
|
15
|
+
/** 输入框当前值(受控) */
|
|
16
|
+
inputValue: string;
|
|
17
|
+
/** 层序(核心下发的 z 表) */
|
|
18
|
+
zIndex: number;
|
|
19
|
+
}>();
|
|
20
|
+
|
|
21
|
+
const emit = defineEmits<{
|
|
22
|
+
choose: [target: string];
|
|
23
|
+
input: [value: string];
|
|
24
|
+
submit: [];
|
|
25
|
+
}>();
|
|
26
|
+
|
|
27
|
+
/** 选项点击:带回核心给的列 id */
|
|
28
|
+
function onChoose(target: string): void {
|
|
29
|
+
emit("choose", target);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 输入事件:小程序把值放在 `event.detail.value`,Web 形态放在 `target.value` */
|
|
33
|
+
function onInput(event: unknown): void {
|
|
34
|
+
const detail = (event as { detail?: { value?: unknown } }).detail;
|
|
35
|
+
if (typeof detail?.value === "string") {
|
|
36
|
+
emit("input", detail.value);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const target = (event as { target?: { value?: unknown } }).target;
|
|
40
|
+
if (typeof target?.value === "string") emit("input", target.value);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** 提交:回车或点按钮都走这里 */
|
|
44
|
+
function onSubmit(): void {
|
|
45
|
+
emit("submit");
|
|
46
|
+
}
|
|
47
|
+
</script>
|
|
48
|
+
|
|
49
|
+
<template>
|
|
50
|
+
<view
|
|
51
|
+
class="layer choices"
|
|
52
|
+
:class="choices.rootClass"
|
|
53
|
+
:style="{ zIndex }"
|
|
54
|
+
@tap.stop
|
|
55
|
+
>
|
|
56
|
+
<view v-if="choices.promptHtml" class="layer-prompt">
|
|
57
|
+
<rich-text :nodes="choices.promptHtml" />
|
|
58
|
+
</view>
|
|
59
|
+
<view v-if="choices.options.length" class="choice-row">
|
|
60
|
+
<view
|
|
61
|
+
v-for="option in choices.options"
|
|
62
|
+
:key="option.key"
|
|
63
|
+
class="option"
|
|
64
|
+
@tap="onChoose(option.target)"
|
|
65
|
+
>
|
|
66
|
+
<rich-text :nodes="option.html" />
|
|
67
|
+
</view>
|
|
68
|
+
</view>
|
|
69
|
+
<view v-if="choices.input" class="input-row">
|
|
70
|
+
<input
|
|
71
|
+
class="input-box"
|
|
72
|
+
:value="inputValue"
|
|
73
|
+
:placeholder="choices.input.prompt"
|
|
74
|
+
@input="onInput"
|
|
75
|
+
@confirm="onSubmit"
|
|
76
|
+
/>
|
|
77
|
+
<view class="submit" @tap="onSubmit">
|
|
78
|
+
{{ choices.input.submitText }}
|
|
79
|
+
</view>
|
|
80
|
+
</view>
|
|
81
|
+
</view>
|
|
82
|
+
</template>
|
|
83
|
+
|
|
84
|
+
<style>
|
|
85
|
+
.layer {
|
|
86
|
+
position: absolute;
|
|
87
|
+
left: 0;
|
|
88
|
+
top: 0;
|
|
89
|
+
width: 100%;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* 选择层:与对话层同一张卡片形态,菜单或输入二选一 */
|
|
93
|
+
.choices {
|
|
94
|
+
top: auto;
|
|
95
|
+
bottom: 24rpx;
|
|
96
|
+
left: 24rpx;
|
|
97
|
+
right: 24rpx;
|
|
98
|
+
width: auto;
|
|
99
|
+
padding: 32rpx 40rpx;
|
|
100
|
+
box-sizing: border-box;
|
|
101
|
+
border: 2rpx solid rgba(68, 68, 68, 0.27);
|
|
102
|
+
border-radius: 24rpx;
|
|
103
|
+
background: rgba(30, 30, 46, 0.92);
|
|
104
|
+
color: #e6e6f0;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.layer-prompt {
|
|
108
|
+
margin: 0 0 20rpx;
|
|
109
|
+
color: #9aa5ce;
|
|
110
|
+
text-align: center;
|
|
111
|
+
font-size: 30rpx;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.choice-row {
|
|
115
|
+
display: flex;
|
|
116
|
+
flex-wrap: wrap;
|
|
117
|
+
gap: 24rpx;
|
|
118
|
+
justify-content: center;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.option {
|
|
122
|
+
padding: 16rpx 32rpx;
|
|
123
|
+
border: 2rpx solid rgba(122, 162, 247, 0.4);
|
|
124
|
+
border-radius: 20rpx;
|
|
125
|
+
background: rgba(30, 30, 46, 0.8);
|
|
126
|
+
color: #e6e6f0;
|
|
127
|
+
font-size: 30rpx;
|
|
128
|
+
text-align: center;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.option:active {
|
|
132
|
+
background: rgba(60, 80, 130, 0.72);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.input-row {
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
gap: 20rpx;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.input-box {
|
|
142
|
+
flex: 1;
|
|
143
|
+
padding: 16rpx 24rpx;
|
|
144
|
+
border: 2rpx solid rgba(122, 162, 247, 0.4);
|
|
145
|
+
border-radius: 20rpx;
|
|
146
|
+
background: rgba(30, 30, 46, 0.8);
|
|
147
|
+
color: #e6e6f0;
|
|
148
|
+
font-size: 30rpx;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.submit {
|
|
152
|
+
padding: 16rpx 32rpx;
|
|
153
|
+
border: 2rpx solid rgba(122, 162, 247, 0.4);
|
|
154
|
+
border-radius: 20rpx;
|
|
155
|
+
background: rgba(30, 30, 46, 0.8);
|
|
156
|
+
color: #e6e6f0;
|
|
157
|
+
font-size: 30rpx;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* 编号选项皮肤:整行靠左(序号由模板正文自带) */
|
|
161
|
+
.choices.tpl-choice-numbered .option {
|
|
162
|
+
text-align: left;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* 横屏:rpx 的基准是窗口宽度,横过来同一串 rpx 会被放大,方向相关的排版改用 px 定值 */
|
|
166
|
+
@media (orientation: landscape) {
|
|
167
|
+
.choices {
|
|
168
|
+
bottom: 12px;
|
|
169
|
+
left: 12px;
|
|
170
|
+
right: 12px;
|
|
171
|
+
padding: 12px 16px;
|
|
172
|
+
border-width: 1px;
|
|
173
|
+
border-radius: 12px;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.layer-prompt,
|
|
177
|
+
.option,
|
|
178
|
+
.input-box,
|
|
179
|
+
.submit {
|
|
180
|
+
font-size: 16px;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.choice-row {
|
|
184
|
+
gap: 10px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.layer-prompt {
|
|
188
|
+
margin-bottom: 10px;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.option,
|
|
192
|
+
.submit {
|
|
193
|
+
padding: 8px 16px;
|
|
194
|
+
border-width: 1px;
|
|
195
|
+
border-radius: 10px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.input-row {
|
|
199
|
+
gap: 10px;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
.input-box {
|
|
203
|
+
padding: 8px 12px;
|
|
204
|
+
border-width: 1px;
|
|
205
|
+
border-radius: 10px;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
</style>
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* 对话层:普通形态三段式(说话人 / 正文 / 推进提示),NVL 形态抬升为阅读区。
|
|
4
|
+
*
|
|
5
|
+
* 两种形态共用同一个容器类(`.dialogue`),NVL 时补 `.nvl-mode` 把高度抬高并改成
|
|
6
|
+
* 纵向排布——与桌面面板同构,差别只在单位(这里用 rpx,横屏在样式表尾部改 px)。
|
|
7
|
+
*
|
|
8
|
+
* 点击整层推进对话:面板本体吃掉点击,不让它穿到舞台层去触发第二次推进。
|
|
9
|
+
*/
|
|
10
|
+
import type {
|
|
11
|
+
MiniprogramDialogueData,
|
|
12
|
+
MiniprogramNvlData,
|
|
13
|
+
} from "../mp/index.ts";
|
|
14
|
+
|
|
15
|
+
defineOptions({ name: "DialogueLayer" });
|
|
16
|
+
|
|
17
|
+
defineProps<{
|
|
18
|
+
/** 普通对话形态的数据 */
|
|
19
|
+
dialogue: MiniprogramDialogueData;
|
|
20
|
+
/** NVL 累积形态的数据;非 null 时优先于普通形态 */
|
|
21
|
+
nvl: MiniprogramNvlData | null;
|
|
22
|
+
/** 层序(核心下发的 z 表) */
|
|
23
|
+
zIndex: number;
|
|
24
|
+
}>();
|
|
25
|
+
|
|
26
|
+
const emit = defineEmits<{ advance: [] }>();
|
|
27
|
+
|
|
28
|
+
function onAdvance(): void {
|
|
29
|
+
emit("advance");
|
|
30
|
+
}
|
|
31
|
+
</script>
|
|
32
|
+
|
|
33
|
+
<template>
|
|
34
|
+
<view
|
|
35
|
+
class="layer dialogue"
|
|
36
|
+
:class="nvl ? 'nvl-mode' : dialogue.rootClass"
|
|
37
|
+
:style="{ zIndex }"
|
|
38
|
+
@tap="onAdvance"
|
|
39
|
+
>
|
|
40
|
+
<!-- NVL 形态:累积行自上而下一行行铺开,最后一行是正在打字的当前行 -->
|
|
41
|
+
<view v-if="nvl" class="nvl-body">
|
|
42
|
+
<view v-for="(html, index) in nvl.pastLines" :key="index" class="text">
|
|
43
|
+
<rich-text :nodes="html" />
|
|
44
|
+
</view>
|
|
45
|
+
<view class="text">
|
|
46
|
+
<rich-text :nodes="nvl.typingHtml" />
|
|
47
|
+
</view>
|
|
48
|
+
</view>
|
|
49
|
+
<template v-else>
|
|
50
|
+
<view
|
|
51
|
+
v-if="dialogue.speakerHtml"
|
|
52
|
+
class="speaker"
|
|
53
|
+
:style="{ color: dialogue.speakerColor }"
|
|
54
|
+
>
|
|
55
|
+
<rich-text :nodes="dialogue.speakerHtml" />
|
|
56
|
+
</view>
|
|
57
|
+
<view
|
|
58
|
+
class="text"
|
|
59
|
+
:style="
|
|
60
|
+
dialogue.bodyFont ? { fontFamily: dialogue.bodyFont } : undefined
|
|
61
|
+
"
|
|
62
|
+
>
|
|
63
|
+
<rich-text :nodes="dialogue.bodyHtml" />
|
|
64
|
+
</view>
|
|
65
|
+
<view v-if="dialogue.hintHtml" class="advance-hint">
|
|
66
|
+
<rich-text :nodes="dialogue.hintHtml" />
|
|
67
|
+
</view>
|
|
68
|
+
</template>
|
|
69
|
+
</view>
|
|
70
|
+
</template>
|
|
71
|
+
|
|
72
|
+
<style>
|
|
73
|
+
.layer {
|
|
74
|
+
position: absolute;
|
|
75
|
+
left: 0;
|
|
76
|
+
top: 0;
|
|
77
|
+
width: 100%;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/* 对话层:贴底居中的卡片。
|
|
81
|
+
高度按视口百分比给下限:竖屏约等于桌面的 104~150px,横屏不会因为 rpx 基准换成大宽度而撑破屏。 */
|
|
82
|
+
.dialogue {
|
|
83
|
+
top: auto;
|
|
84
|
+
bottom: 24rpx;
|
|
85
|
+
left: 24rpx;
|
|
86
|
+
right: 24rpx;
|
|
87
|
+
width: auto;
|
|
88
|
+
min-height: 16%;
|
|
89
|
+
padding: 32rpx 40rpx;
|
|
90
|
+
box-sizing: border-box;
|
|
91
|
+
border: 2rpx solid rgba(68, 68, 68, 0.27);
|
|
92
|
+
border-radius: 24rpx;
|
|
93
|
+
background: rgba(30, 30, 46, 0.8);
|
|
94
|
+
color: #e6e6f0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/* NVL 累积层形态:容器抬升为阅读区(累积文本自上而下滚动) */
|
|
98
|
+
.dialogue.nvl-mode {
|
|
99
|
+
min-height: 46%;
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.nvl-body {
|
|
105
|
+
flex: 1;
|
|
106
|
+
display: flex;
|
|
107
|
+
flex-direction: column;
|
|
108
|
+
gap: 12rpx;
|
|
109
|
+
overflow-y: auto;
|
|
110
|
+
min-height: 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.speaker {
|
|
114
|
+
margin: 0 0 16rpx;
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
font-size: 30rpx;
|
|
117
|
+
letter-spacing: 1rpx;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.text {
|
|
121
|
+
margin: 0;
|
|
122
|
+
font-size: 34rpx;
|
|
123
|
+
line-height: 1.7;
|
|
124
|
+
white-space: pre-wrap;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* 推进指示器:右下角闪烁(桌面对应 `.advance-hint`) */
|
|
128
|
+
.advance-hint {
|
|
129
|
+
position: absolute;
|
|
130
|
+
right: 24rpx;
|
|
131
|
+
bottom: 16rpx;
|
|
132
|
+
font-size: 26rpx;
|
|
133
|
+
color: #7aa2f7;
|
|
134
|
+
opacity: 0.9;
|
|
135
|
+
animation: blink 1.2s infinite;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/* 对话模板皮肤:居中独白形态(与桌面的 .tpl-center 同名同色) */
|
|
139
|
+
.dialogue.tpl-center {
|
|
140
|
+
display: flex;
|
|
141
|
+
flex-direction: column;
|
|
142
|
+
justify-content: center;
|
|
143
|
+
align-items: center;
|
|
144
|
+
text-align: center;
|
|
145
|
+
font-size: 1.15em;
|
|
146
|
+
background: rgba(22, 22, 30, 0.8);
|
|
147
|
+
border-color: rgba(122, 162, 247, 0.27);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* 横屏:rpx 的基准是窗口宽度,横过来同一串 rpx 会被放大,方向相关的排版改用 px 定值 */
|
|
151
|
+
@media (orientation: landscape) {
|
|
152
|
+
.dialogue {
|
|
153
|
+
bottom: 12px;
|
|
154
|
+
left: 12px;
|
|
155
|
+
right: 12px;
|
|
156
|
+
padding: 12px 16px;
|
|
157
|
+
border-width: 1px;
|
|
158
|
+
border-radius: 12px;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
.speaker {
|
|
162
|
+
margin: 0 0 6px;
|
|
163
|
+
font-size: 15px;
|
|
164
|
+
letter-spacing: 0;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.text {
|
|
168
|
+
font-size: 16px;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.advance-hint {
|
|
172
|
+
right: 12px;
|
|
173
|
+
bottom: 8px;
|
|
174
|
+
font-size: 13px;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
@keyframes blink {
|
|
179
|
+
50% {
|
|
180
|
+
opacity: 0.2;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
</style>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* 递归节点树:把 `mp/view.ts` 算出的描述树铺成节点。
|
|
4
|
+
*
|
|
5
|
+
* 小程序没有 DOM,渲染就是「数据 + 模板」:这一层只做映射,不拼样式(行内样式串已在数据侧
|
|
6
|
+
* 算好)。组件自带样式——小程序组件默认样式隔离,页面样式不会穿透进来。自引用靠文件名。
|
|
7
|
+
*/
|
|
8
|
+
import type { MiniNode } from "../mp/view.ts";
|
|
9
|
+
|
|
10
|
+
defineProps<{ nodes: MiniNode[] }>();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<template>
|
|
14
|
+
<template v-for="node in nodes" :key="node.key">
|
|
15
|
+
<image
|
|
16
|
+
v-if="node.kind === 'image'"
|
|
17
|
+
class="node node-image"
|
|
18
|
+
:class="node.className"
|
|
19
|
+
:style="node.style"
|
|
20
|
+
:src="node.src"
|
|
21
|
+
mode="widthFix"
|
|
22
|
+
/>
|
|
23
|
+
<view
|
|
24
|
+
v-else
|
|
25
|
+
class="node"
|
|
26
|
+
:class="[`node-${node.kind}`, node.className]"
|
|
27
|
+
:style="node.style"
|
|
28
|
+
>
|
|
29
|
+
<text v-if="node.text">{{ node.text }}</text>
|
|
30
|
+
<rich-text v-else-if="node.html" :nodes="node.html" />
|
|
31
|
+
<NodeTree v-if="node.children" :nodes="node.children" />
|
|
32
|
+
</view>
|
|
33
|
+
</template>
|
|
34
|
+
</template>
|
|
35
|
+
|
|
36
|
+
<style>
|
|
37
|
+
.node {
|
|
38
|
+
position: absolute;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.node-image {
|
|
42
|
+
display: block;
|
|
43
|
+
}
|
|
44
|
+
</style>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* 通知层:贴右上角的逐条气泡,不吃点击(挡住下面的层会让舞台点不动)。
|
|
4
|
+
*
|
|
5
|
+
* 气泡的皮肤由 `rootClass` 决定(`tpl-notify-warning` / `tpl-notify-error`),
|
|
6
|
+
* 配色与桌面那份同名同色——两份实现同一套视觉语言。
|
|
7
|
+
*/
|
|
8
|
+
import type { MiniprogramNoticeData } from "../mp/index.ts";
|
|
9
|
+
|
|
10
|
+
defineOptions({ name: "NoticeLayer" });
|
|
11
|
+
|
|
12
|
+
defineProps<{
|
|
13
|
+
/** 当前显示的通知列表(新通知在末尾) */
|
|
14
|
+
notices: MiniprogramNoticeData[];
|
|
15
|
+
/** 层序(核心下发的 z 表) */
|
|
16
|
+
zIndex: number;
|
|
17
|
+
}>();
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<view class="layer notices" :style="{ zIndex }">
|
|
22
|
+
<view
|
|
23
|
+
v-for="notice in notices"
|
|
24
|
+
:key="notice.key"
|
|
25
|
+
class="notice"
|
|
26
|
+
:class="notice.rootClass"
|
|
27
|
+
>
|
|
28
|
+
<rich-text :nodes="notice.bodyHtml" />
|
|
29
|
+
</view>
|
|
30
|
+
</view>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<style>
|
|
34
|
+
.layer {
|
|
35
|
+
position: absolute;
|
|
36
|
+
left: 0;
|
|
37
|
+
top: 0;
|
|
38
|
+
width: 100%;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/* 通知层:贴右上角的一条条气泡(桌面对应 NotificationLayer),不吃点击 */
|
|
42
|
+
.notices {
|
|
43
|
+
top: 24rpx;
|
|
44
|
+
left: auto;
|
|
45
|
+
right: 24rpx;
|
|
46
|
+
width: auto;
|
|
47
|
+
max-width: 560rpx;
|
|
48
|
+
display: flex;
|
|
49
|
+
flex-direction: column;
|
|
50
|
+
align-items: flex-end;
|
|
51
|
+
gap: 16rpx;
|
|
52
|
+
pointer-events: none;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.notice {
|
|
56
|
+
padding: 16rpx 28rpx;
|
|
57
|
+
border-radius: 16rpx;
|
|
58
|
+
background: rgba(42, 47, 69, 0.87);
|
|
59
|
+
color: #9ece6a;
|
|
60
|
+
font-size: 26rpx;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* 通知层皮肤:按 tone 换色(与桌面的 .tpl-notify-* 同名同色) */
|
|
64
|
+
.notice.tpl-notify-warning {
|
|
65
|
+
color: #e0af68;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.notice.tpl-notify-error {
|
|
69
|
+
color: #f7768e;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* 横屏:rpx 的基准是窗口宽度,横过来同一串 rpx 会被放大,方向相关的排版改用 px 定值 */
|
|
73
|
+
@media (orientation: landscape) {
|
|
74
|
+
.notices {
|
|
75
|
+
top: 12px;
|
|
76
|
+
right: 12px;
|
|
77
|
+
max-width: 320px;
|
|
78
|
+
gap: 8px;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.notice {
|
|
82
|
+
padding: 8px 14px;
|
|
83
|
+
border-radius: 8px;
|
|
84
|
+
font-size: 13px;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
</style>
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* 存/读入口:左上角两颗按钮 + 打开后的槽位面板。
|
|
4
|
+
*
|
|
5
|
+
* 组件只管渲染与转发:槽位视图由页面在打开时从宿主取(`loadHostSlotViews`),点选判据与
|
|
6
|
+
* 存读命令都在宿主侧(`chooseHostSlot`),这里不判断空槽、不碰引擎——「谁能点」与「点了
|
|
7
|
+
* 做什么」各只有一处实现。
|
|
8
|
+
*
|
|
9
|
+
* 面板形态与桌面宿主的槽位面板同构(列表 + 标题/时间),移动端换成 rpx 与更大的点击面。
|
|
10
|
+
*/
|
|
11
|
+
import {
|
|
12
|
+
canChooseSlot,
|
|
13
|
+
type SlotPanelMode,
|
|
14
|
+
type SlotView,
|
|
15
|
+
} from "../mp/slots.ts";
|
|
16
|
+
|
|
17
|
+
const props = defineProps<{
|
|
18
|
+
/** 面板模式:null = 收起(只留入口条) */
|
|
19
|
+
mode: SlotPanelMode | null;
|
|
20
|
+
/** 逐槽视图(打开面板时由页面拉取) */
|
|
21
|
+
views: SlotView[];
|
|
22
|
+
}>();
|
|
23
|
+
|
|
24
|
+
const emit = defineEmits<{
|
|
25
|
+
/** 点入口条:打开对应模式的面板 */
|
|
26
|
+
open: [mode: SlotPanelMode];
|
|
27
|
+
/** 点槽位:交给宿主执行存/读 */
|
|
28
|
+
choose: [view: SlotView];
|
|
29
|
+
/** 关闭面板 */
|
|
30
|
+
close: [];
|
|
31
|
+
}>();
|
|
32
|
+
|
|
33
|
+
/** 槽位能否点:判据与桌面面板同一份 */
|
|
34
|
+
function pickable(view: SlotView): boolean {
|
|
35
|
+
return canChooseSlot(view, props.mode);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 槽位上的一行说明:有档给标题,没档写「空槽」 */
|
|
39
|
+
function slotHint(view: SlotView): string {
|
|
40
|
+
if (view.empty) return "空槽";
|
|
41
|
+
const title = (view.title ?? "").trim();
|
|
42
|
+
return title === "" ? "已有存档" : title;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** 点槽位:不可点(读档模式的空槽)就什么都不做 */
|
|
46
|
+
function pick(view: SlotView): void {
|
|
47
|
+
if (pickable(view)) emit("choose", view);
|
|
48
|
+
}
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<view class="save-load">
|
|
53
|
+
<view class="entries">
|
|
54
|
+
<view class="entry" @tap.stop="emit('open', 'save')">存</view>
|
|
55
|
+
<view class="entry" @tap.stop="emit('open', 'load')">读</view>
|
|
56
|
+
</view>
|
|
57
|
+
|
|
58
|
+
<view v-if="mode" class="sheet">
|
|
59
|
+
<view class="sheet-title">{{
|
|
60
|
+
mode === "save" ? "写入存档" : "读取存档"
|
|
61
|
+
}}</view>
|
|
62
|
+
<view class="slots">
|
|
63
|
+
<view
|
|
64
|
+
v-for="view in views"
|
|
65
|
+
:key="view.id"
|
|
66
|
+
class="slot"
|
|
67
|
+
:class="{ 'slot-disabled': !pickable(view) }"
|
|
68
|
+
@tap.stop="pick(view)"
|
|
69
|
+
>
|
|
70
|
+
<view class="slot-label">{{ view.label }}</view>
|
|
71
|
+
<view class="slot-hint">{{ slotHint(view) }}</view>
|
|
72
|
+
</view>
|
|
73
|
+
</view>
|
|
74
|
+
<view class="close" @tap.stop="emit('close')">关闭</view>
|
|
75
|
+
</view>
|
|
76
|
+
</view>
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<style>
|
|
80
|
+
/* 入口与面板都在左上:右上角归通知层,底部归对话与选择层 */
|
|
81
|
+
.save-load {
|
|
82
|
+
position: absolute;
|
|
83
|
+
left: 24rpx;
|
|
84
|
+
top: 24rpx;
|
|
85
|
+
pointer-events: auto;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.entries {
|
|
89
|
+
display: flex;
|
|
90
|
+
gap: 16rpx;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.entry,
|
|
94
|
+
.close {
|
|
95
|
+
padding: 12rpx 28rpx;
|
|
96
|
+
border: 2rpx solid rgba(122, 162, 247, 0.4);
|
|
97
|
+
border-radius: 16rpx;
|
|
98
|
+
background: rgba(30, 30, 46, 0.72);
|
|
99
|
+
color: #e6e6f0;
|
|
100
|
+
font-size: 26rpx;
|
|
101
|
+
text-align: center;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.entry:active,
|
|
105
|
+
.close:active {
|
|
106
|
+
background: rgba(60, 80, 130, 0.72);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* 面板:卡片形态与对话层同源(同一批色值),点击面按移动端放大 */
|
|
110
|
+
.sheet {
|
|
111
|
+
margin-top: 16rpx;
|
|
112
|
+
width: 520rpx;
|
|
113
|
+
padding: 24rpx 24rpx 16rpx;
|
|
114
|
+
box-sizing: border-box;
|
|
115
|
+
border: 2rpx solid rgba(68, 68, 68, 0.27);
|
|
116
|
+
border-radius: 24rpx;
|
|
117
|
+
background: rgba(30, 30, 46, 0.92);
|
|
118
|
+
color: #e6e6f0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.sheet-title {
|
|
122
|
+
margin: 0 0 16rpx;
|
|
123
|
+
color: #9aa5ce;
|
|
124
|
+
font-size: 26rpx;
|
|
125
|
+
text-align: center;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.slots {
|
|
129
|
+
display: flex;
|
|
130
|
+
flex-direction: column;
|
|
131
|
+
gap: 12rpx;
|
|
132
|
+
max-height: 520rpx;
|
|
133
|
+
overflow-y: auto;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.slot {
|
|
137
|
+
display: flex;
|
|
138
|
+
align-items: baseline;
|
|
139
|
+
justify-content: space-between;
|
|
140
|
+
gap: 16rpx;
|
|
141
|
+
padding: 16rpx 20rpx;
|
|
142
|
+
border: 2rpx solid rgba(122, 162, 247, 0.28);
|
|
143
|
+
border-radius: 16rpx;
|
|
144
|
+
background: rgba(30, 30, 46, 0.8);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.slot:active {
|
|
148
|
+
background: rgba(60, 80, 130, 0.72);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/* 空槽在读档模式下置灰:引擎读不到档必失败,点了只会多一条提示 */
|
|
152
|
+
.slot-disabled {
|
|
153
|
+
opacity: 0.45;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.slot-label {
|
|
157
|
+
font-size: 28rpx;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.slot-hint {
|
|
161
|
+
color: #9aa5ce;
|
|
162
|
+
font-size: 24rpx;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.close {
|
|
166
|
+
margin-top: 16rpx;
|
|
167
|
+
}
|
|
168
|
+
</style>
|