feat(add): 新增多页面新增和编辑表单功能

- 添加编辑和新增收货地址页面,支持表单数据加载和提交
- 新增应用密钥凭证、新增应用操作动态、新增应用成员、新增应用版本页面配置
- 实现文章新增及编辑页面,包含图片上传及多种文章属性配置
- 增加注册会员页面,支持头像上传、手机号获取和邀请人关系处理
- 引入统一表单提交成功和失败处理,支持编辑模式数据回显
- 配置统一eslint和editorconfig规则,增强代码规范和编辑体验
- 新增.gitignore规则,屏蔽无关文件和目录,优化版本管理
This commit is contained in:
2026-04-11 12:22:29 +08:00
commit 07f5c92f4b
627 changed files with 85725 additions and 0 deletions

472
src/pages/index/index.tsx Normal file
View File

@@ -0,0 +1,472 @@
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 [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 (/^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(
() => [
{
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: '应用开发与交付入口应用中心、源码仓库、Git 账号绑定、权限申请与教程文档。',
tags: ['应用', '源码', 'Git', '教程'],
adminUrl: '/developer',
},
{
title: '模板/插件市场',
recommend: false,
desc: '支持模板与插件购买、授权与更新,形成生态与增值体系。',
tags: ['市场', '授权', '更新', '变现'],
adminUrl: '/market',
},
],
[]
)
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="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>
</>
)
}
export default Home