fix/首页样式优化

This commit is contained in:
季万俊 2025-08-26 17:16:28 +08:00
parent dbf7162405
commit 6482d2a4ee
4 changed files with 349 additions and 122 deletions

View File

@ -5,10 +5,12 @@
--> -->
<template> <template>
<router-view></router-view> <router-view></router-view>
<speechControl></speechControl>
</template> </template>
<script setup> <script setup>
import { onMounted } from "vue"; import { onMounted } from "vue";
import speechControl from "./view/components/speechControl.vue";
onMounted(() => { onMounted(() => {

View File

View File

@ -59,30 +59,16 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { ref, onMounted } from "vue"; import { ref, onMounted, watch } from "vue";
const props = defineProps({
config: {
type: Object,
default: () => {},
},
});
watch(config, (newVal, oldVal) => {
if (voiceControl.value && voiceControl.value.isListening) {
voiceControl.value.stopListening();
}
initVoiceControl();
});
const config = { const config = {
page: "Ecommerce Home", page: "Ecommerce Home",
commands: [ commands: [
{ {
command: "open_login", command: "add_project",
description: "打开登录弹窗或页面", description: "新建项目",
action: "click", action: "click",
selector: "#login-button, .login-btn, [data-login]", selector: "#ai-speech-add-project",
params: [], params: [],
}, },
{ {
@ -148,6 +134,20 @@ const config = {
], ],
}; };
const props = defineProps({
config: {
type: Object,
default: () => {},
},
});
watch(config, (newVal, oldVal) => {
if (voiceControl.value && voiceControl.value.isListening) {
voiceControl.value.stopListening();
}
initVoiceControl();
});
class VoiceControl { class VoiceControl {
constructor(callback) { constructor(callback) {
this.config = config; this.config = config;
@ -232,6 +232,8 @@ class VoiceControl {
const sequence = await this.queryDeepSeek(transcript); const sequence = await this.queryDeepSeek(transcript);
this.showStatus("指令执行完成", "success"); this.showStatus("指令执行完成", "success");
this.executeSequence(sequence);
if (sequence && sequence.sequence && sequence.sequence.length > 0) { if (sequence && sequence.sequence && sequence.sequence.length > 0) {
this.callback(sequence.sequence); this.callback(sequence.sequence);
} else { } else {
@ -380,6 +382,92 @@ class VoiceControl {
} }
} }
async executeSequence(sequence) {
for (const [index, instruction] of sequence.sequence.entries()) {
try {
await this.executeInstruction(instruction);
//
if (index < sequence.sequence.length - 1) {
await this.delay(800);
}
} catch (error) {
throw error;
}
}
}
async executeInstruction(instruction) {
const commandConfig = this.config.commands.find(
(c) => c.command === instruction.command
);
if (!commandConfig) {
throw new Error(`未知指令: ${instruction.command}`);
}
const element = document.querySelector(commandConfig.selector);
if (!element) {
throw new Error(`找不到元素: ${commandConfig.selector}`);
}
//
element.scrollIntoView({ behavior: "smooth", block: "center" });
switch (commandConfig.action) {
case "click":
element.click();
break;
case "input":
const inputParam = commandConfig.params[0];
if (instruction.params && instruction.params[inputParam.name]) {
element.value = instruction.params[inputParam.name];
element.dispatchEvent(new Event("input", { bubbles: true }));
}
break;
case "navigate":
if (instruction.params && instruction.params.product_url) {
window.location.href = instruction.params.product_url;
}
break;
case "input_and_submit":
if (instruction.params && instruction.params.keyword) {
element.value = instruction.params.keyword;
element.dispatchEvent(new Event("input", { bubbles: true }));
//
if (element.form) {
element.form.submit();
} else {
//
const submitBtn = document.querySelector(
'#search-submit, [type="submit"]'
);
if (submitBtn) submitBtn.click();
}
}
break;
default:
throw new Error(`未知动作类型: ${commandConfig.action}`);
}
//
this.highlightElement(element);
}
highlightElement(element) {
const originalStyle = element.style.boxShadow;
// 15px4px0.4
element.style.boxShadow = "0 0 12px 5px rgba(173, 216, 230, 0.6)";
setTimeout(() => {
element.style.boxShadow = originalStyle;
}, 1000);
}
// UI // UI
updateUI() { updateUI() {
const voiceBtn = document.getElementById("voice-btn"); const voiceBtn = document.getElementById("voice-btn");
@ -416,7 +504,7 @@ const voiceControl = ref(null);
const action = ref([]); const action = ref([]);
const initVoiceControl = () => { const initVoiceControl = () => {
voiceControl.value = new VoiceControl(this.callBackFun); voiceControl.value = new VoiceControl(callBackFun);
}; };
const callBackFun = (data) => { const callBackFun = (data) => {
@ -543,7 +631,7 @@ p {
padding: 8px 16px; padding: 8px 16px;
border-radius: 20px; border-radius: 20px;
font-size: 14px; font-size: 14px;
color: #00000085; color: #ffffff85;
opacity: 0; opacity: 0;
transition: opacity 0.3s; transition: opacity 0.3s;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
@ -573,11 +661,11 @@ p {
position: fixed; position: fixed;
right: 160px; right: 160px;
bottom: 30px; bottom: 30px;
background-color: rgba(0, 0, 0, 0.15); background-color: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px); backdrop-filter: blur(10px);
padding: 12px 20px; padding: 12px 20px;
border-radius: 12px; border-radius: 12px;
color: #00000080; color: #fff;
max-width: 300px; max-width: 300px;
opacity: 0; opacity: 0;
transform: translateX(-20px); transform: translateX(-20px);
@ -594,6 +682,7 @@ p {
.command-text { .command-text {
font-size: 16px; font-size: 16px;
margin-bottom: 5px; margin-bottom: 5px;
color: #fff !important;
} }
.command-action { .command-action {

View File

@ -2,97 +2,120 @@
<div class="app-container"> <div class="app-container">
<!-- 左侧导航栏 --> <!-- 左侧导航栏 -->
<aside class="sidebar"> <aside class="sidebar">
<!-- 用户信息 --> <div>
<div class="user-section"> <!-- 用户信息 -->
<div class="avatar"></div> <div class="user-section">
<div> <div class="avatar">
<div class="user-id">1735244688</div> <img src="./../assets/default-photo.jpeg" alt="" />
<div class="user-label">用户ID</div> </div>
<div class="user-info">
<div class="user-id">15586961409</div>
<div class="user-label">网络公开版</div>
</div>
</div> </div>
<div class="split-line"></div>
<!-- 导航菜单 -->
<nav class="nav-menu">
<ul>
<template v-for="(item, index) in menu">
<li
@click="selectMenu(item)"
:class="item.active ? 'active' : ''"
>
<el-icon :size="20"><component :is="item.icon" /></el-icon>
<span>{{ item.name }}</span>
</li>
<div v-if="index == 2" class="split-line"></div>
</template>
</ul>
</nav>
</div> </div>
<div class="line"></div>
<!-- 导航菜单 -->
<nav class="nav-menu">
<ul>
<li
@click="selectMenu(item)"
v-for="item in menu"
:class="item.active ? 'active' : ''"
>
<el-icon :size="20"><component :is="item.icon" /></el-icon>
<span>{{ item.name }}</span>
</li>
</ul>
</nav>
<!-- 广告卡片 --> <!-- 广告卡片 -->
<div class="ad-card"> <el-card header-class="card-header" body-class="card-body">
<div class="ad-img"></div> <div class="card-content">
<div class="ad-title">大幅面优化 倾斜摄影卡顿</div> <img src="./../assets/bg-1.png" style="width: 100%" />
<button class="ad-btn">前往下载</button> </div>
</div> </el-card>
<!-- 底部链接 -->
<div class="footer-links">
<a href="#">SDK文档</a>
<a href="#">视频教程</a>
<a href="#">人工客服</a>
<a href="#">建议反馈</a>
</div>
</aside> </aside>
<!-- 主内容区 --> <!-- 主内容区 -->
<main class="main-content"> <main class="main-content">
<!-- 顶部横幅 --> <!-- 顶部横幅 -->
<section class="header-banner"> <section class="header-banner">
<div class="banner-content"> <el-carousel height="300px" motion-blur :interval="10000">
<h1>可视化</h1> <el-carousel-item v-for="item in 2" :key="item">
<p>零代码数字孪生可视化平台</p> <div class="banner-content">
<div class="tag-group"> <div class="banner-content-info">
<span>可视化大屏</span> <h1>可视化平台</h1>
<span>三维地图</span> <p>零代码数字孪生可视化平台</p>
<span>GIS</span> <div class="tag-group">
<span>数字孪生</span> <span>可视化大屏</span>
</div> <span class="splitdot">·</span>
</div> <span>三维地图</span>
<span class="splitdot">·</span>
<span>GIS</span>
<span class="splitdot">·</span>
<span>数字孪生</span>
</div>
</div>
</div>
</el-carousel-item>
</el-carousel>
</section> </section>
<!-- 项目操作栏 --> <!-- 项目操作栏 -->
<section class="project-actions"> <section class="project-actions">
<div class="action-buttons"> <div class="action-buttons">
<button>新建</button> <button id="ai-speech-add-project" @click="addProject">
<button>新建文件夹</button> <el-icon :size="16"><DocumentAdd /></el-icon><span></span>
<button>导入项目</button> </button>
</div> <button>
<div class="tab-group"> <el-icon :size="16"><FolderAdd /></el-icon><span></span>
<span class="active">本地项目</span> </button>
<span>云托管项目</span> <button>
<el-icon :size="16"><UploadFilled /></el-icon><span></span>
</button>
</div> </div>
<el-tabs
v-model="tabName"
class="demo-tabs"
@tab-click="handleClick"
style="margin: 20px 0 8px 0"
>
<el-tab-pane label="本地项目" name="local"></el-tab-pane>
<el-tab-pane label="云托管项目" name="cloud"></el-tab-pane>
</el-tabs>
<div class="search-box"> <div class="search-box">
<input type="text" placeholder="搜索项目" /> <el-input
v-model="searchValue"
style="width: 260px"
class="responsive-input"
placeholder="搜索项目"
prefix-icon="Search"
/>
</div> </div>
</section> </section>
<!-- 项目列表 --> <!-- 项目列表 -->
<section class="project-list"> <section class="project-list">
<div class="project-card"> <div class="project-card" v-for="item in projects">
<div class="card-img"></div> <div class="project-item-info">
<div class="card-title">我的驾驶大屏示例</div> <div class="card-title">{{ item.name }}</div>
<div class="card-actions"> <div class="card-actions">
<span>编辑</span> <span><el-icon :size="18"><Promotion /></el-icon></span>
<span>复制</span> <span><el-icon :size="18"><Delete /></el-icon></span>
<span>删除</span> <span><el-icon :size="18"><Share /></el-icon></span>
<span><el-icon><MoreFilled /></el-icon></span>
</div>
</div> </div>
</div>
<div class="project-card">
<div class="card-img"></div> <div class="card-img"></div>
<div class="card-title">我的智慧城市3D场景</div>
<div class="card-actions">
<span>编辑</span>
<span>复制</span>
<span>删除</span>
</div>
</div> </div>
</section> </section>
</main> </main>
</div> </div>
@ -100,7 +123,6 @@
<script setup > <script setup >
import { ref } from "vue"; import { ref } from "vue";
const activeValue = ref(1);
const menu = ref([ const menu = ref([
{ {
name: "我的项目", name: "我的项目",
@ -133,6 +155,15 @@ const menu = ref([
active: false, active: false,
}, },
]); ]);
const searchValue = ref("");
const tabName = ref("local");
const projects = ref([
{
name: '苏州站基础大屏示例',
img: ''
}
])
const selectMenu = (data) => { const selectMenu = (data) => {
menu.value.forEach((d) => { menu.value.forEach((d) => {
@ -143,6 +174,15 @@ const selectMenu = (data) => {
} }
}); });
}; };
const handleClick = (tab, event) => {
console.log(tab, event);
};
const addProject = () => {
console.log("正在点击新建按钮 ======> addProject ");
}
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
@ -161,30 +201,45 @@ const selectMenu = (data) => {
padding: 12px; padding: 12px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between;
background-color: #181818; background-color: #181818;
justify-content: space-between;
} }
/* 用户信息区 */ /* 用户信息区 */
.user-section { .user-section {
text-align: center; text-align: center;
margin-bottom: 20px; margin-top: 20px;
display: flex; display: flex;
justify-content: space-around;
} }
.avatar { .avatar {
width: 60px; width: 60px;
height: 60px; height: 60px;
background: #444; background: #444;
border-radius: 50%; border-radius: 50%;
margin: 0 auto 8px; overflow: hidden;
img {
width: 100%;
height: 100%;
}
} }
.user-id { .user-id {
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: 20px;
}
.user-info {
height: 60px;
display: flex;
flex-direction: column;
justify-content: space-around;
} }
.user-label { .user-label {
font-size: 12px; font-size: 12px;
color: #999; color: #fff;
background: rgba(255, 255, 255, 0.2);
border-radius: 2px;
line-height: 20px;
} }
/* 导航菜单 */ /* 导航菜单 */
@ -201,7 +256,7 @@ const selectMenu = (data) => {
margin: 4px 0; margin: 4px 0;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
color: #ffffff99; color: #f8f8ff99;
.el-icon { .el-icon {
margin-right: 6px; margin-right: 6px;
line-height: 20px; line-height: 20px;
@ -211,7 +266,7 @@ const selectMenu = (data) => {
background: rgba(5, 85, 158, 0.4); background: rgba(5, 85, 158, 0.4);
} }
.nav-menu li.active { .nav-menu li.active {
background: rgb(5, 85, 158); background: #0078d4;
font-weight: 500; font-weight: 500;
} }
.divider { .divider {
@ -230,11 +285,15 @@ const selectMenu = (data) => {
text-align: center; text-align: center;
} }
.ad-img { .ad-img {
width: 80px; width: 80%;
height: 80px; height: 80px;
background: #444; background: #444;
border-radius: 4px; border-radius: 4px;
margin: 0 auto 12px; margin: 0 auto 12px;
img {
width: 100%;
height: 100%;
}
} }
.ad-title { .ad-title {
font-size: 14px; font-size: 14px;
@ -281,30 +340,35 @@ const selectMenu = (data) => {
height: 300px; height: 300px;
position: relative; position: relative;
text-align: center; text-align: center;
margin-bottom: 24px;
background-image: url("@/assets/banner.png");
background-size: cover;
background-repeat: no-repeat;
background-position: center center;
&::before {
content: "";
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
position: absolute;
z-index: 9;
}
h1 { h1 {
margin: 0; margin: 0;
} }
.banner-content { .banner-content {
position: absolute; position: relative;
z-index: 20; height: 100%;
left: 50%; width: 100%;
top: 50%; background-image: url("@/assets/banner.png");
transform: translate3d(-50%, -50%, 0); background-size: cover;
background-repeat: no-repeat;
background-position: center center;
&::before {
content: "";
left: 0;
right: 0;
top: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
position: absolute;
z-index: 9;
}
&-info {
position: absolute;
left: 50%;
top: 50%;
transform: translate3d(-50%, -50%, 0);
z-index: 200;
}
} }
} }
.header-banner h1 { .header-banner h1 {
@ -345,10 +409,13 @@ const selectMenu = (data) => {
/* 项目操作栏 */ /* 项目操作栏 */
.project-actions { .project-actions {
display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 24px; padding: 16px;
position: relative;
}
.action-buttons {
display: flex;
} }
.action-buttons button { .action-buttons button {
background: rgb(26, 27, 29); background: rgb(26, 27, 29);
@ -359,9 +426,14 @@ const selectMenu = (data) => {
color: #fff; color: #fff;
margin-right: 8px; margin-right: 8px;
transition: background 0.3s; transition: background 0.3s;
display: flex;
.el-icon {
margin-right: 4px;
}
} }
.action-buttons button:hover { .action-buttons button:hover {
background: #383838; background: #0078d4;
border: 1px solid #0078d4;
} }
.tab-group span { .tab-group span {
margin: 0 12px; margin: 0 12px;
@ -375,6 +447,11 @@ const selectMenu = (data) => {
color: #0078d4; color: #0078d4;
font-weight: 500; font-weight: 500;
} }
.search-box {
position: absolute;
right: 20px;
top: 60px;
}
.search-box input { .search-box input {
background: rgb(26, 27, 29); background: rgb(26, 27, 29);
border: 1px solid #444; border: 1px solid #444;
@ -392,21 +469,25 @@ const selectMenu = (data) => {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 24px; gap: 24px;
padding: 16px;
padding-top: 0;
} }
.project-card { .project-card {
width: 240px; width: 300px;
height: 220px;
background: rgb(26, 27, 29); background: rgb(26, 27, 29);
border-radius: 8px; border-radius: 8px;
padding: 16px; padding: 18px;
text-align: center; text-align: center;
transition: transform 0.3s; transition: transform 0.3s;
cursor: pointer;
} }
.project-card:hover { .project-card:hover {
transform: translateY(-5px); transform: translateY(-5px);
} }
.card-img { .card-img {
width: 100%; width: 100%;
height: 120px; height: 170px;
background: #444; background: #444;
border-radius: 4px; border-radius: 4px;
margin-bottom: 12px; margin-bottom: 12px;
@ -431,4 +512,59 @@ const selectMenu = (data) => {
width: 160px; width: 160px;
background: rgb(15, 15, 15); background: rgb(15, 15, 15);
} }
.splitdot {
margin: 0 20px !important;
display: inline-block;
}
.split-line {
height: 1px;
width: 160px;
background: rgba(0, 0, 0, 0.7);
margin: 30px auto;
}
.demo-tabs > .el-tabs__content {
padding: 32px;
color: #6b778c;
font-size: 32px;
font-weight: 600;
}
::v-deep {
.el-tabs__item {
color: #ffffff80;
}
.el-tabs__item.is-active {
color: #fff !important;
}
.el-tabs__nav-wrap::after {
background-color: rgba(255, 255, 255, 0.1);
}
.el-input__wrapper {
background: rgba(0, 0, 0, 0.3);
box-shadow: none;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.card-body {
background: transparent;
padding: 0;
}
.el-card {
background: transparent;
border: 0;
}
}
.card-content {
width: 100%;
border-radius: 8px;
overflow: hidden;
height: 270px;
img {
height: 280px;
}
}
.project-item-info {
display: flex;
justify-content: space-between;
margin-bottom: 16px;
}
</style> </style>