koishi-plugin-media-luna 0.0.7 → 0.0.8

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.
@@ -0,0 +1,159 @@
1
+ <template>
2
+ <div class="setup-storage">
3
+ <h3>存储配置</h3>
4
+ <p class="step-desc">选择生成图片的存储方式。推荐使用本地存储或 S3 兼容存储。</p>
5
+
6
+ <div v-if="loading" class="loading-state">
7
+ <k-icon name="sync" class="spin" />
8
+ <span>加载配置中...</span>
9
+ </div>
10
+
11
+ <template v-else>
12
+ <!-- 使用 ConfigRenderer 渲染配置字段 -->
13
+ <ConfigRenderer
14
+ :fields="fields"
15
+ v-model="localConfig"
16
+ />
17
+
18
+ <!-- 警告提示(当选择不使用时) -->
19
+ <div v-if="localConfig.backend === 'none'" class="warning-box">
20
+ <k-icon name="warning" />
21
+ <div>
22
+ <strong>注意</strong>
23
+ <p>选择"不使用"将保留生成服务返回的原始 URL,这些 URL 可能会过期或无法访问。建议配置存储后端以确保图片长期可用。</p>
24
+ </div>
25
+ </div>
26
+ </template>
27
+
28
+ <!-- 操作按钮 -->
29
+ <div class="step-actions">
30
+ <k-button type="primary" :loading="saving" :disabled="loading" @click="handleNext">
31
+ 下一步
32
+ </k-button>
33
+ </div>
34
+ </div>
35
+ </template>
36
+
37
+ <script setup lang="ts">
38
+ import { ref, watch, onMounted } from 'vue'
39
+ import { setupApi } from '../../api'
40
+ import type { ConfigField } from '../../types'
41
+ import ConfigRenderer from '../ConfigRenderer.vue'
42
+
43
+ const props = defineProps<{
44
+ modelValue: Record<string, any>
45
+ saving: boolean
46
+ }>()
47
+
48
+ const emit = defineEmits<{
49
+ (e: 'update:modelValue', value: Record<string, any>): void
50
+ (e: 'next'): void
51
+ }>()
52
+
53
+ const loading = ref(true)
54
+ const fields = ref<ConfigField[]>([])
55
+ const localConfig = ref<Record<string, any>>({})
56
+
57
+ // 同步 localConfig 到父组件
58
+ watch(localConfig, (newVal) => {
59
+ emit('update:modelValue', { ...newVal })
60
+ }, { deep: true })
61
+
62
+ // 加载配置字段和当前值
63
+ const loadConfig = async () => {
64
+ try {
65
+ loading.value = true
66
+ // 并行获取字段定义和当前配置
67
+ const [fieldsResult, configResult] = await Promise.all([
68
+ setupApi.getStorageFields(),
69
+ setupApi.getStorageConfig()
70
+ ])
71
+
72
+ fields.value = fieldsResult
73
+
74
+ // 合并当前配置(填充默认值)
75
+ const newConfig: Record<string, any> = { ...props.modelValue }
76
+ for (const field of fieldsResult) {
77
+ if (configResult[field.key] !== undefined) {
78
+ newConfig[field.key] = configResult[field.key]
79
+ } else if (newConfig[field.key] === undefined && field.default !== undefined) {
80
+ newConfig[field.key] = field.default
81
+ }
82
+ }
83
+ localConfig.value = newConfig
84
+ } catch (e) {
85
+ console.error('Failed to load storage config:', e)
86
+ } finally {
87
+ loading.value = false
88
+ }
89
+ }
90
+
91
+ const handleNext = () => {
92
+ emit('next')
93
+ }
94
+
95
+ onMounted(loadConfig)
96
+ </script>
97
+
98
+ <style scoped>
99
+ .setup-storage h3 {
100
+ font-size: 1.25rem;
101
+ font-weight: 600;
102
+ color: var(--k-color-text);
103
+ margin: 0 0 0.5rem 0;
104
+ }
105
+
106
+ .step-desc {
107
+ color: var(--k-color-text-description);
108
+ margin: 0 0 1.5rem 0;
109
+ }
110
+
111
+ .loading-state {
112
+ display: flex;
113
+ align-items: center;
114
+ justify-content: center;
115
+ gap: 0.5rem;
116
+ padding: 3rem;
117
+ color: var(--k-color-text-description);
118
+ }
119
+
120
+ .spin {
121
+ animation: spin 1s linear infinite;
122
+ }
123
+
124
+ @keyframes spin {
125
+ to { transform: rotate(360deg); }
126
+ }
127
+
128
+ /* 警告框 */
129
+ .warning-box {
130
+ display: flex;
131
+ gap: 1rem;
132
+ padding: 1rem;
133
+ margin-top: 1rem;
134
+ background: color-mix(in srgb, var(--k-color-warning) 10%, transparent);
135
+ border: 1px solid var(--k-color-warning);
136
+ border-radius: 8px;
137
+ color: var(--k-color-warning);
138
+ }
139
+
140
+ .warning-box strong {
141
+ display: block;
142
+ margin-bottom: 0.25rem;
143
+ }
144
+
145
+ .warning-box p {
146
+ margin: 0;
147
+ font-size: 0.9rem;
148
+ color: var(--k-color-text);
149
+ }
150
+
151
+ /* 操作按钮 */
152
+ .step-actions {
153
+ margin-top: 2rem;
154
+ padding-top: 1.5rem;
155
+ border-top: 1px solid var(--k-color-border);
156
+ display: flex;
157
+ justify-content: flex-end;
158
+ }
159
+ </style>
@@ -1,30 +1,36 @@
1
1
  <template>
2
2
  <k-layout class="app-layout media-luna-app">
3
- <div class="top-nav">
4
- <div class="nav-container">
5
- <div class="logo-area"><span class="logo-text">MEDIA LUNA</span></div>
6
- <div class="nav-tabs" role="tablist">
7
- <div
8
- v-for="item in menuItems"
9
- :key="item.id"
10
- class="nav-tab"
11
- :class="{ active: currentView === item.id }"
12
- @click="currentView = item.id"
13
- role="tab"
14
- :aria-selected="currentView === item.id"
15
- >
16
- <component :is="item.icon" class="tab-icon" />
17
- <span>{{ item.label }}</span>
3
+ <!-- 设置向导 -->
4
+ <SetupWizard v-if="showSetupWizard" @complete="handleSetupComplete" />
5
+
6
+ <!-- 主界面 -->
7
+ <template v-else>
8
+ <div class="top-nav">
9
+ <div class="nav-container">
10
+ <div class="logo-area"><span class="logo-text">MEDIA LUNA</span></div>
11
+ <div class="nav-tabs" role="tablist">
12
+ <div
13
+ v-for="item in menuItems"
14
+ :key="item.id"
15
+ class="nav-tab"
16
+ :class="{ active: currentView === item.id }"
17
+ @click="currentView = item.id"
18
+ role="tab"
19
+ :aria-selected="currentView === item.id"
20
+ >
21
+ <component :is="item.icon" class="tab-icon" />
22
+ <span>{{ item.label }}</span>
23
+ </div>
18
24
  </div>
19
25
  </div>
20
26
  </div>
21
- </div>
22
27
 
23
- <div class="main-content">
24
- <keep-alive>
25
- <component :is="activeComponent" />
26
- </keep-alive>
27
- </div>
28
+ <div class="main-content">
29
+ <keep-alive>
30
+ <component :is="activeComponent" />
31
+ </keep-alive>
32
+ </div>
33
+ </template>
28
34
  </k-layout>
29
35
  </template>
30
36
 
@@ -35,8 +41,28 @@ import PresetsView from '../components/PresetsView.vue'
35
41
  import TasksView from '../components/TasksView.vue'
36
42
  import GenerateView from '../components/GenerateView.vue'
37
43
  import SettingsView from '../components/SettingsView.vue'
44
+ import SetupWizard from '../components/SetupWizard.vue'
45
+ import { setupApi } from '../api'
38
46
 
39
47
  const currentView = ref('generate')
48
+ const showSetupWizard = ref(false)
49
+
50
+ // 检查是否需要显示设置向导
51
+ const checkSetupStatus = async () => {
52
+ try {
53
+ const status = await setupApi.status()
54
+ showSetupWizard.value = status.needsSetup
55
+ } catch (e) {
56
+ // 出错时不显示向导,正常进入应用
57
+ console.error('Failed to check setup status:', e)
58
+ showSetupWizard.value = false
59
+ }
60
+ }
61
+
62
+ // 设置完成回调
63
+ const handleSetupComplete = () => {
64
+ showSetupWizard.value = false
65
+ }
40
66
 
41
67
  const activeComponent = computed(() => {
42
68
  switch (currentView.value) {
@@ -69,7 +95,10 @@ function restoreHeader() {
69
95
  const el = document.querySelector('.layout-header') as HTMLElement
70
96
  if (el) el.style.display = prevHeaderDisplay || ''
71
97
  }
72
- onMounted(hideHeader)
98
+ onMounted(() => {
99
+ hideHeader()
100
+ checkSetupStatus()
101
+ })
73
102
  onBeforeUnmount(restoreHeader)
74
103
  </script>
75
104