feat(src): 新增文章、地址和经销商申请相关页面

- 新增文章添加/编辑页面(add.tsx)
- 新增用户地址添加/编辑页面(add.tsx)
- 新增经销商申请页面(add.tsx)
- 添加相关配置文件(.editorconfig, .eslintrc, .gitignore)
This commit is contained in:
2025-08-24 13:10:52 +08:00
commit 3d5b77ae51
522 changed files with 74442 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '购物车',
navigationStyle: 'custom'
})

31
src/pages/cart/cart.scss Normal file
View File

@@ -0,0 +1,31 @@
// 购物车页面样式
.cart-page {
// 当购物车为空时,设置透明背景
&.empty {
page {
background-color: transparent !important;
}
.cart-empty-container {
background-color: transparent !important;
}
}
}
// 空购物车容器样式
.cart-empty-container {
background-color: transparent !important;
// 确保 Empty 组件及其子元素也是透明的
.nut-empty {
background-color: transparent !important;
}
.nut-empty__image {
background-color: transparent !important;
}
.nut-empty__description {
background-color: transparent !important;
}
}

356
src/pages/cart/cart.tsx Normal file
View File

@@ -0,0 +1,356 @@
import {useEffect, useState} from "react";
import Taro, {useShareAppMessage, useShareTimeline, useDidShow} from '@tarojs/taro';
import {
NavBar,
Checkbox,
Image,
InputNumber,
Button,
Empty,
Divider,
ConfigProvider
} from '@nutui/nutui-react-taro';
import {ArrowLeft, Del} from '@nutui/icons-react-taro';
import {View} from '@tarojs/components';
import {CartItem, useCart} from "@/hooks/useCart";
import './cart.scss';
function Cart() {
const [statusBarHeight, setStatusBarHeight] = useState<number>(0);
const [selectedItems, setSelectedItems] = useState<number[]>([]);
const [isAllSelected, setIsAllSelected] = useState(false);
const {
cartItems,
cartCount,
updateQuantity,
removeFromCart,
clearCart,
loadCartFromStorage
} = useCart();
// InputNumber 主题配置
const customTheme = {
nutuiInputnumberButtonWidth: '28px',
nutuiInputnumberButtonHeight: '28px',
nutuiInputnumberInputWidth: '40px',
nutuiInputnumberInputHeight: '28px',
nutuiInputnumberInputBorderRadius: '4px',
nutuiInputnumberButtonBorderRadius: '4px',
}
useShareTimeline(() => {
return {
title: '购物车 - 网宿小店'
};
});
useShareAppMessage(() => {
return {
title: '购物车 - 网宿小店',
success: function () {
console.log('分享成功');
},
fail: function () {
console.log('分享失败');
}
};
});
// 页面显示时刷新购物车数据
useDidShow(() => {
loadCartFromStorage();
});
useEffect(() => {
Taro.getSystemInfo({
success: (res) => {
setStatusBarHeight(res.statusBarHeight || 0);
},
});
// 设置导航栏背景色
Taro.setNavigationBarColor({
backgroundColor: '#ffffff',
frontColor: 'black',
});
}, []);
// 根据购物车状态动态设置页面背景色
useEffect(() => {
if (cartItems.length === 0) {
// 购物车为空时设置透明背景
Taro.setBackgroundColor({
backgroundColor: 'transparent'
});
} else {
// 有商品时恢复默认背景
Taro.setBackgroundColor({
backgroundColor: '#f5f5f5'
});
}
}, [cartItems.length]);
// 处理单个商品选择
const handleItemSelect = (goodsId: number, checked: boolean) => {
if (checked) {
setSelectedItems([...selectedItems, goodsId]);
} else {
setSelectedItems(selectedItems.filter(id => id !== goodsId));
setIsAllSelected(false);
}
};
// 处理全选
const handleSelectAll = (checked: boolean) => {
setIsAllSelected(checked);
if (checked) {
setSelectedItems(cartItems.map((item: CartItem) => item.goodsId));
} else {
setSelectedItems([]);
}
};
// 更新商品数量
const handleQuantityChange = (goodsId: number, value: number) => {
updateQuantity(goodsId, value);
};
// 删除商品
const handleRemoveItem = (goodsId: number) => {
Taro.showModal({
title: '确认删除',
content: '确定要删除这个商品吗?',
success: (res) => {
if (res.confirm) {
removeFromCart(goodsId);
setSelectedItems(selectedItems.filter(id => id !== goodsId));
}
}
});
};
// 计算选中商品的总价
const getSelectedTotalPrice = () => {
return cartItems
.filter((item: CartItem) => selectedItems.includes(item.goodsId))
.reduce((total: number, item: CartItem) => total + (parseFloat(item.price) * item.quantity), 0)
.toFixed(2);
};
// 去结算
const handleCheckout = () => {
if (selectedItems.length === 0) {
Taro.showToast({
title: '请选择要结算的商品',
icon: 'none'
});
return;
}
// 获取选中的商品
const selectedCartItems = cartItems.filter((item: CartItem) =>
selectedItems.includes(item.goodsId)
);
// 将选中的商品信息存储到本地,供结算页面使用
Taro.setStorageSync('checkout_items', JSON.stringify(selectedCartItems));
// 跳转到购物车结算页面
Taro.navigateTo({
url: '/shop/orderConfirmCart/index'
});
};
// 检查是否全选
useEffect(() => {
if (cartItems.length > 0 && selectedItems.length === cartItems.length) {
setIsAllSelected(true);
} else {
setIsAllSelected(false);
}
}, [selectedItems, cartItems]);
if (cartItems.length === 0) {
return (
<>
<NavBar
fixed={true}
style={{marginTop: `${statusBarHeight}px`}}
left={<ArrowLeft onClick={() => Taro.navigateBack()}/>}
right={
cartItems.length > 0 && (
<Button
size="small"
type="primary"
fill="none"
onClick={() => {
Taro.showModal({
title: '确认清空',
content: '确定要清空购物车吗?',
success: (res) => {
if (res.confirm) {
clearCart();
setSelectedItems([]);
}
}
});
}}
>
</Button>
)
}
>
<span className="text-lg">({cartCount})</span>
</NavBar>
{/* 垂直居中的空状态容器 */}
<View
className="flex items-center justify-center"
style={{
height: `calc(100vh - ${statusBarHeight + 150}px)`,
paddingTop: `${statusBarHeight + 50}px`,
backgroundColor: 'transparent'
}}
>
<Empty
description="购物车空空如也"
actions={[{ text: '去逛逛' }]}
style={{
backgroundColor: 'transparent'
}}
onClick={() => Taro.switchTab({ url: '/pages/index/index' })}
/>
</View>
</>
)
}
return (
<>
<View style={{backgroundColor: '#f6f6f6', height: `${statusBarHeight}px`}}
className="fixed z-10 top-0 w-full"></View>
<NavBar
fixed={true}
style={{marginTop: `${statusBarHeight}px`}}
left={<ArrowLeft onClick={() => Taro.navigateBack()}/>}
right={
cartItems.length > 0 && (
<Button
size="small"
type="primary"
fill="none"
onClick={() => {
Taro.showModal({
title: '确认清空',
content: '确定要清空购物车吗?',
success: (res) => {
if (res.confirm) {
clearCart();
setSelectedItems([]);
}
}
});
}}
>
</Button>
)
}
>
<span className="text-lg">({cartCount})</span>
</NavBar>
{/* 购物车内容 */}
<View className="pt-24">
{/* 商品列表 */}
<View className="bg-white">
{cartItems.map((item: CartItem, index: number) => (
<View key={item.goodsId}>
<View className="bg-white px-4 py-3 flex items-center gap-3">
{/* 选择框 */}
<Checkbox
checked={selectedItems.includes(item.goodsId)}
onChange={(checked) => handleItemSelect(item.goodsId, checked)}
/>
{/* 商品图片 */}
<Image
src={item.image}
width="80"
height="80"
lazyLoad={false}
radius="8"
className="flex-shrink-0"
/>
{/* 商品信息 */}
<View className="flex-1 min-w-0">
<View className="text-lg font-bold text-gray-900 truncate mb-1">
{item.name}
</View>
<View className="flex items-center justify-between">
<View className={'flex text-red-500 text-xl items-baseline'}>
<span className={'text-xs'}></span>
<span className={'font-bold text-lg'}>{item.price}</span>
</View>
<ConfigProvider theme={customTheme}>
<InputNumber
value={item.quantity}
min={1}
onChange={(value) => handleQuantityChange(item.goodsId, Number(value))}
/>
</ConfigProvider>
<Del className={'text-red-500'} size={14} onClick={() => handleRemoveItem(item.goodsId)}/>
</View>
</View>
</View>
{index < cartItems.length - 1 && <Divider/>}
</View>
))}
</View>
{/* 底部结算栏 */}
<View
className="fixed z-50 bottom-0 left-0 right-0 bg-white border-t border-gray-200 px-4 py-3 safe-area-bottom">
<View className="flex items-center justify-between">
<View className="flex items-center gap-3">
<Checkbox
checked={isAllSelected}
onChange={handleSelectAll}
>
</Checkbox>
<View className="text-sm text-gray-600">
{selectedItems.length}
</View>
</View>
<View className="flex items-center gap-4">
<View className="text-right">
<View className="text-xs text-gray-500">:</View>
<div className={'flex text-red-500 text-xl items-baseline'}>
<span className={'text-xs'}></span>
<span className={'font-bold text-lg'}>{getSelectedTotalPrice()}</span>
</div>
</View>
<Button
type="primary"
size="large"
disabled={selectedItems.length === 0}
onClick={handleCheckout}
className="px-6"
>
({selectedItems.length})
</Button>
</View>
</View>
</View>
{/* 底部安全区域占位 */}
<View className="h-20"></View>
</View>
</>
);
}
export default Cart;

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '发现',
navigationStyle: 'custom',
})

4
src/pages/find/find.scss Normal file
View File

@@ -0,0 +1,4 @@
page {
background: linear-gradient(to bottom, #e9fff2, #f9fafb);
background-size: 100%;
}

82
src/pages/find/find.tsx Normal file
View File

@@ -0,0 +1,82 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro';
import {pageCmsArticle} from "@/api/cms/cmsArticle";
import {CmsArticle} from "@/api/cms/cmsArticle/model";
import {NavBar, Cell} from '@nutui/nutui-react-taro';
import {Text} from '@tarojs/components';
import {ArrowRight, ImageRectangle, Coupon, Follow} from '@nutui/icons-react-taro'
import './find.scss'
import navTo from "@/utils/common";
/**
* 文章终极列表
* @constructor
*/
const Find = () => {
const [statusBarHeight, setStatusBarHeight] = useState<number>()
const [loading, setLoading] = useState<boolean>(false)
const [list, setList] = useState<CmsArticle[]>()
const reload = async () => {
setLoading(true)
const article = await pageCmsArticle({categoryId: 4289, status: 0})
if (article) {
setList(article?.list)
setLoading(false)
}
}
useEffect(() => {
Taro.getSystemInfo({
success: (res) => {
setStatusBarHeight(res.statusBarHeight)
},
})
reload().then(() => {
console.log('初始化完成')
})
}, [])
return (
<>
{loading && (<div></div>)}
<NavBar
fixed={false}
style={{marginTop: `${statusBarHeight}px`, backgroundColor: 'transparent'}}
onBackClick={() => {
}}
>
<span></span>
</NavBar>
{list && (
<>
<Cell title={
<div style={{display: 'inline-flex', alignItems: 'center'}}>
<ImageRectangle size={18}/>
<Text className={'pl-3'} style={{fontSize: '16px'}}></Text>
</div>
} extra={<ArrowRight color="#cccccc" size={18}/>}/>
<Cell
title={
<div style={{display: 'inline-flex', alignItems: 'center'}}>
<Coupon size={18}/>
<span className={'pl-3'} style={{fontSize: '16px'}}></span>
</div>
}
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/shop/shopArticle/index', true)
}}
/>
<Cell title={
<div style={{display: 'inline-flex', alignItems: 'center'}}>
<Follow size={18}/>
<span className={'pl-3'} style={{fontSize: '16px'}}></span>
</div>
} extra={<ArrowRight color="#cccccc" size={18}/>}/>
</>
)}
</>
)
}
export default Find

View File

@@ -0,0 +1,31 @@
import { useEffect, useState } from 'react'
import { Swiper } from '@nutui/nutui-react-taro'
import {CmsAd} from "@/api/cms/cmsAd/model";
import {Image} from '@nutui/nutui-react-taro'
import {getCmsAd} from "@/api/cms/cmsAd";
const MyPage = () => {
const [item, setItem] = useState<CmsAd>()
const reload = () => {
getCmsAd(439).then(data => {
setItem(data)
})
}
useEffect(() => {
reload()
}, [])
return (
<>
<Swiper defaultValue={0} height={item?.height} indicator style={{ height: item?.height + 'px', display: 'none' }}>
{item?.imageList?.map((item) => (
<Swiper.Item key={item}>
<Image width="100%" height="100%" src={item.url} mode={'scaleToFill'} lazyLoad={false} style={{ height: item.height + 'px' }} />
</Swiper.Item>
))}
</Swiper>
</>
)
}
export default MyPage

View File

View File

@@ -0,0 +1,141 @@
import {useEffect, useState} from "react";
import {Image} from '@nutui/nutui-react-taro'
import {Share} from '@nutui/icons-react-taro'
import {View, Text} from '@tarojs/components';
import Taro, {useShareAppMessage, useShareTimeline} from "@tarojs/taro";
import {ShopGoods} from "@/api/shop/shopGoods/model";
import {pageShopGoods} from "@/api/shop/shopGoods";
import './BestSellers.scss'
const BestSellers = () => {
const [list, setList] = useState<ShopGoods[]>([])
const [goods, setGoods] = useState<ShopGoods>()
const reload = () => {
pageShopGoods({}).then(res => {
setList(res?.list || []);
})
}
// 处理分享点击
const handleShare = (item: ShopGoods) => {
setGoods(item);
// 显示分享选项菜单
Taro.showActionSheet({
itemList: ['分享给好友', '分享到朋友圈'],
success: (res) => {
if (res.tapIndex === 0) {
// 分享给好友 - 触发转发
Taro.showShareMenu({
withShareTicket: true,
success: () => {
// 提示用户点击右上角分享
Taro.showToast({
title: '请点击右上角分享给好友',
icon: 'none',
duration: 2000
});
}
});
} else if (res.tapIndex === 1) {
// 分享到朋友圈
Taro.showToast({
title: '请点击右上角分享到朋友圈',
icon: 'none',
duration: 2000
});
}
},
fail: (err) => {
console.log('显示分享菜单失败', err);
}
});
}
useEffect(() => {
reload()
}, [])
// 分享给好友
useShareAppMessage(() => {
return {
title: goods?.name || '精选商品',
path: `/shop/goodsDetail/index?id=${goods?.goodsId}`,
imageUrl: goods?.image, // 分享图片
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: `${goods?.name || '精选商品'} - 网宿小店`,
path: `/shop/goodsDetail/index?id=${goods?.goodsId}`,
imageUrl: goods?.image
};
});
return (
<>
<View className={'py-3'}>
<View className={'flex flex-col justify-between items-center rounded-lg px-2'}>
{list?.map((item, index) => {
return (
<View key={index} className={'flex flex-col rounded-lg bg-white shadow-sm w-full mb-5'}>
<Image src={item.image} mode={'aspectFit'} lazyLoad={false}
radius="10px 10px 0 0" height="180"
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
<View className={'flex flex-col p-2 rounded-lg'}>
<View>
<View className={'car-no text-sm'}>{item.name}</View>
<View className={'flex justify-between text-xs py-1'}>
<Text className={'text-orange-500'}>{item.comments}</Text>
<Text className={'text-gray-400'}> {item.sales}</Text>
</View>
<View className={'flex justify-between items-center py-2'}>
<View className={'flex text-red-500 text-xl items-baseline'}>
<Text className={'text-xs'}></Text>
<Text className={'font-bold text-2xl'}>{item.price}</Text>
</View>
<View className={'buy-btn'}>
<View className={'cart-icon flex items-center'}>
<View
className={'flex flex-col justify-center items-center text-white px-3 gap-1 text-nowrap whitespace-nowrap cursor-pointer'}
onClick={() => handleShare(item)}
>
<Share size={20}/>
</View>
</View>
<Text className={'text-white pl-4 pr-5'}
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}>
</Text>
</View>
</View>
</View>
</View>
</View>
)
})}
</View>
</View>
</>
)
}
export default BestSellers

69
src/pages/index/Chart.tsx Normal file
View File

@@ -0,0 +1,69 @@
import {useEffect, useState} from "react";
import {Tabs, TabPane} from '@nutui/nutui-react-taro'
const list = [
{
title: '今天',
id: 1
},
{
title: '昨天',
id: 2
},
{
title: '过去7天',
id: 3
},
{
title: '过去30天',
id: 4
}
]
const Chart = () => {
const [tapIndex, setTapIndex] = useState<string | number>('0')
const reload = () => {
}
useEffect(() => {
reload()
}, [])
return (
<>
<Tabs
align={'left'}
tabStyle={{position: 'sticky', top: '0px'}}
value={tapIndex}
onChange={(paneKey) => {
setTapIndex(paneKey)
}}
>
{
list?.map((item, index) => {
return (
<TabPane key={index} title={item.title}/>
)
})
}
</Tabs>
{
list?.map((item, index) => {
console.log(item.title)
return (
<div key={index} className={'px-3'}>
{
tapIndex != index ? null :
<div className={'bg-white rounded-lg p-4 flex justify-center items-center text-center text-gray-300'} style={{height: '200px'}}>
线
</div>
}
</div>
)
})
}
</>
)
}
export default Chart

View File

@@ -0,0 +1,83 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro'
import {Button} from '@nutui/nutui-react-taro'
import {Target, Scan, Truck} from '@nutui/icons-react-taro'
import {getUserInfo} from "@/api/layout";
import navTo from "@/utils/common";
const ExpirationTime = () => {
const [isAdmin, setIsAdmin] = useState<boolean>(false)
const [roleName, setRoleName] = useState<string>()
const onScanCode = () => {
Taro.scanCode({
onlyFromCamera: true,
scanType: ['qrCode'],
success: (res) => {
console.log(res, 'qrcode...')
Taro.navigateTo({url: '/hjm/query?id=' + res.result})
},
fail: (res) => {
console.log(res, '扫码失败')
Taro.showToast({
title: '扫码失败',
icon: 'none',
duration: 2000
})
}
})
}
const navToCarList = () => {
if (isAdmin) {
navTo('/hjm/list', true)
}
}
useEffect(() => {
getUserInfo().then((data) => {
if (data) {
if(data.certification){
setIsAdmin( true)
}
data.roles?.map((item, index) => {
if (index == 0) {
setRoleName(item.roleCode)
}
})
}
})
}, [])
return (
<div className={'mb-3 fixed top-36 z-20'} style={{width: '96%', marginLeft: '3%'}}>
<div className={'w-full flex justify-around items-center py-3 rounded-lg'}>
<>
<Button size={'large'}
style={{background: 'linear-gradient(to right, #f3f2f7, #805de1)', borderColor: '#f3f2f7'}}
icon={<Truck/>} onClick={navToCarList}></Button>
<Button size={'large'}
style={{background: 'linear-gradient(to right, #fffbe6, #ffc53d)', borderColor: '#f3f2f7'}}
icon={<Scan/>}
onClick={onScanCode}>
</Button>
</>
{
roleName == 'youzheng' && <Button size={'large'} style={{
background: 'linear-gradient(to right, #eaff8f, #7cb305)',
borderColor: '#f3f2f7'
}} icon={<Target/>} onClick={() => Taro.navigateTo({url: '/hjm/fence'})}></Button>
}
{
roleName == 'kuaidiyuan' && <Button size={'large'} style={{
background: 'linear-gradient(to right, #ffa39e, #ff4d4f)',
borderColor: '#f3f2f7'
}} icon={<Target/>} onClick={() => Taro.navigateTo({url: '/hjm/bx/bx-add'})}></Button>
}
</div>
</div>
)
}
export default ExpirationTime

View File

View File

@@ -0,0 +1,67 @@
import {useEffect, useState} from "react";
import {Image} from '@nutui/nutui-react-taro'
import {Share} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
import {ShopGoods} from "@/api/shop/shopGoods/model";
import {pageShopGoods} from "@/api/shop/shopGoods";
import './GoodsList.scss'
const BestSellers = () => {
const [list, setList] = useState<ShopGoods[]>([])
const reload = () => {
pageShopGoods({}).then(res => {
setList(res?.list || []);
})
}
useEffect(() => {
reload()
}, [])
return (
<>
<div className={'py-3'}>
<div className={'flex flex-wrap justify-between items-start rounded-lg px-2'}>
{list?.map((item, index) => {
return (
<div key={index} className={'flex flex-col rounded-lg bg-white shadow-sm mb-5'} style={{
width: '48%'
}}>
<Image src={item.image} mode={'scaleToFill'} lazyLoad={false}
radius="10px 10px 0 0" height="180"
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
<div className={'flex flex-col p-2 rounded-lg'}>
<div>
<div className={'car-no text-sm'}>{item.name}</div>
<div className={'flex justify-between text-xs py-1'}>
<span className={'text-orange-500'}>{item.comments}</span>
<span className={'text-gray-400'}> {item.sales}</span>
</div>
<div className={'flex justify-between items-center py-2'}>
<div className={'flex text-red-500 text-xl items-baseline'}>
<span className={'text-xs'}></span>
<span className={'font-bold text-2xl'}>{item.price}</span>
</div>
<div className={'buy-btn'}>
<div className={'cart-icon'}>
<Share size={20} className={'mx-4 mt-2'}
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
</div>
<div className={'text-white pl-4 pr-5'}
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}>
</div>
</div>
</div>
</div>
</div>
</div>
)
})}
</div>
</div>
</>
)
}
export default BestSellers

View File

@@ -0,0 +1,16 @@
.header-bg{
background: linear-gradient(to bottom, #03605c, #18ae4f);
height: 335px;
width: 100%;
top: 0;
position: absolute;
z-index: 0;
}
.header-bg2{
background: linear-gradient(to bottom, #03605c, #18ae4f);
height: 200px;
width: 100%;
top: 0;
position: absolute;
z-index: 0;
}

197
src/pages/index/Header.tsx Normal file
View File

@@ -0,0 +1,197 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro';
import {Button, Space} from '@nutui/nutui-react-taro'
import {TriangleDown} from '@nutui/icons-react-taro'
import {Avatar, NavBar} from '@nutui/nutui-react-taro'
import {getUserInfo, getWxOpenId} from "@/api/layout";
import {TenantId} from "@/config/app";
import {getOrganization} from "@/api/system/organization";
import {myUserVerify} from "@/api/system/userVerify";
import { useShopInfo } from '@/hooks/useShopInfo';
import {handleInviteRelation} from "@/utils/invite";
import {View,Text} from '@tarojs/components'
import MySearch from "./MySearch";
import './Header.scss';
const Header = (props: any) => {
// 使用新的useShopInfo Hook
const {
getWebsiteName,
getWebsiteLogo
} = useShopInfo();
const [IsLogin, setIsLogin] = useState<boolean>(true)
const [statusBarHeight, setStatusBarHeight] = useState<number>()
const reload = async () => {
Taro.getSystemInfo({
success: (res) => {
setStatusBarHeight(res.statusBarHeight)
},
})
// 注意商店信息现在通过useShopInfo自动管理不需要手动获取
// 获取用户信息
getUserInfo().then((data) => {
if (data) {
setIsLogin(true);
console.log('用户信息>>>', data.phone)
// 保存userId
Taro.setStorageSync('UserId', data.userId)
// 获取openId
if (!data.openid) {
Taro.login({
success: (res) => {
getWxOpenId({code: res.code}).then(() => {
})
}
})
}
// 是否已认证
if (data.certification) {
Taro.setStorageSync('Certification', '1')
}
// 机构ID
Taro.setStorageSync('OrganizationId', data.organizationId)
// 父级机构ID
if (Number(data.organizationId) > 0) {
getOrganization(Number(data.organizationId)).then(res => {
Taro.setStorageSync('OrganizationParentId', res.parentId)
})
}
// 管理员
const isKdy = data.roles?.findIndex(item => item.roleCode == 'admin')
if (isKdy != -1) {
Taro.setStorageSync('RoleName', '管理')
Taro.setStorageSync('RoleCode', 'admin')
return false;
}
// 注册用户
const isUser = data.roles?.findIndex(item => item.roleCode == 'user')
if (isUser != -1) {
Taro.setStorageSync('RoleName', '注册用户')
Taro.setStorageSync('RoleCode', 'user')
return false;
}
}
}).catch(() => {
setIsLogin(false);
console.log('未登录')
});
// 认证信息
myUserVerify({status: 1}).then(data => {
if (data?.realName) {
Taro.setStorageSync('RealName', data.realName)
}
})
}
/* 获取用户手机号 */
const handleGetPhoneNumber = ({detail}: {detail: {code?: string, encryptedData?: string, iv?: string}}) => {
const {code, encryptedData, iv} = detail
Taro.login({
success: function () {
if (code) {
Taro.request({
url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone',
method: 'POST',
data: {
code,
encryptedData,
iv,
notVerifyPhone: true,
refereeId: 0,
sceneType: 'save_referee',
tenantId: TenantId
},
header: {
'content-type': 'application/json',
TenantId
},
success: async function (res) {
if (res.data.code == 1) {
Taro.showToast({
title: res.data.message,
icon: 'error',
duration: 2000
})
return false;
}
// 登录成功
Taro.setStorageSync('access_token', res.data.data.access_token)
Taro.setStorageSync('UserId', res.data.data.user.userId)
setIsLogin(true)
// 处理邀请关系
if (res.data.data.user?.userId) {
try {
const inviteSuccess = await handleInviteRelation(res.data.data.user.userId)
if (inviteSuccess) {
Taro.showToast({
title: '邀请关系建立成功',
icon: 'success',
duration: 2000
})
}
} catch (error) {
console.error('处理邀请关系失败:', error)
}
}
// 重新加载小程序
Taro.reLaunch({
url: '/pages/index/index'
})
}
})
} else {
console.log('登录失败!')
}
}
})
}
useEffect(() => {
reload().then()
}, [])
return (
<>
<View className={'fixed top-0 header-bg'} style={{
height: !props.stickyStatus ? '180px' : '148px',
}}>
<MySearch/>
{/*{!props.stickyStatus && <MySearch done={reload}/>}*/}
</View>
<NavBar
style={{marginTop: `${statusBarHeight}px`, marginBottom: '0px', backgroundColor: 'transparent'}}
onBackClick={() => {
}}
left={
!IsLogin ? (
<View style={{display: 'flex', alignItems: 'center'}}>
<Button style={{color: '#ffffff'}} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
<Space>
<Avatar
size="22"
src={getWebsiteLogo()}
/>
<Text style={{color: '#ffffff'}}>{getWebsiteName()}</Text>
<TriangleDown size={9} className={'text-white'}/>
</Space>
</Button>
</View>
) : (
<View style={{display: 'flex', alignItems: 'center', gap: '8px'}}>
<Avatar
size="22"
src={getWebsiteLogo()}
/>
<Text className={'text-white'}>{getWebsiteName()}</Text>
<TriangleDown className={'text-white'} size={9}/>
</View>
)}>
</NavBar>
</>
)
}
export default Header

View File

@@ -0,0 +1,222 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro';
import {Button, Space} from '@nutui/nutui-react-taro'
import {TriangleDown} from '@nutui/icons-react-taro'
import {Popup, Avatar, NavBar} from '@nutui/nutui-react-taro'
import {getUserInfo, getWxOpenId} from "@/api/layout";
import {TenantId} from "@/config/app";
import {getOrganization} from "@/api/system/organization";
import {myUserVerify} from "@/api/system/userVerify";
import {User} from "@/api/system/user/model";
import { useShopInfo } from '@/hooks/useShopInfo';
import { useUser } from '@/hooks/useUser';
import {handleInviteRelation} from "@/utils/invite";
import MySearch from "./MySearch";
import './Header.scss';
const Header = (props: any) => {
// 使用新的hooks
const {
shopInfo,
loading: shopLoading,
getWebsiteName,
getWebsiteLogo
} = useShopInfo();
const {
user,
isLoggedIn,
loading: userLoading
} = useUser();
const [showBasic, setShowBasic] = useState(false)
const [statusBarHeight, setStatusBarHeight] = useState<number>()
const reload = async () => {
Taro.getSystemInfo({
success: (res) => {
setStatusBarHeight(res.statusBarHeight)
},
})
// 注意商店信息现在通过useShopInfo自动管理不需要手动获取
// 用户信息现在通过useUser自动管理不需要手动获取
// 如果需要获取openId可以在用户登录后处理
if (user && !user.openid) {
Taro.login({
success: (res) => {
getWxOpenId({code: res.code}).then(() => {
console.log('OpenId获取成功');
})
}
})
}
// 检查用户认证状态
if (user?.userId) {
// 获取组织信息
getOrganization({userId: user.userId}).then((data) => {
console.log('组织信息>>>', data)
}).catch(() => {
console.log('获取组织信息失败')
});
// 检查用户认证
myUserVerify({userId: user.userId}).then((data) => {
console.log('认证信息>>>', data)
}).catch(() => {
console.log('获取认证信息失败')
});
}
}
// 获取手机号授权
const handleGetPhoneNumber = ({detail}: {detail: {code?: string, encryptedData?: string, iv?: string}}) => {
const {code, encryptedData, iv} = detail
Taro.login({
success: function () {
if (code) {
Taro.request({
url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone',
method: 'POST',
data: {
code,
encryptedData,
iv,
notVerifyPhone: true,
refereeId: 0,
sceneType: 'save_referee',
tenantId: TenantId
},
success: async function (res) {
if (res.data.code == 1) {
Taro.showToast({
title: res.data.message,
icon: 'error',
duration: 2000
})
return false;
}
// 登录成功
Taro.setStorageSync('access_token', res.data.data.access_token)
Taro.setStorageSync('UserId', res.data.data.user.userId)
// 处理邀请关系
if (res.data.data.user?.userId) {
try {
const inviteSuccess = await handleInviteRelation(res.data.data.user.userId)
if (inviteSuccess) {
Taro.showToast({
title: '邀请关系建立成功',
icon: 'success',
duration: 2000
})
}
} catch (error) {
console.error('处理邀请关系失败:', error)
}
}
// 重新加载小程序
Taro.reLaunch({
url: '/pages/index/index'
})
}
})
} else {
console.log('登录失败!')
}
}
})
}
useEffect(() => {
reload().then()
}, [])
// 显示加载状态
if (shopLoading || userLoading) {
return (
<div className={'fixed top-0 header-bg'} style={{
height: !props.stickyStatus ? '180px' : '148px',
}}>
<div style={{padding: '20px', textAlign: 'center', color: '#fff'}}>
...
</div>
</div>
);
}
return (
<>
<div className={'fixed top-0 header-bg'} style={{
height: !props.stickyStatus ? '180px' : '148px',
}}>
<MySearch/>
</div>
<NavBar
style={{marginTop: `${statusBarHeight}px`, marginBottom: '0px', backgroundColor: 'transparent'}}
onBackClick={() => {
}}
left={
!isLoggedIn ? (
<div style={{display: 'flex', alignItems: 'center'}}>
<Button style={{color: '#000'}} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
<Space>
<Avatar
size="22"
src={getWebsiteLogo()}
/>
<span style={{color: '#000'}}>{getWebsiteName()}</span>
</Space>
</Button>
<TriangleDown size={9}/>
</div>
) : (
<div style={{display: 'flex', alignItems: 'center', gap: '8px'}}>
<Avatar
size="22"
src={getWebsiteLogo()}
/>
<span className={'text-white'}>{getWebsiteName()}</span>
<TriangleDown className={'text-white'} size={9}/>
</div>
)}>
</NavBar>
<Popup
visible={showBasic}
position="bottom"
style={{width: '100%', height: '100%'}}
onClose={() => {
setShowBasic(false)
}}
>
<div style={{padding: '20px'}}>
<h3></h3>
<div>: {getWebsiteName()}</div>
<div>Logo: <img src={getWebsiteLogo()} alt="logo" style={{width: '50px', height: '50px'}} /></div>
<h3></h3>
<div>: {isLoggedIn ? '已登录' : '未登录'}</div>
{user && (
<>
<div>ID: {user.userId}</div>
<div>: {user.phone}</div>
<div>: {user.nickname}</div>
</>
)}
<button
onClick={() => setShowBasic(false)}
style={{marginTop: '20px', padding: '10px 20px'}}
>
</button>
</div>
</Popup>
</>
)
}
export default Header;

68
src/pages/index/Help.tsx Normal file
View File

@@ -0,0 +1,68 @@
import {useEffect, useState} from "react";
import {ArrowRight} from '@nutui/icons-react-taro'
import {CmsArticle} from "@/api/cms/cmsArticle/model";
import Taro from '@tarojs/taro'
import {useRouter} from '@tarojs/taro'
import {BaseUrl} from "@/config/app";
import {TEMPLATE_ID} from "@/utils/server";
/**
* 帮助中心
* @constructor
*/
const Help = () => {
const {params} = useRouter();
const [categoryId, setCategoryId] = useState<number>(3494)
const [list, setList] = useState<CmsArticle[]>([])
const reload = () => {
if (params.id) {
setCategoryId(Number(params.id))
}
Taro.request({
url: BaseUrl + '/cms/cms-article/page',
method: 'GET',
data: {
categoryId
},
header: {
'content-type': 'application/json',
TenantId: TEMPLATE_ID
},
success: function (res) {
const data = res.data.data;
if (data?.list) {
setList(data?.list)
}
}
})
}
useEffect(() => {
reload()
}, [])
return (
<div className={'px-3 mb-10'}>
<div className={'flex flex-col justify-between items-center bg-white rounded-lg p-4'}>
<div className={'title-bar flex justify-between items-center w-full mb-2'}>
<div className={'font-bold text-lg flex text-gray-800 justify-center items-center'}></div>
<a className={'text-gray-400 text-sm'} onClick={() => Taro.navigateTo({url: `/cms/article?id=${categoryId}`})}></a>
</div>
<div className={'bg-white min-h-36 w-full'}>
{
list.map((item, index) => {
return (
<div key={index} className={'flex justify-between items-center py-2'} onClick={() => Taro.navigateTo({url: `/cms/help?id=${item.articleId}`}) }>
<div className={'text-sm'}>{item.title}</div>
<ArrowRight color={'#cccccc'} size={18} />
</div>
)
})
}
</div>
</div>
</div>
)
}
export default Help

View File

@@ -0,0 +1,189 @@
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;

151
src/pages/index/Login.tsx Normal file
View File

@@ -0,0 +1,151 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro'
import {Input, Radio, Button} from '@nutui/nutui-react-taro'
import {TenantId} from "@/config/app";
import './login.scss';
import {saveStorageByLoginUser} from "@/utils/server";
import {handleInviteRelation} from "@/utils/invite";
// 微信获取手机号回调参数类型
interface GetPhoneNumberDetail {
code?: string;
encryptedData?: string;
iv?: string;
errMsg: string;
}
interface GetPhoneNumberEvent {
detail: GetPhoneNumberDetail;
}
interface LoginProps {
done?: (user: any) => void;
[key: string]: any;
}
// 登录接口返回数据类型
interface LoginResponse {
data: {
data: {
access_token: string;
user: any;
};
};
}
const Login = (props: LoginProps) => {
const [isAgree, setIsAgree] = useState(false)
const [env, setEnv] = useState<string>()
/* 获取用户手机号 */
const handleGetPhoneNumber = ({detail}: GetPhoneNumberEvent) => {
const {code, encryptedData, iv} = detail
Taro.login({
success: function () {
if (code) {
Taro.request({
url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone',
method: 'POST',
data: {
code,
encryptedData,
iv,
notVerifyPhone: true,
refereeId: 0,
sceneType: 'save_referee',
tenantId: TenantId
},
header: {
'content-type': 'application/json',
TenantId
},
success: async function (res: LoginResponse) {
saveStorageByLoginUser(res.data.data.access_token, res.data.data.user)
// 处理邀请关系
if (res.data.data.user?.userId) {
try {
const inviteSuccess = await handleInviteRelation(res.data.data.user.userId)
if (inviteSuccess) {
Taro.showToast({
title: '邀请关系建立成功',
icon: 'success',
duration: 2000
})
}
} catch (error) {
console.error('处理邀请关系失败:', error)
}
}
props.done?.(res.data.data.user);
}
})
} else {
console.log('登录失败!')
}
}
})
}
const reload = () => {
Taro.hideTabBar()
setEnv(Taro.getEnv())
}
useEffect(() => {
reload()
}, [])
return (
<>
<div style={{height: '80vh'}} className={'flex flex-col justify-center px-5'}>
<div className={'text-3xl text-center py-5 font-normal mb-10 '}></div>
{
env === 'WEAPP' && (
<>
<div className={'flex flex-col w-full text-white rounded-full justify-between items-center my-2'} style={{ background: 'linear-gradient(to right, #7e22ce, #9333ea)'}}>
<Button open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
</Button>
</div>
</>
)
}
{
env === 'WEB' && (
<>
<div className={'flex flex-col justify-between items-center my-2'}>
<Input type="text" placeholder="手机号" maxLength={11}
style={{backgroundColor: '#ffffff', borderRadius: '8px'}}/>
</div>
<div className={'flex flex-col justify-between items-center my-2'}>
<Input type="password" placeholder="密码" style={{backgroundColor: '#ffffff', borderRadius: '8px'}}/>
</div>
<div className={'flex justify-between my-2 text-left px-1'}>
<a href={'#'} className={'text-blue-600 text-sm'}
onClick={() => Taro.navigateTo({url: '/passport/forget'})}></a>
<a href={'#'} className={'text-blue-600 text-sm'}
onClick={() => Taro.navigateTo({url: '/passport/setting'})}></a>
</div>
<div className={'flex justify-center my-5'}>
<Button type="info" size={'large'} className={'w-full rounded-lg p-2'} disabled={!isAgree}></Button>
</div>
<div className={'w-full bottom-20 my-2 flex justify-center text-sm items-center text-center'}>
<a href={''} onClick={() => Taro.navigateTo({url: '/passport/register'})}
className={'text-blue-600'}></a>
</div>
<div className={'my-2 flex fixed bottom-20 text-sm items-center px-1'}>
<Radio style={{color: '#333333'}} checked={isAgree} onClick={() => setIsAgree(!isAgree)}></Radio>
<span className={'text-gray-400'} onClick={() => setIsAgree(!isAgree)}></span><a
onClick={() => Taro.navigateTo({url: '/passport/agreement'})}
className={'text-blue-600'}></a>
</div>
</>
)
}
</div>
</>
)
}
export default Login

52
src/pages/index/Menu.tsx Normal file
View File

@@ -0,0 +1,52 @@
import {Image} from '@nutui/nutui-react-taro'
import {Loading} from '@nutui/nutui-react-taro'
import {goTo} from "@/utils/navigation"
import {useShopInfo} from "@/hooks/useShopInfo"
const Page = () => {
// 使用 useShopInfo hooks 获取导航数据
const {
loading: shopLoading,
error,
getNavigation
} = useShopInfo()
// 获取顶部导航菜单
const navigation = getNavigation()
const home = navigation.topNavs.find(item => item.model == 'index')
const navItems = navigation.topNavs.filter(item => item.parentId == home?.navigationId) || []
const onNav = (item: any) => {
if (item.path) {
return goTo(`${item.path}`)
}
}
// 处理错误状态
if (error) {
return (
<div className={'p-2 text-center text-red-500'}>
</div>
)
}
return (
shopLoading ? (<Loading></Loading>) :
<div className={'p-2 z-50 mt-1'}>
<div className={'flex justify-between pb-2 p-2 bg-white rounded-xl shadow-sm'}>
{
navItems.map((item, index) => (
<div key={index} className={'text-center'} onClick={() => onNav(item)}>
<div className={'flex flex-col justify-center items-center p-1'}>
<Image src={item.icon} height={36} width={36} lazyLoad={false}/>
<div className={'mt-1 text-gray-600'} style={{fontSize: '14px'}}>{item?.title}</div>
</div>
</div>
))
}
</div>
</div>
)
}
export default Page

View File

@@ -0,0 +1,67 @@
import {Search} from '@nutui/icons-react-taro'
import {Button, Input} from '@nutui/nutui-react-taro'
import {useState} from "react";
import Taro from '@tarojs/taro';
import { goTo } from '@/utils/navigation';
function MySearch() {
const [keywords, setKeywords] = useState<string>('')
const onKeywords = (keywords: string) => {
setKeywords(keywords)
}
const onQuery = () => {
if(!keywords.trim()){
Taro.showToast({
title: '请输入关键字',
icon: 'none'
});
return false;
}
// 跳转到搜索页面 - 使用新的导航工具,自动处理路径和参数
goTo('shop/search/index', { keywords: keywords.trim() });
}
// 点击搜索框跳转到搜索页面
const onInputFocus = () => {
goTo('shop/search/index');
}
return (
<div className={'z-50 left-0 w-full'}>
<div className={'px-2'}>
<div
style={{
display: 'flex',
alignItems: 'center',
background: '#ffffff',
padding: '0 5px',
borderRadius: '20px',
marginTop: '100px',
}}
>
<Search size={18} className={'ml-2 text-gray-400'}/>
<Input
placeholder="搜索商品"
value={keywords}
onChange={onKeywords}
onConfirm={onQuery}
onFocus={onInputFocus}
style={{ padding: '9px 8px'}}
/>
<div
className={'flex items-center'}
>
<Button type="success" style={{background: 'linear-gradient(to bottom, #1cd98a, #24ca94)'}} onClick={onQuery}>
</Button>
</div>
</div>
</div>
</div>
);
}
export default MySearch;

View File

@@ -0,0 +1,29 @@
import {useEffect, useState} from "react";
import {Input, Button} from '@nutui/nutui-react-taro'
import {copyText} from "@/utils/common";
import Taro from '@tarojs/taro'
const SiteUrl = (props: any) => {
const [siteUrl, setSiteUrl] = useState<string>('')
const reload = () => {
if(props.tenantId){
setSiteUrl(`https://${props.tenantId}.shoplnk.cn`)
}else {
setSiteUrl(`https://${Taro.getStorageSync('TenantId')}.shoplnk.cn`)
}
}
useEffect(() => {
reload()
}, [props])
return (
<div className={'px-3 mt-1 mb-4'}>
<div className={'flex justify-between items-center bg-gray-300 rounded-lg pr-2'}>
<Input type="text" value={siteUrl} disabled style={{backgroundColor: '#d1d5db', borderRadius: '8px'}}/>
<Button type={'info'} onClick={() => copyText(siteUrl)}></Button>
</div>
</div>
)
}
export default SiteUrl

View File

@@ -0,0 +1,38 @@
import { useRef, useEffect } from 'react'
import { View } from '@tarojs/components'
import { EChart } from "echarts-taro3-react";
import './index.scss'
export default function Index() {
const refBarChart = useRef<any>()
const defautOption = {
xAxis: {
type: "category",
data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
},
yAxis: {
type: "value",
},
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: "line",
showBackground: true,
backgroundStyle: {
color: "rgba(220, 220, 220, 0.8)",
},
},
],
};
useEffect(() => {
if(refBarChart.current) {
refBarChart.current?.refresh(defautOption);
}
})
return (
<View className='index'>
<EChart ref={refBarChart} canvasId='line-canvas' />
</View>
)
}

View File

@@ -0,0 +1,7 @@
.index {
width: 100vw;
height: 100vh;
background-color: #F3F3F3;
background-repeat: no-repeat;
background-size: 100%;
}

View File

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

View File

@@ -0,0 +1,20 @@
page {
//background: url('https://oss.wsdns.cn/20250621/33ca4ca532e647bc918a59d01f5d88a9.jpg?x-oss-process=image/resize,m_fixed,w_2000/quality,Q_90') no-repeat top center;
//background-size: 100%;
background: linear-gradient(to bottom, #e9fff2, #ffffff);
}
.buy-btn{
height: 70px;
background: linear-gradient(to bottom, #1cd98a, #24ca94);
border-radius: 100px;
color: #ffffff;
display: flex;
align-items: center;
justify-content: space-around;
.cart-icon{
background: linear-gradient(to bottom, #bbe094, #4ee265);
border-radius: 100px 0 0 100px;
height: 70px;
}
}

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

@@ -0,0 +1,146 @@
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 {getShopInfo} from "@/api/layout";
import {Sticky} from '@nutui/nutui-react-taro'
import Menu from "./Menu";
import Banner from "./Banner";
import {checkAndHandleInviteRelation, hasPendingInvite} from "@/utils/invite";
import './index.scss'
// import GoodsList from "./GoodsList";
function Home() {
// 吸顶状态
const [stickyStatus, setStickyStatus] = useState<boolean>(false)
useShareTimeline(() => {
return {
title: '网宿小店 - 网宿软件',
path: `/pages/index/index`
};
});
useShareAppMessage(() => {
return {
title: '网宿小店 - 网宿软件',
path: `/pages/index/index`,
success: function () {
console.log('分享成功');
},
fail: function () {
console.log('分享失败');
}
};
});
// const reloadMore = async () => {
// setPage(page + 1)
// }
const showAuthModal = () => {
Taro.showModal({
title: '授权提示',
content: '需要获取您的用户信息',
confirmText: '去授权',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
// 用户点击确认,打开授权设置页面
openSetting();
}
}
});
};
const openSetting = () => {
// Taro.openSetting调起客户端小程序设置界面返回用户设置的操作结果。设置界面只会出现小程序已经向用户请求过的权限。
Taro.openSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
// 用户授权成功,可以获取用户信息
reload();
} else {
// 用户拒绝授权,提示授权失败
Taro.showToast({
title: '授权失败',
icon: 'none'
});
}
}
});
};
const onSticky = (item: IArguments) => {
if(item){
setStickyStatus(!stickyStatus)
}
}
const reload = () => {
};
useEffect(() => {
// 获取站点信息
getShopInfo().then(() => {
})
// 检查是否有待处理的邀请关系
if (hasPendingInvite()) {
console.log('检测到待处理的邀请关系')
// 延迟处理,确保用户信息已加载
setTimeout(async () => {
try {
const success = await checkAndHandleInviteRelation()
if (success) {
console.log('首页邀请关系处理成功')
}
} catch (error) {
console.error('首页邀请关系处理失败:', error)
}
}, 2000)
}
// Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
Taro.getSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
// 用户已经授权过,可以直接获取用户信息
console.log('用户已经授权过,可以直接获取用户信息')
reload();
} else {
// 用户未授权,需要弹出授权窗口
console.log('用户未授权,需要弹出授权窗口')
showAuthModal();
}
}
});
// 获取用户信息
Taro.getUserInfo({
success: (res) => {
const avatar = res.userInfo.avatarUrl;
console.log(avatar, 'avatarUrl')
}
});
}, []);
return (
<>
<Sticky threshold={0} onChange={() => onSticky(arguments)}>
<Header stickyStatus={stickyStatus}/>
</Sticky>
<div className={'flex flex-col mt-12'}>
<Menu/>
<Banner/>
<BestSellers/>
{/*<GoodsList/>*/}
</div>
</>
)
}
export default Home

View File

@@ -0,0 +1,10 @@
// 微信授权按钮的特殊样式
button[open-type="getPhoneNumber"] {
width: 100%;
padding: 8px 0 !important;
height: 80px;
color: #ffffff !important;
margin: 0 !important;
border: none !important;
border-radius: 50px !important;
}

View File

@@ -0,0 +1,4 @@
// 订单页面样式
.order-page {
// 订单相关样式
}

View File

@@ -0,0 +1,100 @@
import {Cell} from '@nutui/nutui-react-taro'
import navTo from "@/utils/common";
import {View, Text} from '@tarojs/components'
import {ArrowRight, Reward, Setting} from '@nutui/icons-react-taro'
import {useUser} from '@/hooks/useUser'
import {useEffect} from "react";
import {useDealerUser} from "@/hooks/useDealerUser";
const UserCell = () => {
const {isSuperAdmin} = useUser();
const {dealerUser} = useDealerUser()
useEffect(() => {
}, [])
/**
* 管理中心
*/
if (isSuperAdmin()) {
return (
<>
<View className={'px-4'}>
<Cell
className="nutui-cell-clickable"
style={{
backgroundImage: 'linear-gradient(to right bottom, #e53e3e, #c53030)',
}}
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Setting className={'text-white '} size={16}/>
<Text style={{fontSize: '16px'}} className={'pl-3 text-white font-medium'}></Text>
</View>
}
extra={<ArrowRight color="#ffffff" size={18}/>}
onClick={() => navTo('/admin/index', true)}
/>
</View>
</>
)
}
/**
* 分销中心
*/
if (dealerUser) {
return (
<>
<View className={'px-4'}>
<Cell
className="nutui-cell-clickable"
style={{
backgroundImage: 'linear-gradient(to right bottom, #54a799, #177b73)',
}}
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Reward className={'text-orange-100 '} size={16}/>
<Text style={{fontSize: '16px'}}
className={'pl-3 text-orange-100 font-medium'}></Text>
{/*<Text className={'text-white opacity-80 pl-3'}>门店核销</Text>*/}
</View>
}
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/dealer/index', true)}
/>
</View>
</>
)
}
/**
* 普通用户
*/
return (
<>
<View className={'px-4'}>
<Cell
className="nutui-cell-clickable"
style={{
backgroundImage: 'linear-gradient(to right bottom, #54a799, #177b73)',
}}
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Reward className={'text-orange-100 '} size={16}/>
<Text style={{fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}></Text>
</View>
}
extra={
<>
<Text className={'text-white opacity-80 px-3'}></Text>
<ArrowRight color="#cccccc" size={18}/>
</>
}
onClick={() => navTo('/dealer/apply/add', true)}
/>
</View>
</>
)
}
export default UserCell

View File

@@ -0,0 +1,267 @@
import {Button} from '@nutui/nutui-react-taro'
import {Avatar, Tag} from '@nutui/nutui-react-taro'
import {View, Text} from '@tarojs/components'
import {Scan} from '@nutui/icons-react-taro';
import {getUserInfo, getWxOpenId} from '@/api/layout';
import Taro from '@tarojs/taro';
import {useEffect, useState} from "react";
import {User} from "@/api/system/user/model";
import navTo from "@/utils/common";
import {TenantId} from "@/config/app";
import {getMyAvailableCoupons} from "@/api/shop/shopUserCoupon";
import {useUser} from "@/hooks/useUser";
import {useUserData} from "@/hooks/useUserData";
function UserCard() {
const {
isAdmin
} = useUser();
const { data, refresh } = useUserData()
const {getDisplayName, getRoleName} = useUser();
const [IsLogin, setIsLogin] = useState<boolean>(false)
const [userInfo, setUserInfo] = useState<User>()
const [couponCount, setCouponCount] = useState(0)
const [pointsCount, setPointsCount] = useState(0)
const [giftCount, setGiftCount] = useState(0)
// 下拉刷新
const handleRefresh = async () => {
await refresh()
Taro.showToast({
title: '刷新成功',
icon: 'success'
})
}
useEffect(() => {
// Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
Taro.getSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
// 用户已经授权过,可以直接获取用户信息
console.log('用户已经授权过,可以直接获取用户信息')
reload();
} else {
// 用户未授权,需要弹出授权窗口
console.log('用户未授权,需要弹出授权窗口')
showAuthModal();
}
}
});
}, []);
const loadUserStats = (userId: number) => {
// 加载优惠券数量
getMyAvailableCoupons()
.then((coupons: any) => {
setCouponCount(coupons?.length || 0)
})
.catch((error: any) => {
console.error('Coupon count error:', error)
})
// 加载积分数量
console.log(userId)
setPointsCount(0)
// getUserPointsStats(userId)
// .then((res: any) => {
// setPointsCount(res.currentPoints || 0)
// })
// .catch((error: any) => {
// console.error('Points stats error:', error)
// })
// 加载礼品劵数量
setGiftCount(0)
// pageUserGiftLog({userId, page: 1, limit: 1}).then(res => {
// setGiftCount(res.count || 0)
// })
}
const reload = () => {
Taro.getUserInfo({
success: (res) => {
const avatar = res.userInfo.avatarUrl;
setUserInfo({
avatar,
nickname: res.userInfo.nickName,
sexName: res.userInfo.gender == 1 ? '男' : '女'
})
getUserInfo().then((data) => {
if (data) {
setUserInfo(data)
setIsLogin(true);
Taro.setStorageSync('UserId', data.userId)
// 加载用户统计数据
if (data.userId) {
loadUserStats(data.userId)
}
// 获取openId
if (!data.openid) {
Taro.login({
success: (res) => {
getWxOpenId({code: res.code}).then(() => {
})
}
})
}
}
}).catch(() => {
console.log('未登录')
});
}
});
};
const showAuthModal = () => {
Taro.showModal({
title: '授权提示',
content: '需要获取您的用户信息',
confirmText: '去授权',
cancelText: '取消',
success: (res) => {
if (res.confirm) {
// 用户点击确认,打开授权设置页面
openSetting();
}
}
});
};
const openSetting = () => {
// Taro.openSetting调起客户端小程序设置界面返回用户设置的操作结果。设置界面只会出现小程序已经向用户请求过的权限。
Taro.openSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
// 用户授权成功,可以获取用户信息
reload();
} else {
// 用户拒绝授权,提示授权失败
Taro.showToast({
title: '授权失败',
icon: 'none'
});
}
}
});
};
/* 获取用户手机号 */
const handleGetPhoneNumber = ({detail}: {detail: {code?: string, encryptedData?: string, iv?: string}}) => {
const {code, encryptedData, iv} = detail
Taro.login({
success: function () {
if (code) {
Taro.request({
url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone',
method: 'POST',
data: {
code,
encryptedData,
iv,
notVerifyPhone: true,
refereeId: 0,
sceneType: 'save_referee',
tenantId: TenantId
},
header: {
'content-type': 'application/json',
TenantId
},
success: function (res) {
if (res.data.code == 1) {
Taro.showToast({
title: res.data.message,
icon: 'error',
duration: 2000
})
return false;
}
// 登录成功
Taro.setStorageSync('access_token', res.data.data.access_token)
Taro.setStorageSync('UserId', res.data.data.user.userId)
setUserInfo(res.data.data.user)
setIsLogin(true)
}
})
} else {
console.log('登录失败!')
}
}
})
}
return (
<View className={'header-bg pt-20'}>
<View className={'p-4'}>
<View
className={'user-card w-full flex flex-col justify-around rounded-xl shadow-sm'}
style={{
background: 'linear-gradient(to bottom, #ffffff, #ffffff)', // 这种情况建议使用类名来控制样式(引入外联样式)
// width: '720rpx',
// margin: '10px auto 0px auto',
height: '170px',
// borderRadius: '22px 22px 0 0',
}}
>
<View className={'user-card-header flex w-full justify-between items-center pt-4'}>
<View className={'flex items-center mx-4'}>
{
IsLogin ? (
<Avatar size="large" src={userInfo?.avatar} shape="round"/>
) : (
<Button className={'text-black'} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
<Avatar size="large" src={userInfo?.avatar} shape="round"/>
</Button>
)
}
<View className={'user-info flex flex-col px-2'}>
<View className={'py-1 text-black font-bold'}>{getDisplayName()}</View>
{IsLogin ? (
<View className={'grade text-xs py-1'}>
<Tag type="success" round>
<Text className={'p-1'}>
{getRoleName()}
</Text>
</Tag>
</View>
) : ''}
</View>
</View>
{isAdmin() && <Scan onClick={() => navTo('/user/store/verification', true)} />}
<View className={'mr-4 text-sm px-3 py-1 text-black border-gray-400 border-solid border-2 rounded-3xl'}
onClick={() => navTo('/user/profile/profile', true)}>
{'个人资料'}
</View>
</View>
<View className={'flex justify-around mt-1'}>
<View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/wallet/wallet', true)}>
<Text className={'text-sm text-gray-500'}></Text>
<Text className={'text-xl'}>{data?.balance || '0.00'}</Text>
</View>
<View className={'item flex justify-center flex-col items-center'}>
<Text className={'text-sm text-gray-500'}></Text>
<Text className={'text-xl'}>{data?.points || 0}</Text>
</View>
<View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/coupon/index', true)}>
<Text className={'text-sm text-gray-500'}></Text>
<Text className={'text-xl'}>{data?.coupons || 0}</Text>
</View>
<View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/gift/index', true)}>
<Text className={'text-sm text-gray-500'}></Text>
<Text className={'text-xl'}>{data?.giftCards || 0}</Text>
</View>
</View>
</View>
</View>
</View>
)
}
export default UserCard;

View File

@@ -0,0 +1,144 @@
import {Cell} from '@nutui/nutui-react-taro'
import navTo from "@/utils/common";
import Taro from '@tarojs/taro'
import {View, Text} from '@tarojs/components'
import {ArrowRight, ShieldCheck, LogisticsError, Location, Tips, Ask} from '@nutui/icons-react-taro'
import {useUser} from '@/hooks/useUser'
const UserCell = () => {
const {logoutUser, isCertified} = useUser();
const onLogout = () => {
Taro.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: function (res) {
if (res.confirm) {
// 使用 useUser hook 的 logoutUser 方法
logoutUser();
Taro.reLaunch({
url: '/pages/index/index'
})
}
}
})
}
return (
<>
<View className={'px-4'}>
<Cell.Group divider={true} description={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Text style={{marginTop: '12px'}}></Text>
</View>
}>
<Cell
className="nutui-cell-clickable"
style={{
display: 'none'
}}
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<LogisticsError size={16}/>
<Text className={'pl-3 text-sm'}></Text>
</View>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/wallet/index', true)
}}
/>
<Cell
className="nutui-cell-clickable"
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Location size={16}/>
<Text className={'pl-3 text-sm'}></Text>
</View>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/address/index', true)
}}
/>
<Cell
className="nutui-cell-clickable"
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<ShieldCheck size={16} color={isCertified() ? '#52c41a' : '#666'}/>
<Text className={'pl-3 text-sm'}></Text>
{isCertified() && (
<Text className={'pl-2 text-xs text-green-500'}></Text>
)}
</View>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/userVerify/index', true)
}}
/>
<Cell
className="nutui-cell-clickable"
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Ask size={16}/>
<Text className={'pl-3 text-sm'}></Text>
</View>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/help/index')
}}
/>
<Cell
className="nutui-cell-clickable"
title={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Tips size={16}/>
<Text className={'pl-3 text-sm'}></Text>
</View>
}
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => {
navTo('/user/about/index')
}}
/>
</Cell.Group>
<Cell.Group divider={true} description={
<View style={{display: 'inline-flex', alignItems: 'center'}}>
<Text style={{marginTop: '12px'}}></Text>
</View>
}>
<Cell
className="nutui-cell-clickable"
title="账号安全"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/user/profile/profile', true)}
/>
<Cell
className="nutui-cell-clickable"
title="切换主题"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/user/theme/index', true)}
/>
<Cell
className="nutui-cell-clickable"
title="退出登录"
align="center"
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={onLogout}
/>
</Cell.Group>
</View>
</>
)
}
export default UserCell

View File

@@ -0,0 +1,102 @@
import {loginBySms} from "@/api/passport/login";
import {useState} from "react";
import Taro from '@tarojs/taro'
import {Popup} from '@nutui/nutui-react-taro'
import {UserParam} from "@/api/system/user/model";
import {Button} from '@nutui/nutui-react-taro'
import {Form, Input} from '@nutui/nutui-react-taro'
import {Copyright, Version} from "@/config/app";
const UserFooter = () => {
const [openLoginByPhone, setOpenLoginByPhone] = useState(false)
const [clickNum, setClickNum] = useState<number>(0)
const [FormData, setFormData] = useState<UserParam>(
{
phone: undefined,
password: undefined
}
)
const onLoginByPhone = () => {
setFormData({})
setClickNum(clickNum + 1);
if (clickNum > 10) {
setOpenLoginByPhone(true);
}
}
const closeLoginByPhone = () => {
setClickNum(0)
setOpenLoginByPhone(false)
}
// 提交表单
const submitByPhone = (values: any) => {
loginBySms({
phone: values.phone,
code: values.code
}).then(() => {
setOpenLoginByPhone(false);
setTimeout(() => {
Taro.reLaunch({
url: '/pages/index/index'
})
},1000)
})
}
return (
<>
<div className={'text-center py-4 w-full text-gray-300'} onClick={onLoginByPhone}>
<div className={'text-xs text-gray-400 py-1'}>{Version}</div>
<div className={'text-xs text-gray-400 py-1'}>Copyright © { new Date().getFullYear() } {Copyright}</div>
</div>
<Popup
style={{width: '350px', padding: '10px'}}
visible={openLoginByPhone}
closeOnOverlayClick={false}
closeable={true}
onClose={closeLoginByPhone}
>
<Form
style={{width: '350px',padding: '10px'}}
divider
initialValues={FormData}
labelPosition="left"
onFinish={(values) => submitByPhone(values)}
footer={
<div
style={{
display: 'flex',
justifyContent: 'center',
width: '100%'
}}
>
<Button nativeType="submit" block style={{backgroundColor: '#000000',color: '#ffffff'}}>
</Button>
</div>
}
>
<Form.Item
label={'手机号码'}
name="phone"
required
rules={[{message: '手机号码'}]}
>
<Input placeholder="请输入手机号码" maxLength={11} type="text"/>
</Form.Item>
<Form.Item
label={'短信验证码'}
name="code"
required
rules={[{message: '短信验证码'}]}
>
<Input placeholder="请输入短信验证码" maxLength={6} type="text"/>
</Form.Item>
</Form>
</Popup>
</>
)
}
export default UserFooter

View File

@@ -0,0 +1,122 @@
import navTo from "@/utils/common";
import {View, Text} from '@tarojs/components';
import {Badge} from '@nutui/nutui-react-taro';
import {ArrowRight, Wallet, Comment, Transit, Refund, Package} from '@nutui/icons-react-taro';
import {useOrderStats} from "@/hooks/useOrderStats";
function UserOrder() {
const { orderStats, refreshOrderStats } = useOrderStats();
// 处理长按刷新
const handleLongPress = () => {
refreshOrderStats();
};
return (
<>
<View className={'px-4 pb-2'}>
<View
className={'user-card w-full flex flex-col justify-around rounded-xl shadow-sm'}
style={{
background: 'linear-gradient(to bottom, #ffffff, #ffffff)', // 这种情况建议使用类名来控制样式(引入外联样式)
// margin: '10px auto 0px auto',
height: '120px',
// paddingBottom: '3px'
// borderRadius: '22px 22px 0 0',
}}
>
<View className={'title-bar flex justify-between pt-2'}>
<Text className={'title font-medium px-4'}></Text>
<View
className={'more flex items-center px-2'}
onClick={() => navTo('/user/order/order', true)}
onLongPress={handleLongPress}
>
<Text className={'text-xs text-gray-500'}></Text>
<ArrowRight color="#cccccc" size={12}/>
</View>
</View>
<View className={'flex justify-around pb-1 mt-4'}>
{/* 待付款 */}
{orderStats.pending > 0 ? (
<Badge value={orderStats.pending} max={99} fill={'outline'}>
<View className={'item flex justify-center flex-col items-center'}>
<Wallet size={26} className={'font-normal text-gray-500'}
onClick={() => navTo('/user/order/order?statusFilter=0', true)}/>
<Text className={'text-sm text-gray-600 py-1'}></Text>
</View>
</Badge>
) : (
<View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/order/order?statusFilter=0', true)}>
<Wallet size={26} className={'font-normal text-gray-500'}/>
<Text className={'text-sm text-gray-600 py-1'}></Text>
</View>
)}
{/* 待发货 */}
{orderStats.paid > 0 ? (
<Badge value={orderStats.paid} max={99} fill={'outline'}>
<View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/order/order?statusFilter=1', true)}>
<Package size={26} className={'text-gray-500 font-normal'}/>
<Text className={'text-sm text-gray-600 py-1'}></Text>
</View>
</Badge>
) : (
<View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/order/order?statusFilter=1', true)}>
<Package size={26} className={'text-gray-500 font-normal'}/>
<Text className={'text-sm text-gray-600 py-1'}></Text>
</View>
)}
{/* 待收货 */}
{orderStats.shipped > 0 ? (
<Badge value={orderStats.shipped} max={99} fill={'outline'}>
<View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/order/order?statusFilter=3', true)}>
<Transit size={24} className={'text-gray-500 font-normal'}/>
<Text className={'text-sm text-gray-600 py-1'}></Text>
</View>
</Badge>
) : (
<View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/order/order?statusFilter=3', true)}>
<Transit size={24} className={'text-gray-500 font-normal'}/>
<Text className={'text-sm text-gray-600 py-1'}></Text>
</View>
)}
{/* 已完成 - 不显示badge */}
<View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/order/order?statusFilter=5', true)}>
<Comment size={24} className={'text-gray-500 font-normal'}/>
<Text className={'text-sm text-gray-600 py-1'}></Text>
</View>
{/* 退货/售后 */}
{orderStats.refund > 0 ? (
<Badge value={orderStats.refund} max={99} fill={'outline'}>
<View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/order/order?statusFilter=6', true)}>
<Refund size={26} className={'font-normal text-gray-500'}/>
<Text className={'text-sm text-gray-600 py-1'}>退/</Text>
</View>
</Badge>
) : (
<View className={'item flex justify-center flex-col items-center'}
onClick={() => navTo('/user/order/order?statusFilter=6', true)}>
<Refund size={26} className={'font-normal text-gray-500'}/>
<Text className={'text-sm text-gray-600 py-1'}>退/</Text>
</View>
)}
</View>
</View>
</View>
</>
)
}
export default UserOrder;

View File

@@ -0,0 +1,5 @@
export default definePageConfig({
navigationBarTitleText: '我的',
navigationStyle: 'custom',
navigationBarBackgroundColor: '#e9fff2'
})

4
src/pages/user/user.scss Normal file
View File

@@ -0,0 +1,4 @@
.header-bg{
background: url('https://oss.wsdns.cn/20250621/edb5d4da976b4d97ba185cb7077d2858.jpg') no-repeat top center;
background-size: 100%;
}

50
src/pages/user/user.tsx Normal file
View File

@@ -0,0 +1,50 @@
import {useEffect} from 'react'
import UserCard from "./components/UserCard";
import UserOrder from "./components/UserOrder";
import UserCell from "./components/UserCell";
import UserFooter from "./components/UserFooter";
import {useUser} from "@/hooks/useUser";
import './user.scss'
import IsDealer from "./components/IsDealer";
function User() {
const {
isAdmin
} = useUser();
useEffect(() => {
}, []);
/**
* 门店核销管理
*/
if (isAdmin()) {
return <>
<div className={'w-full'} style={{
background: 'linear-gradient(to bottom, #e9fff2, #f9fafb)'
}}>
<UserCard/>
<UserOrder/>
<IsDealer/>
<UserCell/>
<UserFooter/>
</div>
</>
}
return (
<>
<div className={'w-full'} style={{
background: 'linear-gradient(to bottom, #e9fff2, #f9fafb)'
}}>
<UserCard/>
<UserOrder/>
<IsDealer/>
<UserCell/>
<UserFooter/>
</div>
</>
)
}
export default User