feat: 地图闯关——知识岛地图+知识关问答页,对比度微调,SEO路由,v1.2.7
This commit is contained in:
+1
-1
@@ -23,4 +23,4 @@ Thumbs.db
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*.swoclient/src/.share-done
|
||||
|
||||
+3
-3
@@ -81,7 +81,7 @@ page, .theme-light {
|
||||
|
||||
/* 卡片 — 纯白微阴影 */
|
||||
--glass-bg: #ffffff;
|
||||
--glass-border: rgba(0, 0, 0, 0.06);
|
||||
--glass-border: rgba(0, 0, 0, 0.08);
|
||||
--glass-hover: #f8f8ff;
|
||||
--glass-hover-border: rgba(99, 102, 241, 0.15);
|
||||
--glass-strong: #ffffff;
|
||||
@@ -89,8 +89,8 @@ page, .theme-light {
|
||||
|
||||
/* 文字 — 清晰深色 */
|
||||
--text-primary: #0d0d1a;
|
||||
--text-secondary: rgba(13, 13, 26, 0.6);
|
||||
--text-tertiary: rgba(13, 13, 26, 0.4);
|
||||
--text-secondary: rgba(13, 13, 26, 0.68);
|
||||
--text-tertiary: rgba(13, 13, 26, 0.48);
|
||||
--text-muted: rgba(13, 13, 26, 0.25);
|
||||
--text-disabled: rgba(13, 13, 26, 0.15);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "宇之然AI维度",
|
||||
"appid": "wxdad62baf4ccd09e3",
|
||||
"versionName": "1.1.5",
|
||||
"versionName": "1.2.7",
|
||||
"versionCode": "100",
|
||||
"description": "AI 知识探索社区 — 从5个维度系统理解人工智能",
|
||||
"uni-app": {
|
||||
|
||||
@@ -124,6 +124,18 @@
|
||||
"navigationBarTitleText": "协议",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/map/map",
|
||||
"style": {
|
||||
"navigationBarTitleText": "AI 知识地图"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/map/quiz",
|
||||
"style": {
|
||||
"navigationBarTitleText": "知识闯关"
|
||||
}
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<view class="map-page">
|
||||
<view class="status-bar"></view>
|
||||
<view class="top-bar">
|
||||
<view class="user-info">
|
||||
<text class="lv-icon">{{ level.icon }}</text>
|
||||
<text class="lv-name">{{ level.name }} Lv{{ level.level }}</text>
|
||||
</view>
|
||||
<view class="coin-box">
|
||||
<text class="coin-icon">💎</text>
|
||||
<text class="coin-num">{{ credits }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="map-scroll">
|
||||
<view class="map-body" :style="{ height: mapHeight + rpx }">
|
||||
<view class="map-path-line" :style="{ height: mapHeight + rpx }"></view>
|
||||
<view v-for="(island, i) in islands" :key="i"
|
||||
class="island-node"
|
||||
:class="{ active: island.status === current, done: island.status === done, locked: island.status === locked }"
|
||||
:style="{ top: island.y + rpx, left: island.x + rpx }"
|
||||
@tap="goIsland(island)">
|
||||
<view class="island-icon">{{ island.icon }}</view>
|
||||
<text class="island-name">{{ island.name }}</text>
|
||||
<view v-if="island.status === done" class="done-star">⭐</view>
|
||||
<view v-if="island.status === locked" class="lock-icon">🔒</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="bottom-bar">
|
||||
<button class="ai-btn" :disabled="!aiUnlocked" @tap="goCreate">✨ AI 生成</button>
|
||||
<text v-if="!aiUnlocked" class="ai-hint">完成一个知识关后解锁</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
credits: 0,
|
||||
level: { level: 1, name: "画童", icon: "🖌️" },
|
||||
islands: [
|
||||
{ id: 1, name: "AI 起源", icon: "🏛️", x: 100, y: 180, status: "current" },
|
||||
{ id: 2, name: "发展脉络", icon: "📈", x: 380, y: 360, status: "locked" },
|
||||
{ id: 3, name: "行业格局", icon: "🌍", x: 100, y: 540, status: "locked" },
|
||||
{ id: 4, name: "学习工具", icon: "🛠️", x: 380, y: 720, status: "locked" },
|
||||
{ id: 5, name: "趋势前沿", icon: "🚀", x: 100, y: 900, status: "locked" },
|
||||
],
|
||||
aiUnlocked: false,
|
||||
mapHeight: 1100,
|
||||
}
|
||||
},
|
||||
onShow() { this.loadUser() },
|
||||
methods: {
|
||||
loadUser() {
|
||||
var token = uni.getStorageSync("dim_admin_token")
|
||||
if (!token) return
|
||||
uni.request({
|
||||
url: "/api/me",
|
||||
header: { Authorization: "Bearer " + token },
|
||||
success: function(res) {
|
||||
if (res.data && res.data.user) {
|
||||
this.credits = res.data.user.credits || 0
|
||||
}
|
||||
}.bind(this)
|
||||
})
|
||||
},
|
||||
goIsland(island) {
|
||||
if (island.status !== "current") {
|
||||
uni.showToast({ title: "先完成前面的知识关", icon: "none" })
|
||||
return
|
||||
}
|
||||
uni.navigateTo({ url: "/pages/map/quiz?islandId=" + island.id + "&name=" + encodeURIComponent(island.name) })
|
||||
},
|
||||
goCreate() {
|
||||
uni.switchTab({ url: "/pages/index/index" })
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.map-page { background: linear-gradient(180deg, #1a1a3e 0%, #2d1b69 50%, #0d0d1a 100%); min-height: 100vh; }
|
||||
.status-bar { height: 80rpx; }
|
||||
.top-bar { display: flex; justify-content: space-between; align-items: center; padding: 16rpx 30rpx; }
|
||||
.user-info { display: flex; align-items: center; gap: 8rpx; }
|
||||
.lv-icon { font-size: 32rpx; }
|
||||
.lv-name { color: #fff; font-size: 26rpx; font-weight: 600; }
|
||||
.coin-box { display: flex; align-items: center; gap: 6rpx; background: rgba(255,255,255,0.1); border-radius: 999rpx; padding: 6rpx 20rpx; }
|
||||
.coin-icon { font-size: 26rpx; }
|
||||
.coin-num { color: #ffd700; font-size: 26rpx; font-weight: 700; }
|
||||
.map-scroll { height: calc(100vh - 80rpx - 70rpx - 120rpx); }
|
||||
.map-body { position: relative; width: 100%; }
|
||||
.map-path-line { position: absolute; left: 50%; top: 100rpx; width: 4rpx; height: 900rpx; background: rgba(255,255,255,0.1); transform: translateX(-50%); }
|
||||
.island-node { position: absolute; width: 180rpx; text-align: center; }
|
||||
.island-icon { font-size: 56rpx; display: block; margin-bottom: 8rpx; }
|
||||
.island-name { color: #fff; font-size: 24rpx; display: block; }
|
||||
.done-star { position: absolute; top: -10rpx; right: 0; font-size: 28rpx; }
|
||||
.lock-icon { position: absolute; top: -8rpx; right: -8rpx; font-size: 28rpx; }
|
||||
.island-node.active .island-icon { animation: bounce 2s infinite; }
|
||||
@keyframes bounce { 0%,100% { transform: translateY(0) } 50% { transform: translateY(-16rpx) } }
|
||||
.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; padding: 20rpx 30rpx calc(20rpx + env(safe-area-inset-bottom)); background: rgba(0,0,0,0.5); display: flex; flex-direction: column; align-items: center; gap: 8rpx; z-index: 100; }
|
||||
.ai-btn { width: 100%; background: linear-gradient(135deg, #6366f1, #a855f7); color: #fff; font-size: 32rpx; font-weight: 700; border-radius: 999rpx; padding: 20rpx; border: none; }
|
||||
.ai-btn[disabled] { opacity: 0.4; }
|
||||
.ai-hint { color: rgba(255,255,255,0.5); font-size: 22rpx; }
|
||||
</style>
|
||||
@@ -0,0 +1,89 @@
|
||||
<template>
|
||||
<view class="quiz-page">
|
||||
<view class="quiz-header">
|
||||
<text class="quiz-title">{{ islandName }} · 知识关</text>
|
||||
<text class="quiz-progress">{{ current + 1 }} / {{ questions.length }}</text>
|
||||
</view>
|
||||
<view class="quiz-body" v-if="currentQ">
|
||||
<text class="quiz-q">{{ currentQ.q }}</text>
|
||||
<view v-for="(opt, i) in currentQ.options" :key="i"
|
||||
class="quiz-opt"
|
||||
:class="{ selected: selected === i, correct: submitted && i === currentQ.answer, wrong: submitted && selected === i && i !== currentQ.answer }"
|
||||
@tap="pick(i)">
|
||||
<text>{{ labels[i] }}. {{ opt }}</text>
|
||||
</view>
|
||||
<view v-if="submitted" class="quiz-result">
|
||||
<text :class="isCorrect ? 'ok' : 'fail'">{{ resultText }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="quiz-footer">
|
||||
<button v-if="!submitted && currentQ" class="submit-btn" @tap="submit">提交答案</button>
|
||||
<button v-if="submitted && current < questions.length - 1" class="next-btn" @tap="nextQ">下一题 →</button>
|
||||
<button v-if="submitted && current >= questions.length - 1" class="next-btn done-btn" @tap="finish">完成知识关</button>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
islandName: '',
|
||||
current: 0,
|
||||
selected: -1,
|
||||
submitted: false,
|
||||
isCorrect: false,
|
||||
resultText: '',
|
||||
labels: ['A', 'B', 'C', 'D'],
|
||||
questions: [
|
||||
{ q: 'AI 的英文全称是什么?', options: ['Artificial Intelligence', 'Automatic Integration', 'Advanced Internet', 'Applied Innovation'], answer: 0 },
|
||||
{ q: '深度学习属于 AI 的哪个领域?', options: ['规则推理', '机器学习', '硬件设计', '网络协议'], answer: 1 },
|
||||
{ q: '以下哪个是当前主流的大语言模型?', options: ['Photoshop', 'GPT', 'Excel', 'Bluetooth'], answer: 1 },
|
||||
],
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentQ() { return this.questions[this.current] || null },
|
||||
isLast() { return this.current >= this.questions.length - 1 },
|
||||
},
|
||||
onLoad(options) {
|
||||
if (options.name) {
|
||||
this.islandName = decodeURIComponent(options.name)
|
||||
uni.setNavigationBarTitle({ title: this.islandName + ' · 知识关' })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
pick(i) {
|
||||
if (this.submitted) return
|
||||
this.selected = i
|
||||
this.submitted = true
|
||||
this.isCorrect = this.selected === this.questions[this.current].answer
|
||||
},
|
||||
nextQ() {
|
||||
if (this.current < this.questions.length - 1) {
|
||||
this.current++
|
||||
this.selected = -1
|
||||
this.submitted = false
|
||||
} else {
|
||||
uni.showToast({ title: '知识关完成!', icon: 'success' })
|
||||
setTimeout(() => uni.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.quiz-page { background: linear-gradient(180deg, #1a1a3e, #0d0d1a); min-height: 100vh; padding: 30rpx; box-sizing: border-box; }
|
||||
.quiz-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 30rpx; }
|
||||
.quiz-title { color: #fff; font-size: 32rpx; font-weight: 700; }
|
||||
.quiz-progress { color: rgba(255,255,255,0.5); font-size: 24rpx; }
|
||||
.quiz-q { color: #fff; font-size: 32rpx; font-weight: 600; margin-bottom: 30rpx; display: block; }
|
||||
.quiz-opt { background: rgba(255,255,255,0.08); border: 2rpx solid rgba(255,255,255,0.15); border-radius: 16rpx; padding: 24rpx 28rpx; margin-bottom: 16rpx; color: #fff; font-size: 28rpx; transition: all 0.2s; }
|
||||
.quiz-opt.selected { border-color: #6366f1; background: rgba(99,102,241,0.15); }
|
||||
.quiz-opt.correct { border-color: #22c55e; background: rgba(34,197,94,0.15); }
|
||||
.quiz-opt.wrong { border-color: #ef4444; background: rgba(239,68,68,0.15); }
|
||||
.quiz-result { text-align: center; margin-top: 20rpx; font-size: 30rpx; }
|
||||
.quiz-footer { margin-top: 30rpx; }
|
||||
.submit-btn { width: 100%; background: linear-gradient(135deg, #6366f1, #a855f7); color: #fff; font-size: 30rpx; font-weight: 700; border-radius: 16rpx; padding: 24rpx; border: none; margin-top: 20rpx; }
|
||||
</style>
|
||||
@@ -9,6 +9,7 @@ const rateLimit = require('express-rate-limit')
|
||||
|
||||
const connectDB = require('./config/database')
|
||||
const initRoutes = require('./routes')
|
||||
const seoRoutes = require('./routes/seo')
|
||||
const logger = require('./utils/logger')
|
||||
|
||||
// 创建 Express 应用
|
||||
@@ -17,6 +18,9 @@ const app = express()
|
||||
// 数据库连接
|
||||
connectDB()
|
||||
|
||||
// SEO/GEO 路由(robots/sitemap/llms/about)
|
||||
app.use('/', seoRoutes)
|
||||
|
||||
// 中间件配置
|
||||
app.use(helmet()) // 安全头部
|
||||
app.use(cors()) // 跨域支持
|
||||
|
||||
Reference in New Issue
Block a user