mdk-skills 2.4.1 → 2.4.2
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/package.json +4 -2
- package/scripts/web-ui/index.html +0 -15
- package/scripts/web-ui/src/App.vue +0 -195
- package/scripts/web-ui/src/api/skills.js +0 -207
- package/scripts/web-ui/src/components/ModalComp.vue +0 -182
- package/scripts/web-ui/src/components/SkillCard.vue +0 -99
- package/scripts/web-ui/src/components/StatusBar.vue +0 -87
- package/scripts/web-ui/src/main.js +0 -10
- package/scripts/web-ui/src/router/index.js +0 -17
- package/scripts/web-ui/src/styles/main.css +0 -352
- package/scripts/web-ui/src/utils/usage.js +0 -46
- package/scripts/web-ui/src/views/Dashboard.vue +0 -1697
- package/scripts/web-ui/src/views/ReadmeView.vue +0 -115
- package/scripts/web-ui/src/views/SceneSwitch.vue +0 -1361
- package/scripts/web-ui/src/views/Settings.vue +0 -529
- package/scripts/web-ui/vite.config.js +0 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mdk-skills",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.2",
|
|
4
4
|
"description": "mdk-engineer - 沉稳靠谱的前端开发助手 Claude Skills 配置包,一键注入 .claude/ 技能目录和 CLAUDE.md 人设配置",
|
|
5
5
|
"author": "XiaoMa",
|
|
6
6
|
"license": "MIT",
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
".claude/",
|
|
10
10
|
"CLAUDE.md",
|
|
11
|
-
"scripts/",
|
|
11
|
+
"scripts/cli.js",
|
|
12
|
+
"scripts/core.js",
|
|
13
|
+
"scripts/web-ui/server.js",
|
|
12
14
|
"scripts/web-ui/dist/"
|
|
13
15
|
],
|
|
14
16
|
"bin": {
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="zh-CN">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8" />
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
-
<title>mdk-skills 管理面板</title>
|
|
7
|
-
<script>
|
|
8
|
-
(function(){var t=localStorage.getItem("mdk-theme");if(t==="dark")document.documentElement.setAttribute("data-theme","dark")})();
|
|
9
|
-
</script>
|
|
10
|
-
</head>
|
|
11
|
-
<body>
|
|
12
|
-
<div id="app"></div>
|
|
13
|
-
<script type="module" src="/src/main.js"></script>
|
|
14
|
-
</body>
|
|
15
|
-
</html>
|
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<n-message-provider>
|
|
3
|
-
<n-notification-provider>
|
|
4
|
-
<n-config-provider :theme="theme" :locale="zhCN" :date-locale="dateZhCN">
|
|
5
|
-
<n-layout class="app-layout">
|
|
6
|
-
<!-- 导航栏 -->
|
|
7
|
-
<n-layout-header class="app-header" bordered>
|
|
8
|
-
<div class="header-inner">
|
|
9
|
-
<div class="header-left">
|
|
10
|
-
<span class="logo">mdk-skills</span>
|
|
11
|
-
<n-menu
|
|
12
|
-
v-model:value="activeKey"
|
|
13
|
-
mode="horizontal"
|
|
14
|
-
:options="menuOptions"
|
|
15
|
-
@update:value="onMenuChange"
|
|
16
|
-
/>
|
|
17
|
-
</div>
|
|
18
|
-
<div class="header-right">
|
|
19
|
-
<div v-if="statusData" class="header-status">
|
|
20
|
-
<span class="header-status-item" title="已启用技能数">
|
|
21
|
-
<n-icon size="14" color="#18a058"><CheckmarkCircleOutline /></n-icon>
|
|
22
|
-
<span>{{ statusData.enabledCount }}/{{ statusData.totalCount }}</span>
|
|
23
|
-
</span>
|
|
24
|
-
<span class="header-status-divider" />
|
|
25
|
-
<span class="header-status-item" title="当前场景">
|
|
26
|
-
<n-icon size="14" color="#2080f0"><SwapHorizontalOutline /></n-icon>
|
|
27
|
-
<span>{{ statusData.activeProfile?.name || "自定义" }}</span>
|
|
28
|
-
</span>
|
|
29
|
-
<span class="header-status-divider" />
|
|
30
|
-
<span
|
|
31
|
-
class="header-status-item clickable"
|
|
32
|
-
title="CLAUDE.md 状态,点击查看"
|
|
33
|
-
@click="$router.push({ name: 'ClaudeMd' })"
|
|
34
|
-
>
|
|
35
|
-
<n-icon size="14" :color="statusData.hasClaudeMd ? '#18a058' : '#d03050'"><DocumentTextOutline /></n-icon>
|
|
36
|
-
<span>{{ statusData.hasClaudeMd ? "已就绪" : "未创建" }}</span>
|
|
37
|
-
</span>
|
|
38
|
-
</div>
|
|
39
|
-
<n-tag
|
|
40
|
-
v-if="statusData"
|
|
41
|
-
:type="statusData.sourcePath ? 'success' : 'warning'"
|
|
42
|
-
size="small"
|
|
43
|
-
>
|
|
44
|
-
{{ statusData.sourcePath ? "已连接" : "未设置" }}
|
|
45
|
-
</n-tag>
|
|
46
|
-
<n-button quaternary size="small" @click="toggleTheme">
|
|
47
|
-
<template #icon>
|
|
48
|
-
<n-icon><MoonOutline v-if="!darkMode" /><SunnyOutline v-else /></n-icon>
|
|
49
|
-
</template>
|
|
50
|
-
</n-button>
|
|
51
|
-
</div>
|
|
52
|
-
</div>
|
|
53
|
-
</n-layout-header>
|
|
54
|
-
|
|
55
|
-
<!-- 内容区 -->
|
|
56
|
-
<n-layout-content class="app-content">
|
|
57
|
-
<transition name="fade" mode="out-in">
|
|
58
|
-
<router-view @refresh="loadStatus" />
|
|
59
|
-
</transition>
|
|
60
|
-
</n-layout-content>
|
|
61
|
-
</n-layout>
|
|
62
|
-
</n-config-provider>
|
|
63
|
-
</n-notification-provider>
|
|
64
|
-
</n-message-provider>
|
|
65
|
-
</template>
|
|
66
|
-
|
|
67
|
-
<script setup>
|
|
68
|
-
import { h, ref, onMounted, computed, watch } from "vue";
|
|
69
|
-
import { useRouter, useRoute } from "vue-router";
|
|
70
|
-
import { NIcon, zhCN, dateZhCN, darkTheme } from "naive-ui";
|
|
71
|
-
import {
|
|
72
|
-
AppsOutline,
|
|
73
|
-
SwapHorizontalOutline,
|
|
74
|
-
CogOutline,
|
|
75
|
-
DocumentTextOutline,
|
|
76
|
-
CheckmarkCircleOutline,
|
|
77
|
-
MoonOutline,
|
|
78
|
-
SunnyOutline,
|
|
79
|
-
} from "@vicons/ionicons5";
|
|
80
|
-
import { getStatus } from "./api/skills";
|
|
81
|
-
|
|
82
|
-
const router = useRouter();
|
|
83
|
-
const route = useRoute();
|
|
84
|
-
|
|
85
|
-
const statusData = ref(null);
|
|
86
|
-
|
|
87
|
-
const darkMode = ref(localStorage.getItem("mdk-theme") === "dark");
|
|
88
|
-
|
|
89
|
-
const theme = computed(() => (darkMode.value ? darkTheme : null));
|
|
90
|
-
|
|
91
|
-
watch(darkMode, (val) => {
|
|
92
|
-
localStorage.setItem("mdk-theme", val ? "dark" : "light");
|
|
93
|
-
document.documentElement.setAttribute("data-theme", val ? "dark" : "light");
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
function toggleTheme() {
|
|
97
|
-
darkMode.value = !darkMode.value;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
const menuOptions = [
|
|
101
|
-
{
|
|
102
|
-
label: "仪表盘",
|
|
103
|
-
key: "Dashboard",
|
|
104
|
-
icon: () => h(NIcon, null, { default: () => h(AppsOutline) }),
|
|
105
|
-
},
|
|
106
|
-
{
|
|
107
|
-
label: "场景切换",
|
|
108
|
-
key: "Scenes",
|
|
109
|
-
icon: () => h(NIcon, null, { default: () => h(SwapHorizontalOutline) }),
|
|
110
|
-
},
|
|
111
|
-
{
|
|
112
|
-
label: "设置",
|
|
113
|
-
key: "Settings",
|
|
114
|
-
icon: () => h(NIcon, null, { default: () => h(CogOutline) }),
|
|
115
|
-
},
|
|
116
|
-
{
|
|
117
|
-
label: "CLAUDE.md",
|
|
118
|
-
key: "ClaudeMd",
|
|
119
|
-
icon: () => h(NIcon, null, { default: () => h(DocumentTextOutline) }),
|
|
120
|
-
},
|
|
121
|
-
];
|
|
122
|
-
|
|
123
|
-
const activeKey = ref(route.name || "Dashboard");
|
|
124
|
-
|
|
125
|
-
function onMenuChange(key) {
|
|
126
|
-
router.push({ name: key });
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
router.afterEach((to) => {
|
|
130
|
-
activeKey.value = to.name;
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
async function loadStatus() {
|
|
134
|
-
try {
|
|
135
|
-
statusData.value = await getStatus();
|
|
136
|
-
} catch {
|
|
137
|
-
// 静默失败
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
onMounted(() => {
|
|
142
|
-
loadStatus();
|
|
143
|
-
document.documentElement.setAttribute("data-theme", darkMode.value ? "dark" : "light");
|
|
144
|
-
});
|
|
145
|
-
</script>
|
|
146
|
-
|
|
147
|
-
<style>
|
|
148
|
-
@import "./styles/main.css";
|
|
149
|
-
|
|
150
|
-
.fade-enter-active,
|
|
151
|
-
.fade-leave-active {
|
|
152
|
-
transition:
|
|
153
|
-
opacity 0.2s ease,
|
|
154
|
-
transform 0.2s ease;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
.fade-enter-from {
|
|
158
|
-
opacity: 0;
|
|
159
|
-
transform: translateY(6px);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.fade-leave-to {
|
|
163
|
-
opacity: 0;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
.header-status {
|
|
167
|
-
display: flex;
|
|
168
|
-
align-items: center;
|
|
169
|
-
gap: 8px;
|
|
170
|
-
margin-right: 8px;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.header-status-item {
|
|
174
|
-
display: flex;
|
|
175
|
-
align-items: center;
|
|
176
|
-
gap: 4px;
|
|
177
|
-
font-size: 12px;
|
|
178
|
-
color: #666;
|
|
179
|
-
white-space: nowrap;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
.header-status-item.clickable {
|
|
183
|
-
cursor: pointer;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.header-status-item.clickable:hover {
|
|
187
|
-
color: #2080f0;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
.header-status-divider {
|
|
191
|
-
width: 1px;
|
|
192
|
-
height: 14px;
|
|
193
|
-
background: #e0e0e0;
|
|
194
|
-
}
|
|
195
|
-
</style>
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
const BASE = "/api";
|
|
2
|
-
|
|
3
|
-
async function request(path, options = {}) {
|
|
4
|
-
// GET 请求加时间戳,绕开浏览器缓存
|
|
5
|
-
const url = (options.method || "GET") === "GET"
|
|
6
|
-
? BASE + path + (path.includes("?") ? "&" : "?") + "_t=" + Date.now()
|
|
7
|
-
: BASE + path;
|
|
8
|
-
const res = await fetch(url, {
|
|
9
|
-
headers: { "Content-Type": "application/json" },
|
|
10
|
-
...options,
|
|
11
|
-
});
|
|
12
|
-
return res.json();
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function getSkills() {
|
|
16
|
-
return request("/skills");
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function toggleSkill(name, enabled) {
|
|
20
|
-
return request("/skills/toggle", {
|
|
21
|
-
method: "POST",
|
|
22
|
-
body: JSON.stringify({ name, enabled }),
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function installSkills(selected) {
|
|
27
|
-
return request("/skills/install", {
|
|
28
|
-
method: "POST",
|
|
29
|
-
body: JSON.stringify({ selected }),
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function getProfiles() {
|
|
34
|
-
return request("/profiles");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function applyProfile(profileId) {
|
|
38
|
-
return request("/profiles/apply", {
|
|
39
|
-
method: "POST",
|
|
40
|
-
body: JSON.stringify({ profileId }),
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function saveProfile(data) {
|
|
45
|
-
return request("/profiles/save", {
|
|
46
|
-
method: "POST",
|
|
47
|
-
body: JSON.stringify(data),
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export function deleteProfile(id) {
|
|
52
|
-
return request("/profiles/delete", {
|
|
53
|
-
method: "POST",
|
|
54
|
-
body: JSON.stringify({ id }),
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function getStatus() {
|
|
59
|
-
return request("/status");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function getSource() {
|
|
63
|
-
return request("/source");
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function connectSource(path) {
|
|
67
|
-
return request("/source/connect", {
|
|
68
|
-
method: "POST",
|
|
69
|
-
body: JSON.stringify({ path }),
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export function clearSource() {
|
|
74
|
-
return request("/source/clear", { method: "POST" });
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export function syncSource() {
|
|
78
|
-
return request("/source/sync", { method: "POST" });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function initSource() {
|
|
82
|
-
return request("/source/init", { method: "POST" });
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export function diagnose() {
|
|
86
|
-
return request("/diagnose");
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function getReadme() {
|
|
90
|
-
return request("/readme");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function getSkillsReadme() {
|
|
94
|
-
return request("/skills-readme");
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export function getClaudeMd() {
|
|
98
|
-
return request("/claudemd");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
export function getSkillReadme(name) {
|
|
102
|
-
return request("/skills/" + encodeURIComponent(name) + "/readme");
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
export function updateSkillMeta(name, data) {
|
|
106
|
-
return request("/skills/" + encodeURIComponent(name) + "/meta", {
|
|
107
|
-
method: "PATCH",
|
|
108
|
-
body: JSON.stringify(data),
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
export function pullSkills(url, names = []) {
|
|
113
|
-
return request("/skills/pull", {
|
|
114
|
-
method: "POST",
|
|
115
|
-
body: JSON.stringify({ url, names }),
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export function cancelPull(url) {
|
|
120
|
-
return request("/skills/pull-cancel", {
|
|
121
|
-
method: "POST",
|
|
122
|
-
body: JSON.stringify({ url }),
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function deleteSkill(name) {
|
|
127
|
-
return request("/skills/" + encodeURIComponent(name), {
|
|
128
|
-
method: "DELETE",
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function getSkillSource(name) {
|
|
133
|
-
return request("/skills/" + encodeURIComponent(name) + "/source");
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
export function updateSkill(name) {
|
|
137
|
-
return request("/skills/" + encodeURIComponent(name) + "/update", {
|
|
138
|
-
method: "POST",
|
|
139
|
-
});
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export function batchUpdateSkills(names, url) {
|
|
143
|
-
return request("/skills/batch-update", {
|
|
144
|
-
method: "POST",
|
|
145
|
-
body: JSON.stringify({ names, url }),
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export function openSkillDir(name) {
|
|
150
|
-
return request("/skills/" + encodeURIComponent(name) + "/open", {
|
|
151
|
-
method: "POST",
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
export function retryDelete(name) {
|
|
156
|
-
return request("/skills/" + encodeURIComponent(name) + "/retry-delete", {
|
|
157
|
-
method: "POST",
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function retryInstall(name) {
|
|
162
|
-
return request("/skills/" + encodeURIComponent(name) + "/retry-install", {
|
|
163
|
-
method: "POST",
|
|
164
|
-
});
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
export function openSkillsDest() {
|
|
168
|
-
return request("/skills-dest/open", {
|
|
169
|
-
method: "POST",
|
|
170
|
-
});
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function createReadme(data) {
|
|
174
|
-
return request("/readme/create", {
|
|
175
|
-
method: "POST",
|
|
176
|
-
body: JSON.stringify(data),
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export function cancelTask() {
|
|
181
|
-
return fetch("/api/tasks/cancel", { method: "POST" }).catch(() => {});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export function getSourceNames() {
|
|
185
|
-
return request("/source-names");
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
export function saveSourceNames(names) {
|
|
189
|
-
return request("/source-names", {
|
|
190
|
-
method: "POST",
|
|
191
|
-
body: JSON.stringify({ names }),
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export function bulkToggleSkills(names, enabled) {
|
|
196
|
-
return request("/skills/bulk-toggle", {
|
|
197
|
-
method: "POST",
|
|
198
|
-
body: JSON.stringify({ names, enabled }),
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export function bulkDeleteSkills(names) {
|
|
203
|
-
return request("/skills/bulk-delete", {
|
|
204
|
-
method: "POST",
|
|
205
|
-
body: JSON.stringify({ names }),
|
|
206
|
-
});
|
|
207
|
-
}
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
<template>
|
|
2
|
-
<Teleport to="body">
|
|
3
|
-
<Transition name="modal-fade">
|
|
4
|
-
<div v-if="show" class="modal-overlay" @click.self="onMaskClick">
|
|
5
|
-
<div class="modal-card" :style="{ maxWidth: width }">
|
|
6
|
-
<div class="modal-header">
|
|
7
|
-
<span class="modal-title">{{ title }}</span>
|
|
8
|
-
<button class="modal-close" @click="close">×</button>
|
|
9
|
-
</div>
|
|
10
|
-
<div class="modal-body">
|
|
11
|
-
<slot />
|
|
12
|
-
</div>
|
|
13
|
-
<div v-if="$slots.footer" class="modal-footer">
|
|
14
|
-
<slot name="footer" />
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
17
|
-
</div>
|
|
18
|
-
</Transition>
|
|
19
|
-
</Teleport>
|
|
20
|
-
</template>
|
|
21
|
-
|
|
22
|
-
<script setup>
|
|
23
|
-
import { watch, onUnmounted } from "vue";
|
|
24
|
-
|
|
25
|
-
const props = defineProps({
|
|
26
|
-
show: Boolean,
|
|
27
|
-
title: { type: String, default: "" },
|
|
28
|
-
width: { type: String, default: "480px" },
|
|
29
|
-
maskClosable: { type: Boolean, default: true },
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const emit = defineEmits(["update:show"]);
|
|
33
|
-
|
|
34
|
-
function close() {
|
|
35
|
-
emit("update:show", false);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function onMaskClick() {
|
|
39
|
-
if (props.maskClosable) close();
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Escape 关闭 + body scroll lock
|
|
43
|
-
let escHandler = null;
|
|
44
|
-
let prevOverflow = null;
|
|
45
|
-
watch(
|
|
46
|
-
() => props.show,
|
|
47
|
-
(v) => {
|
|
48
|
-
if (escHandler) {
|
|
49
|
-
document.removeEventListener("keydown", escHandler);
|
|
50
|
-
escHandler = null;
|
|
51
|
-
}
|
|
52
|
-
if (v) {
|
|
53
|
-
prevOverflow = document.body.style.overflow;
|
|
54
|
-
document.body.style.overflow = "hidden";
|
|
55
|
-
escHandler = (e) => {
|
|
56
|
-
if (e.key === "Escape") close();
|
|
57
|
-
};
|
|
58
|
-
document.addEventListener("keydown", escHandler);
|
|
59
|
-
} else {
|
|
60
|
-
document.body.style.overflow = prevOverflow || "";
|
|
61
|
-
prevOverflow = null;
|
|
62
|
-
}
|
|
63
|
-
},
|
|
64
|
-
{ immediate: true },
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
onUnmounted(() => {
|
|
68
|
-
if (escHandler) document.removeEventListener("keydown", escHandler);
|
|
69
|
-
if (prevOverflow !== null) document.body.style.overflow = prevOverflow || "";
|
|
70
|
-
});
|
|
71
|
-
</script>
|
|
72
|
-
|
|
73
|
-
<style scoped>
|
|
74
|
-
.modal-overlay {
|
|
75
|
-
position: fixed;
|
|
76
|
-
inset: 0;
|
|
77
|
-
z-index: 2000;
|
|
78
|
-
display: flex;
|
|
79
|
-
align-items: center;
|
|
80
|
-
justify-content: center;
|
|
81
|
-
background: rgba(0, 0, 0, 0.45);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.modal-card {
|
|
85
|
-
width: 90%;
|
|
86
|
-
background: #fff;
|
|
87
|
-
border-radius: 8px;
|
|
88
|
-
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12);
|
|
89
|
-
display: flex;
|
|
90
|
-
flex-direction: column;
|
|
91
|
-
max-height: 85vh;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.modal-header {
|
|
95
|
-
display: flex;
|
|
96
|
-
align-items: center;
|
|
97
|
-
justify-content: space-between;
|
|
98
|
-
padding: 14px 20px 0;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.modal-title {
|
|
102
|
-
font-size: 16px;
|
|
103
|
-
font-weight: 600;
|
|
104
|
-
color: #333;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
.modal-close {
|
|
108
|
-
width: 28px;
|
|
109
|
-
height: 28px;
|
|
110
|
-
border: none;
|
|
111
|
-
background: transparent;
|
|
112
|
-
font-size: 20px;
|
|
113
|
-
line-height: 1;
|
|
114
|
-
color: #999;
|
|
115
|
-
cursor: pointer;
|
|
116
|
-
border-radius: 4px;
|
|
117
|
-
display: flex;
|
|
118
|
-
align-items: center;
|
|
119
|
-
justify-content: center;
|
|
120
|
-
transition: background 0.15s, color 0.15s;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.modal-close:hover {
|
|
124
|
-
background: #f0f0f0;
|
|
125
|
-
color: #333;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
.modal-body {
|
|
129
|
-
padding: 16px 20px;
|
|
130
|
-
overflow-y: auto;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
.modal-footer {
|
|
134
|
-
display: flex;
|
|
135
|
-
justify-content: flex-end;
|
|
136
|
-
gap: 8px;
|
|
137
|
-
padding: 12px 20px 16px;
|
|
138
|
-
border-top: 1px solid #f0f0f0;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/* fade 过渡 */
|
|
142
|
-
.modal-fade-enter-active {
|
|
143
|
-
transition: opacity 0.2s ease;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
.modal-fade-leave-active {
|
|
147
|
-
transition: opacity 0.15s ease;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.modal-fade-enter-from,
|
|
151
|
-
.modal-fade-leave-to {
|
|
152
|
-
opacity: 0;
|
|
153
|
-
}
|
|
154
|
-
</style>
|
|
155
|
-
|
|
156
|
-
<style>
|
|
157
|
-
[data-theme="dark"] .modal-card {
|
|
158
|
-
background: #2a2a42 !important;
|
|
159
|
-
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5) !important;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
[data-theme="dark"] .modal-title {
|
|
163
|
-
color: #e4e4ef !important;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
[data-theme="dark"] .modal-close {
|
|
167
|
-
color: #6c7086 !important;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
[data-theme="dark"] .modal-close:hover {
|
|
171
|
-
background: #323250 !important;
|
|
172
|
-
color: #cdd6f4 !important;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
[data-theme="dark"] .modal-footer {
|
|
176
|
-
border-top-color: #363650 !important;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
[data-theme="dark"] .modal-overlay {
|
|
180
|
-
background: rgba(0, 0, 0, 0.65) !important;
|
|
181
|
-
}
|
|
182
|
-
</style>
|