refactor(src): 移除客户详情相关代码
- 删除了客户详情页面的配置、样式和组件文件 - 更新了 app.config.ts,移除了与客户详情相关的页面引用 - 优化了首页 Grid 组件的导航逻辑
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 |
@@ -180,7 +180,9 @@ const DealerOrders: React.FC = () => {
|
|||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Empty description="暂无收益"/>
|
<Empty description="暂无收益" style={{
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
}}/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '我的团队'
|
navigationBarTitleText: '邀请推广'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
export default definePageConfig({
|
|
||||||
navigationBarTitleText: '客户详情',
|
|
||||||
navigationBarTextStyle: 'white',
|
|
||||||
navigationStyle: 'custom',
|
|
||||||
backgroundColor: '#f5f5f5'
|
|
||||||
})
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export default definePageConfig({
|
|
||||||
navigationBarTitleText: '邀请好友'
|
|
||||||
})
|
|
||||||
@@ -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 {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export default definePageConfig({
|
|
||||||
navigationBarTitleText: '客户列表'
|
|
||||||
})
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export default definePageConfig({
|
|
||||||
navigationBarTitleText: '客户签约',
|
|
||||||
navigationBarTextStyle: 'white',
|
|
||||||
navigationStyle: 'custom',
|
|
||||||
backgroundColor: '#f5f5f5'
|
|
||||||
})
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export default definePageConfig({
|
|
||||||
navigationBarTitleText: '入市交易'
|
|
||||||
})
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
@@ -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',
|
||||||
}}/>
|
}}/>
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -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>
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user