refactor: 项目架构重构 - 目录标准化 + 清理冗余

- 目录重命名: backend/wdkj-server/ → server/, frontend/ai-dimension/ → client/
- 删除 20+ 冗余文件(WDKJ 旧脚本、Windows 脚本、过时文档、设计稿)
- 更新 package.json 元数据(移除 wdkj 命名)
- 完善三级 .gitignore(根 + server + client)
- 重写 README.md 和 CHANGELOG.md
- 工具脚本移至 scripts/
This commit is contained in:
Yuzhiran Dev
2026-07-11 12:45:37 +08:00
parent 66ac576082
commit c1d6dd3b29
106 changed files with 17436 additions and 2311 deletions
+386
View File
@@ -0,0 +1,386 @@
<template>
<view class="search-page">
<!-- 顶部导航 -->
<view class="nav-bar">
<view class="nav-back" @click="goBack">‹</view>
<text class="nav-title">搜索</text>
<view class="nav-spacer"></view>
</view>
<!-- 搜索框 -->
<view class="search-box">
<input
class="search-input"
type="text"
:value="searchText"
:placeholder="placeholderText"
placeholder-style="color: var(--text-secondary); font-size: 14px;"
@input="onInput"
@confirm="doSearch"
confirm-type="search"
/>
<view class="search-icon">🔍</view>
<view v-if="searchText" class="clear-btn" @click="clearSearch">✕</view>
</view>
<!-- 加载状态 -->
<view class="loading-state" v-if="loading">
<view class="loading-spinner"></view>
<text class="loading-text">搜索中...</text>
</view>
<!-- 空搜索提示 -->
<view class="empty-state" v-else-if="!hasSearched">
<text class="empty-icon">🔍</text>
<text class="empty-text">输入关键词搜索 AI 知识</text>
<text class="empty-hint">支持按标题、摘要、标签搜索</text>
</view>
<!-- 无结果提示 -->
<view class="empty-state" v-else-if="results.length === 0 && !loading">
<text class="empty-icon">📭</text>
<text class="empty-text">没有找到相关内容</text>
<text class="empty-hint">试试换个关键词搜索</text>
</view>
<!-- 搜索结果列表 -->
<scroll-view class="results-scroll" scroll-y v-else>
<view
class="result-card"
v-for="item in results"
:key="item._id || item.id"
@click="goDetail(item._id || item.id)"
>
<!-- 维度标签 -->
<view
class="dim-badge"
:style="{ background: dimColor(item.dim) }"
>
<text class="dim-name">D{{ item.dim }}</text>
</view>
<text class="result-title">{{ item.title }}</text>
<!-- 摘要 -->
<text class="result-summary" v-if="item.summary">
{{ item.summary }}
</text>
<!-- 标签与分类 -->
<view class="result-meta">
<view class="meta-tags" v-if="item.tags?.length">
<text class="meta-tag" v-for="tag in item.tags" :key="tag">
#{{ tag }}
</text>
</view>
<text class="result-category" v-if="item.category">
{{ categoryLabel(item.category) }}
</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { knowledgeApi } from '@/utils/api'
import { DIMENSIONS } from '@/utils/dimensions'
const searchText = ref('')
const results = ref([])
const loading = ref(false)
const hasSearched = ref(false)
let debounceTimer = null
let page = 1
// 维度颜色
function dimColor(dim) {
if (!dim) return 'var(--dim1-color)'
const dimInfo = DIMENSIONS.find(d => d.id === dim)
return dimInfo ? dimInfo.color : 'var(--dim1-color)'
}
// 分类标签
function categoryLabel(cat) {
const map = {
concept: '📚 概念',
history: '📜 历史',
application: '🛠️ 应用',
question: '❓ 问答'
}
return map[cat] || cat
}
// 占位符提示
const placeholderText = computed(() =>
searchText.value ? '' : '输入关键词搜索 AI 知识'
)
// 输入处理(debounced)
function onInput(e) {
const val = e.detail?.value || e.target?.value || ''
searchText.value = val
if (debounceTimer) clearTimeout(debounceTimer)
debounceTimer = setTimeout(() => {
if (val.trim()) {
doSearch()
}
}, 500)
}
// 执行搜索
async function doSearch() {
const q = searchText.value.trim()
if (!q) return
loading.value = true
hasSearched.value = true
try {
const data = await knowledgeApi.search(q, { page, limit: 20 })
results.value = data.list || data.items || data || []
} catch (err) {
console.error('[search] 搜索失败:', err)
uni.showToast({ title: '搜索失败,请重试', icon: 'none' })
} finally {
loading.value = false
}
}
// 清空搜索
function clearSearch() {
searchText.value = ''
results.value = []
hasSearched.value = false
page = 1
if (debounceTimer) {
clearTimeout(debounceTimer)
debounceTimer = null
}
}
// 跳转到详情
function goDetail(id) {
if (!id) return
uni.navigateTo({ url: `/pages/detail/detail?id=${id}` })
}
// 返回
function goBack() {
uni.navigateBack()
}
onMounted(() => {
// 检查是否有路由传入的搜索词
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const query = currentPage.$page?.query || {}
if (query.q) {
searchText.value = query.q
doSearch()
}
})
onUnmounted(() => {
if (debounceTimer) clearTimeout(debounceTimer)
})
</script>
<style lang="scss" scoped>
.search-page {
width: 100%;
height: 100vh;
display: flex;
flex-direction: column;
background: var(--bg-primary);
}
// 顶部导航
.nav-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 50px 20px 12px;
flex-shrink: 0;
.nav-back {
width: 30px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
color: var(--text-primary);
}
.nav-title {
font-size: 16px;
font-weight: 600;
color: var(--text-primary);
}
.nav-spacer { width: 30px; }
}
// 搜索框
.search-box {
position: relative;
margin: 0 20px 16px;
flex-shrink: 0;
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 14px;
padding: 10px 44px 10px 14px;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
.search-input {
width: 100%;
height: 32px;
font-size: 14px;
color: var(--text-primary);
background: transparent;
outline: none;
}
.search-icon {
position: absolute;
right: 14px;
top: 50%;
transform: translateY(-50%);
font-size: 16px;
color: var(--text-secondary);
}
.clear-btn {
position: absolute;
right: 14px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.1);
display: flex;
align-items: center;
justify-content: center;
font-size: 11px;
color: var(--text-secondary);
}
}
// 加载状态
.loading-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 12px;
.loading-spinner {
width: 32px;
height: 32px;
border: 3px solid rgba(179, 136, 255, 0.2);
border-top-color: var(--dim1-color);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.loading-text {
font-size: 14px;
color: var(--text-secondary);
}
}
@keyframes spin {
to { transform: rotate(360deg); }
}
// 空状态
.empty-state {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 10px;
.empty-icon { font-size: 48px; opacity: 0.5; }
.empty-text { font-size: 16px; color: var(--text-secondary); }
.empty-hint { font-size: 13px; color: var(--text-muted); }
}
// 结果列表
.results-scroll {
flex: 1;
padding: 0 20px;
overflow-y: auto;
padding-bottom: 40px;
}
.result-card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
border-radius: 14px;
padding: 14px 16px;
margin-bottom: 10px;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
transition: all 0.3s;
}
.dim-badge {
display: inline-block;
padding: 2px 10px;
border-radius: 8px;
font-size: 11px;
font-weight: 600;
color: white;
margin-bottom: 8px;
opacity: 0.85;
}
.result-title {
display: block;
font-size: 15px;
font-weight: 600;
color: var(--text-primary);
line-height: 1.5;
margin-bottom: 6px;
}
.result-summary {
display: block;
font-size: 13px;
color: var(--text-tertiary);
line-height: 1.6;
margin-bottom: 10px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.result-meta {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
}
.meta-tags {
display: flex;
gap: 6px;
flex-wrap: wrap;
.meta-tag {
font-size: 11px;
color: var(--dim1-color);
background: rgba(179, 136, 255, 0.1);
padding: 2px 8px;
border-radius: 8px;
}
}
.result-category {
font-size: 11px;
color: var(--text-secondary);
}
</style>