feat(home): 更新首页UI设计并重构组件结构

- 更新导航栏标题为WEBSOFT,移除自定义导航样式
- 重构Hero区域样式,替换渐变背景和品牌展示组件
- 新增快速导航栏支持产品、能力、流程等功能跳转
- 实现产品矩阵展示企业官网、小程序、电商系统等六大产品
- 添加核心能力介绍包括SaaS多租户、私有化部署、模板插件市场
- 集成联系咨询功能和电话呼叫能力
- 优化分享消息内容显示站点名称和网宿软件信息
- 重构页面布局结构提升移动端用户体验
This commit is contained in:
2026-02-03 08:44:02 +08:00
parent 5612f40818
commit 36f87272df
10 changed files with 822 additions and 605 deletions

View File

@@ -1,5 +1,4 @@
export default definePageConfig({
navigationBarTitleText: 'shopLnk.cn - 数灵云店',
navigationBarTextStyle: 'black',
navigationStyle: 'custom'
navigationBarTitleText: 'WEBSOFT',
navigationBarTextStyle: 'black'
})

View File

@@ -1,446 +1,486 @@
page {
//background: url('https://oss.wsdns.cn/20250621/33ca4ca532e647bc918a59d01f5d88a9.jpg?x-oss-process=image/resize,m_fixed,w_2000/quality,Q_90') no-repeat top center;
//background-size: 100%;
background: linear-gradient(to bottom, #e9fff2, #ffffff);
background: linear-gradient(to bottom, #f6f7fb, #ffffff);
}
.home-page {
padding: 24rpx 24rpx calc(32rpx + env(safe-area-inset-bottom));
}
.home-hero {
/* Hero */
.hero {
position: relative;
overflow: hidden;
border-radius: 28rpx;
background: linear-gradient(180deg, #bfefff 0%, #eafaff 40%, #fff7ec 100%);
box-shadow: 0 18rpx 36rpx rgba(0, 0, 0, 0.06);
padding: 26rpx 22rpx 28rpx;
background: linear-gradient(180deg, #0b1220 0%, #0b0f1a 100%);
box-shadow: 0 18rpx 36rpx rgba(0, 0, 0, 0.14);
}
.home-hero__bg {
.hero__bgGlow {
position: absolute;
inset: 0;
background:
radial-gradient(360rpx 240rpx at 18% 16%, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0)),
radial-gradient(320rpx 220rpx at 84% 18%, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0)),
linear-gradient(180deg, rgba(0, 207, 255, 0.12), rgba(0, 0, 0, 0));
left: 50%;
top: -160rpx;
width: 760rpx;
height: 420rpx;
transform: translateX(-50%);
border-radius: 999rpx;
background: rgba(34, 214, 74, 0.22);
filter: blur(60rpx);
pointer-events: none;
}
.home-hero__content {
.hero__inner {
position: relative;
display: flex;
justify-content: space-between;
gap: 18rpx;
padding: 26rpx 24rpx 28rpx;
min-height: 320rpx;
}
.home-hero__left {
flex: 1;
min-width: 0;
}
.home-hero__topRow {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16rpx;
}
.home-hero__brand {
flex: none;
.hero__tag {
display: inline-flex;
align-items: center;
padding: 8rpx 14rpx;
padding: 10rpx 16rpx;
border-radius: 999rpx;
background: rgba(255, 214, 84, 0.92);
color: #2a2a2a;
font-weight: 700;
background: rgba(255, 150, 0, 0.16);
border: 2rpx solid rgba(255, 150, 0, 0.35);
}
.hero__tagText {
font-size: 24rpx;
font-weight: 700;
color: rgba(255, 195, 115, 0.95);
line-height: 1;
}
.home-hero__brandText {
line-height: 1;
.hero__title {
margin-top: 18rpx;
}
.home-hero__tag {
flex: none;
display: inline-flex;
align-items: center;
padding: 10rpx 18rpx;
border-radius: 18rpx;
background: linear-gradient(90deg, #22d64a 0%, #7df4b0 100%);
box-shadow: 0 14rpx 24rpx rgba(36, 202, 148, 0.22);
}
.home-hero__tagText {
font-size: 56rpx;
.hero__titleText {
display: block;
font-size: 40rpx;
font-weight: 900;
color: #ffffff;
line-height: 1;
line-height: 1.18;
}
.home-hero__date {
flex: 1;
min-width: 0;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10rpx 14rpx;
border-radius: 999rpx;
background: rgba(255, 255, 255, 0.75);
.hero__desc {
margin-top: 14rpx;
}
.home-hero__dateText {
.hero__descText {
display: block;
font-size: 26rpx;
font-weight: 700;
color: #1a1a1a;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
color: rgba(255, 255, 255, 0.78);
line-height: 1.6;
}
.home-hero__headline {
margin-top: 22rpx;
}
.home-hero__headlineText {
display: block;
font-size: 42rpx;
font-weight: 900;
color: #0b0b0b;
letter-spacing: 0.5px;
line-height: 1.15;
}
.home-hero__right {
width: 200rpx;
display: flex;
justify-content: flex-end;
align-items: flex-end;
}
.home-hero__bottle {
position: relative;
width: 190rpx;
height: 250rpx;
border-radius: 28rpx;
background:
radial-gradient(240rpx 360rpx at 60% 30%, rgba(255, 255, 255, 0.95), rgba(255, 255, 255, 0.18)),
linear-gradient(180deg, rgba(255, 255, 255, 0.7), rgba(255, 255, 255, 0.1));
border: 2rpx solid rgba(255, 255, 255, 0.65);
box-shadow: 0 18rpx 36rpx rgba(0, 0, 0, 0.12);
}
.home-hero__bottleCap {
position: absolute;
top: 14rpx;
left: 50%;
transform: translateX(-50%);
width: 88rpx;
height: 26rpx;
border-radius: 999rpx;
background: linear-gradient(180deg, #d7e6f3, #b0cadd);
box-shadow: 0 10rpx 20rpx rgba(0, 0, 0, 0.12);
}
.home-hero__bottleLabel {
position: absolute;
left: 18rpx;
right: 18rpx;
bottom: 30rpx;
padding: 12rpx 12rpx;
border-radius: 18rpx;
background: linear-gradient(90deg, rgba(0, 150, 255, 0.18), rgba(0, 255, 210, 0.18));
border: 2rpx solid rgba(255, 255, 255, 0.45);
}
.home-hero__bottleLabelText {
font-size: 30rpx;
font-weight: 800;
color: rgba(0, 80, 140, 0.95);
text-align: center;
display: block;
}
.ticket-card {
margin-top: 18rpx;
border-radius: 22rpx;
overflow: hidden;
background: #ffffff;
box-shadow: 0 18rpx 36rpx rgba(0, 0, 0, 0.06);
}
.ticket-card__head {
display: flex;
justify-content: space-between;
align-items: center;
padding: 18rpx 20rpx;
background: linear-gradient(90deg, #22d64a 0%, #7df4b0 100%);
}
.ticket-card__title {
color: #ffffff;
font-weight: 800;
font-size: 28rpx;
}
.ticket-card__count {
color: rgba(255, 255, 255, 0.92);
font-size: 24rpx;
}
.ticket-card__countNum {
color: #ffffff;
font-weight: 900;
}
.ticket-card__body {
padding: 20rpx 10rpx 22rpx;
}
.shortcut-grid {
display: flex;
justify-content: space-between;
align-items: flex-start;
gap: 12rpx;
}
.shortcut-grid__item {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 12rpx;
}
.shortcut-grid__icon {
width: 88rpx;
height: 88rpx;
border-radius: 18rpx;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
color: #20c26a;
border: 2rpx solid rgba(32, 194, 106, 0.35);
}
.shortcut-grid__text {
font-size: 24rpx;
color: #333333;
}
.home-tabs {
.hero__actions {
margin-top: 18rpx;
}
.home-tabs__inner {
display: flex;
gap: 18rpx;
padding: 0 4rpx;
.hero__btn {
border-radius: 18rpx !important;
}
.home-tabs__item {
display: inline-flex;
align-items: center;
justify-content: center;
padding: 10rpx 18rpx;
border-radius: 999rpx;
background: transparent;
.hero__btn--primary {
background: linear-gradient(90deg, #24d34c 0%, #6df09a 100%) !important;
border: none !important;
}
.home-tabs__item--active {
background: rgba(32, 194, 106, 0.16);
}
.home-tabs__itemText {
font-size: 28rpx;
color: #2a2a2a;
white-space: nowrap;
}
.home-tabs__item--active .home-tabs__itemText {
color: #16b65a;
font-weight: 800;
}
.goods-grid {
margin-top: 18rpx;
.hero__btnRow {
margin-top: 12rpx;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 18rpx;
gap: 12rpx;
}
.goods-card {
border-radius: 22rpx;
overflow: hidden;
background: #ffffff;
box-shadow: 0 18rpx 36rpx rgba(0, 0, 0, 0.06);
}
.goods-card__imgWrap {
padding: 18rpx 18rpx 0;
}
.goods-card__img {
width: 100%;
height: 280rpx;
border-radius: 18rpx;
background: #f4f4f4;
}
.goods-card__body {
padding: 18rpx 18rpx 20rpx;
}
.goods-card__title {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
font-size: 26rpx;
font-weight: 700;
color: #1c1c1c;
min-height: 72rpx;
}
.goods-card__meta {
margin-top: 10rpx;
display: flex;
justify-content: space-between;
align-items: flex-end;
/* Quick nav */
.quickNav {
margin-top: 14rpx;
padding: 12rpx 10rpx;
border-radius: 20rpx;
background: rgba(255, 255, 255, 0.92);
box-shadow: 0 14rpx 26rpx rgba(0, 0, 0, 0.06);
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 10rpx;
position: sticky;
top: 0;
z-index: 20;
}
.goods-card__sold {
font-size: 22rpx;
color: #9a9a9a;
white-space: nowrap;
}
.goods-card__price {
.quickNav__item {
height: 66rpx;
border-radius: 16rpx;
display: flex;
align-items: baseline;
gap: 4rpx;
color: #27c86b;
white-space: nowrap;
align-items: center;
justify-content: center;
background: rgba(22, 182, 90, 0.08);
border: 2rpx solid rgba(22, 182, 90, 0.14);
}
.goods-card__priceUnit {
font-size: 22rpx;
.quickNav__text {
font-size: 24rpx;
font-weight: 800;
color: #168c49;
line-height: 1;
}
.goods-card__priceValue {
font-size: 36rpx;
/* Sections */
.section {
margin-top: 22rpx;
padding: 20rpx 18rpx;
border-radius: 24rpx;
background: #ffffff;
box-shadow: 0 16rpx 32rpx rgba(0, 0, 0, 0.06);
}
.section--alt {
background: #f7f9fc;
}
.section__head {
margin-bottom: 14rpx;
}
.section__title {
display: block;
font-size: 34rpx;
font-weight: 900;
color: #111111;
line-height: 1.2;
}
.goods-card__actions {
.section__desc {
display: block;
margin-top: 8rpx;
font-size: 26rpx;
color: #666666;
line-height: 1.6;
}
.section__foot {
margin-top: 16rpx;
display: flex;
}
/* Generic card */
.card {
border-radius: 20rpx;
padding: 18rpx 16rpx;
background: #ffffff;
box-shadow: 0 14rpx 26rpx rgba(0, 0, 0, 0.05);
}
.card__title {
display: block;
font-size: 30rpx;
font-weight: 800;
color: #111111;
}
.card__desc {
display: block;
margin-top: 10rpx;
font-size: 26rpx;
color: #666666;
line-height: 1.6;
}
/* Product grid */
.productGrid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14rpx;
}
.goods-card__btn {
flex: 1;
height: 64rpx;
.productCard {
padding: 16rpx 14rpx;
}
.productCard__top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10rpx;
}
.productCard__title {
font-size: 28rpx;
font-weight: 900;
color: #111111;
}
.productCard__desc {
display: block;
margin-top: 10rpx;
font-size: 24rpx;
color: #666666;
line-height: 1.55;
}
.productCard__actions {
margin-top: 12rpx;
}
.tagRow {
margin-top: 10rpx;
display: flex;
flex-wrap: wrap;
gap: 10rpx;
}
.tag {
padding: 6rpx 10rpx;
border-radius: 999rpx;
background: rgba(22, 182, 90, 0.12);
border: 2rpx solid rgba(22, 182, 90, 0.22);
}
.tag__text {
font-size: 22rpx;
font-weight: 700;
color: #16b65a;
line-height: 1;
}
/* Badges */
.badge {
padding: 6rpx 10rpx;
border-radius: 999rpx;
}
.badge__text {
font-size: 22rpx;
font-weight: 800;
line-height: 1;
}
.badge--green {
background: rgba(22, 182, 90, 0.14);
border: 2rpx solid rgba(22, 182, 90, 0.24);
}
.badge--green .badge__text {
color: #16b65a;
}
.badge--soft {
background: rgba(0, 0, 0, 0.06);
}
.badge--soft .badge__text {
color: #333333;
}
/* Capabilities */
.capGrid {
display: grid;
grid-template-columns: 1fr;
gap: 14rpx;
}
.capCard__top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10rpx;
}
.capCard__title {
font-size: 30rpx;
font-weight: 900;
color: #111111;
}
.capCard__desc {
display: block;
margin-top: 10rpx;
font-size: 26rpx;
color: #666666;
line-height: 1.6;
}
/* Flow */
.flowCard {
padding: 6rpx 10rpx;
}
.flowStep {
display: flex;
gap: 14rpx;
padding: 14rpx 8rpx;
}
.flowStep + .flowStep {
border-top: 2rpx solid rgba(0, 0, 0, 0.06);
}
.flowStep__dot {
flex: none;
width: 40rpx;
height: 40rpx;
border-radius: 999rpx;
background: rgba(22, 182, 90, 0.14);
display: flex;
align-items: center;
justify-content: center;
}
.goods-card__btn--ghost {
border: 2rpx solid rgba(32, 194, 106, 0.7);
background: #ffffff;
.flowStep__dotText {
font-size: 22rpx;
font-weight: 900;
color: #16b65a;
}
.goods-card__btn--primary {
background: linear-gradient(90deg, #24d34c 0%, #6df09a 100%);
.flowStep__body {
flex: 1;
min-width: 0;
}
.goods-card__btnText {
.flowStep__title {
display: block;
font-size: 28rpx;
font-weight: 900;
color: #111111;
line-height: 1.2;
}
.flowStep__desc {
display: block;
margin-top: 8rpx;
font-size: 24rpx;
font-weight: 700;
color: #18b85a;
color: #666666;
line-height: 1.55;
}
/* Segmented tabs */
.segTabs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12rpx;
padding: 10rpx;
border-radius: 18rpx;
background: rgba(0, 0, 0, 0.04);
}
.segTabs__item {
height: 70rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
}
.segTabs__item--active {
background: #ffffff;
box-shadow: 0 10rpx 22rpx rgba(0, 0, 0, 0.08);
}
.segTabs__text {
font-size: 28rpx;
font-weight: 900;
color: #2a2a2a;
}
.twoCard {
margin-top: 14rpx;
display: grid;
grid-template-columns: 1fr;
gap: 14rpx;
}
/* List */
.list {
margin-top: 12rpx;
}
.list__item {
display: flex;
gap: 12rpx;
padding: 10rpx 0;
}
.list__dot {
flex: none;
width: 10rpx;
height: 10rpx;
margin-top: 12rpx;
border-radius: 999rpx;
background: rgba(22, 182, 90, 0.9);
}
.list__text {
flex: 1;
min-width: 0;
font-size: 26rpx;
color: #666666;
line-height: 1.55;
}
.contactMeta {
margin-top: 14rpx;
padding-top: 14rpx;
border-top: 2rpx solid rgba(0, 0, 0, 0.06);
}
.contactMeta__row {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12rpx;
padding: 10rpx 0;
}
.contactMeta__label {
font-size: 24rpx;
color: #888888;
white-space: nowrap;
}
.goods-card__btnText--primary {
.contactMeta__value {
font-size: 26rpx;
font-weight: 800;
color: #111111;
text-align: right;
}
.contactActions {
margin-top: 14rpx;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12rpx;
}
/* Bottom CTA */
.bottomCta {
margin-top: 22rpx;
padding: 22rpx 18rpx;
border-radius: 24rpx;
background: linear-gradient(180deg, #0b1220 0%, #0b0f1a 100%);
box-shadow: 0 18rpx 36rpx rgba(0, 0, 0, 0.14);
}
.bottom__view{
background: linear-gradient(90deg, #24d34c 0%, #6df09a 100%) !important;
//background: linear-gradient(180deg, #2dd022 0%, #9be52b 100%);
}
.bottomCta__title {
display: block;
font-size: 32rpx;
font-weight: 900;
color: #ffffff;
line-height: 1.2;
}
.buy-btn{
height: 70px;
background: linear-gradient(to bottom, #1cd98a, #24ca94);
border-radius: 100px;
color: #ffffff;
display: flex;
align-items: center;
justify-content: space-around;
.cart-icon{
background: linear-gradient(to bottom, #bbe094, #4ee265);
border-radius: 100px 0 0 100px;
height: 70px;
}
.bottomCta__desc {
display: block;
margin-top: 10rpx;
font-size: 26rpx;
color: rgba(255, 255, 255, 0.75);
line-height: 1.55;
}
/* 轮播图容器样式,确保支持两种滑动操作 */
.banner-swiper-container {
touch-action: pan-y !important; /* 允许垂直滑动 */
.nut-swiper {
touch-action: pan-y !important; /* 允许垂直滑动 */
.nut-swiper-item {
touch-action: pan-x pan-y !important; /* 允许水平和垂直滑动 */
image {
pointer-events: auto; /* 确保图片点击事件正常 */
touch-action: manipulation; /* 优化触摸操作 */
}
}
}
/* 为Swiper容器添加特殊处理 */
.nut-swiper--horizontal {
touch-action: pan-y !important; /* 允许垂直滑动 */
}
}
/* 吸顶状态下的样式 */
.nutui-sticky--fixed {
.header-bg {
height: 100%;
}
}
/* 为Swiper添加更精确的触摸控制 */
.nut-swiper {
touch-action: pan-y !important;
.nut-swiper-inner {
touch-action: pan-x pan-y !important;
}
}
/* 自定义Swiper样式 */
.custom-swiper {
touch-action: pan-y !important;
.nut-swiper-item {
touch-action: pan-x pan-y !important;
}
}
/* 确保Swiper内部元素不会阻止页面滚动 */
.banner-swiper-container,
.custom-swiper,
.nut-swiper,
.nut-swiper-item {
-webkit-overflow-scrolling: touch; /* iOS平台启用硬件加速滚动 */
.bottomCta__actions {
margin-top: 16rpx;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12rpx;
}

View File

@@ -1,291 +1,469 @@
import Header from './Header'
import Banner from './Banner'
import Taro, { useShareAppMessage } from '@tarojs/taro'
import { View, Text, Image, ScrollView } from '@tarojs/components'
import { useEffect, useMemo, useState, type ReactNode } from 'react'
import { Cart, Coupon, Gift, Ticket } from '@nutui/icons-react-taro'
import { getShopInfo } from '@/api/layout'
import { checkAndHandleInviteRelation, hasPendingInvite } from '@/utils/invite'
import { pageShopGoods } from '@/api/shop/shopGoods'
import type { ShopGoods } from '@/api/shop/shopGoods/model'
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 { copyText } from '@/utils/common'
import './index.scss'
function Home() {
const [activeTab, setActiveTab] = useState('推荐')
const [goodsList, setGoodsList] = useState<ShopGoods[]>([])
const [ecoTab, setEcoTab] = useState<'template' | 'plugin'>('template')
const { config } = useConfig()
useShareAppMessage(() => {
// 获取当前用户ID用于生成邀请链接
const userId = Taro.getStorageSync('UserId');
const userId = Taro.getStorageSync('UserId')
return {
title: '🏠 首页 🏠',
path: userId ? `/pages/index/index?inviter=${userId}&source=share&t=${Date.now()}` : `/pages/index/index`,
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('首页分享成功');
console.log('首页分享成功')
Taro.showToast({
title: '分享成功',
icon: 'success',
duration: 2000
});
})
},
fail: function () {
console.log('首页分享失败');
console.log('首页分享失败')
Taro.showToast({
title: '分享失败',
icon: 'none',
duration: 2000
});
})
}
};
});
// const reloadMore = async () => {
// setPage(page + 1)
// }
const showAuthModal = () => {
Taro.showModal({
title: '授权提示',
content: '需要获取您的用户信息',
confirmText: '去授权',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
// 用户点击确认,打开授权设置页面
openSetting();
}
}
});
};
const openSetting = () => {
// Taro.openSetting调起客户端小程序设置界面返回用户设置的操作结果。设置界面只会出现小程序已经向用户请求过的权限。
Taro.openSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
// 用户授权成功,可以获取用户信息
reload();
} else {
// 用户拒绝授权,提示授权失败
Taro.showToast({
title: '授权失败',
icon: 'none'
});
}
}
});
};
// const onSticky = (item: IArguments) => {
// if(item){
// setStickyStatus(!stickyStatus)
// }
// }
// 处理Tabs粘性状态变化
// const handleTabsStickyChange = (isSticky: boolean) => {}
const reload = () => {
};
useEffect(() => {
// 获取站点信息
getShopInfo().then(() => {
})
pageShopGoods({}).then(res => {
setGoodsList(res?.list || [])
})
// 检查是否有待处理的邀请关系 - 异步处理,不阻塞页面加载
if (hasPendingInvite()) {
console.log('检测到待处理的邀请关系')
// 延迟处理,确保用户信息已加载,并设置超时保护
setTimeout(async () => {
try {
// 设置超时保护,避免长时间等待
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('邀请关系处理超时')), 8000)
);
const invitePromise = checkAndHandleInviteRelation();
const success = await Promise.race([invitePromise, timeoutPromise]);
if (success) {
console.log('首页邀请关系处理成功')
}
} catch (error) {
console.error('首页邀请关系处理失败:', error)
// 邀请关系处理失败不应该影响页面正常显示
// 可以选择清除邀请参数,避免重复尝试
const errorMessage = error instanceof Error ? error.message : String(error)
if (errorMessage?.includes('超时')) {
console.log('邀请关系处理超时,清除邀请参数')
// 可以选择清除邀请参数或稍后重试
}
}
}, 2000)
}
})
// Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
Taro.getSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
// 用户已经授权过,可以直接获取用户信息
console.log('用户已经授权过,可以直接获取用户信息')
reload();
} else {
// 用户未授权,需要弹出授权窗口
console.log('用户未授权,需要弹出授权窗口')
showAuthModal();
}
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 })
}
});
// 获取用户信息
Taro.getUserInfo({
success: (res) => {
const avatar = res.userInfo.avatarUrl;
console.log(avatar, 'avatarUrl')
}
});
}, []);
})
}
const tabs = useMemo(() => ['推荐', '桶装水', '优惠组合', '购机套餐', '饮水设备'], [])
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 shortcuts = useMemo<
Array<{ key: string; title: string; icon: ReactNode; onClick: () => void }>
>(
const openMaybeLink = (url?: string) => {
if (!url) return
// 小程序内无法直接打开外链,这里统一“复制链接”降低认知成本
const abs = toAbsoluteMaybe(url)
if (/^https?:\/\//i.test(abs)) {
copyText(abs)
return
}
Taro.showToast({ title: '请在 PC 端访问该入口', icon: 'none', duration: 1600 })
copyText(abs)
}
const copyConsultTemplate = () => {
const tel = config?.tel ? `\n联系电话${config.tel}` : ''
const text =
'【需求咨询】\n' +
'1) 想开通哪些产品:企业官网 / 电商 / 小程序 / 其他\n' +
'2) 是否需要模板/插件市场:是 / 否\n' +
'3) 是否需要“支付即开通”:是 / 否\n' +
'4) 交付方式SaaS / 私有化 / 混合\n' +
'5) 合规/部署要求:\n' +
'6) 期望上线时间:\n' +
tel
copyText(text)
}
const callPhone = () => {
const tel = (config?.tel || '').trim()
if (!tel) {
Taro.showToast({ title: '暂无联系电话', icon: 'none', duration: 1600 })
return
}
Taro.makePhoneCall({ phoneNumber: tel })
}
const products = useMemo(
() => [
{
key: 'ticket',
title: '我的水票',
icon: <Ticket size={30} />,
onClick: () => Taro.navigateTo({ url: '/user/gift/index' }),
title: '企业官网',
recommend: true,
desc: '品牌展示与获客转化支持多模板、多语言、SEO 与可视化配置。',
tags: ['模板', 'SEO', '多语言', '私有化'],
adminUrl: 'https://site.websoft.top',
},
{
key: 'order',
title: '立即订水',
icon: <Cart size={30} />,
onClick: () => Taro.navigateTo({ url: '/shop/goodsDetail/index?id=10072' }),
title: '小程序/公众号',
recommend: false,
desc: '多端渠道接入与统一管理,适配常见内容与电商场景。',
tags: ['多端', '渠道', '可扩展'],
adminUrl: 'https://mp.websoft.top',
},
{
key: 'invite',
title: '邀请有礼',
icon: <Gift size={30} />,
onClick: () => Taro.navigateTo({ url: '/dealer/qrcode/index' }),
title: '电商系统',
recommend: true,
desc: '商品/订单/支付/营销基础能力,插件化扩展,支持多端触达。',
tags: ['支付', '插件', '营销', '多租户'],
adminUrl: 'https://shop.websoft.top',
},
{
key: 'coupon',
title: '领券中心',
icon: <Coupon size={30} />,
onClick: () => Taro.navigateTo({ url: '/coupon/index' }),
title: '管理后台',
recommend: false,
desc: '多租户管理、角色权限、组织架构与可扩展菜单体系。',
tags: ['权限', '多租户', '审计'],
adminUrl: 'https://oa.websoft.top',
},
{
title: '开发者中心',
recommend: false,
desc: '应用开发与交付入口应用中心、源码仓库、Git 账号绑定、权限申请与教程文档。',
tags: ['应用', '源码', 'Git', '教程'],
adminUrl: '/developer',
},
{
title: '模板/插件市场',
recommend: false,
desc: '支持模板与插件购买、授权与更新,形成生态与增值体系。',
tags: ['市场', '授权', '更新', '变现'],
adminUrl: '/market',
},
],
[]
)
const visibleGoods = useMemo(() => {
// 先按效果图展示两列卡片,数据不够时也保持布局稳定
const list = goodsList || []
if (list.length <= 6) return list
return list.slice(0, 6)
}, [goodsList])
const capabilities = useMemo(
() => [
{
title: 'SaaS 多租户平台',
badge: '核心',
desc: '租户隔离、组织与权限体系、配置中心与审计能力,为多业务线统一底座。',
},
{
title: '私有化部署',
badge: '可选',
desc: '支持本地/专有云部署,提供部署文档、验收清单与升级策略,满足安全合规。',
},
{
title: '模板市场',
badge: '生态',
desc: '行业模板一键套用,默认配置与初始化脚本配套,交付更标准、上线更快。',
},
{
title: '插件市场',
badge: '扩展',
desc: '支付、会员、营销、工单等能力按需加购;支持授权、更新与版本管理。',
},
{
title: '自动开通链路',
badge: '交付',
desc: '选品支付后自动创建租户、初始化模块/菜单/基础数据,并交付访问入口。',
},
{
title: '模块化与可扩展',
badge: '开发',
desc: '支持按模块组合产品能力,插件化扩展点让二开与生态合作更高效。',
},
],
[]
)
return (
<>
{/* Header区域 - 现在由Header组件内部处理吸顶逻辑 */}
<Header />
<View className="home-page">
{/* 顶部活动主视觉:使用 Banner 组件 */}
<Banner />
{/* 首屏:价值主张 + 关键动作 */}
<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="ticket-card">
<View className="ticket-card__head">
<Text className="ticket-card__title"></Text>
<Text className="ticket-card__count">
<Text className="ticket-card__countNum">0</Text>
</Text>
</View>
<View className="hero__title">
<Text className="hero__titleText">SaaS + + /</Text>
</View>
<View className="ticket-card__body">
<View className="shortcut-grid">
{shortcuts.map((item) => (
<View
key={item.key}
className="shortcut-grid__item"
onClick={item.onClick}
>
<View className="shortcut-grid__icon">{item.icon}</View>
<Text className="shortcut-grid__text">{item.title}</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>
{/* 分类Tabs */}
<ScrollView className="home-tabs" scrollX enableFlex>
<View className="home-tabs__inner">
{tabs.map((tab) => {
const active = tab === activeTab
return (
<View
key={tab}
className={`home-tabs__item ${active ? 'home-tabs__item--active' : ''}`}
onClick={() => setActiveTab(tab)}
>
<Text className="home-tabs__itemText">{tab}</Text>
</View>
)
})}
</View>
</ScrollView>
{/* 商品列表 */}
<View className="goods-grid">
{visibleGoods.map((item) => (
<View key={item.goodsId} className="goods-card">
<View className="goods-card__imgWrap">
<Image
className="goods-card__img"
src={item.image || ''}
mode="aspectFill"
lazyLoad={false}
onClick={() =>
Taro.navigateTo({ url: `/shop/goodsDetail/index?id=${item.goodsId}` })
}
/>
</View>
<View className="goods-card__body">
<Text className="goods-card__title">{item.name}</Text>
<View className="goods-card__meta">
<Text className="goods-card__sold">:{item.sales || 0}</Text>
<View className="goods-card__price">
<Text className="goods-card__priceUnit"></Text>
<Text className="goods-card__priceValue">{item.price}</Text>
</View>
</View>
<View className="goods-card__actions">
<View
className="goods-card__btn goods-card__btn--primary"
onClick={() =>
Taro.navigateTo({ url: `/shop/goodsDetail/index?id=${item.goodsId}` })
}
>
<Text className="goods-card__btnText goods-card__btnText--primary"></Text>
</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="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">
<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 })
openMaybeLink('/market')
}}
>
/
</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="default" block onClick={copyConsultTemplate}>
</Button>
<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>
</>
)

View File

@@ -75,7 +75,7 @@ const IsDealer = () => {
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Reward className={'text-orange-100 '} size={16}/>
<Text style={{fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>{config?.vipText || '门店入驻'}</Text>
<Text style={{fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>{config?.vipText || '开发者中心'}</Text>
<Text className={'text-white opacity-80 pl-3'}>{config?.vipComments || ''}</Text>
</View>
}

View File

@@ -61,8 +61,8 @@ const UserCell = () => {
</Grid.Item>
)}
{hasRole('rider') && (
<Grid.Item text="配送中心" onClick={() => navTo('/rider/index', true)}>
{hasRole('developer') && (
<Grid.Item text="开发者中心" onClick={() => navTo('/rider/index', true)}>
<View className="text-center">
<View className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center mx-auto mb-2">
<Jdl color="#3b82f6" size="20"/>