```
feat(registration): 优化经销商注册流程并增加地址定位功能 - 修改导航栏标题从“邀请注册”为“注册成为会员” - 修复重复提交问题并移除不必要的submitting状态 - 增加昵称和头像的必填验证提示 - 添加用户角色缺失时的默认角色写入机制 - 集成地图选点功能,支持经纬度获取和地址解析 - 实现微信地址导入功能,自动填充基本信息 - 增加定位权限检查和错误处理机制 - 添加.gitignore规则忽略备份文件夹src__bak - 移除已废弃的银行卡和客户管理页面代码 - 优化表单验证规则和错误提示信息 - 实现经销商注册成功后自动跳转到“我的”页面 - 添加用户信息缓存刷新机制确保角色信息同步 ```
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import Taro, {useShareAppMessage, useShareTimeline, useDidShow} from '@tarojs/taro';
|
||||
import Taro, {useShareAppMessage, useDidShow} from '@tarojs/taro';
|
||||
import {
|
||||
NavBar,
|
||||
Checkbox,
|
||||
@@ -10,10 +10,11 @@ import {
|
||||
Divider,
|
||||
ConfigProvider
|
||||
} from '@nutui/nutui-react-taro';
|
||||
import {ArrowLeft, Del} from '@nutui/icons-react-taro';
|
||||
import {Del} from '@nutui/icons-react-taro';
|
||||
import {View} from '@tarojs/components';
|
||||
import {CartItem, useCart} from "@/hooks/useCart";
|
||||
import './cart.scss';
|
||||
import { ensureLoggedIn } from '@/utils/auth'
|
||||
|
||||
function Cart() {
|
||||
const [statusBarHeight, setStatusBarHeight] = useState<number>(0);
|
||||
@@ -39,12 +40,6 @@ function Cart() {
|
||||
nutuiInputnumberButtonBorderRadius: '4px',
|
||||
}
|
||||
|
||||
useShareTimeline(() => {
|
||||
return {
|
||||
title: '购物车 - 易赊宝'
|
||||
};
|
||||
});
|
||||
|
||||
useShareAppMessage(() => {
|
||||
return {
|
||||
title: '购物车 - 易赊宝',
|
||||
@@ -156,6 +151,9 @@ function Cart() {
|
||||
// 将选中的商品信息存储到本地,供结算页面使用
|
||||
Taro.setStorageSync('checkout_items', JSON.stringify(selectedCartItems));
|
||||
|
||||
// 未登录则引导去注册/登录;登录后回到购物车结算页
|
||||
if (!ensureLoggedIn('/shop/orderConfirmCart/index')) return
|
||||
|
||||
// 跳转到购物车结算页面
|
||||
Taro.navigateTo({
|
||||
url: '/shop/orderConfirmCart/index'
|
||||
@@ -177,7 +175,6 @@ function Cart() {
|
||||
<NavBar
|
||||
fixed={true}
|
||||
style={{marginTop: `${statusBarHeight}px`}}
|
||||
left={<ArrowLeft onClick={() => Taro.navigateBack()}/>}
|
||||
right={
|
||||
cartItems.length > 0 && (
|
||||
<Button
|
||||
@@ -233,7 +230,6 @@ function Cart() {
|
||||
<NavBar
|
||||
fixed={true}
|
||||
style={{marginTop: `${statusBarHeight}px`}}
|
||||
left={<ArrowLeft onClick={() => Taro.navigateBack()}/>}
|
||||
right={
|
||||
cartItems.length > 0 && (
|
||||
<Button
|
||||
|
||||
35
src/pages/category/components/ArticleList.tsx
Normal file
35
src/pages/category/components/ArticleList.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import {Image, Cell} from '@nutui/nutui-react-taro'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
const ArticleList = (props: any) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className={'px-3'}>
|
||||
{props.data.map((item: any, index: number) => {
|
||||
return (
|
||||
<Cell
|
||||
title={
|
||||
<View>
|
||||
<View className="text-base font-medium mb-1">{item.title}</View>
|
||||
{item.comments && (
|
||||
<Text className="text-sm text-gray-500 leading-relaxed">
|
||||
{item.comments}
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
}
|
||||
extra={
|
||||
<Image src={item.image} mode={'aspectFit'} lazyLoad={false} width={100} height="100"/>
|
||||
}
|
||||
key={index}
|
||||
onClick={() => Taro.navigateTo({url: '/cms/detail/index?id=' + item.articleId})}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default ArticleList
|
||||
59
src/pages/category/components/ArticleTabs.tsx
Normal file
59
src/pages/category/components/ArticleTabs.tsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import {useEffect, useState} from "react";
|
||||
import {Tabs, Loading} from '@nutui/nutui-react-taro'
|
||||
import {pageCmsArticle} from "@/api/cms/cmsArticle";
|
||||
import {CmsArticle} from "@/api/cms/cmsArticle/model";
|
||||
import ArticleList from "./ArticleList";
|
||||
|
||||
const ArticleTabs = (props: any) => {
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [tab1value, setTab1value] = useState<string | number>('0')
|
||||
const [list, setList] = useState<CmsArticle[]>([])
|
||||
|
||||
const reload = async (value) => {
|
||||
const {data} = props
|
||||
pageCmsArticle({
|
||||
categoryId: data[value].navigationId,
|
||||
page: 1,
|
||||
status: 0,
|
||||
limit: 10
|
||||
}).then((res) => {
|
||||
res && setList(res?.list || [])
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err)
|
||||
})
|
||||
.finally(() => {
|
||||
setTab1value(value)
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
reload(0).then()
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<Loading className={'px-2'}>加载中</Loading>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Tabs
|
||||
value={tab1value}
|
||||
onChange={(value) => {
|
||||
reload(value).then()
|
||||
}}
|
||||
>
|
||||
{props.data?.map((item, index) => {
|
||||
return (
|
||||
<Tabs.TabPane title={item.categoryName} key={index}/>
|
||||
)
|
||||
})}
|
||||
</Tabs>
|
||||
<ArticleList data={list}/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default ArticleTabs
|
||||
31
src/pages/category/components/Banner.tsx
Normal file
31
src/pages/category/components/Banner.tsx
Normal 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
|
||||
4
src/pages/category/index.config.ts
Normal file
4
src/pages/category/index.config.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '文章列表',
|
||||
navigationBarTextStyle: 'black'
|
||||
})
|
||||
0
src/pages/category/index.scss
Normal file
0
src/pages/category/index.scss
Normal file
96
src/pages/category/index.tsx
Normal file
96
src/pages/category/index.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import Taro from '@tarojs/taro'
|
||||
import {useShareAppMessage} from "@tarojs/taro"
|
||||
import {useEffect, useState} from "react"
|
||||
import {useRouter} from '@tarojs/taro'
|
||||
import {View} from '@tarojs/components'
|
||||
import {getCmsNavigation, listCmsNavigation} from "@/api/cms/cmsNavigation";
|
||||
import {CmsNavigation} from "@/api/cms/cmsNavigation/model";
|
||||
import {pageCmsArticle} from "@/api/cms/cmsArticle";
|
||||
import {CmsArticle} from "@/api/cms/cmsArticle/model";
|
||||
import ArticleList from './components/ArticleList'
|
||||
import ArticleTabs from "./components/ArticleTabs";
|
||||
import './index.scss'
|
||||
|
||||
function Category() {
|
||||
const {params} = useRouter();
|
||||
const [categoryId, setCategoryId] = useState<number>(0)
|
||||
const [category, setCategory] = useState<CmsNavigation[]>([])
|
||||
const [loading, setLoading] = useState<boolean>(true)
|
||||
const [nav, setNav] = useState<CmsNavigation>()
|
||||
const [list, setList] = useState<CmsArticle[]>([])
|
||||
|
||||
const reload = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
// 1.加载远程数据
|
||||
const id = Number(params.id || 4328)
|
||||
const nav = await getCmsNavigation(id)
|
||||
const categoryList = await listCmsNavigation({parentId: id})
|
||||
const shopGoods = await pageCmsArticle({categoryId: id})
|
||||
|
||||
// 2.赋值
|
||||
setCategoryId(id)
|
||||
setNav(nav)
|
||||
setList(shopGoods?.list || [])
|
||||
setCategory(categoryList)
|
||||
Taro.setNavigationBarTitle({
|
||||
title: `${nav?.categoryName}`
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('文章分类加载失败:', error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
reload()
|
||||
}, []);
|
||||
|
||||
useShareAppMessage(() => {
|
||||
return {
|
||||
title: `${nav?.categoryName}_易赊宝`,
|
||||
path: `/shop/category/index?id=${categoryId}`,
|
||||
success: function () {
|
||||
console.log('分享成功');
|
||||
},
|
||||
fail: function () {
|
||||
console.log('分享失败');
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// 骨架屏组件
|
||||
const ArticleSkeleton = () => (
|
||||
<View className="px-3">
|
||||
{[1, 2, 3, 4, 5].map(i => (
|
||||
<View key={i} className="flex items-center py-4 border-b border-gray-100">
|
||||
{/* 左侧文字骨架屏 */}
|
||||
<View className="flex-1 pr-4">
|
||||
{/* 标题骨架屏 */}
|
||||
<View className="bg-gray-200 h-5 rounded animate-pulse mb-2" style={{width: '75%'}}/>
|
||||
{/* 副标题骨架屏 */}
|
||||
<View className="bg-gray-200 h-4 rounded animate-pulse" style={{width: '50%'}}/>
|
||||
</View>
|
||||
{/* 右侧图片骨架屏 */}
|
||||
<View
|
||||
className="bg-gray-200 rounded animate-pulse flex-shrink-0"
|
||||
style={{width: '100px', height: '100px'}}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
)
|
||||
|
||||
if (loading) {
|
||||
return <ArticleSkeleton />
|
||||
}
|
||||
|
||||
if(category.length > 0){
|
||||
return <ArticleTabs data={category}/>
|
||||
}
|
||||
|
||||
return <ArticleList data={list}/>
|
||||
}
|
||||
|
||||
export default Category
|
||||
@@ -1,5 +1,4 @@
|
||||
export default definePageConfig({
|
||||
navigationBarTitleText: '网点',
|
||||
navigationBarTextStyle: 'black',
|
||||
navigationBarBackgroundColor: '#ffffff'
|
||||
navigationBarTitleText: '发现',
|
||||
navigationStyle: 'custom',
|
||||
})
|
||||
|
||||
@@ -1,144 +1,4 @@
|
||||
page {
|
||||
background: #f7f7f7;
|
||||
}
|
||||
|
||||
.sitePage {
|
||||
min-height: 100vh;
|
||||
background: linear-gradient(180deg, #fde8ea 0%, #f7f7f7 320rpx, #f7f7f7 100%);
|
||||
padding-bottom: calc(40rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.searchArea {
|
||||
padding: 22rpx 24rpx 18rpx;
|
||||
}
|
||||
|
||||
.searchBox {
|
||||
height: 86rpx;
|
||||
background: #fff;
|
||||
border: 2rpx solid #b51616;
|
||||
border-radius: 12rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.searchInput {
|
||||
flex: 1;
|
||||
height: 86rpx;
|
||||
padding: 0 20rpx;
|
||||
font-size: 30rpx;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.searchPlaceholder {
|
||||
color: #9e9e9e;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.searchIconWrap {
|
||||
width: 88rpx;
|
||||
height: 86rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.siteList {
|
||||
padding: 0 24rpx 24rpx;
|
||||
}
|
||||
|
||||
.siteCard {
|
||||
background: #fff;
|
||||
border-radius: 18rpx;
|
||||
padding: 22rpx 22rpx 18rpx;
|
||||
margin-top: 18rpx;
|
||||
box-shadow: 0 10rpx 24rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.siteCardInner {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.siteInfo {
|
||||
flex: 1;
|
||||
padding-right: 10rpx;
|
||||
}
|
||||
|
||||
.siteRow {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
padding: 10rpx 0;
|
||||
}
|
||||
|
||||
.siteRowTop {
|
||||
padding-top: 2rpx;
|
||||
padding-bottom: 14rpx;
|
||||
}
|
||||
|
||||
.siteLabel {
|
||||
width: 170rpx;
|
||||
flex: 0 0 170rpx;
|
||||
color: #9a9a9a;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.siteValue {
|
||||
flex: 1;
|
||||
color: #222;
|
||||
font-size: 30rpx;
|
||||
line-height: 1.6;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.siteValueStrong {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.siteDivider {
|
||||
height: 2rpx;
|
||||
background: #ededed;
|
||||
}
|
||||
|
||||
.siteSide {
|
||||
width: 160rpx;
|
||||
flex: 0 0 160rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding-left: 12rpx;
|
||||
}
|
||||
|
||||
.navArrow {
|
||||
width: 34rpx;
|
||||
height: 34rpx;
|
||||
border-top: 8rpx solid #e60012;
|
||||
border-right: 8rpx solid #e60012;
|
||||
border-radius: 4rpx;
|
||||
transform: rotate(45deg);
|
||||
margin-right: 8rpx;
|
||||
}
|
||||
|
||||
.distanceText {
|
||||
margin-top: 18rpx;
|
||||
font-size: 28rpx;
|
||||
color: #e60012;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.emptyWrap {
|
||||
padding: 40rpx 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.emptyText {
|
||||
font-size: 28rpx;
|
||||
color: #9a9a9a;
|
||||
}
|
||||
|
||||
.bottomSafe {
|
||||
height: 20rpx;
|
||||
background: linear-gradient(to bottom, #e9fff2, #f9fafb);
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
@@ -1,130 +1,82 @@
|
||||
import {useMemo, useState} from 'react'
|
||||
import Taro from '@tarojs/taro'
|
||||
import {Input, Text, View} from '@tarojs/components'
|
||||
import {Search} from '@nutui/icons-react-taro'
|
||||
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";
|
||||
|
||||
type SiteItem = {
|
||||
id: string
|
||||
cityName: string
|
||||
address: string
|
||||
phone: string
|
||||
contact: string
|
||||
distanceMeter: number
|
||||
}
|
||||
|
||||
const MOCK_SITES: SiteItem[] = [
|
||||
{
|
||||
id: '1',
|
||||
cityName: '北京朝阳区网点',
|
||||
address: '地安门西大街(南门)',
|
||||
phone: '15878179339',
|
||||
contact: '刘先生',
|
||||
distanceMeter: 100
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
cityName: '兰州某某区网点',
|
||||
address: '地安门西大街(南门)',
|
||||
phone: '15878179339',
|
||||
contact: '黄先生',
|
||||
distanceMeter: 150
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
cityName: '合肥市某某区网点',
|
||||
address: '地安门西大街(南门)',
|
||||
phone: '15878179339',
|
||||
contact: '黄先生',
|
||||
distanceMeter: 250
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
cityName: '南宁市某某区网点',
|
||||
address: '广西壮族自治区南宁市良庆区五象新区五象大道403号富雅国际金融中心G1栋高层6006',
|
||||
phone: '15878179339',
|
||||
contact: '柳先生',
|
||||
distanceMeter: 1250
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* 文章终极列表
|
||||
* @constructor
|
||||
*/
|
||||
const Find = () => {
|
||||
const [keyword, setKeyword] = useState<string>('')
|
||||
const [statusBarHeight, setStatusBarHeight] = useState<number>()
|
||||
const [loading, setLoading] = useState<boolean>(false)
|
||||
const [list, setList] = useState<CmsArticle[]>()
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
const key = keyword.trim()
|
||||
if (!key) return MOCK_SITES
|
||||
return MOCK_SITES.filter((it) => it.cityName.includes(key))
|
||||
}, [keyword])
|
||||
|
||||
const onNavigate = (item: SiteItem) => {
|
||||
Taro.showToast({title: `导航至:${item.cityName}(示例)`, icon: 'none'})
|
||||
const reload = async () => {
|
||||
setLoading(true)
|
||||
const article = await pageCmsArticle({categoryId: 4289, status: 0})
|
||||
if (article) {
|
||||
setList(article?.list)
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
const onSearch = () => {
|
||||
Taro.showToast({title: '查询(示例)', icon: 'none'})
|
||||
}
|
||||
useEffect(() => {
|
||||
Taro.getSystemInfo({
|
||||
success: (res) => {
|
||||
setStatusBarHeight(res.statusBarHeight)
|
||||
},
|
||||
})
|
||||
reload().then(() => {
|
||||
console.log('初始化完成')
|
||||
})
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<View className='sitePage'>
|
||||
<View className='searchArea'>
|
||||
<View className='searchBox'>
|
||||
<Input
|
||||
className='searchInput'
|
||||
value={keyword}
|
||||
placeholder='请输入城市名称查询'
|
||||
placeholderClass='searchPlaceholder'
|
||||
confirmType='search'
|
||||
onInput={(e) => setKeyword(e.detail.value)}
|
||||
onConfirm={onSearch}
|
||||
<>
|
||||
{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)
|
||||
}}
|
||||
/>
|
||||
<View className='searchIconWrap' onClick={onSearch}>
|
||||
<Search size={18} color='#b51616' />
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='siteList'>
|
||||
{filtered.map((item) => (
|
||||
<View key={item.id} className='siteCard'>
|
||||
<View className='siteCardInner'>
|
||||
<View className='siteInfo'>
|
||||
<View className='siteRow siteRowTop'>
|
||||
<Text className='siteLabel'>城市名称:</Text>
|
||||
<Text className='siteValue siteValueStrong'>{item.cityName}</Text>
|
||||
</View>
|
||||
<View className='siteDivider' />
|
||||
<View className='siteRow'>
|
||||
<Text className='siteLabel'>网点地址:</Text>
|
||||
<Text className='siteValue'>{item.address}</Text>
|
||||
</View>
|
||||
<View className='siteRow'>
|
||||
<Text className='siteLabel'>联系电话:</Text>
|
||||
<Text className='siteValue'>{item.phone}</Text>
|
||||
</View>
|
||||
<View className='siteRow'>
|
||||
<Text className='siteLabel'>联系人:</Text>
|
||||
<Text className='siteValue'>{item.contact}</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
<View className='siteSide' onClick={() => onNavigate(item)}>
|
||||
<View className='navArrow' />
|
||||
<Text className='distanceText'>距离{item.distanceMeter}米</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
))}
|
||||
|
||||
{filtered.length === 0 && (
|
||||
<View className='emptyWrap'>
|
||||
<Text className='emptyText'>暂无网点</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<View className='bottomSafe' />
|
||||
</View>
|
||||
<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
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
// 订单页面样式
|
||||
.order-page {
|
||||
// 订单相关样式
|
||||
}
|
||||
@@ -3,16 +3,16 @@ 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";
|
||||
import {useThemeStyles} from "@/hooks/useTheme";
|
||||
import { useConfig } from "@/hooks/useConfig"; // 使用新的自定义Hook
|
||||
import Taro from '@tarojs/taro'
|
||||
|
||||
const UserCell = () => {
|
||||
const IsDealer = () => {
|
||||
const themeStyles = useThemeStyles();
|
||||
const { config } = useConfig(); // 使用新的Hook
|
||||
const {isSuperAdmin} = useUser();
|
||||
const {dealerUser} = useDealerUser()
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
}, [])
|
||||
const {dealerUser, loading: dealerLoading} = useDealerUser()
|
||||
|
||||
/**
|
||||
* 管理中心
|
||||
@@ -20,12 +20,10 @@ const UserCell = () => {
|
||||
if (isSuperAdmin()) {
|
||||
return (
|
||||
<>
|
||||
<View className={'px-4'}>
|
||||
<View className={'px-4'} style={{ marginTop: '8px', position: 'relative', zIndex: 25 }}>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(to right bottom, #e53e3e, #c53030)',
|
||||
}}
|
||||
style={themeStyles.primaryBackground}
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Setting className={'text-white '} size={16}/>
|
||||
@@ -46,17 +44,15 @@ const UserCell = () => {
|
||||
if (dealerUser) {
|
||||
return (
|
||||
<>
|
||||
<View className={'px-4'}>
|
||||
<View className={'px-4'} style={{ marginTop: '8px', position: 'relative', zIndex: 25 }}>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
style={{
|
||||
backgroundImage: 'linear-gradient(to right bottom, #54a799, #177b73)',
|
||||
}}
|
||||
style={themeStyles.primaryBackground}
|
||||
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>
|
||||
className={'pl-3 text-orange-100 font-medium'}>{config?.vipText || '易赊宝分享中心'}</Text>
|
||||
{/*<Text className={'text-white opacity-80 pl-3'}>门店核销</Text>*/}
|
||||
</View>
|
||||
}
|
||||
@@ -71,6 +67,30 @@ const UserCell = () => {
|
||||
/**
|
||||
* 普通用户
|
||||
*/
|
||||
return null
|
||||
return (
|
||||
<>
|
||||
<View className={'px-4'} style={{ marginTop: '8px', position: 'relative', zIndex: 25 }}>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
style={themeStyles.primaryBackground}
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Reward className={'text-orange-100 '} size={16}/>
|
||||
<Text style={{fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>{config?.vipText || '易赊宝分享中心'}</Text>
|
||||
<Text className={'text-white opacity-80 pl-3'}>{config?.vipComments || ''}</Text>
|
||||
</View>
|
||||
}
|
||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||
onClick={() => {
|
||||
if (dealerLoading) {
|
||||
Taro.showToast({ title: '正在加载信息,请稍等...', icon: 'none' })
|
||||
return
|
||||
}
|
||||
navTo('/dealer/apply/add', true)
|
||||
}}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default UserCell
|
||||
export default IsDealer
|
||||
|
||||
@@ -1,26 +1,101 @@
|
||||
import {Button} from '@nutui/nutui-react-taro'
|
||||
import {Avatar, Tag} from '@nutui/nutui-react-taro'
|
||||
import {Avatar, Tag, Space, Button} from '@nutui/nutui-react-taro'
|
||||
import {View, Text} from '@tarojs/components'
|
||||
import {Scan} from '@nutui/icons-react-taro';
|
||||
import {getWxOpenId} from '@/api/layout';
|
||||
import {getUserInfo, getWxOpenId} from '@/api/layout';
|
||||
import Taro from '@tarojs/taro';
|
||||
import {useEffect} from "react";
|
||||
import {useEffect, useState, forwardRef, useImperativeHandle} from "react";
|
||||
import {User} from "@/api/system/user/model";
|
||||
import navTo from "@/utils/common";
|
||||
import {TenantId} from "@/config/app";
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
import {useUserData} from "@/hooks/useUserData";
|
||||
import {checkAndHandleInviteRelation, getStoredInviteParams, hasPendingInvite} from "@/utils/invite";
|
||||
import UnifiedQRButton from "@/components/UnifiedQRButton";
|
||||
import {useThemeStyles} from "@/hooks/useTheme";
|
||||
import {getRootDomain} from "@/utils/domain";
|
||||
import { getMyGltUserTicketTotal } from '@/api/glt/gltUserTicket'
|
||||
import { saveStorageByLoginUser } from '@/utils/server'
|
||||
|
||||
function UserCard() {
|
||||
const {
|
||||
user,
|
||||
isAdmin,
|
||||
isLoggedIn,
|
||||
loginUser,
|
||||
fetchUserInfo,
|
||||
getDisplayName,
|
||||
getRoleName
|
||||
} = useUser();
|
||||
const UserCard = forwardRef<any, any>((_, ref) => {
|
||||
const {data, refresh} = useUserData()
|
||||
const {loadUserFromStorage} = useUser();
|
||||
const [IsLogin, setIsLogin] = useState<boolean>(false)
|
||||
const [userInfo, setUserInfo] = useState<User>()
|
||||
const [ticketTotal, setTicketTotal] = useState<number>(0)
|
||||
|
||||
const themeStyles = useThemeStyles();
|
||||
const canShowScanButton = (() => {
|
||||
const v: any = (userInfo as any)?.isAdmin
|
||||
return v === true || v === 1 || v === '1'
|
||||
})()
|
||||
|
||||
const getDisplayName = () => {
|
||||
if (!userInfo) return IsLogin ? '用户' : '点击登录'
|
||||
return userInfo.nickname || (userInfo as any).realName || (userInfo as any).username || (IsLogin ? '用户' : '点击登录')
|
||||
}
|
||||
|
||||
// 角色名称:优先取用户 roles 数组的第一个角色名称
|
||||
const getRoleName = () => {
|
||||
return userInfo?.roles?.[0]?.roleName || userInfo?.roleName || '注册用户'
|
||||
}
|
||||
|
||||
// 下拉刷新
|
||||
const reloadStats = async (showToast = false) => {
|
||||
await refresh()
|
||||
reloadTicketTotal()
|
||||
if (showToast) {
|
||||
Taro.showToast({
|
||||
title: '刷新成功',
|
||||
icon: 'success'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const syncUserToStorage = (u: User) => {
|
||||
// Keep storage up-to-date for other places that read user info synchronously.
|
||||
Taro.setStorageSync('User', u)
|
||||
if (u?.userId) Taro.setStorageSync('UserId', u.userId)
|
||||
if (u?.nickname) Taro.setStorageSync('WxNickName', u.nickname)
|
||||
}
|
||||
|
||||
const reloadUserInfo = async () => {
|
||||
try {
|
||||
const u = await getUserInfo()
|
||||
if (u) {
|
||||
setUserInfo(u)
|
||||
setIsLogin(true)
|
||||
syncUserToStorage(u)
|
||||
// Refresh this hook instance's state from storage (defensive).
|
||||
await loadUserFromStorage()
|
||||
|
||||
// 获取openId(不阻塞 UI 刷新)
|
||||
if (!u.openid) {
|
||||
Taro.login({
|
||||
success: (res) => {
|
||||
getWxOpenId({code: res.code}).catch(() => {})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Not logged in / token expired: keep UI in "not login" state.
|
||||
// Other error handling is done in request interceptor / callers.
|
||||
}
|
||||
}
|
||||
|
||||
// 暴露方法给父组件
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleRefresh: async () => {
|
||||
await reloadUserInfo()
|
||||
await reloadStats(true)
|
||||
},
|
||||
reloadStats,
|
||||
reloadUserInfo
|
||||
}))
|
||||
|
||||
useEffect(() => {
|
||||
// 独立于用户信息授权:只要有登录 token,就可以拉取水票总数
|
||||
reloadTicketTotal()
|
||||
|
||||
// Taro.getSetting:获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
|
||||
Taro.getSetting({
|
||||
success: (res) => {
|
||||
@@ -37,27 +112,43 @@ function UserCard() {
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
||||
const reload = async () => {
|
||||
// 如果已登录,获取最新用户信息
|
||||
if (isLoggedIn) {
|
||||
try {
|
||||
const data = await fetchUserInfo();
|
||||
if (data) {
|
||||
// 获取openId
|
||||
if (!data.openid) {
|
||||
Taro.login({
|
||||
success: (res) => {
|
||||
getWxOpenId({code: res.code}).then(() => {
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取用户信息失败:', error)
|
||||
}
|
||||
const reloadTicketTotal = () => {
|
||||
const token = Taro.getStorageSync('access_token')
|
||||
const userIdRaw = Taro.getStorageSync('UserId')
|
||||
const userId = Number(userIdRaw)
|
||||
const hasUserId = Number.isFinite(userId) && userId > 0
|
||||
if (!token && !hasUserId) {
|
||||
setTicketTotal(0)
|
||||
return
|
||||
}
|
||||
getMyGltUserTicketTotal(hasUserId ? userId : undefined)
|
||||
.then((total) => setTicketTotal(typeof total === 'number' ? total : 0))
|
||||
.catch((err) => {
|
||||
console.error('个人中心水票总数加载失败:', err)
|
||||
setTicketTotal(0)
|
||||
})
|
||||
}
|
||||
|
||||
const reload = () => {
|
||||
Taro.getUserInfo({
|
||||
success: (res) => {
|
||||
const avatar = res.userInfo.avatarUrl;
|
||||
setUserInfo({
|
||||
avatar,
|
||||
nickname: res.userInfo.nickName,
|
||||
sexName: res.userInfo.gender == 1 ? '男' : '女'
|
||||
})
|
||||
reloadUserInfo()
|
||||
.then(() => {
|
||||
// 登录态已就绪后刷新卡片统计(余额/积分/券/水票)
|
||||
refresh().then()
|
||||
reloadTicketTotal()
|
||||
})
|
||||
.catch(() => {
|
||||
console.log('未登录')
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const showAuthModal = () => {
|
||||
@@ -75,7 +166,6 @@ function UserCard() {
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const openSetting = () => {
|
||||
// Taro.openSetting:调起客户端小程序设置界面,返回用户设置的操作结果。设置界面只会出现小程序已经向用户请求过的权限。
|
||||
Taro.openSetting({
|
||||
@@ -97,19 +187,28 @@ function UserCard() {
|
||||
/* 获取用户手机号 */
|
||||
const handleGetPhoneNumber = ({detail}: { detail: { code?: string, encryptedData?: string, iv?: string } }) => {
|
||||
const {code, encryptedData, iv} = detail
|
||||
|
||||
// 判断用户是否已登录
|
||||
if(IsLogin){
|
||||
return navTo(`/user/profile/profile`)
|
||||
}
|
||||
|
||||
// 获取存储的邀请参数
|
||||
const inviteParams = getStoredInviteParams()
|
||||
const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter) : 0
|
||||
|
||||
Taro.login({
|
||||
success: function (loginRes) {
|
||||
success: function () {
|
||||
if (code) {
|
||||
Taro.request({
|
||||
url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone',
|
||||
method: 'POST',
|
||||
data: {
|
||||
authCode: loginRes.code,
|
||||
code,
|
||||
encryptedData,
|
||||
iv,
|
||||
notVerifyPhone: true,
|
||||
refereeId: 0,
|
||||
refereeId: refereeId, // 使用解析出的推荐人ID
|
||||
sceneType: 'save_referee',
|
||||
tenantId: TenantId
|
||||
},
|
||||
@@ -127,21 +226,19 @@ function UserCard() {
|
||||
return false;
|
||||
}
|
||||
// 登录成功
|
||||
const token = res.data.data.access_token;
|
||||
const userData = res.data.data.user;
|
||||
saveStorageByLoginUser(res.data.data.access_token, res.data.data.user)
|
||||
setUserInfo(res.data.data.user)
|
||||
setIsLogin(true)
|
||||
// 登录态已就绪后刷新卡片统计(余额/积分/券/水票)
|
||||
refresh().then()
|
||||
reloadTicketTotal()
|
||||
|
||||
// 使用useUser Hook的loginUser方法更新状态
|
||||
loginUser(token, userData);
|
||||
|
||||
// 显示登录成功提示
|
||||
Taro.showToast({
|
||||
title: '登录成功',
|
||||
icon: 'success',
|
||||
duration: 1500
|
||||
})
|
||||
|
||||
// 刷新页面数据
|
||||
reload();
|
||||
// 登录成功后(可能是新注册用户),检查是否存在待处理的邀请关系并尝试绑定
|
||||
if (hasPendingInvite()) {
|
||||
checkAndHandleInviteRelation().catch((e) => {
|
||||
console.error('个人中心登录后处理邀请关系失败:', e)
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
@@ -152,55 +249,116 @@ function UserCard() {
|
||||
}
|
||||
|
||||
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',
|
||||
minHeight: '120px',
|
||||
// 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'}>
|
||||
{
|
||||
isLoggedIn ? (
|
||||
<Avatar size="large" src={user?.avatar} shape="round"/>
|
||||
) : (
|
||||
<Button className={'text-black'} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
|
||||
<Avatar size="large" src={user?.avatar} shape="round"/>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
<View className={'user-info flex flex-col px-2'}>
|
||||
<View className={'py-1 text-black font-bold max-w-28'}>{getDisplayName()}</View>
|
||||
{isLoggedIn ? (
|
||||
<View className={'grade text-xs py-1'}>
|
||||
<Tag type="success" round>
|
||||
<Text className={'p-1'}>
|
||||
{getRoleName()}
|
||||
</Text>
|
||||
</Tag>
|
||||
<View className={'pt-14'}>
|
||||
{/* 使用相对定位容器,让个人资料图片可以绝对定位在右上角 */}
|
||||
<View className="relative z-20">
|
||||
<View
|
||||
className={'user-card w-full flex flex-col justify-around rounded-xl'}
|
||||
>
|
||||
<View className={'user-card-header flex w-full justify-between items-center pt-4'}>
|
||||
<View className={'flex items-center mx-4'}>
|
||||
{IsLogin && (
|
||||
<View style={{color: '#ffffff', height: 'auto'}} onClick={() => navTo(`/user/profile/profile`)}>
|
||||
<View className={'flex items-center gap-2'}>
|
||||
<Avatar
|
||||
size={'large'}
|
||||
src={userInfo?.avatar || ''}
|
||||
/>
|
||||
<View className={'flex flex-col'}>
|
||||
<Text style={{color: '#ffffff'}}>{getDisplayName() || '点击登录'}</Text>
|
||||
{getRootDomain() && (
|
||||
<View><Tag type="success">{getRoleName()}</Tag></View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
) : ''}
|
||||
)}
|
||||
{!IsLogin && (
|
||||
<Button style={{color: '#ffffff', height: 'auto'}} open-type="getPhoneNumber"
|
||||
onGetPhoneNumber={handleGetPhoneNumber}>
|
||||
<View className={'flex items-center gap-2'}>
|
||||
<Avatar
|
||||
size={'large'}
|
||||
src={userInfo?.avatar || ''}
|
||||
/>
|
||||
<View className={'flex flex-col'}>
|
||||
<Text style={{color: '#ffffff'}}>{getDisplayName() || '点击登录'}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</Button>
|
||||
)}
|
||||
</View>
|
||||
{/*统一扫码入口 - 仅管理员可见*/}
|
||||
{canShowScanButton && (
|
||||
<Space style={{
|
||||
marginTop: '30px',
|
||||
marginRight: '10px'
|
||||
}}>
|
||||
<UnifiedQRButton
|
||||
text="扫一扫"
|
||||
size="small"
|
||||
onSuccess={(result) => {
|
||||
console.log('统一扫码成功:', result);
|
||||
// 根据扫码类型给出不同的提示
|
||||
if (result.type === 'verification') {
|
||||
const businessType = result?.data?.businessType;
|
||||
if (businessType === 'gift' && result?.data?.gift) {
|
||||
const gift = result.data.gift;
|
||||
Taro.showModal({
|
||||
title: '核销成功',
|
||||
content: `已成功核销:${gift.goodsName || gift.name || '礼品'},面值¥${gift.faceValue}`
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (businessType === 'ticket' && result?.data?.ticket) {
|
||||
const ticket = result.data.ticket;
|
||||
const qty = result.data.qty || 1;
|
||||
Taro.showModal({
|
||||
title: '核销成功',
|
||||
content: `已成功核销:${ticket.templateName || '水票'},本次使用${qty}次,剩余可用${ticket.availableQty ?? 0}次`
|
||||
});
|
||||
return;
|
||||
}
|
||||
Taro.showModal({
|
||||
title: '核销成功',
|
||||
content: '已成功核销'
|
||||
});
|
||||
}
|
||||
}}
|
||||
onError={(error) => {
|
||||
console.error('统一扫码失败:', error);
|
||||
}}
|
||||
/>
|
||||
</Space>
|
||||
)}
|
||||
</View>
|
||||
<View className={'gap-2 flex items-center'}>
|
||||
{isAdmin() && <Scan className={'text-gray-900'} size={24} 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 className={'py-2'}>
|
||||
<View className={'flex justify-around mt-1'}>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/ticket/index', true)}>
|
||||
<Text className={'text-xs text-gray-200'} style={themeStyles.textColor}>水票</Text>
|
||||
<Text className={'text-xl text-white'} style={themeStyles.textColor}>{ticketTotal}</Text>
|
||||
</View>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/coupon/index', true)}>
|
||||
<Text className={'text-xs text-gray-200'} style={themeStyles.textColor}>优惠券</Text>
|
||||
<Text className={'text-xl text-white'} style={themeStyles.textColor}>{data?.coupons || 0}</Text>
|
||||
</View>
|
||||
<View className={'item flex justify-center flex-col items-center'}
|
||||
onClick={() => navTo('/user/wallet/wallet', true)}>
|
||||
<Text className={'text-xs text-gray-200'} style={themeStyles.textColor}>余额</Text>
|
||||
<Text className={'text-xl text-white'} style={themeStyles.textColor}>{data?.balance || '0.00'}</Text>
|
||||
</View>
|
||||
<View className={'item flex justify-center flex-col items-center'}>
|
||||
<Text className={'text-xs text-gray-200'} style={themeStyles.textColor}>积分</Text>
|
||||
<Text className={'text-xl text-white'} style={themeStyles.textColor}>{data?.points || 0}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
export default UserCard;
|
||||
|
||||
@@ -2,11 +2,11 @@ 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, LogisticsError, Location} from '@nutui/icons-react-taro'
|
||||
import {ArrowRight, ShieldCheck, LogisticsError, Location, Tips, Ask} from '@nutui/icons-react-taro'
|
||||
import {useUser} from '@/hooks/useUser'
|
||||
|
||||
const UserCell = () => {
|
||||
const {logoutUser} = useUser();
|
||||
const {logoutUser, isCertified} = useUser();
|
||||
|
||||
const onLogout = () => {
|
||||
Taro.showModal({
|
||||
@@ -26,7 +26,7 @@ const UserCell = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className={'px-4'}>
|
||||
<View className={'px-4'} style={{ marginTop: '8px', position: 'relative', zIndex: 20 }}>
|
||||
|
||||
<Cell.Group divider={true} description={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
@@ -55,7 +55,7 @@ const UserCell = () => {
|
||||
title={
|
||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||
<Location size={16}/>
|
||||
<Text className={'pl-3 text-sm'}>我的需求</Text>
|
||||
<Text className={'pl-3 text-sm'}>配送地址</Text>
|
||||
</View>
|
||||
}
|
||||
align="center"
|
||||
@@ -64,51 +64,51 @@ const UserCell = () => {
|
||||
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
|
||||
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'}}>
|
||||
@@ -122,13 +122,13 @@ const UserCell = () => {
|
||||
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={() => navTo('/user/theme/index', true)}
|
||||
/>
|
||||
<Cell
|
||||
className="nutui-cell-clickable"
|
||||
title="退出登录"
|
||||
|
||||
216
src/pages/user/components/UserGrid.tsx
Normal file
216
src/pages/user/components/UserGrid.tsx
Normal file
@@ -0,0 +1,216 @@
|
||||
import {Grid, ConfigProvider} from '@nutui/nutui-react-taro'
|
||||
import navTo from "@/utils/common";
|
||||
import Taro from '@tarojs/taro'
|
||||
import {View, Button} from '@tarojs/components'
|
||||
import {
|
||||
ShieldCheck,
|
||||
Location,
|
||||
Tips,
|
||||
Ask,
|
||||
// Dongdong,
|
||||
People,
|
||||
// AfterSaleService,
|
||||
Logout,
|
||||
Shop,
|
||||
Jdl,
|
||||
Service
|
||||
} from '@nutui/icons-react-taro'
|
||||
import {useUser} from "@/hooks/useUser";
|
||||
|
||||
const UserCell = () => {
|
||||
const {logoutUser, hasRole} = 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="bg-white mx-4 mt-4 rounded-xl">
|
||||
<View className="font-semibold text-gray-800 pt-4 pl-4">我的服务</View>
|
||||
<ConfigProvider>
|
||||
<Grid
|
||||
columns={4}
|
||||
className="no-border-grid"
|
||||
style={{
|
||||
'--nutui-grid-border-color': 'transparent',
|
||||
'--nutui-grid-item-border-width': '0px',
|
||||
border: 'none'
|
||||
} as React.CSSProperties}
|
||||
>
|
||||
|
||||
{hasRole('store') && (
|
||||
<Grid.Item text="门店中心" onClick={() => navTo('/store/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Shop color="#3b82f6" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
)}
|
||||
|
||||
{hasRole('rider') && (
|
||||
<Grid.Item text="配送中心" onClick={() => navTo('/rider/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Jdl color="#3b82f6" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
)}
|
||||
|
||||
{(hasRole('staff') || hasRole('admin')) && (
|
||||
<Grid.Item text="门店订单" onClick={() => navTo('/user/store/orders/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-orange-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Shop color="#f59e0b" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
)}
|
||||
|
||||
<Grid.Item text="配送地址" onClick={() => navTo('/user/address/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-emerald-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Location color="#3b82f6" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'常见问题'} onClick={() => navTo('/user/help/index')}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-cyan-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Ask className={'text-cyan-500'} size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
{/* 修改联系我们为微信客服 */}
|
||||
<Grid.Item text="联系我们">
|
||||
<Button
|
||||
open-type="contact"
|
||||
className="w-full h-full flex flex-col items-center justify-center p-0 bg-transparent"
|
||||
hover-class="none"
|
||||
style={{border: 'none'}}
|
||||
>
|
||||
<View className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Service color="#67C23A" size="20"/>
|
||||
</View>
|
||||
</Button>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'实名认证'} onClick={() => navTo('/user/userVerify/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<ShieldCheck color="#10b981" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'推广邀请'} onClick={() => navTo('/dealer/team/index', true)}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-purple-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<People color="#8b5cf6" size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
{/*<Grid.Item text={'我的邀请码'} onClick={() => navTo('/dealer/qrcode/index', true)}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
{/* <View className="w-12 h-12 bg-orange-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
|
||||
{/* <Dongdong color="#f59e0b" size="20"/>*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
{/*</Grid.Item>*/}
|
||||
|
||||
{/*<Grid.Item text={'管理中心'} onClick={() => navTo('/admin/index', true)}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
{/* <View className="w-12 h-12 bg-red-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
|
||||
{/* <AfterSaleService className={'text-red-500'} size="20"/>*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
{/*</Grid.Item>*/}
|
||||
|
||||
|
||||
<Grid.Item text={'关于我们'} onClick={() => navTo('/user/about/index')}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-amber-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Tips className={'text-amber-500'} size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
<Grid.Item text={'安全退出'} onClick={onLogout}>
|
||||
<View className="text-center">
|
||||
<View className="w-12 h-12 bg-pink-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||
<Logout className={'text-pink-500'} size="20"/>
|
||||
</View>
|
||||
</View>
|
||||
</Grid.Item>
|
||||
|
||||
</Grid>
|
||||
</ConfigProvider>
|
||||
</View>
|
||||
{/*<View className="bg-white mx-4 mt-4 rounded-xl">*/}
|
||||
{/* <View className="font-semibold text-gray-800 pt-4 pl-4">账号管理</View>*/}
|
||||
{/* <ConfigProvider>*/}
|
||||
{/* <Grid*/}
|
||||
{/* columns={4}*/}
|
||||
{/* className="no-border-grid"*/}
|
||||
{/* style={{*/}
|
||||
{/* '--nutui-grid-border-color': 'transparent',*/}
|
||||
{/* '--nutui-grid-item-border-width': '0px',*/}
|
||||
{/* border: 'none'*/}
|
||||
{/* } as React.CSSProperties}*/}
|
||||
{/* >*/}
|
||||
{/* <Grid.Item text="账号安全" onClick={() => navTo('/user/profile/profile', true)}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
{/* <View className="w-12 h-12 bg-blue-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
|
||||
{/* <ShoppingAdd color="#3b82f6" size="20"/>*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
{/* </Grid.Item>*/}
|
||||
|
||||
{/* <Grid.Item text="切换主题" onClick={() => navTo('/user/theme/index', true)}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
{/* <View className="w-12 h-12 bg-emerald-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
|
||||
{/* <Location color="#3b82f6" size="20"/>*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
{/* </Grid.Item>*/}
|
||||
|
||||
{/* <Grid.Item text={'关于我们'} onClick={() => navTo('/user/about/index')}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
{/* <View className="w-12 h-12 bg-amber-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
|
||||
{/* <Tips className={'text-amber-500'} size="20"/>*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
{/* </Grid.Item>*/}
|
||||
|
||||
{/* <Grid.Item text={'安全退出'} onClick={onLogout}>*/}
|
||||
{/* <View className="text-center">*/}
|
||||
{/* <View className="w-12 h-12 bg-pink-50 rounded-xl flex items-center justify-center mx-auto mb-2">*/}
|
||||
{/* <Logout className={'text-pink-500'} size="20"/>*/}
|
||||
{/* </View>*/}
|
||||
{/* </View>*/}
|
||||
{/* </Grid.Item>*/}
|
||||
|
||||
{/* </Grid>*/}
|
||||
{/* </ConfigProvider>*/}
|
||||
{/*</View>*/}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default UserCell
|
||||
@@ -14,7 +14,7 @@ function UserOrder() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<View className={'px-4 pb-2'}>
|
||||
<View className={'px-4 pb-2 z-30 relative'} style={{ marginTop: '8px' }}>
|
||||
<View
|
||||
className={'user-card w-full flex flex-col justify-around rounded-xl shadow-sm'}
|
||||
style={{
|
||||
@@ -26,7 +26,7 @@ function UserOrder() {
|
||||
}}
|
||||
>
|
||||
<View className={'title-bar flex justify-between pt-2'}>
|
||||
<Text className={'title font-medium px-4'}>我的订单</Text>
|
||||
<Text className={'title font-medium px-4'}>商城订单</Text>
|
||||
<View
|
||||
className={'more flex items-center px-2'}
|
||||
onClick={() => navTo('/user/order/order', true)}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
.header-bg{
|
||||
/* Replace top background image with a gradient + subtle decoration */
|
||||
background:
|
||||
radial-gradient(circle at 18% 22%, rgba(255, 255, 255, 0.35) 0%, rgba(255, 255, 255, 0) 55%),
|
||||
radial-gradient(circle at 82% 30%, rgba(255, 255, 255, 0.22) 0%, rgba(255, 255, 255, 0) 52%),
|
||||
radial-gradient(circle at 60% 8%, rgba(255, 255, 255, 0.18) 0%, rgba(255, 255, 255, 0) 45%),
|
||||
linear-gradient(180deg, #ff0000 0%, #ffeee9 45%, #f9fafb 100%);
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
//background: url('https://oss.wsdns.cn/20250621/edb5d4da976b4d97ba185cb7077d2858.jpg') no-repeat top center;
|
||||
background: linear-gradient(to bottom, #03605c, #18ae4f);
|
||||
background-size: 100%;
|
||||
}
|
||||
|
||||
.my-bg{
|
||||
background: url('https://oss.wsdns.cn/20250913/5ae575a50dbb4ccaab086c3679c5e2c3.png') no-repeat top center;
|
||||
}
|
||||
|
||||
@@ -1,41 +1,71 @@
|
||||
import {useEffect, useRef, useState} from 'react'
|
||||
import {PullToRefresh} from '@nutui/nutui-react-taro'
|
||||
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 {View} from '@tarojs/components';
|
||||
import './user.scss'
|
||||
import IsDealer from "./components/IsDealer";
|
||||
import {useThemeStyles} from "@/hooks/useTheme";
|
||||
import UserGrid from "@/pages/user/components/UserGrid";
|
||||
import { useDidShow } from '@tarojs/taro'
|
||||
|
||||
function User() {
|
||||
const {
|
||||
isAdmin
|
||||
} = useUser();
|
||||
|
||||
/**
|
||||
* 门店核销管理
|
||||
*/
|
||||
if (isAdmin()) {
|
||||
return <>
|
||||
<div className={'w-full'}>
|
||||
<UserCard/>
|
||||
<UserOrder/>
|
||||
<IsDealer/>
|
||||
<UserCell/>
|
||||
<UserFooter/>
|
||||
</div>
|
||||
</>
|
||||
const userCardRef = useRef<any>()
|
||||
const themeStyles = useThemeStyles();
|
||||
// TabBar 页在小程序里通常不会销毁;从“注册/申请”页返回时需要触发子组件重新初始化/拉取最新状态。
|
||||
const [dealerViewKey, setDealerViewKey] = useState(0)
|
||||
|
||||
// 下拉刷新处理
|
||||
const handleRefresh = async () => {
|
||||
if (userCardRef.current?.handleRefresh) {
|
||||
await userCardRef.current.handleRefresh()
|
||||
}
|
||||
setDealerViewKey(v => v + 1)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
}, []);
|
||||
|
||||
// 每次进入/切回个人中心都刷新一次统计(包含水票数量)
|
||||
useDidShow(() => {
|
||||
userCardRef.current?.reloadStats?.()
|
||||
// 个人资料(头像/昵称)可能在其它页面被修改,这里确保返回时立刻刷新
|
||||
userCardRef.current?.reloadUserInfo?.()
|
||||
setDealerViewKey(v => v + 1)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={'w-full'}>
|
||||
<UserCard/>
|
||||
{/*<UserOrder/>*/}
|
||||
<IsDealer/>
|
||||
<UserCell/>
|
||||
<UserFooter/>
|
||||
</div>
|
||||
</>
|
||||
<PullToRefresh
|
||||
onRefresh={handleRefresh}
|
||||
headHeight={60}
|
||||
>
|
||||
{/* 装饰性背景 */}
|
||||
<View className={'h-64 w-full fixed top-0 z-0'} style={themeStyles.primaryBackground}>
|
||||
{/* 装饰性背景元素 - 小程序兼容版本 */}
|
||||
<View className="absolute w-32 h-32 rounded-full" style={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
top: '-16px',
|
||||
right: '-16px'
|
||||
}}></View>
|
||||
<View className="absolute w-24 h-24 rounded-full" style={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.08)',
|
||||
bottom: '-12px',
|
||||
left: '-12px'
|
||||
}}></View>
|
||||
<View className="absolute w-16 h-16 rounded-full" style={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.05)',
|
||||
top: '60px',
|
||||
left: '120px'
|
||||
}}></View>
|
||||
</View>
|
||||
<UserCard ref={userCardRef}/>
|
||||
<UserOrder/>
|
||||
<IsDealer key={dealerViewKey}/>
|
||||
<UserGrid/>
|
||||
<UserFooter/>
|
||||
</PullToRefresh>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user