Compare commits

...

10 Commits

Author SHA1 Message Date
bc8d882104 fix(order): 修改订单确认页面的商品名称显示问题
- 将商品名称替换为固定文本“时里院子市集”
- 确保配送类型和买家备注正确传递
- 优化注释说明,确保 couponId 类型正确处理
2025-12-25 13:04:40 +08:00
253613e077 fix(order): 修改订单确认页面的商品名称显示问题
- 将商品名称替换为固定文本“时里院子市集”
- 确保配送类型和买家备注正确传递
- 优化注释说明,确保 couponId 类型正确处理
2025-12-05 12:51:10 +08:00
4c6391053d feat(cms): 添加视频内容支持并优化文章加载逻辑
- 在文章详情页添加视频播放功能,支持视频宽度自适应
- 优化文章加载逻辑,确保内容存在时才进行解析
- 添加视频元素的条件渲染判断
2025-11-21 14:52:25 +08:00
078b422db0 feat(user): 更新用户卡片组件以支持微信手机号登录
- 引入 Button 组件用于触发微信获取手机号功能
- 根据登录状态显示不同的用户头像和信息区域
- 为未登录用户提供点击登录入口并绑定手机号
- 登录后展示用户头像、昵称及角色标签
- 优化用户卡片界面布局与交互逻辑
2025-11-21 13:38:48 +08:00
1d73521e17 feat(shop): 更新商品价格显示及订单状态逻辑
- 修改商品列表和详情页价格展示,增加会员价和原价显示
- 调整订单确认收货状态值从30改为20
- 恢复订单列表中查看物流按钮功能
- 更新二维码接口域名地址
- 统一使用Taro组件替换部分原生HTML标签
2025-11-21 10:47:12 +08:00
7db4da5081 feat(shop): 调整商品详情页分享图片比例
- 修改分享图片URL,添加OSS处理参数- 设置图片尺寸为500x40,裁剪模式填充
- 保持图片宽高比为5:4
- 当图片不存在时返回undefined
2025-10-11 20:20:14 +08:00
f808c22a22 refactor(api): 统一文章和导航查询接口命名
- 将getCmsArticleByCode重命名为getByCode
- 新增cmsNavigation的getByCode查询方法
- 统一返回数据处理逻辑

style(ui): 调整商品详情页底部按钮样式

- 减少底部按钮区域内边距
- 调整咨询按钮文字大小和内间距
- 缩小耳机图标尺寸

chore(app): 移除多余空行和调试信息

- 删除组件间无用空白行
- 将邀请提示改为控制台输出
- 保持导出语句格式一致
2025-10-11 11:52:24 +08:00
ea5419dfb5 feat(index):优化轮播图触摸交互支持垂直滚动- 引入touchStartRef记录触摸起始位置- 新增disableSwiper状态控制轮播图禁用
- 设置touchAction样式支持水平垂直滑动
- 调整Swiper组件direction为horizontal
-为Swiper.Item添加touchAction样式优化
- 更新CSS样式确保图片点击事件正常- 添加自定义Swiper类名及触摸控制样式
- 启用-webkit-overflow-scrolling提升滚动体验
2025-10-03 01:27:50 +08:00
aa879f0062 feat(index): 实现Header组件吸顶效果和轮播图触摸优化
- 添加Sticky组件实现Header吸顶功能
- 优化轮播图触摸事件,防止与页面滚动冲突
- 调整轮播图容器样式,确保手势操作流畅
- 更新Header组件结构以支持粘性布局
- 修复Banner组件中图片列表的key属性问题
- 移除不必要的状态传递和冗余代码
2025-10-03 01:21:19 +08:00
5270cab7e9 feat(config): 新增网站配置字段和自定义Hook
- 在cmsWebsiteField模型中新增deliveryText、guaranteeText和openComments三个字段
- 创建useConfig自定义Hook用于获取和管理网站配置数据
- 更新Banner组件中的默认轮播图高度从200px调整为300px- 修改Banner组件中热门商品列表项的右边距样式
- 在ArticleList组件中为map函数添加类型注解
- 移除IsDealer组件中旧的配置获取逻辑,改用新的useConfig Hook
- 删除src/pages/user_bak目录下的所有旧版用户相关组件文件
- 删除src/pages/user_bak目录下的配置文件和样式文件
2025-09-29 17:07:48 +08:00
39 changed files with 588 additions and 1227 deletions

View File

@@ -2,7 +2,8 @@
export const ENV_CONFIG = { export const ENV_CONFIG = {
// 开发环境 // 开发环境
development: { development: {
API_BASE_URL: 'https://cms-api.websoft.top/api', API_BASE_URL: 'http://127.0.0.1:9200/api',
// API_BASE_URL: 'https://cms-api.websoft.top/api',
APP_NAME: '开发环境', APP_NAME: '开发环境',
DEBUG: 'true', DEBUG: 'true',
}, },

View File

@@ -117,7 +117,7 @@ export const STATUS_COLOR_MAP = {
// 申请售后 // 申请售后
export const applyAfterSale = async (params: AfterSaleApplyParams): Promise<AfterSaleDetailResponse> => { export const applyAfterSale = async (params: AfterSaleApplyParams): Promise<AfterSaleDetailResponse> => {
try { try {
const response = await request({ const response = await request<AfterSaleDetailResponse>({
url: '/api/after-sale/apply', url: '/api/after-sale/apply',
method: 'POST', method: 'POST',
data: params data: params
@@ -136,7 +136,7 @@ export const getAfterSaleDetail = async (params: {
afterSaleId?: string afterSaleId?: string
}): Promise<AfterSaleDetailResponse> => { }): Promise<AfterSaleDetailResponse> => {
try { try {
const response = await request({ const response = await request<AfterSaleDetailResponse>({
url: '/api/after-sale/detail', url: '/api/after-sale/detail',
method: 'GET', method: 'GET',
data: params data: params
@@ -154,7 +154,7 @@ export const getAfterSaleDetail = async (params: {
// 查询售后列表 // 查询售后列表
export const getAfterSaleList = async (params: AfterSaleListParams): Promise<AfterSaleListResponse> => { export const getAfterSaleList = async (params: AfterSaleListParams): Promise<AfterSaleListResponse> => {
try { try {
const response = await request({ const response = await request<AfterSaleListResponse>({
url: '/api/after-sale/list', url: '/api/after-sale/list',
method: 'GET', method: 'GET',
data: params data: params
@@ -170,7 +170,7 @@ export const getAfterSaleList = async (params: AfterSaleListParams): Promise<Aft
// 撤销售后申请 // 撤销售后申请
export const cancelAfterSale = async (afterSaleId: string): Promise<{ success: boolean; message?: string }> => { export const cancelAfterSale = async (afterSaleId: string): Promise<{ success: boolean; message?: string }> => {
try { try {
const response = await request({ const response = await request<{ success: boolean; message?: string }>({
url: '/api/after-sale/cancel', url: '/api/after-sale/cancel',
method: 'POST', method: 'POST',
data: { afterSaleId } data: { afterSaleId }
@@ -312,11 +312,9 @@ export const getAfterSaleSteps = (type: AfterSaleType, status: AfterSaleStatus)
// 根据类型调整步骤 // 根据类型调整步骤
if (type === 'return' || type === 'exchange') { if (type === 'return' || type === 'exchange') {
baseSteps.splice(2, 1, baseSteps.splice(2, 0, { title: '等待收货', description: '等待用户寄回商品' })
{ title: '寄回商品', description: '请将商品寄回指定地址' }, baseSteps.splice(3, 0, { title: '确认收货', description: '商家确认收到退回商品' })
{ title: '处理中', description: '收到商品,正在处理' }
)
} }
return baseSteps return baseSteps
} }

View File

@@ -207,7 +207,7 @@ export async function getByIds(params?: CmsArticleParam) {
/** /**
* 根据code查询文章 * 根据code查询文章
*/ */
export async function getCmsArticleByCode(code: string) { export async function getByCode(code: string) {
const res = await request.get<ApiResult<CmsArticle>>( const res = await request.get<ApiResult<CmsArticle>>(
'/cms/cms-article/getByCode/' + code '/cms/cms-article/getByCode/' + code
); );

View File

@@ -123,3 +123,16 @@ export async function getNavigationByPath(params: CmsNavigationParam) {
} }
/**
* 根据code查询导航
*/
export async function getByCode(code: string) {
const res = await request.get<ApiResult<CmsNavigation>>(
'/cms/cms-navigation/getByCode/' + code
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}

View File

@@ -57,4 +57,7 @@ export interface Config {
sysLogo?: string; sysLogo?: string;
vipText?: string; vipText?: string;
vipComments?: string; vipComments?: string;
deliveryText?: string;
guaranteeText?: string;
openComments?: string;
} }

View File

@@ -87,6 +87,7 @@ export default {
'goodsDetail/index', 'goodsDetail/index',
'orderConfirm/index', 'orderConfirm/index',
'orderConfirmCart/index', 'orderConfirmCart/index',
'comments/index',
'search/index'] 'search/index']
}, },
{ {

View File

@@ -7,9 +7,11 @@ import {loginByOpenId} from "@/api/layout";
import {TenantId} from "@/config/app"; import {TenantId} from "@/config/app";
import {saveStorageByLoginUser} from "@/utils/server"; import {saveStorageByLoginUser} from "@/utils/server";
import {parseInviteParams, saveInviteParams, trackInviteSource, handleInviteRelation} from "@/utils/invite"; import {parseInviteParams, saveInviteParams, trackInviteSource, handleInviteRelation} from "@/utils/invite";
import {configWebsiteField} from "@/api/cms/cmsWebsiteField"; import { useConfig } from "@/hooks/useConfig"; // 引入新的自定义Hook
function App(props: { children: any; }) { function App(props: { children: any; }) {
const { refetch: handleTheme } = useConfig(); // 使用新的Hook
const reload = () => { const reload = () => {
Taro.login({ Taro.login({
success: (res) => { success: (res) => {
@@ -38,7 +40,7 @@ function App(props: { children: any; }) {
}; };
// 可以使用所有的 React Hooks // 可以使用所有的 React Hooks
useEffect(() => { useEffect(() => {
// 设置主题 // 设置主题 (现在由useConfig Hook处理)
handleTheme() handleTheme()
// Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。 // Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
Taro.getSetting({ Taro.getSetting({
@@ -60,7 +62,7 @@ function App(props: { children: any; }) {
// 处理启动参数 // 处理启动参数
const handleLaunchOptions = (options: any) => { const handleLaunchOptions = (options: any) => {
try { try {
console.log('=== 小程序启动参数处理开始 ===') console.log('=== 小程 序启动参数处理开始 ===')
console.log('完整启动参数:', JSON.stringify(options, null, 2)) console.log('完整启动参数:', JSON.stringify(options, null, 2))
// 解析邀请参数 // 解析邀请参数
@@ -76,11 +78,7 @@ function App(props: { children: any; }) {
// 显示邀请提示 // 显示邀请提示
setTimeout(() => { setTimeout(() => {
Taro.showToast({ console.log(`检测到邀请信息 ID:${inviteParams.inviter}`)
title: `检测到邀请信息 ID:${inviteParams.inviter}`,
icon: 'success',
duration: 3000
})
}, 1000) }, 1000)
} else { } else {
@@ -93,15 +91,6 @@ function App(props: { children: any; }) {
} }
} }
const handleTheme = () => {
configWebsiteField().then(data => {
// 设置主题
if(data.theme && !Taro.getStorageSync('user_theme')){
Taro.setStorageSync('user_theme', data.theme)
}
})
}
// 对应 onHide // 对应 onHide
useDidHide(() => { useDidHide(() => {
}) })

View File

@@ -1,12 +1,13 @@
import {Image, Cell} from '@nutui/nutui-react-taro' import {Image, Cell} from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import {CmsArticle} from "@/api/cms/cmsArticle/model";
const ArticleList = (props: any) => { const ArticleList = (props: any) => {
return ( return (
<> <>
<div className={'px-3'}> <div className={'px-3'}>
{props.data.map((item, index) => { {props.data.map((item: CmsArticle, index: number) => {
return ( return (
<Cell <Cell
title={item.title} title={item.title}

View File

@@ -17,7 +17,7 @@ function Detail() {
const reload = async () => { const reload = async () => {
const item = await getCmsArticle(Number(params.id)) const item = await getCmsArticle(Number(params.id))
if (item) { if (item && item.content) {
item.content = wxParse(item.content) item.content = wxParse(item.content)
setItem(item) setItem(item)
Taro.setNavigationBarTitle({ Taro.setNavigationBarTitle({
@@ -43,6 +43,10 @@ function Detail() {
<div className={'p-4 font-bold text-lg'}>{item?.title}</div> <div className={'p-4 font-bold text-lg'}>{item?.title}</div>
<div className={'text-gray-400 text-sm px-4 '}>{item?.createTime}</div> <div className={'text-gray-400 text-sm px-4 '}>{item?.createTime}</div>
<View className={'content p-4'}> <View className={'content p-4'}>
{/*如果有视频就显示视频 视频沾满宽度*/}
{item?.video && <View className={'w-full'}>
<video src={item?.video} controls={true} width={'100%'}></video>
</View>}
<RichText nodes={item?.content}/> <RichText nodes={item?.content}/>
</View> </View>
<Line height={44}/> <Line height={44}/>

View File

@@ -81,7 +81,7 @@ const SimpleQRCodeModal: React.FC<SimpleQRCodeModalProps> = ({
{qrContent ? ( {qrContent ? (
<View className={'flex flex-col justify-center'}> <View className={'flex flex-col justify-center'}>
<img <img
src={`https://cms-api.websoft.top/api/qr-code/create-encrypted-qr-image?size=300x300&expireMinutes=60&businessType=gift&data=${encodeURIComponent(qrContent)}`} src={`https://mp-api.websoft.top/api/qr-code/create-encrypted-qr-image?size=300x300&expireMinutes=60&businessType=gift&data=${encodeURIComponent(qrContent)}`}
alt="二维码" alt="二维码"
style={{width: '200px', height: '200px'}} style={{width: '200px', height: '200px'}}
className="mx-auto" className="mx-auto"

50
src/hooks/useConfig.ts Normal file
View File

@@ -0,0 +1,50 @@
import { useEffect, useState } from 'react';
import Taro from '@tarojs/taro';
import { configWebsiteField } from '@/api/cms/cmsWebsiteField';
import { Config } from '@/api/cms/cmsWebsiteField/model';
/**
* 自定义Hook用于获取和管理网站配置数据
* @returns {Object} 包含配置数据和加载状态的对象
*/
export const useConfig = () => {
const [config, setConfig] = useState<Config | null>(null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
const fetchConfig = async () => {
try {
setLoading(true);
const data = await configWebsiteField();
setConfig(data);
Taro.setStorageSync('config', data);
// 设置主题
if (data.theme && !Taro.getStorageSync('user_theme')) {
Taro.setStorageSync('user_theme', data.theme);
}
} catch (err) {
setError(err instanceof Error ? err : new Error('获取配置失败'));
console.error('获取网站配置失败:', err);
} finally {
setLoading(false);
}
};
fetchConfig();
}, []);
return { config, loading, error, refetch: () => {
setLoading(true);
setError(null);
configWebsiteField().then(data => {
setConfig(data);
Taro.setStorageSync('config', data);
setLoading(false);
}).catch(err => {
setError(err instanceof Error ? err : new Error('获取配置失败'));
setLoading(false);
});
}};
};

View File

@@ -14,6 +14,10 @@ const MyPage = () => {
const [hotToday, setHotToday] = useState<CmsAd>() const [hotToday, setHotToday] = useState<CmsAd>()
const [item, setItem] = useState<CmsArticle>() const [item, setItem] = useState<CmsArticle>()
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
// const [disableSwiper, setDisableSwiper] = useState(false)
// 用于记录触摸开始位置
// const touchStartRef = useRef({x: 0, y: 0})
// 加载数据 // 加载数据
const loadData = async () => { const loadData = async () => {
@@ -46,8 +50,8 @@ const MyPage = () => {
loadData() loadData()
}, []) }, [])
// 轮播图高度,默认200px // 轮播图高度,默认300px
const carouselHeight = carouselData?.height || 200; const carouselHeight = carouselData?.height || 300;
// 骨架屏组件 // 骨架屏组件
const BannerSkeleton = () => ( const BannerSkeleton = () => (
@@ -98,7 +102,10 @@ const MyPage = () => {
return ( return (
<View className="flex p-2 justify-between" style={{height: `${carouselHeight}px`}}> <View className="flex p-2 justify-between" style={{height: `${carouselHeight}px`}}>
{/* 左侧轮播图区域 */} {/* 左侧轮播图区域 */}
<View style={{width: '50%', height: '100%'}}> <View
style={{width: '50%', height: '100%'}}
className="banner-swiper-container"
>
<Swiper <Swiper
defaultValue={0} defaultValue={0}
height={carouselHeight} height={carouselHeight}
@@ -107,11 +114,14 @@ const MyPage = () => {
duration={3000} duration={3000}
style={{ style={{
height: `${carouselHeight}px`, height: `${carouselHeight}px`,
touchAction: 'pan-y' touchAction: 'pan-y' // 关键修改:允许垂直滑动
}} }}
disableTouch={false}
direction="horizontal"
className="custom-swiper"
> >
{carouselData && carouselData?.imageList?.map((img, index) => ( {carouselData && carouselData?.imageList?.map((img, index) => (
<Swiper.Item key={index}> <Swiper.Item key={index} style={{ touchAction: 'pan-x pan-y' }}>
<Image <Image
width="100%" width="100%"
height="100%" height="100%"
@@ -121,7 +131,8 @@ const MyPage = () => {
lazyLoad={false} lazyLoad={false}
style={{ style={{
height: `${carouselHeight}px`, height: `${carouselHeight}px`,
borderRadius: '4px' borderRadius: '4px',
touchAction: 'manipulation' // 关键修改:优化触摸操作
}} }}
/> />
</Swiper.Item> </Swiper.Item>
@@ -139,7 +150,7 @@ const MyPage = () => {
}}> }}>
{ {
hotToday?.imageList?.map(item => ( hotToday?.imageList?.map(item => (
<View className={'item flex flex-col mr-4'}> <View className={'item flex flex-col mr-1'} key={item.url}>
<Image <Image
width={70} width={70}
height={70} height={70}
@@ -181,4 +192,3 @@ const MyPage = () => {
} }
export default MyPage export default MyPage

View File

@@ -124,6 +124,8 @@ const BestSellers = (props: {onStickyChange?: (isSticky: boolean) => void}) => {
<View className={'flex text-red-500 text-xl items-baseline'}> <View className={'flex text-red-500 text-xl items-baseline'}>
<Text className={'text-xs'}></Text> <Text className={'text-xs'}></Text>
<Text className={'font-bold text-2xl'}>{item.price}</Text> <Text className={'font-bold text-2xl'}>{item.price}</Text>
<Text className={'text-xs px-1'}></Text>
<Text className={'text-xs text-gray-400 line-through'}>{item.salePrice}</Text>
</View> </View>
<View className={'buy-btn'}> <View className={'buy-btn'}>
<View className={'cart-icon items-center hidden'}> <View className={'cart-icon items-center hidden'}>

View File

@@ -14,3 +14,10 @@
position: absolute; position: absolute;
z-index: 0; z-index: 0;
} }
/* 吸顶状态下的样式 */
.nutui-sticky--fixed {
.header-bg {
height: 100%;
}
}

View File

@@ -1,6 +1,6 @@
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
import {Button, Space} from '@nutui/nutui-react-taro' import {Button, Space, Sticky} from '@nutui/nutui-react-taro'
import {TriangleDown} from '@nutui/icons-react-taro' import {TriangleDown} from '@nutui/icons-react-taro'
import {Avatar, NavBar} from '@nutui/nutui-react-taro' import {Avatar, NavBar} from '@nutui/nutui-react-taro'
import {getUserInfo, getWxOpenId} from "@/api/layout"; import {getUserInfo, getWxOpenId} from "@/api/layout";
@@ -13,7 +13,7 @@ import {View,Text} from '@tarojs/components'
import MySearch from "./MySearch"; import MySearch from "./MySearch";
import './Header.scss'; import './Header.scss';
const Header = (props: any) => { const Header = (_: any) => {
// 使用新的useShopInfo Hook // 使用新的useShopInfo Hook
const { const {
getWebsiteName, getWebsiteName,
@@ -22,6 +22,7 @@ const Header = (props: any) => {
const [IsLogin, setIsLogin] = useState<boolean>(true) const [IsLogin, setIsLogin] = useState<boolean>(true)
const [statusBarHeight, setStatusBarHeight] = useState<number>() const [statusBarHeight, setStatusBarHeight] = useState<number>()
const [stickyStatus, setStickyStatus] = useState<boolean>(false)
const reload = async () => { const reload = async () => {
Taro.getSystemInfo({ Taro.getSystemInfo({
@@ -166,53 +167,76 @@ const Header = (props: any) => {
}) })
} }
// 处理粘性布局状态变化
const onStickyChange = (isSticky: boolean) => {
setStickyStatus(isSticky)
console.log('Header 粘性状态:', isSticky ? '已固定' : '取消固定')
}
// 获取小程序系统信息
// const getSystemInfo = () => {
// const systemInfo = Taro.getSystemInfoSync()
// // 状态栏高度 + 导航栏高度 (一般为44px)
// return (systemInfo.statusBarHeight || 0) + 44
// }
useEffect(() => { useEffect(() => {
reload().then() reload().then()
}, []) }, [])
return ( return (
<> <>
<View className={'fixed top-0 header-bg'} style={{ <Sticky
height: !props.stickyStatus ? '180px' : `${(statusBarHeight || 0) + 44}px`, threshold={0}
paddingBottom: !props.stickyStatus ? '12px' : '0px' onChange={onStickyChange}
}}>
{/* 只在非吸顶状态下显示搜索框 */}
{!props.stickyStatus && <MySearch statusBarHeight={statusBarHeight} />}
</View>
<NavBar
style={{ style={{
marginTop: `${statusBarHeight}px`, zIndex: 1000,
marginBottom: '0px', backgroundColor: stickyStatus ? '#03605c' : 'transparent',
backgroundColor: 'transparent' transition: 'background-color 0.3s ease',
}} }}
onBackClick={() => { >
}} <View className={'header-bg'} style={{
left={ height: !stickyStatus ? '180px' : `${(statusBarHeight || 0) + 44}px`,
!IsLogin ? ( paddingBottom: !stickyStatus ? '12px' : '0px'
<View style={{display: 'flex', alignItems: 'center'}}> }}>
<Button style={{color: '#ffffff'}} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}> {/* 只在非吸顶状态下显示搜索框 */}
<Space> {!stickyStatus && <MySearch statusBarHeight={statusBarHeight} />}
<Avatar </View>
size="22" <NavBar
src={getWebsiteLogo()} style={{
/> marginTop: `${statusBarHeight}px`,
<Text style={{color: '#ffffff'}}>{getWebsiteName()}</Text> marginBottom: '0px',
<TriangleDown size={9} className={'text-white'}/> backgroundColor: 'transparent'
</Space> }}
</Button> onBackClick={() => {
</View> }}
) : ( left={
<View style={{display: 'flex', alignItems: 'center', gap: '8px'}}> !IsLogin ? (
<Avatar <View style={{display: 'flex', alignItems: 'center'}}>
size="22" <Button style={{color: '#ffffff'}} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
src={getWebsiteLogo()} <Space>
/> <Avatar
<Text className={'text-white'}>{getWebsiteName()}</Text> size="22"
<TriangleDown className={'text-white'} size={9}/> src={getWebsiteLogo()}
</View> />
)}> <Text style={{color: '#ffffff'}}>{getWebsiteName()}</Text>
{/*<QRLoginButton />*/} <TriangleDown size={9} className={'text-white'}/>
</NavBar> </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>
)}>
{/*<QRLoginButton />*/}
</NavBar>
</Sticky>
</> </>
) )
} }

View File

@@ -18,3 +18,59 @@ page {
height: 70px; height: 70px;
} }
} }
/* 轮播图容器样式,确保支持两种滑动操作 */
.banner-swiper-container {
touch-action: pan-y !important; /* 允许垂直滑动 */
.nut-swiper {
touch-action: pan-y !important; /* 允许垂直滑动 */
.nut-swiper-item {
touch-action: pan-x pan-y !important; /* 允许水平和垂直滑动 */
image {
pointer-events: auto; /* 确保图片点击事件正常 */
touch-action: manipulation; /* 优化触摸操作 */
}
}
}
/* 为Swiper容器添加特殊处理 */
.nut-swiper--horizontal {
touch-action: pan-y !important; /* 允许垂直滑动 */
}
}
/* 吸顶状态下的样式 */
.nutui-sticky--fixed {
.header-bg {
height: 100%;
}
}
/* 为Swiper添加更精确的触摸控制 */
.nut-swiper {
touch-action: pan-y !important;
.nut-swiper-inner {
touch-action: pan-x pan-y !important;
}
}
/* 自定义Swiper样式 */
.custom-swiper {
touch-action: pan-y !important;
.nut-swiper-item {
touch-action: pan-x pan-y !important;
}
}
/* 确保Swiper内部元素不会阻止页面滚动 */
.banner-swiper-container,
.custom-swiper,
.nut-swiper,
.nut-swiper-item {
-webkit-overflow-scrolling: touch; /* iOS平台启用硬件加速滚动 */
}

View File

@@ -4,19 +4,16 @@ import Taro from '@tarojs/taro';
import {useShareAppMessage} from "@tarojs/taro" import {useShareAppMessage} from "@tarojs/taro"
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {getShopInfo} from "@/api/layout"; import {getShopInfo} from "@/api/layout";
import {Sticky} from '@nutui/nutui-react-taro'
import Menu from "./Menu"; import Menu from "./Menu";
import Banner from "./Banner"; import Banner from "./Banner";
import {checkAndHandleInviteRelation, hasPendingInvite} from "@/utils/invite"; import {checkAndHandleInviteRelation, hasPendingInvite} from "@/utils/invite";
import './index.scss' import './index.scss'
// import GoodsList from "./GoodsList";
function Home() { function Home() {
// 吸顶状态 // 吸顶状态
// const [stickyStatus, setStickyStatus] = useState<boolean>(false) // const [stickyStatus, setStickyStatus] = useState<boolean>(false)
// Tabs粘性状态 // Tabs粘性状态
const [tabsStickyStatus, setTabsStickyStatus] = useState<boolean>(false) const [_, setTabsStickyStatus] = useState<boolean>(false)
useShareAppMessage(() => { useShareAppMessage(() => {
// 获取当前用户ID用于生成邀请链接 // 获取当前用户ID用于生成邀请链接
@@ -157,20 +154,13 @@ function Home() {
return ( return (
<> <>
{/* Header区域 - 根据Tabs粘性状态决定是否固定 */} {/* Header区域 - 现在由Header组件内部处理吸顶逻辑 */}
{tabsStickyStatus ? ( <Header />
<Sticky threshold={0}>
<Header stickyStatus={tabsStickyStatus}/>
</Sticky>
) : (
<Header stickyStatus={tabsStickyStatus}/>
)}
<div className={'flex flex-col mt-12'}> <div className={'flex flex-col mt-12'}>
<Menu/> <Menu/>
<Banner/> <Banner/>
<BestSellers onStickyChange={handleTabsStickyChange}/> <BestSellers onStickyChange={handleTabsStickyChange}/>
{/*<GoodsList/>*/}
</div> </div>
</> </>
) )

View File

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

View File

@@ -3,25 +3,16 @@ import navTo from "@/utils/common";
import {View, Text} from '@tarojs/components' import {View, Text} from '@tarojs/components'
import {ArrowRight, Reward, Setting} from '@nutui/icons-react-taro' import {ArrowRight, Reward, Setting} from '@nutui/icons-react-taro'
import {useUser} from '@/hooks/useUser' import {useUser} from '@/hooks/useUser'
import {useEffect, useState} from "react";
import {useDealerUser} from "@/hooks/useDealerUser"; import {useDealerUser} from "@/hooks/useDealerUser";
import {useThemeStyles} from "@/hooks/useTheme"; import {useThemeStyles} from "@/hooks/useTheme";
import {configWebsiteField} from "@/api/cms/cmsWebsiteField"; import { useConfig } from "@/hooks/useConfig"; // 使用新的自定义Hook
import {Config} from "@/api/cms/cmsWebsiteField/model";
const IsDealer = () => { const IsDealer = () => {
const themeStyles = useThemeStyles(); const themeStyles = useThemeStyles();
const [config, setConfig] = useState<Config>() const { config } = useConfig(); // 使用新的Hook
const {isSuperAdmin} = useUser(); const {isSuperAdmin} = useUser();
const {dealerUser} = useDealerUser() const {dealerUser} = useDealerUser()
useEffect(() => {
configWebsiteField().then(data => {
console.log(data)
setConfig(data)
})
}, [])
console.log(dealerUser,'dealerUserdealerUserdealerUserdealerUserdealerUser')
/** /**
* 管理中心 * 管理中心
*/ */

View File

@@ -1,4 +1,4 @@
import {Avatar, Tag, Space} from '@nutui/nutui-react-taro' import {Avatar, Tag, Space, Button} from '@nutui/nutui-react-taro'
import {View, Text} from '@tarojs/components' import {View, Text} from '@tarojs/components'
import {getUserInfo, getWxOpenId} from '@/api/layout'; import {getUserInfo, getWxOpenId} from '@/api/layout';
import Taro from '@tarojs/taro'; import Taro from '@tarojs/taro';
@@ -179,22 +179,35 @@ const UserCard = forwardRef<any, any>((_, ref) => {
className={'user-card w-full flex flex-col justify-around rounded-xl'} 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={'user-card-header flex w-full justify-between items-center pt-4'}>
<View className={'flex items-center mx-4'} onClick={handleGetPhoneNumber}> <View className={'flex items-center mx-4'}>
<Avatar size="large" {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 || ''} src={userInfo?.avatar || ''}
shape="round"/> />
<View className={'user-info flex flex-col px-2'}> <View className={'flex flex-col'}>
<View className={'py-1 text-white font-bold'}>{getDisplayName()}</View> <Text style={{color: '#ffffff'}}>{getDisplayName() || '点击登录'}</Text>
{IsLogin ? ( <View><Tag type="success">{getRoleName()}</Tag></View>
<View className={'grade text-xs py-1'}> </View>
<Tag type="success" round>
<Text className={'p-1'}>
{getRoleName()}
</Text>
</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> </View>
<Space style={{ <Space style={{
marginTop: '30px', marginTop: '30px',

View File

@@ -1,102 +0,0 @@
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, useState} from "react";
import {useDealerUser} from "@/hooks/useDealerUser";
import {configWebsiteField} from "@/api/cms/cmsWebsiteField";
import {Config} from "@/api/cms/cmsWebsiteField/model";
const UserCell = () => {
const {isSuperAdmin} = useUser();
const {dealerUser} = useDealerUser()
const [config, setConfig] = useState<Config>()
useEffect(() => {
configWebsiteField().then(data => {
console.log(data)
setConfig(data)
})
}, [])
/**
* 管理中心
*/
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'}>{config?.vipText}VIP</Text>
<Text className={'text-white opacity-80 pl-3'}>{config?.vipComments}</Text>
</View>
}
extra={<ArrowRight color="#cccccc" size={18}/>}
onClick={() => navTo('/dealer/apply/add', true)}
/>
</View>
</>
)
}
export default UserCell

View File

@@ -1,277 +0,0 @@
import {Avatar, Tag, Space, Button} from '@nutui/nutui-react-taro'
import {View, Text, Image} from '@tarojs/components'
import {getUserInfo, getWxOpenId} from '@/api/layout';
import Taro from '@tarojs/taro';
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 {getStoredInviteParams} from "@/utils/invite";
import UnifiedQRButton from "@/components/UnifiedQRButton";
const UserCard = forwardRef<any, any>((_, ref) => {
const {data, refresh} = useUserData()
const {getDisplayName, getRoleName} = useUser();
const [IsLogin, setIsLogin] = useState<boolean>(false)
const [userInfo, setUserInfo] = useState<User>()
// 下拉刷新
const handleRefresh = async () => {
await refresh()
Taro.showToast({
title: '刷新成功',
icon: 'success'
})
}
// 暴露方法给父组件
useImperativeHandle(ref, () => ({
handleRefresh
}))
useEffect(() => {
// Taro.getSetting获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
Taro.getSetting({
success: (res) => {
if (res.authSetting['scope.userInfo']) {
// 用户已经授权过,可以直接获取用户信息
console.log('用户已经授权过,可以直接获取用户信息')
reload();
} else {
// 用户未授权,需要弹出授权窗口
console.log('用户未授权,需要弹出授权窗口')
showAuthModal();
}
}
});
}, []);
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)
// 获取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
// 获取存储的邀请参数
const inviteParams = getStoredInviteParams()
const refereeId = inviteParams?.inviter ? parseInt(inviteParams.inviter) : 0
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: refereeId, // 使用解析出的推荐人ID
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="relative">
<View
className={'user-card w-full flex flex-col justify-around rounded-xl'}
style={{
background: 'linear-gradient(to bottom, #ffffff, #ffffff)',
height: '170px',
}}
>
<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 || 'https://oss.wsdns.cn/20250623/62f830b85edb4a7293b8948c25e6f987.jpeg'}
shape="round"/>
) : (
<Button className={'text-black'} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
<Avatar size="large"
src={userInfo?.avatar || 'https://oss.wsdns.cn/20250623/62f830b85edb4a7293b8948c25e6f987.jpeg'}
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>
<Space style={{
marginTop: '30px',
marginRight: '10px'
}}>
{/*统一扫码入口 - 支持登录和核销*/}
<UnifiedQRButton
text="扫码"
size="small"
onSuccess={(result) => {
console.log('统一扫码成功:', result);
// 根据扫码类型给出不同的提示
if (result.type === 'verification') {
// 核销成功,可以显示更多信息或跳转到详情页
Taro.showModal({
title: '核销成功',
content: `已成功核销的品类:${result.data.goodsName || '礼品卡'},面值¥${result.data.faceValue}`
});
}
}}
onError={(error) => {
console.error('统一扫码失败:', error);
}}
/>
</Space>
</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
className="absolute top-0 right-0 overflow-hidden"
style={{
borderRadius: "0 0.75rem 0 0"
}}
onClick={() => navTo('/user/profile/profile', true)}
>
<Image
src="https://oss.wsdns.cn/20250913/7c3de38b377344b89131aba40214f63f.png"
style={{
width: "200rpx"
}}
mode="widthFix"
/>
</View>
</View>
</View>
</View>
)
})
export default UserCard;

View File

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

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

@@ -1,145 +0,0 @@
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,
ShoppingAdd,
Service
} from '@nutui/icons-react-taro'
import {useUser} from "@/hooks/useUser";
const UserCell = () => {
const {logoutUser} = 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}
>
<Grid.Item text="企业采购" onClick={() => navTo('/user/poster/poster', 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="联系我们">
<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/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/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/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={'关于我们'} 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

View File

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

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

View File

@@ -1,9 +0,0 @@
.header-bg{
//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;
}

View File

@@ -1,46 +0,0 @@
import {useEffect, useRef} from 'react'
import {PullToRefresh} from '@nutui/nutui-react-taro'
import UserCard from "./components/UserCard";
import UserOrder from "./components/UserOrder";
import UserFooter from "./components/UserFooter";
import {useUserData} from "@/hooks/useUserData";
import './user.scss'
import IsDealer from "./components/IsDealer";
import UserGrid from "@/pages/user/components/UserGrid";
function User() {
const { refresh } = useUserData()
const userCardRef = useRef<any>()
// 下拉刷新处理
const handleRefresh = async () => {
await refresh()
// 如果 UserCard 组件有自己的刷新方法,也可以调用
if (userCardRef.current?.handleRefresh) {
await userCardRef.current.handleRefresh()
}
}
useEffect(() => {
}, []);
return (
<PullToRefresh
onRefresh={handleRefresh}
headHeight={60}
>
<div className={'w-full'} style={{
background: 'linear-gradient(to bottom, #e9fff2, #f9fafb)'
}}>
<UserCard ref={userCardRef}/>
<UserOrder/>
<IsDealer/>
<UserGrid/>
<UserFooter/>
</div>
</PullToRefresh>
)
}
export default User

View File

@@ -1,5 +1,6 @@
import {Image} from '@nutui/nutui-react-taro' import {Image} from '@nutui/nutui-react-taro'
import {Share} from '@nutui/icons-react-taro' import {Share} from '@nutui/icons-react-taro'
import {View, Text} from '@tarojs/components'
import Taro from '@tarojs/taro' import Taro from '@tarojs/taro'
import './GoodsList.scss' import './GoodsList.scss'
@@ -8,43 +9,45 @@ const GoodsList = (props: any) => {
return ( return (
<> <>
<div className={'py-3'}> <View className={'py-3'}>
<div className={'flex flex-col justify-between items-center rounded-lg px-2'}> <View className={'flex flex-col justify-between items-center rounded-lg px-2'}>
{props.data?.map((item, index) => { {props.data?.map((item: any, index: number) => {
return ( return (
<div key={index} className={'flex flex-col rounded-lg bg-white shadow-sm w-full mb-5'}> <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} <Image src={item.image} mode={'aspectFit'} lazyLoad={false}
radius="10px 10px 0 0" height="180" radius="10px 10px 0 0" height="180"
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/> onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
<div className={'flex flex-col p-2 rounded-lg'}> <View className={'flex flex-col p-2 rounded-lg'}>
<div> <View>
<div className={'car-no text-sm'}>{item.name}</div> <View className={'car-no text-sm'}>{item.name}</View>
<div className={'flex justify-between text-xs py-1'}> <View className={'flex justify-between text-xs py-1'}>
<span className={'text-orange-500'}>{item.comments}</span> <Text className={'text-orange-500'}>{item.comments}</Text>
<span className={'text-gray-400'}> {item.sales}</span> <Text className={'text-gray-400'}> {item.sales}</Text>
</div> </View>
<div className={'flex justify-between items-center py-2'}> <View className={'flex justify-between items-center py-2'}>
<div className={'flex text-red-500 text-xl items-baseline'}> <View className={'flex text-red-500 text-xl items-baseline'}>
<span className={'text-xs'}></span> <Text className={'text-xs'}></Text>
<span className={'font-bold text-2xl'}>{item.price}</span> <Text className={'font-bold text-2xl'}>{item.price}</Text>
</div> <Text className={'text-xs px-1'}></Text>
<div className={'buy-btn'}> <Text className={'text-xs text-gray-400 line-through'}>{item.salePrice}</Text>
<div className={'cart-icon'}> </View>
<View className={'buy-btn'}>
<View className={'cart-icon'}>
<Share size={20} className={'mx-4 mt-2'} <Share size={20} className={'mx-4 mt-2'}
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/> onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
</div> </View>
<div className={'text-white pl-4 pr-5'} <View className={'text-white pl-4 pr-5'}
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}> onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}>
</div> </View>
</div> </View>
</div> </View>
</div> </View>
</div> </View>
</div> </View>
) )
})} })}
</div> </View>
</div> </View>
</> </>
) )
} }

View File

@@ -0,0 +1,4 @@
export default definePageConfig({
navigationBarTitleText: '全部评论',
navigationBarTextStyle: 'black'
})

View File

View File

@@ -15,3 +15,11 @@ rich-text img {
.no-margin { .no-margin {
margin: 0 !important; /* 使用 !important 来确保覆盖默认样式 */ margin: 0 !important; /* 使用 !important 来确保覆盖默认样式 */
} }
/* 文本截断样式 */
.truncate {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
}

View File

@@ -1,8 +1,8 @@
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {Image, Divider, Badge} from "@nutui/nutui-react-taro"; import {Image, Badge, Popup, CellGroup, Cell} from "@nutui/nutui-react-taro";
import {ArrowLeft, Headphones, Share, Cart} from "@nutui/icons-react-taro"; import {ArrowLeft, Headphones, Share, Cart, ArrowRight} from "@nutui/icons-react-taro";
import Taro, {useShareAppMessage} from "@tarojs/taro"; import Taro, {useShareAppMessage} from "@tarojs/taro";
import {RichText, View} from '@tarojs/components' import {RichText, View, Text} from '@tarojs/components'
import {ShopGoods} from "@/api/shop/shopGoods/model"; import {ShopGoods} from "@/api/shop/shopGoods/model";
import {getShopGoods} from "@/api/shop/shopGoods"; import {getShopGoods} from "@/api/shop/shopGoods";
import {listShopGoodsSpec} from "@/api/shop/shopGoodsSpec"; import {listShopGoodsSpec} from "@/api/shop/shopGoodsSpec";
@@ -14,21 +14,30 @@ import navTo, {wxParse} from "@/utils/common";
import SpecSelector from "@/components/SpecSelector"; import SpecSelector from "@/components/SpecSelector";
import "./index.scss"; import "./index.scss";
import {useCart} from "@/hooks/useCart"; import {useCart} from "@/hooks/useCart";
import {useConfig} from "@/hooks/useConfig";
const GoodsDetail = () => { const GoodsDetail = () => {
const [statusBarHeight, setStatusBarHeight] = useState<number>(44);
const [windowWidth, setWindowWidth] = useState<number>(390)
const [goods, setGoods] = useState<ShopGoods | null>(null); const [goods, setGoods] = useState<ShopGoods | null>(null);
const [files, setFiles] = useState<any[]>([]); const [files, setFiles] = useState<any[]>([]);
const [specs, setSpecs] = useState<ShopGoodsSpec[]>([]); const [specs, setSpecs] = useState<ShopGoodsSpec[]>([]);
const [skus, setSkus] = useState<ShopGoodsSku[]>([]); const [skus, setSkus] = useState<ShopGoodsSku[]>([]);
const [showSpecSelector, setShowSpecSelector] = useState(false); const [showSpecSelector, setShowSpecSelector] = useState(false);
const [specAction, setSpecAction] = useState<'cart' | 'buy'>('cart'); const [specAction, setSpecAction] = useState<'cart' | 'buy'>('cart');
const [showBottom, setShowBottom] = useState(false)
const [bottomItem, setBottomItem] = useState<any>({
title: '',
content: ''
})
// const [selectedSku, setSelectedSku] = useState<ShopGoodsSku | null>(null); // const [selectedSku, setSelectedSku] = useState<ShopGoodsSku | null>(null);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const router = Taro.getCurrentInstance().router; const router = Taro.getCurrentInstance().router;
const goodsId = router?.params?.id; const goodsId = router?.params?.id;
// 使用购物车Hook // 使用购物车Hook
const {cartCount, addToCart} = useCart(); const {cartCount, addToCart} = useCart()
const {config} = useConfig()
// 处理加入购物车 // 处理加入购物车
const handleAddToCart = () => { const handleAddToCart = () => {
@@ -117,7 +126,21 @@ const GoodsDetail = () => {
} }
}; };
const openBottom = (title: string, content: string) => {
setBottomItem({
title,
content
})
setShowBottom(true)
}
useEffect(() => { useEffect(() => {
Taro.getSystemInfo({
success: (res) => {
setWindowWidth(res.windowWidth)
setStatusBarHeight(Number(res.statusBarHeight) + 5)
},
});
if (goodsId) { if (goodsId) {
setLoading(true); setLoading(true);
@@ -166,7 +189,7 @@ const GoodsDetail = () => {
return { return {
title: goods?.name || '精选商品', title: goods?.name || '精选商品',
path: `/shop/goodsDetail/index?id=${goodsId}`, path: `/shop/goodsDetail/index?id=${goodsId}`,
imageUrl: goods?.image, // 分享图片 imageUrl: goods?.image ? `${goods.image}?x-oss-process=image/resize,w_500,h_400,m_fill` : undefined, // 分享图片调整为5:4比例
success: function (res: any) { success: function (res: any) {
console.log('分享成功', res); console.log('分享成功', res);
Taro.showToast({ Taro.showToast({
@@ -187,12 +210,12 @@ const GoodsDetail = () => {
}); });
if (!goods || loading) { if (!goods || loading) {
return <div>...</div>; return <View>...</View>;
} }
return ( return (
<div className={"py-0"}> <View className={"py-0"}>
<div <View
className={ className={
"fixed z-10 bg-white flex justify-center items-center font-bold shadow-sm opacity-70" "fixed z-10 bg-white flex justify-center items-center font-bold shadow-sm opacity-70"
} }
@@ -200,36 +223,36 @@ const GoodsDetail = () => {
borderRadius: "100%", borderRadius: "100%",
width: "32px", width: "32px",
height: "32px", height: "32px",
top: "50px", top: statusBarHeight + 'px',
left: "10px", left: "10px",
}} }}
onClick={() => Taro.navigateBack()} onClick={() => Taro.navigateBack()}
> >
<ArrowLeft size={14}/> <ArrowLeft size={14}/>
</div> </View>
<div className={ <View className={
"fixed z-10 bg-white flex justify-center items-center font-bold shadow-sm opacity-90" "fixed z-10 bg-white flex justify-center items-center font-bold shadow-sm opacity-90"
} }
style={{ style={{
borderRadius: "100%", borderRadius: "100%",
width: "32px", width: "32px",
height: "32px", height: "32px",
top: "50px", top: statusBarHeight + 'px',
right: "110px", right: "110px",
}} }}
onClick={() => Taro.switchTab({url: `/pages/cart/cart`})}> onClick={() => Taro.switchTab({url: `/pages/cart/cart`})}>
<Badge value={cartCount} top="-2" right="2"> <Badge value={cartCount} top="-2" right="2">
<div style={{display: 'flex', alignItems: 'center'}}> <View style={{display: 'flex', alignItems: 'center'}}>
<Cart size={16}/> <Cart size={16}/>
</div> </View>
</Badge> </Badge>
</div> </View>
{ {
files.length > 0 && ( files.length > 0 && (
<Swiper defaultValue={0} indicator height={'350px'}> <Swiper defaultValue={0} indicator height={windowWidth}>
{files.map((item) => ( {files.map((item) => (
<Swiper.Item key={item}> <Swiper.Item key={item}>
<Image width="100%" height={'100%'} src={item.url} mode={'scaleToFill'} lazyLoad={false}/> <Image width={windowWidth} height={windowWidth} src={item.url} mode={'scaleToFill'} lazyLoad={false}/>
</Swiper.Item> </Swiper.Item>
))} ))}
</Swiper> </Swiper>
@@ -245,69 +268,118 @@ const GoodsDetail = () => {
/> />
) )
} }
<div <View
className={"flex flex-col justify-between items-center rounded-lg px-2"} className={"flex flex-col justify-between items-center"}
> >
<div <View
className={ className={
"flex flex-col rounded-lg bg-white shadow-sm w-full mt-2" "flex flex-col bg-white w-full"
} }
> >
<div className={"flex flex-col p-2 rounded-lg"}> <View className={"flex flex-col p-3 rounded-lg"}>
<> <>
<div className={'flex justify-between'}> <View className={'flex justify-between'}>
<div className={'flex text-red-500 text-xl items-baseline'}> <View className={'flex text-red-500 text-xl items-baseline'}>
<span className={'text-xs'}></span> <Text className={'text-xs'}></Text>
<span className={'font-bold text-2xl'}>{goods.price}</span> <Text className={'font-bold text-2xl'}>{goods.price}</Text>
</div> <Text className={'text-xs px-1'}></Text>
<Text className={'text-xs text-gray-400 line-through'}>{goods.salePrice}</Text>
</View>
<span className={"text-gray-400 text-xs"}> {goods.sales}</span> <span className={"text-gray-400 text-xs"}> {goods.sales}</span>
</div> </View>
<div className={'flex justify-between items-center'}> <View className={'flex justify-between items-center'}>
<div className={'goods-info'}> <View className={'goods-info'}>
<div className={"car-no text-lg"}> <View className={"car-no text-lg"}>
{goods.name} {goods.name}
</div> </View>
<div className={"flex justify-between text-xs py-1"}> <View className={"flex justify-between text-xs py-1"}>
<span className={"text-orange-500"}> <span className={"text-orange-500"}>
{goods.comments} {goods.comments}
</span> </span>
</div> </View>
</div> </View>
<View> <View>
<button <button
className={'flex flex-col justify-center items-center text-gray-500 px-1 gap-1 text-nowrap whitespace-nowrap'} className={'flex flex-col justify-center items-center bg-white text-gray-500 px-1 gap-1 text-nowrap whitespace-nowrap'}
open-type="share"><Share open-type="share">
size={20}/> <Share size={20}/>
<span className={'text-xs'}></span> <span className={'text-xs'}></span>
</button> </button>
</View> </View>
</div> </View>
</> </>
</div> </View>
</div>
<div className={"mt-2 py-2"}>
<Divider></Divider>
<RichText nodes={goods.content || '内容详情'}/>
</div>
</div>
{/*底部购买按钮*/}
<div className={'fixed bg-white w-full bottom-0 left-0 pt-4 pb-10'}>
<View className={'btn-bar flex justify-between items-center'}>
<div className={'flex justify-center items-center mx-4'}>
<button open-type="contact" className={'flex items-center'}>
<Headphones size={18} style={{marginRight: '4px'}}/>
</button>
</div>
<div className={'buy-btn mx-4'}>
<div className={'cart-add px-4 text-sm'}
onClick={() => handleAddToCart()}>
</div>
<div className={'cart-buy pl-4 pr-5 text-sm'}
onClick={() => handleBuyNow()}>
</div>
</div>
</View> </View>
</div> <View className={'w-full'}>
{
config?.deliveryText && (
<CellGroup>
<Cell title={'配送'} extra={
<View className="flex items-center">
<Text className={'truncate max-w-56 inline-block'}>{config?.deliveryText || '14:30下单明天配送'}</Text>
<ArrowRight color="#cccccc" size={15}/>
</View>
} onClick={() => openBottom('配送', `${config?.deliveryText}`)}/>
<Cell title={'保障'} extra={
<View className="flex items-center">
<Text className={'truncate max-w-56 inline-block'}>{config?.guaranteeText || '支持7天无理由退货'}</Text>
<ArrowRight color="#cccccc" size={15}/>
</View>
} onClick={() => openBottom('保障', `${config?.guaranteeText}`)}/>
</CellGroup>
)
}
{config?.openComments == '1' && (
<CellGroup>
<Cell title={'用户评价 (0)'} extra={
<>
<Text></Text>
<ArrowRight color="#cccccc" size={15}/>
</>
} onClick={() => navTo(`/shop/comments/index`)}/>
<Cell className={'flex h-32 bg-white p-4'}>
</Cell>
</CellGroup>
)}
</View>
<View className={'w-full'}>
<RichText nodes={goods.content || '内容详情'}/>
<View className={'h-24'}></View>
</View>
</View>
{/*底部弹窗*/}
<Popup
visible={showBottom}
position="bottom"
onClose={() => {
setShowBottom(false)
}}
lockScroll
>
<View className={'flex flex-col p-4'}>
<Text className={'font-bold text-sm'}>{bottomItem.title}</Text>
<Text className={'text-gray-500 my-2'}>{bottomItem.content}</Text>
</View>
</Popup>
{/*底部购买按钮*/}
<View className={'fixed bg-white w-full bottom-0 left-0 pt-4 pb-6'}>
<View className={'btn-bar flex justify-between items-center'}>
<View className={'flex justify-center items-center mx-4'}>
<button open-type="contact" className={'flex items-center text-sm py-2'}>
<Headphones size={16} style={{marginRight: '4px'}}/>
</button>
</View>
<View className={'buy-btn mx-4'}>
<View className={'cart-add px-4 text-sm'}
onClick={() => handleAddToCart()}>
</View>
<View className={'cart-buy pl-4 pr-5 text-sm'}
onClick={() => handleBuyNow()}>
</View>
</View>
</View>
</View>
{/* 规格选择器 */} {/* 规格选择器 */}
{showSpecSelector && ( {showSpecSelector && (
@@ -320,7 +392,7 @@ const GoodsDetail = () => {
onClose={() => setShowSpecSelector(false)} onClose={() => setShowSpecSelector(false)}
/> />
)} )}
</div> </View>
); );
}; };

View File

@@ -74,20 +74,20 @@ const OrderConfirm = () => {
const getGoodsTotal = () => { const getGoodsTotal = () => {
if (!goods) return 0 if (!goods) return 0
const price = parseFloat(goods.price || '0') const price = parseFloat(goods.price || '0')
const total = price * quantity // const total = price * quantity
// 🔍 详细日志,用于排查数值精度问题 // 🔍 详细日志,用于排查数值精度问题
console.log('💵 商品总价计算:', { // console.log('💵 商品总价计算:', {
goodsPrice: goods.price, // goodsPrice: goods.price,
goodsPriceType: typeof goods.price, // goodsPriceType: typeof goods.price,
parsedPrice: price, // parsedPrice: price,
quantity: quantity, // quantity: quantity,
total: total, // total: total,
totalFixed2: total.toFixed(2), // totalFixed2: total.toFixed(2),
totalString: total.toString() // totalString: total.toString()
}) // })
return total return price * quantity
} }
// 计算优惠券折扣 // 计算优惠券折扣
@@ -418,9 +418,9 @@ const OrderConfirm = () => {
couponId: parseInt(String(bestCoupon.id), 10) couponId: parseInt(String(bestCoupon.id), 10)
} }
); );
console.log('🎯 使用推荐优惠券的订单数据:', updatedOrderData); console.log('🎯 使用推荐优惠券的订单数据:', updatedOrderData);
// 执行支付 // 执行支付
await PaymentHandler.pay(updatedOrderData, currentPaymentType); await PaymentHandler.pay(updatedOrderData, currentPaymentType);
return; // 提前返回,避免重复执行支付 return; // 提前返回,避免重复执行支付
@@ -437,7 +437,7 @@ const OrderConfirm = () => {
quantity, quantity,
address.id, address.id,
{ {
comments: goods.name, comments: '时里院子市集',
deliveryType: 0, deliveryType: 0,
buyerRemarks: orderRemark, buyerRemarks: orderRemark,
// 🔧 确保 couponId 是正确的数字类型,且不传递 undefined // 🔧 确保 couponId 是正确的数字类型,且不传递 undefined

View File

@@ -40,6 +40,33 @@ const OrderDetail = () => {
} }
}; };
// 申请退款
const handleApplyRefund = async () => {
if (order) {
try {
// 更新订单状态为"退款申请中"
await updateShopOrder({
orderId: order.orderId,
orderStatus: 4 // 退款申请中
});
// 更新本地状态
setOrder(prev => prev ? {...prev, orderStatus: 4} : null);
// 跳转到退款申请页面
Taro.navigateTo({
url: `/user/order/refund/index?orderId=${order.orderId}&orderNo=${order.orderNo}`
});
} catch (error) {
console.error('更新订单状态失败:', error);
Taro.showToast({
title: '操作失败,请重试',
icon: 'none'
});
}
}
};
const getOrderStatusText = (order: ShopOrder) => { const getOrderStatusText = (order: ShopOrder) => {
// 优先检查订单状态 // 优先检查订单状态
if (order.orderStatus === 2) return '已取消'; if (order.orderStatus === 2) return '已取消';
@@ -162,7 +189,7 @@ const OrderDetail = () => {
<Space> <Space>
{!order.payStatus && <Button onClick={() => console.log('取消订单')}></Button>} {!order.payStatus && <Button onClick={() => console.log('取消订单')}></Button>}
{!order.payStatus && <Button type="primary" onClick={() => console.log('立即支付')}></Button>} {!order.payStatus && <Button type="primary" onClick={() => console.log('立即支付')}></Button>}
{order.orderStatus === 1 && <Button onClick={() => console.log('申请退款')}>退</Button>} {order.orderStatus === 1 && <Button onClick={handleApplyRefund}>退</Button>}
{order.deliveryStatus === 20 && {order.deliveryStatus === 20 &&
<Button type="primary" onClick={() => console.log('确认收货')}></Button>} <Button type="primary" onClick={() => console.log('确认收货')}></Button>}
</Space> </Space>

View File

@@ -9,15 +9,14 @@ import {listCmsNavigation} from "@/api/cms/cmsNavigation";
import {View, RichText} from '@tarojs/components' import {View, RichText} from '@tarojs/components'
import {listCmsDesign} from "@/api/cms/cmsDesign"; import {listCmsDesign} from "@/api/cms/cmsDesign";
import {CmsDesign} from "@/api/cms/cmsDesign/model"; import {CmsDesign} from "@/api/cms/cmsDesign/model";
import {type Config} from "@/api/cms/cmsWebsiteField/model"; import { useConfig } from "@/hooks/useConfig"; // 使用新的自定义Hook
import {configWebsiteField} from "@/api/cms/cmsWebsiteField";
const Helper = () => { const Helper = () => {
const [nav, setNav] = useState<CmsNavigation>() const [nav, setNav] = useState<CmsNavigation>()
const [design, setDesign] = useState<CmsDesign>() const [design, setDesign] = useState<CmsDesign>()
const [category, setCategory] = useState<CmsNavigation[]>([]) const [category, setCategory] = useState<CmsNavigation[]>([])
const [config, setConfig] = useState<Config>() const { config } = useConfig(); // 使用新的Hook
const reload = async () => { const reload = async () => {
const navs = await listCmsNavigation({model: 'page', parentId: 0}); const navs = await listCmsNavigation({model: 'page', parentId: 0});
@@ -33,9 +32,7 @@ const Helper = () => {
category[index].articles = await listCmsArticle({categoryId: item.navigationId}); category[index].articles = await listCmsArticle({categoryId: item.navigationId});
}) })
setCategory(category) setCategory(category)
// 查询字段 // 注意config 现在通过 useConfig Hook 获取,不再在这里调用 configWebsiteField
const configInfo = await configWebsiteField({})
setConfig(configInfo)
} }
} }

View File

@@ -284,7 +284,7 @@ function OrderList(props: OrderListProps) {
await updateShopOrder({ await updateShopOrder({
...orderToConfirmReceive, ...orderToConfirmReceive,
deliveryStatus: 30, // 已收货 deliveryStatus: 20, // 已收货
orderStatus: 1 // 已完成 orderStatus: 1 // 已完成
}); });
@@ -316,11 +316,30 @@ function OrderList(props: OrderListProps) {
}; };
// 申请退款 (待发货状态) // 申请退款 (待发货状态)
const applyRefund = (order: ShopOrder) => { const applyRefund = async (order: ShopOrder) => {
// 跳转到退款申请页面 try {
Taro.navigateTo({ // 更新订单状态为"退款申请中"
url: `/user/order/refund/index?orderId=${order.orderId}&orderNo=${order.orderNo}` await updateShopOrder({
}); orderId: order.orderId,
orderStatus: 4 // 退款申请中
});
// 更新本地状态
setDataSource(prev => prev.map(item =>
item.orderId === order.orderId ? {...item, orderStatus: 4} : item
));
// 跳转到退款申请页面
Taro.navigateTo({
url: `/user/order/refund/index?orderId=${order.orderId}&orderNo=${order.orderNo}`
});
} catch (error) {
console.error('更新订单状态失败:', error);
Taro.showToast({
title: '操作失败,请重试',
icon: 'none'
});
}
}; };
// 查看物流 (待收货状态) // 查看物流 (待收货状态)
@@ -738,20 +757,20 @@ function OrderList(props: OrderListProps) {
)} )}
{/* 待发货状态:显示申请退款 */} {/* 待发货状态:显示申请退款 */}
{/*{item.payStatus && item.deliveryStatus === 10 && item.orderStatus !== 2 && item.orderStatus !== 4 && (*/} {item.payStatus && item.deliveryStatus === 10 && item.orderStatus !== 2 && item.orderStatus !== 4 && (
{/* <Button size={'small'} onClick={(e) => {*/} <Button size={'small'} onClick={(e) => {
{/* e.stopPropagation();*/} e.stopPropagation();
{/* applyRefund(item);*/} applyRefund(item);
{/* }}>申请退款</Button>*/} }}>退</Button>
{/*)}*/} )}
{/* 待收货状态:显示查看物流和确认收货 */} {/* 待收货状态:显示查看物流和确认收货 */}
{item.deliveryStatus === 20 && item.orderStatus !== 2 && ( {item.deliveryStatus === 20 && item.orderStatus !== 2 && (
<Space> <Space>
{/*<Button size={'small'} onClick={(e) => {*/} <Button size={'small'} onClick={(e) => {
{/* e.stopPropagation();*/} e.stopPropagation();
{/* viewLogistics(item);*/} viewLogistics(item);
{/*}}>查看物流</Button>*/} }}></Button>
<Button size={'small'} type="primary" onClick={(e) => { <Button size={'small'} type="primary" onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
confirmReceive(item); confirmReceive(item);
@@ -770,10 +789,10 @@ function OrderList(props: OrderListProps) {
{/* e.stopPropagation();*/} {/* e.stopPropagation();*/}
{/* evaluateGoods(item);*/} {/* evaluateGoods(item);*/}
{/*}}>评价商品</Button>*/} {/*}}>评价商品</Button>*/}
{/*<Button size={'small'} onClick={(e) => {*/} <Button size={'small'} onClick={(e) => {
{/* e.stopPropagation();*/} e.stopPropagation();
{/* applyRefund(item);*/} applyRefund(item);
{/*}}>申请退款</Button>*/} }}>退</Button>
</Space> </Space>
)} )}

View File

@@ -13,6 +13,8 @@ import {
Empty, Empty,
InputNumber InputNumber
} from '@nutui/nutui-react-taro' } from '@nutui/nutui-react-taro'
import { applyAfterSale } from '@/api/afterSale'
import { updateShopOrder } from '@/api/shop/shopOrder'
import './index.scss' import './index.scss'
// 订单商品信息 // 订单商品信息
@@ -232,23 +234,55 @@ const RefundPage: React.FC = () => {
setSubmitting(true) setSubmitting(true)
// 模拟API调用 // 构造请求参数
await new Promise(resolve => setTimeout(resolve, 2000)) const params = {
orderId: orderId || '',
type: 'refund' as const,
reason: refundApp.refundReason,
description: refundApp.refundDescription,
amount: refundApp.refundAmount,
contactPhone: refundApp.contactPhone,
evidenceImages: refundApp.evidenceImages,
goodsItems: refundApp.refundGoods.filter(item => item.refundNum > 0).map(item => ({
goodsId: item.goodsId,
quantity: item.refundNum
}))
}
Taro.showToast({ // 调用API提交退款申请
title: '退款申请提交成功', const result = await applyAfterSale(params)
icon: 'success'
}) if (result.success) {
// 更新订单状态为"退款申请中"
if (orderId) {
try {
await updateShopOrder({
orderId: parseInt(orderId),
orderStatus: 4 // 退款申请中
})
} catch (updateError) {
console.error('更新订单状态失败:', updateError)
// 即使更新订单状态失败,也继续执行后续操作
}
}
// 延迟返回上一页 Taro.showToast({
setTimeout(() => { title: '退款申请提交成功',
Taro.navigateBack() icon: 'success'
}, 1500) })
// 延迟返回上一页
setTimeout(() => {
Taro.navigateBack()
}, 1500)
} else {
throw new Error(result.message || '提交失败')
}
} catch (error) { } catch (error) {
console.error('提交退款申请失败:', error) console.error('提交退款申请失败:', error)
Taro.showToast({ Taro.showToast({
title: '提交失败,请重试', title: error instanceof Error ? error.message : '提交失败,请重试',
icon: 'none' icon: 'none'
}) })
} finally { } finally {
@@ -420,4 +454,4 @@ const RefundPage: React.FC = () => {
) )
} }
export default RefundPage export default RefundPage