225 lines
5.9 KiB
Vue
225 lines
5.9 KiB
Vue
<template>
|
|
<div class="vocab-language-view">
|
|
<section class="vocab-language-hero surface-card">
|
|
<div v-if="loading">{{ $t('general.loading') }}</div>
|
|
<div v-else-if="!language">{{ $t('socialnetwork.vocab.notFound') }}</div>
|
|
<div v-else>
|
|
<span class="vocab-language-kicker">Sprache</span>
|
|
<h2>{{ $t('socialnetwork.vocab.languageTitle', { name: language?.name || '' }) }}</h2>
|
|
<p>Kapitel, Suchfunktionen und Freigaben für diese Sprache an einem Ort.</p>
|
|
</div>
|
|
</section>
|
|
|
|
<div class="box surface-card" v-if="language">
|
|
<div class="row row--meta">
|
|
<strong>{{ $t('socialnetwork.vocab.languageName') }}</strong>
|
|
<span>{{ language.name }}</span>
|
|
</div>
|
|
|
|
<div class="row row--meta" v-if="language.isOwner && language.shareCode">
|
|
<strong>{{ $t('socialnetwork.vocab.shareCode') }}</strong>
|
|
<code>{{ language.shareCode }}</code>
|
|
</div>
|
|
|
|
<div class="row row--actions">
|
|
<button @click="goSubscribe">{{ $t('socialnetwork.vocab.subscribeByCode') }}</button>
|
|
<button @click="openSearch">{{ $t('socialnetwork.vocab.search.open') }}</button>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<h3>{{ $t('socialnetwork.vocab.chapters') }}</h3>
|
|
</div>
|
|
|
|
<div class="row row--create" v-if="language.isOwner">
|
|
<label>
|
|
{{ $t('socialnetwork.vocab.newChapter') }}
|
|
<input v-model="newChapterTitle" type="text" />
|
|
</label>
|
|
<button :disabled="creatingChapter || newChapterTitle.trim().length < 2" @click="createChapter">
|
|
{{ creatingChapter ? $t('socialnetwork.vocab.saving') : $t('socialnetwork.vocab.createChapter') }}
|
|
</button>
|
|
</div>
|
|
|
|
<div v-if="chaptersLoading">{{ $t('general.loading') }}</div>
|
|
<div v-else>
|
|
<div v-if="chapters.length === 0">{{ $t('socialnetwork.vocab.noChapters') }}</div>
|
|
<ul v-else class="chapter-list">
|
|
<li v-for="c in chapters" :key="c.id">
|
|
<button type="button" class="chapter-card" @click="openChapter(c.id)">
|
|
<span>{{ c.title }}</span>
|
|
<span class="count">{{ c.vocabCount }}</span>
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<VocabSearchDialog ref="searchDialog" />
|
|
</template>
|
|
|
|
<script>
|
|
import apiClient from '@/utils/axios.js';
|
|
import VocabSearchDialog from '@/dialogues/socialnetwork/VocabSearchDialog.vue';
|
|
|
|
export default {
|
|
name: 'VocabLanguageView',
|
|
components: { VocabSearchDialog },
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
language: null,
|
|
chaptersLoading: false,
|
|
chapters: [],
|
|
newChapterTitle: '',
|
|
creatingChapter: false,
|
|
};
|
|
},
|
|
methods: {
|
|
goSubscribe() {
|
|
this.$router.push('/socialnetwork/vocab/subscribe');
|
|
},
|
|
openSearch() {
|
|
this.$refs.searchDialog?.open?.({
|
|
languageId: this.$route.params.languageId,
|
|
languageName: this.language?.name || '',
|
|
});
|
|
},
|
|
openChapter(chapterId) {
|
|
this.$router.push(`/socialnetwork/vocab/${this.$route.params.languageId}/chapters/${chapterId}`);
|
|
},
|
|
async load() {
|
|
this.loading = true;
|
|
try {
|
|
const res = await apiClient.get(`/api/vocab/languages/${this.$route.params.languageId}`);
|
|
this.language = res.data;
|
|
await this.loadChapters();
|
|
} catch (e) {
|
|
console.error('Load vocab language failed:', e);
|
|
this.language = null;
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
async loadChapters() {
|
|
this.chaptersLoading = true;
|
|
try {
|
|
const res = await apiClient.get(`/api/vocab/languages/${this.$route.params.languageId}/chapters`);
|
|
this.chapters = res.data?.chapters || [];
|
|
} catch (e) {
|
|
console.error('Load chapters failed:', e);
|
|
this.chapters = [];
|
|
} finally {
|
|
this.chaptersLoading = false;
|
|
}
|
|
},
|
|
async createChapter() {
|
|
this.creatingChapter = true;
|
|
try {
|
|
await apiClient.post(`/api/vocab/languages/${this.$route.params.languageId}/chapters`, {
|
|
title: this.newChapterTitle,
|
|
});
|
|
this.newChapterTitle = '';
|
|
await this.loadChapters();
|
|
} catch (e) {
|
|
console.error('Create chapter failed:', e);
|
|
this.$root.$refs.messageDialog?.open(
|
|
this.$t('socialnetwork.vocab.createChapterError'),
|
|
this.$t('error.title')
|
|
);
|
|
} finally {
|
|
this.creatingChapter = false;
|
|
}
|
|
},
|
|
},
|
|
watch: {
|
|
'$route.params.languageId'() {
|
|
this.load();
|
|
},
|
|
},
|
|
mounted() {
|
|
this.load();
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.vocab-language-view {
|
|
max-width: var(--content-max-width);
|
|
margin: 0 auto;
|
|
padding-bottom: 24px;
|
|
}
|
|
|
|
.vocab-language-hero {
|
|
padding: 24px 26px;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.vocab-language-kicker {
|
|
display: inline-block;
|
|
margin-bottom: 10px;
|
|
padding: 4px 10px;
|
|
border-radius: 999px;
|
|
background: rgba(248, 162, 43, 0.14);
|
|
color: #8a5411;
|
|
font-size: 0.75rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
|
|
.vocab-language-hero p {
|
|
margin: 0;
|
|
color: var(--color-text-secondary);
|
|
}
|
|
|
|
.box {
|
|
padding: 20px;
|
|
}
|
|
.row {
|
|
margin-bottom: 12px;
|
|
}
|
|
.row--meta {
|
|
display: flex;
|
|
gap: 10px;
|
|
align-items: center;
|
|
}
|
|
.row--actions {
|
|
display: flex;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.row--create {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: end;
|
|
flex-wrap: wrap;
|
|
}
|
|
.click {
|
|
cursor: pointer;
|
|
text-decoration: underline;
|
|
}
|
|
.chapter-list {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
|
gap: 12px;
|
|
}
|
|
.chapter-card {
|
|
width: 100%;
|
|
justify-content: space-between;
|
|
background: rgba(255, 255, 255, 0.72);
|
|
border: 1px solid var(--color-border);
|
|
box-shadow: none;
|
|
padding: 14px 16px;
|
|
border-radius: var(--radius-lg);
|
|
color: var(--color-text-primary);
|
|
}
|
|
.count {
|
|
color: var(--color-text-muted);
|
|
font-size: 0.9em;
|
|
}
|
|
</style>
|