refactor(src): 移除客户详情相关代码

- 删除了客户详情页面的配置、样式和组件文件
- 更新了 app.config.ts,移除了与客户详情相关的页面引用
- 优化了首页 Grid 组件的导航逻辑
This commit is contained in:
2025-09-03 17:55:49 +08:00
parent 1368155736
commit ece93d4fcc
28 changed files with 34 additions and 3490 deletions

View File

@@ -1,5 +1,5 @@
import request from '@/utils/request'; import request from '@/utils/request';
import type {ApiResult, PageResult} from '@/api/index'; import type {ApiResult, PageResult} from '@/api';
import type {User, UserParam} from './model'; import type {User, UserParam} from './model';
import {SERVER_API_URL} from "@/utils/server"; import {SERVER_API_URL} from "@/utils/server";
@@ -154,7 +154,7 @@ export async function updateUserStatus(userId?: number, status?: number) {
/** /**
* 修改推荐状态 * 修改推荐状态
*/ */
export async function updateUserRecommend(form) { export async function updateUserRecommend(form:any) {
const res = await request.put<ApiResult<unknown>>( const res = await request.put<ApiResult<unknown>>(
'/system/user/recommend', '/system/user/recommend',
form form

View File

@@ -3,12 +3,7 @@ export default defineAppConfig({
'pages/index/index', 'pages/index/index',
'pages/cart/cart', 'pages/cart/cart',
'pages/find/find', 'pages/find/find',
'pages/user/user', 'pages/user/user'
'pages/customer/list',
'pages/customer/sign',
'pages/customer/detail',
'pages/customer/trading',
'pages/customer/invite'
], ],
"subpackages": [ "subpackages": [
{ {

View File

@@ -1,22 +0,0 @@
# 微信二维码图片说明
请将以下微信二维码图片放置在此目录中:
## 需要的图片文件
1. `wechat-service-qr.png` - 客服微信二维码
2. `wechat-tech-qr.png` - 技术支持微信二维码
## 图片要求
- 格式PNG 或 JPG
- 尺寸:建议 200x200 像素或更高分辨率
- 背景:建议白色背景,确保二维码清晰可见
## 使用说明
这些图片将在 `/dealer/wechat` 页面中显示,用户可以长按保存并扫码添加微信好友。
## 自定义
如需修改图片路径或添加更多二维码,请编辑 `src/dealer/wechat/index.tsx` 文件中的 `qrCodeData` 数组。

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

View File

@@ -180,7 +180,9 @@ const DealerOrders: React.FC = () => {
)} )}
</> </>
) : ( ) : (
<Empty description="暂无收益"/> <Empty description="暂无收益" style={{
backgroundColor: 'transparent'
}}/>
)} )}
</View> </View>
</ScrollView> </ScrollView>

View File

@@ -1,3 +1,3 @@
export default definePageConfig({ export default definePageConfig({
navigationBarTitleText: '我的团队' navigationBarTitleText: '邀请推广'
}) })

View File

@@ -1,12 +1,14 @@
import React, {useState, useEffect, useCallback} from 'react' import React, {useState, useEffect, useCallback} from 'react'
import {View, Text} from '@tarojs/components' import {View, Text} from '@tarojs/components'
import {Space, Avatar, Loading} from '@nutui/nutui-react-taro' import {Space,Empty, Avatar} from '@nutui/nutui-react-taro'
import {User} from '@nutui/icons-react-taro' import {User} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import {useDealerUser} from '@/hooks/useDealerUser' import {useDealerUser} from '@/hooks/useDealerUser'
import {listShopDealerReferee} from '@/api/shop/shopDealerReferee' import {listShopDealerReferee} from '@/api/shop/shopDealerReferee'
import {pageShopDealerOrder} from '@/api/shop/shopDealerOrder' import {pageShopDealerOrder} from '@/api/shop/shopDealerOrder'
import type {ShopDealerReferee} from '@/api/shop/shopDealerReferee/model' import type {ShopDealerReferee} from '@/api/shop/shopDealerReferee/model'
import FixedButton from "@/components/FixedButton";
import navTo from "@/utils/common";
interface TeamMemberWithStats extends ShopDealerReferee { interface TeamMemberWithStats extends ShopDealerReferee {
name?: string name?: string
@@ -176,16 +178,28 @@ const DealerTeam: React.FC = () => {
if (!dealerUser) { if (!dealerUser) {
return ( return (
<View className="bg-gray-50 min-h-screen flex items-center justify-center"> <Space className="bg-gray-50 flex items-center justify-center">
<Loading/> <Empty description="您还不是经销商" style={{
<Text className="text-gray-500 mt-2">...</Text> backgroundColor: 'transparent'
</View> }} actions={[{ text: '去申请开通', onClick: () => navTo(`/dealer/apply/add`,true)}]}
/>
</Space>
) )
} }
return ( return (
<View className="min-h-screen"> <View className="min-h-screen">
{renderOverview()} {teamMembers.length > 0 ? (
renderOverview()
) : (
<View className="flex items-center justify-center mt-20">
<Empty description="暂无数据" style={{
backgroundColor: 'transparent'
}}/>
</View>
)}
<FixedButton text={'立即邀请'} onClick={() => navTo(`/dealer/qrcode/index`, true)}/>
</View> </View>
) )
} }

View File

@@ -5,13 +5,6 @@ import './index.scss'
import {listCmsWebsiteField} from "@/api/cms/cmsWebsiteField"; import {listCmsWebsiteField} from "@/api/cms/cmsWebsiteField";
import {CmsWebsiteField} from "@/api/cms/cmsWebsiteField/model"; import {CmsWebsiteField} from "@/api/cms/cmsWebsiteField/model";
interface QrCodeData {
id: string
title: string
description: string
qrCode: string
wechatId: string
}
const WechatService = () => { const WechatService = () => {
const [activeTab, setActiveTab] = useState('0') const [activeTab, setActiveTab] = useState('0')
const [codes, setCodes] = useState<CmsWebsiteField[]>([]) const [codes, setCodes] = useState<CmsWebsiteField[]>([])
@@ -26,7 +19,6 @@ const WechatService = () => {
className="qr-code-image" className="qr-code-image"
mode="aspectFit" mode="aspectFit"
/> />
<Text className="wechat-id">{data.comments}</Text>
</View> </View>
<View className="qr-tips"> <View className="qr-tips">
@@ -41,8 +33,8 @@ const WechatService = () => {
) )
useEffect(() => { useEffect(() => {
listCmsWebsiteField({ name: 'kefu'}).then(data => { listCmsWebsiteField({name: 'kefu'}).then(data => {
if(data){ if (data) {
setCodes(data) setCodes(data)
} }
}) })

View File

@@ -42,7 +42,6 @@ export const useDealerUser = (): UseDealerUserReturn => {
// 查询当前用户的经销商信息 // 查询当前用户的经销商信息
const dealer = await getShopDealerUser(userId) const dealer = await getShopDealerUser(userId)
if (dealer) { if (dealer) {
setDealerUser(dealer) setDealerUser(dealer)
} else { } else {
@@ -66,7 +65,7 @@ export const useDealerUser = (): UseDealerUserReturn => {
useEffect(() => { useEffect(() => {
if (userId) { if (userId) {
console.log('🔍 调用 fetchDealerData') console.log('🔍 调用 fetchDealerData')
fetchDealerData() fetchDealerData().then()
} else { } else {
console.log('🔍 用户ID不存在不调用 fetchDealerData') console.log('🔍 用户ID不存在不调用 fetchDealerData')
} }

View File

@@ -1,6 +0,0 @@
export default definePageConfig({
navigationBarTitleText: '客户详情',
navigationBarTextStyle: 'white',
navigationStyle: 'custom',
backgroundColor: '#f5f5f5'
})

View File

@@ -1,318 +0,0 @@
.customer-detail-page {
min-height: 100vh;
background: #f5f5f5;
position: relative;
padding-bottom: 100px;
.header-bg {
background: linear-gradient(to bottom, #03605c, #18ae4f);
width: 100%;
top: 0;
position: absolute;
z-index: 0;
}
.nav-actions {
display: flex;
gap: 16px;
align-items: center;
}
.loading-container,
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 50vh;
color: #999;
.nut-button {
margin-top: 16px;
}
}
.detail-container {
position: relative;
z-index: 10;
padding: 20px 16px;
margin-top: 20px;
.customer-card {
background: #ffffff;
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.customer-header {
margin-bottom: 16px;
.company-info {
display: flex;
justify-content: space-between;
align-items: center;
.company-name {
font-size: 18px;
font-weight: bold;
color: #333;
flex: 1;
}
.nut-tag {
margin-left: 12px;
}
}
}
.contact-info {
.contact-item {
display: flex;
align-items: center;
margin-bottom: 12px;
padding: 8px 0;
&:last-child {
margin-bottom: 0;
}
.nut-icon {
margin-right: 8px;
flex-shrink: 0;
}
.contact-text {
font-size: 14px;
color: #333;
line-height: 1.4;
&.phone {
color: #52c41a;
cursor: pointer;
}
}
}
}
}
.info-section {
background: #ffffff;
border-radius: 12px;
margin-bottom: 16px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 16px 8px;
border-bottom: 1px solid #f0f0f0;
.section-title {
font-size: 16px;
font-weight: bold;
color: #333;
}
.nut-button {
height: 28px;
font-size: 12px;
padding: 0 12px;
}
}
.info-content {
.nut-cell {
padding: 12px 16px;
border-bottom: 1px solid #f8f8f8;
&:last-child {
border-bottom: none;
}
.nut-cell__title {
font-size: 14px;
color: #666;
font-weight: normal;
}
.nut-cell__value {
font-size: 14px;
color: #333;
text-align: right;
}
}
}
.contact-history {
padding: 0 16px 16px;
.history-item {
padding: 12px 0;
border-bottom: 1px solid #f8f8f8;
&:last-child {
border-bottom: none;
}
.history-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
.history-type {
font-size: 14px;
font-weight: 500;
color: #333;
}
.history-date {
font-size: 12px;
color: #999;
}
}
.history-content {
font-size: 14px;
color: #666;
line-height: 1.4;
margin-bottom: 4px;
}
.history-operator {
font-size: 12px;
color: #999;
}
}
}
}
}
.fixed-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
background: #ffffff;
border-top: 1px solid #f0f0f0;
z-index: 100;
.action-buttons {
display: flex;
gap: 12px;
.action-btn {
flex: 1;
height: 44px;
border-radius: 22px;
font-size: 14px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
&.call-btn {
background: #52c41a;
color: #ffffff;
border: none;
}
&.renew-btn {
background: #1890ff;
color: #ffffff;
border: none;
}
}
}
}
}
// 适配安全区域
@supports (bottom: env(safe-area-inset-bottom)) {
.customer-detail-page .fixed-bottom {
padding-bottom: calc(16px + env(safe-area-inset-bottom));
}
}
// 标签样式优化
.nut-tag {
&.nut-tag--plain {
border-width: 1px;
font-size: 12px;
padding: 2px 8px;
}
}
// 响应式适配
@media (max-width: 375px) {
.customer-detail-page {
.detail-container {
padding: 16px 12px;
.customer-card {
padding: 16px;
.customer-header {
.company-info {
.company-name {
font-size: 16px;
}
}
}
.contact-info {
.contact-item {
.contact-text {
font-size: 13px;
}
}
}
}
.info-section {
.section-header {
padding: 14px 12px 6px;
.section-title {
font-size: 15px;
}
}
.info-content {
.nut-cell {
padding: 10px 12px;
.nut-cell__title,
.nut-cell__value {
font-size: 13px;
}
}
}
.contact-history {
padding: 0 12px 12px;
.history-item {
.history-content {
font-size: 13px;
}
}
}
}
}
.fixed-bottom {
.action-buttons {
.action-btn {
height: 40px;
font-size: 13px;
}
}
}
}
}

View File

@@ -1,346 +0,0 @@
import { useEffect, useState } from "react";
// import Taro, { useRouter } from '@tarojs/taro';
import Taro from '@tarojs/taro'
import { View, Text } from '@tarojs/components';
import {
NavBar,
Button,
Cell,
Tag,
Toast
} from '@nutui/nutui-react-taro';
import {
Phone,
Location,
Calendar,
User,
Edit,
Share
} from '@nutui/icons-react-taro';
import './detail.scss';
// 已签约客户详情数据类型
interface CustomerDetail {
id: string;
companyName: string;
contactPerson: string;
phone: string;
address: string;
signDate: string;
contractInfo: {
contractType: string;
contractAmount: string;
startDate: string;
endDate: string;
paymentMethod: string;
paymentCycle: string;
status: 'active' | 'expired' | 'terminated';
};
businessInfo: {
industry: string;
scale: string;
registeredCapital: string;
businessScope: string;
};
contactHistory: Array<{
id: string;
date: string;
type: string;
content: string;
operator: string;
}>;
}
const CustomerDetail = () => {
// const router = useRouter();
const [statusBarHeight, setStatusBarHeight] = useState<number>(0);
const [customerDetail, setCustomerDetail] = useState<CustomerDetail | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [showToast, setShowToast] = useState(false);
const [toastMsg, setToastMsg] = useState('');
// 模拟客户详情数据
const mockCustomerDetail: CustomerDetail = {
id: '1',
companyName: '广州雅虎信息科技公司',
contactPerson: '张经理',
phone: '13882223433',
address: '广西南宁市良庆区五象大道401号五象新城1号楼1226室',
signDate: '2025-08-15 10:23:33',
contractInfo: {
contractType: '服务合同',
contractAmount: '500,000',
startDate: '2025-08-15',
endDate: '2026-08-14',
paymentMethod: '分期付款',
paymentCycle: '按季度付款',
status: 'active'
},
businessInfo: {
industry: '信息技术服务',
scale: '中型企业',
registeredCapital: '1000万元',
businessScope: '软件开发、技术咨询、系统集成'
},
contactHistory: [
{
id: '1',
date: '2025-08-20',
type: '电话沟通',
content: '讨论项目进度,客户反馈良好',
operator: '李销售'
},
{
id: '2',
date: '2025-08-18',
type: '现场拜访',
content: '实地考察客户需求,确认技术方案',
operator: '王工程师'
},
{
id: '3',
date: '2025-08-15',
type: '合同签署',
content: '正式签署服务合同,项目启动',
operator: '张经理'
}
]
};
const showToastMsg = (msg: string) => {
setToastMsg(msg);
setShowToast(true);
setTimeout(() => setShowToast(false), 2000);
};
const getStatusColor = (status: string) => {
switch (status) {
case 'active':
return '#52c41a';
case 'expired':
return '#ff6b35';
case 'terminated':
return '#ff4d4f';
default:
return '#999';
}
};
const getStatusText = (status: string) => {
switch (status) {
case 'active':
return '履行中';
case 'expired':
return '已到期';
case 'terminated':
return '已终止';
default:
return '未知';
}
};
const handleCall = () => {
if (customerDetail?.phone) {
Taro.makePhoneCall({
phoneNumber: customerDetail.phone
});
}
};
const handleEdit = () => {
showToastMsg('编辑功能开发中');
};
const handleShare = () => {
showToastMsg('分享功能开发中');
};
const handleAddContact = () => {
showToastMsg('添加联系记录功能开发中');
};
const handleViewContract = () => {
showToastMsg('查看合同详情功能开发中');
};
const handleRenewContract = () => {
showToastMsg('续约功能开发中');
};
const loadCustomerDetail = async () => {
setLoading(true);
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 800));
setCustomerDetail(mockCustomerDetail);
} catch (error) {
showToastMsg('加载失败,请重试');
} finally {
setLoading(false);
}
};
useEffect(() => {
Taro.getSystemInfo({
success: (res) => {
setStatusBarHeight(Number(res.statusBarHeight));
},
});
loadCustomerDetail();
}, []);
if (loading) {
return (
<View className="customer-detail-page">
<View className="loading-container">
<Text>...</Text>
</View>
</View>
);
}
if (!customerDetail) {
return (
<View className="customer-detail-page">
<View className="error-container">
<Text></Text>
<Button onClick={loadCustomerDetail}></Button>
</View>
</View>
);
}
return (
<View className="customer-detail-page">
{/* 头部背景 */}
<View className="header-bg" style={{ height: '180px' }} />
{/* 导航栏 */}
<NavBar
style={{
marginTop: `${statusBarHeight}px`,
backgroundColor: 'transparent',
color: '#ffffff'
}}
onBackClick={() => Taro.navigateBack()}
right={
<View className="nav-actions">
<Edit size={20} color="#ffffff" onClick={handleEdit} />
<Share size={20} color="#ffffff" onClick={handleShare} />
</View>
}
>
<Text style={{ color: '#ffffff', fontSize: '18px', fontWeight: 'bold' }}>
</Text>
</NavBar>
{/* 客户基本信息 */}
<View className="detail-container">
<View className="customer-card">
<View className="customer-header">
<View className="company-info">
<Text className="company-name">{customerDetail.companyName}</Text>
<Tag
color={getStatusColor(customerDetail.contractInfo.status)}
plain
>
{getStatusText(customerDetail.contractInfo.status)}
</Tag>
</View>
</View>
<View className="contact-info">
<View className="contact-item">
<User size={16} color="#666" />
<Text className="contact-text">{customerDetail.contactPerson}</Text>
</View>
<View className="contact-item" onClick={handleCall}>
<Phone size={16} color="#52c41a" />
<Text className="contact-text phone">{customerDetail.phone}</Text>
</View>
<View className="contact-item">
<Location size={16} color="#666" />
<Text className="contact-text">{customerDetail.address}</Text>
</View>
<View className="contact-item">
<Calendar size={16} color="#666" />
<Text className="contact-text">{customerDetail.signDate}</Text>
</View>
</View>
</View>
{/* 合同信息 */}
<View className="info-section">
<View className="section-header">
<Text className="section-title"></Text>
<Button size="small" onClick={handleViewContract}></Button>
</View>
<View className="info-content">
<Cell title="合同类型" extra={customerDetail.contractInfo.contractType} />
<Cell title="合同金额" extra={`¥${customerDetail.contractInfo.contractAmount}`} />
<Cell title="合同期限" extra={`${customerDetail.contractInfo.startDate}${customerDetail.contractInfo.endDate}`} />
<Cell title="付款方式" extra={customerDetail.contractInfo.paymentMethod} />
<Cell title="付款周期" extra={customerDetail.contractInfo.paymentCycle} />
</View>
</View>
{/* 企业信息 */}
<View className="info-section">
<View className="section-header">
<Text className="section-title"></Text>
</View>
<View className="info-content">
<Cell title="所属行业" extra={customerDetail.businessInfo.industry} />
<Cell title="企业规模" extra={customerDetail.businessInfo.scale} />
<Cell title="注册资本" extra={customerDetail.businessInfo.registeredCapital} />
<Cell title="经营范围" extra={customerDetail.businessInfo.businessScope} />
</View>
</View>
{/* 联系记录 */}
<View className="info-section">
<View className="section-header">
<Text className="section-title"></Text>
<Button size="small" onClick={handleAddContact}></Button>
</View>
<View className="contact-history">
{customerDetail.contactHistory.map((record) => (
<View key={record.id} className="history-item">
<View className="history-header">
<Text className="history-type">{record.type}</Text>
<Text className="history-date">{record.date}</Text>
</View>
<Text className="history-content">{record.content}</Text>
<Text className="history-operator">{record.operator}</Text>
</View>
))}
</View>
</View>
</View>
{/* 底部操作按钮 */}
<View className="fixed-bottom">
<View className="action-buttons">
<Button className="action-btn call-btn" onClick={handleCall}>
<Phone size={16} />
</Button>
<Button className="action-btn renew-btn" onClick={handleRenewContract}>
</Button>
</View>
</View>
{/* Toast提示 */}
<Toast
visible={showToast}
content={toastMsg}
duration={2000}
/>
</View>
);
};
export default CustomerDetail;

View File

@@ -1,3 +0,0 @@
export default definePageConfig({
navigationBarTitleText: '邀请好友'
})

View File

@@ -1,429 +0,0 @@
.customer-invite-page {
min-height: 100vh;
background: #f5f5f5;
position: relative;
.header-bg {
background: linear-gradient(to bottom, #03605c, #18ae4f);
width: 100%;
top: 0;
position: absolute;
z-index: 0;
}
.tabs-container {
position: relative;
z-index: 10;
.nut-tabs {
background: transparent;
.nut-tabs__titles {
background: transparent;
border: none;
.nut-tabs__titles-item {
color: rgba(255, 255, 255, 0.8);
padding: 12px 16px;
&.nut-tabs__titles-item--active {
color: #ffffff;
font-weight: bold;
}
}
}
.nut-tabs__line {
background: #ffffff;
height: 3px;
border-radius: 2px;
}
}
}
.content-container {
position: relative;
z-index: 10;
padding: 20px 16px;
margin-top: 20px;
}
// 二维码标签页样式
.qrcode-container {
.qr-card {
background: #ffffff;
border-radius: 16px;
padding: 24px;
text-align: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
margin-bottom: 20px;
.qr-header {
margin-bottom: 24px;
.qr-title {
font-weight: bold;
color: #333;
display: block;
margin-bottom: 8px;
}
.qr-subtitle {
color: #666;
}
}
.qr-code {
margin-bottom: 24px;
.qr-placeholder {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 280px;
height: 280px;
margin: 0 auto;
border: 2px dashed #ddd;
border-radius: 12px;
background: #fafafa;
.qr-text {
margin-top: 8px;
color: #999;
}
}
.qr-loading {
width: 160px;
height: 160px;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 12px;
color: #999;
}
}
.qr-actions {
display: flex;
justify-content: center;
gap: 12px;
.action-btn {
display: flex;
justify-content: center;
align-items: center;
gap: 4px;
padding: 8px 16px;
border-radius: 20px;
background: #f0f0f0;
color: #666;
border: none;
&:active {
background: #e0e0e0;
}
}
}
}
.invite-tips {
background: #ffffff;
border-radius: 12px;
padding: 16px;
margin-bottom: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.tips-header {
display: flex;
align-items: center;
margin-bottom: 12px;
.tips-title {
font-weight: bold;
color: #333;
margin-left: 8px;
}
}
.tips-content {
.tip-item {
display: block;
color: #666;
line-height: 1.6;
margin-bottom: 4px;
&:last-child {
margin-bottom: 0;
}
}
}
}
.manual-invite {
.manual-btn {
width: 100%;
background: #52c41a;
color: #ffffff;
font-weight: bold;
border: none;
box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3);
}
}
}
// 邀请记录标签页样式
.records-container {
.record-item {
background: #ffffff;
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.record-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
.user-info {
display: flex;
align-items: center;
.user-name {
font-weight: bold;
color: #333;
margin-left: 8px;
}
}
}
.record-details {
.detail-item {
display: flex;
align-items: center;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.detail-text {
color: #666;
margin-left: 8px;
}
}
}
}
.empty-records {
text-align: center;
padding: 60px 20px;
color: #999;
}
}
// 邀请统计标签页样式
.stats-container {
.stats-overview {
display: flex;
background: #ffffff;
border-radius: 12px;
padding: 20px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.stat-item {
flex: 1;
text-align: center;
.stat-number {
display: block;
font-weight: bold;
color: #333;
margin-bottom: 4px;
}
.stat-label {
color: #999;
}
}
}
.reward-summary {
margin-bottom: 16px;
.reward-card {
background: linear-gradient(135deg, #52c41a, #73d13d);
border-radius: 12px;
padding: 20px;
text-align: center;
color: #ffffff;
.reward-title {
opacity: 0.9;
margin-bottom: 8px;
}
.reward-amount {
font-weight: bold;
margin-bottom: 16px;
}
.withdraw-btn {
background: rgba(255, 255, 255, 0.2);
color: #ffffff;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 20px;
padding: 8px 24px;
}
}
}
.stats-chart {
background: #ffffff;
border-radius: 12px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.chart-title {
font-weight: bold;
color: #333;
margin-bottom: 16px;
}
.chart-placeholder {
height: 200px;
display: flex;
align-items: center;
justify-content: center;
background: #f5f5f5;
border-radius: 8px;
color: #999;
}
}
}
// 邀请表单弹窗样式
.invite-form {
padding: 20px;
background: #ffffff;
border-radius: 16px 16px 0 0;
.form-header {
text-align: center;
margin-bottom: 20px;
.form-title {
font-weight: bold;
color: #333;
}
}
.nut-form-item {
margin-bottom: 16px;
.nut-form-item__label {
color: #333;
font-weight: 500;
}
.nut-input {
&::placeholder {
color: #999;
}
}
}
.form-actions {
display: flex;
gap: 12px;
margin-top: 24px;
.cancel-btn,
.submit-btn {
flex: 1;
height: 44px;
border-radius: 22px;
font-weight: 500;
}
.cancel-btn {
background: #f5f5f5;
color: #666;
border: none;
}
.submit-btn {
background: #52c41a;
color: #ffffff;
border: none;
}
}
}
}
// 标签样式优化
.nut-tag {
&.nut-tag--plain {
border-width: 1px;
padding: 2px 6px;
&.nut-tag--small {
padding: 1px 4px;
}
}
}
// 响应式适配
@media (max-width: 375px) {
.customer-invite-page {
.content-container {
padding: 16px 12px;
}
.qrcode-container {
.qr-card {
padding: 20px;
.qr-code {
.qr-placeholder {
width: 140px;
height: 140px;
}
}
.qr-actions {
.action-btn {
padding: 6px 12px;
}
}
}
}
.stats-container {
.stats-overview {
padding: 16px;
.stat-item {
.stat-number {
}
}
}
.reward-summary {
.reward-card {
padding: 16px;
.reward-amount {
}
}
}
}
}
}

View File

@@ -1,459 +0,0 @@
import { useEffect, useState } from "react";
import Taro from '@tarojs/taro';
import { View, Text } from '@tarojs/components';
import {
Space,
Tabs,
Button,
Tag,
Toast,
Popup,
Input,
Form
} from '@nutui/nutui-react-taro';
import {
QrCode,
Share,
Copy,
Download,
User,
Phone,
Calendar,
Gift
} from '@nutui/icons-react-taro';
import './invite.scss';
// 邀请记录数据类型
interface InviteRecord {
id: string;
inviteeName: string;
inviteePhone: string;
inviteDate: string;
status: 'pending' | 'registered' | 'signed';
reward: string;
}
// 邀请统计数据类型
interface InviteStats {
totalInvites: number;
registeredCount: number;
signedCount: number;
totalReward: string;
}
const CustomerInvite = () => {
const [statusBarHeight, setStatusBarHeight] = useState<number>(0);
const [activeTab, setActiveTab] = useState<string>('qrcode');
const [qrCodeUrl, setQrCodeUrl] = useState<string>('');
const [inviteRecords, setInviteRecords] = useState<InviteRecord[]>([]);
const [inviteStats, setInviteStats] = useState<InviteStats | null>(null);
const [showToast, setShowToast] = useState(false);
const [toastMsg, setToastMsg] = useState('');
const [showInviteForm, setShowInviteForm] = useState(false);
const [inviteFormData, setInviteFormData] = useState({
name: '',
phone: ''
});
// 模拟邀请记录数据
const mockInviteRecords: InviteRecord[] = [
{
id: '1',
inviteeName: '张三',
inviteePhone: '138****1234',
inviteDate: '2025-08-20 14:30:00',
status: 'signed',
reward: '500'
},
{
id: '2',
inviteeName: '李四',
inviteePhone: '139****5678',
inviteDate: '2025-08-19 10:15:00',
status: 'registered',
reward: '200'
},
{
id: '3',
inviteeName: '王五',
inviteePhone: '136****9012',
inviteDate: '2025-08-18 16:45:00',
status: 'pending',
reward: '0'
}
];
// 模拟邀请统计数据
const mockInviteStats: InviteStats = {
totalInvites: 15,
registeredCount: 8,
signedCount: 3,
totalReward: '2,800'
};
const tabList = [
{ title: '邀请二维码', value: 'qrcode' },
{ title: '邀请记录', value: 'records' },
{ title: '邀请统计', value: 'stats' }
];
const showToastMsg = (msg: string) => {
setToastMsg(msg);
setShowToast(true);
setTimeout(() => setShowToast(false), 2000);
};
const getStatusText = (status: string) => {
switch (status) {
case 'pending':
return '待注册';
case 'registered':
return '已注册';
case 'signed':
return '已签约';
default:
return '';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'pending':
return '#ff6b35';
case 'registered':
return '#1890ff';
case 'signed':
return '#52c41a';
default:
return '#999';
}
};
// 生成二维码
const generateQRCode = () => {
// 模拟生成二维码URL
const inviteCode = 'INV' + Date.now().toString().slice(-6);
const qrUrl = `https://example.com/invite?code=${inviteCode}`;
setQrCodeUrl(qrUrl);
// 实际项目中这里应该调用二维码生成库
showToastMsg('二维码已生成');
};
// 复制邀请链接
const copyInviteLink = () => {
const inviteLink = `https://example.com/invite?code=INV123456`;
Taro.setClipboardData({
data: inviteLink,
success: () => {
showToastMsg('邀请链接已复制到剪贴板');
},
fail: () => {
showToastMsg('复制失败,请重试');
}
});
};
// 分享邀请
const shareInvite = () => {
Taro.showShareMenu({
withShareTicket: true,
success: () => {
showToastMsg('分享成功');
},
fail: () => {
showToastMsg('分享失败');
}
});
};
// 下载二维码
const downloadQRCode = () => {
showToastMsg('下载功能开发中');
};
// 手动邀请
const handleManualInvite = () => {
setShowInviteForm(true);
};
// 提交邀请表单
const submitInviteForm = () => {
if (!inviteFormData.name || !inviteFormData.phone) {
showToastMsg('请填写完整信息');
return;
}
// 模拟发送邀请
showToastMsg('邀请已发送');
setShowInviteForm(false);
setInviteFormData({ name: '', phone: '' });
// 刷新邀请记录
loadInviteData().then();
};
// 加载邀请数据
const loadInviteData = async () => {
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 500));
setInviteRecords(mockInviteRecords);
setInviteStats(mockInviteStats);
} catch (error) {
showToastMsg('加载失败,请重试');
}
};
useEffect(() => {
Taro.getSystemInfo({
success: (res) => {
setStatusBarHeight(Number(res.statusBarHeight))
},
});
generateQRCode();
loadInviteData().then();
}, []);
const renderQRCodeTab = () => (
<View className="qrcode-container">
<View className="qr-card">
<View className="qr-header">
<View className="qr-title pt-2"></View>
<View className="qr-subtitle py-2"></View>
</View>
<View className="qr-code">
{qrCodeUrl ? (
<View className="qr-placeholder">
<QrCode size={80} color="#333" />
<Text className="qr-text"></Text>
</View>
) : (
<View className="qr-loading">
<Text>...</Text>
</View>
)}
</View>
<Space className="qr-actions mt-4">
<Button
className="action-btn"
size="small"
onClick={copyInviteLink}
>
<View className={'flex items-center justify-center px-1'}>
<Copy size={14} />
<Text className="ml-1"></Text>
</View>
</Button>
<Button
className="action-btn"
size="small"
onClick={shareInvite}
>
<View className={'flex items-center justify-center px-1'}>
<Share size={14} />
<Text className="ml-1"></Text>
</View>
</Button>
<Button
size="small"
onClick={downloadQRCode}
>
<View className={'flex items-center justify-center px-1'}>
<Download size={14} />
<Text className="ml-1"></Text>
</View>
</Button>
</Space>
</View>
<View className="invite-tips">
<View className="tips-header">
<Gift size={16} color="#52c41a" />
<Text className="tips-title"></Text>
</View>
<View className="tips-content">
<Text className="tip-item"> 200</Text>
<Text className="tip-item"> 300</Text>
<Text className="tip-item"> 24</Text>
</View>
</View>
<View className="manual-invite">
<Button
className="manual-btn"
onClick={handleManualInvite}
>
</Button>
</View>
</View>
);
const renderRecordsTab = () => (
<View className="records-container">
{inviteRecords.length > 0 ? (
inviteRecords.map((record) => (
<View key={record.id} className="record-item">
<View className="record-header">
<View className="user-info">
<User size={16} color="#666" />
<Text className="user-name">{record.inviteeName}</Text>
</View>
<Tag
color={getStatusColor(record.status)}
plain
>
{getStatusText(record.status)}
</Tag>
</View>
<View className="record-details">
<View className="detail-item">
<Phone size={14} color="#999" />
<Text className="detail-text">{record.inviteePhone}</Text>
</View>
<View className="detail-item">
<Calendar size={14} color="#999" />
<Text className="detail-text">{record.inviteDate}</Text>
</View>
<View className="detail-item">
<Gift size={14} color="#52c41a" />
<Text className="detail-text">
¥{record.reward}
</Text>
</View>
</View>
</View>
))
) : (
<View className="empty-records">
<Text></Text>
</View>
)}
</View>
);
const renderStatsTab = () => (
<View className="stats-container">
{inviteStats && (
<>
<View className="stats-overview">
<View className="stat-item">
<Text className="stat-number">{inviteStats.totalInvites}</Text>
<Text className="stat-label"></Text>
</View>
<View className="stat-item">
<Text className="stat-number">{inviteStats.registeredCount}</Text>
<Text className="stat-label"></Text>
</View>
<View className="stat-item">
<Text className="stat-number">{inviteStats.signedCount}</Text>
<Text className="stat-label"></Text>
</View>
</View>
<View className="reward-summary">
<Space className="reward-card flex items-center justify-center">
<Text></Text>
<Text>¥{inviteStats.totalReward}</Text>
<Button type={'success'} size="small">
</Button>
</Space>
</View>
<View className="stats-chart">
<Text className="chart-title"></Text>
<View className="chart-placeholder">
<Text></Text>
</View>
</View>
</>
)}
</View>
);
return (
<View className="customer-invite-page">
{/* 头部背景 */}
<View className="header-bg" style={{ height: '180px' }} />
{/* 标签页 */}
<View className="tabs-container">
<Tabs
value={activeTab}
onChange={(value) => setActiveTab(value as string)}
>
{tabList.map(tab => (
<Tabs.TabPane key={tab.value} title={tab.title} value={tab.value} />
))}
</Tabs>
</View>
{/* 内容区域 */}
<View className="content-container">
{activeTab === 'qrcode' && renderQRCodeTab()}
{activeTab === 'records' && renderRecordsTab()}
{activeTab === 'stats' && renderStatsTab()}
</View>
{/* 手动邀请弹窗 */}
<Popup
visible={showInviteForm}
position="bottom"
onClose={() => setShowInviteForm(false)}
>
<View className="invite-form">
<View className="form-header">
<Text className="form-title"></Text>
</View>
<Form>
<Form.Item label="姓名" required>
<Input
placeholder="请输入好友姓名"
value={inviteFormData.name}
onChange={(value) => setInviteFormData(prev => ({ ...prev, name: value }))}
/>
</Form.Item>
<Form.Item label="手机号" required>
<Input
placeholder="请输入好友手机号"
type="tel"
value={inviteFormData.phone}
onChange={(value) => setInviteFormData(prev => ({ ...prev, phone: value }))}
/>
</Form.Item>
</Form>
<View className="form-actions">
<Button
className="cancel-btn"
onClick={() => setShowInviteForm(false)}
>
</Button>
<Button
className="submit-btn"
onClick={submitInviteForm}
>
</Button>
</View>
</View>
</Popup>
{/* Toast提示 */}
<Toast
visible={showToast}
content={toastMsg}
duration={2000}
/>
</View>
);
};
export default CustomerInvite;

View File

@@ -1,3 +0,0 @@
export default definePageConfig({
navigationBarTitleText: '客户列表'
})

View File

@@ -1,197 +0,0 @@
.customer-list-page {
min-height: 100vh;
background: #f5f5f5;
position: relative;
.header-bg {
width: 100%;
top: 0;
position: absolute;
z-index: 0;
}
.tabs-container {
position: relative;
z-index: 10;
.nut-tabs {
background: transparent;
.nut-tabs__titles {
background: transparent;
border: none;
.nut-tabs__titles-item {
color: rgba(255, 255, 255, 0.8);
padding: 12px 20px;
&.nut-tabs__titles-item--active {
color: #ffffff;
font-weight: bold;
}
}
}
.nut-tabs__line {
background: #ffffff;
height: 3px;
border-radius: 2px;
}
}
}
.customer-list {
position: relative;
z-index: 10;
padding: 20px 16px 100px;
margin-top: 20px;
.loading-container {
text-align: center;
padding: 40px 0;
color: #999;
}
.customer-item {
background: #ffffff;
border-radius: 12px;
padding: 24px 30px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.customer-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
.company-name {
font-weight: bold;
color: #333;
flex: 1;
}
.status-tag {
font-weight: 500;
padding: 4px 8px;
border-radius: 4px;
background: rgba(0, 0, 0, 0.05);
}
}
.customer-info {
.info-row {
display: flex;
align-items: center;
margin-bottom: 8px;
flex-wrap: wrap;
.label {
color: #666;
margin-right: 8px;
}
.contact-label {
margin-left: 16px;
}
.value {
color: #333;
margin-right: 8px;
}
.phone {
color: #52c41a;
}
.phone-icon {
cursor: pointer;
padding: 4px;
border-radius: 50%;
background: rgba(82, 196, 26, 0.1);
}
}
.address-row {
display: flex;
margin-bottom: 8px;
.label {
color: #666;
margin-right: 8px;
flex-shrink: 0;
}
.address {
color: #333;
line-height: 1.4;
flex: 1;
}
}
.time-row {
.time {
color: #999;
}
}
}
.action-buttons {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 12px;
padding-top: 12px;
border-top: 1px solid #f0f0f0;
.action-btn {
border-radius: 6px;
border: none;
&.sign-btn {
background: #52c41a;
color: #ffffff;
}
&.cancel-btn {
background: #ff4d4f;
color: #ffffff;
}
&.detail-btn {
background: #1890ff;
color: #ffffff;
}
}
}
}
}
.fixed-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
background: #ffffff;
border-top: 1px solid #f0f0f0;
z-index: 100;
.report-btn {
width: 100%;
background: #52c41a;
color: #ffffff;
font-weight: bold;
border-radius: 24px;
border: none;
box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3);
}
}
}
// 适配安全区域
@supports (bottom: env(safe-area-inset-bottom)) {
.customer-list-page .fixed-bottom {
padding-bottom: calc(16px + env(safe-area-inset-bottom));
}
}

View File

@@ -1,235 +0,0 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro';
import {View, Text} from '@tarojs/components';
import {Space, Tabs, Button, Empty} from '@nutui/nutui-react-taro';
import {Phone} from '@nutui/icons-react-taro';
import './list.scss';
import {pageUsers} from "@/api/system/user";
import {ShopDealerUser} from "@/api/shop/shopDealerUser/model";
const CustomerList = () => {
const [activeTab, setActiveTab] = useState<string>('all');
const [loading, setLoading] = useState<boolean>(false);
const [list, setList] = useState<ShopDealerUser[]>([]);
const tabList = [
{title: '全部', value: 'all'},
{title: '跟进中', value: 'pending'},
{title: '已签约', value: 'confirmed'},
{title: '已取消', value: 'cancelled'}
];
const reload = async () => {
setLoading(true);
try {
const res = await pageUsers({status: 0});
console.log(res, '客户列表');
if(res?.list){
// 为每个用户添加默认状态
const customersWithStatus: ShopDealerUser[] = res.list.map(user => ({
...user,
status: 'pending' // 默认状态为跟进中
}));
setList(customersWithStatus);
}
} catch (error) {
console.error('获取客户列表失败:', error);
Taro.showToast({
title: '获取客户列表失败',
icon: 'error'
});
} finally {
setLoading(false);
}
};
const getStatusText = (status: string) => {
switch (status) {
case 'pending':
return '跟进中';
case 'confirmed':
return '已签约';
case 'cancelled':
return '已取消';
default:
return '';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'pending':
return '#ff6b35';
case 'confirmed':
return '#52c41a';
case 'cancelled':
return '#999';
default:
return '#999';
}
};
const handleCall = (phone: string) => {
Taro.makePhoneCall({
phoneNumber: phone
});
};
const handleAction = (customer: ShopDealerUser, action: 'sign' | 'cancel' | 'detail') => {
switch (action) {
case 'sign':
// 跳转到签约页面
Taro.navigateTo({
url: `/pages/customer/sign?customerId=${customer.userId}`
});
break;
case 'cancel':
Taro.showModal({
title: '确认取消',
content: '确定要取消该客户吗?',
success: (res) => {
if (res.confirm) {
// 这里应该调用取消客户的API
Taro.showToast({
title: '已取消',
icon: 'success'
});
// 刷新列表
reload().then();
}
}
});
break;
case 'detail':
// 跳转到客户详情页面
Taro.navigateTo({
url: `/pages/customer/detail?customerId=${customer.userId}`
});
break;
}
};
const handleReport = () => {
// 跳转到邀请页面
Taro.navigateTo({
url: '/pages/customer/invite'
});
};
useEffect(() => {
reload().then();
}, []);
return (
<View className="customer-list-page">
{/* 头部背景 */}
<View className="header-bg" style={{
height: '180px'
}} />
{/* 标签页 */}
<View className="tabs-container">
<Tabs
value={activeTab}
onChange={(value) => setActiveTab(value as string)}
>
{tabList.map(tab => (
<Tabs.TabPane key={tab.value} title={tab.title} value={tab.value} />
))}
</Tabs>
</View>
{/* 客户列表 */}
<View className="customer-list">
{loading ? (
<View className="loading-container">
<Text>...</Text>
</View>
) : list.length > 0 ? (
list.map((record) => (
<View key={record.userId} className="customer-item">
<View className="customer-header">
<Text className="company-name">{record.realName || '未知客户'}</Text>
<Text
className="status-tag"
style={{color: getStatusColor('pending')}}
>
{getStatusText('pending')}
</Text>
</View>
<View className="customer-info">
<View className="info-row">
<Text className="label"></Text>
<Text className="value">{record.realName || '未知'}</Text>
<Text className="label contact-label"></Text>
<Text className="value">{record.mobile || '未提供'}</Text>
<Phone
size={14}
className={'text-green-500'}
onClick={() => handleCall(`${record?.mobile}`)}
/>
</View>
<View className="address-row">
<Text className="label"></Text>
<Text className="address">{'地址未提供'}</Text>
</View>
<View className="time-row">
<Text className="time">{record.createTime || '未知'}</Text>
</View>
</View>
{/* 操作按钮 */}
<View className="action-buttons">
{record.payPassword === 'pending' && (
<Space>
<Button
className="action-btn sign-btn"
size="small"
onClick={() => handleAction(record, 'sign')}
>
</Button>
<Button
className="action-btn cancel-btn"
size="small"
onClick={() => handleAction(record, 'cancel')}
>
</Button>
</Space>
)}
{record.payPassword === 'confirmed' && (
<Button
className="action-btn detail-btn"
size="small"
onClick={() => handleAction(record, 'detail')}
>
</Button>
)}
</View>
</View>
))
) : (
<Empty description="暂无客户数据" />
)}
</View>
{/* 底部邀请好友按钮 */}
<View className="fixed-bottom">
<Button
className="report-btn"
onClick={handleReport}
>
</Button>
</View>
</View>
);
};
export default CustomerList;

View File

@@ -1,6 +0,0 @@
export default definePageConfig({
navigationBarTitleText: '客户签约',
navigationBarTextStyle: 'white',
navigationStyle: 'custom',
backgroundColor: '#f5f5f5'
})

View File

@@ -1,212 +0,0 @@
.customer-sign-page {
min-height: 100vh;
background: #f5f5f5;
position: relative;
padding-bottom: 100px;
.header-bg {
background: linear-gradient(to bottom, #03605c, #18ae4f);
width: 100%;
top: 0;
position: absolute;
z-index: 0;
}
.form-container {
position: relative;
z-index: 10;
padding: 20px 16px;
margin-top: 20px;
.form-section {
background: #ffffff;
border-radius: 12px;
margin-bottom: 16px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
.section-title {
padding: 16px 16px 8px;
font-size: 16px;
font-weight: bold;
color: #333;
border-bottom: 1px solid #f0f0f0;
margin-bottom: 8px;
}
.nut-form-item {
padding: 12px 16px;
border-bottom: 1px solid #f8f8f8;
&:last-child {
border-bottom: none;
}
.nut-form-item__label {
font-size: 14px;
color: #333;
font-weight: 500;
min-width: 80px;
}
.nut-form-item__body {
flex: 1;
}
.nut-input {
font-size: 14px;
color: #333;
&::placeholder {
color: #999;
}
}
.nut-textarea {
font-size: 14px;
color: #333;
&::placeholder {
color: #999;
}
}
.picker-cell {
padding: 0;
background: transparent;
border: none;
.nut-cell__title {
font-size: 14px;
color: #333;
}
.nut-cell__value {
color: #999;
}
}
.nut-switch {
transform: scale(0.8);
}
}
}
}
.fixed-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
background: #ffffff;
border-top: 1px solid #f0f0f0;
z-index: 100;
.submit-btn {
width: 100%;
height: 48px;
background: #52c41a;
color: #ffffff;
font-size: 16px;
font-weight: bold;
border-radius: 24px;
border: none;
box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3);
&.nut-button--loading {
background: #52c41a;
opacity: 0.8;
}
}
}
}
// 适配安全区域
@supports (bottom: env(safe-area-inset-bottom)) {
.customer-sign-page .fixed-bottom {
padding-bottom: calc(16px + env(safe-area-inset-bottom));
}
}
// 表单验证错误样式
.nut-form-item--error {
.nut-form-item__label {
color: #ff4d4f !important;
}
.nut-input {
border-color: #ff4d4f !important;
}
}
// 选择器样式优化
.nut-picker {
.nut-picker__toolbar {
background: #ffffff;
border-bottom: 1px solid #f0f0f0;
}
.nut-picker__confirm {
color: #52c41a;
}
.nut-picker__cancel {
color: #999;
}
}
// 日期选择器样式
.nut-date-picker {
.nut-picker__toolbar {
background: #ffffff;
border-bottom: 1px solid #f0f0f0;
}
.nut-picker__confirm {
color: #52c41a;
}
.nut-picker__cancel {
color: #999;
}
}
// Toast样式
.nut-toast {
.nut-toast__text {
font-size: 14px;
}
}
// 开关组件样式
.nut-switch {
&.nut-switch--active {
background: #52c41a;
}
}
// 响应式适配
@media (max-width: 375px) {
.customer-sign-page {
.form-container {
padding: 16px 12px;
.form-section {
.nut-form-item {
padding: 10px 12px;
.nut-form-item__label {
min-width: 70px;
font-size: 13px;
}
.nut-input,
.nut-textarea {
font-size: 13px;
}
}
}
}
}
}

View File

@@ -1,391 +0,0 @@
import { useEffect, useState } from "react";
import Taro, { useRouter } from '@tarojs/taro';
import { View, Text } from '@tarojs/components';
import {
NavBar,
Form,
Input,
Button,
DatePicker,
Picker,
TextArea,
Cell,
Switch,
Toast
} from '@nutui/nutui-react-taro';
import { ArrowDown } from '@nutui/icons-react-taro';
import './sign.scss';
// 签约表单数据类型
interface SignFormData {
customerId: string;
contractType: string;
contractAmount: string;
contractDate: string;
startDate: string;
endDate: string;
paymentMethod: string;
paymentCycle: string[];
specialTerms: string;
isUrgent: boolean;
contactPerson: string;
contactPhone: string;
signLocation: string;
remarks: string;
}
const CustomerSign = () => {
const router = useRouter();
const [statusBarHeight, setStatusBarHeight] = useState<number>(0);
const [formData, setFormData] = useState<SignFormData>({
customerId: '',
contractType: '',
contractAmount: '',
contractDate: '',
startDate: '',
endDate: '',
paymentMethod: '',
paymentCycle: [],
specialTerms: '',
isUrgent: false,
contactPerson: '',
contactPhone: '',
signLocation: '',
remarks: ''
});
const [loading, setLoading] = useState(false);
const [showToast, setShowToast] = useState(false);
const [toastMsg, setToastMsg] = useState('');
// 合同类型选项
const contractTypes = [
{ text: '服务合同', value: 'service' },
{ text: '销售合同', value: 'sales' },
{ text: '代理合同', value: 'agency' },
{ text: '合作协议', value: 'cooperation' }
];
// 付款方式选项
const paymentMethods = [
{ text: '一次性付款', value: 'onetime' },
{ text: '分期付款', value: 'installment' },
{ text: '月付', value: 'monthly' },
{ text: '季付', value: 'quarterly' },
{ text: '年付', value: 'yearly' }
];
// 付款周期选项
const paymentCycles = [
{ text: '签约后立即付款', value: 'immediate' },
{ text: '签约后7天内', value: '7days' },
{ text: '签约后15天内', value: '15days' },
{ text: '签约后30天内', value: '30days' },
{ text: '按月付款', value: 'monthly' }
];
const showToastMsg = (msg: string) => {
setToastMsg(msg);
setShowToast(true);
setTimeout(() => setShowToast(false), 2000);
};
const handleInputChange = (field: keyof SignFormData, value: any) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
const validateForm = (): boolean => {
if (!formData.contractType) {
showToastMsg('请选择合同类型');
return false;
}
if (!formData.contractAmount) {
showToastMsg('请输入合同金额');
return false;
}
if (!formData.contractDate) {
showToastMsg('请选择签约日期');
return false;
}
if (!formData.startDate) {
showToastMsg('请选择合同开始日期');
return false;
}
if (!formData.endDate) {
showToastMsg('请选择合同结束日期');
return false;
}
if (!formData.paymentMethod) {
showToastMsg('请选择付款方式');
return false;
}
if (!formData.contactPerson) {
showToastMsg('请输入联系人姓名');
return false;
}
if (!formData.contactPhone) {
showToastMsg('请输入联系电话');
return false;
}
if (!formData.signLocation) {
showToastMsg('请输入签约地点');
return false;
}
return true;
};
const handleSubmit = async () => {
if (!validateForm()) return;
setLoading(true);
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1500));
showToastMsg('签约成功!');
// 延迟跳转,让用户看到成功提示
setTimeout(() => {
Taro.navigateBack();
}, 2000);
} catch (error) {
showToastMsg('签约失败,请重试');
} finally {
setLoading(false);
}
};
const formatDate = (date: Date): string => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
useEffect(() => {
Taro.getSystemInfo({
success: (res) => {
setStatusBarHeight(Number(res.statusBarHeight));
},
});
// 获取客户ID
const { customerId } = router.params;
if (customerId) {
setFormData(prev => ({ ...prev, customerId }));
}
}, []);
return (
<View className="customer-sign-page">
{/* 头部背景 */}
<View className="header-bg" style={{ height: '120px' }} />
{/* 导航栏 */}
<NavBar
style={{
marginTop: `${statusBarHeight}px`,
backgroundColor: 'transparent',
color: '#ffffff'
}}
onBackClick={() => Taro.navigateBack()}
>
<Text style={{ color: '#ffffff', fontSize: '18px', fontWeight: 'bold' }}>
</Text>
</NavBar>
{/* 表单内容 */}
<View className="form-container">
<Form>
{/* 基本信息 */}
<View className="form-section">
<View className="section-title"></View>
<Form.Item label="合同类型" required>
<Picker
options={contractTypes}
value={formData.contractType}
onConfirm={(options) => handleInputChange('contractType', options[0]?.value)}
>
<Cell
className="picker-cell"
title={contractTypes.find(item => item.value === formData.contractType)?.text || '请选择合同类型'}
extra={<ArrowDown />}
/>
</Picker>
</Form.Item>
<Form.Item label="合同金额" required>
<Input
placeholder="请输入合同金额"
type="digit"
value={formData.contractAmount}
onChange={(value) => handleInputChange('contractAmount', value)}
/>
</Form.Item>
<Form.Item label="签约日期" required>
<DatePicker
value={formData.contractDate ? new Date(formData.contractDate) : new Date()}
onConfirm={(options, value) => handleInputChange('contractDate', formatDate(value))}
>
<Cell
className="picker-cell"
title={formData.contractDate || '请选择签约日期'}
extra={<ArrowDown />}
/>
</DatePicker>
</Form.Item>
</View>
{/* 合同期限 */}
<View className="form-section">
<View className="section-title"></View>
<Form.Item label="开始日期" required>
<DatePicker
value={formData.startDate ? new Date(formData.startDate) : new Date()}
onConfirm={(options, value) => handleInputChange('startDate', formatDate(value))}
>
<Cell
className="picker-cell"
title={formData.startDate || '请选择开始日期'}
extra={<ArrowDown />}
/>
</DatePicker>
</Form.Item>
<Form.Item label="结束日期" required>
<DatePicker
value={formData.endDate ? new Date(formData.endDate) : new Date()}
onConfirm={(options, value) => handleInputChange('endDate', formatDate(value))}
>
<Cell
className="picker-cell"
title={formData.endDate || '请选择结束日期'}
extra={<ArrowDown />}
/>
</DatePicker>
</Form.Item>
</View>
{/* 付款信息 */}
<View className="form-section">
<View className="section-title"></View>
<Form.Item label="付款方式" required>
<Picker
options={paymentMethods}
value={formData.paymentMethod}
onConfirm={(options) => handleInputChange('paymentMethod', options[0]?.value)}
>
<Cell
className="picker-cell"
title={paymentMethods.find(item => item.value === formData.paymentMethod)?.text || '请选择付款方式'}
extra={<ArrowDown />}
/>
</Picker>
</Form.Item>
<Form.Item label="付款周期">
<Picker
options={paymentCycles}
value={formData.paymentCycle}
onConfirm={(options) => handleInputChange('paymentCycle', options[0]?.value)}
>
<Cell
className="picker-cell"
title={paymentCycles.find(item => item.value === formData.paymentCycle)?.text || '请选择付款周期'}
extra={<ArrowDown />}
/>
</Picker>
</Form.Item>
</View>
{/* 联系信息 */}
<View className="form-section">
<View className="section-title"></View>
<Form.Item label="联系人" required>
<Input
placeholder="请输入联系人姓名"
value={formData.contactPerson}
onChange={(value) => handleInputChange('contactPerson', value)}
/>
</Form.Item>
<Form.Item label="联系电话" required>
<Input
placeholder="请输入联系电话"
type="tel"
value={formData.contactPhone}
onChange={(value) => handleInputChange('contactPhone', value)}
/>
</Form.Item>
<Form.Item label="签约地点" required>
<Input
placeholder="请输入签约地点"
value={formData.signLocation}
onChange={(value) => handleInputChange('signLocation', value)}
/>
</Form.Item>
</View>
{/* 其他信息 */}
<View className="form-section">
<View className="section-title"></View>
<Form.Item label="特殊条款">
<TextArea
placeholder="请输入特殊条款(可选)"
value={formData.specialTerms}
onChange={(value) => handleInputChange('specialTerms', value)}
rows={3}
/>
</Form.Item>
<Form.Item label="紧急处理">
<Switch
checked={formData.isUrgent}
onChange={(value) => handleInputChange('isUrgent', value)}
/>
</Form.Item>
<Form.Item label="备注">
<TextArea
placeholder="请输入备注信息(可选)"
value={formData.remarks}
onChange={(value) => handleInputChange('remarks', value)}
rows={3}
/>
</Form.Item>
</View>
</Form>
</View>
{/* 底部提交按钮 */}
<View className="fixed-bottom">
<Button
className="submit-btn"
loading={loading}
onClick={handleSubmit}
>
{loading ? '提交中...' : '确认签约'}
</Button>
</View>
{/* Toast提示 */}
<Toast
visible={showToast}
content={toastMsg}
duration={2000}
/>
</View>
);
};
export default CustomerSign;

View File

@@ -1,3 +0,0 @@
export default definePageConfig({
navigationBarTitleText: '入市交易'
})

View File

@@ -1,251 +0,0 @@
.customer-trading-page {
min-height: 100vh;
background: #f5f5f5;
position: relative;
padding-bottom: 100px;
.header-bg {
background: linear-gradient(to bottom, #03605c, #18ae4f);
width: 100%;
top: 0;
position: absolute;
z-index: 0;
}
.search-container {
position: relative;
z-index: 10;
padding: 0 16px;
margin-top: 16px;
.nut-searchbar {
border-radius: 20px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
}
.tabs-container {
position: relative;
z-index: 10;
padding: 0 16px;
margin-top: 16px;
.nut-tabs {
background: transparent;
.nut-tabs__titles {
background: transparent;
border: none;
.nut-tabs__titles-item {
color: rgba(255, 255, 255, 0.8);
padding: 12px 20px;
&.nut-tabs__titles-item--active {
color: #ffffff;
font-weight: bold;
}
}
}
.nut-tabs__line {
background: #ffffff;
height: 3px;
border-radius: 2px;
}
}
}
.trading-list {
position: relative;
z-index: 10;
padding: 20px 16px;
margin-top: 20px;
.loading-container {
text-align: center;
padding: 40px 0;
color: #999;
}
.trading-item {
background: #ffffff;
border-radius: 12px;
padding: 16px;
margin-bottom: 16px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
cursor: pointer;
transition: transform 0.2s ease;
&:active {
transform: scale(0.98);
}
.trading-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
.customer-info {
display: flex;
align-items: center;
flex: 1;
.customer-name {
font-weight: bold;
color: #333;
margin-right: 8px;
}
.nut-tag {
margin-left: 8px;
}
}
.status-info {
.nut-tag {
}
}
}
.trading-details {
margin-bottom: 12px;
.detail-row {
display: flex;
justify-content: space-between;
margin-bottom: 8px;
&:last-child {
margin-bottom: 0;
}
.detail-item {
flex: 1;
display: flex;
flex-direction: column;
.label {
color: #999;
margin-bottom: 4px;
}
.value {
color: #333;
font-weight: 500;
&.amount {
font-weight: bold;
color: #333;
}
&.profit {
font-weight: bold;
}
}
}
}
}
.trading-footer {
padding-top: 8px;
border-top: 1px solid #f8f8f8;
.time-info {
display: flex;
align-items: center;
.nut-icon {
margin-right: 4px;
}
.time-text {
color: #999;
}
}
}
}
}
.fixed-bottom {
position: fixed;
bottom: 0;
left: 0;
right: 0;
padding: 16px;
background: #ffffff;
border-top: 1px solid #f0f0f0;
z-index: 100;
.add-btn {
width: 100%;
background: #52c41a;
color: #ffffff;
font-weight: bold;
border: none;
box-shadow: 0 4px 12px rgba(82, 196, 26, 0.3);
}
}
}
// 适配安全区域
@supports (bottom: env(safe-area-inset-bottom)) {
.customer-trading-page .fixed-bottom {
padding-bottom: calc(16px + env(safe-area-inset-bottom));
}
}
// 标签样式优化
.nut-tag {
&.nut-tag--plain {
border-width: 1px;
padding: 2px 6px;
&.nut-tag--small {
padding: 1px 4px;
}
}
}
// 下拉刷新样式
.nut-pulltorefresh {
.nut-pulltorefresh__track {
min-height: auto;
}
}
// 无限加载样式
.nut-infiniteloading {
padding: 16px 0;
text-align: center;
color: #999;
}
// 空状态样式
.nut-empty {
padding: 60px 20px;
.nut-empty__description {
color: #999;
}
}
// 响应式适配
@media (max-width: 375px) {
.customer-trading-page {
.search-container,
.tabs-container {
padding: 0 12px;
}
.trading-list {
padding: 16px 12px;
.trading-item {
padding: 12px;
}
}
}
}

View File

@@ -1,381 +0,0 @@
import { useEffect, useState } from "react";
import Taro from '@tarojs/taro';
import { View, Text } from '@tarojs/components';
import {
NavBar,
SearchBar,
Tabs,
Button,
Tag,
Empty,
PullToRefresh,
InfiniteLoading
} from '@nutui/nutui-react-taro';
import {
Filter,
Calendar
} from '@nutui/icons-react-taro';
import './trading.scss';
// 交易记录数据类型
interface TradingRecord {
id: string;
customerName: string;
customerId: string;
tradingType: 'buy' | 'sell';
amount: string;
price: string;
quantity: string;
tradingDate: string;
status: 'pending' | 'completed' | 'cancelled';
profit: string;
profitRate: string;
}
const CustomerTrading = () => {
const [statusBarHeight, setStatusBarHeight] = useState<number>(0);
const [activeTab, setActiveTab] = useState<string>('all');
const [searchValue, setSearchValue] = useState<string>('');
const [tradingRecords, setTradingRecords] = useState<TradingRecord[]>([]);
const [loading, setLoading] = useState<boolean>(false);
const [refreshing, setRefreshing] = useState<boolean>(false);
const [hasMore, setHasMore] = useState<boolean>(true);
const [page, setPage] = useState<number>(1);
// 模拟交易数据
const mockTradingRecords: TradingRecord[] = [
{
id: '1',
customerName: '广州雅虎信息科技公司',
customerId: '1',
tradingType: 'buy',
amount: '100,000',
price: '15.50',
quantity: '6,451',
tradingDate: '2025-08-21 09:30:15',
status: 'completed',
profit: '+8,500',
profitRate: '+8.5%'
},
{
id: '2',
customerName: '深圳腾讯科技有限公司',
customerId: '2',
tradingType: 'sell',
amount: '250,000',
price: '28.80',
quantity: '8,680',
tradingDate: '2025-08-21 10:15:30',
status: 'completed',
profit: '+15,200',
profitRate: '+6.1%'
},
{
id: '3',
customerName: '阿里巴巴网络技术有限公司',
customerId: '3',
tradingType: 'buy',
amount: '500,000',
price: '42.30',
quantity: '11,820',
tradingDate: '2025-08-21 11:45:20',
status: 'pending',
profit: '0',
profitRate: '0%'
},
{
id: '4',
customerName: '百度在线网络技术公司',
customerId: '4',
tradingType: 'sell',
amount: '180,000',
price: '22.10',
quantity: '8,144',
tradingDate: '2025-08-21 14:20:45',
status: 'cancelled',
profit: '-2,800',
profitRate: '-1.6%'
}
];
const tabList = [
{ title: '全部', value: 'all' },
{ title: '买入', value: 'buy' },
{ title: '卖出', value: 'sell' },
{ title: '待处理', value: 'pending' }
];
const getFilteredRecords = () => {
let filtered = tradingRecords;
// 按标签页筛选
if (activeTab === 'buy') {
filtered = filtered.filter(record => record.tradingType === 'buy');
} else if (activeTab === 'sell') {
filtered = filtered.filter(record => record.tradingType === 'sell');
} else if (activeTab === 'pending') {
filtered = filtered.filter(record => record.status === 'pending');
}
// 按搜索关键词筛选
if (searchValue) {
filtered = filtered.filter(record =>
record.customerName.toLowerCase().includes(searchValue.toLowerCase()) ||
record.id.includes(searchValue)
);
}
return filtered;
};
const getStatusText = (status: string) => {
switch (status) {
case 'pending':
return '待处理';
case 'completed':
return '已完成';
case 'cancelled':
return '已取消';
default:
return '';
}
};
const getStatusColor = (status: string) => {
switch (status) {
case 'pending':
return '#ff6b35';
case 'completed':
return '#52c41a';
case 'cancelled':
return '#ff4d4f';
default:
return '#999';
}
};
const getTradingTypeText = (type: string) => {
return type === 'buy' ? '买入' : '卖出';
};
const getTradingTypeColor = (type: string) => {
return type === 'buy' ? '#52c41a' : '#ff4d4f';
};
const getProfitColor = (profit: string) => {
if (profit.startsWith('+')) return '#52c41a';
if (profit.startsWith('-')) return '#ff4d4f';
return '#999';
};
const loadTradingRecords = async (isRefresh = false) => {
if (isRefresh) {
setRefreshing(true);
setPage(1);
} else {
setLoading(true);
}
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 800));
if (isRefresh) {
setTradingRecords(mockTradingRecords);
setHasMore(true);
} else {
// 模拟分页加载
if (page === 1) {
setTradingRecords(mockTradingRecords);
} else {
setHasMore(false);
}
}
} catch (error) {
Taro.showToast({
title: '加载失败,请重试',
icon: 'none'
});
} finally {
setLoading(false);
setRefreshing(false);
}
};
const loadMore = async () => {
if (!hasMore || loading) return;
setPage(prev => prev + 1);
await loadTradingRecords();
};
const handleSearch = (value: string) => {
setSearchValue(value);
};
const handleFilter = () => {
Taro.showToast({
title: '筛选功能开发中',
icon: 'none'
});
};
const handleRecordClick = (_: TradingRecord) => {
Taro.showToast({
title: '查看交易详情功能开发中',
icon: 'none'
});
};
const handleAddTrading = () => {
Taro.showToast({
title: '新增交易功能开发中',
icon: 'none'
});
};
useEffect(() => {
Taro.getSystemInfo({
success: (res) => {
setStatusBarHeight(Number(res.statusBarHeight));
},
});
loadTradingRecords();
}, []);
return (
<View className="customer-trading-page">
{/* 头部背景 */}
<View className="header-bg" style={{ height: '200px' }} />
{/* 搜索栏 */}
<View className="search-container">
<SearchBar
placeholder="搜索客户名称或交易编号"
value={searchValue}
onChange={handleSearch}
style={{
backgroundColor: 'transparent'
}}
/>
</View>
{/* 标签页 */}
<View className="tabs-container">
<Tabs
value={activeTab}
onChange={(value) => setActiveTab(value as string)}
activeColor="#ffffff"
style={{
backgroundColor: 'transparent',
color: '#ffffff',
}}
>
{tabList.map(tab => (
<Tabs.TabPane key={tab.value} title={tab.title} value={tab.value} />
))}
</Tabs>
</View>
{/* 交易列表 */}
<View className="trading-list">
<PullToRefresh
onRefresh={() => loadTradingRecords(true)}
disabled={refreshing}
>
{loading && page === 1 ? (
<View className="loading-container">
<Text>...</Text>
</View>
) : getFilteredRecords().length > 0 ? (
<>
{getFilteredRecords().map((record) => (
<View
key={record.id}
className="trading-item"
onClick={() => handleRecordClick(record)}
>
<View className="trading-header">
<View className="customer-info">
<Text className="customer-name">{record.customerName}</Text>
<Tag
color={getTradingTypeColor(record.tradingType)}
plain
>
{getTradingTypeText(record.tradingType)}
</Tag>
</View>
<View className="status-info">
<Tag
color={getStatusColor(record.status)}
plain
>
{getStatusText(record.status)}
</Tag>
</View>
</View>
<View className="trading-details">
<View className="detail-row">
<View className="detail-item">
<Text className="label"></Text>
<Text className="value amount">¥{record.amount}</Text>
</View>
<View className="detail-item">
<Text className="label"></Text>
<Text className="value">¥{record.price}</Text>
</View>
</View>
<View className="detail-row">
<View className="detail-item">
<Text className="label"></Text>
<Text className="value">{record.quantity}</Text>
</View>
<View className="detail-item">
<Text className="label"></Text>
<Text
className="value profit"
style={{ color: getProfitColor(record.profit) }}
>
{record.profit} ({record.profitRate})
</Text>
</View>
</View>
</View>
<View className="trading-footer">
<View className="time-info">
<Calendar size={12} color="#999" />
<Text className="time-text">{record.tradingDate}</Text>
</View>
</View>
</View>
))}
<InfiniteLoading
hasMore={hasMore}
onLoadMore={loadMore}
loadingText={loading && page > 1}
/>
</>
) : (
<Empty description="暂无交易记录" />
)}
</PullToRefresh>
</View>
{/* 底部新增交易按钮 */}
<View className="fixed-bottom">
<Button
className="add-btn"
onClick={handleAddTrading}
>
</Button>
</View>
</View>
);
};
export default CustomerTrading;

View File

@@ -37,7 +37,7 @@ const MyGrid = () => {
}}> }}>
{ {
list.map((item) => ( list.map((item) => (
<Grid.Item key={item.navigationId} onClick={() => navTo(`${item.path}`)}> <Grid.Item key={item.navigationId} onClick={() => navTo(`${item.path}`,true)}>
<Avatar src={item.icon} className={'mb-2'} shape="square" style={{ <Avatar src={item.icon} className={'mb-2'} shape="square" style={{
backgroundColor: 'transparent', backgroundColor: 'transparent',
}}/> }}/>

View File

@@ -1,189 +0,0 @@
import Header from './Header';
import BestSellers from './BestSellers';
import Taro from '@tarojs/taro';
import {useShareAppMessage, useShareTimeline} from "@tarojs/taro"
import {useEffect, useState} from "react";
import {Sticky} from '@nutui/nutui-react-taro'
import { useShopInfo } from '@/hooks/useShopInfo';
import { useUser } from '@/hooks/useUser';
import Menu from "./Menu";
import Banner from "./Banner";
import './index.scss'
const Home = () => {
const [stickyStatus, setStickyStatus] = useState(false);
// 使用新的hooks
const {
shopInfo,
loading: shopLoading,
error: shopError,
getWebsiteName,
getWebsiteLogo,
refreshShopInfo
} = useShopInfo();
const {
user,
isLoggedIn,
loading: userLoading
} = useUser();
const onSticky = (args: any) => {
setStickyStatus(args[0].isFixed);
};
const showAuthModal = () => {
Taro.showModal({
title: '授权提示',
content: '需要获取您的用户信息',
confirmText: '去授权',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
// 用户点击确认,打开授权设置页面
Taro.openSetting({
success: (settingRes) => {
if (settingRes.authSetting['scope.userInfo']) {
console.log('用户已授权');
} else {
console.log('用户拒绝授权');
}
}
});
}
}
});
};
// 分享给好友
useShareAppMessage(() => {
return {
title: `${getWebsiteName()} - 精选商城`,
path: '/pages/index/index',
imageUrl: getWebsiteLogo(),
success: function (res: any) {
console.log('分享成功', res);
Taro.showToast({
title: '分享成功',
icon: 'success',
duration: 2000
});
},
fail: function (res: any) {
console.log('分享失败', res);
Taro.showToast({
title: '分享失败',
icon: 'none',
duration: 2000
});
}
};
});
// 分享到朋友圈
useShareTimeline(() => {
return {
title: `${getWebsiteName()} - 精选商城`,
imageUrl: getWebsiteLogo(),
success: function (res: any) {
console.log('分享到朋友圈成功', res);
},
fail: function (res: any) {
console.log('分享到朋友圈失败', res);
}
};
});
useEffect(() => {
// 设置页面标题
if (shopInfo?.appName) {
Taro.setNavigationBarTitle({
title: shopInfo.appName
});
}
}, [shopInfo]);
useEffect(() => {
// 检查用户授权状态
Taro.getSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
console.log('用户已经授权过,可以直接获取用户信息');
} else {
console.log('用户未授权,需要弹出授权窗口');
showAuthModal();
}
}
});
// 获取用户基本信息(头像、昵称等)
Taro.getUserInfo({
success: (res) => {
const avatar = res.userInfo.avatarUrl;
console.log('用户头像:', avatar);
},
fail: (err) => {
console.log('获取用户信息失败:', err);
}
});
}, []);
// 处理错误状态
if (shopError) {
return (
<div style={{padding: '20px', textAlign: 'center'}}>
<div>: {shopError}</div>
<button
onClick={refreshShopInfo}
style={{marginTop: '10px', padding: '10px 20px'}}
>
</button>
</div>
);
}
// 显示加载状态
if (shopLoading) {
return (
<div style={{padding: '20px', textAlign: 'center'}}>
<div>...</div>
</div>
);
}
return (
<>
<Sticky threshold={0} onChange={(args) => onSticky(args)}>
<Header stickyStatus={stickyStatus}/>
</Sticky>
<div className={'flex flex-col mt-12'}>
<Menu/>
<Banner/>
<BestSellers/>
{/* 调试信息面板 - 仅在开发环境显示 */}
{process.env.NODE_ENV === 'development' && (
<div style={{
position: 'fixed',
bottom: '10px',
right: '10px',
background: 'rgba(0,0,0,0.8)',
color: 'white',
padding: '10px',
borderRadius: '5px',
fontSize: '12px',
maxWidth: '200px'
}}>
<div>: {getWebsiteName()}</div>
<div>: {isLoggedIn ? (user?.nickname || '已登录') : '未登录'}</div>
<div>: {userLoading ? '用户加载中' : '已完成'}</div>
</div>
)}
</div>
</>
)
}
export default Home;

View File

@@ -4,15 +4,13 @@ import Taro from '@tarojs/taro';
import {useShareAppMessage, useShareTimeline} from "@tarojs/taro" import {useShareAppMessage, useShareTimeline} from "@tarojs/taro"
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {getShopInfo} from "@/api/layout"; import {getShopInfo} from "@/api/layout";
import {Sticky, Button} from '@nutui/nutui-react-taro' import {Sticky} from '@nutui/nutui-react-taro'
import {View} from '@tarojs/components' import {View} from '@tarojs/components'
import Menu from "./Menu"; import Menu from "./Menu";
import Banner from "./Banner"; import Banner from "./Banner";
import './index.scss' import './index.scss'
import Grid from "@/pages/index/Grid"; import Grid from "@/pages/index/Grid";
// import GoodsList from "./GoodsList";
function Home() { function Home() {
// 吸顶状态 // 吸顶状态
const [stickyStatus, setStickyStatus] = useState<boolean>(false) const [stickyStatus, setStickyStatus] = useState<boolean>(false)
@@ -37,10 +35,6 @@ function Home() {
}; };
}); });
// const reloadMore = async () => {
// setPage(page + 1)
// }
const showAuthModal = () => { const showAuthModal = () => {
Taro.showModal({ Taro.showModal({
title: '授权提示', title: '授权提示',
@@ -117,13 +111,12 @@ function Home() {
<Sticky threshold={0} onChange={() => onSticky(arguments)}> <Sticky threshold={0} onChange={() => onSticky(arguments)}>
<Header stickyStatus={stickyStatus}/> <Header stickyStatus={stickyStatus}/>
</Sticky> </Sticky>
<div className={'flex flex-col mt-1'}> <View className={'flex flex-col mt-1'}>
<Menu/> <Menu/>
<Banner/> <Banner/>
<BestSellers/> <BestSellers/>
<Grid /> <Grid />
{/*<GoodsList/>*/} </View>
</div>
</> </>
) )
} }