feat(pages): 实现商品分类页面及优化轮播图功能
- 新增商品分类页面,包含左侧导航和右侧商品列表 - 实现分类切换和商品展示功能 - 添加骨架屏加载效果和空状态处理 - 优化首页轮播图组件,支持自动播放和触摸滑动 - 调整轮播图高度默认值为300px- 移除旧的热卖商品逻辑,改为获取推荐文章 - 修复医生申请页面用户类型选择功能 - 更新页面标题文本内容 - 添加网站配置获取hook
This commit is contained in:
@@ -43,6 +43,7 @@ export interface Config {
|
|||||||
siteName?: string;
|
siteName?: string;
|
||||||
siteLogo?: string;
|
siteLogo?: string;
|
||||||
domain?: string;
|
domain?: string;
|
||||||
|
apiUrl?: string;
|
||||||
icpNo?: string;
|
icpNo?: string;
|
||||||
copyright?: string;
|
copyright?: string;
|
||||||
loginBgImg?: string;
|
loginBgImg?: string;
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ import {SERVER_API_URL} from "@/utils/server";
|
|||||||
export async function listDictionaries(params?: DictParam) {
|
export async function listDictionaries(params?: DictParam) {
|
||||||
const res = await request.get<ApiResult<Dict[]>>(
|
const res = await request.get<ApiResult<Dict[]>>(
|
||||||
SERVER_API_URL + '/system/dict',
|
SERVER_API_URL + '/system/dict',
|
||||||
{
|
params
|
||||||
params
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
return res.data;
|
return res.data;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ export default {
|
|||||||
'pages/cart/cart',
|
'pages/cart/cart',
|
||||||
'pages/find/find',
|
'pages/find/find',
|
||||||
'pages/user/user',
|
'pages/user/user',
|
||||||
'pages/cms/category/index'
|
'pages/category/category'
|
||||||
],
|
],
|
||||||
"subpackages": [
|
"subpackages": [
|
||||||
{
|
{
|
||||||
@@ -126,6 +126,12 @@ export default {
|
|||||||
selectedIconPath: "assets/tabbar/home-active.png",
|
selectedIconPath: "assets/tabbar/home-active.png",
|
||||||
text: "首页",
|
text: "首页",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
pagePath: "pages/category/category",
|
||||||
|
iconPath: "assets/tabbar/category.png",
|
||||||
|
selectedIconPath: "assets/tabbar/category-active.png",
|
||||||
|
text: "分类",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
pagePath: "pages/cart/cart",
|
pagePath: "pages/cart/cart",
|
||||||
iconPath: "assets/tabbar/cart.png",
|
iconPath: "assets/tabbar/cart.png",
|
||||||
|
|||||||
24
src/app.ts
24
src/app.ts
@@ -6,10 +6,12 @@ import './app.scss'
|
|||||||
import {loginByOpenId} from "@/api/layout";
|
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, debugInviteInfo} 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,6 +40,8 @@ function App(props: { children: any; }) {
|
|||||||
};
|
};
|
||||||
// 可以使用所有的 React Hooks
|
// 可以使用所有的 React Hooks
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// 设置主题 (现在由useConfig Hook处理)
|
||||||
|
handleTheme()
|
||||||
// Taro.getSetting:获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
|
// Taro.getSetting:获取用户的当前设置。返回值中只会出现小程序已经向用户请求过的权限。
|
||||||
Taro.getSetting({
|
Taro.getSetting({
|
||||||
success: (res) => {
|
success: (res) => {
|
||||||
@@ -53,13 +57,12 @@ function App(props: { children: any; }) {
|
|||||||
// 处理小程序启动参数中的邀请信息
|
// 处理小程序启动参数中的邀请信息
|
||||||
const options = Taro.getLaunchOptionsSync()
|
const options = Taro.getLaunchOptionsSync()
|
||||||
handleLaunchOptions(options)
|
handleLaunchOptions(options)
|
||||||
handleTheme()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 处理启动参数
|
// 处理启动参数
|
||||||
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))
|
||||||
|
|
||||||
// 解析邀请参数
|
// 解析邀请参数
|
||||||
@@ -82,10 +85,6 @@ function App(props: { children: any; }) {
|
|||||||
})
|
})
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
// 打印调试信息
|
|
||||||
setTimeout(() => {
|
|
||||||
debugInviteInfo()
|
|
||||||
}, 2000)
|
|
||||||
} else {
|
} else {
|
||||||
console.log('❌ 未检测到邀请参数')
|
console.log('❌ 未检测到邀请参数')
|
||||||
}
|
}
|
||||||
@@ -96,15 +95,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(() => {
|
||||||
})
|
})
|
||||||
|
|||||||
BIN
src/assets/tabbar/category-active.png
Normal file
BIN
src/assets/tabbar/category-active.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.2 KiB |
BIN
src/assets/tabbar/category.png
Normal file
BIN
src/assets/tabbar/category.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.1 KiB |
@@ -1,15 +1,25 @@
|
|||||||
import {Image, Cell} from '@nutui/nutui-react-taro'
|
import {Image, Cell} from '@nutui/nutui-react-taro'
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
|
|
||||||
const ArticleList = (props: any) => {
|
const ArticleList = (props: any) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={'px-3'}>
|
<View className={'px-3'}>
|
||||||
{props.data.map((item, index) => {
|
{props.data.map((item: any, index: number) => {
|
||||||
return (
|
return (
|
||||||
<Cell
|
<Cell
|
||||||
title={item.title}
|
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={
|
extra={
|
||||||
<Image src={item.image} mode={'aspectFit'} lazyLoad={false} width={100} height="100"/>
|
<Image src={item.image} mode={'aspectFit'} lazyLoad={false} width={100} height="100"/>
|
||||||
}
|
}
|
||||||
@@ -18,7 +28,7 @@ const ArticleList = (props: any) => {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</View>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '邀请注册',
|
navigationBarTitleText: '会员注册',
|
||||||
navigationBarTextStyle: 'black'
|
navigationBarTextStyle: 'black'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {useEffect, useState, useRef} from "react";
|
import {useEffect, useState, useRef} from "react";
|
||||||
import {Loading, CellGroup, Input, Form, Avatar, Button, Space} from '@nutui/nutui-react-taro'
|
import {Loading, CellGroup, Input, Form, Avatar, Radio, Button, Space, InputNumber, TextArea, ConfigProvider} from '@nutui/nutui-react-taro'
|
||||||
import {Edit} from '@nutui/icons-react-taro'
|
import {Edit} from '@nutui/icons-react-taro'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import {View} from '@tarojs/components'
|
import {View} from '@tarojs/components'
|
||||||
@@ -11,6 +11,8 @@ import {User} from "@/api/system/user/model";
|
|||||||
import {getStoredInviteParams, handleInviteRelation} from "@/utils/invite";
|
import {getStoredInviteParams, handleInviteRelation} from "@/utils/invite";
|
||||||
import {addShopDealerUser} from "@/api/shop/shopDealerUser";
|
import {addShopDealerUser} from "@/api/shop/shopDealerUser";
|
||||||
import {listUserRole, updateUserRole} from "@/api/system/userRole";
|
import {listUserRole, updateUserRole} from "@/api/system/userRole";
|
||||||
|
import {DictData} from "@/api/system/dict-data/model";
|
||||||
|
import {listDictData} from "@/api/system/dict-data";
|
||||||
|
|
||||||
// 类型定义
|
// 类型定义
|
||||||
interface ChooseAvatarEvent {
|
interface ChooseAvatarEvent {
|
||||||
@@ -19,16 +21,20 @@ interface ChooseAvatarEvent {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InputEvent {
|
const customTheme = {
|
||||||
detail: {
|
nutuiInputnumberButtonWidth: '30px',
|
||||||
value: string;
|
nutuiInputnumberButtonHeight: '30px',
|
||||||
};
|
nutuiInputnumberButtonBorderRadius: '2px',
|
||||||
|
nutuiInputnumberButtonBackgroundColor: `#f4f4f4`,
|
||||||
|
nutuiInputnumberInputHeight: '30px',
|
||||||
|
nutuiInputnumberInputMargin: '0 2px',
|
||||||
}
|
}
|
||||||
|
|
||||||
const AddUserAddress = () => {
|
const AddUserAddress = () => {
|
||||||
const {user, loginUser} = useUser()
|
const {user, loginUser} = useUser()
|
||||||
const [loading, setLoading] = useState<boolean>(true)
|
const [loading, setLoading] = useState<boolean>(true)
|
||||||
const [FormData, setFormData] = useState<User>()
|
const [FormData, setFormData] = useState<User>()
|
||||||
|
const [userType, setUserType] = useState<DictData[]>()
|
||||||
const formRef = useRef<any>(null)
|
const formRef = useRef<any>(null)
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
@@ -47,6 +53,9 @@ const AddUserAddress = () => {
|
|||||||
nickname: '',
|
nickname: '',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
listDictData({dictCode: 'UserType'}).then((data) => {
|
||||||
|
setUserType(data)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -217,23 +226,6 @@ const AddUserAddress = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 获取微信昵称
|
|
||||||
const getWxNickname = (nickname: string) => {
|
|
||||||
// 更新表单数据
|
|
||||||
const updatedFormData = {
|
|
||||||
...FormData,
|
|
||||||
nickname: nickname
|
|
||||||
}
|
|
||||||
setFormData(updatedFormData);
|
|
||||||
|
|
||||||
// 同步更新表单字段
|
|
||||||
if (formRef.current) {
|
|
||||||
formRef.current.setFieldsValue({
|
|
||||||
realName: nickname
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 获取用户手机号 */
|
/* 获取用户手机号 */
|
||||||
const handleGetPhoneNumber = ({detail}: { detail: { code?: string, encryptedData?: string, iv?: string } }) => {
|
const handleGetPhoneNumber = ({detail}: { detail: { code?: string, encryptedData?: string, iv?: string } }) => {
|
||||||
const {code, encryptedData, iv} = detail
|
const {code, encryptedData, iv} = detail
|
||||||
@@ -382,9 +374,13 @@ const AddUserAddress = () => {
|
|||||||
>
|
>
|
||||||
<View className={'bg-gray-100 h-3'}></View>
|
<View className={'bg-gray-100 h-3'}></View>
|
||||||
<CellGroup style={{padding: '4px 0'}}>
|
<CellGroup style={{padding: '4px 0'}}>
|
||||||
<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>
|
{
|
||||||
<Input placeholder="邀请人ID" disabled={true}/>
|
FormData?.phone && <Form.Item name="avatar" label="头像" initialValue={FormData?.avatar} required>
|
||||||
</Form.Item>
|
<Button open-type="chooseAvatar" style={{height: '58px'}} onChooseAvatar={uploadAvatar}>
|
||||||
|
<Avatar src={FormData?.avatar || user?.avatar} size="54"/>
|
||||||
|
</Button>
|
||||||
|
</Form.Item>
|
||||||
|
}
|
||||||
<Form.Item name="phone" label="手机号" initialValue={FormData?.phone} required>
|
<Form.Item name="phone" label="手机号" initialValue={FormData?.phone} required>
|
||||||
<View className="flex items-center justify-between">
|
<View className="flex items-center justify-between">
|
||||||
<Input
|
<Input
|
||||||
@@ -400,22 +396,76 @@ const AddUserAddress = () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{
|
{FormData?.refereeId && <Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required><Input placeholder="邀请人ID" disabled={true}/></Form.Item>}
|
||||||
FormData?.phone && <Form.Item name="avatar" label="头像" initialValue={FormData?.avatar} required>
|
</CellGroup>
|
||||||
<Button open-type="chooseAvatar" style={{height: '58px'}} onChooseAvatar={uploadAvatar}>
|
<View className={'h-3 bg-gray-100'}></View>
|
||||||
<Avatar src={FormData?.avatar || user?.avatar} size="54"/>
|
<CellGroup style={{padding: '4px 0'}}>
|
||||||
</Button>
|
{FormData?.type}
|
||||||
</Form.Item>
|
<Form.Item label="用户类型" name="type" initialValue={FormData?.type} required>
|
||||||
}
|
<Radio.Group defaultValue="1" direction="horizontal" value={FormData?.type}>
|
||||||
<Form.Item name="realName" label="昵称" initialValue="" required>
|
{userType?.map((item) => (
|
||||||
<Input
|
<Radio key={item.value} value={item.value} onChange={() => {
|
||||||
type="nickname"
|
setFormData({
|
||||||
className="info-content__input"
|
...FormData,
|
||||||
placeholder="请获取微信昵称"
|
type: item.value
|
||||||
value={FormData?.nickname || ''}
|
})
|
||||||
onInput={(e: InputEvent) => getWxNickname(e.detail.value)}
|
}}>
|
||||||
|
{item.label}
|
||||||
|
</Radio>
|
||||||
|
))}
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="性别" name="sex" required>
|
||||||
|
<Radio.Group defaultValue="0" direction="horizontal">
|
||||||
|
<Radio value="1">
|
||||||
|
男
|
||||||
|
</Radio>
|
||||||
|
<Radio value="2">
|
||||||
|
女
|
||||||
|
</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="真实姓名" name="realName">
|
||||||
|
<Input placeholder={'请填写真实姓名'} value={FormData?.realName || ''} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="年龄" name="age" initialValue={18} style={{
|
||||||
|
display: 'none'
|
||||||
|
}}>
|
||||||
|
<ConfigProvider theme={customTheme}>
|
||||||
|
<InputNumber defaultValue={1} />
|
||||||
|
</ConfigProvider>
|
||||||
|
<InputNumber
|
||||||
|
value={FormData?.age || 0}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
{FormData?.type == 1 && (
|
||||||
|
<Form.Item label="个人签名" name="introduction">
|
||||||
|
<TextArea
|
||||||
|
placeholder={'个人签名'}
|
||||||
|
style={{
|
||||||
|
height: '50px',
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
padding: '10px',
|
||||||
|
}}
|
||||||
|
value={FormData?.introduction || ''}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{
|
||||||
|
FormData?.type == 2 && (
|
||||||
|
<Form.Item label="医生简介" name="introduction">
|
||||||
|
<TextArea
|
||||||
|
placeholder={'医生简介'}
|
||||||
|
style={{
|
||||||
|
height: '50px',
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
padding: '10px',
|
||||||
|
}}
|
||||||
|
value={FormData?.introduction || ''}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
</CellGroup>
|
</CellGroup>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ const CustomerIndex = () => {
|
|||||||
<Space className="flex justify-end">
|
<Space className="flex justify-end">
|
||||||
<Button
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
onClick={() => navTo(`/dealer/customer/add?id=${customer.applyId}`, true)}
|
onClick={() => navTo(`/doctor/customer/add?id=${customer.applyId}`, true)}
|
||||||
style={{marginRight: '8px', backgroundColor: '#52c41a', color: 'white'}}
|
style={{marginRight: '8px', backgroundColor: '#52c41a', color: 'white'}}
|
||||||
>
|
>
|
||||||
签约
|
签约
|
||||||
@@ -575,7 +575,7 @@ const CustomerIndex = () => {
|
|||||||
{/* 客户列表 */}
|
{/* 客户列表 */}
|
||||||
{renderCustomerList()}
|
{renderCustomerList()}
|
||||||
|
|
||||||
<FixedButton text={'客户报备'} onClick={() => Taro.navigateTo({url: '/dealer/customer/add'})}/>
|
<FixedButton text={'客户报备'} onClick={() => Taro.navigateTo({url: '/doctor/customer/add'})}/>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
50
src/hooks/useConfig.ts
Normal file
50
src/hooks/useConfig.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}};
|
||||||
|
};
|
||||||
3
src/pages/category/category.config.ts
Normal file
3
src/pages/category/category.config.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '分类'
|
||||||
|
})
|
||||||
425
src/pages/category/category.scss
Normal file
425
src/pages/category/category.scss
Normal file
@@ -0,0 +1,425 @@
|
|||||||
|
.category-container {
|
||||||
|
display: flex;
|
||||||
|
height: calc(100vh - 1rpx);
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左侧分类导航 */
|
||||||
|
.category-left {
|
||||||
|
width: 200rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-right: 2rpx solid #f0f0f0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
.category-scroll {
|
||||||
|
height: calc(100vh - 1rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-item {
|
||||||
|
padding: 30rpx 20rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: #fff;
|
||||||
|
color: #ff6b35;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 8rpx;
|
||||||
|
height: 50rpx;
|
||||||
|
background: linear-gradient(180deg, #ff6b35 0%, #ff8f6b 100%);
|
||||||
|
border-radius: 0 8rpx 8rpx 0;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 2rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 500;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-count {
|
||||||
|
font-size: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 8rpx;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .category-name {
|
||||||
|
color: #ff6b35;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active .category-count {
|
||||||
|
color: #ff6b35;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右侧商品列表 */
|
||||||
|
.category-right {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #fff;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.goods-scroll {
|
||||||
|
height: calc(100vh - 1rpx);
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-section {
|
||||||
|
.section-title {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
background: linear-gradient(135deg, #f8f8f8 0%, #f0f0f0 100%);
|
||||||
|
padding: 24rpx 30rpx;
|
||||||
|
border-bottom: 2rpx solid #f0f0f0;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 32rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-list {
|
||||||
|
padding: 20rpx 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-item {
|
||||||
|
display: flex;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
border-bottom: 2rpx solid #f8f8f8;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #fafafa;
|
||||||
|
transform: translateY(-2rpx);
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-image {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-info {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.goods-name {
|
||||||
|
font-size: 30rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1.4;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-desc {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-bottom: 12rpx;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-price-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
|
||||||
|
.goods-price {
|
||||||
|
font-size: 36rpx;
|
||||||
|
color: #ff6b35;
|
||||||
|
font-weight: 700;
|
||||||
|
margin-right: 16rpx;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '¥';
|
||||||
|
font-size: 24rpx;
|
||||||
|
position: relative;
|
||||||
|
top: -2rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-original-price {
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #999;
|
||||||
|
text-decoration: line-through;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '¥';
|
||||||
|
font-size: 20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-stock {
|
||||||
|
font-size: 22rpx;
|
||||||
|
color: #999;
|
||||||
|
background-color: #f8f8f8;
|
||||||
|
padding: 4rpx 8rpx;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
display: inline-block;
|
||||||
|
width: fit-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-section {
|
||||||
|
padding: 120rpx 30rpx;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #999;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '📋';
|
||||||
|
display: block;
|
||||||
|
font-size: 80rpx;
|
||||||
|
margin-bottom: 20rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 骨架屏样式 */
|
||||||
|
.category-skeleton {
|
||||||
|
display: flex;
|
||||||
|
height: calc(100vh - 1rpx);
|
||||||
|
|
||||||
|
.category-left {
|
||||||
|
width: 200rpx;
|
||||||
|
background-color: #fff;
|
||||||
|
border-right: 2rpx solid #f0f0f0;
|
||||||
|
padding: 20rpx 0;
|
||||||
|
|
||||||
|
.skeleton-category-item {
|
||||||
|
padding: 30rpx 20rpx;
|
||||||
|
|
||||||
|
.skeleton-text {
|
||||||
|
height: 32rpx;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-right {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 30rpx;
|
||||||
|
|
||||||
|
.skeleton-goods-item {
|
||||||
|
display: flex;
|
||||||
|
padding: 24rpx 0;
|
||||||
|
border-bottom: 2rpx solid #f8f8f8;
|
||||||
|
|
||||||
|
.skeleton-image {
|
||||||
|
width: 160rpx;
|
||||||
|
height: 160rpx;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
margin-right: 24rpx;
|
||||||
|
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.skeleton-info {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.skeleton-text {
|
||||||
|
height: 32rpx;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
border-radius: 4rpx;
|
||||||
|
animation: skeleton-loading 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes skeleton-loading {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 750rpx) {
|
||||||
|
.category-left {
|
||||||
|
width: 160rpx;
|
||||||
|
|
||||||
|
.category-item {
|
||||||
|
padding: 24rpx 16rpx;
|
||||||
|
|
||||||
|
.category-name {
|
||||||
|
font-size: 26rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-count {
|
||||||
|
font-size: 18rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-right {
|
||||||
|
.goods-section {
|
||||||
|
.goods-list {
|
||||||
|
padding: 16rpx 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-item {
|
||||||
|
padding: 20rpx 0;
|
||||||
|
|
||||||
|
.goods-image {
|
||||||
|
width: 140rpx;
|
||||||
|
height: 140rpx;
|
||||||
|
margin-right: 20rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-info {
|
||||||
|
.goods-name {
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-desc {
|
||||||
|
font-size: 22rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.goods-price-row {
|
||||||
|
.goods-price {
|
||||||
|
font-size: 32rpx;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 空状态样式 */
|
||||||
|
.empty-container {
|
||||||
|
height: calc(100vh - 1rpx);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
|
||||||
|
.empty-content {
|
||||||
|
text-align: center;
|
||||||
|
padding: 60rpx 40rpx;
|
||||||
|
|
||||||
|
.empty-icon {
|
||||||
|
font-size: 120rpx;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 30rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-title {
|
||||||
|
font-size: 32rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-desc {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #666;
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 40rpx;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-action {
|
||||||
|
background: linear-gradient(135deg, #ff6b35 0%, #ff8f6b 100%);
|
||||||
|
color: #fff;
|
||||||
|
padding: 20rpx 40rpx;
|
||||||
|
border-radius: 50rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3);
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
transform: translateY(-2rpx);
|
||||||
|
box-shadow: 0 6rpx 16rpx rgba(255, 107, 53, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
270
src/pages/category/category.tsx
Normal file
270
src/pages/category/category.tsx
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
import Taro from '@tarojs/taro'
|
||||||
|
import { useShareAppMessage } from "@tarojs/taro"
|
||||||
|
import { useEffect, useState, useRef, useCallback } from "react"
|
||||||
|
import { View, Text, ScrollView } from '@tarojs/components'
|
||||||
|
import { Image } from '@nutui/nutui-react-taro'
|
||||||
|
import { listCmsNavigation } from "@/api/cms/cmsNavigation"
|
||||||
|
import { CmsNavigation } from "@/api/cms/cmsNavigation/model"
|
||||||
|
import { pageShopGoods } from "@/api/shop/shopGoods"
|
||||||
|
import { ShopGoods } from "@/api/shop/shopGoods/model"
|
||||||
|
import './category.scss'
|
||||||
|
|
||||||
|
function Category() {
|
||||||
|
const [loading, setLoading] = useState<boolean>(true)
|
||||||
|
const [categories, setCategories] = useState<CmsNavigation[]>([])
|
||||||
|
const [selectedCategoryId, setSelectedCategoryId] = useState<number>(0)
|
||||||
|
const [goods, setGoods] = useState<{ [key: number]: ShopGoods[] }>({})
|
||||||
|
const [allGoods, setAllGoods] = useState<ShopGoods[]>([])
|
||||||
|
const rightScrollRef = useRef<any>(null)
|
||||||
|
const [scrollIntoView, setScrollIntoView] = useState('')
|
||||||
|
const [isScrollingByClick, setIsScrollingByClick] = useState(false)
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
const initData = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
// 获取商品分类
|
||||||
|
const categoryList = await listCmsNavigation({ model: 'goods' })
|
||||||
|
|
||||||
|
if (!categoryList || categoryList.length === 0) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '暂无商品分类',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
setLoading(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setCategories(categoryList)
|
||||||
|
const firstCategory = categoryList[0]
|
||||||
|
setSelectedCategoryId(firstCategory.navigationId!)
|
||||||
|
|
||||||
|
// 并行获取所有分类的商品数据
|
||||||
|
const goodsPromises = categoryList.map((category: CmsNavigation) =>
|
||||||
|
pageShopGoods({ categoryId: category.navigationId }).catch(err => {
|
||||||
|
console.error(`分类 ${category.title} 商品加载失败:`, err)
|
||||||
|
return { list: [] }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const goodsResults = await Promise.all(goodsPromises)
|
||||||
|
|
||||||
|
// 组织商品数据
|
||||||
|
const goodsByCategory: { [key: number]: ShopGoods[] } = {}
|
||||||
|
categoryList.forEach((category: CmsNavigation, index: number) => {
|
||||||
|
goodsByCategory[category.navigationId!] = goodsResults[index]?.list || []
|
||||||
|
})
|
||||||
|
|
||||||
|
setGoods(goodsByCategory)
|
||||||
|
|
||||||
|
// 获取所有商品用于搜索等功能
|
||||||
|
try {
|
||||||
|
const allGoodsRes = await pageShopGoods({})
|
||||||
|
setAllGoods(allGoodsRes?.list || [])
|
||||||
|
} catch (err) {
|
||||||
|
console.error('获取所有商品失败:', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.setNavigationBarTitle({
|
||||||
|
title: '商品分类'
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('分类数据加载失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '加载失败,请重试',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
initData().then()
|
||||||
|
console.log(allGoods,'allGoods')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
// 点击左侧分类
|
||||||
|
const handleCategoryClick = (categoryId: number) => {
|
||||||
|
setIsScrollingByClick(true)
|
||||||
|
setSelectedCategoryId(categoryId)
|
||||||
|
setScrollIntoView(`category-${categoryId}`)
|
||||||
|
|
||||||
|
// 延迟重置滚动标志
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsScrollingByClick(false)
|
||||||
|
}, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 右侧滚动时处理分类切换
|
||||||
|
const handleRightScroll = useCallback((e: any) => {
|
||||||
|
console.log(e,'右侧滚动时处理分类切换')
|
||||||
|
if (isScrollingByClick) return
|
||||||
|
|
||||||
|
// 这里可以添加逻辑来检测当前滚动到哪个分类
|
||||||
|
// 由于小程序限制,暂时简化处理
|
||||||
|
}, [isScrollingByClick])
|
||||||
|
|
||||||
|
// 跳转商品详情
|
||||||
|
const goToGoodsDetail = (goodsId: number) => {
|
||||||
|
Taro.navigateTo({ url: `/shop/goodsDetail/index?id=${goodsId}` })
|
||||||
|
}
|
||||||
|
|
||||||
|
useShareAppMessage(() => {
|
||||||
|
return {
|
||||||
|
title: '商品分类',
|
||||||
|
path: '/pages/category/category',
|
||||||
|
success: function () {
|
||||||
|
console.log('分享成功')
|
||||||
|
},
|
||||||
|
fail: function () {
|
||||||
|
console.log('分享失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 骨架屏组件
|
||||||
|
const CategorySkeleton = () => (
|
||||||
|
<View className="category-skeleton" style={{
|
||||||
|
marginTop: '1rpx',
|
||||||
|
}}>
|
||||||
|
<View className="category-left">
|
||||||
|
{[1, 2, 3, 4, 5].map(i => (
|
||||||
|
<View key={i} className="skeleton-category-item">
|
||||||
|
<View className="skeleton-text" />
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<View className="category-right">
|
||||||
|
{[1, 2, 3, 4].map(i => (
|
||||||
|
<View key={i} className="skeleton-goods-item">
|
||||||
|
<View className="skeleton-image" />
|
||||||
|
<View className="skeleton-info">
|
||||||
|
<View className="skeleton-text" style={{ width: '80%', marginBottom: '8px' }} />
|
||||||
|
<View className="skeleton-text" style={{ width: '60%' }} />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <CategorySkeleton />
|
||||||
|
}
|
||||||
|
|
||||||
|
// 空状态处理
|
||||||
|
if (!categories || categories.length === 0) {
|
||||||
|
return (
|
||||||
|
<View className="empty-container">
|
||||||
|
<View className="empty-content">
|
||||||
|
<Text className="empty-icon">📋</Text>
|
||||||
|
<Text className="empty-title">暂无商品分类</Text>
|
||||||
|
<Text className="empty-desc">请稍后再试或联系客服</Text>
|
||||||
|
<View className="empty-action" onClick={initData}>
|
||||||
|
<Text>重新加载</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="category-container" style={{
|
||||||
|
marginTop: '1rpx',
|
||||||
|
}}>
|
||||||
|
{/* 左侧分类导航 */}
|
||||||
|
<View className="category-left">
|
||||||
|
<ScrollView
|
||||||
|
className="category-scroll"
|
||||||
|
scrollY
|
||||||
|
enhanced
|
||||||
|
showScrollbar={false}
|
||||||
|
> {categories.map((category) => (
|
||||||
|
<View
|
||||||
|
key={category.navigationId}
|
||||||
|
className={`category-item ${
|
||||||
|
selectedCategoryId === category.navigationId ? 'active' : ''
|
||||||
|
}`}
|
||||||
|
onClick={() => handleCategoryClick(category.navigationId!)}
|
||||||
|
>
|
||||||
|
<Text className="category-name">{category.title}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 右侧商品列表 */}
|
||||||
|
<View className="category-right">
|
||||||
|
<ScrollView
|
||||||
|
ref={rightScrollRef}
|
||||||
|
className="goods-scroll"
|
||||||
|
scrollY
|
||||||
|
enhanced
|
||||||
|
showScrollbar={false}
|
||||||
|
scrollIntoView={scrollIntoView}
|
||||||
|
onScroll={handleRightScroll}
|
||||||
|
>
|
||||||
|
{categories.map((category) => {
|
||||||
|
const categoryGoods = goods[category.navigationId!] || []
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
key={category.navigationId}
|
||||||
|
id={`category-${category.navigationId}`}
|
||||||
|
className="goods-section"
|
||||||
|
>
|
||||||
|
<View className="section-title">
|
||||||
|
<Text>{category.title}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{categoryGoods.length > 0 ? (
|
||||||
|
<View className="goods-list">
|
||||||
|
{categoryGoods.map((item) => (
|
||||||
|
<View
|
||||||
|
key={item.goodsId}
|
||||||
|
className="goods-item"
|
||||||
|
onClick={() => goToGoodsDetail(item.goodsId!)}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
className="goods-image"
|
||||||
|
src={item.image || ''}
|
||||||
|
mode="aspectFill"
|
||||||
|
lazyLoad
|
||||||
|
width={80}
|
||||||
|
height={80}
|
||||||
|
/>
|
||||||
|
<View className="goods-info">
|
||||||
|
<Text className="goods-name">{item.name}</Text>
|
||||||
|
{item.comments && (
|
||||||
|
<Text className="goods-desc">{item.comments}</Text>
|
||||||
|
)}
|
||||||
|
<View className="goods-price-row">
|
||||||
|
<Text className="goods-price">¥{item.price}</Text>
|
||||||
|
{item.salePrice && Number(item.salePrice) !== Number(item.price) && (
|
||||||
|
<Text className="goods-original-price">¥{item.salePrice}</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
{item.stock !== undefined && (
|
||||||
|
<Text className="goods-stock">
|
||||||
|
库存: {item.stock > 0 ? item.stock : '缺货'}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<View className="empty-section">
|
||||||
|
<Text>该分类暂无商品</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Category
|
||||||
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
|
||||||
@@ -5,55 +5,123 @@ import {CmsAd} from "@/api/cms/cmsAd/model";
|
|||||||
import {Image} from '@nutui/nutui-react-taro'
|
import {Image} from '@nutui/nutui-react-taro'
|
||||||
import {getCmsAdByCode} from "@/api/cms/cmsAd";
|
import {getCmsAdByCode} from "@/api/cms/cmsAd";
|
||||||
import navTo from "@/utils/common";
|
import navTo from "@/utils/common";
|
||||||
import {ShopGoods} from "@/api/shop/shopGoods/model";
|
import {pageCmsArticle} from "@/api/cms/cmsArticle";
|
||||||
import {listShopGoods} from "@/api/shop/shopGoods";
|
import {CmsArticle} from "@/api/cms/cmsArticle/model";
|
||||||
|
|
||||||
|
|
||||||
const MyPage = () => {
|
const MyPage = () => {
|
||||||
const [carouselData, setCarouselData] = useState<CmsAd>()
|
const [carouselData, setCarouselData] = useState<CmsAd>()
|
||||||
// const [hotToday, setHotToday] = useState<CmsAd>()
|
const [hotToday, setHotToday] = useState<CmsAd>()
|
||||||
// const [groupBuy, setGroupBuy] = useState<CmsAd>()
|
const [item, setItem] = useState<CmsArticle>()
|
||||||
const [hotGoods, setHotGoods] = useState<ShopGoods[]>([])
|
const [loading, setLoading] = useState(true)
|
||||||
|
// const [disableSwiper, setDisableSwiper] = useState(false)
|
||||||
|
|
||||||
|
// 用于记录触摸开始位置
|
||||||
|
// const touchStartRef = useRef({x: 0, y: 0})
|
||||||
|
|
||||||
// 加载数据
|
// 加载数据
|
||||||
const loadData = () => {
|
const loadData = async () => {
|
||||||
// 轮播图
|
try {
|
||||||
getCmsAdByCode('flash').then(data => {
|
setLoading(true)
|
||||||
setCarouselData(data)
|
// 轮播图
|
||||||
})
|
const flash = await getCmsAdByCode('flash')
|
||||||
// 今日热卖素材(上层图片)
|
// 今日热卖
|
||||||
// getCmsAd(444).then(data => {
|
const hotToday = await getCmsAdByCode('hot_today')
|
||||||
// setHotToday(data)
|
// 时里动态
|
||||||
// })
|
const news = await pageCmsArticle({limit:1,recommend:1})
|
||||||
// 社区拼团素材(下层图片)
|
// 赋值
|
||||||
// getCmsAd(445).then(data => {
|
if(flash){
|
||||||
// setGroupBuy(data)
|
setCarouselData(flash)
|
||||||
// })
|
}
|
||||||
// 今日热卖
|
if(hotToday){
|
||||||
listShopGoods({categoryId: 4424, limit: 2}).then(data => {
|
setHotToday(hotToday)
|
||||||
setHotGoods(data)
|
}
|
||||||
})
|
if(news && news.list.length > 0){
|
||||||
|
setItem(news.list[0])
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Banner数据加载失败:', error)
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadData()
|
loadData()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// 轮播图高度,默认200px
|
// 轮播图高度,默认300px
|
||||||
const carouselHeight = carouselData?.height || 200;
|
const carouselHeight = carouselData?.height || 300;
|
||||||
|
|
||||||
|
// 骨架屏组件
|
||||||
|
const BannerSkeleton = () => (
|
||||||
|
<View className="flex p-2 justify-between" style={{height: `${carouselHeight}px`}}>
|
||||||
|
{/* 左侧轮播图骨架屏 */}
|
||||||
|
<View style={{width: '50%', height: '100%'}}>
|
||||||
|
<View
|
||||||
|
className="bg-gray-200 rounded animate-pulse"
|
||||||
|
style={{height: `${carouselHeight}px`}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 右侧骨架屏 */}
|
||||||
|
<View className="flex flex-col" style={{width: '50%', height: '100%'}}>
|
||||||
|
{/* 上层骨架屏 */}
|
||||||
|
<View className="ml-2 bg-white rounded-lg">
|
||||||
|
<View className="px-3 my-2">
|
||||||
|
<View className="bg-gray-200 h-4 w-16 rounded animate-pulse"/>
|
||||||
|
</View>
|
||||||
|
<View className="px-3 flex" style={{height: '110px'}}>
|
||||||
|
{[1, 2].map(i => (
|
||||||
|
<View key={i} className="item flex flex-col mr-4">
|
||||||
|
<View className="bg-gray-200 rounded animate-pulse" style={{width: 70, height: 70}}/>
|
||||||
|
<View className="bg-gray-200 h-3 w-16 rounded mt-2 animate-pulse"/>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 下层骨架屏 */}
|
||||||
|
<View className="ml-2 bg-white rounded-lg mt-3">
|
||||||
|
<View className="px-3 my-2">
|
||||||
|
<View className="bg-gray-200 h-4 w-20 rounded animate-pulse"/>
|
||||||
|
</View>
|
||||||
|
<View className="rounded-lg px-3 pb-3">
|
||||||
|
<View className="bg-gray-200 rounded animate-pulse" style={{width: '100%', height: 106}}/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
|
||||||
|
// 如果正在加载,显示骨架屏
|
||||||
|
if (loading) {
|
||||||
|
return <BannerSkeleton />
|
||||||
|
}
|
||||||
|
|
||||||
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}
|
||||||
indicator
|
indicator
|
||||||
style={{height: `${carouselHeight}px`}}
|
autoPlay
|
||||||
|
duration={3000}
|
||||||
|
style={{
|
||||||
|
height: `${carouselHeight}px`,
|
||||||
|
touchAction: 'pan-y' // 关键修改:允许垂直滑动
|
||||||
|
}}
|
||||||
|
disableTouch={false}
|
||||||
|
direction="horizontal"
|
||||||
|
className="custom-swiper"
|
||||||
>
|
>
|
||||||
{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%"
|
||||||
@@ -61,7 +129,11 @@ const MyPage = () => {
|
|||||||
mode={'scaleToFill'}
|
mode={'scaleToFill'}
|
||||||
onClick={() => navTo(`${img.path}`)}
|
onClick={() => navTo(`${img.path}`)}
|
||||||
lazyLoad={false}
|
lazyLoad={false}
|
||||||
style={{height: `${carouselHeight}px`, borderRadius: '4px'}}
|
style={{
|
||||||
|
height: `${carouselHeight}px`,
|
||||||
|
borderRadius: '4px',
|
||||||
|
touchAction: 'manipulation' // 关键修改:优化触摸操作
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Swiper.Item>
|
</Swiper.Item>
|
||||||
))}
|
))}
|
||||||
@@ -71,26 +143,26 @@ const MyPage = () => {
|
|||||||
{/* 右侧上下图片区域 - 从API获取数据 */}
|
{/* 右侧上下图片区域 - 从API获取数据 */}
|
||||||
<View className="flex flex-col" style={{width: '50%', height: '100%'}}>
|
<View className="flex flex-col" style={{width: '50%', height: '100%'}}>
|
||||||
{/* 上层图片 - 使用今日热卖素材 */}
|
{/* 上层图片 - 使用今日热卖素材 */}
|
||||||
<View className={'ml-2 bg-white rounded-lg'}>
|
<View className={'ml-2 bg-white rounded-lg shadow-sm'}>
|
||||||
<View className={'px-3 my-2 font-bold text-sm'}>今日热卖</View>
|
<View className={'px-3 my-2 font-bold text-sm'}>今日热卖</View>
|
||||||
<View className={'px-3 flex'} style={{
|
<View className={'px-3 flex justify-between'} style={{
|
||||||
height: '110px'
|
height: '110px'
|
||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
hotGoods.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}
|
||||||
src={item.image}
|
src={item.url}
|
||||||
mode={'scaleToFill'}
|
mode={'scaleToFill'}
|
||||||
lazyLoad={false}
|
lazyLoad={false}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: '4px'
|
borderRadius: '4px'
|
||||||
}}
|
}}
|
||||||
onClick={() => navTo('/shop/category/index?id=4424')}
|
onClick={() => navTo(item.path)}
|
||||||
/>
|
/>
|
||||||
<View className={'text-xs py-2'}>到手价¥{item.price}</View>
|
<View className={'text-xs py-2 text-orange-600 whitespace-nowrap text-center'}>{item.title || '到手价¥9.9'}</View>
|
||||||
</View>
|
</View>
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@@ -98,19 +170,19 @@ const MyPage = () => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* 下层图片 - 使用社区拼团素材 */}
|
{/* 下层图片 - 使用社区拼团素材 */}
|
||||||
<View className={'ml-2 bg-white rounded-lg mt-3'}>
|
<View className={'ml-2 bg-white rounded-lg mt-3 shadow-sm'}>
|
||||||
<View className={'px-3 my-2 font-bold text-sm'}>走进社区</View>
|
<View className={'px-3 my-2 font-bold text-sm'}>{item?.overview || item?.categoryName || '推荐文章'}</View>
|
||||||
<View className={'rounded-lg px-3 pb-3'}>
|
<View className={'rounded-lg px-3 pb-3'}>
|
||||||
<Image
|
<Image
|
||||||
width={'100%'}
|
width={'100%'}
|
||||||
height={100}
|
height={94}
|
||||||
src={'https://oss.wsdns.cn/20250919/941c99899e694a7798cab3bb28f1f238.png?x-oss-process=image/resize,m_fixed,w_750/quality,Q_90'}
|
src={item?.image}
|
||||||
mode={'scaleToFill'}
|
mode={'scaleToFill'}
|
||||||
lazyLoad={false}
|
lazyLoad={false}
|
||||||
style={{
|
style={{
|
||||||
borderRadius: '4px'
|
borderRadius: '4px'
|
||||||
}}
|
}}
|
||||||
onClick={() => navTo('cms/detail/index?id=10109')}
|
onClick={() => navTo('cms/detail/index?id=' + item?.articleId)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -120,4 +192,3 @@ const MyPage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default MyPage
|
export default MyPage
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
// 订单页面样式
|
|
||||||
.order-page {
|
|
||||||
// 订单相关样式
|
|
||||||
}
|
|
||||||
@@ -3,18 +3,28 @@ 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} from "react";
|
|
||||||
import {useDealerUser} from "@/hooks/useDealerUser";
|
import {useDealerUser} from "@/hooks/useDealerUser";
|
||||||
import {useThemeStyles} from "@/hooks/useTheme";
|
import {useThemeStyles} from "@/hooks/useTheme";
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {getCmsAdByCode} from "@/api/cms/cmsAd";
|
||||||
|
import {CmsAd} from "@/api/cms/cmsAd/model"; // 使用新的自定义Hook
|
||||||
|
|
||||||
const IsDealer = () => {
|
const IsDealer = () => {
|
||||||
const themeStyles = useThemeStyles();
|
const themeStyles = useThemeStyles();
|
||||||
|
const { config } = useConfig(); // 使用新的Hook
|
||||||
const {isSuperAdmin} = useUser();
|
const {isSuperAdmin} = useUser();
|
||||||
const {dealerUser} = useDealerUser()
|
const {dealerUser} = useDealerUser()
|
||||||
|
const [register, setRegister] = useState<CmsAd>()
|
||||||
|
|
||||||
|
const reload = async () => {
|
||||||
|
const item = await getCmsAdByCode('register')
|
||||||
|
setRegister(item)
|
||||||
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
reload().then()
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 管理中心
|
* 管理中心
|
||||||
@@ -54,7 +64,7 @@ const IsDealer = () => {
|
|||||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||||
<Reward className={'text-orange-100 '} size={16}/>
|
<Reward className={'text-orange-100 '} size={16}/>
|
||||||
<Text style={{fontSize: '16px'}}
|
<Text style={{fontSize: '16px'}}
|
||||||
className={'pl-3 text-orange-100 font-medium'}>VIP申请</Text>
|
className={'pl-3 text-orange-100 font-medium'}>{config?.vipText || '入驻申请'}</Text>
|
||||||
{/*<Text className={'text-white opacity-80 pl-3'}>门店核销</Text>*/}
|
{/*<Text className={'text-white opacity-80 pl-3'}>门店核销</Text>*/}
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
@@ -78,12 +88,12 @@ const IsDealer = () => {
|
|||||||
title={
|
title={
|
||||||
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
<View style={{display: 'inline-flex', alignItems: 'center'}}>
|
||||||
<Reward className={'text-orange-100 '} size={16}/>
|
<Reward className={'text-orange-100 '} size={16}/>
|
||||||
<Text style={{fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>开通VIP</Text>
|
<Text style={{fontSize: '16px'}} className={'pl-3 text-orange-100 font-medium'}>{register?.name || '注册会员'}</Text>
|
||||||
<Text className={'text-white opacity-80 pl-3'}>享优惠</Text>
|
<Text className={'text-white opacity-80 pl-3'}></Text>
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
||||||
extra={<ArrowRight color="#cccccc" size={18}/>}
|
extra={<ArrowRight color="#cccccc" size={18}/>}
|
||||||
onClick={() => navTo('/doctor/apply/add', true)}
|
onClick={() => navTo(`${register?.path}`, true)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -53,7 +53,29 @@ const DEFAULT_CONFIG = {
|
|||||||
showError: true
|
showError: true
|
||||||
};
|
};
|
||||||
|
|
||||||
let baseUrl = BaseUrl;
|
// 获取API基础地址的函数
|
||||||
|
const getBaseUrl = (): string => {
|
||||||
|
// 尝试从本地存储获取后台配置的API地址
|
||||||
|
try {
|
||||||
|
const configStr = Taro.getStorageSync('config');
|
||||||
|
if (configStr) {
|
||||||
|
// 如果是字符串,需要解析为对象
|
||||||
|
const config = typeof configStr === 'string' ? JSON.parse(configStr) : configStr;
|
||||||
|
console.log('获取后台配置API地址:', config);
|
||||||
|
// 注意属性名是 ApiUrl(首字母大写),不是 apiUrl
|
||||||
|
if (config && config.ApiUrl) {
|
||||||
|
console.log('使用后台配置的API地址:', config.ApiUrl);
|
||||||
|
return config.ApiUrl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('获取后台配置API地址失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果后台没有配置API地址,则使用本地配置
|
||||||
|
console.log('使用本地配置的API地址:', BaseUrl);
|
||||||
|
return BaseUrl;
|
||||||
|
};
|
||||||
|
|
||||||
// 开发环境配置
|
// 开发环境配置
|
||||||
if (process.env.NODE_ENV === 'development') {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
@@ -303,8 +325,11 @@ export async function request<T>(options: RequestConfig): Promise<T> {
|
|||||||
|
|
||||||
// 构建完整URL
|
// 构建完整URL
|
||||||
const buildUrl = (url: string): string => {
|
const buildUrl = (url: string): string => {
|
||||||
|
// 每次构建URL时都检查最新的baseUrl
|
||||||
|
const currentBaseUrl = getBaseUrl();
|
||||||
|
|
||||||
if (url.indexOf('http') === -1) {
|
if (url.indexOf('http') === -1) {
|
||||||
return baseUrl + url;
|
return currentBaseUrl + url;
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user