修复bug
This commit is contained in:
@ -11,12 +11,22 @@
|
||||
<img src="/logo/bottom-logo.png" alt="" />
|
||||
</div>
|
||||
<div class="navigation-bottom">
|
||||
<span v-for="item in first">
|
||||
<span
|
||||
v-for="item in first"
|
||||
:key="item.name"
|
||||
@click="goto(item.path)"
|
||||
:class="{'special-color': isParentMatch(item)}"
|
||||
>
|
||||
{{item.name}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="navigation-bottom">
|
||||
<span v-for="item in two" @click="goto(item.path)">
|
||||
<span
|
||||
v-for="item in two"
|
||||
:key="item.name"
|
||||
@click="goto(item.path)"
|
||||
:class="{'special-color': isParentMatch(item)}"
|
||||
>
|
||||
{{item.name}}
|
||||
</span>
|
||||
</div>
|
||||
@ -30,91 +40,159 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "Footer",
|
||||
data() {
|
||||
return {
|
||||
first: [{
|
||||
name: 'Home'
|
||||
export default {
|
||||
name: "Footer",
|
||||
data() {
|
||||
return {
|
||||
first: [{
|
||||
name: 'Home',
|
||||
path: '/home/list',
|
||||
meta: {
|
||||
parent: 'Home',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AI Daily News',
|
||||
path: '/dailyNews',
|
||||
meta: {
|
||||
parent: 'DailyNews',
|
||||
},
|
||||
{
|
||||
name: 'AI Daily News'
|
||||
},
|
||||
{
|
||||
name: 'AI Hub'
|
||||
},
|
||||
{
|
||||
name: 'Learn'
|
||||
},
|
||||
{
|
||||
name: 'About Us'
|
||||
},
|
||||
{
|
||||
name: 'AI Hub',
|
||||
meta: {
|
||||
parent: 'Hub',
|
||||
}
|
||||
],
|
||||
two: [{
|
||||
name: 'Privacy Policy',
|
||||
path: '/privacy'
|
||||
},
|
||||
{
|
||||
name: 'Terms Of Service',
|
||||
path: '/service'
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Learn',
|
||||
meta: {
|
||||
parent: 'Learn',
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'About Us',
|
||||
path: '/about',
|
||||
meta: {
|
||||
parent: 'About',
|
||||
}
|
||||
}
|
||||
],
|
||||
two: [{
|
||||
name: 'Privacy Policy',
|
||||
path: '/privacy',
|
||||
meta: {
|
||||
parent: 'Privacy',
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Terms Of Service',
|
||||
path: '/service',
|
||||
meta: {
|
||||
parent: 'Service',
|
||||
}
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goto(path) {
|
||||
this.$router.push(path);
|
||||
},
|
||||
methods: {
|
||||
goto(path) {
|
||||
this.$router.push(path)
|
||||
/**
|
||||
* 判断当前导航项是否应该被选中
|
||||
* @param {Object} item 导航项
|
||||
* @returns {Boolean} 是否选中
|
||||
*/
|
||||
isParentMatch(item) {
|
||||
// 首先检查路径匹配逻辑,保持向后兼容性
|
||||
const pathMatch = item.path && item.path === this.$route.path;
|
||||
if (pathMatch) return true;
|
||||
|
||||
// 获取当前路由的meta信息
|
||||
const currentRouteMeta = this.$route.meta || {};
|
||||
const currentParent = currentRouteMeta.parent;
|
||||
|
||||
// 如果当前路由没有parent属性,直接返回false
|
||||
if (!currentParent) return false;
|
||||
|
||||
// 获取当前导航项的meta信息
|
||||
const navItemMeta = item.meta || {};
|
||||
const navItemParent = navItemMeta.parent || '';
|
||||
|
||||
// 根据parent属性的类型进行判断
|
||||
if (typeof currentParent === 'string') {
|
||||
// parent是字符串时,直接比较是否相等
|
||||
return currentParent === navItemParent;
|
||||
} else if (Array.isArray(currentParent)) {
|
||||
// parent是数组时,判断导航项的parent是否在数组中
|
||||
return currentParent.includes(navItemParent);
|
||||
}
|
||||
|
||||
// 其他情况返回false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#footer-container {
|
||||
width: 100%;
|
||||
#footer-container {
|
||||
width: 100%;
|
||||
height: $footerBarHeight;
|
||||
|
||||
.container {
|
||||
height: $footerBarHeight;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.container {
|
||||
height: $footerBarHeight;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
.left-container {
|
||||
@include flex-center;
|
||||
flex-direction: column;
|
||||
|
||||
span {
|
||||
font-size: $larg-font-size;
|
||||
color: $main-color;
|
||||
font-weight: bold;
|
||||
font-family: 'Poppins-Bold', serif;
|
||||
}
|
||||
}
|
||||
|
||||
.right-container {
|
||||
text-align: right;
|
||||
img {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-span {
|
||||
color: $grey-color;
|
||||
font-family: 'Poppins-Regular', serif;
|
||||
}
|
||||
|
||||
.navigation-bottom {
|
||||
margin-bottom: 14px;
|
||||
span {
|
||||
display: inline-block;
|
||||
font-family: 'Poppins-Medium', serif;
|
||||
cursor: pointer;
|
||||
margin-left: 30px;
|
||||
&:hover {
|
||||
color: #7B61FF;
|
||||
}
|
||||
}
|
||||
|
||||
.left-container {
|
||||
@include flex-center;
|
||||
flex-direction: column;
|
||||
|
||||
span {
|
||||
font-size: $larg-font-size;
|
||||
color: $main-color;
|
||||
font-weight: bold;
|
||||
font-family: 'Poppins-Bold', serif;
|
||||
}
|
||||
span:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.right-container {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.bottom-span {
|
||||
color: $grey-color;
|
||||
font-family: 'Poppins-Regular', serif;
|
||||
}
|
||||
|
||||
.navigation-bottom {
|
||||
span {
|
||||
display: inline-block;
|
||||
padding: 14px;
|
||||
font-family: 'Poppins-Medium', serif;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
span:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
.special-color {
|
||||
color: #7B61FF;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -8,15 +8,15 @@
|
||||
</div>
|
||||
<div class="flex">
|
||||
<div class="navigation-item pointer" v-for="item in navRoutes" :key="item.path"
|
||||
@click="handleParentClick(item)" @mouseenter="showSubmenu(item)" @mouseleave="hideSubmenu">
|
||||
@click="handleParentClick(item)" @mouseenter="showSubmenu(item)" @mouseleave="hideSubmenu">
|
||||
<span
|
||||
:class="{ 'selected-navigation': $route.matched.some(record => record.path === item.path) }">{{
|
||||
:class="{ 'selected-navigation': isParentMatch(item) }">{{
|
||||
item.meta.navigationName }}
|
||||
<i class="el-icon-arrow-down" v-if="item.meta.children"></i>
|
||||
<i class="el-icon-arrow-down" v-if="item.meta.children"></i>
|
||||
</span>
|
||||
<div v-if="activeMenu === item.path && item.meta.children" class="submenu">
|
||||
<div v-for="sub in item.children" :key="sub.path" @click.stop="goto(sub.path)"
|
||||
class="submenu-item pointer">
|
||||
class="submenu-item pointer">
|
||||
<img :src="`/logo/${sub.meta.icon}.png`" alt="" />
|
||||
{{ sub.name }}
|
||||
</div>
|
||||
@ -84,6 +84,39 @@ export default {
|
||||
hideSubmenu() {
|
||||
this.activeMenu = null;
|
||||
},
|
||||
/**
|
||||
* 判断当前导航项是否应该被选中
|
||||
* @param {Object} item 导航项
|
||||
* @returns {Boolean} 是否选中
|
||||
*/
|
||||
isParentMatch(item) {
|
||||
// 首先检查原始的路径匹配逻辑,保持向后兼容性
|
||||
const pathMatch = this.$route.matched.some(record => record.path === item.path);
|
||||
if (pathMatch) return true;
|
||||
|
||||
// 获取当前路由的meta信息
|
||||
const currentRouteMeta = this.$route.meta || {};
|
||||
const currentParent = currentRouteMeta.parent;
|
||||
|
||||
// 如果当前路由没有parent属性,直接返回false
|
||||
if (!currentParent) return false;
|
||||
|
||||
// 获取当前导航项的meta信息
|
||||
const navItemMeta = item.meta || {};
|
||||
const navItemParent = navItemMeta.parent || '';
|
||||
|
||||
// 根据parent属性的类型进行判断
|
||||
if (typeof currentParent === 'string') {
|
||||
// parent是字符串时,直接比较是否相等
|
||||
return currentParent === navItemParent;
|
||||
} else if (Array.isArray(currentParent)) {
|
||||
// parent是数组时,判断导航项的parent是否在数组中
|
||||
return currentParent.includes(navItemParent);
|
||||
}
|
||||
|
||||
// 其他情况返回false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@ -98,13 +131,13 @@ export default {
|
||||
.navigation-container {
|
||||
height: $navigationBarHeight;
|
||||
display: flex;
|
||||
font-weight: 500;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.logo {
|
||||
@include flex-center;
|
||||
color: $white;
|
||||
//font-family: 'Poppins-Bold', serif;
|
||||
|
||||
span {
|
||||
font-size: $normal-font-size;
|
||||
@ -115,6 +148,7 @@ export default {
|
||||
margin-left: 20px;
|
||||
padding: 10px;
|
||||
position: relative;
|
||||
//font-family: 'Poppins-Medium', serif;
|
||||
|
||||
span {
|
||||
color: $grey;
|
||||
@ -125,6 +159,8 @@ export default {
|
||||
|
||||
&.selected-navigation {
|
||||
color: $white;
|
||||
font-weight: 500;
|
||||
//font-family: 'Poppins-Bold', serif;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
28
components/MyTabs/MyTabPane.vue
Normal file
28
components/MyTabs/MyTabPane.vue
Normal file
@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div v-show="isActive" class="my-tab-pane">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MyTabPane',
|
||||
props: {
|
||||
label: String,
|
||||
name: [String, Number],
|
||||
disabled: Boolean,
|
||||
closable: Boolean
|
||||
},
|
||||
computed: {
|
||||
isActive() {
|
||||
return this.$parent.activeName === this.name
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$parent.registerPane(this)
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.$parent.unregisterPane(this)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
184
components/MyTabs/MyTabs.vue
Normal file
184
components/MyTabs/MyTabs.vue
Normal file
@ -0,0 +1,184 @@
|
||||
<template>
|
||||
<div class="my-tabs">
|
||||
<!-- 导航栏 -->
|
||||
<div class="my-tabs__nav">
|
||||
<div
|
||||
v-for="pane in panes"
|
||||
:key="pane.name"
|
||||
:class="{
|
||||
'my-tabs__item': true,
|
||||
'is-active': activeName === pane.name,
|
||||
'is-disabled': pane.disabled
|
||||
}"
|
||||
@click="handleClick(pane)"
|
||||
>
|
||||
<!-- 标签文本 -->
|
||||
<span>{{ pane.label }}</span>
|
||||
|
||||
<!-- 关闭按钮 -->
|
||||
<i
|
||||
v-if="pane.closable"
|
||||
class="my-tabs__close"
|
||||
@click.stop="handleClose(pane)"
|
||||
>×</i>
|
||||
</div>
|
||||
|
||||
<!-- 底部横线和滑块 -->
|
||||
<div class="my-tabs__slider-wrapper">
|
||||
<div class="my-tabs__slider-track"></div>
|
||||
<div
|
||||
class="my-tabs__slider"
|
||||
ref="slider"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 内容区 -->
|
||||
<div class="my-tabs__content">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MyTabs',
|
||||
model: {
|
||||
prop: 'value',
|
||||
event: 'input'
|
||||
},
|
||||
props: {
|
||||
value: [String, Number], // 当前激活
|
||||
type: { type: String, default: '' }, // 后期可扩展 card/border-card
|
||||
closable: Boolean // 全局是否允许关闭
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
panes: [] // 缓存所有 MyTabPane 实例
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
activeName: {
|
||||
get() { return this.value },
|
||||
set(val) { this.$emit('input', val) }
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
activeName() {
|
||||
this.$nextTick(() => {
|
||||
this.updateSliderPosition();
|
||||
});
|
||||
},
|
||||
panes() {
|
||||
this.$nextTick(() => {
|
||||
this.updateSliderPosition();
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.updateSliderPosition();
|
||||
});
|
||||
},
|
||||
methods: {
|
||||
/* 收集子组件 */
|
||||
registerPane(pane) {
|
||||
this.panes.push(pane)
|
||||
// 默认选中第一个
|
||||
if (!this.activeName && this.panes.length === 1) {
|
||||
this.activeName = pane.name
|
||||
}
|
||||
},
|
||||
unregisterPane(pane) {
|
||||
const idx = this.panes.indexOf(pane)
|
||||
if (idx > -1) this.panes.splice(idx, 1)
|
||||
},
|
||||
/* 点击导航 */
|
||||
handleClick(pane) {
|
||||
if (pane.disabled) return
|
||||
this.activeName = pane.name
|
||||
this.$emit('tab-click', pane)
|
||||
},
|
||||
/* 关闭 */
|
||||
handleClose(pane) {
|
||||
this.$emit('edit', pane.name, 'remove')
|
||||
// 业务层把 v-model 绑定的数组删掉即可
|
||||
},
|
||||
/* 更新滑块位置和宽度 */
|
||||
updateSliderPosition() {
|
||||
if (!this.activeName || !this.$refs.slider) return;
|
||||
|
||||
const activeTab = this.$el.querySelector(`.my-tabs__item.is-active`);
|
||||
if (!activeTab) return;
|
||||
|
||||
const textElement = activeTab.querySelector('span');
|
||||
if (!textElement) return;
|
||||
|
||||
const { width } = textElement.getBoundingClientRect();
|
||||
const { left } = activeTab.getBoundingClientRect();
|
||||
const navLeft = this.$el.querySelector('.my-tabs__nav').getBoundingClientRect().left;
|
||||
|
||||
this.$refs.slider.style.width = `${width}px`;
|
||||
this.$refs.slider.style.left = `${left - navLeft + (activeTab.offsetWidth - width) / 2}px`;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.my-tabs__nav {
|
||||
display: flex;
|
||||
position: relative;
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
.my-tabs__item {
|
||||
padding: 8px 16px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
font-size: 20px;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
}
|
||||
.my-tabs__item.is-active {
|
||||
color: #409eff;
|
||||
background: linear-gradient(90deg, $linear-gradient-start 22%, $linear-gradient-end 73%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
font-weight: bold;
|
||||
}
|
||||
.my-tabs__item.is-disabled {
|
||||
color: #1e293b;
|
||||
cursor: not-allowed;
|
||||
font-family: 'Poppins-Regular', serif;
|
||||
}
|
||||
.my-tabs__close { margin-left: 6px; color: #999; }
|
||||
.my-tabs__close:hover { color: #f56c6c; }
|
||||
.my-tabs__content { padding: 15px 0; }
|
||||
|
||||
/* 底部横线和滑块样式 */
|
||||
.my-tabs__slider-wrapper {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.my-tabs__slider-track {
|
||||
width: 100%;
|
||||
height: 4px;
|
||||
background-color: #e2e8f0;
|
||||
}
|
||||
.my-tabs__slider {
|
||||
height: 4px;
|
||||
background: $header-backgroungd;
|
||||
border-radius: 4px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -12,20 +12,20 @@ export default {
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
localValue: this.value, // 创建本地副本
|
||||
localValue: this.value,
|
||||
isInputFocused: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.localValue = newVal; // 监听外部 value 变化并同步到本地
|
||||
this.localValue = newVal;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleInput(event) {
|
||||
const newValue = event.target.value;
|
||||
this.localValue = newValue;
|
||||
this.$emit('input', newValue); // 触发 input 事件以支持 v-model
|
||||
this.$emit('input', newValue);
|
||||
},
|
||||
// 新增:处理查询事件
|
||||
handleSearch() {
|
||||
@ -36,7 +36,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="input-container" :class="{ focused: isInputFocused || localValue }">
|
||||
<div class="input-container flex items-center" :class="{ focused: isInputFocused || localValue }">
|
||||
<input
|
||||
type="text"
|
||||
:placeholder="placeholder"
|
||||
@ -54,21 +54,24 @@ export default {
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<style scoped lang="scss">
|
||||
/* 添加输入框容器聚焦状态样式 */
|
||||
.input-container {
|
||||
position: relative;
|
||||
|
||||
height: 60px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
input {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
padding: 0 20px;
|
||||
border-radius: 12px;
|
||||
border: 2px solid transparent;
|
||||
outline: none;
|
||||
font-size: $normal-font-size;
|
||||
transition: border-color 0.3s ease;
|
||||
color: #1e293b;
|
||||
flex: 1;
|
||||
background: #fff !important;
|
||||
border: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-shadow: none;
|
||||
height: 40px;
|
||||
|
||||
&::placeholder {
|
||||
color: #ccc;
|
||||
@ -76,14 +79,12 @@ export default {
|
||||
}
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
right: 15px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
position: relative;
|
||||
right: 0;
|
||||
width: 54px;
|
||||
height: 40px;
|
||||
pointer-events: none;
|
||||
cursor: pointer;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
&.focused {
|
||||
|
||||
@ -126,6 +126,7 @@ export default {
|
||||
border-radius: 6px;
|
||||
gap: 12px;
|
||||
color: #1E293B;
|
||||
font-weight: 500;
|
||||
|
||||
img {
|
||||
width: 28px;
|
||||
|
||||
Reference in New Issue
Block a user