2861 lines
68 KiB
Plaintext
2861 lines
68 KiB
Plaintext
<!-- 公司模式首页 - 严格UTS Android规范,弹性布局+waterfall+多语言+AI助手浮动+热门搜索浮动 -->
|
||
<template>
|
||
<scroll-view direction="vertical" class="company-home" :enable-back-to-top="true">
|
||
<!-- 顶部导航栏 -->
|
||
<view class="header">
|
||
<view class="header-content">
|
||
<view class="header-left">
|
||
<image class="company-logo" :src="companyLogoUrl" mode="aspectFit" />
|
||
<text class="company-name">{{ companyDisplayName }}</text>
|
||
<view v-if="isLoadingCompanyConfig" class="loading-indicator">
|
||
<text class="loading-dot">●</text>
|
||
</view>
|
||
</view>
|
||
<view class="header-center">
|
||
<view class="search-btn" @click="showSearchModal">
|
||
<text class="search-icon">🔍</text>
|
||
<text class="search-text">{{ searchPlaceholder }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="header-right">
|
||
<view class="lang-switch" @click="showLanguageSelector">
|
||
<text class="lang-text">{{ currentLanguageName }}</text>
|
||
<text class="lang-arrow">⌄</text>
|
||
</view>
|
||
<view v-if="!isUserLogin" class="login-btn" @click="showLoginModal">
|
||
<text class="login-text">{{ loginText }}</text>
|
||
</view>
|
||
<view v-else class="user-profile" @click="showUserMenu">
|
||
<image class="user-avatar" :src="userAvatarUrl" mode="aspectFill" />
|
||
<text class="user-nickname">{{ userNickname }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 热门专题展示区 -->
|
||
<view class="featured-topics-section">
|
||
<view class="section-header">
|
||
<text class="section-title">{{ tt('mt.topic.featured') }}</text>
|
||
<view class="section-actions">
|
||
<view class="more-btn" @click="navigateToTopics">
|
||
<text class="more-text">{{ tt('mt.button.viewAll') }}</text>
|
||
<text class="more-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 热门专题横向滚动 -->
|
||
<scroll-view direction="horizontal" class="topics-scroll" :scroll-x="true">
|
||
<view class="topics-container">
|
||
<view v-for="(topic, index) in featuredTopicsList" :key="topic.id" class="topic-card"
|
||
@click="navigateToTopicDetail(topic)">
|
||
<view class="topic-cover"
|
||
:style="{ backgroundImage: `url(${topic.cover_image_url || '/static/default-topic.png'})` }">
|
||
<view class="topic-overlay">
|
||
<view class="topic-type-badge"
|
||
:style="{ backgroundColor: getTopicTypeColor(topic.topic_type) }">
|
||
<text class="badge-text">{{ tt(`mt.topicType.${topic.topic_type}`) }}</text>
|
||
</view>
|
||
<view class="topic-stats">
|
||
<text class="stat-item">📄 {{ topic.content_count }}</text>
|
||
<text class="stat-item">👁 {{ topic.view_count }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="topic-info">
|
||
<text class="topic-title">{{ topic.title }}</text>
|
||
<text class="topic-description">{{ topic.description }}</text>
|
||
<view class="topic-meta">
|
||
<text class="topic-update-time">{{ formatTopicTime(topic.updated_at) }}</text>
|
||
<view class="topic-priority" v-if="topic.priority_level > 5">
|
||
<text class="priority-icon">🔥</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 加载中占位符 -->
|
||
<view v-if="isLoadingTopics" class="topic-card loading-card">
|
||
<view class="topic-cover loading-shimmer"></view>
|
||
<view class="topic-info">
|
||
<view class="loading-line short"></view>
|
||
<view class="loading-line medium"></view>
|
||
<view class="loading-line long"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 主要业务展示区 -->
|
||
<view class="business-section">
|
||
<view class="section-header">
|
||
<text class="section-title">{{ businessSectionTitle }}</text>
|
||
<view class="section-actions">
|
||
<view class="view-mode-btn" @click="toggleViewMode">
|
||
<text class="view-mode-text">{{ viewModeText }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 业务卡片瀑布流 -->
|
||
<!-- #ifdef APP -->
|
||
<waterflow :column-count="waterfallColumns" :column-gap="waterfallGap" :row-gap="waterfallGap"
|
||
@loadmore="handleLoadMore" class="business-waterfall">
|
||
<view v-for="(item, index) in businessList" :key="item.id" class="business-card"
|
||
@click="navigateToBusinessDetail(item)" :class="{ 'card-loading': item.loading }">
|
||
<view class="card-image-container">
|
||
<image class="card-image" :src="item.image_url" mode="aspectFill" @error="handleImageError" />
|
||
<view class="card-category">
|
||
<text class="category-text">{{ item.category_name_text }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="card-content">
|
||
<text class="card-title">{{ item.title }}</text>
|
||
<text class="card-description">{{ item.summary }}</text>
|
||
<view class="card-meta">
|
||
<text class="meta-text">{{ item.published_at }}</text>
|
||
<text class="meta-views">{{ item.view_count }} {{ viewsText }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="card-actions">
|
||
<view class="action-btn primary">
|
||
<text class="action-text">{{ learnMoreText }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</waterflow>
|
||
<!-- #endif -->
|
||
<!-- #ifndef APP -->
|
||
<view class="business-flex-container">
|
||
<view v-for="(item, index) in businessList" :key="item.id" class="business-card flex-card"
|
||
@click="navigateToBusinessDetail(item)" :class="{ 'card-loading': item.loading }">
|
||
<view class="card-image-container">
|
||
<image class="card-image" :src="item.image_url" mode="aspectFill" @error="handleImageError" />
|
||
<view class="card-category">
|
||
<text class="category-text">{{ item.category_name_text }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="card-content">
|
||
<text class="card-title">{{ item.title }}</text>
|
||
<text class="card-description">{{ item.summary }}</text>
|
||
<view class="card-meta">
|
||
<text class="meta-text">{{ item.published_at }}</text>
|
||
<text class="meta-views">{{ item.view_count }} {{ viewsText }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="card-actions">
|
||
<view class="action-btn primary">
|
||
<text class="action-text">{{ learnMoreText }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 非APP端的加载更多按钮 -->
|
||
<view v-if="pageState.hasMore && !pageState.loading && businessList.length > 0" class="load-more-section">
|
||
<view class="load-more-btn" @click="handleLoadMore">
|
||
<text class="load-more-text">{{ loadMoreText }}</text>
|
||
</view>
|
||
</view>
|
||
<!-- #endif -->
|
||
|
||
<!-- 加载状态 -->
|
||
<view v-if="isLoadingBusiness && businessList.length === 0" class="loading-section">
|
||
<text class="loading-text">{{ loadingText }}</text>
|
||
</view>
|
||
|
||
<!-- 错误状态 -->
|
||
<view v-if="pageState.error !== null && businessList.length === 0" class="error-section">
|
||
<text class="error-icon">⚠️</text>
|
||
<text class="error-title">{{ errorTitle }}</text>
|
||
<text class="error-desc">{{ pageState.error }}</text>
|
||
<view class="retry-btn" @click="retryLoad">
|
||
<text class="retry-text">{{ retryText }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 空状态 -->
|
||
<view v-if="businessList.length === 0 && !isLoadingBusiness && pageState.error === null"
|
||
class="empty-section">
|
||
<text class="empty-icon">📋</text>
|
||
<text class="empty-title">{{ emptyTitle }}</text>
|
||
<text class="empty-desc">{{ emptyDescription }}</text>
|
||
<view class="refresh-btn" @click="refreshBusinessData">
|
||
<text class="refresh-text">{{ refreshText }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 多媒体内容展示区 -->
|
||
<view class="multimedia-section">
|
||
<view class="section-header">
|
||
<text class="section-title">{{ tt('mt.multimedia.featured') }}</text>
|
||
<view class="section-actions">
|
||
<view class="more-btn" @click="navigateToMultimedia">
|
||
<text class="more-text">{{ tt('mt.button.viewAll') }}</text>
|
||
<text class="more-arrow">›</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 多媒体内容横向滚动 -->
|
||
<scroll-view direction="horizontal" class="multimedia-scroll" :scroll-x="true">
|
||
<view class="multimedia-container">
|
||
<view v-for="(item, index) in multimediaList" :key="item.id" class="multimedia-card"
|
||
:class="{ 'video-card': item.content_type === 'video', 'audio-card': item.content_type === 'audio', 'image-card': item.content_type === 'images' }"
|
||
@click="navigateToMultimediaDetail(item)">
|
||
<view class="multimedia-cover"
|
||
:style="{ backgroundImage: `url(${item.cover_image_url || getDefaultCoverByType(item.content_type)})` }">
|
||
<view class="multimedia-overlay">
|
||
<view class="content-type-badge"
|
||
:style="{ backgroundColor: getContentTypeColor(item.content_type) }">
|
||
<text class="type-icon">{{ getContentTypeIcon(item.content_type) }}</text>
|
||
<text class="type-text">{{ tt(`mt.contentType.${item.content_type}`) }}</text>
|
||
</view>
|
||
<view class="multimedia-stats">
|
||
<text class="stat-item">👁 {{ item.view_count }}</text>
|
||
<text class="stat-item" v-if="item.content_type === 'video'">🎬
|
||
{{ formatDuration(item.duration) }}</text>
|
||
<text class="stat-item" v-if="item.content_type === 'audio'">🎵
|
||
{{ formatDuration(item.duration) }}</text>
|
||
<text class="stat-item" v-if="item.content_type === 'images'">📷
|
||
{{ item.image_count }}</text>
|
||
</view>
|
||
<view class="play-overlay"
|
||
v-if="item.content_type === 'video' || item.content_type === 'audio'">
|
||
<text class="play-icon">▶</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="multimedia-info">
|
||
<text class="multimedia-title">{{ item.title }}</text>
|
||
<text class="multimedia-description">{{ item.summary }}</text>
|
||
<view class="multimedia-meta">
|
||
<text class="multimedia-date">{{ formatContentTime(item.published_at) }}</text>
|
||
<view class="multimedia-category">
|
||
<text class="category-text">{{ item.category_name_text }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 加载中占位符 -->
|
||
<view v-if="isLoadingMultimedia" class="multimedia-card loading-card">
|
||
<view class="multimedia-cover loading-shimmer"></view>
|
||
<view class="multimedia-info">
|
||
<view class="loading-line short"></view>
|
||
<view class="loading-line medium"></view>
|
||
<view class="loading-line long"></view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 专题快速入口区域 -->
|
||
<view class="topics-quick-access">
|
||
<view class="quick-access-header">
|
||
<text class="quick-access-title">{{ tt('mt.topic.quickAccess') }}</text>
|
||
</view>
|
||
<view class="quick-access-grid">
|
||
<view v-for="category in topicCategoriesList" :key="category.type" class="quick-access-item"
|
||
@click="navigateToTopicsByType(category.type)">
|
||
<view class="access-icon-container" :style="{ backgroundColor: category.color }">
|
||
<text class="access-icon">{{ category.icon }}</text>
|
||
</view>
|
||
<text class="access-label">{{ tt(`mt.topicType.${category.type}`) }}</text>
|
||
<text class="access-count">{{ category.count }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 底部公司信息 -->
|
||
<view class="footer-section" v-if="showFooter">
|
||
<view class="footer-content">
|
||
<view class="footer-main">
|
||
<image class="footer-logo" :src="companyLogoUrl" mode="aspectFit" />
|
||
<view class="footer-info">
|
||
<text class="footer-company-name">{{ companyDisplayName }}</text>
|
||
<text class="footer-slogan">{{ companySlogan }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="footer-details">
|
||
<view class="contact-info">
|
||
<text class="contact-label">{{ contactLabel }}</text>
|
||
<text class="contact-value">{{ companyPhone }}</text>
|
||
<text class="contact-value">{{ companyEmail }}</text>
|
||
</view>
|
||
<view class="address-info">
|
||
<text class="address-label">{{ addressLabel }}</text>
|
||
<text class="address-value">{{ companyAddress }}</text>
|
||
</view>
|
||
<view class="legal-info">
|
||
<text class="icp-text">{{ companyIcp }}</text>
|
||
</view>
|
||
</view>
|
||
<view class="social-links">
|
||
<view v-for="social in socialMediaList" :key="social.id" class="social-item"
|
||
@click="openSocialLink(social)">
|
||
<image class="social-icon" :src="social.iconUrl" mode="aspectFit" />
|
||
<text class="social-name">{{ social.name }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 浮动热门搜索 -->
|
||
<view class="hot-search-float" v-if="hotSearchVisible">
|
||
<view class="hot-search-header">
|
||
<text class="hot-search-title">{{ hotSearchTitle }}</text>
|
||
<view class="hot-search-close" @click="hideHotSearch">
|
||
<text class="close-icon">×</text>
|
||
</view>
|
||
</view>
|
||
<view class="hot-search-list">
|
||
<view v-for="(keyword, index) in hotKeywordsList" :key="keyword.id" class="hot-keyword-item"
|
||
@click="searchKeyword(keyword)">
|
||
<text class="keyword-rank">{{ index + 1 }}</text>
|
||
<text class="keyword-text">{{ keyword.text }}</text>
|
||
<text class="keyword-trend" :class="keyword.trendClass">{{ keyword.trend }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- AI助手浮动按钮 -->
|
||
<view class="ai-assistant-float" @click="toggleAiAssistant">
|
||
<view class="ai-avatar">
|
||
<text class="ai-icon">🤖</text>
|
||
</view>
|
||
<view v-if="hasUnreadAiMessage" class="ai-badge">
|
||
<text class="badge-text">{{ unreadAiCount }}</text>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- AI助手对话窗口 -->
|
||
<view v-if="showAiAssistant" class="ai-chat-modal">
|
||
<view class="ai-chat-container">
|
||
<view class="ai-chat-header">
|
||
<text class="ai-chat-title">{{ aiAssistantTitle }}</text>
|
||
<view class="ai-chat-actions">
|
||
<view class="minimize-btn" @click="minimizeAiChat">
|
||
<text class="minimize-icon">-</text>
|
||
</view>
|
||
<view class="close-btn" @click="closeAiChat">
|
||
<text class="close-icon">×</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
<view class="ai-chat-content">
|
||
<!-- AI聊天内容区域 -->
|
||
<scroll-view direction="vertical" class="chat-messages">
|
||
<view v-for="message in aiMessagesList" :key="message.id" class="message-item"
|
||
:class="message.type">
|
||
<text class="message-text">{{ message.content }}</text>
|
||
<text class="message-time">{{ message.timestamp }}</text>
|
||
</view>
|
||
</scroll-view>
|
||
<view class="chat-input-area">
|
||
<input class="chat-input" :placeholder="chatInputPlaceholder" v-model="chatInputText"
|
||
@confirm="sendChatMessage" />
|
||
<view class="send-btn" @click="sendChatMessage">
|
||
<text class="send-icon">→</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 模态框 -->
|
||
<!-- 语言选择 -->
|
||
<view v-if="showLanguageModal" class="modal-overlay" @click="hideLanguageSelector">
|
||
<view class="language-modal" @click.stop>
|
||
<view class="modal-header">
|
||
<text class="modal-title">{{ selectLanguageTitle }}</text> <view class="modal-close" @click="hideLanguageSelector">
|
||
<text class="close-text">×</text>
|
||
</view>
|
||
</view>
|
||
<scroll-view direction="vertical" class="language-list">
|
||
<view v-for="lang in availableLanguages" :key="lang.code" class="language-item"
|
||
:class="{ active: currentLanguageCode === lang.code }" @click="selectLanguage(lang)">
|
||
<text class="language-name">{{ lang.displayName }}</text>
|
||
<text class="language-native">{{ lang.nativeName }}</text>
|
||
<text v-if="currentLanguageCode === lang.code" class="check-icon">✓</text>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 搜索模态框 -->
|
||
<view v-if="searchModalVisible" class="modal-overlay" @click="hideSearchModal">
|
||
<view class="search-modal" @click.stop>
|
||
<!-- 搜索组件内容 -->
|
||
<search-component :hot-keywords="hotKeywordsList" :search-history="searchHistoryList"
|
||
@search="handleSearch" @close="hideSearchModal" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 登录模态框 -->
|
||
<view v-if="loginModalVisible" class="modal-overlay" @click="hideLoginModal">
|
||
<view class="login-modal" @click.stop>
|
||
<!-- 登录组件内容 -->
|
||
<login-component @login-success="handleLoginSuccess" @close="hideLoginModal" />
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 用户菜单 -->
|
||
<view v-if="showUserMenuModal" class="modal-overlay" @click="hideUserMenu">
|
||
<view class="user-menu-modal" @click.stop>
|
||
<view class="user-menu-header">
|
||
<image class="menu-avatar" :src="userAvatarUrl" mode="aspectFill" />
|
||
<text class="menu-username">{{ userNickname }}</text>
|
||
</view>
|
||
<view class="user-menu-list">
|
||
<view class="menu-item" @click="navigateToProfile">
|
||
<text class="menu-icon">👤</text>
|
||
<text class="menu-label">{{ profileText }}</text>
|
||
</view>
|
||
<view class="menu-item" @click="navigateToSettings">
|
||
<text class="menu-icon">⚙️</text>
|
||
<text class="menu-label">{{ settingsText }}</text>
|
||
</view>
|
||
<view class="menu-item" @click="toggleNightMode">
|
||
<text class="menu-icon">{{ nightModeIcon }}</text>
|
||
<text class="menu-label">{{ nightModeText }}</text>
|
||
</view>
|
||
<view class="menu-item logout" @click="handleLogout">
|
||
<text class="menu-icon">🚪</text>
|
||
<text class="menu-label">{{ logoutText }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
</scroll-view>
|
||
</template>
|
||
|
||
<script setup lang="uts">
|
||
import { ref, computed, onMounted, onUnmounted } from 'vue'
|
||
import type { UserProfile, UserStats } from '@/pages/user/types.uts'
|
||
|
||
import supa from '@/components/supadb/aksupainstance.uts'
|
||
import { OrderOptions } from '@/components/supadb/aksupa.uts'
|
||
import { tt } from '@/utils/i18nfun.uts'
|
||
import i18n from '@/i18n/index.uts' // 保留用于语言切换和全局配置
|
||
import { ResponsiveState, InfoContent } from '@/pages/info/types.uts'
|
||
|
||
import { LanguageOption } from '@/pages/user/types.uts'
|
||
import { setClipboardData, getClipboardData, SetClipboardDataOption, GetClipboardDataOption, GetClipboardDataSuccessCallbackOption } from '@/uni_modules/lime-clipboard'
|
||
|
||
// 类型定义 - UTS Android 兼容
|
||
type LanguageItem = {
|
||
code : string
|
||
displayName : string
|
||
nativeName : string
|
||
}
|
||
|
||
type SocialMediaItem = {
|
||
id : string
|
||
name : string
|
||
iconUrl : string
|
||
linkUrl : string
|
||
}
|
||
|
||
type HotKeyword = {
|
||
id : string
|
||
text : string
|
||
trend : string
|
||
trendClass : string
|
||
}
|
||
|
||
type AiMessage = {
|
||
id : string
|
||
type : string
|
||
content : string
|
||
timestamp : string
|
||
}
|
||
|
||
type CompanyConfig = {
|
||
id ?: string
|
||
config_key : string
|
||
config_value : string
|
||
config_type : string
|
||
description ?: string
|
||
language ?: string
|
||
is_active ?: boolean
|
||
created_at ?: string
|
||
updated_at ?: string
|
||
}
|
||
|
||
type TopicData = {
|
||
id : string
|
||
title : string
|
||
description : string
|
||
topic_type : string
|
||
status : string
|
||
cover_image_url : string
|
||
content_count : number
|
||
view_count : number
|
||
like_count : number
|
||
priority_level : number
|
||
tags : Array<string>
|
||
created_at : string
|
||
updated_at : string
|
||
}
|
||
|
||
type TopicCategory = {
|
||
type : string
|
||
icon : string
|
||
color : string
|
||
count : number
|
||
}
|
||
|
||
// 页面状态管理
|
||
type PageState = {
|
||
isLoading : boolean
|
||
error : string | null
|
||
currentPage : number
|
||
pageSize : number
|
||
hasMore ?: boolean
|
||
total ?: number
|
||
}
|
||
const pageState = ref<PageState>({
|
||
isLoading: false,
|
||
error: null,
|
||
currentPage: 1,
|
||
pageSize: 10
|
||
})
|
||
|
||
// 响应式状态
|
||
const responsiveState = ref<ResponsiveState>({
|
||
isLargeScreen: false,
|
||
isSmallScreen: false,
|
||
screenWidth: 375,
|
||
cardColumns: 1
|
||
})
|
||
|
||
// 公司信息
|
||
const companyDisplayName = ref('创新科技有限公司')
|
||
const companySlogan = ref('科技创新,未来可期')
|
||
const companyPhone = ref('400-123-4567')
|
||
const companyEmail = ref('info@example.com')
|
||
const companyAddress = ref('上海市浦东新区张江高科技园区XX路88号')
|
||
const companyIcp = ref('沪ICP备12345678号-1')
|
||
const companyLogoUrl = ref('/static/company-logo.png')
|
||
const showFooter = ref(false)
|
||
// 全局配置表相关状态
|
||
const isLoadingCompanyConfig = ref(false)
|
||
|
||
// 用户状态
|
||
const isUserLogin = ref(false)
|
||
const userNickname = ref('')
|
||
const userAvatarUrl = ref('/static/default-avatar.png')
|
||
|
||
// UI显示状态
|
||
const showLanguageModal = ref(false)
|
||
const searchModalVisible = ref(false)
|
||
const loginModalVisible = ref(false)
|
||
const showUserMenuModal = ref(false)
|
||
const showAiAssistant = ref(false)
|
||
const hotSearchVisible = ref(false)
|
||
const isNightMode = ref(false)
|
||
const showDebugPanel = ref(false) // 调试面板显示状态
|
||
|
||
// 语言设置
|
||
const currentLanguageCode = ref('zh-CN')
|
||
const currentLanguageName = ref('简体中文')
|
||
const availableLanguages = ref<Array<LanguageItem>>([
|
||
{ code: 'zh-CN', displayName: '简体中文', nativeName: '简体中文' },
|
||
{ code: 'en-US', displayName: 'English', nativeName: 'English' },
|
||
{ code: 'zh-TW', displayName: '繁體中文', nativeName: '繁體中文' },
|
||
{ code: 'ja-JP', displayName: '日本語', nativeName: '日本語' }
|
||
])
|
||
|
||
// 业务数据
|
||
const businessList = ref<Array<InfoContent>>([])
|
||
const isLoadingBusiness = ref(false)
|
||
|
||
// 专题数据
|
||
const featuredTopicsList = ref<Array<TopicData>>([])
|
||
const isLoadingTopics = ref(false)
|
||
const topicCategoriesList = ref<Array<TopicCategory>>([
|
||
{ type: 'breaking', icon: '⚠', color: '#ef4444', count: 0 },
|
||
{ type: 'trending', icon: '🔥', color: '#f97316', count: 0 },
|
||
{ type: 'series', icon: '📚', color: '#3b82f6', count: 0 },
|
||
{ type: 'analysis', icon: '🔍', color: '#8b5cf6', count: 0 },
|
||
{ type: 'guide', icon: '📖', color: '#10b981', count: 0 },
|
||
{ type: 'interview', icon: '🎤', color: '#f59e0b', count: 0 }
|
||
])
|
||
|
||
// 多媒体内容数据
|
||
const multimediaList = ref<Array<InfoContent>>([])
|
||
const isLoadingMultimedia = ref(false)
|
||
|
||
// 社交媒体
|
||
const socialMediaList = ref<Array<SocialMediaItem>>([
|
||
{ id: '1', name: '微信', iconUrl: '/static/wechat.png', linkUrl: '' },
|
||
{ id: '2', name: '微博', iconUrl: '/static/weibo.png', linkUrl: '' },
|
||
{ id: '3', name: 'QQ', iconUrl: '/static/qq.png', linkUrl: '' }
|
||
])
|
||
|
||
// 热门搜索
|
||
const hotKeywordsList = ref<Array<HotKeyword>>([
|
||
{ id: '1', text: '人工智能', trend: '↗', trendClass: 'trend-up' },
|
||
{ id: '2', text: '大数据分析', trend: '↗', trendClass: 'trend-up' },
|
||
{ id: '3', text: '物联网', trend: '→', trendClass: 'trend-stable' },
|
||
{ id: '4', text: '云计算', trend: '↗', trendClass: 'trend-up' },
|
||
{ id: '5', text: '区块链', trend: '↘', trendClass: 'trend-down' }
|
||
])
|
||
|
||
const searchHistoryList = ref<Array<string>>([])
|
||
|
||
// AI助手
|
||
const aiMessagesList = ref<Array<AiMessage>>([])
|
||
const chatInputText = ref('')
|
||
const hasUnreadAiMessage = ref(false)
|
||
const unreadAiCount = ref(0)
|
||
|
||
// 计算属性
|
||
const waterfallColumns = computed(() : number => {
|
||
return responsiveState.value.isLargeScreen ? 4 : 3
|
||
})
|
||
|
||
const waterfallGap = computed(() : number => {
|
||
return responsiveState.value.isLargeScreen ? 12 : 8
|
||
})
|
||
|
||
// 多语言文本 - 使用 tt 系统
|
||
const searchPlaceholder = computed(() : string => {
|
||
return tt('mt.search.placeholder')
|
||
})
|
||
|
||
const loginText = computed(() : string => {
|
||
return tt('mt.button.login')
|
||
})
|
||
|
||
const businessSectionTitle = computed(() : string => {
|
||
return tt('mt.section.business')
|
||
})
|
||
|
||
const viewModeText = computed(() : string => {
|
||
return tt('mt.button.gridView')
|
||
})
|
||
|
||
const learnMoreText = computed(() : string => {
|
||
return tt('mt.button.learnMore')
|
||
})
|
||
|
||
const viewsText = computed(() : string => {
|
||
return tt('mt.text.views')
|
||
})
|
||
|
||
const loadingText = computed(() : string => {
|
||
return tt('mt.status.loading')
|
||
})
|
||
|
||
const emptyTitle = computed(() : string => {
|
||
return tt('mt.empty.title')
|
||
})
|
||
|
||
const emptyDescription = computed(() : string => {
|
||
return tt('mt.empty.description')
|
||
})
|
||
|
||
const refreshText = computed(() : string => {
|
||
return tt('mt.action.refresh')
|
||
})
|
||
|
||
const contactLabel = computed(() : string => {
|
||
return tt('mt.label.contact')
|
||
})
|
||
|
||
const addressLabel = computed(() : string => {
|
||
return tt('mt.label.address')
|
||
})
|
||
|
||
const hotSearchTitle = computed(() : string => {
|
||
return tt('mt.section.hotSearch')
|
||
})
|
||
|
||
const aiAssistantTitle = computed(() : string => {
|
||
return tt('mt.section.aiAssistant')
|
||
})
|
||
|
||
const chatInputPlaceholder = computed(() : string => {
|
||
return tt('mt.chat.inputPlaceholder')
|
||
})
|
||
|
||
const selectLanguageTitle = computed(() : string => {
|
||
return tt('mt.modal.selectLanguage')
|
||
})
|
||
|
||
const profileText = computed(() : string => {
|
||
return tt('mt.button.profile')
|
||
})
|
||
|
||
const settingsText = computed(() : string => {
|
||
return tt('mt.button.settings')
|
||
})
|
||
|
||
const nightModeText = computed(() : string => {
|
||
return isNightMode.value
|
||
? tt('mt.button.lightMode')
|
||
: tt('mt.button.nightMode')
|
||
})
|
||
|
||
const nightModeIcon = computed(() : string => {
|
||
return isNightMode.value ? '☀️' : '🌙'
|
||
})
|
||
|
||
const logoutText = computed(() : string => {
|
||
return tt('mt.button.logout')
|
||
})
|
||
|
||
const errorTitle = computed(() : string => {
|
||
return tt('mt.error.loadFailed')
|
||
})
|
||
|
||
const retryText = computed(() : string => {
|
||
return tt('mt.action.retry')
|
||
})
|
||
|
||
const loadMoreText = computed(() : string => {
|
||
return currentLanguageCode.value === 'en' ? 'Load More' : '加载更多'
|
||
})
|
||
|
||
// 获取标准语言代码(直接返回标准格式,兼容空值)
|
||
function getStandardLanguageCode(langCode : string) : string {
|
||
if (langCode !== null && langCode !== '') {
|
||
return langCode
|
||
}
|
||
return 'zh-CN'
|
||
}
|
||
|
||
// 响应式处理
|
||
const handleResize = () => {
|
||
const systemInfo = uni.getSystemInfoSync()
|
||
responsiveState.value.screenWidth = systemInfo.screenWidth
|
||
responsiveState.value.isLargeScreen = systemInfo.screenWidth >= 768
|
||
}
|
||
|
||
// 工具函数
|
||
const getTopicTypeColor = (topicType : string) : string => {
|
||
const category = topicCategoriesList.value.find(cat => cat.type === topicType)
|
||
return category?.color ?? '#6b7280'
|
||
}
|
||
|
||
const formatTopicTime = (timeString : string) : string => {
|
||
try {
|
||
const date = new Date(timeString)
|
||
const now = new Date()
|
||
const diffMs = now.getTime() - date.getTime()
|
||
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
|
||
if (diffDays === 0) {
|
||
return tt('mt.time.today')
|
||
} else if (diffDays === 1) {
|
||
return tt('mt.time.yesterday')
|
||
} else if (diffDays < 7) {
|
||
return `${diffDays}${tt('mt.time.daysAgo')}`
|
||
|
||
} else {
|
||
// UTS Android compatible date formatting
|
||
const year = date.getFullYear()
|
||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||
const day = date.getDate().toString().padStart(2, '0')
|
||
return `${year}-${month}-${day}`
|
||
}
|
||
} catch (error) {
|
||
return timeString
|
||
}
|
||
}
|
||
|
||
const getDefaultCoverByType = (contentType : string) : string => {
|
||
switch (contentType) {
|
||
case 'video':
|
||
return '/static/default-video.png'
|
||
case 'audio':
|
||
return '/static/default-audio.png'
|
||
case 'images':
|
||
return '/static/default-image.png'
|
||
default:
|
||
return '/static/default-content.png'
|
||
}
|
||
}
|
||
|
||
const getContentTypeColor = (contentType : string) : string => {
|
||
switch (contentType) {
|
||
case 'video':
|
||
return '#3b82f6'
|
||
case 'audio':
|
||
return '#10b981'
|
||
case 'images':
|
||
return '#ef4444'
|
||
default:
|
||
return '#6b7280'
|
||
}
|
||
}
|
||
|
||
const getContentTypeIcon = (contentType : string) : string => {
|
||
switch (contentType) {
|
||
case 'video':
|
||
return '📹'
|
||
case 'audio':
|
||
return '🎵'
|
||
case 'images':
|
||
return '🖼️'
|
||
default:
|
||
return '📂'
|
||
}
|
||
}
|
||
|
||
const formatDuration = (duration : number) : string => {
|
||
const minutes = Math.floor(duration / 60)
|
||
const seconds = duration % 60
|
||
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`
|
||
}
|
||
|
||
const formatContentTime = (timeString : string) : string => {
|
||
try {
|
||
const date = new Date(timeString)
|
||
// UTS Android compatible date/time formatting
|
||
const year = date.getFullYear()
|
||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||
const day = date.getDate().toString().padStart(2, '0')
|
||
const hours = date.getHours().toString().padStart(2, '0')
|
||
const minutes = date.getMinutes().toString().padStart(2, '0')
|
||
return `${year}-${month}-${day} ${hours}:${minutes}`
|
||
} catch (error) {
|
||
return timeString
|
||
}
|
||
}
|
||
// 从配置数据更新社交媒体链接(简化版本)
|
||
const updateSocialMediaFromConfig = (configData : Array<CompanyConfig>) => {
|
||
const getConfigValue = (key : string) : string => {
|
||
for (let i : Int = 0; i < configData.length; i++) {
|
||
const item : CompanyConfig = configData[i]
|
||
if (item.config_key === key) {
|
||
const value : string | null = item.config_value
|
||
return value ?? ''
|
||
}
|
||
}
|
||
return ''
|
||
}
|
||
|
||
// Define proper type for social media config
|
||
type SocialMediaConfig = {
|
||
key : string
|
||
name : string
|
||
id : string
|
||
}
|
||
|
||
const socialMediaConfigs : Array<SocialMediaConfig> = [
|
||
{ key: 'social_wechat_url', name: '微信', id: 'wechat' },
|
||
{ key: 'social_weibo_url', name: '微博', id: 'weibo' },
|
||
{ key: 'social_qq_url', name: 'QQ', id: 'qq' },
|
||
{ key: 'social_linkedin_url', name: 'LinkedIn', id: 'linkedin' },
|
||
{ key: 'social_twitter_url', name: 'Twitter', id: 'twitter' }
|
||
]
|
||
|
||
for (let i : Int = 0; i < socialMediaConfigs.length; i++) {
|
||
const config : SocialMediaConfig = socialMediaConfigs[i]
|
||
const linkUrl = getConfigValue(config.key)
|
||
if (linkUrl !== '') {
|
||
// 找到对应的社交媒体项并更新链接
|
||
let index = -1
|
||
for (let j : Int = 0; j < socialMediaList.value.length; j++) {
|
||
if (socialMediaList.value[j].name === config.name || socialMediaList.value[j].id === config.id) {
|
||
index = j
|
||
break
|
||
}
|
||
}
|
||
|
||
if (index !== -1) {
|
||
socialMediaList.value[index].linkUrl = linkUrl
|
||
} else {
|
||
// 如果不存在,则添加新的社交媒体项
|
||
const newSocialMediaItem : SocialMediaItem = {
|
||
id: config.id,
|
||
name: config.name,
|
||
iconUrl: `/static/${config.id}.png`,
|
||
linkUrl: linkUrl
|
||
}
|
||
socialMediaList.value.push(newSocialMediaItem)
|
||
}
|
||
}
|
||
}
|
||
|
||
console.log('社交媒体链接更新完成,总数:', socialMediaList.value.length)
|
||
}
|
||
|
||
// 直接从配置数据更新公司信息(简化版本)
|
||
const updateCompanyInfoFromConfig = (configData : Array<CompanyConfig>) => {
|
||
console.log('更新公司信息,配置项数量:', configData.length)
|
||
|
||
// 辅助函数:根据配置键获取值
|
||
const getConfigValue = (key : string) : string => {
|
||
for (let i : Int = 0; i < configData.length; i++) {
|
||
const item : CompanyConfig = configData[i]
|
||
if (item.config_key === key) {
|
||
const value : string | null = item.config_value
|
||
return value !== null && value !== '' ? value : ''
|
||
}
|
||
}
|
||
return ''
|
||
}
|
||
|
||
// 更新公司基础信息
|
||
const companyNameValue = getConfigValue('company_name')
|
||
if (companyNameValue !== '') {
|
||
companyDisplayName.value = companyNameValue
|
||
console.log('公司名称:', companyNameValue)
|
||
}
|
||
|
||
const companySloganValue = getConfigValue('company_slogan')
|
||
if (companySloganValue !== '') {
|
||
companySlogan.value = companySloganValue
|
||
console.log('公司标语:', companySloganValue)
|
||
}
|
||
|
||
const companyAddressValue = getConfigValue('company_address')
|
||
if (companyAddressValue !== '') {
|
||
companyAddress.value = companyAddressValue
|
||
console.log('公司地址:', companyAddressValue)
|
||
}
|
||
|
||
// 更新联系信息
|
||
const phone = getConfigValue('company_phone')
|
||
if (phone !== '') {
|
||
companyPhone.value = phone
|
||
}
|
||
|
||
const email = getConfigValue('company_email')
|
||
if (email !== '') {
|
||
companyEmail.value = email
|
||
}
|
||
|
||
const icp = getConfigValue('company_icp')
|
||
if (icp !== '') {
|
||
companyIcp.value = icp
|
||
}
|
||
|
||
const logoUrl = getConfigValue('company_logo_url')
|
||
if (logoUrl !== '') {
|
||
companyLogoUrl.value = logoUrl
|
||
}
|
||
|
||
// 更新社交媒体
|
||
updateSocialMediaFromConfig(configData)
|
||
|
||
console.log('公司信息更新完成')
|
||
}
|
||
|
||
// 从配置数据更新热门搜索关键词(简化版本)
|
||
const updateHotSearchFromConfig = (configData : Array<CompanyConfig>) => {
|
||
const getConfigValue = (key : string) : string => {
|
||
for (let i : Int = 0; i < configData.length; i++) {
|
||
const item : CompanyConfig = configData[i]
|
||
if (item.config_key === key) {
|
||
const value : string | null = item.config_value
|
||
return value !== null && value !== '' ? value : ''
|
||
}
|
||
}
|
||
return ''
|
||
}
|
||
|
||
const hotSearches : Array<string> = []
|
||
|
||
// 获取热门搜索关键词(1-5个)
|
||
for (let i : Int = 1; i <= 5; i++) {
|
||
const searchKeyword = getConfigValue(`hot_search_${i}`)
|
||
if (searchKeyword !== '') {
|
||
hotSearches.push(searchKeyword)
|
||
}
|
||
}
|
||
|
||
// 如果没有配置或配置不足,使用默认关键词补充
|
||
if (hotSearches.length < 3) {
|
||
const langCode = getStandardLanguageCode(currentLanguageCode.value)
|
||
|
||
// Define proper type for default searches
|
||
type DefaultSearchesType = {
|
||
zh : Array<string>
|
||
en : Array<string>
|
||
tw : Array<string>
|
||
ja : Array<string>
|
||
}
|
||
|
||
const defaultSearches : DefaultSearchesType = {
|
||
zh: ['人工智能', '大数据分析', '物联网', '云计算', '区块链'],
|
||
en: ['Artificial Intelligence', 'Big Data Analytics', 'Internet of Things', 'Cloud Computing', 'Blockchain'],
|
||
tw: ['人工智慧', '大數據分析', '物聯網', '雲端運算', '區塊鏈'],
|
||
ja: ['人工知能', 'ビッグデータ解析', 'IoT', 'クラウドコンピューティング', 'ブロックチェーン']
|
||
}
|
||
|
||
// UTS Android compatible property access
|
||
let fallbackSearches : Array<string>
|
||
if (langCode === 'zh') {
|
||
fallbackSearches = defaultSearches.zh
|
||
} else if (langCode === 'en') {
|
||
fallbackSearches = defaultSearches.en
|
||
} else if (langCode === 'tw') {
|
||
fallbackSearches = defaultSearches.tw
|
||
} else if (langCode === 'ja') {
|
||
fallbackSearches = defaultSearches.ja
|
||
} else {
|
||
fallbackSearches = defaultSearches.zh
|
||
}
|
||
|
||
// 补充默认搜索关键词
|
||
const currentLength : Int = hotSearches.length as Int
|
||
const targetLength : Int = Math.min(5, fallbackSearches.length) as Int
|
||
for (let i : Int = currentLength; i < targetLength; i++) {
|
||
hotSearches.push(fallbackSearches[i])
|
||
}
|
||
}
|
||
|
||
const hotKeywords : Array<HotKeyword> = []
|
||
for (let i : Int = 0; i < hotSearches.length && i < 5; i++) {
|
||
hotKeywords.push({
|
||
id: (i + 1).toString(),
|
||
text: hotSearches[i],
|
||
trend: '↗',
|
||
trendClass: 'trend-up'
|
||
})
|
||
}
|
||
hotKeywordsList.value = hotKeywords
|
||
console.log('热门搜索关键词更新完成', hotKeywordsList.value)
|
||
}
|
||
|
||
// 数据加载函数 - 简化版本,直接使用配置数据
|
||
const loadCompanyConfig = async () => {
|
||
if (supa === null) return
|
||
|
||
isLoadingCompanyConfig.value = true
|
||
|
||
try {
|
||
const langCode = getStandardLanguageCode(currentLanguageCode.value)
|
||
console.log('加载配置,语言代码:', langCode)
|
||
|
||
// 直接调用 RPC 函数获取配置
|
||
const result = await supa.rpc('get_configs_by_language', {
|
||
p_language_code: langCode
|
||
})
|
||
|
||
if (result.error !== null) {
|
||
const error = result.error
|
||
const errorMsg = error !== null && error.message !== null ? error.message : '未知错误'
|
||
throw new Error(`配置查询失败: ${errorMsg}`)
|
||
}
|
||
|
||
const configData = result.data
|
||
|
||
if (configData === null || !Array.isArray(configData) || configData.length === 0) {
|
||
throw new Error('无法获取配置数据')
|
||
}
|
||
|
||
console.log('成功获取配置数据:', configData.length, '条记录')
|
||
|
||
// 直接更新公司信息,无需构建映射表
|
||
const typedConfigData = configData as Array<CompanyConfig>
|
||
updateCompanyInfoFromConfig(typedConfigData)
|
||
|
||
// 更新热门搜索关键词
|
||
updateHotSearchFromConfig(typedConfigData)
|
||
|
||
console.log('配置加载完成')
|
||
showFooter.value = true
|
||
} catch (error) {
|
||
console.error('配置加载失败:', error)
|
||
} finally {
|
||
isLoadingCompanyConfig.value = false
|
||
}
|
||
}
|
||
|
||
// 交互函数
|
||
const showLanguageSelector = () => {
|
||
showLanguageModal.value = true
|
||
}
|
||
|
||
const hideLanguageSelector = () => {
|
||
showLanguageModal.value = false
|
||
}
|
||
|
||
|
||
|
||
const showSearchModal = () => {
|
||
searchModalVisible.value = true
|
||
}
|
||
|
||
const hideSearchModal = () => {
|
||
searchModalVisible.value = false
|
||
}
|
||
|
||
const showLoginModal = () => {
|
||
loginModalVisible.value = true
|
||
}
|
||
|
||
const hideLoginModal = () => {
|
||
loginModalVisible.value = false
|
||
}
|
||
|
||
const showUserMenu = () => {
|
||
showUserMenuModal.value = true
|
||
}
|
||
|
||
const hideUserMenu = () => {
|
||
showUserMenuModal.value = false
|
||
}
|
||
|
||
const toggleViewMode = () => {
|
||
// 切换视图模式逻辑
|
||
}
|
||
|
||
const navigateToBusinessDetail = (item : InfoContent) => {
|
||
// 现在业务项目存储在 ak_contents 表中,使用通用的内容详情页面
|
||
uni.navigateTo({
|
||
url: `/pages/info/detail?id=${item.id}&type=business_item`
|
||
})
|
||
}
|
||
|
||
|
||
// UTS Android 不支持 toLocaleTimeString,这里自定义一个时间格式化函数
|
||
const formatTimeHHMMSS = (date : Date) : string => {
|
||
const h = date.getHours().toString().padStart(2, '0')
|
||
const m = date.getMinutes().toString().padStart(2, '0')
|
||
const s = date.getSeconds().toString().padStart(2, '0')
|
||
return `${h}:${m}:${s}`
|
||
}
|
||
|
||
const handleImageError = (event : UniImageErrorEvent) => {
|
||
// 图片加载错误时使用默认图片
|
||
}
|
||
|
||
const openSocialLink = (social : SocialMediaItem) => {
|
||
if (social.linkUrl !== '') {
|
||
setClipboardData({
|
||
data: social.linkUrl,
|
||
success: (_err : UniError) => {
|
||
uni.showToast({
|
||
title: '链接已复制到剪贴板',
|
||
icon: 'success'
|
||
})
|
||
}
|
||
} as SetClipboardDataOption)
|
||
|
||
|
||
} else {
|
||
uni.showToast({
|
||
title: '暂无链接信息',
|
||
icon: 'none'
|
||
})
|
||
}
|
||
}
|
||
|
||
const hideHotSearch = () => {
|
||
hotSearchVisible.value = false
|
||
}
|
||
|
||
const searchKeyword = (keyword : HotKeyword) => {
|
||
// 执行搜索
|
||
uni.navigateTo({
|
||
url: `/pages/info/search?keyword=${keyword.text}`
|
||
})
|
||
}
|
||
|
||
const toggleAiAssistant = () => {
|
||
showAiAssistant.value = !showAiAssistant.value
|
||
if (showAiAssistant.value) {
|
||
hasUnreadAiMessage.value = false
|
||
unreadAiCount.value = 0
|
||
|
||
// 如果是第一次打开,添加欢迎消息
|
||
if (aiMessagesList.value.length === 0) {
|
||
const welcomeMessage : AiMessage = {
|
||
id: 'welcome',
|
||
type: 'ai',
|
||
content: `您好!欢迎来到${companyDisplayName.value}!我是您的智能助手,有什么可以帮助您的吗?`,
|
||
timestamp: formatTimeHHMMSS(new Date())
|
||
}
|
||
aiMessagesList.value.push(welcomeMessage)
|
||
}
|
||
}
|
||
}
|
||
|
||
const minimizeAiChat = () => {
|
||
showAiAssistant.value = false
|
||
}
|
||
|
||
const closeAiChat = () => {
|
||
showAiAssistant.value = false
|
||
}
|
||
|
||
const sendChatMessage = () => {
|
||
if (chatInputText.value.trim() === '') return
|
||
|
||
const userMessageContent = chatInputText.value.trim()
|
||
|
||
// 添加用户消息
|
||
const userMessage : AiMessage = {
|
||
id: Date.now().toString(),
|
||
type: 'user',
|
||
content: userMessageContent,
|
||
timestamp: formatTimeHHMMSS(new Date())
|
||
}
|
||
|
||
aiMessagesList.value.push(userMessage)
|
||
chatInputText.value = ''
|
||
|
||
// 模拟AI回复 - 根据用户输入给出不同回复
|
||
setTimeout(() => {
|
||
let aiReplyContent = '感谢您的咨询,我是智能助手,很高兴为您服务!'
|
||
|
||
if (userMessageContent.includes('业务') || userMessageContent.includes('服务')) {
|
||
aiReplyContent = '我们公司提供人工智能、大数据分析、物联网系统和云计算服务。您想了解哪个方面的详细信息?'
|
||
} else if (userMessageContent.includes('联系') || userMessageContent.includes('电话')) {
|
||
aiReplyContent = `您可以通过以下方式联系我们:\n电话:${companyPhone.value}\n邮箱:${companyEmail.value}`
|
||
} else if (userMessageContent.includes('地址') || userMessageContent.includes('位置')) {
|
||
aiReplyContent = `我们的地址是:${companyAddress.value}`
|
||
} else if (userMessageContent.includes('价格') || userMessageContent.includes('费用')) {
|
||
aiReplyContent = '具体的价格会根据您的需求定制,建议您联系我们的销售团队获取详细报价。'
|
||
}
|
||
|
||
const aiReply : AiMessage = {
|
||
id: (Date.now() + 1).toString(),
|
||
type: 'ai',
|
||
content: aiReplyContent,
|
||
timestamp: formatTimeHHMMSS(new Date())
|
||
}
|
||
aiMessagesList.value.push(aiReply)
|
||
}, 1000)
|
||
}
|
||
|
||
const handleSearch = (keyword : string) => {
|
||
// 处理搜索
|
||
uni.navigateTo({
|
||
url: `/pages/info/search?keyword=${keyword}`
|
||
})
|
||
}
|
||
|
||
const handleLoginSuccess = (userData : UserProfile) => {
|
||
isUserLogin.value = true
|
||
userNickname.value = userData['username'] as string ?? ''
|
||
|
||
let avatarUrl = ''
|
||
if (userData['avatar'] != null) {
|
||
avatarUrl = userData['avatar'] as string
|
||
} else if (userData['avatar_url'] != null) {
|
||
avatarUrl = userData['avatar_url'] as string
|
||
} else if (userData['profile'] != null) {
|
||
const profile = userData['profile'] as UTSJSONObject
|
||
if (profile['avatar'] != null) {
|
||
avatarUrl = profile['avatar'] as string
|
||
}
|
||
}
|
||
userAvatarUrl.value = avatarUrl !== '' ? avatarUrl : '/static/default-avatar.png'
|
||
|
||
hideLoginModal()
|
||
}
|
||
|
||
const navigateToProfile = () => {
|
||
hideUserMenu()
|
||
uni.navigateTo({
|
||
url: '/pages/user/profile'
|
||
})
|
||
}
|
||
|
||
const navigateToSettings = () => {
|
||
hideUserMenu()
|
||
uni.navigateTo({
|
||
url: '/pages/user/settings'
|
||
})
|
||
}
|
||
|
||
const toggleNightMode = () => {
|
||
isNightMode.value = !isNightMode.value
|
||
hideUserMenu()
|
||
|
||
// 应用夜间模式
|
||
if (isNightMode.value) {
|
||
// 添加夜间模式样式
|
||
} else {
|
||
// 移除夜间模式样式
|
||
}
|
||
}
|
||
|
||
const handleLogout = () => {
|
||
isUserLogin.value = false
|
||
userNickname.value = ''
|
||
userAvatarUrl.value = '/static/default-avatar.png'
|
||
hideUserMenu()
|
||
}
|
||
|
||
// 简化的配置调试函数
|
||
const debugConfigSystem = async () => {
|
||
console.log('=== 配置系统状态 ===')
|
||
console.log('当前语言:', currentLanguageCode.value, '->', getStandardLanguageCode(currentLanguageCode.value))
|
||
console.log('公司名称:', companyDisplayName.value)
|
||
console.log('公司标语:', companySlogan.value)
|
||
|
||
// 测试数据库连接
|
||
if (supa !== null) {
|
||
try {
|
||
const result = await supa.rpc('get_configs_by_language', {
|
||
p_language_code: getStandardLanguageCode(currentLanguageCode.value)
|
||
})
|
||
console.log('数据库连接正常,配置数据:', result.data !== null ? result.data.length : 0, '条')
|
||
} catch (error) {
|
||
console.error('数据库连接测试失败', error)
|
||
}
|
||
}
|
||
|
||
console.log('=== 调试完成 ===')
|
||
}
|
||
|
||
// 重新加载配置
|
||
const reloadConfig = async () => {
|
||
console.log('重新加载配置...')
|
||
await loadCompanyConfig()
|
||
}
|
||
// 数据加载函数
|
||
const loadBusinessData = async () => {
|
||
if (supa === null) return
|
||
|
||
isLoadingBusiness.value = true
|
||
pageState.value.isLoading = true
|
||
pageState.value.error = null
|
||
|
||
try {
|
||
const start = (pageState.value.currentPage - 1) * pageState.value.pageSize
|
||
const end = start + pageState.value.pageSize - 1
|
||
|
||
// 使用 ak_contents 表,通过内容类型筛选业务项目,并连接分类表
|
||
const result = await supa
|
||
.from('ak_contents')
|
||
.select(`
|
||
id,
|
||
title,
|
||
summary,
|
||
image_url,
|
||
published_at,
|
||
view_count,
|
||
status,
|
||
tags,
|
||
ak_content_categories!inner(name_key)
|
||
`, { count: 'exact' })
|
||
.eq('status', 'published')
|
||
.eq('content_type', 'business_item')
|
||
// .eq('original_language', currentLanguageCode.value)
|
||
.order('published_at', { ascending: false } as OrderOptions)
|
||
.range(start, end)
|
||
.executeAs<InfoContent>()
|
||
|
||
if (result.data !== null && Array.isArray(result.data)) {
|
||
const rawItems = result.data as Array<InfoContent>
|
||
|
||
// 转换数据格式以匹配 InfoContent 类型
|
||
const newItems : Array<InfoContent> = rawItems.map((item : InfoContent) => {
|
||
// UTS Android compatible date formatting
|
||
let formattedDate = ''
|
||
const publishedAt = item.published_at as string | null
|
||
if (publishedAt != null) {
|
||
try {
|
||
const date = new Date(publishedAt)
|
||
const year = date.getFullYear()
|
||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||
const day = date.getDate().toString().padStart(2, '0')
|
||
formattedDate = `${year}-${month}-${day}`
|
||
} catch (error) {
|
||
formattedDate = ''
|
||
}
|
||
}
|
||
|
||
return {
|
||
...item,
|
||
id: item.id as string,
|
||
title: item.title ?? '无标题',
|
||
summary: item.summary ?? '暂无描述',
|
||
image_url: item.image_url ?? 'https://picsum.photos/300/200?random=' + Math.floor(Math.random() * 100),
|
||
published_at: formattedDate,
|
||
view_count: item.view_count ?? 0,
|
||
category_name_text: (item as InfoContent).ak_content_categories?.name_key ?? '未分类',
|
||
loading: false
|
||
} as InfoContent
|
||
})
|
||
|
||
if (pageState.value.currentPage === 1) {
|
||
businessList.value = newItems
|
||
} else {
|
||
businessList.value = businessList.value.concat(newItems)
|
||
}
|
||
|
||
pageState.value.hasMore = newItems.length === pageState.value.pageSize
|
||
pageState.value.total = result.count ?? 0
|
||
} else {
|
||
}
|
||
} catch (error) {
|
||
console.error('加载业务数据失败:', error)
|
||
pageState.value.error = tt('mt.error.loadFailed')
|
||
} finally {
|
||
isLoadingBusiness.value = false
|
||
pageState.value.loading = false
|
||
}
|
||
}
|
||
|
||
const refreshBusinessData = async () => {
|
||
pageState.value.currentPage = 1
|
||
pageState.value.error = null
|
||
await loadBusinessData()
|
||
}
|
||
|
||
// 专题数据加载函数
|
||
const loadFeaturedTopics = async () => {
|
||
if (isLoadingTopics.value) return
|
||
isLoadingTopics.value = true
|
||
|
||
try {
|
||
// 使用多语言专题查询函数
|
||
const result = await supa.rpc('get_topics_by_language', {
|
||
p_language_code: currentLanguageCode.value,
|
||
p_status: 'featured',
|
||
p_limit: 8,
|
||
p_offset: 0
|
||
})
|
||
|
||
if (result.error) {
|
||
console.error('专题加载失败:', result.error)
|
||
return
|
||
}
|
||
|
||
const topics = result.data
|
||
if (topics && Array.isArray(topics)) {
|
||
featuredTopicsList.value = topics.map((topic : any) => ({
|
||
id: topic.topic_id,
|
||
title: topic.title,
|
||
description: topic.description,
|
||
topic_type: topic.topic_type,
|
||
status: topic.status,
|
||
cover_image_url: topic.cover_image_url || '',
|
||
content_count: topic.content_count || 0,
|
||
view_count: topic.view_count || 0,
|
||
like_count: topic.like_count || 0,
|
||
priority_level: topic.priority_level || 0,
|
||
tags: topic.tags || [],
|
||
created_at: topic.created_at,
|
||
updated_at: topic.updated_at
|
||
} as TopicData))
|
||
}
|
||
} catch (error) {
|
||
console.error('专题数据加载异常:', error)
|
||
// 可以在这里显示错误提示
|
||
pageState.value.error = tt('mt.error.loadTopicsFailed')
|
||
} finally {
|
||
isLoadingTopics.value = false
|
||
}
|
||
}
|
||
|
||
// 加载专题分类统计
|
||
const loadTopicCategories = async () => {
|
||
try {
|
||
const result = await supa.from('ak_topics')
|
||
.select('topic_type', {})
|
||
.eq('status', 'active')
|
||
.execute()
|
||
|
||
if (result.error) {
|
||
console.error('专题分类加载失败:', result.error)
|
||
return
|
||
}
|
||
|
||
const topicTypes = result.data || []
|
||
const typeCountMap = new Map<string, number>()
|
||
|
||
// 统计各类型数量
|
||
topicTypes.forEach((item : any) => {
|
||
const type = item.topic_type
|
||
typeCountMap.set(type, (typeCountMap.get(type) || 0) + 1)
|
||
})
|
||
|
||
// 更新分类列表的计数
|
||
topicCategoriesList.value = topicCategoriesList.value.map(category => ({
|
||
...category,
|
||
count: typeCountMap.get(category.type) || 0
|
||
}))
|
||
} catch (error) {
|
||
console.error('专题分类统计异常:', error)
|
||
}
|
||
}
|
||
// 专题导航函数
|
||
const navigateToTopics = () => {
|
||
try {
|
||
uni.navigateTo({
|
||
url: '/pages/info/topics'
|
||
})
|
||
} catch (error) {
|
||
console.error('导航到专题页面失败', error)
|
||
}
|
||
}
|
||
|
||
const navigateToTopicDetail = (topic : TopicData) => {
|
||
try {
|
||
uni.navigateTo({
|
||
url: `/pages/info/topic-detail?id=${topic.id}`
|
||
})
|
||
} catch (error) {
|
||
console.error('导航到专题详情失败', error)
|
||
}
|
||
}
|
||
const navigateToTopicsByType = (topicType : string) => {
|
||
try {
|
||
uni.navigateTo({
|
||
url: `/pages/info/topics?type=${topicType}`
|
||
})
|
||
} catch (error) {
|
||
console.error('导航到分类专题失败', error)
|
||
}
|
||
}
|
||
|
||
const navigateToMultimedia = () => {
|
||
// 导航到多媒体内容页面
|
||
uni.navigateTo({
|
||
url: '/pages/info/multimedia'
|
||
})
|
||
}
|
||
|
||
const navigateToMultimediaDetail = (item : InfoContent) => {
|
||
if (item.content_type === 'video') {
|
||
uni.navigateTo({
|
||
url: `/pages/info/video-player?id=${item.id}`
|
||
});
|
||
} else {
|
||
uni.showToast({ title: '即将支持', icon: 'none' });
|
||
}
|
||
}
|
||
// 多媒体内容数据加载函数
|
||
const loadMultimediaData = async () => {
|
||
if (!supa) return;
|
||
isLoadingMultimedia.value = true;
|
||
try {
|
||
const types = ['video', 'audio', 'images'];
|
||
let all : InfoContent[] = [];
|
||
for (const type of types) {
|
||
const result = await supa
|
||
.from('ak_contents')
|
||
.select(`
|
||
id, title, summary, content_type, image_url, published_at, view_count, status, ak_content_categories!inner(name_key)
|
||
`)
|
||
.eq('status', 'published')
|
||
.eq('content_type', type)
|
||
.order('published_at', { ascending: false })
|
||
.limit(2)
|
||
.execute();
|
||
if (result.data && Array.isArray(result.data)) {
|
||
all = all.concat(result.data.map((item : any) => ({
|
||
...item,
|
||
id: item.id,
|
||
title: item.title || '无标题',
|
||
summary: item.summary || '',
|
||
content_type: item.content_type,
|
||
image_url: item.image_url || '',
|
||
published_at: item.published_at || '',
|
||
view_count: item.view_count || 0,
|
||
status: item.status,
|
||
category_name_text: item.ak_content_categories?.name_key || '未分类',
|
||
loading: false
|
||
} as InfoContent)));
|
||
}
|
||
}
|
||
multimediaList.value = all;
|
||
} catch (e) {
|
||
console.error('加载多媒体内容失败', e);
|
||
} finally {
|
||
isLoadingMultimedia.value = false;
|
||
}
|
||
};
|
||
|
||
const selectLanguage = async (language : LanguageItem) => {
|
||
// 使用 i18n 切换语言
|
||
i18n.global.locale.value = language.code
|
||
currentLanguageCode.value = language.code
|
||
currentLanguageName.value = language.displayName
|
||
hideLanguageSelector()
|
||
|
||
// 重新加载公司配置信息(动态多语言)
|
||
await loadCompanyConfig()
|
||
|
||
// 重新加载数据以支持多语言
|
||
await refreshBusinessData()
|
||
await loadFeaturedTopics() // 重新加载专题数据
|
||
|
||
// 显示切换成功提示
|
||
uni.showToast({
|
||
title: tt('mt.message.languageChanged'),
|
||
icon: 'success',
|
||
duration: 1500
|
||
})
|
||
}
|
||
|
||
// 生命周期
|
||
onMounted(() => {
|
||
// 初始化语言设置,与 i18n 保持同步
|
||
currentLanguageCode.value = i18n.global.locale.value
|
||
let currentLang : LanguageOption | null = null
|
||
for (let i : Int = 0; i < availableLanguages.value.length; i++) {
|
||
if (availableLanguages.value[i].code === i18n.global.locale.value) {
|
||
currentLang = availableLanguages.value[i]
|
||
break
|
||
}
|
||
}
|
||
if (currentLang !== null) {
|
||
currentLanguageName.value = currentLang.displayName
|
||
}
|
||
|
||
handleResize()
|
||
loadCompanyConfig() // 加载公司配置
|
||
loadBusinessData()
|
||
loadFeaturedTopics() // 加载热门专题
|
||
loadTopicCategories() // 加载专题分类统计
|
||
loadMultimediaData()
|
||
|
||
// 初始化热门搜索显示状态
|
||
setTimeout(() => {
|
||
hotSearchVisible.value = true
|
||
}, 2000)
|
||
|
||
// 模拟AI助手有新消息
|
||
setTimeout(() => {
|
||
hasUnreadAiMessage.value = true
|
||
unreadAiCount.value = 1
|
||
}, 5000)
|
||
|
||
|
||
})
|
||
|
||
onUnmounted(() => {
|
||
|
||
})
|
||
</script>
|
||
|
||
<style>
|
||
/* 基础样式,参考根目录 comindex-BoM1ZRrn.css,已精简为单 class、无嵌套、无媒体查询、无伪类,保证 Android 100% 兼容 */
|
||
.company-home {
|
||
flex: 1;
|
||
background-color: #f8fafc;
|
||
}
|
||
|
||
.header {
|
||
background-color: #fff;
|
||
border-bottom-width: 1px;
|
||
border-bottom-color: #e2e8f0;
|
||
padding-top: 10px;
|
||
padding-bottom: 10px;
|
||
}
|
||
|
||
.header-content {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-left: 16px;
|
||
padding-right: 16px;
|
||
}
|
||
|
||
.header-left {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.company-logo {
|
||
width: 32px;
|
||
height: 32px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.company-name {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.loading-indicator {
|
||
margin-left: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.loading-dot {
|
||
font-size: 10px;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.header-center {
|
||
flex: 1;
|
||
display: flex;
|
||
justify-content: center;
|
||
margin-left: 16px;
|
||
margin-right: 16px;
|
||
}
|
||
|
||
.search-btn {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
background-color: #f1f5f9;
|
||
border-radius: 20px;
|
||
padding: 8px 16px;
|
||
min-width: 200px;
|
||
}
|
||
|
||
.search-icon {
|
||
font-size: 14px;
|
||
margin-right: 8px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.search-text {
|
||
font-size: 14px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.header-right {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
flex: 0 0 auto;
|
||
}
|
||
|
||
.lang-switch {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 6px 12px;
|
||
background-color: #f1f5f9;
|
||
border-radius: 16px;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.lang-text {
|
||
font-size: 14px;
|
||
color: #475569;
|
||
}
|
||
|
||
.lang-arrow {
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
margin-left: 4px;
|
||
}
|
||
|
||
.login-btn {
|
||
padding: 8px 16px;
|
||
background-color: #3b82f6;
|
||
border-radius: 16px;
|
||
}
|
||
|
||
.login-text {
|
||
color: #fff;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.user-profile {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.user-avatar {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 14px;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.user-nickname {
|
||
font-size: 14px;
|
||
color: #475569;
|
||
}
|
||
|
||
.business-section {
|
||
padding: 16px;
|
||
}
|
||
|
||
.business-flex-container {
|
||
display: flex;
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
gap: 16rpx;
|
||
justify-content: flex-start;
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.section-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.section-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.view-mode-btn {
|
||
padding: 6px 12px;
|
||
background-color: #f1f5f9;
|
||
border-radius: 12px;
|
||
}
|
||
|
||
.view-mode-text {
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.business-card {
|
||
background-color: #fff;
|
||
border-radius: 12px;
|
||
margin-bottom: 16px;
|
||
border-width: 1px;
|
||
border-color: #e5e7eb;
|
||
overflow: hidden;
|
||
width: 300rpx;
|
||
max-width: 100%;
|
||
}
|
||
|
||
.card-image-container {
|
||
position: relative;
|
||
width: 100%;
|
||
}
|
||
|
||
.card-image {
|
||
width: 100%;
|
||
height: 120px;
|
||
border-top-left-radius: 12px;
|
||
border-top-right-radius: 12px;
|
||
object-fit: cover;
|
||
}
|
||
|
||
.card-category {
|
||
position: absolute;
|
||
top: 6px;
|
||
right: 6px;
|
||
background-color: rgba(59, 130, 246, .9);
|
||
border-radius: 6px;
|
||
padding: 3px 6px;
|
||
}
|
||
|
||
.category-text {
|
||
font-size: 9px;
|
||
color: #fff;
|
||
}
|
||
|
||
.card-content {
|
||
padding: 10px;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
margin-bottom: 4px;
|
||
line-height: 18px;
|
||
}
|
||
|
||
.card-description {
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
line-height: 16px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.card-meta {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.meta-text,
|
||
.meta-views {
|
||
font-size: 10px;
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.card-actions {
|
||
padding: 0 10px 8px;
|
||
}
|
||
|
||
.action-btn {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 6px 12px;
|
||
border-radius: 6px;
|
||
}
|
||
|
||
.action-btn.primary {
|
||
background-color: #3b82f6;
|
||
}
|
||
|
||
.action-text {
|
||
font-size: 12px;
|
||
color: #fff;
|
||
}
|
||
|
||
.loading-section {
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
padding: 40px 0;
|
||
}
|
||
|
||
.loading-text {
|
||
font-size: 14px;
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.error-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 60px 16px;
|
||
}
|
||
|
||
.error-icon {
|
||
font-size: 48px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.error-title {
|
||
font-size: 18px;
|
||
color: #ef4444;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.error-desc {
|
||
font-size: 14px;
|
||
color: #64748b;
|
||
margin-bottom: 16px;
|
||
text-align: center;
|
||
}
|
||
|
||
.retry-btn {
|
||
padding: 10px 24px;
|
||
background-color: #ef4444;
|
||
border-radius: 20px;
|
||
}
|
||
|
||
.retry-text {
|
||
font-size: 14px;
|
||
color: #fff;
|
||
}
|
||
|
||
.empty-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
padding: 60px 16px;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 48px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.empty-title {
|
||
font-size: 18px;
|
||
color: #1f2937;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.empty-desc {
|
||
font-size: 14px;
|
||
color: #64748b;
|
||
margin-bottom: 16px;
|
||
text-align: center;
|
||
}
|
||
|
||
.refresh-btn {
|
||
padding: 10px 24px;
|
||
background-color: #3b82f6;
|
||
border-radius: 20px;
|
||
}
|
||
|
||
.refresh-text {
|
||
font-size: 14px;
|
||
color: #fff;
|
||
}
|
||
|
||
.footer-section {
|
||
background-color: #fff;
|
||
border-top-width: 1px;
|
||
border-top-color: #e2e8f0;
|
||
padding: 24px 16px;
|
||
}
|
||
|
||
.footer-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.footer-main {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.footer-logo {
|
||
width: 48px;
|
||
height: 48px;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.footer-info {
|
||
flex: 1;
|
||
}
|
||
|
||
.footer-company-name {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.footer-slogan {
|
||
font-size: 14px;
|
||
color: #64748b;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.footer-details {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.contact-info {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.contact-label {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #374151;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.contact-value {
|
||
font-size: 14px;
|
||
color: #64748b;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.address-info {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.address-label {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #374151;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.address-value {
|
||
font-size: 14px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.icp-text {
|
||
font-size: 12px;
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.social-links {
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.social-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
margin-right: 24px;
|
||
}
|
||
|
||
.social-icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.social-name {
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.hot-search-float {
|
||
position: fixed;
|
||
right: 16px;
|
||
bottom: 140px;
|
||
z-index: 1001;
|
||
background-color: #fff;
|
||
border-radius: 12px;
|
||
padding: 12px;
|
||
border-width: 1px;
|
||
border-color: #e5e7eb;
|
||
max-width: 200px;
|
||
}
|
||
|
||
.hot-search-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.hot-search-title {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.hot-search-close {
|
||
width: 20px;
|
||
height: 20px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.close-icon {
|
||
font-size: 12px;
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.hot-keyword-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 6px 0;
|
||
}
|
||
|
||
.keyword-rank {
|
||
font-size: 12px;
|
||
color: #ef4444;
|
||
font-weight: 700;
|
||
width: 16px;
|
||
}
|
||
|
||
.keyword-text {
|
||
flex: 1;
|
||
font-size: 12px;
|
||
color: #374151;
|
||
margin-left: 8px;
|
||
}
|
||
|
||
.keyword-trend {
|
||
font-size: 12px;
|
||
}
|
||
|
||
.keyword-trend.trend-up {
|
||
color: #ef4444;
|
||
}
|
||
|
||
.keyword-trend.trend-stable {
|
||
color: #64748b;
|
||
}
|
||
|
||
.keyword-trend.trend-down {
|
||
color: #10b981;
|
||
}
|
||
|
||
.ai-assistant-float {
|
||
position: fixed;
|
||
right: 16px;
|
||
bottom: 80px;
|
||
z-index: 1002;
|
||
width: 56px;
|
||
height: 56px;
|
||
background-color: #3b82f6;
|
||
border-radius: 28px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.ai-icon {
|
||
font-size: 24px;
|
||
}
|
||
|
||
.ai-badge {
|
||
position: absolute;
|
||
top: -4px;
|
||
right: -4px;
|
||
background-color: #ef4444;
|
||
border-radius: 10px;
|
||
min-width: 20px;
|
||
height: 20px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.badge-text {
|
||
font-size: 10px;
|
||
color: #fff;
|
||
}
|
||
|
||
.ai-chat-modal {
|
||
position: fixed;
|
||
right: 16px;
|
||
bottom: 140px;
|
||
z-index: 1003;
|
||
width: 300px;
|
||
height: 400px;
|
||
background-color: #fff;
|
||
border-radius: 12px;
|
||
border-width: 1px;
|
||
border-color: #e5e7eb;
|
||
}
|
||
|
||
.ai-chat-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
}
|
||
|
||
.ai-chat-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 12px 16px;
|
||
border-bottom-width: 1px;
|
||
border-bottom-color: #e5e7eb;
|
||
}
|
||
|
||
.ai-chat-title {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.ai-chat-actions {
|
||
display: flex;
|
||
flex-direction: row;
|
||
}
|
||
|
||
.minimize-btn {
|
||
width: 24px;
|
||
height: 24px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin-right: 8px;
|
||
}
|
||
|
||
.minimize-icon {
|
||
font-size: 16px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.close-btn {
|
||
width: 24px;
|
||
height: 24px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.ai-chat-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.chat-messages {
|
||
flex: 1;
|
||
padding: 12px;
|
||
}
|
||
|
||
.message-item {
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.message-item.user {
|
||
align-items: flex-end;
|
||
}
|
||
|
||
.message-item.ai {
|
||
align-items: flex-start;
|
||
}
|
||
|
||
.message-text {
|
||
background-color: #f1f5f9;
|
||
padding: 8px 12px;
|
||
border-radius: 12px;
|
||
font-size: 14px;
|
||
color: #374151;
|
||
line-height: 20px;
|
||
}
|
||
|
||
.message-item.user .message-text {
|
||
background-color: #3b82f6;
|
||
color: #fff;
|
||
}
|
||
|
||
.message-time {
|
||
font-size: 10px;
|
||
color: #9ca3af;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.chat-input-area {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 12px;
|
||
border-top-width: 1px;
|
||
border-top-color: #e5e7eb;
|
||
}
|
||
|
||
.chat-input {
|
||
flex: 1;
|
||
border-width: 1px;
|
||
border-color: #d1d5db;
|
||
border-radius: 20px;
|
||
padding: 8px 12px;
|
||
font-size: 14px;
|
||
}
|
||
|
||
.send-btn {
|
||
margin-left: 8px;
|
||
width: 36px;
|
||
height: 36px;
|
||
background-color: #3b82f6;
|
||
border-radius: 18px;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.send-icon {
|
||
font-size: 16px;
|
||
color: #fff;
|
||
}
|
||
|
||
.modal-overlay {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-color: rgba(0, 0, 0, .5);
|
||
z-index: 2000;
|
||
display: flex;
|
||
justify-content: center;
|
||
align-items: center;
|
||
}
|
||
|
||
.language-modal {
|
||
background-color: #fff;
|
||
border-radius: 16px;
|
||
margin: 20px;
|
||
max-height: 500px;
|
||
min-width: 300px;
|
||
}
|
||
|
||
.modal-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 16px 20px;
|
||
border-bottom-width: 1px;
|
||
border-bottom-color: #e2e8f0;
|
||
}
|
||
|
||
.modal-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.modal-close {
|
||
width: 32px;
|
||
height: 32px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 16px;
|
||
background-color: #f1f5f9;
|
||
}
|
||
|
||
.close-text {
|
||
font-size: 18px;
|
||
color: #64748b;
|
||
}
|
||
|
||
.language-list {
|
||
max-height: 400px;
|
||
}
|
||
|
||
.language-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 16px 20px;
|
||
border-bottom-width: 1px;
|
||
border-bottom-color: #f1f5f9;
|
||
}
|
||
|
||
.language-item.active {
|
||
background-color: #eff6ff;
|
||
}
|
||
|
||
.language-name {
|
||
flex: 1;
|
||
font-size: 16px;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.language-native {
|
||
font-size: 14px;
|
||
color: #64748b;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.check-icon {
|
||
font-size: 16px;
|
||
color: #3b82f6;
|
||
}
|
||
|
||
.search-modal {
|
||
width: 90%;
|
||
max-width: 600px;
|
||
max-height: 80%;
|
||
background-color: #fff;
|
||
border-radius: 16px;
|
||
}
|
||
|
||
.login-modal {
|
||
width: 90%;
|
||
max-width: 400px;
|
||
background-color: #fff;
|
||
border-radius: 16px;
|
||
}
|
||
|
||
.user-menu-modal {
|
||
background-color: #fff;
|
||
border-radius: 16px;
|
||
margin: 20px;
|
||
min-width: 250px;
|
||
}
|
||
|
||
.user-menu-header {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 20px;
|
||
border-bottom-width: 1px;
|
||
border-bottom-color: #e2e8f0;
|
||
}
|
||
|
||
.menu-avatar {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 24px;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.menu-username {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.menu-item {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 16px 20px;
|
||
border-bottom-width: 1px;
|
||
border-bottom-color: #f1f5f9;
|
||
}
|
||
|
||
.menu-item.logout {
|
||
border-bottom-width: 0;
|
||
}
|
||
|
||
.menu-icon {
|
||
font-size: 16px;
|
||
margin-right: 12px;
|
||
}
|
||
|
||
.menu-label {
|
||
font-size: 16px;
|
||
color: #374151;
|
||
}
|
||
|
||
.menu-item.logout .menu-label {
|
||
color: #ef4444;
|
||
}
|
||
|
||
.featured-topics-section {
|
||
background-color: #fff;
|
||
margin: 8px 0;
|
||
padding: 16px 0;
|
||
}
|
||
|
||
.topics-scroll {
|
||
height: 280px;
|
||
}
|
||
|
||
.topics-container {
|
||
flex-direction: row;
|
||
padding: 0 16px;
|
||
}
|
||
|
||
.topic-card {
|
||
width: 240px;
|
||
background-color: #fff;
|
||
border-radius: 12px;
|
||
margin-right: 12px;
|
||
border: 1px solid #e5e7eb;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.topic-cover {
|
||
height: 120px;
|
||
background-color: #f3f4f6;
|
||
background-size: cover;
|
||
background-position: center;
|
||
position: relative;
|
||
}
|
||
|
||
.topic-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
padding: 12px;
|
||
justify-content: space-between;
|
||
background: linear-gradient(to bottom, rgba(0, 0, 0, .1), rgba(0, 0, 0, .4));
|
||
}
|
||
|
||
.topic-type-badge {
|
||
background-color: #3b82f6;
|
||
border-radius: 12px;
|
||
padding: 4px 8px;
|
||
align-self: flex-start;
|
||
}
|
||
|
||
.badge-text {
|
||
font-size: 12px;
|
||
color: #fff;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.topic-stats {
|
||
flex-direction: row;
|
||
align-self: flex-end;
|
||
}
|
||
|
||
.stat-item {
|
||
font-size: 12px;
|
||
color: #fff;
|
||
margin-right: 8px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.topic-info {
|
||
padding: 12px;
|
||
}
|
||
|
||
.topic-title {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
line-height: 22px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.topic-description {
|
||
font-size: 14px;
|
||
color: #6b7280;
|
||
line-height: 20px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.topic-meta {
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.topic-update-time {
|
||
font-size: 12px;
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.topic-priority {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
}
|
||
|
||
.priority-icon {
|
||
font-size: 14px;
|
||
}
|
||
|
||
.loading-card {
|
||
opacity: .6;
|
||
}
|
||
|
||
.loading-shimmer {
|
||
background: linear-gradient(90deg, #f3f4f6 25%, #e5e7eb, #f3f4f6 75%);
|
||
}
|
||
|
||
.loading-line {
|
||
height: 12px;
|
||
background-color: #e5e7eb;
|
||
border-radius: 6px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.loading-line.short {
|
||
width: 60%;
|
||
}
|
||
|
||
.loading-line.medium {
|
||
width: 80%;
|
||
}
|
||
|
||
.loading-line.long {
|
||
width: 100%;
|
||
}
|
||
|
||
.topics-quick-access {
|
||
background-color: #fff;
|
||
margin: 8px 16px;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.quick-access-header {
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.quick-access-title {
|
||
font-size: 18px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.quick-access-grid {
|
||
flex-direction: row;
|
||
flex-wrap: wrap;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.quick-access-item {
|
||
width: 48%;
|
||
margin-bottom: 12px;
|
||
padding: 16px;
|
||
background-color: #f9fafb;
|
||
border-radius: 8px;
|
||
align-items: center;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.access-icon-container {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 20px;
|
||
justify-content: center;
|
||
align-items: center;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.access-icon {
|
||
font-size: 20px;
|
||
color: #fff;
|
||
}
|
||
|
||
.access-label {
|
||
font-size: 14px;
|
||
color: #374151;
|
||
font-weight: 500;
|
||
margin-bottom: 4px;
|
||
text-align: center;
|
||
}
|
||
|
||
.access-count {
|
||
font-size: 12px;
|
||
color: #6b7280;
|
||
text-align: center;
|
||
}
|
||
|
||
.more-btn {
|
||
flex-direction: row;
|
||
align-items: center;
|
||
padding: 6px 12px;
|
||
background-color: #f3f4f6;
|
||
border-radius: 16px;
|
||
}
|
||
|
||
.more-text {
|
||
font-size: 14px;
|
||
color: #6b7280;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.more-arrow {
|
||
font-size: 16px;
|
||
color: #6b7280;
|
||
}
|
||
|
||
.multimedia-section {
|
||
background-color: #fff;
|
||
margin: 8px 0;
|
||
padding: 16px 0;
|
||
}
|
||
|
||
.multimedia-scroll {
|
||
height: 220px;
|
||
}
|
||
|
||
.multimedia-container {
|
||
display: flex;
|
||
flex-direction: row;
|
||
padding: 0 16px;
|
||
overflow: visible;
|
||
flex: 1;
|
||
}
|
||
|
||
.multimedia-card {
|
||
width: 180px;
|
||
background-color: #f9fafb;
|
||
border-radius: 12px;
|
||
margin-right: 12px;
|
||
border: 1px solid #e5e7eb;
|
||
overflow: hidden;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, .03);
|
||
}
|
||
|
||
.multimedia-cover {
|
||
height: 100px;
|
||
background-color: #f3f4f6;
|
||
background-size: cover;
|
||
background-position: center;
|
||
position: relative;
|
||
}
|
||
|
||
.multimedia-overlay {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
padding: 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
background: linear-gradient(to bottom, rgba(0, 0, 0, .08), rgba(0, 0, 0, .25));
|
||
}
|
||
|
||
.content-type-badge {
|
||
background-color: #3b82f6;
|
||
border-radius: 10px;
|
||
padding: 2px 8px;
|
||
align-self: flex-start;
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 12px;
|
||
color: #fff;
|
||
}
|
||
|
||
.type-icon {
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.multimedia-stats {
|
||
display: flex;
|
||
flex-direction: row;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 12px;
|
||
color: #fff;
|
||
}
|
||
|
||
.play-overlay {
|
||
position: absolute;
|
||
right: 8px;
|
||
bottom: 8px;
|
||
background: rgba(59, 130, 246, .8);
|
||
border-radius: 50%;
|
||
width: 28px;
|
||
height: 28px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.play-icon {
|
||
color: #fff;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.multimedia-info {
|
||
padding: 10px;
|
||
}
|
||
|
||
.multimedia-title {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #1f2937;
|
||
margin-bottom: 4px;
|
||
line-height: 18px;
|
||
}
|
||
|
||
.multimedia-description {
|
||
font-size: 12px;
|
||
color: #64748b;
|
||
line-height: 16px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.multimedia-meta {
|
||
display: flex;
|
||
flex-direction: row;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.multimedia-date {
|
||
font-size: 10px;
|
||
color: #9ca3af;
|
||
}
|
||
|
||
.multimedia-category .category-text {
|
||
font-size: 10px;
|
||
color: #3b82f6;
|
||
background: #e0e7ff;
|
||
border-radius: 6px;
|
||
padding: 2px 6px;
|
||
}
|
||
</style>
|