feat 首页
This commit is contained in:
@@ -85,6 +85,11 @@ const coreBusiness = [
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-footer {
|
||||
// 设计稿:footer 顶 y=1942,总高 481px。各行绝对坐标换算为相对 footer 顶:
|
||||
// 列标题 54 / 内容首行 115 / legal[0] 332 / legal[1] 371。
|
||||
min-height: @footer-height;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: @footer-bg;
|
||||
color: @white;
|
||||
|
||||
@@ -95,7 +100,7 @@ const coreBusiness = [
|
||||
margin: 0 auto;
|
||||
height: 250px;
|
||||
box-sizing: border-box;
|
||||
padding-top: 30px;
|
||||
padding-top: 54px;
|
||||
padding-left: clamp(
|
||||
@spacing-base,
|
||||
calc((100% - 1609px) / 2),
|
||||
@@ -182,6 +187,8 @@ const coreBusiness = [
|
||||
margin: 0 auto;
|
||||
padding-bottom: 20px;
|
||||
text-align: left;
|
||||
// legal[0] 需落在相对 footer 顶 332px 处
|
||||
margin-top: 62px;
|
||||
}
|
||||
|
||||
&__legal {
|
||||
|
||||
@@ -84,25 +84,16 @@ onMounted(() => void messageStore.fetchUnreadCount())
|
||||
&__inner {
|
||||
.flex(row, flex-start);
|
||||
height: 100%;
|
||||
// padding-left: clamp(@spacing-base, calc((100% - 1600px) / 2), 155px);
|
||||
padding-left: 15%;
|
||||
// padding-right: clamp(@spacing-base, calc((100% - 1680px) / 2), 120px);
|
||||
padding-right: 15%;
|
||||
// 设计稿 1920 基准:导航首项左 275px,操作区右边界 1800px(右侧留白 120px)。
|
||||
// 视口窄于 1920 时按留白收缩,避免两端顶死。
|
||||
padding-left: clamp(@spacing-base, calc((100% - 1370px) / 2), 275px);
|
||||
padding-right: clamp(@spacing-base, calc((100% - 1680px) / 2), 120px);
|
||||
}
|
||||
|
||||
&__nav {
|
||||
.flex(row, center);
|
||||
gap: @header-nav-gap;
|
||||
height: 100%;
|
||||
margin-left: 0px;
|
||||
|
||||
@media (max-width: 1600px) {
|
||||
gap: 48px;
|
||||
}
|
||||
|
||||
@media (max-width: 1280px) {
|
||||
gap: 32px;
|
||||
}
|
||||
|
||||
&-item {
|
||||
position: relative;
|
||||
|
||||
@@ -21,11 +21,7 @@ const showFooter = computed(() => !route.meta.noFooter)
|
||||
<style lang="less" scoped>
|
||||
.layout {
|
||||
.flex(column, flex-start, stretch);
|
||||
min-height: calc(100vh / @page-zoom);
|
||||
|
||||
@media (max-width: 1286px) {
|
||||
min-height: calc(100vh / @page-zoom--sm);
|
||||
}
|
||||
min-height: 100vh;
|
||||
|
||||
&__main {
|
||||
flex: 1;
|
||||
|
||||
@@ -10,7 +10,8 @@
|
||||
.container--home {
|
||||
padding: 0 @home-gutter;
|
||||
|
||||
@media (max-width: 1919px) {
|
||||
// 1:1 还原基准为 1920。仅当视口明显窄于 1920 时才收窄留白。
|
||||
@media (max-width: 1600px) {
|
||||
padding: 0 @spacing-lg;
|
||||
}
|
||||
}
|
||||
@@ -18,13 +19,9 @@
|
||||
.auth-page {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: calc(100vh / @page-zoom);
|
||||
min-height: 100vh;
|
||||
background: @white;
|
||||
|
||||
@media (max-width: 1286px) {
|
||||
min-height: calc(100vh / @page-zoom--sm);
|
||||
}
|
||||
|
||||
&__brand {
|
||||
.flex(column, flex-start, center);
|
||||
flex: 0 0 @login-brand-width;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
@import './reset.less';
|
||||
@import './common.less';
|
||||
|
||||
// 视觉基准:蓝湖设计稿 1920px,1:1 还原,不做整体缩放。
|
||||
// 如需适配窄屏,请在各页面内部用媒体查询处理,不要在根元素缩放。
|
||||
html {
|
||||
zoom: @page-zoom;
|
||||
|
||||
@media (max-width: 1286px) {
|
||||
zoom: @page-zoom--sm;
|
||||
}
|
||||
}
|
||||
zoom: 1;
|
||||
}
|
||||
@@ -56,8 +56,10 @@
|
||||
|
||||
@container-width: 1920px;
|
||||
|
||||
@page-zoom: 0.67;
|
||||
@page-zoom--sm: 0.66;
|
||||
// 视觉基准:蓝湖设计稿 1920px,1:1 还原,根元素不做缩放(html { zoom: 1 })。
|
||||
// @page-zoom 仅作为「设计稿 px 与实际渲染 px」的换算说明保留,实际值为 1。
|
||||
@page-zoom: 1;
|
||||
@page-zoom--sm: 1;
|
||||
|
||||
@page-content: 1780px;
|
||||
@page-gutter: 70px;
|
||||
|
||||
@@ -241,15 +241,17 @@ function clearChips(): void {
|
||||
>
|
||||
{{ shortcut.label }}
|
||||
</button>
|
||||
<ElDatePicker
|
||||
v-model="dateRange"
|
||||
class="case-filter__date"
|
||||
type="daterange"
|
||||
range-separator="—"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
<div class="case-filter__date-wrap">
|
||||
<ElDatePicker
|
||||
v-model="dateRange"
|
||||
class="case-filter__date"
|
||||
type="daterange"
|
||||
range-separator="—"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
value-format="YYYY-MM-DD"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -433,16 +435,37 @@ function clearChips(): void {
|
||||
}
|
||||
}
|
||||
|
||||
&__date {
|
||||
// ElDatePicker 有两层元素:外层 div.case-filter__date 与内部 .el-range-editor,
|
||||
// 真正控制盒子尺寸的是内层。所以尺寸/背景/圆角都要用 :deep() 打到内层,
|
||||
// 且不能写在 &__date 上——那样只命中外层,视觉上看不到变化。
|
||||
&__date-wrap {
|
||||
grid-column: 1 / -1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
:deep(.case-filter__date),
|
||||
:deep(.case-filter__date .el-range-editor) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: 53px;
|
||||
padding: 0 @spacing-sm;
|
||||
border-radius: @radius-base;
|
||||
background: @input-bg;
|
||||
box-sizing: border-box;
|
||||
box-shadow: none;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
&__date {
|
||||
justify-content: center;
|
||||
min-width: 0;
|
||||
height: 53px;
|
||||
padding: 0 @spacing-sm;
|
||||
border-radius: @radius-base;
|
||||
background: @input-bg;
|
||||
box-sizing: border-box;
|
||||
box-shadow: none;
|
||||
|
||||
&:hover,
|
||||
@@ -469,6 +492,8 @@ function clearChips(): void {
|
||||
}
|
||||
|
||||
:deep(.el-range-separator) {
|
||||
flex: 0 0 auto;
|
||||
margin: 0 @spacing-xs;
|
||||
color: @cases-text;
|
||||
font-size: 20px;
|
||||
line-height: 53px;
|
||||
|
||||
@@ -17,10 +17,6 @@ const emit = defineEmits<{
|
||||
|
||||
<template>
|
||||
<div class="cases-grid">
|
||||
<p class="cases-grid__total">
|
||||
{{ total }}
|
||||
</p>
|
||||
|
||||
<div v-if="cases.length > 0" class="cases-grid__list">
|
||||
<CaseCard
|
||||
v-for="item in cases"
|
||||
@@ -39,17 +35,10 @@ const emit = defineEmits<{
|
||||
|
||||
<style lang="less" scoped>
|
||||
.cases-grid {
|
||||
&__total {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: @heading-color;
|
||||
}
|
||||
|
||||
&__list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 23px 28px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
&__empty {
|
||||
|
||||
@@ -60,14 +60,18 @@ const keyword = computed<string>({
|
||||
.case-search {
|
||||
.flex(row, flex-start, center);
|
||||
gap: 24px;
|
||||
width: 100%;
|
||||
height: 128px;
|
||||
box-sizing: border-box;
|
||||
padding: 0 36px;
|
||||
// 设计稿 #31:输入框 x=653(栏左 606 + 47),重置 1305,搜索 1429
|
||||
padding: 0 47px;
|
||||
border-radius: @radius-base;
|
||||
background: @white;
|
||||
|
||||
&__field {
|
||||
position: relative;
|
||||
// 输入框右边界 1245 → 重置按钮 1305,设计稿间距 60
|
||||
margin-right: 36px;
|
||||
flex: 0 0 592px;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,8 +119,12 @@ onMounted(() => {
|
||||
background: @white;
|
||||
}
|
||||
|
||||
// 设计稿 #31:hero 标题 x=112、副标题 x=112;
|
||||
// 主体筛选栏 x=70、右侧操作区右边界 1850。两套留白并存,
|
||||
// 所以 hero 内容单独内缩,不动全局 .container 的 70px 留白。
|
||||
&__hero-inner {
|
||||
padding-left: 42px;
|
||||
padding-left: 112px;
|
||||
padding-right: 112px;
|
||||
}
|
||||
|
||||
&__title {
|
||||
@@ -146,7 +150,10 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
&__main {
|
||||
.flex(column, flex-start);
|
||||
// 设计稿:右侧操作区 x=606 w=1244(右边界 1850,与筛选栏区留白 70 对称)。
|
||||
// mixin 签名是 .flex(direction; justify; align),第三参才是 align-items。
|
||||
// 纵向排列时 justify 无意义,align 用 stretch 让子项撑满 1244。
|
||||
.flex(column, flex-start, stretch);
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
gap: 24px;
|
||||
|
||||
@@ -12,6 +12,10 @@ const router = useRouter()
|
||||
const activeIndex = ref(0)
|
||||
let timer: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
// 后端未下发banner 时退回内置插图。插图在设计稿中并非铺满背景,
|
||||
// 而是定位在右侧 (928,104) 尺寸 723x598,需按原比例显示。
|
||||
const hasBanners = computed(() => props.banners.length > 0)
|
||||
|
||||
const slides = computed(() => {
|
||||
if (props.banners.length > 0) {
|
||||
return props.banners.map((item) => item.imageUrl)
|
||||
@@ -64,7 +68,12 @@ onBeforeUnmount(stopAutoplay)
|
||||
class="home-banner__slide"
|
||||
:class="{ 'home-banner__slide--active': index === activeIndex }"
|
||||
>
|
||||
<img class="home-banner__image" :src="src" alt="">
|
||||
<img
|
||||
class="home-banner__image"
|
||||
:class="{ 'home-banner__image--inline': !hasBanners }"
|
||||
:src="src"
|
||||
alt=""
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -128,6 +137,19 @@ onBeforeUnmount(stopAutoplay)
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
|
||||
// 内置插图:按设计稿定位与尺寸原样显示,不铺满
|
||||
&--inline {
|
||||
position: absolute;
|
||||
top: 31px;
|
||||
right: 269px;
|
||||
left: auto;
|
||||
bottom: auto;
|
||||
width: 723px;
|
||||
height: 598px;
|
||||
margin-left: auto;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
&__inner {
|
||||
@@ -216,25 +238,25 @@ onBeforeUnmount(stopAutoplay)
|
||||
|
||||
&__dots {
|
||||
position: absolute;
|
||||
right: 80px;
|
||||
bottom: 40px;
|
||||
left: 929px;
|
||||
top: 594px;
|
||||
z-index: 1;
|
||||
.flex(row, flex-end, center);
|
||||
gap: 8px;
|
||||
.flex(row, flex-start, center);
|
||||
gap: 13px;
|
||||
}
|
||||
|
||||
&__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: fade(@home-banner-dot, 40%);
|
||||
background: @home-banner-dot;
|
||||
cursor: pointer;
|
||||
|
||||
&--active {
|
||||
width: 24px;
|
||||
border-radius: 4px;
|
||||
width: 44px;
|
||||
border-radius: 3px;
|
||||
background: @home-banner-dot;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import type { BusinessType } from '@/types/api'
|
||||
import type { BusinessLine } from '@/types/business/home'
|
||||
import illInternet from '@/assets/home/business-internet.png'
|
||||
import illHardware from '@/assets/home/business-hardware.png'
|
||||
import illIot from '@/assets/home/business-iot.png'
|
||||
import illManufacturing from '@/assets/home/business-manufacturing.png'
|
||||
|
||||
interface Props {
|
||||
lines: BusinessLine[]
|
||||
@@ -12,13 +8,7 @@ interface Props {
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
const FALLBACK_ILL: Record<BusinessType, string> = {
|
||||
internet: illInternet,
|
||||
hardware: illHardware,
|
||||
iot: illIot,
|
||||
manufacturing: illManufacturing,
|
||||
other: illIot,
|
||||
}
|
||||
const FALLBACK_ACTIVE: BusinessType[] = ['internet']
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -31,13 +21,10 @@ const FALLBACK_ILL: Record<BusinessType, string> = {
|
||||
:key="line.id"
|
||||
:to="`/business/${line.id}`"
|
||||
class="home-business__card"
|
||||
:class="`home-business__card--${line.id}`"
|
||||
:class="{
|
||||
'home-business__card--active': FALLBACK_ACTIVE.includes(line.id),
|
||||
}"
|
||||
>
|
||||
<img
|
||||
class="home-business__ill"
|
||||
:src="line.icon || FALLBACK_ILL[line.id]"
|
||||
:alt="line.name"
|
||||
/>
|
||||
<span class="home-business__check" aria-hidden="true" />
|
||||
<h4 class="home-business__name">
|
||||
{{ line.name }}
|
||||
@@ -76,6 +63,7 @@ const FALLBACK_ILL: Record<BusinessType, string> = {
|
||||
min-width: 0;
|
||||
height: 206px;
|
||||
box-sizing: border-box;
|
||||
padding: 0 20px;
|
||||
overflow: hidden;
|
||||
border: 1px solid @home-business-border;
|
||||
border-radius: @radius-base;
|
||||
@@ -90,19 +78,10 @@ const FALLBACK_ILL: Record<BusinessType, string> = {
|
||||
}
|
||||
}
|
||||
|
||||
&__ill {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 48px;
|
||||
width: 81px;
|
||||
height: 111px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
&__check {
|
||||
position: absolute;
|
||||
top: 24px;
|
||||
right: 20px;
|
||||
top: 27px;
|
||||
right: 37px;
|
||||
flex-shrink: 0;
|
||||
width: 31px;
|
||||
height: 31px;
|
||||
@@ -113,8 +92,8 @@ const FALLBACK_ILL: Record<BusinessType, string> = {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
width: 21px;
|
||||
height: 21px;
|
||||
margin: auto;
|
||||
border-radius: @radius-round;
|
||||
background: @white;
|
||||
@@ -123,7 +102,7 @@ const FALLBACK_ILL: Record<BusinessType, string> = {
|
||||
}
|
||||
|
||||
&__name {
|
||||
margin: 106px 0 0;
|
||||
margin: 104px 0 0;
|
||||
font-size: @font-size-title;
|
||||
font-weight: 500;
|
||||
line-height: 41px;
|
||||
@@ -139,7 +118,7 @@ const FALLBACK_ILL: Record<BusinessType, string> = {
|
||||
.multi-ellipsis(1);
|
||||
}
|
||||
|
||||
&__card--internet {
|
||||
&__card--active {
|
||||
border-color: @primary-strong;
|
||||
background: @primary-strong;
|
||||
|
||||
|
||||
@@ -173,13 +173,17 @@ function onMore(): void {
|
||||
position: relative;
|
||||
.flex(column, flex-start, stretch);
|
||||
flex-shrink: 0;
|
||||
flex: 0 1 722px;
|
||||
flex: 0 0 529px;
|
||||
width: 529px;
|
||||
height: 245px;
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
gap: 0;
|
||||
padding: 18px 40px 28px;
|
||||
border: 1px solid @home-card-border;
|
||||
border-radius: @radius-lg;
|
||||
background: @white;
|
||||
overflow: hidden;
|
||||
|
||||
&__head {
|
||||
.flex(row, flex-start);
|
||||
|
||||
@@ -59,15 +59,17 @@ onMounted(() => {
|
||||
|
||||
&__dashboard {
|
||||
.flex(row, space-between, stretch);
|
||||
gap: 36px;
|
||||
gap: 60px;
|
||||
}
|
||||
|
||||
&__dashboard-main {
|
||||
.flex(row, space-between, center);
|
||||
flex: 1;
|
||||
flex: 0 0 1091px;
|
||||
width: 1091px;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
gap: @spacing-lg;
|
||||
padding: 0 73px 0 106px;
|
||||
padding: 0 59px 0 105px;
|
||||
border: 1px solid @home-card-border;
|
||||
border-radius: @radius-lg;
|
||||
background: @white;
|
||||
|
||||
@@ -177,7 +177,7 @@ async function onGetCode(target: string): Promise<void> {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
height: calc(2px / @page-zoom);
|
||||
height: 2px;
|
||||
background: @primary-strong;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user