恢复修改的数据
This commit is contained in:
@@ -16,24 +16,28 @@ defineProps<Props>()
|
||||
</h3>
|
||||
<span class="home-business__slogan text-secondary">研发智造一体化自营平台,全链路质量兜底</span>
|
||||
</div>
|
||||
|
||||
<div class="home-business__grid">
|
||||
<RouterLink
|
||||
v-for="line in lines"
|
||||
:key="line.id"
|
||||
:to="`/business/${line.id}`"
|
||||
class="home-business__card"
|
||||
:class="`home-business__card--${line.id}`"
|
||||
>
|
||||
<div class="home-business__icon">
|
||||
<!-- TODO(蓝湖): 替换业务图标切图 -->
|
||||
<span>{{ line.name.slice(0, 2) }}</span>
|
||||
<div class="home-business__card-head">
|
||||
<span class="home-business__icon">
|
||||
<!-- TODO(蓝湖): 替换业务图标切图 -->
|
||||
{{ line.name.slice(0, 2) }}
|
||||
</span>
|
||||
<h4 class="home-business__name">
|
||||
{{ line.name }}
|
||||
</h4>
|
||||
</div>
|
||||
<h4 class="home-business__name">
|
||||
{{ line.name }}
|
||||
</h4>
|
||||
<p class="home-business__desc text-secondary">
|
||||
<p class="home-business__desc">
|
||||
{{ line.desc }}
|
||||
</p>
|
||||
<span class="home-business__more">了解详细 ></span>
|
||||
<span class="home-business__more">了解详情 ></span>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</section>
|
||||
@@ -64,29 +68,31 @@ defineProps<Props>()
|
||||
|
||||
&__card {
|
||||
.flex(column, flex-start);
|
||||
gap: @spacing-sm;
|
||||
gap: @spacing-base;
|
||||
padding: @spacing-lg;
|
||||
border-radius: @radius-lg;
|
||||
background: @white;
|
||||
background: @primary-light;
|
||||
box-shadow: @shadow-base;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: @shadow-lg;
|
||||
transform: translateY(-2px);
|
||||
|
||||
.home-business__more {
|
||||
color: @primary-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__card-head {
|
||||
.flex(row, flex-start);
|
||||
gap: @spacing-sm;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
.flex(row, center);
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: @radius-base;
|
||||
background: @primary-light;
|
||||
background: @white;
|
||||
color: @primary-color;
|
||||
font-size: @font-size-base;
|
||||
font-weight: 600;
|
||||
@@ -101,13 +107,68 @@ defineProps<Props>()
|
||||
&__desc {
|
||||
min-height: 40px;
|
||||
font-size: @font-size-sm;
|
||||
color: @text-color-secondary;
|
||||
.multi-ellipsis(2);
|
||||
}
|
||||
|
||||
&__more {
|
||||
align-self: flex-end;
|
||||
font-size: @font-size-sm;
|
||||
color: @text-color-secondary;
|
||||
transition: color 0.2s;
|
||||
}
|
||||
|
||||
// 四类业务各自的主题色(与设计稿一致:卡片浅色底 + 同色系图标/文案)
|
||||
&__card--internet {
|
||||
background: #e8f0ff;
|
||||
|
||||
.home-business__icon {
|
||||
color: #3370ff;
|
||||
}
|
||||
|
||||
.home-business__desc,
|
||||
.home-business__more {
|
||||
color: #598bff;
|
||||
}
|
||||
}
|
||||
|
||||
&__card--hardware {
|
||||
background: #e7f8ec;
|
||||
|
||||
.home-business__icon {
|
||||
color: #22b14c;
|
||||
}
|
||||
|
||||
.home-business__desc,
|
||||
.home-business__more {
|
||||
color: #34c724;
|
||||
}
|
||||
}
|
||||
|
||||
&__card--iot {
|
||||
background: #f0eaff;
|
||||
|
||||
.home-business__icon {
|
||||
color: #7c4dff;
|
||||
}
|
||||
|
||||
.home-business__desc,
|
||||
.home-business__more {
|
||||
color: #9a7bff;
|
||||
}
|
||||
}
|
||||
|
||||
&__card--manufacturing {
|
||||
background: #fff2e4;
|
||||
|
||||
.home-business__icon {
|
||||
color: #ff8f1f;
|
||||
}
|
||||
|
||||
.home-business__desc,
|
||||
.home-business__more {
|
||||
color: #ffa94d;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { CaseCard } from '@/components'
|
||||
import { formatBusinessType } from '@/utils/format'
|
||||
import type { BusinessType } from '@/types/api'
|
||||
import type { Case } from '@/types/business/cases'
|
||||
|
||||
@@ -46,16 +46,39 @@ function switchCategory(value: BusinessType | 'all'): void {
|
||||
</button>
|
||||
</div>
|
||||
<RouterLink to="/cases" class="home-cases__more link">
|
||||
查看更多 >
|
||||
查看全部 >
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<div class="home-cases__grid">
|
||||
<CaseCard
|
||||
<article
|
||||
v-for="item in cases"
|
||||
:key="item.id"
|
||||
:item="item"
|
||||
@view="$router.push(`/cases/${$event}`)"
|
||||
/>
|
||||
class="home-cases__card"
|
||||
@click="$router.push(`/cases/${item.id}`)"
|
||||
>
|
||||
<div class="home-cases__cover">
|
||||
<!-- TODO(蓝湖): 替换案例封面切图 -->
|
||||
<img v-if="item.cover" v-lazy="item.cover" class="home-cases__img" :alt="item.title">
|
||||
<div v-else class="home-cases__img home-cases__img--placeholder" />
|
||||
<span class="home-cases__tag" :class="`home-cases__tag--${item.category}`">
|
||||
{{ formatBusinessType(item.category) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="home-cases__body">
|
||||
<h4 class="home-cases__name">
|
||||
{{ item.title }}
|
||||
</h4>
|
||||
<ul class="home-cases__meta">
|
||||
<li>客户对象:{{ item.clientType }}</li>
|
||||
<li>研发周期:{{ item.duration }}</li>
|
||||
<li>团队规模:{{ item.teamSize }}人</li>
|
||||
<li>完成时间:{{ item.finishDate }}</li>
|
||||
</ul>
|
||||
<span class="home-cases__view">查看详情</span>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -64,6 +87,7 @@ function switchCategory(value: BusinessType | 'all'): void {
|
||||
.home-cases {
|
||||
&__head {
|
||||
.flex(row, space-between);
|
||||
gap: @spacing-base;
|
||||
margin-bottom: @spacing-base;
|
||||
}
|
||||
|
||||
@@ -75,6 +99,7 @@ function switchCategory(value: BusinessType | 'all'): void {
|
||||
|
||||
&__tabs {
|
||||
.flex(row, center);
|
||||
flex: 1;
|
||||
gap: @spacing-sm;
|
||||
}
|
||||
|
||||
@@ -102,5 +127,103 @@ function switchCategory(value: BusinessType | 'all'): void {
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: @spacing-base;
|
||||
}
|
||||
|
||||
&__card {
|
||||
overflow: hidden;
|
||||
border-radius: @radius-lg;
|
||||
background: @white;
|
||||
box-shadow: @shadow-base;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: @shadow-lg;
|
||||
transform: translateY(-2px);
|
||||
|
||||
.home-cases__view {
|
||||
background: @primary-color;
|
||||
color: @white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__cover {
|
||||
position: relative;
|
||||
height: 130px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
|
||||
&--placeholder {
|
||||
background: linear-gradient(135deg, @primary-light, @bg-color);
|
||||
}
|
||||
}
|
||||
|
||||
&__tag {
|
||||
position: absolute;
|
||||
top: @spacing-sm;
|
||||
left: @spacing-sm;
|
||||
padding: 2px @spacing-sm;
|
||||
border-radius: @radius-sm;
|
||||
background: @primary-color;
|
||||
color: @white;
|
||||
font-size: @font-size-sm;
|
||||
|
||||
&--hardware {
|
||||
background: #22b14c;
|
||||
}
|
||||
|
||||
&--iot {
|
||||
background: #7c4dff;
|
||||
}
|
||||
|
||||
&--manufacturing {
|
||||
background: #ff8f1f;
|
||||
}
|
||||
|
||||
&--other {
|
||||
background: @text-color-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
&__body {
|
||||
padding: @spacing-base;
|
||||
}
|
||||
|
||||
&__name {
|
||||
margin-bottom: @spacing-sm;
|
||||
font-size: @font-size-lg;
|
||||
font-weight: 600;
|
||||
color: @heading-color;
|
||||
.multi-ellipsis(1);
|
||||
}
|
||||
|
||||
&__meta {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4px @spacing-sm;
|
||||
font-size: @font-size-sm;
|
||||
color: @text-color-secondary;
|
||||
|
||||
li {
|
||||
.ellipsis();
|
||||
}
|
||||
}
|
||||
|
||||
&__view {
|
||||
display: block;
|
||||
width: fit-content;
|
||||
margin: @spacing-base 0 0 auto;
|
||||
padding: 2px @spacing-base;
|
||||
border: 1px solid @primary-color;
|
||||
border-radius: @radius-sm;
|
||||
color: @primary-color;
|
||||
font-size: @font-size-sm;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,70 +1,185 @@
|
||||
<script setup lang="ts">
|
||||
// 快捷入口为固定模块链接(纯 UI,静态数据)
|
||||
const entries = [
|
||||
{ label: '财务台账', path: '/finance/bills', icon: '💰' },
|
||||
{ label: '合同台账', path: '/contract', icon: '📄' },
|
||||
{ label: '客服工作台', path: '/service', icon: '💬' },
|
||||
{ label: '案例库管理', path: '/cases', icon: '📁' },
|
||||
{ label: '工单需求池', path: '/demand', icon: '📋' },
|
||||
import { computed, ref } from 'vue'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useHomeStore } from '@/stores/modules/home'
|
||||
import type { PortalEntryCard } from '@/types/business/home'
|
||||
import { toPortalEntryCards } from '@/utils/entryIcon'
|
||||
import HomeAppDialog from './HomeAppDialog.vue'
|
||||
|
||||
/**
|
||||
* 首页「快捷入口」卡片。
|
||||
*
|
||||
* 数据来自 store.quickEntries(← /vermins/portalEntryUser/list 的 groupType=1 项),
|
||||
* 接口不可用(游客 / 无权限)时回退到内置默认入口,保证卡片不空。
|
||||
* 「更多应用」打开 HomeAppDialog,可在快捷 / 其他两组之间调整入口。
|
||||
*/
|
||||
const homeStore = useHomeStore()
|
||||
const { quickEntries } = storeToRefs(homeStore)
|
||||
const router = useRouter()
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
/** 接口不可用时的默认入口(与改造前的静态入口保持一致) */
|
||||
const FALLBACK_QUICK: PortalEntryCard[] = [
|
||||
{ id: 0, label: '财务台账', route: '/finance/bills', groupType: '1', icon: '💰', isImage: false, tone: 'amber' },
|
||||
{ id: 0, label: '合同台账', route: '/contract', groupType: '1', icon: '📄', isImage: false, tone: 'blue' },
|
||||
{ id: 0, label: '客服工作台', route: '/service', groupType: '1', icon: '💬', isImage: false, tone: 'green' },
|
||||
{ id: 0, label: '案例库管理', route: '/cases', groupType: '1', icon: '📁', isImage: false, tone: 'purple' },
|
||||
{ id: 0, label: '工单需求池', route: '/demand', groupType: '1', icon: '📋', isImage: false, tone: 'cyan' },
|
||||
]
|
||||
|
||||
/** 前端已注册的静态路由(route 无效时不跳转,避免出现无匹配路由) */
|
||||
const knownPaths = new Set(router.getRoutes().map((item) => item.path))
|
||||
|
||||
/** 卡片展示的入口(最多 5 个) */
|
||||
const entries = computed<PortalEntryCard[]>(() => {
|
||||
const list = toPortalEntryCards(quickEntries.value)
|
||||
return (list.length > 0 ? list : FALLBACK_QUICK).slice(0, 5)
|
||||
})
|
||||
|
||||
/** 解析可跳转路径:优先后端 route,其次同名默认入口,最后回首页 */
|
||||
function resolvePath(card: PortalEntryCard): string {
|
||||
if (card.route && knownPaths.has(card.route)) return card.route
|
||||
const fallback = FALLBACK_QUICK.find((item) => item.label === card.label)
|
||||
if (fallback && knownPaths.has(fallback.route)) return fallback.route
|
||||
return '/'
|
||||
}
|
||||
|
||||
function onMore(): void {
|
||||
// 打开时同步一次,确保分组是后端最新结果
|
||||
void homeStore.fetchPortalEntries()
|
||||
dialogVisible.value = true
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="home-quick">
|
||||
<h3 class="home-quick__title">
|
||||
快捷入口
|
||||
</h3>
|
||||
<div class="home-quick__head">
|
||||
<span class="home-quick__head-icon">⊞</span>
|
||||
<h3 class="home-quick__title">
|
||||
快捷入口
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div class="home-quick__list">
|
||||
<RouterLink
|
||||
v-for="entry in entries"
|
||||
:key="entry.label"
|
||||
:to="entry.path"
|
||||
:key="`${entry.label}-${entry.id}`"
|
||||
:to="resolvePath(entry)"
|
||||
class="home-quick__item"
|
||||
>
|
||||
<span class="home-quick__icon">{{ entry.icon }}</span>
|
||||
<span class="home-quick__icon" :class="`home-quick__icon--${entry.tone}`">
|
||||
<img v-if="entry.isImage" :src="entry.icon" :alt="entry.label" class="home-quick__img">
|
||||
<template v-else>{{ entry.icon }}</template>
|
||||
</span>
|
||||
<span class="home-quick__label">{{ entry.label }}</span>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<button class="home-quick__more link" type="button" @click="onMore">
|
||||
更多应用 >
|
||||
</button>
|
||||
|
||||
<HomeAppDialog v-model:visible="dialogVisible" />
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.home-quick {
|
||||
.flex(column, flex-start, stretch);
|
||||
flex-shrink: 0;
|
||||
width: 320px;
|
||||
gap: @spacing-base;
|
||||
padding: @spacing-lg;
|
||||
border-radius: @radius-lg;
|
||||
background: @white;
|
||||
box-shadow: @shadow-base;
|
||||
|
||||
&__head {
|
||||
.flex(row, flex-start);
|
||||
gap: @spacing-sm;
|
||||
}
|
||||
|
||||
&__head-icon {
|
||||
color: @heading-color;
|
||||
font-size: @font-size-lg;
|
||||
}
|
||||
|
||||
&__title {
|
||||
margin-bottom: @spacing-base;
|
||||
font-size: @font-size-lg;
|
||||
font-weight: 600;
|
||||
color: @heading-color;
|
||||
}
|
||||
|
||||
&__list {
|
||||
.flex(row, space-between);
|
||||
gap: @spacing-base;
|
||||
.flex(row, space-between, flex-start);
|
||||
gap: @spacing-sm;
|
||||
}
|
||||
|
||||
&__item {
|
||||
.flex(column, center);
|
||||
.flex(column, flex-start, center);
|
||||
gap: @spacing-sm;
|
||||
flex: 1;
|
||||
padding: @spacing-base;
|
||||
border-radius: @radius-lg;
|
||||
background: @white;
|
||||
box-shadow: @shadow-base;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
transition: transform 0.2s;
|
||||
|
||||
&:hover {
|
||||
box-shadow: @shadow-lg;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
}
|
||||
|
||||
&__icon {
|
||||
font-size: 24px;
|
||||
.flex(row, center);
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: @radius-base;
|
||||
font-size: @font-size-xl;
|
||||
|
||||
&--amber {
|
||||
background: #fff3e0;
|
||||
}
|
||||
|
||||
&--blue {
|
||||
background: @primary-light;
|
||||
}
|
||||
|
||||
&--green {
|
||||
background: #e7f8ec;
|
||||
}
|
||||
|
||||
&--purple {
|
||||
background: #f0eaff;
|
||||
}
|
||||
|
||||
&--red {
|
||||
background: #ffeceb;
|
||||
}
|
||||
|
||||
&--cyan {
|
||||
background: #e4f6fa;
|
||||
}
|
||||
}
|
||||
|
||||
&__img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: @font-size-base;
|
||||
font-size: @font-size-sm;
|
||||
color: @text-color;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
&__more {
|
||||
align-self: flex-end;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
font-size: @font-size-sm;
|
||||
color: @text-color-secondary;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -7,10 +7,11 @@ interface Props {
|
||||
|
||||
defineProps<Props>()
|
||||
|
||||
/** 展示顺序与设计稿一致:累积项目 / 进行中 / 已提交 */
|
||||
const items = [
|
||||
{ key: 'pending', label: '待您处理' },
|
||||
{ key: 'total', label: '累积项目' },
|
||||
{ key: 'inProgress', label: '进行中' },
|
||||
{ key: 'total', label: '累计项目' },
|
||||
{ key: 'pending', label: '已提交' },
|
||||
] as const
|
||||
</script>
|
||||
|
||||
@@ -22,24 +23,23 @@ const items = [
|
||||
class="home-stats__item"
|
||||
>
|
||||
<span class="home-stats__value">{{ stats[item.key] }}</span>
|
||||
<span class="home-stats__label text-secondary">{{ item.label }}</span>
|
||||
<span class="home-stats__label">{{ item.label }}</span>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.home-stats {
|
||||
.flex(row, space-between);
|
||||
.flex(row, flex-end);
|
||||
gap: @spacing-base;
|
||||
|
||||
&__item {
|
||||
.flex(column, center);
|
||||
flex: 1;
|
||||
.flex(column, center, center);
|
||||
gap: @spacing-xs;
|
||||
padding: @spacing-base @spacing-lg;
|
||||
min-width: 84px;
|
||||
padding: @spacing-base;
|
||||
border-radius: @radius-lg;
|
||||
background: @white;
|
||||
box-shadow: @shadow-base;
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
&__value {
|
||||
@@ -49,7 +49,8 @@ const items = [
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: @font-size-base;
|
||||
font-size: @font-size-sm;
|
||||
color: @text-color-secondary;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,57 +18,42 @@ const greeting = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="home-welcome">
|
||||
<div class="home-welcome__info">
|
||||
<h2 class="home-welcome__greeting">
|
||||
{{ greeting }}
|
||||
</h2>
|
||||
<p class="home-welcome__summary text-secondary">
|
||||
{{ summary }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="home-welcome__brand">
|
||||
<!-- TODO(蓝湖): 替换头像切图 -->
|
||||
<span class="home-welcome__avatar">王</span>
|
||||
<span class="home-welcome__appName">虫子智造</span>
|
||||
</div>
|
||||
</section>
|
||||
<div class="home-welcome">
|
||||
<h2 class="home-welcome__greeting">
|
||||
{{ greeting }}
|
||||
<span class="home-welcome__wave">👋</span>
|
||||
</h2>
|
||||
<p class="home-welcome__summary">
|
||||
{{ summary }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.home-welcome {
|
||||
.flex(row, space-between);
|
||||
padding: @spacing-lg @spacing-xl;
|
||||
border-radius: @radius-lg;
|
||||
background: linear-gradient(120deg, @primary-color, @primary-hover);
|
||||
color: @white;
|
||||
.flex(column, center, flex-start);
|
||||
gap: @spacing-sm;
|
||||
|
||||
&__greeting {
|
||||
.flex(row, flex-start);
|
||||
gap: @spacing-sm;
|
||||
font-size: @font-size-xxl;
|
||||
font-weight: 600;
|
||||
color: @white;
|
||||
}
|
||||
|
||||
&__summary {
|
||||
margin-top: @spacing-xs;
|
||||
color: rgba(255, 255, 255, 0.85);
|
||||
}
|
||||
|
||||
&__brand {
|
||||
&__wave {
|
||||
.flex(row, center);
|
||||
gap: @spacing-sm;
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
.flex(row, center);
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: @radius-round;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
font-size: @font-size-lg;
|
||||
}
|
||||
|
||||
&__appName {
|
||||
font-size: @font-size-base;
|
||||
&__summary {
|
||||
font-size: @font-size-lg;
|
||||
color: rgba(255, 255, 255, 0.9);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { AppButton } from '@/components'
|
||||
// 业务详情页:数据只经 useHomeStore,不直接 import api
|
||||
import { useHomeStore } from '@/stores/modules/home'
|
||||
import type { BusinessType } from '@/types/api'
|
||||
import HomeServiceTabs from './cpns/HomeServiceTabs.vue'
|
||||
import HomeServiceContents from './cpns/HomeServiceContents.vue'
|
||||
import HomeServiceTypes from './cpns/HomeServiceTypes.vue'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const homeStore = useHomeStore()
|
||||
|
||||
const line = computed(() => homeStore.currentBusiness)
|
||||
@@ -47,10 +50,15 @@ onMounted(load)
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<HomeServiceTabs
|
||||
:service-types="line.serviceTypes"
|
||||
:service-content="line.serviceContent"
|
||||
/>
|
||||
<HomeServiceTypes :service-types="line.serviceTypes" />
|
||||
|
||||
<HomeServiceContents :contents="line.contents" />
|
||||
|
||||
<div class="business-detail__cta">
|
||||
<AppButton type="primary" size="lg" @click="router.push('/demand')">
|
||||
立即提报
|
||||
</AppButton>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p v-else class="text-secondary">
|
||||
@@ -104,5 +112,13 @@ onMounted(load)
|
||||
&__slogan {
|
||||
font-size: @font-size-base;
|
||||
}
|
||||
|
||||
&__cta {
|
||||
padding-top: @spacing-base;
|
||||
|
||||
.app-button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
export { default } from './index.vue'
|
||||
export { default as HomeBanner } from './cpns/HomeBanner.vue'
|
||||
export { default as HomeWelcome } from './cpns/HomeWelcome.vue'
|
||||
export { default as HomeStats } from './cpns/HomeStats.vue'
|
||||
export { default as HomeQuickEntries } from './cpns/HomeQuickEntries.vue'
|
||||
export { default as HomeAppDialog } from './cpns/HomeAppDialog.vue'
|
||||
export { default as HomeBusinessCards } from './cpns/HomeBusinessCards.vue'
|
||||
export { default as HomeCaseShowcase } from './cpns/HomeCaseShowcase.vue'
|
||||
export { default as HomeServiceTabs } from './cpns/HomeServiceTabs.vue'
|
||||
export { default as HomeServiceTypes } from './cpns/HomeServiceTypes.vue'
|
||||
export { default as HomeServiceContents } from './cpns/HomeServiceContents.vue'
|
||||
|
||||
@@ -4,23 +4,18 @@ import { storeToRefs } from 'pinia'
|
||||
// 页面容器:只做组件引入、props 分发、事件中转,数据只经 store
|
||||
import { useHomeStore } from '@/stores/modules/home'
|
||||
import type { BusinessType } from '@/types/api'
|
||||
import HomeBanner from './cpns/HomeBanner.vue'
|
||||
import HomeWelcome from './cpns/HomeWelcome.vue'
|
||||
import HomeStats from './cpns/HomeStats.vue'
|
||||
import HomeQuickEntries from './cpns/HomeQuickEntries.vue'
|
||||
import HomeBusinessCards from './cpns/HomeBusinessCards.vue'
|
||||
import HomeCaseShowcase from './cpns/HomeCaseShowcase.vue'
|
||||
import HomeServiceTabs from './cpns/HomeServiceTabs.vue'
|
||||
|
||||
const homeStore = useHomeStore()
|
||||
const { stats, businessLines, featuredCases } = storeToRefs(homeStore)
|
||||
const { stats, banners, businessLines, featuredCases } = storeToRefs(homeStore)
|
||||
|
||||
const todaySummary = computed(() => `今天有${stats.value.inProgress}个项目正在进行`)
|
||||
|
||||
// 服务 Tab 展示互联网软件线内容(服务内容区默认锚点)
|
||||
const serviceLine = computed(
|
||||
() => businessLines.value.find((line) => line.id === 'internet') ?? businessLines.value[0],
|
||||
)
|
||||
|
||||
function onCategoryChange(category: BusinessType | 'all'): void {
|
||||
void homeStore.fetchFeaturedCases(category)
|
||||
}
|
||||
@@ -32,19 +27,28 @@ onMounted(() => {
|
||||
|
||||
<template>
|
||||
<div class="home">
|
||||
<HomeWelcome :summary="todaySummary" />
|
||||
<HomeStats :stats="stats" />
|
||||
<HomeQuickEntries />
|
||||
<HomeBusinessCards :lines="businessLines" />
|
||||
<HomeCaseShowcase
|
||||
:cases="featuredCases"
|
||||
@category-change="onCategoryChange"
|
||||
/>
|
||||
<HomeServiceTabs
|
||||
v-if="serviceLine"
|
||||
:service-types="serviceLine.serviceTypes"
|
||||
:service-content="serviceLine.serviceContent"
|
||||
/>
|
||||
<div class="container">
|
||||
<HomeBanner :banners="banners" />
|
||||
</div>
|
||||
|
||||
<div class="container home__dashboard">
|
||||
<section class="home__dashboard-main">
|
||||
<HomeWelcome :summary="todaySummary" />
|
||||
<HomeStats :stats="stats" />
|
||||
</section>
|
||||
<HomeQuickEntries />
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<HomeBusinessCards :lines="businessLines" />
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<HomeCaseShowcase
|
||||
:cases="featuredCases"
|
||||
@category-change="onCategoryChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -54,5 +58,20 @@ onMounted(() => {
|
||||
gap: @spacing-xl;
|
||||
padding-top: @spacing-xl;
|
||||
padding-bottom: @spacing-xxl;
|
||||
|
||||
&__dashboard {
|
||||
.flex(row, space-between, stretch);
|
||||
gap: @spacing-base;
|
||||
}
|
||||
|
||||
&__dashboard-main {
|
||||
.flex(row, space-between, center);
|
||||
flex: 1;
|
||||
gap: @spacing-lg;
|
||||
padding: @spacing-xl;
|
||||
border-radius: @radius-lg;
|
||||
background: linear-gradient(120deg, @primary-color, #6a5cff);
|
||||
box-shadow: @shadow-base;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user