feat(official): 实现企业官网分包及内容展示

- 新增官方分包目录及页面(首页、资讯列表、详情、关于我们、留言、我的)
- 自定义实现底部导航组件 OfficialTabBar,解决微信 tabBar 不支持分包问题
- 统一接口调用走 shop-api 代理,无新增 CMS 域名,减少合法域名负担
- 新增 CMS 站点信息获取接口及留言咨询提交与查询接口
- 设计并完善官网富文本样式,品牌色统一为 #185FA5,并更新 tailwind 配置
- 首页新增企业官网入口卡片,方便用户访问官网内容
- 完成官网分包构建验证,解决 Taro 构建批量删除保护问题
- 规范 CMS 字段使用,适配字段名称及状态含义
- 后续计划对历史页面 NutUI 组件进行空壳化整改,及更新 UI 设计文档
This commit is contained in:
2026-08-11 08:48:16 +08:00
parent d611a76ebd
commit 2e80a49ca1
20 changed files with 926 additions and 16 deletions
+31
View File
@@ -0,0 +1,31 @@
import request from '@/utils/request'
import type { ApiResult, PageResult } from '@/api'
import type { CmsContactLead, CmsContactLeadParam } from './model'
/**
* 提交联系线索(留言咨询),免登录
*/
export async function submitCmsContactLead(data: Partial<CmsContactLead>) {
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-contact-lead/submit',
data
)
if (res.code === 0) {
return res.message
}
return Promise.reject(new Error(res.message))
}
/**
* 分页查询我的联系线索
*/
export async function pageCmsContactLead(params: CmsContactLeadParam) {
const res = await request.get<ApiResult<PageResult<CmsContactLead>>>(
'/cms/cms-contact-lead/page',
params
)
if (res.code === 0) {
return res.data
}
return Promise.reject(new Error(res.message))
}
+36
View File
@@ -0,0 +1,36 @@
import type { PageParam } from '@/api'
/**
* 联系线索(留言咨询)
*/
export interface CmsContactLead {
// 线索ID
leadId?: number
// 称呼
name?: string
// 手机号
phone?: string
// 公司
company?: string
// 邮箱
email?: string
// 需求描述
need?: string
// 来源 web|miniapp|app
source?: string
// 状态 0待跟进 1已回复 2已完成 3已归档
status?: number
// 租户id
tenantId?: number
// 创建时间
createTime?: string
}
/**
* 联系线索搜索条件
*/
export interface CmsContactLeadParam extends PageParam {
name?: string
phone?: string
source?: string
}
+13
View File
@@ -118,6 +118,19 @@ export async function getCmsWebsite(id: number) {
return Promise.reject(new Error(res.message));
}
/**
* 获取站点信息(含 topNavs / bottomNavs),官网首页与关于页使用
*/
export async function getCmsWebsiteSiteInfo() {
const res = await request.get<ApiResult<CmsWebsite>>(
'/cms/cms-website/getSiteInfo'
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* 清除缓存
*/
+19
View File
@@ -0,0 +1,19 @@
/**
* 官网(企业官网展示端)统一 API 出口
* 复用 src/api/cms 下已有模块,并补齐两个缺口:
* - getCmsWebsiteSiteInfo:站点信息(含顶部/底部导航)
* - submitCmsContactLead / pageCmsContactLead:留言咨询
*
* 内容读取走与现有 listCmsAd / listCmsArticle 一致的默认请求链路(shop-api 已代理 /cms)。
*/
export { pageCmsArticle, getCmsArticle, listCmsArticle } from '@/api/cms/cmsArticle'
export { treeNavigation, listCmsNavigation } from '@/api/cms/cmsNavigation'
export { listCmsMpPages } from '@/api/cms/cmsMpPages'
export { getCmsWebsiteSiteInfo, listCmsWebsite, getCmsWebsite } from '@/api/cms/cmsWebsite'
export { listCmsAd } from '@/api/cms/cmsAd'
export { submitCmsContactLead, pageCmsContactLead } from '@/api/cms/cmsContactLead'
export type { CmsArticle } from '@/api/cms/cmsArticle/model'
export type { CmsWebsite, CmsNavigation } from '@/api/cms/cmsWebsite/model'
export type { CmsAd } from '@/api/cms/cmsAd/model'
export type { CmsContactLead } from '@/api/cms/cmsContactLead/model'
+11
View File
@@ -144,6 +144,17 @@ export default {
"unified-qr/index",
"pay/index"
]
},
{
"root": "official",
"pages": [
"index",
"article",
"article-detail",
"about",
"message",
"mine"
]
}
],
window: {
+72
View File
@@ -15,6 +15,9 @@
--text-color: #333333;
--bg-color: #ffffff;
--border-color: #e8e8e8;
// 官网品牌色(统一 token
--brand-color: #185FA5;
--brand-light: #EAF2FB;
}
// 暗黑模式
@@ -78,3 +81,72 @@ page {
to { transform: rotate(360deg); }
}
// 官网分包富文本渲染(RichText nodes 内的标签样式)
.official-rich {
font-size: 30px;
line-height: 1.8;
color: #333333;
word-break: break-all;
img {
max-width: 100% !important;
height: auto !important;
display: block;
margin: 16px 0;
border-radius: 12px;
}
p {
margin: 0 0 20px 0;
}
h1, h2, h3, h4 {
font-weight: 600;
color: #1a1a1a;
margin: 28px 0 16px 0;
line-height: 1.5;
}
h1 { font-size: 38px; }
h2 { font-size: 34px; }
h3 { font-size: 32px; }
h4 { font-size: 30px; }
a {
color: var(--brand-color);
}
ul, ol {
padding-left: 40px;
margin: 0 0 20px 0;
}
li {
margin-bottom: 10px;
}
blockquote {
margin: 20px 0;
padding: 16px 20px;
background-color: var(--brand-light);
border-left: 6px solid var(--brand-color);
color: #555555;
}
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
border: 1px solid #e8e8e8;
padding: 12px;
font-size: 26px;
}
video {
width: 100%;
}
}
+66
View File
@@ -0,0 +1,66 @@
import React, { useState, useEffect } from 'react'
import { View, Text, ScrollView, RichText, Image } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { getCmsWebsiteSiteInfo } from '@/api/official'
import type { CmsWebsite } from '@/api/official'
import { getCompressedImageUrl } from '@/utils/image'
import OfficialTabBar from '../components/OfficialTabBar'
import NavBar from '@/components/NavBar'
definePageConfig({
navigationBarTitleText: '关于我们',
})
const About: React.FC = () => {
const [site, setSite] = useState<CmsWebsite>()
const [loading, setLoading] = useState(true)
useEffect(() => {
getCmsWebsiteSiteInfo()
.then((s) => setSite(s))
.catch(() => {})
.finally(() => setLoading(false))
}, [])
const aboutHtml =
(site as any)?.config?.about || (site as any)?.description || ''
return (
<View className='flex flex-col h-screen bg-gray-50'>
<NavBar title='关于我们' />
<ScrollView scrollY className='flex-1'>
<View className='p-4'>
<View className='flex items-center mb-4'>
{site?.websiteLogo && (
<Image
src={getCompressedImageUrl(site.websiteLogo, { width: 120 })}
className='w-16 h-16 rounded mr-3'
mode='aspectFill'
/>
)}
<Text className='text-lg font-semibold text-gray-800'>
{site?.websiteName || '企业官网'}
</Text>
</View>
{aboutHtml ? (
<RichText nodes={aboutHtml} className='official-rich' />
) : (
<Text className='text-sm text-gray-400'>
{loading ? '加载中...' : '暂无介绍'}
</Text>
)}
<View className='mt-4 bg-white rounded-lg p-4'>
<Text className='text-sm text-gray-500 block'>{site?.phone || '—'}</Text>
<Text className='text-sm text-gray-500 block mt-2'>{site?.email || '—'}</Text>
<Text className='text-sm text-gray-500 block mt-2'>{site?.address || '—'}</Text>
</View>
</View>
</ScrollView>
<OfficialTabBar active='about' />
</View>
)
}
export default About
+61
View File
@@ -0,0 +1,61 @@
import React, { useState, useEffect } from 'react'
import { View, Text, ScrollView, RichText } from '@tarojs/components'
import Taro, { useRouter } from '@tarojs/taro'
import { getCmsArticle } from '@/api/official'
import type { CmsArticle } from '@/api/official'
import NavBar from '@/components/NavBar'
definePageConfig({
navigationBarTitleText: '资讯详情',
})
const ArticleDetail: React.FC = () => {
const router = useRouter()
const id = router.params.id
const [article, setArticle] = useState<CmsArticle>()
const [loading, setLoading] = useState(true)
useEffect(() => {
if (!id) return
getCmsArticle(Number(id))
.then((a) => setArticle(a))
.catch(() => {})
.finally(() => setLoading(false))
}, [id])
return (
<View className='flex flex-col h-screen bg-white'>
<NavBar title='资讯详情' />
<ScrollView scrollY className='flex-1'>
<View className='p-4'>
<Text className='text-xl font-semibold text-gray-800 block'>
{article?.title || '加载中...'}
</Text>
<View className='flex items-center mt-2 mb-4'>
<Text className='text-xs text-gray-400'>{article?.source || article?.author || ''}</Text>
<Text className='text-xs text-gray-400 ml-3'>{article?.createTime?.slice(0, 10)}</Text>
</View>
{article?.content ? (
<RichText nodes={article.content} className='official-rich' />
) : (
<Text className='text-sm text-gray-400'>
{loading ? '加载中...' : '暂无内容'}
</Text>
)}
</View>
</ScrollView>
<View
className='px-4 py-3 bg-white border-t border-gray-200 safe-area-bottom'
>
<View
className='bg-brand-600 rounded-full py-3 text-center'
onClick={() => Taro.navigateTo({ url: '/official/message' })}
>
<Text className='text-white text-sm font-medium'></Text>
</View>
</View>
</View>
)
}
export default ArticleDetail
+72
View File
@@ -0,0 +1,72 @@
import React, { useState, useEffect } from 'react'
import { View, Text, ScrollView } from '@tarojs/components'
import Taro, { useRouter } from '@tarojs/taro'
import { pageCmsArticle } from '@/api/official'
import type { CmsArticle } from '@/api/official'
import ArticleCard from '../components/ArticleCard'
import OfficialTabBar from '../components/OfficialTabBar'
import NavBar from '@/components/NavBar'
definePageConfig({
navigationBarTitleText: '资讯',
})
const ArticleList: React.FC = () => {
const router = useRouter()
const categoryId = router.params.categoryId
const [articles, setArticles] = useState<CmsArticle[]>([])
const [page, setPage] = useState(1)
const [finished, setFinished] = useState(false)
const [loading, setLoading] = useState(false)
const load = async (p: number) => {
if (loading || finished) return
setLoading(true)
try {
const res = await pageCmsArticle({
status: 0,
deleted: 0,
page: p,
limit: 10,
categoryId: categoryId ? Number(categoryId) : undefined,
})
const list = res?.list || []
setArticles((prev) => (p === 1 ? list : [...prev, ...list]))
setPage(p)
if (list.length < 10) setFinished(true)
} catch (e) {
console.error('资讯列表加载失败', e)
} finally {
setLoading(false)
}
}
useEffect(() => {
load(1)
}, [])
return (
<View className='flex flex-col h-screen bg-gray-50'>
<NavBar title='资讯' />
<ScrollView scrollY className='flex-1' onScrollToLower={() => load(page + 1)}>
<View className='p-3'>
{articles.length > 0 ? (
articles.map((a) => <ArticleCard key={a.articleId} article={a} />)
) : (
<View className='py-10 text-center'>
<Text className='text-sm text-gray-400'>{loading ? '加载中...' : '暂无资讯'}</Text>
</View>
)}
{loading && (
<View className='py-3 text-center'>
<Text className='text-xs text-gray-400'>...</Text>
</View>
)}
</View>
</ScrollView>
<OfficialTabBar active='article' />
</View>
)
}
export default ArticleList
+49
View File
@@ -0,0 +1,49 @@
import { View, Text, Image } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { getCompressedImageUrl } from '@/utils/image'
import type { CmsArticle } from '@/api/cms/cmsArticle/model'
interface Props {
article: CmsArticle
onClick?: () => void
}
/**
* 资讯列表卡片:左图右文,点击进入详情。
* 复用项目图片工具 getCompressedImageUrl 保证 OSS 压缩一致。
*/
export default function ArticleCard({ article, onClick }: Props) {
const handle = () => {
if (onClick) {
onClick()
} else {
Taro.navigateTo({ url: `/official/article-detail?id=${article.articleId}` })
}
}
return (
<View className='flex bg-white rounded-lg overflow-hidden mb-3' onClick={handle}>
{article.image ? (
<Image
src={getCompressedImageUrl(article.image, { width: 240 })}
className='w-24 h-24'
mode='aspectFill'
/>
) : (
<View className='w-24 h-24 bg-gray-100 flex items-center justify-center'>
<Text className='text-gray-300 text-xs'></Text>
</View>
)}
<View className='flex-1 p-3 flex flex-col justify-center'>
<Text className='text-sm text-gray-800 font-medium text-ellipsis-2'>{article.title}</Text>
{article.overview ? (
<Text className='text-xs text-gray-400 mt-1 text-ellipsis-2'>{article.overview}</Text>
) : null}
<View className='flex items-center mt-2'>
<Text className='text-xs text-gray-300'>{article.source || article.author || ''}</Text>
<Text className='text-xs text-gray-300 ml-2'>{article.createTime?.slice(0, 10)}</Text>
</View>
</View>
</View>
)
}
@@ -0,0 +1,38 @@
import { View, Text } from '@tarojs/components'
import Taro from '@tarojs/taro'
const TABS = [
{ key: 'home', text: '首页', icon: '🏠', path: '/official/index' },
{ key: 'article', text: '资讯', icon: '📰', path: '/official/article' },
{ key: 'about', text: '关于', icon: '️', path: '/official/about' },
{ key: 'mine', text: '我的', icon: '👤', path: '/official/mine' },
]
/**
* 官网分包内底部导航(微信原生 tabBar 不支持分包页,故自实现)
* 通过 redirectTo 在四个官网页面间切换,避免页面栈无限增长。
*/
export default function OfficialTabBar({ active }: { active: string }) {
const go = (path: string) => {
if (path === active) return
Taro.redirectTo({ url: path })
}
return (
<View className='fixed bottom-0 left-0 right-0 flex bg-white border-t border-gray-200 safe-area-bottom'>
{TABS.map((t) => {
const on = active === t.key
return (
<View
key={t.key}
className='flex-1 flex flex-col items-center py-2'
onClick={() => go(t.path)}
>
<Text className={`text-xl ${on ? 'text-brand-600' : 'text-gray-400'}`}>{t.icon}</Text>
<Text className={`text-xs mt-0.5 ${on ? 'text-brand-600' : 'text-gray-400'}`}>{t.text}</Text>
</View>
)
})}
</View>
)
}
+29
View File
@@ -0,0 +1,29 @@
import { View, Text } from '@tarojs/components'
import Taro from '@tarojs/taro'
interface Props {
title: string
moreUrl?: string
}
/**
* 区块标题:左侧色条 + 标题,右侧「更多」跳转。
*/
export default function SectionTitle({ title, moreUrl }: Props) {
return (
<View className='flex items-center justify-between px-4 py-3'>
<View className='flex items-center'>
<View className='w-1 h-4 bg-brand-600 rounded mr-2' />
<Text className='text-base font-semibold text-gray-800'>{title}</Text>
</View>
{moreUrl && (
<Text
className='text-xs text-gray-400'
onClick={() => Taro.navigateTo({ url: moreUrl })}
>
</Text>
)}
</View>
)
}
+166
View File
@@ -0,0 +1,166 @@
import React, { useState, useEffect } from 'react'
import { View, Text, Swiper, SwiperItem, Image, ScrollView } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { getCompressedImageUrl } from '@/utils/image'
import {
listCmsAd,
getCmsWebsiteSiteInfo,
pageCmsArticle,
treeNavigation,
} from '@/api/official'
import type { CmsAd, CmsWebsite, CmsArticle, CmsNavigation } from '@/api/official'
import OfficialTabBar from '../components/OfficialTabBar'
import ArticleCard from '../components/ArticleCard'
import SectionTitle from '../components/SectionTitle'
definePageConfig({
navigationBarTitleText: '企业官网',
enableShareAppMessage: true,
})
const OfficialIndex: React.FC = () => {
const [site, setSite] = useState<CmsWebsite>()
const [banners, setBanners] = useState<CmsAd[]>([])
const [navs, setNavs] = useState<CmsNavigation[]>([])
const [articles, setArticles] = useState<CmsArticle[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
let mounted = true
;(async () => {
try {
const [siteRes, adRes, navRes, artRes] = await Promise.all([
getCmsWebsiteSiteInfo().catch(() => undefined),
listCmsAd({ adType: 'banner', status: 0 }).catch(() => [] as CmsAd[]),
treeNavigation({ top: 1 }).catch(() => [] as CmsNavigation[]),
pageCmsArticle({ status: 0, deleted: 0, page: 1, limit: 5 }).catch(() => undefined),
])
if (!mounted) return
if (siteRes) setSite(siteRes)
setBanners(Array.isArray(adRes) ? adRes : ((adRes as any)?.list || []))
const topNavs =
siteRes?.topNavs && siteRes.topNavs.length
? siteRes.topNavs
: (navRes || [])
setNavs(topNavs)
setArticles(artRes?.list || [])
} catch (e) {
console.error('官网首页加载失败', e)
} finally {
if (mounted) setLoading(false)
}
})()
return () => {
mounted = false
}
}, [])
const bannerSlides = banners.flatMap((b) =>
b.imageList && b.imageList.length
? b.imageList.map((img, i) => ({ key: `${b.adId}-${i}`, src: (img as any).url || '' }))
: b.image
? [{ key: `${b.adId}-0`, src: b.image }]
: []
)
const onNavClick = (n: CmsNavigation) => {
if (n.path) {
Taro.navigateTo({ url: n.path.startsWith('/') ? n.path : `/${n.path}` })
} else if (n.children && n.children.length) {
Taro.navigateTo({ url: `/official/article?categoryId=${n.navigationId}` })
}
}
return (
<View className='flex flex-col h-screen bg-gray-50'>
{/* 顶部品牌栏 */}
<View className='flex items-center px-4 py-3 bg-white'>
{site?.websiteLogo ? (
<Image
src={getCompressedImageUrl(site.websiteLogo, { width: 80 })}
className='w-8 h-8 rounded mr-2'
mode='aspectFill'
/>
) : null}
<Text className='text-lg font-semibold text-gray-800'>
{site?.websiteName || '企业官网'}
</Text>
</View>
<ScrollView scrollY className='flex-1'>
{/* Banner 轮播 */}
{bannerSlides.length > 0 && (
<Swiper
autoplay
interval={3000}
className='mx-3 mt-3 rounded-lg overflow-hidden'
style={{ height: '150px' }}
>
{bannerSlides.map((s) => (
<SwiperItem key={s.key}>
<Image
src={getCompressedImageUrl(s.src, { width: 750 })}
className='w-full h-full'
mode='aspectFill'
/>
</SwiperItem>
))}
</Swiper>
)}
{/* 栏目导航宫格 */}
{navs.length > 0 && (
<View className='mx-3 mt-3 bg-white rounded-lg p-3'>
<View className='flex flex-wrap'>
{navs.map((n) => (
<View
key={n.navigationId}
className='w-1/4 flex flex-col items-center py-2'
onClick={() => onNavClick(n)}
>
<View
className='w-10 h-10 rounded-full flex items-center justify-center mb-1'
style={{ background: n.color || '#EAF2FB' }}
>
<Text className='text-base'>{(n.icon as any) || '•'}</Text>
</View>
<Text className='text-xs text-gray-600'>{n.title}</Text>
</View>
))}
</View>
</View>
)}
{/* 资讯动态 */}
<View className='mt-1'>
<SectionTitle title='资讯动态' moreUrl='/official/article' />
<View className='px-3'>
{articles.length > 0 ? (
articles.map((a) => <ArticleCard key={a.articleId} article={a} />)
) : (
<View className='py-8 text-center'>
<Text className='text-sm text-gray-400'>
{loading ? '加载中...' : '暂无资讯'}
</Text>
</View>
)}
</View>
</View>
{/* 留言咨询 CTA */}
<View className='mx-3 mt-3 mb-4'>
<View
className='bg-brand-600 rounded-lg py-3 flex items-center justify-center'
onClick={() => Taro.navigateTo({ url: '/official/message' })}
>
<Text className='text-white text-sm font-medium'>📩 </Text>
</View>
</View>
</ScrollView>
<OfficialTabBar active='home' />
</View>
)
}
export default OfficialIndex
+111
View File
@@ -0,0 +1,111 @@
import React, { useState } from 'react'
import { View, Text, Input, Textarea } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { submitCmsContactLead } from '@/api/official'
import NavBar from '@/components/NavBar'
definePageConfig({
navigationBarTitleText: '留言咨询',
})
interface FormState {
name: string
phone: string
company: string
need: string
}
const Message: React.FC = () => {
const [form, setForm] = useState<FormState>({ name: '', phone: '', company: '', need: '' })
const [submitting, setSubmitting] = useState(false)
const set = (k: keyof FormState, v: string) =>
setForm((prev) => ({ ...prev, [k]: v }))
const submit = async () => {
if (!form.name.trim()) {
Taro.showToast({ title: '请填写称呼', icon: 'none' })
return
}
if (!/^1\d{10}$/.test(form.phone.trim())) {
Taro.showToast({ title: '请填写正确的手机号', icon: 'none' })
return
}
setSubmitting(true)
try {
await submitCmsContactLead({ ...form, source: 'miniapp' })
Taro.showToast({ title: '提交成功,我们会尽快联系您', icon: 'success' })
setTimeout(() => Taro.navigateBack(), 1200)
} catch (e: any) {
Taro.showToast({ title: e?.message || '提交失败', icon: 'none' })
} finally {
setSubmitting(false)
}
}
return (
<View className='flex flex-col h-screen bg-gray-50'>
<NavBar title='留言咨询' />
<View className='flex-1 p-4'>
<View className='bg-white rounded-lg p-4'>
<Field label='称呼' placeholder='请输入您的称呼' value={form.name} onInput={(v) => set('name', v)} />
<Field
label='手机号'
placeholder='请输入手机号'
value={form.phone}
type='number'
onInput={(v) => set('phone', v)}
/>
<Field label='公司' placeholder='选填' value={form.company} onInput={(v) => set('company', v)} />
<View className='mt-3'>
<Text className='text-sm text-gray-600'></Text>
<Textarea
className='w-full mt-2 p-2 bg-gray-50 rounded text-sm text-gray-800'
style={{ height: '120px' }}
placeholder='请描述您的需求'
value={form.need}
onInput={(e) => set('need', (e.detail as any).value)}
/>
</View>
</View>
<View
className='mt-4 bg-brand-600 rounded-full py-3 text-center'
onClick={submit}
>
<Text className='text-white text-sm font-medium'>
{submitting ? '提交中...' : '提交留言'}
</Text>
</View>
</View>
</View>
)
}
function Field({
label,
placeholder,
value,
onInput,
type,
}: {
label: string
placeholder?: string
value: string
onInput: (v: string) => void
type?: 'text' | 'number'
}) {
return (
<View className='flex items-center py-3 border-b border-gray-100'>
<Text className='text-sm text-gray-600 w-16'>{label}</Text>
<Input
className='flex-1 text-sm text-gray-800 ml-2'
placeholder={placeholder}
value={value}
type={type || 'text'}
onInput={(e) => onInput((e.detail as any).value)}
/>
</View>
)
}
export default Message
+66
View File
@@ -0,0 +1,66 @@
import React, { useState, useEffect } from 'react'
import { View, Text, ScrollView } from '@tarojs/components'
import Taro from '@tarojs/taro'
import { getCmsWebsiteSiteInfo, pageCmsContactLead } from '@/api/official'
import type { CmsWebsite } from '@/api/official'
import OfficialTabBar from '../components/OfficialTabBar'
import NavBar from '@/components/NavBar'
definePageConfig({
navigationBarTitleText: '我的',
})
const Mine: React.FC = () => {
const [site, setSite] = useState<CmsWebsite>()
const [leadCount, setLeadCount] = useState(0)
useEffect(() => {
getCmsWebsiteSiteInfo()
.then((s) => setSite(s))
.catch(() => {})
pageCmsContactLead({ page: 1, limit: 1 })
.then((r) => setLeadCount(r?.count || 0))
.catch(() => {})
}, [])
const menus = [
{ label: '我的留言', value: leadCount ? `${leadCount}` : '暂无', url: '/official/message' },
{ label: '关于我们', url: '/official/about' },
{ label: '留言咨询', url: '/official/message' },
]
return (
<View className='flex flex-col h-screen bg-gray-50'>
<NavBar title='我的' />
<ScrollView scrollY className='flex-1'>
<View className='flex items-center p-4 bg-white'>
<View className='w-14 h-14 rounded-full bg-brand-600 flex items-center justify-center'>
<Text className='text-white text-xl'>{(site?.websiteName || '官').slice(0, 1)}</Text>
</View>
<View className='ml-3'>
<Text className='text-base font-semibold text-gray-800'>
{site?.websiteName || '企业官网'}
</Text>
<Text className='text-xs text-gray-400 mt-1'></Text>
</View>
</View>
<View className='mt-3 bg-white rounded-lg'>
{menus.map((m) => (
<View
key={m.label}
className='flex items-center justify-between px-4 py-4 border-b border-gray-100'
onClick={() => Taro.navigateTo({ url: m.url })}
>
<Text className='text-sm text-gray-700'>{m.label}</Text>
<Text className='text-xs text-gray-400'>{m.value || ''}</Text>
</View>
))}
</View>
</ScrollView>
<OfficialTabBar active='mine' />
</View>
)
}
export default Mine
+12
View File
@@ -280,6 +280,18 @@ const IndexPage: React.FC = () => {
)}
</View>
)}
{/* 企业官网入口 */}
<View
className='mx-3 mt-2 bg-white rounded-lg p-3 flex items-center justify-between'
onClick={() => Taro.navigateTo({ url: '/official/index' })}
>
<View>
<Text className='text-base font-medium text-gray-800'></Text>
<Text className='text-xs text-gray-400 mt-1 block'> · · </Text>
</View>
<Text className='text-brand-600 text-sm'> </Text>
</View>
{/* 轮播图:有数据才显示,为空则不渲染 */}
{bannerSlides.length > 0 && (
<View className='mx-3 mt-2 rounded-lg overflow-hidden index-banner-wrap'>