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>
<router-view></router-view>
<speechControl></speechControl>
</template>
<script setup>
import { onMounted } from "vue";
import speechControl from "./view/components/speechControl.vue";
onMounted(() => {

View File

View File

@ -59,30 +59,16 @@
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
const props = defineProps({
config: {
type: Object,
default: () => {},
},
});
watch(config, (newVal, oldVal) => {
if (voiceControl.value && voiceControl.value.isListening) {
voiceControl.value.stopListening();
}
initVoiceControl();
});
import { ref, onMounted, watch } from "vue";
const config = {
page: "Ecommerce Home",
commands: [
{
command: "open_login",
description: "打开登录弹窗或页面",
command: "add_project",
description: "新建项目",
action: "click",
selector: "#login-button, .login-btn, [data-login]",
selector: "#ai-speech-add-project",
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 {
constructor(callback) {
this.config = config;
@ -232,6 +232,8 @@ class VoiceControl {
const sequence = await this.queryDeepSeek(transcript);
this.showStatus("指令执行完成", "success");
this.executeSequence(sequence);
if (sequence && sequence.sequence && sequence.sequence.length > 0) {
this.callback(sequence.sequence);
} 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
updateUI() {
const voiceBtn = document.getElementById("voice-btn");
@ -416,7 +504,7 @@ const voiceControl = ref(null);
const action = ref([]);
const initVoiceControl = () => {
voiceControl.value = new VoiceControl(this.callBackFun);
voiceControl.value = new VoiceControl(callBackFun);
};
const callBackFun = (data) => {
@ -543,7 +631,7 @@ p {
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
color: #00000085;
color: #ffffff85;
opacity: 0;
transition: opacity 0.3s;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
@ -573,11 +661,11 @@ p {
position: fixed;
right: 160px;
bottom: 30px;
background-color: rgba(0, 0, 0, 0.15);
background-color: rgba(255, 255, 255, 0.15);
backdrop-filter: blur(10px);
padding: 12px 20px;
border-radius: 12px;
color: #00000080;
color: #fff;
max-width: 300px;
opacity: 0;
transform: translateX(-20px);
@ -594,6 +682,7 @@ p {
.command-text {
font-size: 16px;
margin-bottom: 5px;
color: #fff !important;
}
.command-action {

View File

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