完全跑通1.0版本
This commit is contained in:
@@ -13,7 +13,9 @@
|
||||
@keyup.enter="login"
|
||||
/>
|
||||
<div v-if="loginError" class="login-error">{{ loginError }}</div>
|
||||
<button class="login-btn" @click="login">进入</button>
|
||||
<button class="login-btn" @click="login" :disabled="loginLoading">
|
||||
{{ loginLoading ? '验证中...' : '进入' }}
|
||||
</button>
|
||||
<router-link to="/" class="back-link">← 返回首页</router-link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -166,7 +168,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import {
|
||||
getLLMConfig, saveLLMConfig, testLLMConfig,
|
||||
getSources, addSource, toggleSource, deleteSource,
|
||||
@@ -177,14 +179,26 @@ import {
|
||||
const authed = ref(!!localStorage.getItem('admin_token'))
|
||||
const tokenInput = ref('')
|
||||
const loginError = ref('')
|
||||
const loginLoading = ref(false)
|
||||
|
||||
function login() {
|
||||
if (!tokenInput.value) return
|
||||
async function login() {
|
||||
if (!tokenInput.value || loginLoading.value) return
|
||||
loginLoading.value = true
|
||||
loginError.value = ''
|
||||
localStorage.setItem('admin_token', tokenInput.value)
|
||||
authed.value = true
|
||||
init()
|
||||
try {
|
||||
await getStats()
|
||||
authed.value = true
|
||||
init()
|
||||
} catch (e) {
|
||||
localStorage.removeItem('admin_token')
|
||||
loginError.value = '令牌错误,请重试'
|
||||
} finally {
|
||||
loginLoading.value = false
|
||||
}
|
||||
}
|
||||
function logout() {
|
||||
stopPolling()
|
||||
localStorage.removeItem('admin_token')
|
||||
authed.value = false
|
||||
}
|
||||
@@ -255,16 +269,31 @@ async function deleteSrc(id) {
|
||||
// ── Ops ───────────────────────────────────────────────────────────────────────
|
||||
const stats = ref(null)
|
||||
const triggering = ref(false)
|
||||
let pollTimer = null
|
||||
|
||||
async function loadStats() {
|
||||
try { stats.value = await getStats() }
|
||||
catch (e) { if (e.message === 'UNAUTHORIZED') logout() }
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
if (pollTimer) return
|
||||
pollTimer = setInterval(async () => {
|
||||
await loadStats()
|
||||
await loadLogs()
|
||||
if (!stats.value?.pipeline_running) stopPolling()
|
||||
}, 4000)
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (pollTimer) { clearInterval(pollTimer); pollTimer = null }
|
||||
}
|
||||
|
||||
async function triggerCrawl() {
|
||||
triggering.value = true
|
||||
try {
|
||||
await apiTrigger()
|
||||
setTimeout(loadStats, 2000)
|
||||
startPolling()
|
||||
} finally {
|
||||
triggering.value = false
|
||||
}
|
||||
@@ -282,6 +311,7 @@ async function init() {
|
||||
}
|
||||
|
||||
onMounted(() => { if (authed.value) init() })
|
||||
onUnmounted(() => stopPolling())
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -318,7 +348,9 @@ onMounted(() => { if (authed.value) init() })
|
||||
.login-btn {
|
||||
width: 100%; height: 40px; border-radius: 8px;
|
||||
background: var(--blue); color: #fff; font-size: 14px; font-weight: 600; border: none;
|
||||
transition: opacity .15s;
|
||||
}
|
||||
.login-btn:disabled { opacity: .6; cursor: default; }
|
||||
.back-link { font-size: 12px; color: var(--t4); }
|
||||
|
||||
/* Topbar */
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||||
import NewsCard from '../components/NewsCard.vue'
|
||||
import TopTenPanel from '../components/TopTenPanel.vue'
|
||||
import { fetchFeatured, fetchNews, fetchDates } from '../api/index.js'
|
||||
@@ -182,7 +182,14 @@ function openDetail(news) {
|
||||
selectedNews.value = news
|
||||
}
|
||||
|
||||
onMounted(() => loadAll(selectedDate.value))
|
||||
function onKeyDown(e) {
|
||||
if (e.key === 'Escape' && selectedNews.value) selectedNews.value = null
|
||||
}
|
||||
onMounted(() => {
|
||||
loadAll(selectedDate.value)
|
||||
window.addEventListener('keydown', onKeyDown)
|
||||
})
|
||||
onUnmounted(() => window.removeEventListener('keydown', onKeyDown))
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user