|
|
@@ -124,40 +124,66 @@
|
|
|
:model="model"
|
|
|
:on-submit="submit"
|
|
|
@success="table.refresh()"
|
|
|
- style="width: 80%"
|
|
|
+ style="width: 90%"
|
|
|
>
|
|
|
<ElForm label-width="100px" label-position="right">
|
|
|
- <div style="display: flex; align-items: stretch; gap: 20px; height: 500px">
|
|
|
- <div style="flex: 1; display: flex; flex-direction: column; justify-content: center">
|
|
|
- <ElFormItem prop="content" label="助记词内容">
|
|
|
- <ElInput v-model="model.content" placeholder="请输入内容" type="textarea" :rows="15" />
|
|
|
+ <div class="dialog-content">
|
|
|
+ <!-- 左侧输入和展示区域 -->
|
|
|
+ <div class="left-section">
|
|
|
+ <ElFormItem prop="content" label="助记词内容: " class="input-section">
|
|
|
+ <div class="input-wrapper">
|
|
|
+ <ElInput
|
|
|
+ v-model="inputWord"
|
|
|
+ placeholder="输入助记词"
|
|
|
+ @input="handleWordInput"
|
|
|
+ @keydown="handleKeyDown"
|
|
|
+ clearable
|
|
|
+ class="word-input"
|
|
|
+ />
|
|
|
+ <div class="suggestions" v-if="suggestions.length > 0">
|
|
|
+ <div
|
|
|
+ v-for="(word, index) in suggestions"
|
|
|
+ :key="word"
|
|
|
+ class="suggestion-item"
|
|
|
+ :class="{ 'suggestion-item-active': index === selectedIndex }"
|
|
|
+ @click="selectWord(word)"
|
|
|
+ >
|
|
|
+ {{ word }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</ElFormItem>
|
|
|
+ <div class="selected-words-container">
|
|
|
+ <div v-for="(word, index) in selectedWords" :key="index" class="selected-word-item">
|
|
|
+ <span>{{ word }}</span>
|
|
|
+ <ElButton type="danger" :icon="X" circle size="small" @click="removeWord(index)" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
- <ElDivider direction="vertical" style="height: 100%" />
|
|
|
+ <ElDivider direction="vertical" class="divider" />
|
|
|
|
|
|
- <div style="flex: 1; display: flex; justify-content: center; align-items: center">
|
|
|
- <RImage
|
|
|
- style="max-width: 100%; height: 100%; object-fit: cover"
|
|
|
- :src="model.img"
|
|
|
- :thumbnail="model.thumbnail"
|
|
|
- fit="cover"
|
|
|
- />
|
|
|
+ <!-- 右侧图片区域 -->
|
|
|
+ <div class="right-section">
|
|
|
+ <div class="image-container">
|
|
|
+ <RImage :src="model.img" :thumbnail="model.thumbnail" fit="contain" class="preview-image" />
|
|
|
+ </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</ElForm>
|
|
|
</EditDialog>
|
|
|
</template>
|
|
|
<script setup>
|
|
|
-import { inject, ref, onMounted } from 'vue'
|
|
|
+import { inject, ref, onMounted, watch } from 'vue'
|
|
|
import PagingTable from '@/components/PagingTable.vue'
|
|
|
import { useTimeFormatter } from '@/utils/formatter'
|
|
|
-import { Check, Edit, Plus, Search, Refresh, Star, StarOff, Heart, HeartBroken, DropletFilled } from '@vicons/tabler'
|
|
|
+import { Check, Edit, Plus, Search, Refresh, Star, StarOff, Heart, HeartBroken, DropletFilled, X } from '@vicons/tabler'
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
import EditDialog from '@/components/EditDialog.vue'
|
|
|
import { setupEditDialog } from '@/utils/editDialog'
|
|
|
import { http } from '@/plugins/http'
|
|
|
import { useClipboard } from '@vueuse/core'
|
|
|
+import { wordlists } from 'bip39'
|
|
|
|
|
|
const query = ref({})
|
|
|
const timeFormatter = useTimeFormatter()
|
|
|
@@ -168,6 +194,110 @@ const { copy } = useClipboard({ legacy: true })
|
|
|
const isAdmin = inject('isAdminAndOperator')
|
|
|
const channelOptions = ref([])
|
|
|
|
|
|
+// 添加新的响应式变量
|
|
|
+const inputWord = ref('')
|
|
|
+const suggestions = ref([])
|
|
|
+const selectedWords = ref([])
|
|
|
+const selectedIndex = ref(-1)
|
|
|
+
|
|
|
+// 监听model变化,初始化selectedWords
|
|
|
+watch(
|
|
|
+ () => model.value.content,
|
|
|
+ (newVal) => {
|
|
|
+ if (newVal) {
|
|
|
+ selectedWords.value = newVal.split(' ').filter(Boolean)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+)
|
|
|
+
|
|
|
+// 使用BIP39的英文单词列表
|
|
|
+const bip39Words = wordlists.english
|
|
|
+
|
|
|
+// 处理单词输入
|
|
|
+function handleWordInput() {
|
|
|
+ if (!inputWord.value) {
|
|
|
+ suggestions.value = []
|
|
|
+ selectedIndex.value = -1
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const input = inputWord.value.toLowerCase()
|
|
|
+ suggestions.value = bip39Words.filter((word) => word.startsWith(input)).slice(0, 10)
|
|
|
+ selectedIndex.value = -1
|
|
|
+}
|
|
|
+
|
|
|
+// 处理键盘事件
|
|
|
+function handleKeyDown(e) {
|
|
|
+ if (suggestions.value.length === 0) return
|
|
|
+
|
|
|
+ switch (e.key) {
|
|
|
+ case 'ArrowDown':
|
|
|
+ e.preventDefault()
|
|
|
+ selectedIndex.value = (selectedIndex.value + 1) % suggestions.value.length
|
|
|
+ scrollToSelected()
|
|
|
+ break
|
|
|
+ case 'ArrowUp':
|
|
|
+ e.preventDefault()
|
|
|
+ selectedIndex.value = (selectedIndex.value - 1 + suggestions.value.length) % suggestions.value.length
|
|
|
+ scrollToSelected()
|
|
|
+ break
|
|
|
+ case 'Enter':
|
|
|
+ e.preventDefault()
|
|
|
+ if (selectedIndex.value >= 0) {
|
|
|
+ selectWord(suggestions.value[selectedIndex.value])
|
|
|
+ }
|
|
|
+ break
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 选择单词
|
|
|
+function selectWord(word) {
|
|
|
+ if (!selectedWords.value.includes(word)) {
|
|
|
+ selectedWords.value.push(word)
|
|
|
+ updateContent()
|
|
|
+ }
|
|
|
+ inputWord.value = ''
|
|
|
+ suggestions.value = []
|
|
|
+ selectedIndex.value = -1
|
|
|
+}
|
|
|
+
|
|
|
+// 移除单词
|
|
|
+function removeWord(index) {
|
|
|
+ selectedWords.value.splice(index, 1)
|
|
|
+ updateContent()
|
|
|
+}
|
|
|
+
|
|
|
+// 更新内容
|
|
|
+function updateContent() {
|
|
|
+ model.value.content = selectedWords.value.join(' ')
|
|
|
+}
|
|
|
+
|
|
|
+// 滚动到选中项
|
|
|
+function scrollToSelected() {
|
|
|
+ const suggestionsEl = document.querySelector('.suggestions')
|
|
|
+ const selectedEl = suggestionsEl?.querySelector('.suggestion-item-active')
|
|
|
+ if (selectedEl) {
|
|
|
+ const containerRect = suggestionsEl.getBoundingClientRect()
|
|
|
+ const selectedRect = selectedEl.getBoundingClientRect()
|
|
|
+ const itemHeight = selectedRect.height
|
|
|
+ const visibleHeight = containerRect.height
|
|
|
+ const scrollTop = suggestionsEl.scrollTop
|
|
|
+ const selectedTop = selectedRect.top - containerRect.top + scrollTop
|
|
|
+
|
|
|
+ // 计算选中项在可视区域中的位置
|
|
|
+ const positionInViewport = selectedTop - scrollTop
|
|
|
+
|
|
|
+ // 如果选中项在中间位置以下,向下滚动
|
|
|
+ if (positionInViewport > visibleHeight / 2) {
|
|
|
+ suggestionsEl.scrollTop = selectedTop - visibleHeight / 2 + itemHeight / 2
|
|
|
+ }
|
|
|
+ // 如果选中项在中间位置以上,向上滚动
|
|
|
+ else if (positionInViewport < visibleHeight / 2 - itemHeight) {
|
|
|
+ suggestionsEl.scrollTop = selectedTop - visibleHeight / 2 + itemHeight / 2
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
onMounted(async () => {
|
|
|
try {
|
|
|
const response = await http.post('/ocrChannel/names')
|
|
|
@@ -371,4 +501,134 @@ async function handleFavoriteClick(row) {
|
|
|
margin-top: 8px;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+.suggestions {
|
|
|
+ position: absolute;
|
|
|
+ top: 100%;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ margin-top: 4px;
|
|
|
+ max-height: 300px;
|
|
|
+ overflow-y: auto;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ background-color: white;
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
+ z-index: 1000;
|
|
|
+}
|
|
|
+
|
|
|
+.suggestion-item {
|
|
|
+ padding: 8px 12px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.2s;
|
|
|
+}
|
|
|
+
|
|
|
+.suggestion-item:hover,
|
|
|
+.suggestion-item-active {
|
|
|
+ background-color: #f5f7fa;
|
|
|
+ color: #409eff;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.suggestion-item-active {
|
|
|
+ background-color: #ecf5ff;
|
|
|
+}
|
|
|
+
|
|
|
+.selected-words-container {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+ padding: 10px;
|
|
|
+ min-height: 300px;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ border-radius: 4px;
|
|
|
+ background-color: white;
|
|
|
+ overflow-y: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.selected-word-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ padding: 6px 12px;
|
|
|
+ background-color: #f0f9eb;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #e1f3d8;
|
|
|
+ transition: all 0.3s;
|
|
|
+}
|
|
|
+
|
|
|
+.selected-word-item:hover {
|
|
|
+ transform: translateY(-2px);
|
|
|
+ box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
|
|
+}
|
|
|
+
|
|
|
+.selected-word-item span {
|
|
|
+ color: #67c23a;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.dialog-content {
|
|
|
+ display: flex;
|
|
|
+ align-items: stretch;
|
|
|
+ gap: 20px;
|
|
|
+ height: 500px;
|
|
|
+ padding: 10px;
|
|
|
+}
|
|
|
+
|
|
|
+.left-section {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 20px;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.input-section {
|
|
|
+ margin-bottom: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.input-wrapper {
|
|
|
+ position: relative;
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.word-input {
|
|
|
+ width: 100%;
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.divider {
|
|
|
+ margin: 0;
|
|
|
+ height: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.right-section {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 10px;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.image-container {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ background-color: white;
|
|
|
+ border-radius: 4px;
|
|
|
+ border: 1px solid #dcdfe6;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+
|
|
|
+.preview-image {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 100%;
|
|
|
+ object-fit: contain;
|
|
|
+}
|
|
|
</style>
|