Files
akmon/pages/info/comindex.uvue
2026-01-20 08:04:15 +08:00

2861 lines
68 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 公司模式首页 - 严格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>