- Implemented a new searchVocabs method in VocabService to allow users to search for vocabulary based on learning and mother tongue terms. - Updated VocabController to include the searchVocabs method wrapped with user authentication. - Added a new route in vocabRouter for searching vocabulary by language ID. - Enhanced VocabChapterView and VocabLanguageView components to include a button for opening the search dialog. - Added translations for search-related terms in both German and English locales, improving user accessibility.
177 lines
4.8 KiB
Vue
177 lines
4.8 KiB
Vue
<template>
|
|
<h2>{{ $t('socialnetwork.vocab.chapterTitle', { title: chapter?.title || '' }) }}</h2>
|
|
|
|
<div class="box">
|
|
<div v-if="loading">{{ $t('general.loading') }}</div>
|
|
<div v-else-if="!chapter">{{ $t('socialnetwork.vocab.notFound') }}</div>
|
|
<div v-else>
|
|
<div v-show="!practiceOpen">
|
|
<div class="row">
|
|
<button @click="back">{{ $t('general.back') }}</button>
|
|
<button v-if="vocabs.length" @click="openPractice">{{ $t('socialnetwork.vocab.practice.open') }}</button>
|
|
<button @click="openSearch">{{ $t('socialnetwork.vocab.search.open') }}</button>
|
|
</div>
|
|
|
|
<div class="row" v-if="chapter.isOwner">
|
|
<h3>{{ $t('socialnetwork.vocab.addVocab') }}</h3>
|
|
<div class="grid">
|
|
<label>
|
|
{{ $t('socialnetwork.vocab.learningWord') }}
|
|
<input v-model="learning" type="text" />
|
|
</label>
|
|
<label>
|
|
{{ $t('socialnetwork.vocab.referenceWord') }}
|
|
<input v-model="reference" type="text" />
|
|
</label>
|
|
</div>
|
|
<button :disabled="saving || !canSave" @click="add">
|
|
{{ saving ? $t('socialnetwork.vocab.saving') : $t('socialnetwork.vocab.add') }}
|
|
</button>
|
|
</div>
|
|
|
|
<hr />
|
|
|
|
<div v-if="vocabs.length === 0">{{ $t('socialnetwork.vocab.noVocabs') }}</div>
|
|
<table v-else class="tbl">
|
|
<thead>
|
|
<tr>
|
|
<th>{{ $t('socialnetwork.vocab.learningWord') }}</th>
|
|
<th>{{ $t('socialnetwork.vocab.referenceWord') }}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr v-for="v in vocabs" :key="v.id">
|
|
<td>{{ v.learning }}</td>
|
|
<td>{{ v.reference }}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<VocabPracticeDialog ref="practiceDialog" />
|
|
<VocabSearchDialog ref="searchDialog" />
|
|
</template>
|
|
|
|
<script>
|
|
import apiClient from '@/utils/axios.js';
|
|
import VocabPracticeDialog from '@/dialogues/socialnetwork/VocabPracticeDialog.vue';
|
|
import VocabSearchDialog from '@/dialogues/socialnetwork/VocabSearchDialog.vue';
|
|
|
|
export default {
|
|
name: 'VocabChapterView',
|
|
components: { VocabPracticeDialog, VocabSearchDialog },
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
saving: false,
|
|
practiceOpen: false,
|
|
chapter: null,
|
|
languageName: '',
|
|
vocabs: [],
|
|
learning: '',
|
|
reference: '',
|
|
};
|
|
},
|
|
computed: {
|
|
canSave() {
|
|
return this.learning.trim().length > 0 && this.reference.trim().length > 0;
|
|
},
|
|
},
|
|
methods: {
|
|
back() {
|
|
this.$router.push(`/socialnetwork/vocab/${this.$route.params.languageId}`);
|
|
},
|
|
openPractice() {
|
|
this.practiceOpen = true;
|
|
this.$refs.practiceDialog?.open?.({
|
|
languageId: this.$route.params.languageId,
|
|
chapterId: this.$route.params.chapterId,
|
|
onClose: () => {
|
|
this.practiceOpen = false;
|
|
},
|
|
});
|
|
},
|
|
openSearch() {
|
|
this.$refs.searchDialog?.open?.({
|
|
languageId: this.$route.params.languageId,
|
|
languageName: this.languageName || '',
|
|
});
|
|
},
|
|
async load() {
|
|
this.loading = true;
|
|
try {
|
|
const res = await apiClient.get(`/api/vocab/chapters/${this.$route.params.chapterId}/vocabs`);
|
|
this.chapter = res.data?.chapter || null;
|
|
this.vocabs = res.data?.vocabs || [];
|
|
try {
|
|
const langRes = await apiClient.get(`/api/vocab/languages/${this.$route.params.languageId}`);
|
|
this.languageName = langRes.data?.name || '';
|
|
} catch (_) {
|
|
this.languageName = '';
|
|
}
|
|
} catch (e) {
|
|
console.error('Load chapter vocabs failed:', e);
|
|
this.chapter = null;
|
|
this.vocabs = [];
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
async add() {
|
|
this.saving = true;
|
|
try {
|
|
await apiClient.post(`/api/vocab/chapters/${this.$route.params.chapterId}/vocabs`, {
|
|
learning: this.learning,
|
|
reference: this.reference,
|
|
});
|
|
this.learning = '';
|
|
this.reference = '';
|
|
await this.load();
|
|
} catch (e) {
|
|
console.error('Add vocab failed:', e);
|
|
this.$root.$refs.messageDialog?.open(
|
|
this.$t('socialnetwork.vocab.addVocabError'),
|
|
this.$t('error.title')
|
|
);
|
|
} finally {
|
|
this.saving = false;
|
|
}
|
|
},
|
|
},
|
|
mounted() {
|
|
this.load();
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.box {
|
|
background: #f6f6f6;
|
|
padding: 12px;
|
|
border: 1px solid #ccc;
|
|
display: inline-block;
|
|
}
|
|
.row {
|
|
margin-bottom: 10px;
|
|
}
|
|
.grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 10px;
|
|
margin-bottom: 8px;
|
|
}
|
|
.tbl {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
.tbl th,
|
|
.tbl td {
|
|
border: 1px solid #ccc;
|
|
padding: 6px;
|
|
}
|
|
</style>
|
|
|
|
|