Commit 5e6bcbb9 authored by 罗超's avatar 罗超

完成部分功能调整

parent cbf236c6
{
"openapi": "3.0.1",
"info": {
"title": "OTA平台项目",
"description": "",
"version": "1.0.0"
},
"tags": [
{
"name": "PlaceServices"
}
],
"paths": {
"/api/app/sys-management/place-services/tree": {
"get": {
"summary": "获取地点树形结构(洲 -> 国家 -> 地点)",
"deprecated": false,
"description": "",
"tags": [
"PlaceServices"
],
"parameters": [
{
"name": "__tenant",
"in": "header",
"description": "租户ID或租户名称(留空表示默认租户)",
"required": false,
"example": "",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Acme.SysManagement.Application.Contracts.Dtos.Place.PlaceTreeQueryDto"
}
}
}
},
"headers": {}
},
"400": {
"description": "Bad Request",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Volo.Abp.Http.RemoteServiceErrorResponse"
}
}
},
"headers": {}
},
"401": {
"description": "Unauthorized",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Volo.Abp.Http.RemoteServiceErrorResponse"
}
}
},
"headers": {}
},
"403": {
"description": "Forbidden",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Volo.Abp.Http.RemoteServiceErrorResponse"
}
}
},
"headers": {}
},
"404": {
"description": "Not Found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Volo.Abp.Http.RemoteServiceErrorResponse"
}
}
},
"headers": {}
},
"500": {
"description": "Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Volo.Abp.Http.RemoteServiceErrorResponse"
}
}
},
"headers": {}
},
"501": {
"description": "Server Error",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Volo.Abp.Http.RemoteServiceErrorResponse"
}
}
},
"headers": {}
}
},
"security": []
}
}
},
"components": {
"schemas": {
"Volo.Abp.Http.RemoteServiceValidationErrorInfo": {
"type": "object",
"properties": {
"message": {
"type": "string",
"nullable": true
},
"members": {
"type": "array",
"items": {
"type": "string"
},
"nullable": true
}
},
"additionalProperties": false
},
"Volo.Abp.Http.RemoteServiceErrorInfo": {
"type": "object",
"properties": {
"code": {
"type": "string",
"nullable": true
},
"message": {
"type": "string",
"nullable": true
},
"details": {
"type": "string",
"nullable": true
},
"data": {
"type": "object",
"additionalProperties": {
"type": "string"
},
"properties": {},
"nullable": true
},
"validationErrors": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Volo.Abp.Http.RemoteServiceValidationErrorInfo"
},
"nullable": true
}
},
"additionalProperties": false
},
"Acme.SysManagement.Application.Contracts.Dtos.Place.PlaceTreeEnum": {
"enum": [
"Continent",
"Country",
"Place"
],
"type": "string",
"description": "【枚举:Continent=1】\r\n【枚举:Country=2】\r\n【枚举:Place=3】\r\n"
},
"Volo.Abp.Http.RemoteServiceErrorResponse": {
"type": "object",
"properties": {
"error": {
"$ref": "#/components/schemas/Volo.Abp.Http.RemoteServiceErrorInfo"
}
},
"additionalProperties": false
},
"Acme.SysManagement.Application.Contracts.Dtos.Place.PlaceTreeQueryDto": {
"type": "object",
"properties": {
"value": {
"type": "string",
"nullable": true
},
"label": {
"type": "string",
"nullable": true
},
"type": {
"$ref": "#/components/schemas/Acme.SysManagement.Application.Contracts.Dtos.Place.PlaceTreeEnum"
},
"children": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Acme.SysManagement.Application.Contracts.Dtos.Place.PlaceTreeQueryDto"
},
"nullable": true
}
},
"additionalProperties": false
}
},
"securitySchemes": {}
},
"servers": []
}
\ No newline at end of file
...@@ -7,6 +7,7 @@ import LanguageSwitcher from './components/common/LanguageSwitcher.vue' ...@@ -7,6 +7,7 @@ import LanguageSwitcher from './components/common/LanguageSwitcher.vue'
import { configureArcoLocale, globalArcoLocale } from './i18n/arco' import { configureArcoLocale, globalArcoLocale } from './i18n/arco'
import { useUserStore } from './stores/user' import { useUserStore } from './stores/user'
import { useSystemConfigStore } from './stores/systemConfig' import { useSystemConfigStore } from './stores/systemConfig'
import { loadThemeFromConfig } from './utils/themeUtils'
const route = useRoute() const route = useRoute()
const { t, locale } = useI18n() const { t, locale } = useI18n()
...@@ -33,12 +34,26 @@ watch( ...@@ -33,12 +34,26 @@ watch(
{ immediate: true } { immediate: true }
) )
// 监听系统配置变化,动态应用配色
watch(
() => systemConfigStore.config?.webSiteColor,
(webSiteColor) => {
if (webSiteColor) {
loadThemeFromConfig(webSiteColor)
}
},
{ immediate: true }
)
onMounted(async () => { onMounted(async () => {
try { try {
// 初始化系统配置(在最开始执行) // 初始化系统配置(在最开始执行)
await systemConfigStore.initialize(location.hostname) await systemConfigStore.initialize(location.hostname)
//await systemConfigStore.getGroupInfoAsync() // 应用配色(如果配置已加载)
if (systemConfigStore.config?.webSiteColor) {
loadThemeFromConfig(systemConfigStore.config.webSiteColor)
}
// 初始化 Arco Design 国际化 // 初始化 Arco Design 国际化
await configureArcoLocale(locale.value) await configureArcoLocale(locale.value)
......
...@@ -30,3 +30,14 @@ body { ...@@ -30,3 +30,14 @@ body {
.arco-dropdown{ .arco-dropdown{
padding: 0 !important; padding: 0 !important;
} }
.overlap::before {
display: block;
content: "";
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
background: linear-gradient(transparent 60%, rgba(0, 0, 0, .85));
}
\ No newline at end of file
...@@ -112,6 +112,8 @@ export default { ...@@ -112,6 +112,8 @@ export default {
header: { header: {
destinations: 'Destinations', destinations: 'Destinations',
destinationsFailed: 'Failed to load destinations', destinationsFailed: 'Failed to load destinations',
recommond:'Recommend',
hotDestinations: 'Hot Destinations',
}, },
place:{ place:{
all:'Explore all' all:'Explore all'
......
...@@ -112,6 +112,8 @@ export default { ...@@ -112,6 +112,8 @@ export default {
header: { header: {
destinations: 'Điểm đến', destinations: 'Điểm đến',
destinationsFailed: 'Không tải được danh sách điểm đến', destinationsFailed: 'Không tải được danh sách điểm đến',
recommond:'Giới thiệu',
hotDestinations: 'Điểm đến nổi tiếng',
}, },
place:{ place:{
all:'Khám phá tất cả' all:'Khám phá tất cả'
......
...@@ -112,6 +112,8 @@ export default { ...@@ -112,6 +112,8 @@ export default {
header: { header: {
destinations: '目的地', destinations: '目的地',
destinationsFailed: '目的地数据加载失败', destinationsFailed: '目的地数据加载失败',
recommond:'推荐',
hotDestinations: '热门目的地',
}, },
place:{ place:{
all:'探索全部' all:'探索全部'
......
...@@ -112,6 +112,8 @@ export default { ...@@ -112,6 +112,8 @@ export default {
header: { header: {
destinations: '目的地', destinations: '目的地',
destinationsFailed: '目的地資料載入失敗', destinationsFailed: '目的地資料載入失敗',
recommond:'推薦',
hotDestinations: '熱門目的地',
}, },
place:{ place:{
all:'探索全部', all:'探索全部',
......
<template> <template>
<footer class="footer bg-gray-800 text-white"> <footer class="footer text-white">
<!-- 顶部区域:社交媒体 + Newsletter --> <!-- 顶部区域:社交媒体 + Newsletter -->
<div class="footer-top border-b border-gray-700 py-5"> <div class="footer-top border-b py-5">
<div class="footer-container py-8"> <div class="footer-container py-8">
<div class="flex flex-col md:flex-row items-center justify-between gap-6"> <div class="flex flex-col md:flex-row items-center justify-between gap-6">
<!-- 社交媒体图标 --> <!-- 社交媒体图标 -->
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
<p class="text-sm font-medium mb-1"> <p class="text-sm font-medium mb-1">
{{ t('footer.newsletter.title') }} {{ t('footer.newsletter.title') }}
</p> </p>
<p class="text-xs text-gray-400"> <p class="text-xs footer-subtitle">
{{ t('footer.newsletter.subtitle') }} {{ t('footer.newsletter.subtitle') }}
</p> </p>
</div> </div>
...@@ -46,7 +46,7 @@ ...@@ -46,7 +46,7 @@
{{ t('footer.newsletter.subscribe') }} {{ t('footer.newsletter.subscribe') }}
</a-button> </a-button>
</div> </div>
<p class="text-xs text-gray-500 mt-2"> <p class="text-xs footer-agreement mt-2">
{{ t('footer.newsletter.agreement') }} {{ t('footer.newsletter.agreement') }}
</p> </p>
</div> </div>
...@@ -55,7 +55,7 @@ ...@@ -55,7 +55,7 @@
</div> </div>
<!-- 中间区域:链接列 --> <!-- 中间区域:链接列 -->
<div class="footer-middle border-b border-gray-700 py-8"> <div class="footer-middle border-b py-8">
<div class="footer-container"> <div class="footer-container">
<div class="grid grid-cols-1 gap-8" :class="[`md:grid-cols-${bottomNavs.length+1}`]"> <div class="grid grid-cols-1 gap-8" :class="[`md:grid-cols-${bottomNavs.length+1}`]">
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
<h3 class="font-semibold mb-4">{{ t(item.navTitle) }}</h3> <h3 class="font-semibold mb-4">{{ t(item.navTitle) }}</h3>
<ul class="space-y-2"> <ul class="space-y-2">
<li v-for="child in item.childList" :key="child.id"> <li v-for="child in item.childList" :key="child.id">
<a :href="child.navUrl??'javascript:void(0);'" class="text-sm text-gray-300 hover:text-white transition-colors"> <a :href="child.navUrl??'javascript:void(0);'" class="text-sm footer-link transition-colors">
{{ t(child.navTitle) }} {{ t(child.navTitle) }}
</a> </a>
</li> </li>
...@@ -97,7 +97,7 @@ ...@@ -97,7 +97,7 @@
<div class="footer-bottom py-4"> <div class="footer-bottom py-4">
<div class="footer-container"> <div class="footer-container">
<div class="flex flex-col md:flex-row items-center justify-between gap-4"> <div class="flex flex-col md:flex-row items-center justify-between gap-4">
<div class="text-sm text-gray-400"> <div class="text-sm footer-copyright">
<span v-if="systemConfigStore.config?.domainName"> <span v-if="systemConfigStore.config?.domainName">
{{ systemConfigStore.config.domainName }} {{ systemConfigStore.config.domainName }}
</span> </span>
...@@ -108,7 +108,7 @@ ...@@ -108,7 +108,7 @@
<a-button <a-button
type="text" type="text"
size="small" size="small"
class="back-to-top !text-gray-400 hover:!text-white" class="back-to-top footer-back-to-top"
@click="scrollToTop" @click="scrollToTop"
> >
<template #icon> <template #icon>
...@@ -243,7 +243,10 @@ const scrollToTop = () => { ...@@ -243,7 +243,10 @@ const scrollToTop = () => {
<style scoped lang="scss"> <style scoped lang="scss">
.footer { .footer {
background-color: #2d2d2d; // 使用动态配色:基于主色生成深色背景
// 如果主色较浅,使用深色背景;如果主色较深,使用稍浅的背景
background-color: rgb(var(--arcoblue-8));
color: white;
} }
.footer-container { .footer-container {
...@@ -252,6 +255,13 @@ const scrollToTop = () => { ...@@ -252,6 +255,13 @@ const scrollToTop = () => {
padding: 0 20px; padding: 0 20px;
} }
// 顶部和中间区域的边框使用动态配色
.footer-top,
.footer-middle {
border-color: rgba(255, 255, 255, 0.1);
}
// 社交媒体图标
.social-icon { .social-icon {
display: flex; display: flex;
align-items: center; align-items: center;
...@@ -260,6 +270,47 @@ const scrollToTop = () => { ...@@ -260,6 +270,47 @@ const scrollToTop = () => {
height: 32px; height: 32px;
color: white; color: white;
cursor: pointer; cursor: pointer;
transition: all 0.2s ease;
&:hover {
opacity: 0.8;
color: rgb(var(--arcoblue-5));
}
}
// Newsletter 副标题和协议文字使用动态文本色
.footer-subtitle {
color: rgba(255, 255, 255, 0.7);
}
.footer-agreement {
color: rgba(255, 255, 255, 0.6);
}
// 链接使用动态配色
.footer-link {
color: rgba(255, 255, 255, 0.7);
&:hover {
color: rgb(var(--arcoblue-5));
}
}
// 版权信息使用动态文本色
.footer-copyright {
color: rgba(255, 255, 255, 0.7);
}
// 返回顶部按钮使用动态配色
.footer-back-to-top {
min-width: auto;
padding: 4px 8px;
color: rgba(255, 255, 255, 0.7) !important;
&:hover {
color: rgb(var(--arcoblue-5)) !important;
background-color: rgba(var(--arcoblue-6), 0.1) !important;
}
} }
.payment-icon { .payment-icon {
...@@ -276,6 +327,7 @@ const scrollToTop = () => { ...@@ -276,6 +327,7 @@ const scrollToTop = () => {
padding: 4px 8px; padding: 4px 8px;
} }
// 输入框使用动态配色
:deep(.arco-input) { :deep(.arco-input) {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.2); border-color: rgba(255, 255, 255, 0.2);
...@@ -287,7 +339,18 @@ const scrollToTop = () => { ...@@ -287,7 +339,18 @@ const scrollToTop = () => {
&:focus { &:focus {
background-color: rgba(255, 255, 255, 0.15); background-color: rgba(255, 255, 255, 0.15);
border-color: rgba(255, 255, 255, 0.3); border-color: rgb(var(--arcoblue-5));
}
}
// 按钮使用动态配色
:deep(.arco-btn-primary) {
background-color: rgb(var(--arcoblue-6));
border-color: rgb(var(--arcoblue-6));
&:hover {
background-color: rgb(var(--arcoblue-5));
border-color: rgb(var(--arcoblue-5));
} }
} }
</style> </style>
......
<template> <template>
<a-dropdown trigger="hover" position="bl"> <a-dropdown trigger="click" position="bl">
<a-button type="text" class="!px-2"> <a-button type="text" class="!px-2">
<v-icon name="geolocation" class="text-[16px]" /> <v-icon name="geolocation" class="text-[16px]" />
<span class="ml-1">{{ t('header.destinations') }}</span> <span class="ml-1">{{ t('header.destinations') }}</span>
...@@ -7,6 +7,9 @@ ...@@ -7,6 +7,9 @@
<template #content> <template #content>
<div class="destination-panel" v-if="placeTree.length"> <div class="destination-panel" v-if="placeTree.length">
<div class="continent-list"> <div class="continent-list">
<div class="continent-item" :class="{ active: -1 === activeIndex }" @mouseenter="activeIndex = -1">
{{ t('header.recommond') }}
</div>
<div <div
v-for="(continent, index) in placeTree" v-for="(continent, index) in placeTree"
:key="continent.value || index" :key="continent.value || index"
...@@ -17,20 +20,40 @@ ...@@ -17,20 +20,40 @@
{{ continent.label }} {{ continent.label }}
</div> </div>
</div> </div>
<div class="continent-content" v-if="activeContinent"> <div v-show="activeIndex === -1" class="p-4 flex-1 min-w-0 overflow-y-auto">
<div class="font-bold text-lg">
{{ t('header.hotDestinations') }}
</div>
<div class="pt-4 mb-2" v-for="(val,index) in hotDestinations" :key="index">
<div class="font-bold text-md mb-3">{{ val.name }}</div>
<div class="grid grid-cols-4 gap-4">
<div class="rounded-md flex items-center justify-center cursor-pointer overlap relative overflow-hidden !aspect-[3/2] !object-cover" v-for="(item,index) in val.reList" :key="index">
<a-image :src="item.placeDetail?.backgroundImage || ''" show-loader></a-image>
<div class="absolute bottom-0 left-0 right-0 p-2">
<div class="text-white text-sm font-bold">
{{ item.placeDetail?.name }}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="continent-content" v-if="activeContinent && activeIndex !== -1">
<template v-for="country in activeContinent.children" :key="country.value"> <template v-for="country in activeContinent.children" :key="country.value">
<div class="country-block"> <div class="country-block">
<div class="country-title"> <div class="country-title">
{{ country.label }} {{ country.label }}
</div> </div>
<div class="place-grid"> <div class="place-grid">
<span class="place-item text-gray-600 hover:bg-gray-200 hover:text-gray-800 rounded-md"> <span class="place-item destination-place-item rounded-md">
{{ t('place.all') }} {{ t('place.all') }}
</span> </span>
<span <span
v-for="place in country.children" v-for="place in country.children"
:key="place.value" :key="place.value"
class="place-item text-gray-600 hover:bg-gray-200 hover:text-gray-800 rounded-md" class="place-item destination-place-item rounded-md"
> >
{{ place.label }} {{ place.label }}
</span> </span>
...@@ -49,29 +72,42 @@ ...@@ -49,29 +72,42 @@
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, computed } from 'vue' import { onMounted, ref, computed } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import PlaceService from '@/services/PlaceService' import PlaceService, { type PlaceGroupOutputDto } from '@/services/PlaceService'
import type { PlaceTreeNode } from '@/types/place' import type { PlaceTreeNode } from '@/types/place'
import { Message } from '@arco-design/web-vue' import { Message } from '@arco-design/web-vue'
import { useSystemConfigStore } from '@/stores'
const { t } = useI18n() const { t } = useI18n()
const placeTree = ref<PlaceTreeNode[]>([]) const placeTree = ref<PlaceTreeNode[]>([])
const activeIndex = ref(0) const activeIndex = ref(-1)
const activeContinent = computed(() => placeTree.value[activeIndex.value]) const activeContinent = computed(() => placeTree.value[activeIndex.value])
const systemConfigStore = useSystemConfigStore()
const hotDestinations = ref<PlaceGroupOutputDto[]>([])
const loadPlaces = async () => { const loadPlaces = async () => {
try { try {
const data = await PlaceService.getPlaceTreeAsync() const data = await PlaceService.getPlaceTreeAsync()
placeTree.value = data || [] placeTree.value = data || []
activeIndex.value = 0 activeIndex.value = -1
console.log(data) console.log(data)
} catch (error) { } catch (error) {
Message.error(t('header.destinationsFailed')) Message.error(t('header.destinationsFailed'))
} }
} }
const loadHotDestinations = async () => {
try {
const data = await PlaceService.getRecommendPlaceListAsync(systemConfigStore.distributorId || 0)
hotDestinations.value = data || []
} catch (error) {
Message.error(t('header.hotDestinationsFailed'))
}
}
onMounted(() => { onMounted(() => {
loadPlaces() loadPlaces()
loadHotDestinations()
}) })
</script> </script>
...@@ -113,7 +149,7 @@ onMounted(() => { ...@@ -113,7 +149,7 @@ onMounted(() => {
padding: 15px 18px; padding: 15px 18px;
cursor: pointer; cursor: pointer;
font-weight: 500; font-weight: 500;
color: #4b5563; color: rgb(var(--customColor-text-7));
transition: all .2s ease; transition: all .2s ease;
} }
.continent-item.active, .continent-item.active,
...@@ -129,12 +165,12 @@ onMounted(() => { ...@@ -129,12 +165,12 @@ onMounted(() => {
.country-block + .country-block{ .country-block + .country-block{
margin-top: 18px; margin-top: 18px;
padding-top: 18px; padding-top: 18px;
border-top: 1px solid #f1f1f1; border-top: 1px solid rgb(var(--arcoblue-7));
} }
.country-title{ .country-title{
font-weight: 600; font-weight: 600;
margin-bottom: 12px; margin-bottom: 12px;
color: #111; color: rgb(var(--customColor-text-10));
} }
.place-grid{ .place-grid{
display: grid; display: grid;
...@@ -146,6 +182,17 @@ onMounted(() => { ...@@ -146,6 +182,17 @@ onMounted(() => {
cursor: pointer; cursor: pointer;
padding: 10px 10px; padding: 10px 10px;
} }
// 目的地项使用动态配色
.destination-place-item {
color: rgb(var(--customColor-text-7));
transition: all 0.2s ease;
&:hover {
background-color: rgba(var(--arcoblue-6), 0.1);
color: rgb(var(--arcoblue-6));
}
}
.destination-empty{ .destination-empty{
width: 240px; width: 240px;
height: 120px; height: 120px;
......
...@@ -71,11 +71,45 @@ const handleMenuClick = (key: string) => { ...@@ -71,11 +71,45 @@ const handleMenuClick = (key: string) => {
<style scoped lang="scss"> <style scoped lang="scss">
.web-header-menu { .web-header-menu {
border-bottom: none; border-bottom: none;
}
// :deep(.arco-menu-inner) { // 导航菜单项使用动态配色
// padding: 10px 0 !important; :deep(.arco-menu-item) {
// overflow: hidden; color: rgb(var(--customColor-text-10));
// } transition: all 0.2s ease;
&:hover {
color: rgb(var(--arcoblue-6));
background-color: rgba(var(--arcoblue-6), 0.05);
}
&.arco-menu-selected {
color: rgb(var(--arcoblue-6));
background-color: rgba(var(--arcoblue-6), 0.08);
&::after {
background-color: rgb(var(--arcoblue-6));
}
}
}
// 子菜单标题使用动态配色
:deep(.arco-sub-menu-title) {
color: rgb(var(--customColor-text-10));
transition: all 0.2s ease;
&:hover {
color: rgb(var(--arcoblue-6));
background-color: rgba(var(--arcoblue-6), 0.05);
}
}
// 子菜单项使用动态配色
:deep(.arco-menu-item) {
&.arco-menu-selected {
color: rgb(var(--arcoblue-6));
background-color: rgba(var(--arcoblue-6), 0.08);
}
} }
</style> </style>
...@@ -192,11 +192,11 @@ const handleGoHome = () => { ...@@ -192,11 +192,11 @@ const handleGoHome = () => {
font-size: 13px; font-size: 13px;
} }
.currency-option:hover{ .currency-option:hover{
background: rgba(74, 144, 226, 0.1); background: rgba(var(--arcoblue-6), 0.1);
color: rgb(var(--arcoblue-6)); color: rgb(var(--arcoblue-6));
} }
.currency-option.active{ .currency-option.active{
background: rgba(74, 144, 226, 0.15); background: rgba(var(--arcoblue-6), 0.15);
color: rgb(var(--arcoblue-6)); color: rgb(var(--arcoblue-6));
font-weight: 600; font-weight: 600;
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="fixed top-0 left-0 right-0 z-10 shadow-sm customPrimary-bg-7 flex justify-center"> <div class="fixed top-0 left-0 right-0 z-10 shadow-sm customPrimary-bg-7 flex justify-center">
<HeaderTopBar /> <HeaderTopBar />
</div> </div>
<div class="h-[1px] w-full bg-gray-700/5"></div> <div class="header-divider"></div>
<HeaderNavBar /> <HeaderNavBar />
</header> </header>
</template> </template>
...@@ -13,8 +13,35 @@ import HeaderNavBar from './HeaderNavBar.vue' ...@@ -13,8 +13,35 @@ import HeaderNavBar from './HeaderNavBar.vue'
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
:deep(.arco-btn-primary:hover, .arco-btn-primary){ // 确保所有按钮使用动态配色
:deep(.arco-btn-primary) {
background-color: rgb(var(--arcoblue-6)); background-color: rgb(var(--arcoblue-6));
border-color: rgb(var(--arcoblue-6));
&:hover {
background-color: rgb(var(--arcoblue-5));
border-color: rgb(var(--arcoblue-5));
}
&:active {
background-color: rgb(var(--arcoblue-6));
border-color: rgb(var(--arcoblue-6));
}
}
// 文本按钮的hover效果
:deep(.arco-btn-text) {
&:hover {
background-color: rgba(var(--arcoblue-6), 0.08);
color: rgb(var(--arcoblue-6));
}
}
// 分隔线使用动态配色
.header-divider {
height: 1px;
width: 100%;
background-color: rgba(var(--arcoblue-8), 0.05);
} }
</style> </style>
...@@ -60,6 +60,42 @@ export interface PlaceOutputDto extends PlaceInputDto { ...@@ -60,6 +60,42 @@ export interface PlaceOutputDto extends PlaceInputDto {
id: string id: string
} }
/**
* 目的地推荐DTO
*/
export interface PlaceReDto {
/** 目的地名称 */
name?: string | null
/** 背景图片 */
backgroundImage?: string | null
/** 状态(true=启用,false=禁用) */
state?: boolean | null
}
/**
* 分组目的地关联DTO
*/
export interface GroupReDto {
/** 目的地ID */
placeId: string
/** 目的地详情 */
placeDetail: PlaceReDto
}
/**
* 目的地分组输出DTO
*/
export interface PlaceGroupOutputDto {
/** 分组ID */
id: string
/** 推荐分组名称 */
name?: string | null
/** 排序 */
orderNum: number
/** 目的地列表 */
reList?: GroupReDto[] | null
}
class PlaceService { class PlaceService {
static async getPlaceTreeAsync(): Promise<PlaceTreeNode[]> { static async getPlaceTreeAsync(): Promise<PlaceTreeNode[]> {
const response = await OtaRequest.get('/sys-management/place-services/tree') const response = await OtaRequest.get('/sys-management/place-services/tree')
...@@ -71,6 +107,20 @@ class PlaceService { ...@@ -71,6 +107,20 @@ class PlaceService {
const response = await OtaRequest.get('/sys-management/place-services/paged',params) const response = await OtaRequest.get('/sys-management/place-services/paged',params)
return response as unknown as PagedResult<PlaceOutputDto> return response as unknown as PagedResult<PlaceOutputDto>
} }
/**
* 获取推荐目的地列表
* @param distributorId 分销商ID
* @returns 推荐目的地分组列表
*/
static async getRecommendPlaceListAsync(
distributorId: number
): Promise<PlaceGroupOutputDto[]> {
const response = await OtaRequest.get(
`/sys-management/place-services/recommend-place-list/${distributorId}`
)
return response as unknown as PlaceGroupOutputDto[]
}
} }
export default PlaceService export default PlaceService
......
/**
* 主题配色工具类
* 支持从后端配置动态加载和应用配色方案
*/
export interface ThemeColors {
// 主色系(基于webSiteColor生成)
primary: {
[key: number]: string
1: string // 最深
2: string
3: string
4: string
5: string // hover色
6: string // 主色
7: string // 浅色背景
8: string // 深色文字
9: string // 强调色(如橙色)
10: string // 最浅背景
11: string // 极浅背景
}
// 文本色系
text: {
[key: number]: string
10: string // 最深
9: string
8: string
7: string
6: string
5: string
4: string
}
// 中性色(灰色系)
gray: {
[key: number]: string
10: string
9: string
8: string
7: string
6: string
5: string
4: string
}
}
/**
* 默认配色方案(绿色系,兼容现有设计)
*/
const DEFAULT_THEME: ThemeColors = {
primary: {
1: '#48605B',
2: '#EBEBE9',
3: '#8AA88A',
4: '#C0CEB3',
5: '#95A997',
6: '#4A664D',
7: '#e3e6da',
8: '#263628',
9: '#FF9707',
10: '#F5F6F0',
11: '#F0F1EB',
},
text: {
10: '#0c150d',
9: '#5a5a5a',
8: '#133537',
7: '#606961',
6: '#A3A4A0',
5: '#4A664D',
4: '#8EAD8E',
},
gray: {
10: '#0c150d',
9: '#5a5a5a',
8: '#133537',
7: '#606961',
6: '#A3A4A0',
5: '#4A664D',
4: '#8EAD8E',
},
}
/**
* 将十六进制颜色转换为RGB值(用于CSS变量)
*/
function hexToRgb(hex: string): string {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
if (!result) return '0, 0, 0'
const r = parseInt(result[1], 16)
const g = parseInt(result[2], 16)
const b = parseInt(result[3], 16)
return `${r}, ${g}, ${b}`
}
/**
* 根据主色生成配色方案
* @param primaryColor 主色(十六进制,如 #4A664D)
* @returns 完整的配色方案
*/
export function generateThemeFromPrimary(primaryColor: string): ThemeColors {
// 如果主色无效,使用默认配色
if (!primaryColor || !/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(primaryColor)) {
return DEFAULT_THEME
}
// 解析主色RGB
const rgb = hexToRgb(primaryColor).split(', ').map(Number)
const [r, g, b] = rgb
// 生成色阶(基于主色进行明暗变化)
const generateShade = (ratio: number): string => {
const newR = Math.max(0, Math.min(255, Math.round(r * ratio)))
const newG = Math.max(0, Math.min(255, Math.round(g * ratio)))
const newB = Math.max(0, Math.min(255, Math.round(b * ratio)))
return `#${[newR, newG, newB].map(x => x.toString(16).padStart(2, '0')).join('')}`
}
const generateTint = (ratio: number): string => {
const newR = Math.max(0, Math.min(255, Math.round(r + (255 - r) * ratio)))
const newG = Math.max(0, Math.min(255, Math.round(g + (255 - g) * ratio)))
const newB = Math.max(0, Math.min(255, Math.round(b + (255 - b) * ratio)))
return `#${[newR, newG, newB].map(x => x.toString(16).padStart(2, '0')).join('')}`
}
// 生成色阶
return {
primary: {
1: generateShade(0.6), // 最深
2: generateTint(0.92), // 极浅
3: generateTint(0.5), // 中等浅
4: generateTint(0.7), // 浅
5: generateTint(0.3), // hover色(稍浅)
6: primaryColor, // 主色
7: generateTint(0.88), // 浅色背景
8: generateShade(0.3), // 深色文字
9: DEFAULT_THEME.primary[9], // 强调色保持橙色
10: generateTint(0.95), // 最浅背景
11: generateTint(0.97), // 极浅背景
},
text: {
10: generateShade(0.1), // 最深文字
9: generateShade(0.35), // 深灰
8: generateShade(0.25), // 中深灰
7: generateShade(0.4), // 中灰
6: generateTint(0.35), // 浅灰
5: primaryColor, // 主色文字
4: generateTint(0.45), // 浅色文字
},
gray: DEFAULT_THEME.gray, // 灰色系保持默认
}
}
/**
* 应用配色方案到CSS变量
* @param theme 配色方案
*/
export function applyTheme(theme: ThemeColors): void {
const root = document.documentElement
const body = document.body
// 应用主色系(Arco Design兼容)
body.style.setProperty('--arcoblue-11', hexToRgb(theme.primary[11]))
body.style.setProperty('--arcoblue-10', hexToRgb(theme.primary[10]))
body.style.setProperty('--arcoblue-9', hexToRgb(theme.primary[9]))
body.style.setProperty('--arcoblue-8', hexToRgb(theme.primary[8]))
body.style.setProperty('--arcoblue-7', hexToRgb(theme.primary[7]))
body.style.setProperty('--arcoblue-6', hexToRgb(theme.primary[6]))
body.style.setProperty('--arcoblue-5', hexToRgb(theme.primary[5]))
body.style.setProperty('--arcoblue-4', hexToRgb(theme.primary[4]))
body.style.setProperty('--arcoblue-3', hexToRgb(theme.primary[3]))
body.style.setProperty('--arcoblue-2', hexToRgb(theme.primary[2]))
body.style.setProperty('--arcoblue-1', hexToRgb(theme.primary[1]))
// 应用自定义主色系
root.style.setProperty('--customPrimary-11', theme.primary[11])
root.style.setProperty('--customPrimary-10', theme.primary[10])
root.style.setProperty('--customPrimary-9', theme.primary[9])
root.style.setProperty('--customPrimary-8', theme.primary[8])
root.style.setProperty('--customPrimary-7', theme.primary[7])
root.style.setProperty('--customPrimary-6', theme.primary[6])
root.style.setProperty('--customPrimary-5', theme.primary[5])
root.style.setProperty('--customPrimary-4', theme.primary[4])
root.style.setProperty('--customPrimary-3', theme.primary[3])
root.style.setProperty('--customPrimary-2', theme.primary[2])
root.style.setProperty('--customPrimary-1', theme.primary[1])
// 应用文本色系
root.style.setProperty('--customColor-text-10', theme.text[10])
root.style.setProperty('--customColor-text-9', theme.text[9])
root.style.setProperty('--customColor-text-8', theme.text[8])
root.style.setProperty('--customColor-text-7', theme.text[7])
root.style.setProperty('--customColor-text-6', theme.text[6])
root.style.setProperty('--customColor-text-5', theme.text[5])
root.style.setProperty('--customColor-text-4', theme.text[4])
// 应用灰色系
body.style.setProperty('--gray-10', hexToRgb(theme.gray[10]))
body.style.setProperty('--gray-9', hexToRgb(theme.gray[9]))
body.style.setProperty('--gray-8', hexToRgb(theme.gray[8]))
body.style.setProperty('--gray-7', hexToRgb(theme.gray[7]))
body.style.setProperty('--gray-6', hexToRgb(theme.gray[6]))
body.style.setProperty('--gray-5', hexToRgb(theme.gray[5]))
body.style.setProperty('--gray-4', hexToRgb(theme.gray[4]))
}
/**
* 从后端配置加载并应用配色
* @param webSiteColor 网站主色(十六进制)
*/
export function loadThemeFromConfig(webSiteColor?: string | null): void {
if (webSiteColor) {
const theme = generateThemeFromPrimary(webSiteColor)
applyTheme(theme)
} else {
// 使用默认配色
applyTheme(DEFAULT_THEME)
}
}
/**
* 重置为默认配色
*/
export function resetToDefaultTheme(): void {
applyTheme(DEFAULT_THEME)
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment