🚧 WIP(custom): optimize UI of uploader config page

This commit is contained in:
Kuingsmile
2026-01-11 22:23:18 +08:00
parent 49b5f7a724
commit a62041439a
5 changed files with 101 additions and 28 deletions

View File

@@ -1054,7 +1054,7 @@
"setAsDefault": "Set as Default PicBed", "setAsDefault": "Set as Default PicBed",
"setSuccess": "Set Success", "setSuccess": "Set Success",
"subtitle": "{count} configurations available", "subtitle": "{count} configurations available",
"title": "Uploader Configurations" "title": "Configurations"
} }
}, },
"settings": { "settings": {

View File

@@ -1049,7 +1049,7 @@
"setAsDefault": "设为默认图床", "setAsDefault": "设为默认图床",
"setSuccess": "设置成功", "setSuccess": "设置成功",
"subtitle": "共 {count} 个配置", "subtitle": "共 {count} 个配置",
"title": "上传配置" "title": "配置"
} }
}, },
"settings": { "settings": {

View File

@@ -1049,7 +1049,7 @@
"setAsDefault": "設為預設圖床", "setAsDefault": "設為預設圖床",
"setSuccess": "設定成功", "setSuccess": "設定成功",
"subtitle": "共 {count} 個配置", "subtitle": "共 {count} 個配置",
"title": "上傳配置" "title": "配置"
} }
}, },
"settings": { "settings": {

View File

@@ -12,7 +12,7 @@
</div> </div>
<div class="header-text"> <div class="header-text">
<h1 class="page-title"> <h1 class="page-title">
{{ `${type} ${t('pages.uploaderConfig.title')}` }} {{ `${picBedName || type} ${t('pages.uploaderConfig.title')}` }}
</h1> </h1>
<p class="page-subtitle"> <p class="page-subtitle">
{{ t('pages.uploaderConfig.subtitle', { count: curConfigList.length }) }} {{ t('pages.uploaderConfig.subtitle', { count: curConfigList.length }) }}
@@ -50,6 +50,18 @@
<Cloud :size="20" /> <Cloud :size="20" />
</div> </div>
<div class="card-actions"> <div class="card-actions">
<button
class="action-btn"
:class="{ 'is-favorited': isConfigFavorited(item._id) }"
:title="
isConfigFavorited(item._id)
? t('pages.uploaderConfig.removeFromFavorites')
: t('pages.uploaderConfig.addToFavorites')
"
@click.stop="() => toggleConfigFavorite(item._id)"
>
<Heart :size="14" :fill="isConfigFavorited(item._id) ? 'currentColor' : 'none'" />
</button>
<button class="action-btn" :title="t('pages.uploaderConfig.edit')" @click.stop="openEditPage(item._id)"> <button class="action-btn" :title="t('pages.uploaderConfig.edit')" @click.stop="openEditPage(item._id)">
<Pencil :size="14" /> <Pencil :size="14" />
</button> </button>
@@ -76,20 +88,18 @@
<div class="card-body"> <div class="card-body">
<h3 class="config-name">{{ item._configName }}</h3> <h3 class="config-name">{{ item._configName }}</h3>
<div class="config-meta"> <div class="config-meta">
<Clock :size="12" /> <div style="display: flex; align-items: center; gap: 4px">
<span>{{ formatTime(item._updatedAt) }}</span> <Clock :size="12" />
</div> <span>{{ formatTime(item._updatedAt) }}</span>
</div> </div>
<div v-if="defaultConfigId === item._id" class="status-badge active">
<!-- Card Footer --> <CheckCircle2 :size="14" />
<div class="card-footer"> <span>{{ t('pages.uploaderConfig.selected') }}</span>
<div v-if="defaultConfigId === item._id" class="status-badge active"> </div>
<CheckCircle2 :size="14" /> <div v-else class="status-badge inactive">
<span>{{ t('pages.uploaderConfig.selected') }}</span> <Circle :size="14" />
</div> <span>{{ t('pages.uploaderConfig.clickToSelect') }}</span>
<div v-else class="status-badge inactive"> </div>
<Circle :size="14" />
<span>{{ t('pages.uploaderConfig.clickToSelect') }}</span>
</div> </div>
</div> </div>
@@ -119,9 +129,10 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useStorage } from '@vueuse/core'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { CheckCircle2, Circle, Clock, Cloud, Copy, Pencil, Plus, Settings2, Star, Trash2 } from 'lucide-vue-next' import { CheckCircle2, Circle, Clock, Cloud, Copy, Heart, Pencil, Plus, Settings2, Star, Trash2 } from 'lucide-vue-next'
import { onBeforeMount, ref } from 'vue' import { computed, onBeforeMount, ref } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router' import { onBeforeRouteUpdate, useRoute, useRouter } from 'vue-router'
@@ -140,11 +151,40 @@ const message = useMessage()
const { confirm } = useConfirm() const { confirm } = useConfirm()
const router = useRouter() const router = useRouter()
const route = useRoute() const route = useRoute()
const { defaultPicBedG } = usePicBed() const { defaultPicBedG, picBedG } = usePicBed()
const favoritePicbeds = useStorage<IFavoritePicbedItem[]>('favorite-picbeds', [])
const type = ref('') const type = ref('')
const curConfigList = ref<IStringKeyMap[]>([]) const curConfigList = ref<IStringKeyMap[]>([])
const defaultConfigId = ref('') const defaultConfigId = ref('')
const favoriteConfigs = useStorage<string[]>('favorite-configs', [])
const picBedName = computed(() => {
if (!picBedG.value || picBedG.value.length === 0) {
return ''
}
const target = picBedG.value.find(item => item.type === type.value)
return target ? target.name : ''
})
const isFavorited = computed(() => {
return favoritePicbeds.value.some(item => item.type === type.value)
})
function isConfigFavorited(configId: string): boolean {
return favoriteConfigs.value.includes(configId)
}
function toggleConfigFavorite(configId: string) {
const index = favoriteConfigs.value.indexOf(configId)
if (index > -1) {
favoriteConfigs.value.splice(index, 1)
message.success(t('pages.uploaderConfig.removedFromFavorites'))
} else {
favoriteConfigs.value.push(configId)
message.success(t('pages.uploaderConfig.addedToFavorites'))
}
}
async function selectItem(id: string) { async function selectItem(id: string) {
await window.electron.triggerRPC<void>(IRPCActionType.UPLOADER_SELECT, type.value, id) await window.electron.triggerRPC<void>(IRPCActionType.UPLOADER_SELECT, type.value, id)
@@ -272,6 +312,16 @@ function setDefaultPicBed(type: string) {
[configPaths.picBed.uploader]: type, [configPaths.picBed.uploader]: type,
}) })
function toggleFavorite() {
const index = favoritePicbeds.value.findIndex(item => item.type === type.value)
if (index > -1) {
favoritePicbeds.value.splice(index, 1)
message.success(t('pages.uploaderConfig.removedFromFavorites'))
} else {
favoritePicbeds.value.push({ type: type.value })
message.success(t('pages.uploaderConfig.addedToFavorites'))
}
}
const currentConfigName = curConfigList.value.find(item => item._id === defaultConfigId.value)?._configName const currentConfigName = curConfigList.value.find(item => item._id === defaultConfigId.value)?._configName
window.electron.sendRPC(IRPCActionType.TRAY_SET_TOOL_TIP, `${type} ${currentConfigName || ''}`) window.electron.sendRPC(IRPCActionType.TRAY_SET_TOOL_TIP, `${type} ${currentConfigName || ''}`)
message.success(t('pages.uploaderConfig.setSuccess')) message.success(t('pages.uploaderConfig.setSuccess'))

View File

@@ -186,6 +186,21 @@
left: 100%; left: 100%;
} }
.btn.is-favorited {
background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%);
box-shadow:
0 4px 14px rgb(239 68 68 / 35%),
0 2px 4px rgb(0 0 0 / 10%),
inset 0 1px 0 rgb(255 255 255 / 15%);
}
.btn.is-favorited:hover:not(:disabled) {
box-shadow:
0 8px 24px rgb(239 68 68 / 45%),
0 4px 8px rgb(0 0 0 / 15%),
inset 0 1px 0 rgb(255 255 255 / 20%);
}
/* ============================================ /* ============================================
Main Content Area Main Content Area
============================================ */ ============================================ */
@@ -340,7 +355,8 @@
/* Card Actions */ /* Card Actions */
.config-card .card-actions { .config-card .card-actions {
display: flex; display: grid;
grid-template-columns: repeat(2, 1fr);
opacity: 0; opacity: 0;
transform: translateY(-4px); transform: translateY(-4px);
transition: all 0.25s ease; transition: all 0.25s ease;
@@ -381,6 +397,19 @@
.action-btn.disabled { .action-btn.disabled {
cursor: not-allowed; cursor: not-allowed;
}
.action-btn.is-favorited {
color: #ef4444;
border-color: #ef4444;
background: rgb(239 68 68 / 10%);
}
.action-btn.is-favorited:hover {
color: #dc2626;
border-color: #dc2626;
background: rgb(239 68 68 / 20%);
transform: scale(1.08);
opacity: 0.4; opacity: 0.4;
pointer-events: none; pointer-events: none;
} }
@@ -410,12 +439,6 @@
} }
/* Card Footer */ /* Card Footer */
.card-footer {
position: relative;
z-index: 1;
margin-top: auto;
padding-top: 1rem;
}
.status-badge { .status-badge {
display: inline-flex; display: inline-flex;