mirror of
https://github.com/OpenListTeam/OpenList-Desktop.git
synced 2025-11-25 03:14:56 +08:00
feat: remove tutorial functionality and related UI components
This commit is contained in:
@@ -178,7 +178,6 @@ winget install OpenListTeam.OpenListDesktop
|
||||
1. **初始设置**:首次启动时,应用程序将指导您完成初始配置
|
||||
2. **服务安装**:在提示时安装 OpenList 服务
|
||||
3. **存储配置**:配置您的第一个云存储连接
|
||||
4. **教程**:完成交互式教程以学习关键功能
|
||||
|
||||
### 基本操作
|
||||
|
||||
|
||||
@@ -178,7 +178,6 @@ winget install OpenListTeam.OpenListDesktop
|
||||
1. **Initial Setup**: On first launch, the application will guide you through initial configuration
|
||||
2. **Service Installation**: Install the OpenList service when prompted
|
||||
3. **Storage Configuration**: Configure your first cloud storage connection
|
||||
4. **Tutorial**: Complete the interactive tutorial to learn key features
|
||||
|
||||
### Basic Operations
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@ import { useTray } from './composables/useTray'
|
||||
import { TauriAPI } from './api/tauri'
|
||||
import Navigation from './components/Navigation.vue'
|
||||
import TitleBar from './components/ui/TitleBar.vue'
|
||||
import TutorialOverlay from './components/ui/TutorialOverlay.vue'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const { t } = useTranslation()
|
||||
@@ -147,8 +146,6 @@ onUnmounted(() => {
|
||||
</router-view>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<TutorialOverlay />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -6,16 +6,12 @@
|
||||
<h4>{{ t('dashboard.quickActions.openlistService') }}</h4>
|
||||
</div>
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
@click="toggleCore"
|
||||
:class="['action-btn', 'service-btn', { running: isCoreRunning }]"
|
||||
:disabled="loading"
|
||||
>
|
||||
<button @click="toggleCore" :class="['action-btn', 'service-btn', { running: isCoreRunning }]">
|
||||
<component :is="serviceButtonIcon" :size="20" />
|
||||
<span>{{ serviceButtonText }}</span>
|
||||
</button>
|
||||
|
||||
<button @click="restartCore" :disabled="!isCoreRunning || loading" class="action-btn restart-btn">
|
||||
<button @click="restartCore" :disabled="!isCoreRunning" class="action-btn restart-btn">
|
||||
<RotateCcw :size="18" />
|
||||
<span>{{ t('dashboard.quickActions.restart') }}</span>
|
||||
</button>
|
||||
@@ -47,7 +43,6 @@
|
||||
<div class="action-buttons">
|
||||
<button
|
||||
@click="rcloneStore.serviceRunning ? stopBackend() : startBackend()"
|
||||
:disabled="loading || rcloneStore.loading"
|
||||
:class="['action-btn', 'service-indicator-btn', { active: rcloneStore.serviceRunning }]"
|
||||
>
|
||||
<component :is="rcloneStore.serviceRunning ? Square : Play" :size="18" />
|
||||
@@ -93,7 +88,7 @@ import { useAppStore } from '../../stores/app'
|
||||
import { useRcloneStore } from '../../stores/rclone'
|
||||
import { useTranslation } from '../../composables/useI18n'
|
||||
import Card from '../ui/Card.vue'
|
||||
import { Play, Square, RotateCcw, ExternalLink, Settings, HardDrive, Loader, Key } from 'lucide-vue-next'
|
||||
import { Play, Square, RotateCcw, ExternalLink, Settings, HardDrive, Key } from 'lucide-vue-next'
|
||||
import { TauriAPI } from '@/api/tauri'
|
||||
|
||||
const { t } = useTranslation()
|
||||
@@ -102,17 +97,14 @@ const appStore = useAppStore()
|
||||
const rcloneStore = useRcloneStore()
|
||||
|
||||
const isCoreRunning = computed(() => appStore.isCoreRunning)
|
||||
const loading = computed(() => appStore.loading)
|
||||
const settings = computed(() => appStore.settings)
|
||||
let statusCheckInterval: number | null = null
|
||||
|
||||
const serviceButtonIcon = computed(() => {
|
||||
if (loading.value) return Loader
|
||||
return isCoreRunning.value ? Square : Play
|
||||
})
|
||||
|
||||
const serviceButtonText = computed(() => {
|
||||
if (loading.value) return t('dashboard.quickActions.processing')
|
||||
return isCoreRunning.value
|
||||
? t('dashboard.quickActions.stopOpenListCore')
|
||||
: t('dashboard.quickActions.startOpenListCore')
|
||||
|
||||
@@ -1,636 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, onMounted, nextTick, watch } from 'vue'
|
||||
import { useAppStore } from '../../stores/app'
|
||||
import { useTranslation } from '../../composables/useI18n'
|
||||
import { ChevronLeft, ChevronRight, X, Check, Play, FileText, Settings, HardDrive, Home } from 'lucide-vue-next'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const tutorialSteps = computed(() => [
|
||||
{
|
||||
title: t('tutorial.welcome.title'),
|
||||
content: t('tutorial.welcome.content'),
|
||||
target: '.app-title',
|
||||
position: 'center',
|
||||
showNext: true,
|
||||
showSkip: true,
|
||||
icon: Home
|
||||
},
|
||||
{
|
||||
title: t('tutorial.navigation.title'),
|
||||
content: t('tutorial.navigation.content'),
|
||||
target: '.nav-menu',
|
||||
position: 'right',
|
||||
showNext: true,
|
||||
showPrev: true,
|
||||
showSkip: true,
|
||||
icon: HardDrive
|
||||
},
|
||||
{
|
||||
title: t('tutorial.service.title'),
|
||||
content: t('tutorial.service.content'),
|
||||
target: '.service-management-card',
|
||||
position: 'top',
|
||||
showNext: true,
|
||||
showPrev: true,
|
||||
showSkip: true,
|
||||
icon: Play
|
||||
},
|
||||
{
|
||||
title: t('tutorial.openlist.title'),
|
||||
content: t('tutorial.openlist.content'),
|
||||
target: '.quick-actions-card',
|
||||
position: 'top',
|
||||
showNext: true,
|
||||
showPrev: true,
|
||||
showSkip: true,
|
||||
icon: HardDrive
|
||||
},
|
||||
{
|
||||
title: t('tutorial.documentation.title'),
|
||||
content: t('tutorial.documentation.content'),
|
||||
target: '.documentation-card',
|
||||
position: 'bottom',
|
||||
showNext: true,
|
||||
showPrev: true,
|
||||
showSkip: true,
|
||||
icon: FileText
|
||||
},
|
||||
{
|
||||
title: t('tutorial.settings.title'),
|
||||
content: t('tutorial.settings.content'),
|
||||
target: '.nav-item[href="/settings"]',
|
||||
position: 'right',
|
||||
showPrev: true,
|
||||
showComplete: true,
|
||||
icon: Settings
|
||||
}
|
||||
])
|
||||
|
||||
const currentStep = computed(() => tutorialSteps.value[appStore.tutorialStep] || tutorialSteps.value[0])
|
||||
|
||||
const highlightStyle = ref({})
|
||||
|
||||
const updateHighlight = async () => {
|
||||
await nextTick()
|
||||
|
||||
if (!currentStep.value.target) {
|
||||
highlightStyle.value = {}
|
||||
return
|
||||
}
|
||||
|
||||
const targetElement = document.querySelector(currentStep.value.target) as HTMLElement
|
||||
if (!targetElement) {
|
||||
highlightStyle.value = {}
|
||||
return
|
||||
}
|
||||
|
||||
const rect = targetElement.getBoundingClientRect()
|
||||
const padding = 8
|
||||
|
||||
highlightStyle.value = {
|
||||
top: `${rect.top - padding}px`,
|
||||
left: `${rect.left - padding}px`,
|
||||
width: `${rect.width + padding * 2}px`,
|
||||
height: `${rect.height + padding * 2}px`
|
||||
}
|
||||
}
|
||||
|
||||
const getTooltipStyle = () => {
|
||||
if (!currentStep.value.target)
|
||||
return {
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}
|
||||
|
||||
const targetElement = document.querySelector(currentStep.value.target) as HTMLElement
|
||||
if (!targetElement)
|
||||
return {
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}
|
||||
|
||||
const rect = targetElement.getBoundingClientRect()
|
||||
const position = currentStep.value.position || 'bottom'
|
||||
const offset = 16
|
||||
const tooltipWidth = 320
|
||||
const tooltipHeight = 200
|
||||
|
||||
let style: any = {}
|
||||
|
||||
let adjustedPosition = position
|
||||
if (position === 'left' && rect.left < tooltipWidth + offset) {
|
||||
adjustedPosition = 'right'
|
||||
} else if (position === 'right' && rect.right + tooltipWidth + offset > window.innerWidth) {
|
||||
adjustedPosition = 'left'
|
||||
} else if (position === 'top' && rect.top < tooltipHeight + offset) {
|
||||
adjustedPosition = 'bottom'
|
||||
} else if (position === 'bottom' && rect.bottom + tooltipHeight + offset > window.innerHeight) {
|
||||
adjustedPosition = 'top'
|
||||
}
|
||||
|
||||
switch (adjustedPosition) {
|
||||
case 'center':
|
||||
style = {
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)'
|
||||
}
|
||||
break
|
||||
case 'top':
|
||||
style = {
|
||||
bottom: `${window.innerHeight - rect.top + offset}px`,
|
||||
left: `${rect.left + rect.width / 2}px`,
|
||||
transform: 'translateX(-50%)'
|
||||
}
|
||||
break
|
||||
case 'bottom':
|
||||
style = {
|
||||
top: `${rect.bottom + offset}px`,
|
||||
left: `${rect.left + rect.width / 2}px`,
|
||||
transform: 'translateX(-50%)'
|
||||
}
|
||||
break
|
||||
case 'left':
|
||||
style = {
|
||||
top: `${rect.top + rect.height / 2}px`,
|
||||
right: `${window.innerWidth - rect.left + offset}px`,
|
||||
transform: 'translateY(-50%)'
|
||||
}
|
||||
break
|
||||
case 'right':
|
||||
style = {
|
||||
top: `${rect.top + rect.height / 2}px`,
|
||||
left: `${rect.right + offset}px`,
|
||||
transform: 'translateY(-50%)'
|
||||
}
|
||||
break
|
||||
case 'bottom-right':
|
||||
style = {
|
||||
top: `${rect.bottom + offset}px`,
|
||||
left: `${Math.max(16, rect.right - tooltipWidth)}px`
|
||||
}
|
||||
break
|
||||
default:
|
||||
style = {
|
||||
top: `${rect.bottom + offset}px`,
|
||||
left: `${rect.left + rect.width / 2}px`,
|
||||
transform: 'translateX(-50%)'
|
||||
}
|
||||
}
|
||||
if (style.left && !style.transform?.includes('translateX')) {
|
||||
const leftPos = parseInt(style.left)
|
||||
if (leftPos + tooltipWidth > window.innerWidth) {
|
||||
style.left = `${window.innerWidth - tooltipWidth - 16}px`
|
||||
}
|
||||
if (leftPos < 16) {
|
||||
style.left = '16px'
|
||||
}
|
||||
}
|
||||
|
||||
if (style.top && !style.transform?.includes('translateY')) {
|
||||
const topPos = parseInt(style.top)
|
||||
if (topPos + tooltipHeight > window.innerHeight) {
|
||||
style.top = `${window.innerHeight - tooltipHeight - 16}px`
|
||||
}
|
||||
if (topPos < 16) {
|
||||
style.top = '16px'
|
||||
}
|
||||
}
|
||||
|
||||
if (style.bottom) {
|
||||
const bottomPos = parseInt(style.bottom)
|
||||
if (window.innerHeight - bottomPos - tooltipHeight < 16) {
|
||||
delete style.bottom
|
||||
style.top = '16px'
|
||||
}
|
||||
}
|
||||
|
||||
if (style.right) {
|
||||
const rightPos = parseInt(style.right)
|
||||
if (window.innerWidth - rightPos - tooltipWidth < 16) {
|
||||
delete style.right
|
||||
style.left = '16px'
|
||||
delete style.transform
|
||||
}
|
||||
}
|
||||
|
||||
if (style.transform?.includes('translate(-50%, -50%)')) {
|
||||
return style
|
||||
}
|
||||
|
||||
if (style.transform?.includes('translateX(-50%)') && style.left) {
|
||||
const leftPos = parseInt(style.left)
|
||||
const halfWidth = tooltipWidth / 2
|
||||
if (leftPos - halfWidth < 16) {
|
||||
style.left = `${halfWidth + 16}px`
|
||||
}
|
||||
if (leftPos + halfWidth > window.innerWidth - 16) {
|
||||
style.left = `${window.innerWidth - halfWidth - 16}px`
|
||||
}
|
||||
}
|
||||
|
||||
if (style.transform?.includes('translateY(-50%)') && style.top) {
|
||||
const topPos = parseInt(style.top)
|
||||
const halfHeight = tooltipHeight / 2
|
||||
if (topPos - halfHeight < 16) {
|
||||
style.top = `${halfHeight + 16}px`
|
||||
}
|
||||
if (topPos + halfHeight > window.innerHeight - 16) {
|
||||
style.top = `${window.innerHeight - halfHeight - 16}px`
|
||||
}
|
||||
}
|
||||
|
||||
return style
|
||||
}
|
||||
|
||||
const handleNext = () => {
|
||||
if (appStore.tutorialStep < tutorialSteps.value.length - 1) {
|
||||
appStore.nextTutorialStep()
|
||||
updateHighlight()
|
||||
}
|
||||
}
|
||||
|
||||
const handlePrev = () => {
|
||||
if (appStore.tutorialStep > 0) {
|
||||
appStore.prevTutorialStep()
|
||||
updateHighlight()
|
||||
}
|
||||
}
|
||||
|
||||
const handleSkip = () => {
|
||||
appStore.skipTutorial()
|
||||
}
|
||||
|
||||
const handleComplete = () => {
|
||||
appStore.completeTutorial()
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
appStore.closeTutorial()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateHighlight()
|
||||
|
||||
watch(
|
||||
() => appStore.tutorialStep,
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
updateHighlight()
|
||||
}, 100)
|
||||
}
|
||||
)
|
||||
|
||||
const handleResize = () => {
|
||||
updateHighlight()
|
||||
}
|
||||
|
||||
window.addEventListener('resize', handleResize)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Teleport to="body">
|
||||
<div v-if="appStore.showTutorial" class="tutorial-overlay">
|
||||
<div class="tutorial-backdrop" @click="handleClose" />
|
||||
<div
|
||||
v-if="currentStep.target && currentStep.position !== 'center'"
|
||||
class="tutorial-highlight"
|
||||
:style="highlightStyle"
|
||||
/>
|
||||
|
||||
<div class="tutorial-tooltip" :style="getTooltipStyle()">
|
||||
<div class="tooltip-header">
|
||||
<div class="tooltip-icon">
|
||||
<component :is="currentStep.icon" :size="20" />
|
||||
</div>
|
||||
<h3 class="tooltip-title">{{ currentStep.title }}</h3>
|
||||
<button class="tooltip-close" @click="handleClose" :title="t('common.close')">
|
||||
<X :size="18" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="tooltip-content">
|
||||
<p>{{ currentStep.content }}</p>
|
||||
</div>
|
||||
|
||||
<div class="tooltip-footer">
|
||||
<div class="step-indicator">
|
||||
<span class="step-current">{{ appStore.tutorialStep + 1 }}</span>
|
||||
<span class="step-divider">/</span>
|
||||
<span class="step-total">{{ tutorialSteps.length }}</span>
|
||||
</div>
|
||||
|
||||
<div class="tutorial-actions">
|
||||
<button v-if="currentStep.showSkip" class="btn-skip" @click="handleSkip">
|
||||
{{ t('tutorial.skip') }}
|
||||
</button>
|
||||
|
||||
<button v-if="currentStep.showPrev" class="btn-prev" @click="handlePrev">
|
||||
<ChevronLeft :size="16" />
|
||||
{{ t('tutorial.previous') }}
|
||||
</button>
|
||||
|
||||
<button v-if="currentStep.showNext" class="btn-next" @click="handleNext">
|
||||
{{ t('tutorial.next') }}
|
||||
<ChevronRight :size="16" />
|
||||
</button>
|
||||
|
||||
<button v-if="currentStep.showComplete" class="btn-complete" @click="handleComplete">
|
||||
<Check :size="16" />
|
||||
{{ t('tutorial.complete') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.tutorial-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 10000;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tutorial-backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: blur(2px);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.tutorial-highlight {
|
||||
position: absolute;
|
||||
border: 2px solid var(--color-accent);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: 0 0 0 4px rgba(0, 122, 255, 0.2), var(--shadow-lg);
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
pointer-events: none;
|
||||
transition: all var(--transition-medium);
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 0 0 4px rgba(0, 122, 255, 0.2), var(--shadow-lg);
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 8px rgba(0, 122, 255, 0.1), var(--shadow-xl);
|
||||
}
|
||||
}
|
||||
|
||||
.tutorial-tooltip {
|
||||
position: absolute;
|
||||
width: 320px;
|
||||
max-width: calc(100vw - 32px);
|
||||
background: var(--color-surface-elevated);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-xl);
|
||||
backdrop-filter: blur(20px);
|
||||
pointer-events: all;
|
||||
animation: tooltipEnter 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
z-index: 10001;
|
||||
}
|
||||
|
||||
@keyframes tooltipEnter {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(16px) scale(0.9);
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.tooltip-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 20px 12px 20px;
|
||||
border-bottom: 1px solid var(--color-border-secondary);
|
||||
}
|
||||
|
||||
.tooltip-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: var(--color-accent);
|
||||
color: white;
|
||||
border-radius: var(--radius-sm);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tooltip-title {
|
||||
flex: 1;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: var(--color-text-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tooltip-close {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--color-text-tertiary);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.tooltip-close:hover {
|
||||
background: var(--color-background-secondary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.tooltip-content {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
.tooltip-content p {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.tooltip-footer {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 20px 16px 20px;
|
||||
border-top: 1px solid var(--color-border-secondary);
|
||||
}
|
||||
|
||||
.step-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.75rem;
|
||||
color: var(--color-text-tertiary);
|
||||
}
|
||||
|
||||
.step-current {
|
||||
font-weight: 600;
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
.step-divider {
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.tutorial-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-skip {
|
||||
padding: 6px 12px;
|
||||
font-size: 0.75rem;
|
||||
background: transparent;
|
||||
border: 1px solid var(--color-border);
|
||||
color: var(--color-text-tertiary);
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.btn-skip:hover {
|
||||
background: var(--color-background-secondary);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.btn-prev,
|
||||
.btn-next,
|
||||
.btn-complete {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 8px 12px;
|
||||
font-size: 0.75rem;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.btn-prev {
|
||||
background: var(--color-background-secondary);
|
||||
color: var(--color-text-primary);
|
||||
}
|
||||
|
||||
.btn-prev:hover {
|
||||
background: var(--color-background-tertiary);
|
||||
}
|
||||
|
||||
.btn-next,
|
||||
.btn-complete {
|
||||
background: var(--color-accent);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-next:hover,
|
||||
.btn-complete:hover {
|
||||
background: var(--color-accent-hover);
|
||||
}
|
||||
|
||||
.btn-complete {
|
||||
background: var(--color-success);
|
||||
}
|
||||
|
||||
.btn-complete:hover {
|
||||
background: #2fb344;
|
||||
}
|
||||
|
||||
:root.dark .tutorial-backdrop {
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
:root.dark .tutorial-highlight {
|
||||
border-color: var(--color-accent);
|
||||
box-shadow: 0 0 0 4px rgba(10, 132, 255, 0.3), var(--shadow-lg);
|
||||
}
|
||||
|
||||
:root.dark .tutorial-tooltip {
|
||||
background: var(--color-surface-elevated);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tutorial-tooltip {
|
||||
width: 280px;
|
||||
max-width: calc(100vw - 24px);
|
||||
position: fixed !important;
|
||||
top: auto !important;
|
||||
bottom: 20px !important;
|
||||
left: 50% !important;
|
||||
right: auto !important;
|
||||
transform: translateX(-50%) !important;
|
||||
}
|
||||
|
||||
.tooltip-header {
|
||||
padding: 12px 16px 8px 16px;
|
||||
}
|
||||
|
||||
.tooltip-content {
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.tooltip-footer {
|
||||
padding: 8px 16px 12px 16px;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.tutorial-actions {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.step-indicator {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.tutorial-tooltip {
|
||||
width: calc(100vw - 32px);
|
||||
}
|
||||
|
||||
.tutorial-actions {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-prev,
|
||||
.btn-next,
|
||||
.btn-complete,
|
||||
.btn-skip {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -163,15 +163,6 @@
|
||||
"auto": "Auto",
|
||||
"autoDesc": "Follow system"
|
||||
},
|
||||
"monitor": {
|
||||
"title": "Monitoring",
|
||||
"subtitle": "System monitoring and refresh settings",
|
||||
"interval": {
|
||||
"label": "Monitor Interval (seconds)",
|
||||
"placeholder": "5",
|
||||
"help": "How often to refresh system metrics and status"
|
||||
}
|
||||
},
|
||||
"ghProxy": {
|
||||
"title": "GitHub Proxy",
|
||||
"subtitle": "Accelerate GitHub with proxy service",
|
||||
@@ -191,12 +182,6 @@
|
||||
"description": "Use system default browser instead of built-in window"
|
||||
}
|
||||
},
|
||||
"tutorial": {
|
||||
"title": "Tutorial",
|
||||
"subtitle": "Learn how to use OpenList Desktop",
|
||||
"restart": "Start Tutorial",
|
||||
"help": "Restart the tutorial to learn about app features and navigation"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Updates",
|
||||
"subtitle": "Manage automatic updates and notifications",
|
||||
@@ -429,36 +414,6 @@
|
||||
"title": "OpenList",
|
||||
"loading": "Initializing OpenList Desktop..."
|
||||
},
|
||||
"tutorial": {
|
||||
"welcome": {
|
||||
"title": "Welcome to OpenList Desktop",
|
||||
"content": "Welcome to OpenList Desktop! This tutorial will guide you through the key features and help you get started quickly."
|
||||
},
|
||||
"navigation": {
|
||||
"title": "Navigation Panel",
|
||||
"content": "Use the navigation panel to access different sections: Dashboard for monitoring, Mount for storage management, Logs for troubleshooting, and Settings for configuration."
|
||||
},
|
||||
"service": {
|
||||
"title": "Install & Start Service",
|
||||
"content": "First, you need to install and start the OpenList service. This is the core component that manages your cloud storage connections."
|
||||
},
|
||||
"openlist": {
|
||||
"title": "OpenList Core Access",
|
||||
"content": "Once the service is running, you can access the OpenList web interface to manage your files and configurations."
|
||||
},
|
||||
"documentation": {
|
||||
"title": "Read Documentation",
|
||||
"content": "For detailed information and advanced configurations, check out the documentation section. You'll find guides, API docs, and troubleshooting tips."
|
||||
},
|
||||
"settings": {
|
||||
"title": "Settings & Configuration",
|
||||
"content": "Customize your OpenList experience in the Settings section. Configure themes, service options, and storage preferences."
|
||||
},
|
||||
"skip": "Skip Tutorial",
|
||||
"next": "Next",
|
||||
"previous": "Previous",
|
||||
"complete": "Complete Tutorial"
|
||||
},
|
||||
"update": {
|
||||
"title": "App Updates",
|
||||
"subtitle": "Keep your application up to date with the latest features and security improvements",
|
||||
|
||||
@@ -163,15 +163,6 @@
|
||||
"auto": "自动",
|
||||
"autoDesc": "跟随系统"
|
||||
},
|
||||
"monitor": {
|
||||
"title": "监控",
|
||||
"subtitle": "系统监控和刷新设置",
|
||||
"interval": {
|
||||
"label": "监控间隔(秒)",
|
||||
"placeholder": "5",
|
||||
"help": "刷新系统指标和状态的频率"
|
||||
}
|
||||
},
|
||||
"ghProxy": {
|
||||
"title": "GitHub 代理",
|
||||
"subtitle": "使用代理服务加速 GitHub",
|
||||
@@ -191,12 +182,6 @@
|
||||
"description": "使用系统默认浏览器而不是内置窗口"
|
||||
}
|
||||
},
|
||||
"tutorial": {
|
||||
"title": "教程",
|
||||
"subtitle": "学习如何使用 OpenList 桌面版",
|
||||
"restart": "开始教程",
|
||||
"help": "重新启动教程以了解应用功能和导航"
|
||||
},
|
||||
"updates": {
|
||||
"title": "更新",
|
||||
"subtitle": "管理自动更新和通知",
|
||||
@@ -429,36 +414,6 @@
|
||||
"title": "OpenList",
|
||||
"loading": "正在初始化"
|
||||
},
|
||||
"tutorial": {
|
||||
"welcome": {
|
||||
"title": "欢迎使用 OpenList 桌面版",
|
||||
"content": "欢迎使用 OpenList 桌面版!本教程将引导您了解主要功能,帮助您快速上手。"
|
||||
},
|
||||
"navigation": {
|
||||
"title": "导航面板",
|
||||
"content": "使用导航面板访问不同部分:仪表板用于监控,挂载用于存储管理,日志用于故障排除,设置用于配置。"
|
||||
},
|
||||
"service": {
|
||||
"title": "安装并启动服务",
|
||||
"content": "首先,您需要安装并启动 OpenList 服务。这是管理云存储连接的核心组件。"
|
||||
},
|
||||
"openlist": {
|
||||
"title": "OpenList 核心访问",
|
||||
"content": "服务运行后,您可以访问 OpenList 网页界面来管理文件和配置。"
|
||||
},
|
||||
"documentation": {
|
||||
"title": "阅读文档",
|
||||
"content": "有关详细信息和高级配置,请查看文档部分。您会找到指南、API 文档和故障排除提示。"
|
||||
},
|
||||
"settings": {
|
||||
"title": "设置和配置",
|
||||
"content": "在设置部分自定义您的 OpenList 体验。配置主题、服务选项和存储首选项。"
|
||||
},
|
||||
"skip": "跳过教程",
|
||||
"next": "下一步",
|
||||
"previous": "上一步",
|
||||
"complete": "完成教程"
|
||||
},
|
||||
"update": {
|
||||
"title": "应用更新",
|
||||
"subtitle": "保持应用程序最新,获取最新功能和安全改进",
|
||||
|
||||
@@ -375,10 +375,6 @@ export const useAppStore = defineStore('app', () => {
|
||||
|
||||
const openlistProcessId = ref<string | undefined>(undefined)
|
||||
|
||||
const showTutorial = ref(false)
|
||||
const tutorialStep = ref(0)
|
||||
const tutorialSkipped = ref(false)
|
||||
|
||||
async function getRcloneMountProcessId(name: string): Promise<string | undefined> {
|
||||
try {
|
||||
const processList = await TauriAPI.process.list()
|
||||
@@ -683,7 +679,6 @@ export const useAppStore = defineStore('app', () => {
|
||||
|
||||
async function init() {
|
||||
try {
|
||||
initTutorial()
|
||||
await loadSettings()
|
||||
await refreshOpenListCoreStatus()
|
||||
await TauriAPI.tray.updateDelayed(openlistCoreStatus.value.running)
|
||||
@@ -697,43 +692,6 @@ export const useAppStore = defineStore('app', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function initTutorial() {
|
||||
const hasSeenTutorial = localStorage.getItem('openlist-tutorial-completed')
|
||||
const tutorialDisabled = localStorage.getItem('openlist-tutorial-disabled')
|
||||
|
||||
if (!hasSeenTutorial && tutorialDisabled !== 'true') {
|
||||
showTutorial.value = true
|
||||
tutorialStep.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
function startTutorial() {
|
||||
showTutorial.value = true
|
||||
tutorialStep.value = 0
|
||||
localStorage.removeItem('openlist-tutorial-disabled')
|
||||
}
|
||||
|
||||
function nextTutorialStep() {
|
||||
tutorialStep.value++
|
||||
}
|
||||
|
||||
function prevTutorialStep() {
|
||||
if (tutorialStep.value > 0) {
|
||||
tutorialStep.value--
|
||||
}
|
||||
}
|
||||
|
||||
function skipTutorial() {
|
||||
showTutorial.value = false
|
||||
tutorialSkipped.value = true
|
||||
localStorage.setItem('openlist-tutorial-disabled', 'true')
|
||||
}
|
||||
|
||||
function completeTutorial() {
|
||||
showTutorial.value = false
|
||||
localStorage.setItem('openlist-tutorial-completed', 'true')
|
||||
}
|
||||
|
||||
async function getAdminPassword(): Promise<string | null> {
|
||||
try {
|
||||
return await TauriAPI.logs.adminPassword()
|
||||
@@ -743,10 +701,6 @@ export const useAppStore = defineStore('app', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function closeTutorial() {
|
||||
showTutorial.value = false
|
||||
}
|
||||
|
||||
// Update management functions
|
||||
function setUpdateAvailable(available: boolean, updateInfo?: UpdateCheck) {
|
||||
updateAvailable.value = available
|
||||
@@ -783,10 +737,6 @@ export const useAppStore = defineStore('app', () => {
|
||||
updateAvailable,
|
||||
updateCheck,
|
||||
|
||||
showTutorial,
|
||||
tutorialStep,
|
||||
tutorialSkipped,
|
||||
|
||||
isCoreRunning,
|
||||
openListCoreUrl,
|
||||
|
||||
@@ -814,13 +764,6 @@ export const useAppStore = defineStore('app', () => {
|
||||
toggleTheme,
|
||||
applyTheme,
|
||||
|
||||
initTutorial,
|
||||
startTutorial,
|
||||
nextTutorialStep,
|
||||
prevTutorialStep,
|
||||
skipTutorial,
|
||||
completeTutorial,
|
||||
closeTutorial,
|
||||
setUpdateAvailable,
|
||||
clearUpdateStatus
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useAppStore } from '../stores/app'
|
||||
import { useTranslation } from '../composables/useI18n'
|
||||
import { Settings, Server, HardDrive, Save, RotateCcw, AlertCircle, CheckCircle, Play } from 'lucide-vue-next'
|
||||
import { Settings, Server, HardDrive, Save, RotateCcw, AlertCircle, CheckCircle } from 'lucide-vue-next'
|
||||
import { enable, isEnabled, disable } from '@tauri-apps/plugin-autostart'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const { t } = useTranslation()
|
||||
const isSaving = ref(false)
|
||||
const message = ref('')
|
||||
@@ -129,11 +128,6 @@ const handleSave = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
async function startTutorial() {
|
||||
router.push({ name: 'Dashboard' })
|
||||
appStore.startTutorial()
|
||||
}
|
||||
|
||||
const handleReset = async () => {
|
||||
if (!confirm(t('settings.confirmReset'))) {
|
||||
return
|
||||
@@ -373,21 +367,6 @@ const handleReset = async () => {
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-section">
|
||||
<h2>{{ t('settings.app.tutorial.title') }}</h2>
|
||||
<p>{{ t('settings.app.tutorial.subtitle') }}</p>
|
||||
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<button @click="startTutorial" class="tutorial-btn" type="button">
|
||||
<Play :size="16" />
|
||||
{{ t('settings.app.tutorial.restart') }}
|
||||
</button>
|
||||
<small>{{ t('settings.app.tutorial.help') }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -604,29 +604,3 @@
|
||||
padding: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.tutorial-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 20px;
|
||||
background: var(--color-accent);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.tutorial-btn:hover {
|
||||
background: var(--color-accent-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 122, 255, 0.3);
|
||||
}
|
||||
|
||||
.tutorial-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 6px rgba(0, 122, 255, 0.3);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user