Files
websopy-taro/src/pages/index/index.tsx
赵忠林 e6c7ffa46c fix(index): 优化外链提示及删除复制咨询模板功能
- 替换小程序内外链打开方式提示为弹窗提醒用户手动复制
- 删除复制咨询模板功能及相关按钮
- 修改联系我们文案,突出直接电话沟通或需求反馈
- 移除无用导入的复制文本工具函数copyText
2026-06-30 18:18:38 +08:00

512 lines
20 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

import Taro, { getCurrentInstance, useShareAppMessage } from '@tarojs/taro'
import { View, Text } from '@tarojs/components'
import { useMemo, useState } from 'react'
import { Button } from '@nutui/nutui-react-taro'
import { useConfig } from '@/hooks/useConfig'
import './index.scss'
function Home() {
const [ecoTab, setEcoTab] = useState<'template' | 'plugin'>('template')
const { config } = useConfig()
useShareAppMessage(() => {
// 获取当前用户ID用于生成邀请链接
const userId = Taro.getStorageSync('UserId')
return {
title: config?.siteName
? `${config.siteName} - 网宿软件`
: '软件开发平台SaaS + 私有化 + 模板/插件生态',
path: userId
? `/pages/index/index?inviter=${userId}&source=share&t=${Date.now()}`
: `/pages/index/index`,
success: function () {
console.log('首页分享成功')
Taro.showToast({
title: '分享成功',
icon: 'success',
duration: 2000
})
},
fail: function () {
console.log('首页分享失败')
Taro.showToast({
title: '分享失败',
icon: 'none',
duration: 2000
})
}
}
})
const scrollToSection = (id: string) => {
const query = Taro.createSelectorQuery()
const inst = getCurrentInstance()
// 兼容 React 运行时:尽量将查询限定在当前页面作用域内
if (inst?.page && typeof query.in === 'function') query.in(inst.page)
query.selectViewport().scrollOffset()
query.select(`#${id}`).boundingClientRect()
query.exec((res: any[]) => {
const viewport = res?.[0]
const rect = res?.[1]
const scrollTop = (viewport?.scrollTop || 0) + (rect?.top || 0) - 50
if (Number.isFinite(scrollTop)) {
Taro.pageScrollTo({ scrollTop: Math.max(scrollTop, 0), duration: 260 })
}
})
}
const toAbsoluteMaybe = (url: string) => {
if (/^https?:\/\//i.test(url)) return url
const base = (config?.domain || '').trim()
if (!base) return url
const withProto = /^https?:\/\//i.test(base) ? base : `https://${base}`
// 处理 domain 末尾/ 与 url 开头/ 的组合
const normalizedBase = withProto.replace(/\/+$/, '')
const normalizedPath = url.startsWith('/') ? url : `/${url}`
return `${normalizedBase}${normalizedPath}`
}
const openMaybeLink = (url?: string) => {
if (!url) return
const abs = toAbsoluteMaybe(url)
// 相对路径在小程序内直接跳转
if (url.startsWith('/')) {
Taro.navigateTo({ url })
return
}
// 小程序内无法直接打开外链,提示用户手动复制到浏览器
if (/^https?:\/\//i.test(abs)) {
Taro.showModal({
title: '提示',
content: '该链接需要在浏览器中打开,请点击确认后手动复制链接访问。',
showCancel: false,
confirmText: '知道了'
})
return
}
Taro.showToast({ title: '请在 PC 端访问该入口', icon: 'none', duration: 1600 })
}
const callPhone = () => {
const tel = (config?.tel || '').trim()
if (!tel) {
Taro.showToast({ title: '暂无联系电话', icon: 'none', duration: 1600 })
return
}
Taro.makePhoneCall({ phoneNumber: tel })
}
const goToDeveloperCenter = () => {
Taro.navigateTo({ url: '/developer/index' })
}
const goToEnterpriseConsole = () => {
Taro.navigateTo({ url: '/enterprise/index' })
}
const products = useMemo(
() => [
{
title: '企业官网',
recommend: true,
desc: '品牌展示与获客转化支持多模板、多语言、SEO 与可视化配置。',
tags: ['模板', 'SEO', '多语言', '私有化'],
adminUrl: 'https://site.websoft.top',
},
{
title: '小程序/公众号',
recommend: false,
desc: '多端渠道接入与统一管理,适配常见内容与电商场景。',
tags: ['多端', '渠道', '可扩展'],
adminUrl: 'https://mp.websoft.top',
},
{
title: '电商系统',
recommend: true,
desc: '商品/订单/支付/营销基础能力,插件化扩展,支持多端触达。',
tags: ['支付', '插件', '营销', '多租户'],
adminUrl: 'https://shop.websoft.top',
},
{
title: '管理后台',
recommend: false,
desc: '多租户管理、角色权限、组织架构与可扩展菜单体系。',
tags: ['权限', '多租户', '审计'],
adminUrl: 'https://oa.websoft.top',
},
{
title: '开发者中心',
recommend: false,
desc: '应用开发与交付入口项目、应用、API Key、部署与教程文档。',
tags: ['项目', '应用', 'API Key', '部署'],
adminUrl: '/developer',
onClick: goToDeveloperCenter,
},
{
title: '企业控制台',
recommend: false,
desc: '企业应用管理、成员权限、订单账单与运营监控。',
tags: ['应用', '成员', '账单', '监控'],
adminUrl: '/enterprise',
onClick: goToEnterpriseConsole,
},
],
[]
)
const capabilities = useMemo(
() => [
{
title: 'SaaS 多租户平台',
badge: '核心',
desc: '租户隔离、组织与权限体系、配置中心与审计能力,为多业务线统一底座。',
},
{
title: '私有化部署',
badge: '可选',
desc: '支持本地/专有云部署,提供部署文档、验收清单与升级策略,满足安全合规。',
},
{
title: '模板市场',
badge: '生态',
desc: '行业模板一键套用,默认配置与初始化脚本配套,交付更标准、上线更快。',
},
{
title: '插件市场',
badge: '扩展',
desc: '支付、会员、营销、工单等能力按需加购;支持授权、更新与版本管理。',
},
{
title: '自动开通链路',
badge: '交付',
desc: '选品支付后自动创建租户、初始化模块/菜单/基础数据,并交付访问入口。',
},
{
title: '模块化与可扩展',
badge: '开发',
desc: '支持按模块组合产品能力,插件化扩展点让二开与生态合作更高效。',
},
],
[]
)
return (
<>
<View className="home-page">
{/* 首屏:价值主张 + 关键动作 */}
<View className="hero">
<View className="hero__bgGlow" />
<View className="hero__inner">
<View className="hero__tag">
<Text className="hero__tagText">v3.0 </Text>
</View>
<View className="hero__title">
<Text className="hero__titleText">SaaS + + /</Text>
</View>
<View className="hero__desc">
<Text className="hero__descText">
/
</Text>
</View>
<View className="hero__actions">
<Button
type="primary"
block
className="hero__btn hero__btn--primary"
onClick={() => scrollToSection('contact')}
>
</Button>
<View className="hero__btnRow">
</View>
</View>
</View>
</View>
{/* 快捷导航:减少滚动成本 */}
<View className="quickNav">
{[
{ key: 'products', title: '产品矩阵' },
{ key: 'capabilities', title: '核心能力' },
{ key: 'flow', title: '开通流程' },
{ key: 'ecosystem', title: '生态' },
].map((x) => (
<View key={x.key} className="quickNav__item" onClick={() => scrollToSection(x.key)}>
<Text className="quickNav__text">{x.title}</Text>
</View>
))}
</View>
{/* 角色入口卡片 */}
<View className="roleEntry">
<View className="roleEntry__title">
<Text className="roleEntry__titleText"></Text>
</View>
<View className="roleEntry__grid">
<View className="roleEntry__card roleEntry__card--developer" onClick={goToDeveloperCenter}>
<View className="roleEntry__icon">
<Text className="roleEntry__iconText">🛠</Text>
</View>
<View className="roleEntry__info">
<Text className="roleEntry__cardTitle"></Text>
<Text className="roleEntry__cardDesc"> · · API Key</Text>
</View>
<View className="roleEntry__arrow">
<Text className="roleEntry__arrowText"></Text>
</View>
</View>
<View className="roleEntry__card roleEntry__card--enterprise" onClick={goToEnterpriseConsole}>
<View className="roleEntry__icon">
<Text className="roleEntry__iconText">🏢</Text>
</View>
<View className="roleEntry__info">
<Text className="roleEntry__cardTitle"></Text>
<Text className="roleEntry__cardDesc"> · · </Text>
</View>
<View className="roleEntry__arrow">
<Text className="roleEntry__arrowText"></Text>
</View>
</View>
</View>
</View>
{/* 产品矩阵 */}
<View className="section" id="products">
<View className="section__head">
<Text className="section__title"></Text>
<Text className="section__desc">
/
</Text>
</View>
<View className="productGrid">
{products.map((p) => (
<View key={p.title} className="card productCard">
<View className="productCard__top">
<Text className="productCard__title">{p.title}</Text>
{p.recommend ? (
<View className="badge badge--green">
<Text className="badge__text"></Text>
</View>
) : null}
</View>
<Text className="productCard__desc">{p.desc}</Text>
<View className="tagRow">
{p.tags.map((t) => (
<View key={t} className="tag">
<Text className="tag__text">{t}</Text>
</View>
))}
</View>
<View className="productCard__actions">
{p.onClick ? (
<Button size="small" type="primary" onClick={p.onClick}>
</Button>
) : (
<Button size="small" type="primary" onClick={() => openMaybeLink(p.adminUrl)}>
</Button>
)}
</View>
</View>
))}
</View>
<View className="section__foot">
<Button type="default" block onClick={() => scrollToSection('ecosystem')}>
/
</Button>
</View>
</View>
{/* 核心能力 */}
<View className="section section--alt" id="capabilities">
<View className="section__head">
<Text className="section__title"></Text>
<Text className="section__desc"></Text>
</View>
<View className="capGrid">
{capabilities.map((c) => (
<View key={c.title} className="card capCard">
<View className="capCard__top">
<Text className="capCard__title">{c.title}</Text>
<View className="badge badge--soft">
<Text className="badge__text">{c.badge}</Text>
</View>
</View>
<Text className="capCard__desc">{c.desc}</Text>
</View>
))}
</View>
</View>
{/* 支付即开通 */}
<View className="section" id="flow">
<View className="section__head">
<Text className="section__title"></Text>
<Text className="section__desc">
访
</Text>
</View>
<View className="card flowCard">
{[
{ title: '选择产品/套餐', desc: '支持产品矩阵、模板/插件加购、增值项' },
{ title: '下单支付', desc: '支付成功触发开通任务编排' },
{ title: '创建租户', desc: '租户隔离、域名/应用信息绑定、管理员生成' },
{ title: '模块初始化', desc: '按所购产品加载模块与菜单权限,写入基础数据/示例数据' },
{ title: '交付上线', desc: 'SaaS 直接可用;私有化交付镜像/部署文档/验收清单' },
].map((s, idx) => (
<View key={s.title} className="flowStep">
<View className="flowStep__dot">
<Text className="flowStep__dotText">{idx + 1}</Text>
</View>
<View className="flowStep__body">
<Text className="flowStep__title">{s.title}</Text>
<Text className="flowStep__desc">{s.desc}</Text>
</View>
</View>
))}
</View>
<View className="section__foot">
<Button type="primary" block onClick={() => scrollToSection('products')}>
</Button>
</View>
</View>
{/* 模板与插件生态 */}
<View className="section section--alt" id="ecosystem">
<View className="section__head">
<Text className="section__title"></Text>
<Text className="section__desc"></Text>
</View>
<View className="segTabs">
<View
className={`segTabs__item ${ecoTab === 'template' ? 'segTabs__item--active' : ''}`}
onClick={() => setEcoTab('template')}
>
<Text className="segTabs__text"></Text>
</View>
<View
className={`segTabs__item ${ecoTab === 'plugin' ? 'segTabs__item--active' : ''}`}
onClick={() => setEcoTab('plugin')}
>
<Text className="segTabs__text"></Text>
</View>
</View>
{ecoTab === 'template' ? (
<View className="twoCard">
<View className="card">
<Text className="card__title"></Text>
<Text className="card__desc">/</Text>
</View>
<View className="card">
<Text className="card__title"></Text>
<Text className="card__desc"></Text>
</View>
</View>
) : (
<View className="twoCard">
<View className="card">
<Text className="card__title"></Text>
<Text className="card__desc"></Text>
</View>
<View className="card">
<Text className="card__title"></Text>
<Text className="card__desc"></Text>
</View>
</View>
)}
<View className="section__foot">
<Button
type="default"
block
onClick={() => {
Taro.showToast({ title: '该入口可对接到模板/插件市场', icon: 'none', duration: 1600 })
}}
>
/
</Button>
</View>
</View>
{/* 底部 CTA */}
<View className="section" id="contact">
<View className="section__head">
<Text className="section__title"></Text>
<Text className="section__desc">
SaaS/
</Text>
</View>
<View className="card">
<Text className="card__title"></Text>
<View className="list">
{[
'你希望售卖哪些产品(官网/电商/小程序/门户等)?',
'是否需要模板/插件市场(购买、授权、更新)?',
'是否需要“支付即开通”(自动创建租户/初始化模块与数据)?',
'交付方式SaaS 或私有化部署?是否有合规要求?',
].map((t) => (
<View key={t} className="list__item">
<View className="list__dot" />
<Text className="list__text">{t}</Text>
</View>
))}
</View>
<View className="contactMeta">
{config?.tel ? (
<View className="contactMeta__row">
<Text className="contactMeta__label"></Text>
<Text className="contactMeta__value">{config.tel}</Text>
</View>
) : null}
{config?.workDay ? (
<View className="contactMeta__row">
<Text className="contactMeta__label"></Text>
<Text className="contactMeta__value">{config.workDay}</Text>
</View>
) : null}
</View>
<View className="contactActions">
<Button type="primary" block onClick={callPhone}>
</Button>
</View>
</View>
</View>
<View className="bottomCta">
<View className="bottomCta__text">
<Text className="bottomCta__title"></Text>
<Text className="bottomCta__desc"></Text>
</View>
<View className="bottomCta__actions">
<Button type="default" block className={'bottom__view'} onClick={() => scrollToSection('products')}>
</Button>
<Button type="primary" block onClick={() => scrollToSection('contact')}>
</Button>
</View>
</View>
</View>
</>
)
}
export default Home