diff --git a/src/App.jsx b/src/App.jsx index e9cc11e..bdeb219 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,6 +3,7 @@ import Admin from './pages/Admin'; import MediaScreen from './pages/MediaScreen'; import DataScreen from './pages/DataScreen'; import DigitalTwin from './pages/DigitalTwin'; +import AiAssistant from './pages/AiAssistant'; import ScreenLayout from './components/ScreenLayout'; import './styles/tokens.css'; @@ -14,6 +15,7 @@ export default function App() { }> } /> } /> + } /> } /> {/* 管理后台:独立布局(不参与大屏循环) */} diff --git a/src/components/AiChatPanel.jsx b/src/components/AiChatPanel.jsx new file mode 100644 index 0000000..cd8b540 --- /dev/null +++ b/src/components/AiChatPanel.jsx @@ -0,0 +1,185 @@ +import { useState, useRef, useEffect, useCallback } from 'react'; +import Icon from './Icons'; + +/* ========================================================= + AI 智能助手对话面板(共享)—— 遵循 bd- 浅色科技 UI 规范 + 左侧对话区:消息流 + 快捷提问 + 输入发送(模拟园区知识库回复) + ========================================================= */ + +/* ---------- 快捷提问 ---------- */ +const QUICK_QUESTIONS = [ + '如何申请入驻园区?', + '园区有哪些政策扶持?', + '办公场地怎么收费?', + 'AI 赋能服务有哪些?', + '三个分区有什么区别?', +]; + +/* ---------- 园区知识库(模拟回复) ---------- */ +const WELCOME = '您好,我是 OPC 园区 AI 智能助手。\n\n我可以为您解答入驻流程、政策扶持、场地服务、AI 赋能、企业名录等问题。您也可以点击下方快捷提问,或直接输入问题。'; + +const RULES = [ + { + match: /入驻|申请|入孵|入园|如何加入|怎样加入/, + reply: '入驻流程如下:\n\n1. 提交入驻申请(线上表单或到园区服务中心现场办理)\n2. 项目评审(园区组织专家对项目可行性、团队背景进行评估)\n3. 签订入驻协议,办理入驻手续\n4. 安排办公场地,对接创业导师与孵化服务\n\n目前园区在园项目 158 家,累计孵化企业 208 余家。如需协助,可直接联系园区服务中心。', + }, + { + match: /政策|补贴|优惠|扶持|减免|奖励|资助/, + reply: '园区政策服务覆盖「七位一体」中的政策维度:\n\n· 场地租金减免:符合条件的大学生创业项目可享受场地优惠\n· 创业补贴:协助对接省、市两级创业补贴与一次性创业资助\n· 税费优惠:对接小微企业所得税减免、增值税优惠等政策\n· 项目申报:指导高企认定(目前在园高新技术企业 32 家)、科技型中小企业备案\n\n更多政策细节可咨询园区政策服务窗口。', + }, + { + match: /场地|办公室|工位|租金|收费|面积|多少钱|价格/, + reply: '园区总建筑面积 1.2 万㎡,按功能划分为三个区域:\n\n· 加速区(A1–A12):成长型项目办公室,共 11 间独立办公室 + 1 间园区管理办公室\n· 国际区(I1–I7):面向国际合作的办公空间(7 间)\n· 成长区(G1–G8):初创团队成长空间(8 间)\n\n租金按创业项目阶段与政策给予差异化优惠,具体可到园区服务中心咨询,或询问「入驻流程」。', + }, + { + match: /AI|模型|算力|tokens|token|工具|智能|赋能|大模型/, + reply: '园区 AI 赋能服务是「七位一体」的核心特色:\n\n· 大模型推理:接入 DeepSeek-V3、通义千问、智谱 GLM-4、豆包、讯飞星火等多模型\n· 工具调用:文档生成、数据查询、图像创作、代码执行、语音合成等智能工具\n· 算力与成本:实时提供 token 消耗统计与速率监控,帮助企业控制 AI 使用成本\n\n今日园区 AI 推理任务持续运行,多模型负载均衡调度,服务节点 12/12 在线。', + }, + { + match: /加速区|国际区|成长区|分区|区域|区别|布局/, + reply: '园区按企业成长阶段划分三个区域:\n\n· 加速区(蓝色):面向已具备产品与市场验证的成长型项目,提供独立办公室(A1–A12),目前在驻 9 家企业\n· 国际区(青色):面向国际合作与跨境业务项目(I1–I7),在驻 7 家\n· 成长区(绿色):面向初创团队的共享成长空间(G1–G8),在驻 8 家\n\n园区管理办公室位于加速区 M 房间,提供一站式服务。', + }, + { + match: /企业|公司|名录|名单|入驻企业|有哪些企业|查.*企业/, + reply: '园区目前有 39 家入驻企业,分布在三个区域:\n\n· 加速区:云南派音人工智能科技、米勒克尔蓝宝石珠宝、云南宸中低空经济等 9 家\n· 国际区:仰光客厅、中越生物医疗、酷享野农AI农业等 7 家\n· 成长区:Facebook越南跨境电商、昆明云韵体育、云南星瑞航空等 8 家\n\n您也可以直接查看右侧「企业分布」面板,按分区浏览企业名录与房间号。', + }, + { + match: /融资|投资|资金|贷款|估值|资本/, + reply: '园区融资服务包含:\n\n· 融资对接:定期组织路演与投资机构对接活动\n· 孵化估值:累计孵化企业估值约 5.2 亿元\n· 金融支持:协助对接银行创业贷款、政府创业担保贷款\n· 财务辅导:提供融资方案设计与商业计划书辅导\n\n有融资需求的企业可向园区服务中心提交商业计划书。', + }, + { + match: /就业|招聘|岗位|人才|用工/, + reply: '园区累计带动就业 2,186 人(其中高校毕业生 1,680 人,实习岗位 506 个)。\n\n· 企业招聘:在驻企业持续发布岗位(今日新增多个岗位)\n· 人才对接:与在昆高校共建实习实训基地\n· 创业带动:园区鼓励以创业带动就业,多个项目已形成稳定用工\n\n具体招聘岗位可关注园区实时动态与入驻企业名录。', + }, + { + match: /导师|培训|课程|辅导|孵化服务|孵化器/, + reply: '园区配备创业导师 58 位,提供全周期孵化服务:\n\n· 一对一辅导:创业导师定期坐诊,覆盖商业模式、法律财税、市场营销\n· 培训课程:创业训练营、行业专题沙龙、政策解读会\n· 资源对接:技术、供应链、渠道等产业资源链接\n· 公共服务:工商注册、知识产权、法律咨询一站式代办', + }, + { + match: /你好|您好|hi|hello|嗨|在吗|hello/, + reply: '您好!我是 OPC 园区 AI 智能助手。\n\n可以问我:入驻流程、政策扶持、场地收费、AI 赋能、企业名录、融资服务等问题,很高兴为您服务。', + }, + { + match: /谢谢|感谢|好的|明白|ok/, + reply: '不客气,随时为您服务。您也可以点击右侧「企业分布」查看入驻企业,或继续向我提问。', + }, +]; + +const DEFAULT_REPLY = '园区提供「空间 + 孵化 + 融资 + 政策 + 资源 + AI 赋能 + 综合服务」七位一体服务,在园项目 158 家,累计带动就业 2,186 人,累计营收 2.08 亿元。\n\n您可以这样问我:\n· 如何申请入驻园区?\n· 园区有哪些政策扶持?\n· AI 赋能服务有哪些?'; + +function getReply(input) { + const text = String(input || '').trim(); + for (const rule of RULES) { + if (rule.match.test(text)) return rule.reply; + } + return DEFAULT_REPLY; +} + +/* ---------- 消息 ---------- */ +let msgId = 0; +const nextId = () => ++msgId; + +export default function AiChatPanel() { + const [msgs, setMsgs] = useState(() => [ + { id: nextId(), role: 'ai', text: WELCOME }, + ]); + const [input, setInput] = useState(''); + const [typing, setTyping] = useState(false); + const scrollRef = useRef(null); + const timerRef = useRef(null); + + // 新消息自动滚动到底部 + useEffect(() => { + const el = scrollRef.current; + if (el) el.scrollTop = el.scrollHeight; + }, [msgs, typing]); + + useEffect(() => () => clearTimeout(timerRef.current), []); + + const send = useCallback((raw) => { + const text = String(raw || '').trim(); + if (!text || typing) return; + setInput(''); + setMsgs((prev) => [...prev, { id: nextId(), role: 'me', text }]); + setTyping(true); + const reply = getReply(text); + timerRef.current = setTimeout(() => { + setMsgs((prev) => [...prev, { id: nextId(), role: 'ai', text: reply }]); + setTyping(false); + }, 700 + Math.random() * 600); + }, [typing]); + + const clear = useCallback(() => { + clearTimeout(timerRef.current); + setTyping(false); + setMsgs([{ id: nextId(), role: 'ai', text: WELCOME }]); + }, []); + + return ( +
+ {/* 头部 */} +
+ +
+ AI 智能助手 + OPC 园区知识库 · 随时为您解答 +
+ DeepSeek-V3 + +
+ + {/* 消息区 */} +
+ {msgs.map((m) => ( +
+ {m.role === 'ai' && ( + + )} +
{m.text}
+
+ ))} + {typing && ( +
+ +
+
+ )} +
+ + {/* 快捷提问 */} +
+ {QUICK_QUESTIONS.map((q) => ( + + ))} +
+ + {/* 输入区 */} +
+ setInput(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter' && !e.nativeEvent.isComposing) { + e.preventDefault(); + send(input); + } + }} + /> + +
+
+ ); +} diff --git a/src/components/Icons.jsx b/src/components/Icons.jsx index 9bb1255..0d21eaa 100644 --- a/src/components/Icons.jsx +++ b/src/components/Icons.jsx @@ -10,6 +10,7 @@ import { Heart, Pill, Dna, Microscope, Atom, MapPin, Flag, Award, Medal, History, Drone, Plane, Rocket, Satellite, Wallet, UserPlus, Activity, Volume, VolumeOff, ChevronLeft, ChevronRight, PlayerPlay, PlayerPause, + Send, Trash, Eraser, Headset, CircleDot, } from '@appica/icons-react'; /* 名称 → 图标组件映射(推荐的调用方式:icon="bolt") */ @@ -73,6 +74,11 @@ const NAME_MAP = { 'player-pause': PlayerPause, play: PlayerPlay, pause: PlayerPause, + send: Send, + trash: Trash, + eraser: Eraser, + headset: Headset, + 'circle-dot': CircleDot, }; /** @@ -105,4 +111,5 @@ export { Heart, Pill, Dna, Microscope, Atom, MapPin, Flag, Award, Medal, History, Drone, Plane, Rocket, Satellite, Wallet, UserPlus, Activity, Volume, VolumeOff, ChevronLeft, ChevronRight, PlayerPlay, PlayerPause, + Send, Trash, Eraser, Headset, CircleDot, }; diff --git a/src/components/PageHeader.jsx b/src/components/PageHeader.jsx index e65a2ea..2374e76 100644 --- a/src/components/PageHeader.jsx +++ b/src/components/PageHeader.jsx @@ -12,6 +12,7 @@ import { Avatar, AvatarImage, AvatarFallback } from '@appica/ui-react/avatar' const STATUS_BY_PATH = { '/': '数据实时', '/twin': '3D 实时', + '/ai': 'AI 助手', '/screen': '媒体播放', }; diff --git a/src/components/ParkSidePanel.jsx b/src/components/ParkSidePanel.jsx new file mode 100644 index 0000000..c0ef7af --- /dev/null +++ b/src/components/ParkSidePanel.jsx @@ -0,0 +1,151 @@ +import { useState, useEffect } from 'react'; +import Icon from './Icons'; + +/* ========================================================= + 园区企业分布侧栏(共享)—— 数字孪生 / AI 智能助手页右侧保持一致 + 分区切换 + 企业名录 + 企业详情 + 园区实时动态 + ========================================================= */ + +/* ---------- 企业数据(名称 / 区域 / 房间号) ---------- */ +export const COMPANIES = [ + { name: '云南派音人工智能科技', zone: '加速区', room: 'A1', color: '#4c8dff' }, + { name: '米勒克尔蓝宝石珠宝', zone: '加速区', room: 'A2', color: '#4c8dff' }, + { name: '中泰研学合作', zone: '加速区', room: 'A3', color: '#4c8dff' }, + { name: '云南宸中低空经济', zone: '加速区', room: 'A4', color: '#4c8dff' }, + { name: '昆明智海银高文化科技', zone: '加速区', room: 'A5', color: '#4c8dff' }, + { name: 'AI机器人大模型训练', zone: '加速区', room: 'A6', color: '#4c8dff' }, + { name: '云南廷秀文旅康养', zone: '加速区', room: 'A7', color: '#4c8dff' }, + { name: '瀚颖AI+教育信息咨询', zone: '加速区', room: 'A8', color: '#4c8dff' }, + { name: '云南大学AI+创业平台', zone: '加速区', room: 'A9', color: '#4c8dff' }, + { name: '仰光客厅', zone: '国际区', room: 'I1', color: '#22d3ee' }, + { name: '云南上古绝学文化', zone: '国际区', room: 'I2', color: '#22d3ee' }, + { name: '中越生物医疗', zone: '国际区', room: 'I3', color: '#22d3ee' }, + { name: '酷享野农AI农业', zone: '国际区', room: 'I4', color: '#22d3ee' }, + { name: '滇缅国际设计', zone: '国际区', room: 'I5', color: '#22d3ee' }, + { name: '昆明舒诺生物科技', zone: '国际区', room: 'I6', color: '#22d3ee' }, + { name: '达岸教育管理', zone: '国际区', room: 'I7', color: '#22d3ee' }, + { name: 'Facebook越南跨境电商', zone: '成长区', room: 'G1', color: '#34d399' }, + { name: '研X同行者网络', zone: '成长区', room: 'G2', color: '#34d399' }, + { name: '朵哈·玫瑰特色产业链', zone: '成长区', room: 'G3', color: '#34d399' }, + { name: '昆明云韵体育', zone: '成长区', room: 'G4', color: '#34d399' }, + { name: '南菌优培食用菌', zone: '成长区', room: 'G5', color: '#34d399' }, + { name: '五华区丽裳文化', zone: '成长区', room: 'G6', color: '#34d399' }, + { name: '云南星瑞航空', zone: '成长区', room: 'G7', color: '#34d399' }, + { name: '综合直播私域平台', zone: '成长区', room: 'G8', color: '#34d399' }, + { name: '园区管理办公室', zone: '园区管理', room: 'M', color: '#fbbf24' }, +]; + +export const ZONES = ['加速区', '国际区', '成长区']; + +export const ZONE_COLORS = { 加速区: '#4c8dff', 国际区: '#22d3ee', 成长区: '#34d399' }; + +/* ---------- 工具 ---------- */ +const nowTime = () => new Date().toLocaleTimeString('zh-CN', { hour12: false }); +const pick = (arr) => arr[Math.floor(Math.random() * arr.length)]; + +const TOOL_NAMES = ['文档生成', '数据查询', '图像创作', '代码执行', '语音合成']; +const MODEL_NAMES = ['DeepSeek-V3', '通义千问', '智谱 GLM-4', '豆包', '讯飞星火']; +const COMPANY_NAMES = COMPANIES.map((c) => c.name); + +function genEvent() { + const r = Math.random; + const pool = [ + { icon: 'bolt', text: `AI 推理任务完成 · 消耗 ${Math.round(800 + r() * 9000).toLocaleString()} tokens(${pick(MODEL_NAMES)})` }, + { icon: 'wrench', text: `工具「${pick(TOOL_NAMES)}」被调用 ${Math.round(10 + r() * 90)} 次` }, + { icon: 'building', text: `「${pick(COMPANY_NAMES)}」提交入驻申请 · 进入评审流程` }, + { icon: 'users', text: `「${pick(COMPANY_NAMES)}」新增招聘岗位 ${Math.round(1 + r() * 5)} 个` }, + { icon: 'coin', text: `园区企业完成一笔 ¥${(0.5 + r() * 9).toFixed(1)}万 交易` }, + { icon: 'robot', text: `「${pick(MODEL_NAMES)}」模型完成一次微调任务` }, + ]; + const e = pick(pool); + return { id: Date.now() + Math.random(), icon: e.icon, text: e.text, time: nowTime() }; +} + +function initFeed() { + return [ + { id: 1, icon: 'bolt', text: '云南派音AI 完成音频向量嵌入任务 · 消耗 12,480 tokens', time: nowTime() }, + { id: 2, icon: 'building', text: '「启元人工智能科技」通过评审 · 正式入驻 OPC 创业空间', time: nowTime() }, + { id: 3, icon: 'users', text: '「昆明舒护安养老服务」新增招聘岗位 2 个', time: nowTime() }, + { id: 4, icon: 'coin', text: '园区企业完成一笔 ¥3.6万 交易', time: nowTime() }, + ]; +} + +export default function ParkSidePanel() { + const [feed, setFeed] = useState(initFeed); + const [activeZone, setActiveZone] = useState('加速区'); + const [selected, setSelected] = useState(null); + + // 实时动态滚动 + useEffect(() => { + const iv = setInterval(() => { + setFeed((prev) => [genEvent(), ...prev].slice(0, 6)); + }, 2600); + return () => clearInterval(iv); + }, []); + + const list = COMPANIES.filter((c) => c.zone === activeZone); + + return ( + + ); +} diff --git a/src/pages/AiAssistant.jsx b/src/pages/AiAssistant.jsx new file mode 100644 index 0000000..a76fdf5 --- /dev/null +++ b/src/pages/AiAssistant.jsx @@ -0,0 +1,23 @@ +import '../styles/datascreen.css'; +import AiChatPanel from '../components/AiChatPanel'; +import ParkSidePanel from '../components/ParkSidePanel'; + +/* ========================================================= + AI 智能助手 · 独立页面(/ai) + 布局沿用数字孪生页接口:左侧为 AI 对话区,右侧为企业分布面板 + 页眉由 ScreenLayout 统一注入,左右方向键切换页面 + ========================================================= */ + +export default function AiAssistant() { + return ( +
+
+ {/* 左侧:AI 智能助手对话区(替代 3D 模型展示区) */} + + + {/* 右侧:企业分布面板(与数字孪生页一致) */} + +
+
+ ); +} diff --git a/src/pages/DigitalTwin.jsx b/src/pages/DigitalTwin.jsx index 6a89c28..5250c0d 100644 --- a/src/pages/DigitalTwin.jsx +++ b/src/pages/DigitalTwin.jsx @@ -1,63 +1,17 @@ -import { useState, useEffect, lazy, Suspense } from 'react'; +import { lazy, Suspense } from 'react'; import '../styles/datascreen.css'; -import Icon from '../components/Icons'; +import ParkSidePanel from '../components/ParkSidePanel'; /* 3D 园区建筑懒加载 */ const Building3D = lazy(() => import('../components/Building3D')); /* ========================================================= 园区数字孪生 · 独立页面(/twin) - 全屏 3D 建筑模型 + 实时动态 + 企业分布 + 全屏 3D 建筑模型 + 右侧企业分布面板(共享组件) 页眉由 ScreenLayout 统一注入,左右方向键切换页面 ========================================================= */ -/* ---------- 企业数据(名称 / 区域 / 房间号) ---------- */ -const COMPANIES = [ - { name: '云南派音人工智能科技', zone: '加速区', room: 'A1', color: '#4c8dff' }, - { name: '米勒克尔蓝宝石珠宝', zone: '加速区', room: 'A2', color: '#4c8dff' }, - { name: '中泰研学合作', zone: '加速区', room: 'A3', color: '#4c8dff' }, - { name: '云南宸中低空经济', zone: '加速区', room: 'A4', color: '#4c8dff' }, - { name: '昆明智海银高文化科技', zone: '加速区', room: 'A5', color: '#4c8dff' }, - { name: 'AI机器人大模型训练', zone: '加速区', room: 'A6', color: '#4c8dff' }, - { name: '云南廷秀文旅康养', zone: '加速区', room: 'A7', color: '#4c8dff' }, - { name: '瀚颖AI+教育信息咨询', zone: '加速区', room: 'A8', color: '#4c8dff' }, - { name: '云南大学AI+创业平台', zone: '加速区', room: 'A9', color: '#4c8dff' }, - { name: '仰光客厅', zone: '国际区', room: 'I1', color: '#22d3ee' }, - { name: '云南上古绝学文化', zone: '国际区', room: 'I2', color: '#22d3ee' }, - { name: '中越生物医疗', zone: '国际区', room: 'I3', color: '#22d3ee' }, - { name: '酷享野农AI农业', zone: '国际区', room: 'I4', color: '#22d3ee' }, - { name: '滇缅国际设计', zone: '国际区', room: 'I5', color: '#22d3ee' }, - { name: '昆明舒诺生物科技', zone: '国际区', room: 'I6', color: '#22d3ee' }, - { name: '达岸教育管理', zone: '国际区', room: 'I7', color: '#22d3ee' }, - { name: 'Facebook越南跨境电商', zone: '成长区', room: 'G1', color: '#34d399' }, - { name: '研X同行者网络', zone: '成长区', room: 'G2', color: '#34d399' }, - { name: '朵哈·玫瑰特色产业链', zone: '成长区', room: 'G3', color: '#34d399' }, - { name: '昆明云韵体育', zone: '成长区', room: 'G4', color: '#34d399' }, - { name: '南菌优培食用菌', zone: '成长区', room: 'G5', color: '#34d399' }, - { name: '五华区丽裳文化', zone: '成长区', room: 'G6', color: '#34d399' }, - { name: '云南星瑞航空', zone: '成长区', room: 'G7', color: '#34d399' }, - { name: '综合直播私域平台', zone: '成长区', room: 'G8', color: '#34d399' }, - { name: '园区管理办公室', zone: '园区管理', room: 'M', color: '#fbbf24' }, -]; - -const ZONES = ['加速区', '国际区', '成长区']; - export default function DigitalTwin() { - const [feed, setFeed] = useState(initFeed()); - const [activeZone, setActiveZone] = useState('加速区'); - const [selected, setSelected] = useState(null); - - // 实时动态滚动 - useEffect(() => { - const iv = setInterval(() => { - setFeed((prev) => [genEvent(), ...prev].slice(0, 6)); - }, 2600); - return () => clearInterval(iv); - }, []); - - const list = COMPANIES.filter((c) => c.zone === activeZone); - const zoneColors = { 加速区: '#4c8dff', 国际区: '#22d3ee', 成长区: '#34d399' }; - return (
{/* 主体:3D 为主 + 侧边栏(页眉由 ScreenLayout 统一注入) */} @@ -76,101 +30,9 @@ export default function DigitalTwin() {
- {/* 右侧企业面板 */} - + {/* 右侧企业分布面板(共享组件,与 AI 助手页一致) */} + ); } - -/* ---------- 工具 ---------- */ - -const nowTime = () => new Date().toLocaleTimeString('zh-CN', { hour12: false }); -const pick = (arr) => arr[Math.floor(Math.random() * arr.length)]; - -const TOOL_NAMES = ['文档生成', '数据查询', '图像创作', '代码执行', '语音合成']; -const MODEL_NAMES = ['DeepSeek-V3', '通义千问', '智谱 GLM-4', '豆包', '讯飞星火']; -const COMPANY_NAMES = COMPANIES.map((c) => c.name); - -function genEvent() { - const r = Math.random; - const pool = [ - { icon: 'bolt', text: `AI 推理任务完成 · 消耗 ${Math.round(800 + r() * 9000).toLocaleString()} tokens(${pick(MODEL_NAMES)})` }, - { icon: 'wrench', text: `工具「${pick(TOOL_NAMES)}」被调用 ${Math.round(10 + r() * 90)} 次` }, - { icon: 'building', text: `「${pick(COMPANY_NAMES)}」提交入驻申请 · 进入评审流程` }, - { icon: 'users', text: `「${pick(COMPANY_NAMES)}」新增招聘岗位 ${Math.round(1 + r() * 5)} 个` }, - { icon: 'coin', text: `园区企业完成一笔 ¥${(0.5 + r() * 9).toFixed(1)}万 交易` }, - { icon: 'robot', text: `「${pick(MODEL_NAMES)}」模型完成一次微调任务` }, - ]; - const e = pick(pool); - return { id: Date.now() + Math.random(), icon: e.icon, text: e.text, time: nowTime() }; -} - -function initFeed() { - return [ - { id: 1, icon: 'bolt', text: '云南派音AI 完成音频向量嵌入任务 · 消耗 12,480 tokens', time: nowTime() }, - { id: 2, icon: 'building', text: '「启元人工智能科技」通过评审 · 正式入驻 OPC 创业空间', time: nowTime() }, - { id: 3, icon: 'users', text: '「昆明舒护安养老服务」新增招聘岗位 2 个', time: nowTime() }, - { id: 4, icon: 'coin', text: '园区企业完成一笔 ¥3.6万 交易', time: nowTime() }, - ]; -} diff --git a/src/styles/datascreen.css b/src/styles/datascreen.css index c1c2b10..c9d1afc 100644 --- a/src/styles/datascreen.css +++ b/src/styles/datascreen.css @@ -1751,6 +1751,246 @@ overflow-y: auto; } +/* ============ AI 智能助手对话区(/ai 左侧) ============ */ +.bd-chat { + position: relative; + min-width: 0; + border-radius: 16px; + overflow: hidden; + display: flex; + flex-direction: column; + background: linear-gradient(180deg, rgba(255,255,255,0.4), rgba(255,255,255,0.15)); + border: 1px solid rgba(120, 160, 220, 0.14); +} +.bd-chat-head { + flex-shrink: 0; + display: flex; + align-items: center; + gap: 10px; + padding: 12px 16px; + background: rgba(255,255,255,0.6); + border-bottom: 1px solid rgba(120,160,220,0.14); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} +.bd-chat-logo { + width: 34px; + height: 34px; + flex-shrink: 0; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + color: #fff; + background: linear-gradient(140deg, var(--bd-blue), var(--bd-cyan)); + box-shadow: 0 4px 12px rgba(47,107,255,0.28); +} +.bd-chat-titles { + min-width: 0; + flex: 1; +} +.bd-chat-titles b { + display: block; + font-size: 14px; + color: var(--bd-ink); + letter-spacing: 0.02em; +} +.bd-chat-titles span { + display: block; + margin-top: 1px; + font-size: 10.5px; + color: var(--bd-sub); +} +.bd-chat-model { + display: inline-flex; + align-items: center; + gap: 6px; + font-size: 10.5px; + font-weight: 700; + color: var(--bd-green); + padding: 4px 10px; + border-radius: 20px; + background: rgba(23,178,106,0.08); + border: 1px solid rgba(23,178,106,0.22); + white-space: nowrap; +} +.bd-chat-model i { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--bd-green); + box-shadow: 0 0 6px rgba(23,178,106,0.8); +} +.bd-chat-clear { + width: 28px; + height: 28px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + border: 1px solid rgba(120,160,220,0.2); + border-radius: 8px; + background: rgba(255,255,255,0.7); + color: var(--bd-sub); + cursor: pointer; + transition: all 0.2s; +} +.bd-chat-clear:hover { + color: var(--bd-red); + border-color: rgba(240,68,56,0.35); + background: rgba(240,68,56,0.06); +} +.bd-chat-msgs { + flex: 1; + min-height: 0; + overflow-y: auto; + padding: 16px 18px; + display: flex; + flex-direction: column; + gap: 12px; +} +.bd-msg { + display: flex; + gap: 9px; + max-width: 86%; +} +.bd-msg.ai { + align-self: flex-start; +} +.bd-msg.me { + align-self: flex-end; + flex-direction: row-reverse; +} +.bd-msg-avatar { + width: 26px; + height: 26px; + flex-shrink: 0; + border-radius: 8px; + display: flex; + align-items: center; + justify-content: center; + color: #fff; + background: linear-gradient(140deg, var(--bd-blue), var(--bd-cyan)); + box-shadow: 0 3px 8px rgba(47,107,255,0.22); +} +.bd-msg-bubble { + padding: 9px 13px; + font-size: 12.5px; + line-height: 1.65; + border-radius: 12px; + color: #33415c; + background: rgba(255,255,255,0.85); + border: 1px solid rgba(120,160,220,0.16); + box-shadow: 0 3px 10px rgba(56,100,170,0.06); + white-space: pre-line; + word-break: break-word; +} +.bd-msg.me .bd-msg-bubble { + color: #fff; + background: linear-gradient(140deg, var(--bd-blue), var(--bd-blue-deep)); + border-color: transparent; + box-shadow: 0 4px 14px rgba(47,107,255,0.25); +} +.bd-typing { + display: inline-flex; + align-items: center; + gap: 4px; + padding: 12px 14px; +} +.bd-typing i { + width: 5px; + height: 5px; + border-radius: 50%; + background: #9fb0c6; + animation: bdTyping 1.2s ease-in-out infinite; +} +.bd-typing i:nth-child(2) { animation-delay: 0.15s; } +.bd-typing i:nth-child(3) { animation-delay: 0.3s; } +@keyframes bdTyping { + 0%, 60%, 100% { opacity: 0.35; transform: translateY(0); } + 30% { opacity: 1; transform: translateY(-3px); } +} +.bd-chat-quick { + flex-shrink: 0; + display: flex; + flex-wrap: wrap; + gap: 8px; + padding: 0 16px 10px; +} +.bd-chat-quick button { + display: inline-flex; + align-items: center; + gap: 5px; + padding: 5px 11px; + font-size: 11px; + font-weight: 600; + color: var(--bd-blue); + background: rgba(47,107,255,0.07); + border: 1px solid rgba(47,107,255,0.16); + border-radius: 20px; + cursor: pointer; + transition: all 0.2s; +} +.bd-chat-quick button:hover { + background: rgba(47,107,255,0.14); + transform: translateY(-1px); +} +.bd-chat-input { + flex-shrink: 0; + display: flex; + align-items: center; + gap: 10px; + margin: 0 16px 16px; + padding: 8px 8px 8px 14px; + border-radius: 12px; + background: rgba(255,255,255,0.85); + border: 1px solid rgba(120,160,220,0.22); + box-shadow: 0 4px 16px rgba(56,100,170,0.08); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); +} +.bd-chat-input input { + flex: 1; + min-width: 0; + border: none; + outline: none; + background: transparent; + font-size: 12.5px; + color: var(--bd-ink); + font-family: inherit; +} +.bd-chat-input input::placeholder { + color: #9fb0c6; +} +.bd-chat-send { + width: 32px; + height: 32px; + flex-shrink: 0; + display: inline-flex; + align-items: center; + justify-content: center; + border: none; + border-radius: 9px; + color: #fff; + cursor: pointer; + background: linear-gradient(140deg, var(--bd-blue), var(--bd-cyan)); + box-shadow: 0 4px 12px rgba(47,107,255,0.3); + transition: all 0.2s; +} +.bd-chat-send:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(47,107,255,0.38); +} +.bd-chat-send:disabled { + opacity: 0.45; + cursor: not-allowed; + box-shadow: none; +} +/* 移动端/窄屏:消息宽度放开 */ +@media (max-width: 1100px) { + .bd-msg { max-width: 94%; } +} + /* 页眉左右切换按钮(← 左页 · 右页 →,可点击) */ .bd-nav-keys { display: inline-flex; diff --git a/src/utils/pageNav.js b/src/utils/pageNav.js index d1bf049..8cc5f8c 100644 --- a/src/utils/pageNav.js +++ b/src/utils/pageNav.js @@ -3,7 +3,7 @@ import { useNavigate, useLocation } from 'react-router-dom'; /* ========================================================= 页面循环导航 —— 左右方向键切换大屏页面 - 顺序:数据大屏 → 数字孪生 → 媒体轮播(循环) + 顺序:数据大屏 → 数字孪生 → AI 助手 → 媒体轮播(循环) 管理后台不参与循环切换,仅在登录后进入 ← 向左切换 · → 向右切换 ========================================================= */ @@ -11,12 +11,14 @@ import { useNavigate, useLocation } from 'react-router-dom'; export const PAGE_ORDER = [ { path: '/', label: '数据大屏' }, { path: '/twin', label: '数字孪生' }, + { path: '/ai', label: 'AI 助手' }, { path: '/screen', label: '媒体轮播' }, ]; /** * 左右方向键循环切换页面。 * 在任意页面使用;当前路径不在 PAGE_ORDER 中时不响应。 + * 输入框/文本域内按键不触发页面切换(避免影响输入)。 */ export function usePageNav() { const navigate = useNavigate(); @@ -24,6 +26,8 @@ export function usePageNav() { useEffect(() => { const onKey = (e) => { + const t = e.target; + if (t && (t.tagName === 'INPUT' || t.tagName === 'TEXTAREA' || t.isContentEditable)) return; const idx = PAGE_ORDER.findIndex((p) => p.path === location.pathname); if (idx === -1) return; const n = PAGE_ORDER.length;