Compare commits

...

2 Commits

Author SHA1 Message Date
科技小王子 a760e46e8b feat(doctor): 重构医生申请页面并优化用户体验 2 days ago
科技小王子 64b97042cb style(index): 调整Banner组件样式 6 days ago
  1. 2
      config/app.ts
  2. 13
      src/api/cms/cmsArticle/index.ts
  3. 14
      src/api/cms/cmsNavigation/index.ts
  4. 2
      src/api/cms/cmsNavigation/model/index.ts
  5. 4
      src/api/cms/cmsWebsiteField/model/index.ts
  6. 218
      src/api/cmsArticle/index.ts
  7. 132
      src/api/cmsArticle/model/index.ts
  8. 6
      src/app.ts
  9. 466
      src/doctor/apply/add.tsx
  10. 24
      src/doctor/index.tsx
  11. 1
      src/hooks/useConfig.ts
  12. 3
      src/pages/category/category.tsx
  13. 12
      src/pages/index/Banner.tsx
  14. 79
      src/shop/category/components/GoodsList.tsx
  15. 8
      src/shop/goodsDetail/index.tsx

2
config/app.ts

@ -8,5 +8,3 @@ export const BaseUrl = API_BASE_URL;
export const Version = 'v3.0.8';
// 版权信息
export const Copyright = 'WebSoft Inc.';
// java -jar CertificateDownloader.jar -k 0kF5OlPr482EZwtn9zGufUcqa7ovgxRL -m 1723321338 -f ./apiclient_key.pem -s 2B933F7C35014A1C363642623E4A62364B34C4EB -o ./

13
src/api/cms/cmsArticle/index.ts

@ -216,3 +216,16 @@ export async function getCmsArticleByCode(code: string) {
}
return Promise.reject(new Error(res.message));
}
/**
* code查询文章
*/
export async function getByCode(code: string) {
const res = await request.get<ApiResult<CmsArticle>>(
'/cms/cms-article/getByCode/' + code
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}

14
src/api/cms/cmsNavigation/index.ts

@ -1,6 +1,7 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsNavigation, CmsNavigationParam } from './model';
import type {CmsArticle} from "@/api/cms/cmsArticle/model";
/**
*
@ -122,4 +123,15 @@ export async function getNavigationByPath(params: CmsNavigationParam) {
return Promise.reject(new Error(res.message));
}
/**
* code查询导航
*/
export async function getByCode(code: string) {
const res = await request.get<ApiResult<CmsArticle>>(
'/cms/cms-navigation/getByCode/' + code
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}

2
src/api/cms/cmsNavigation/model/index.ts

@ -113,5 +113,7 @@ export interface CmsNavigationParam extends PageParam {
parentId?: number;
hide?: number;
model?: string;
home?: number;
position?: number;
keywords?: string;
}

4
src/api/cms/cmsWebsiteField/model/index.ts

@ -43,7 +43,6 @@ export interface Config {
siteName?: string;
siteLogo?: string;
domain?: string;
apiUrl?: string;
icpNo?: string;
copyright?: string;
loginBgImg?: string;
@ -58,4 +57,7 @@ export interface Config {
sysLogo?: string;
vipText?: string;
vipComments?: string;
deliveryText?: string;
guaranteeText?: string;
openComments?: string;
}

218
src/api/cmsArticle/index.ts

@ -1,218 +0,0 @@
import request from '@/utils/request';
import type {ApiResult, PageResult} from '@/api';
import type {CmsArticle, CmsArticleParam} from './model';
/**
*
*/
export async function pageCmsArticle(params: CmsArticleParam) {
const res = await request.get<ApiResult<PageResult<CmsArticle>>>(
'/cms/cms-article/page',
params
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function listCmsArticle(params?: CmsArticleParam) {
const res = await request.get<ApiResult<CmsArticle[]>>(
'/cms/cms-article',
params
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function addCmsArticle(data: CmsArticle) {
const res = await request.post<ApiResult<unknown>>(
'/cms/cms-article',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function updateCmsArticle(data: CmsArticle) {
const res = await request.put<ApiResult<unknown>>(
'/cms/cms-article',
data
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function removeCmsArticle(id?: number) {
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article/' + id
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
*
*/
export async function removeBatchCmsArticle(data: (number | undefined)[]) {
const res = await request.del<ApiResult<unknown>>(
'/cms/cms-article/batch',
{
data
}
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
/**
* id查询文章
*/
export async function getCmsArticle(id: number) {
const res = await request.get<ApiResult<CmsArticle>>(
'/cms/cms-article/' + id
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
export async function getCount(params?: CmsArticleParam) {
const res = await request.get<ApiResult<unknown>>('/cms/cms-article/data', {
params
});
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
*
* @param params
*/
export async function getPrevious(params?: CmsArticleParam) {
const res = await request.get<ApiResult<CmsArticle>>(
'/cms/cms-article/getPrevious/' + params?.articleId,
{
params
}
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
*
* @param params
*/
export async function getNext(params?: CmsArticleParam) {
const res = await request.get<ApiResult<CmsArticle>>(
'/cms/cms-article/getNext/' + params?.articleId,
{
params
}
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
*
* @param params
*/
export async function checkArticlePassword(params?: CmsArticleParam) {
const res = await request.get<ApiResult<unknown>>(
'/cms/cms-article/checkArticlePassword',
{
params
}
);
if (res.code === 0) {
return res.message;
}
return Promise.reject(new Error(res.message));
}
export async function findTags(params?: CmsArticleParam) {
const res = await request.get<ApiResult<CmsArticle[]>>(
'/cms/cms-article/findTags',
{
params
}
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
export async function pageTags(params?: CmsArticleParam) {
const res = await request.get<ApiResult<CmsArticle[]>>(
'/cms/cms-article/pageTags',
{
params
}
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* IDS查询文章
* @param params
*/
export async function getByIds(params?: CmsArticleParam) {
const res = await request.get<ApiResult<CmsArticle[]>>(
'/cms/cms-article/getByIds',
{
params
}
);
if (res.code === 0) {
return res.data;
}
return Promise.reject(new Error(res.message));
}
/**
* code查询文章
*/
export async function getByCode(code: string) {
const res = await request.get<ApiResult<CmsArticle>>(
'/cms/cms-article/getByCode/' + code
);
if (res.code === 0 && res.data) {
return res.data;
}
return Promise.reject(new Error(res.message));
}

132
src/api/cmsArticle/model/index.ts

@ -1,132 +0,0 @@
import type { PageParam } from '@/api/index';
/**
*
*/
export interface CmsArticle {
// 文章ID
articleId?: number;
// 文章标题
title?: string;
// 文章类型 0常规 1视频
type?: number;
// 文章模型
model?: string;
// 文章详情页模板
detail?: string;
// banner图片
banner?: string;
// 列表显示方式(10小图展示 20大图展示)
showType?: number;
// 话题
topic?: string;
// 标签
tags?: any;
// 栏目ID
categoryId?: number;
// 栏目名称
categoryName?: string;
// 封面图
image?: string;
// 价格
price?: number;
startTime?: any;
endTime?: any;
// 缩列图
thumbnail?: string;
// 来源
source?: string;
// 产品概述
overview?: string;
// 虚拟阅读量(仅用作展示)
virtualViews?: number;
// 实际阅读量
actualViews?: number;
// 购买人数
bmUsers?: number;
// 浏览权限(0公开 1会员 2密码)
permission?: number;
// 访问密码
password?: string;
// 确认密码
password2?: string;
// 发布来源客户端 (APP、H5、小程序等)
platform?: string;
// 文章附件
files?: string;
// 视频地址
video?: string;
// 接受的文件类型
accept?: string;
// 经度
longitude?: string;
// 纬度
latitude?: string;
// 所在省份
province?: string;
// 所在城市
city?: string;
// 所在辖区
region?: string;
// 街道地址
address?: string;
// 点赞数
likes?: number;
// pdf地址
pdfUrl?: string;
// 评论数
commentNumbers?: number;
// 提醒谁看
toUsers?: string;
// 文章内容
content?: string;
// 是否推荐
recommend?: number;
// 用户ID
userId?: number;
// 排序(数字越小越靠前)
sortNumber?: number;
// 备注
comments?: string;
// 状态, 0已发布, 1待审核 2已驳回 3违规内容
status?: number;
// 是否删除, 0否, 1是
deleted?: number;
// 租户id
tenantId?: number;
// 创建时间
createTime?: string;
// 修改时间
updateTime?: string;
// 父级id
parentId?: number;
nickname?: string;
username?: string;
author?: string;
shopId?: number;
tenantName?: string;
logo?: string;
fileList?: any;
// 编辑器类型
editor?: number;
}
/**
*
*/
export interface CmsArticleParam extends PageParam {
articleId?: number;
articleIds?: string;
categoryId?: number;
parentId?: number;
status?: number;
// 是否推荐
recommend?: number;
keywords?: string;
// 验证密码
password?: string;
password2?: string;
tags?: string;
detail?: string;
sceneType?: string;
}

6
src/app.ts

@ -78,11 +78,7 @@ function App(props: { children: any; }) {
// 显示邀请提示
setTimeout(() => {
Taro.showToast({
title: `检测到邀请信息 ID:${inviteParams.inviter}`,
icon: 'success',
duration: 3000
})
console.log(`检测到邀请信息 ID:${inviteParams.inviter}`)
}, 1000)
} else {

466
src/doctor/apply/add.tsx

@ -1,68 +1,207 @@
import {useEffect, useState, useRef} from "react";
import {Loading, CellGroup, Input, Form, Radio, InputNumber, TextArea, ConfigProvider} from '@nutui/nutui-react-taro'
import {Loading, CellGroup, Input, Form, Avatar, Button, Space} from '@nutui/nutui-react-taro'
import {Edit} from '@nutui/icons-react-taro'
import Taro from '@tarojs/taro'
import {Image} from '@tarojs/components';
import {View} from '@tarojs/components'
import FixedButton from "@/components/FixedButton";
import {DictData} from "@/api/system/dict-data/model";
import {listDictData} from "@/api/system/dict-data";
import {addShopUser, getShopUser} from "@/api/shop/shopUser";
import {ShopUser} from "@/api/shop/shopUser/model";
import {uploadFile} from "@/api/system/file";
import {useUser} from "@/hooks/useUser";
import {TenantId} from "@/config/app";
import {updateUser} from "@/api/system/user";
import {User} from "@/api/system/user/model";
import {getStoredInviteParams, handleInviteRelation} from "@/utils/invite";
import {addShopDealerUser} from "@/api/shop/shopDealerUser";
import {listUserRole, updateUserRole} from "@/api/system/userRole";
const customTheme = {
nutuiInputnumberButtonWidth: '30px',
nutuiInputnumberButtonHeight: '30px',
nutuiInputnumberButtonBorderRadius: '2px',
nutuiInputnumberButtonBackgroundColor: `#f4f4f4`,
nutuiInputnumberInputHeight: '30px',
nutuiInputnumberInputMargin: '0 2px',
// 类型定义
interface ChooseAvatarEvent {
detail: {
avatarUrl: string;
};
}
const AddApply = () => {
const {user} = useUser()
interface InputEvent {
detail: {
value: string;
};
}
const AddDoctor = () => {
const {user, loginUser} = useUser()
const [loading, setLoading] = useState<boolean>(true)
const [FormData, setFormData] = useState<ShopUser>()
const [userType, setUserType] = useState<DictData[]>()
const [FormData, setFormData] = useState<User>()
const formRef = useRef<any>(null)
const reload = async () => {
const userId = Taro.getStorageSync('UserId')
if(!userId || userId == '') return
const shopUser = await getShopUser(Number(Taro.getStorageSync('UserId')))
if(shopUser){
console.log(shopUser.type,'shopUsershopUsershopUsershopUsershopUsershopUsershopUser')
const inviteParams = getStoredInviteParams()
if (inviteParams?.inviter) {
setFormData({
...user,
refereeId: Number(inviteParams.inviter),
// 清空昵称,强制用户手动输入
nickname: '',
})
} else {
// 如果没有邀请参数,也要确保昵称为空
setFormData({
...user,
nickname: '',
})
}
}
const uploadAvatar = ({detail}: ChooseAvatarEvent) => {
// 先更新本地显示的头像(临时显示)
const tempFormData = {
...FormData,
avatar: `${detail.avatarUrl}`,
}
setFormData(tempFormData)
Taro.uploadFile({
url: 'https://server.websoft.top/api/oss/upload',
filePath: detail.avatarUrl,
name: 'file',
header: {
'content-type': 'application/json',
TenantId
},
success: async (res) => {
const data = JSON.parse(res.data);
if (data.code === 0) {
const finalAvatarUrl = `${data.data.thumbnail}`
try {
// 使用 useUser hook 的 updateUser 方法更新头像
await updateUser({
avatar: finalAvatarUrl
})
Taro.showToast({
title: '头像上传成功',
icon: 'success',
duration: 1500
})
} catch (error) {
console.error('更新用户头像失败:', error)
}
// 无论用户信息更新是否成功,都要更新本地FormData
const finalFormData = {
...tempFormData,
avatar: finalAvatarUrl
}
setFormData(finalFormData)
// 同步更新表单字段
if (formRef.current) {
formRef.current.setFieldsValue({
avatar: finalAvatarUrl
})
}
} else {
// 上传失败,恢复原来的头像
setFormData({
...shopUser,
type : Number(shopUser.type)
...FormData,
avatar: user?.avatar || ''
})
Taro.showToast({
title: '上传失败',
icon: 'error'
})
}
const userType = await listDictData({dictCode: 'UserType'})
if(userType){
setUserType(userType)
},
fail: (error) => {
console.error('上传头像失败:', error)
Taro.showToast({
title: '上传失败',
icon: 'error'
})
// 恢复原来的头像
setFormData({
...FormData,
avatar: user?.avatar || ''
})
}
})
}
// 提交表单
const submitSucceed = async (values: any) => {
if (!FormData?.age || FormData?.age == 0) {
try {
// 验证必填字段
if (!values.phone && !FormData?.phone) {
Taro.showToast({
title: `请填写年龄`,
title: '请先获取手机号',
icon: 'error'
});
return;
}
try {
// 验证昵称:必须填写且不能是默认的微信昵称
const nickname = values.realName || FormData?.nickname || '';
if (!nickname || nickname.trim() === '') {
Taro.showToast({
title: '请填写昵称',
icon: 'error'
});
return;
}
// 检查是否为默认的微信昵称(常见的默认昵称)
const defaultNicknames = ['微信用户', 'WeChat User', '微信昵称'];
if (defaultNicknames.includes(nickname.trim())) {
Taro.showToast({
title: '请填写真实昵称,不能使用默认昵称',
icon: 'error'
});
return;
}
// 验证昵称长度
if (nickname.trim().length < 2) {
Taro.showToast({
title: '昵称至少需要2个字符',
icon: 'error'
});
return;
}
if (!values.avatar && !FormData?.avatar) {
Taro.showToast({
title: '请上传头像',
icon: 'error'
});
return;
}
console.log(values,FormData)
const roles = await listUserRole({userId: user?.userId})
console.log(roles, 'roles...')
// 准备提交的数据
await addShopUser({
userId: Taro.getStorageSync('UserId'),
type: values.type,
username: user?.username,
nickname: user?.nickname,
await updateUser({
userId: user?.userId,
nickname: values.realName || FormData?.nickname,
phone: values.phone || FormData?.phone,
avatar: values.avatar || FormData?.avatar,
refereeId: values.refereeId || FormData?.refereeId
});
await addShopDealerUser({
userId: user?.userId,
realName: values.realName || FormData?.nickname,
phone: values.phone || FormData?.phone
mobile: values.phone || FormData?.phone,
refereeId: values.refereeId || FormData?.refereeId
})
if (roles.length > 0) {
await updateUserRole({
...roles[0],
roleId: 1848
})
}
Taro.showToast({
title: `注册成功`,
@ -78,6 +217,127 @@ const AddApply = () => {
}
}
// 获取微信昵称
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 {code, encryptedData, iv} = detail
Taro.login({
success: (loginRes) => {
if (code) {
Taro.request({
url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone',
method: 'POST',
data: {
authCode: loginRes.code,
code,
encryptedData,
iv,
notVerifyPhone: true,
refereeId: 0,
sceneType: 'save_referee',
tenantId: TenantId
},
header: {
'content-type': 'application/json',
TenantId
},
success: async function (res) {
if (res.data.code == 1) {
Taro.showToast({
title: res.data.message,
icon: 'error',
duration: 2000
})
return false;
}
// 登录成功
const token = res.data.data.access_token;
const userData = res.data.data.user;
console.log(userData, 'userData...')
// 使用useUser Hook的loginUser方法更新状态
loginUser(token, userData);
if (userData.phone) {
console.log('手机号已获取', userData.phone)
const updatedFormData = {
...FormData,
phone: userData.phone,
// 不自动填充微信昵称,保持用户已输入的昵称
nickname: FormData?.nickname || '',
// 只在没有头像时才使用微信头像
avatar: FormData?.avatar || userData.avatar
}
setFormData(updatedFormData)
// 更新表单字段值
if (formRef.current) {
formRef.current.setFieldsValue({
phone: userData.phone,
// 不覆盖用户已输入的昵称
realName: FormData?.nickname || '',
avatar: FormData?.avatar || userData.avatar
})
}
Taro.showToast({
title: '手机号获取成功',
icon: 'success',
duration: 1500
})
}
// 处理邀请关系
if (userData?.userId) {
try {
const inviteSuccess = await handleInviteRelation(userData.userId)
if (inviteSuccess) {
Taro.showToast({
title: '邀请关系建立成功',
icon: 'success',
duration: 2000
})
}
} catch (error) {
console.error('处理邀请关系失败:', error)
}
}
// 显示登录成功提示
// Taro.showToast({
// title: '注册成功',
// icon: 'success',
// duration: 1500
// })
// 不需要重新启动小程序,状态已经通过useUser更新
// 可以选择性地刷新当前页面数据
// await reload();
}
})
} else {
console.log('登录失败!')
}
}
})
}
// 处理固定按钮点击事件
const handleFixedButtonClick = () => {
// 触发表单提交
@ -88,25 +348,17 @@ const AddApply = () => {
console.log(error, 'err...')
}
const uploadImg1 = () => {
uploadFile().then(data => {
setFormData({
...FormData,
uploadImg1: data.url
})
});
}
useEffect(() => {
reload().then(() => {
setLoading(false)
})
}, []); // 依赖用户ID,当用户变化时重新加载
}, [user?.userId]); // 依赖用户ID,当用户变化时重新加载
// 当FormData变化时,同步更新表单字段值
useEffect(() => {
if (formRef.current && FormData) {
formRef.current.setFieldsValue({
refereeId: FormData.refereeId,
phone: FormData.phone,
avatar: FormData.avatar,
realName: FormData.nickname
@ -128,106 +380,42 @@ const AddApply = () => {
onFinish={(values) => submitSucceed(values)}
onFinishFailed={(errors) => submitFailed(errors)}
>
<View className={'bg-gray-100 h-3'}></View>
<CellGroup style={{padding: '4px 0'}}>
{JSON.stringify(FormData)}
<Form.Item label="用户类型" name="type" initialValue={FormData?.type} required>
<Radio.Group
defaultValue="0"
direction="horizontal"
value={FormData?.type}
onChange={(value: any) => {
setFormData({
...FormData,
type: value
})
}}
>
{userType?.map((item) => (
<Radio key={item.value} value={item.value}>
{item.label}
</Radio>
))}
</Radio.Group>
<Form.Item name="refereeId" label="邀请人ID" initialValue={FormData?.refereeId} required>
<Input placeholder="邀请人ID" disabled={true}/>
</Form.Item>
<Form.Item label="姓名" name="realName" required>
<Form.Item name="phone" label="手机号" initialValue={FormData?.phone} required>
<View className="flex items-center justify-between">
<Input
placeholder={'请填写真实姓名'}
value={FormData?.realName || ''}
onChange={(value) => {
setFormData({
...FormData,
realName: value
})
}}
placeholder="请填写手机号"
disabled={true}
maxLength={11}
value={FormData?.phone || ''}
/>
<Button style={{color: '#ffffff'}} open-type="getPhoneNumber" onGetPhoneNumber={handleGetPhoneNumber}>
<Space>
<Button size="small"></Button>
</Space>
</Button>
</View>
</Form.Item>
<Form.Item label="性别" name="sex" required>
<Radio.Group
direction="horizontal"
value={FormData?.sex || "0"}
>
<Radio value="1">
</Radio>
<Radio value="2">
</Radio>
</Radio.Group>
</Form.Item>
<Form.Item label="年龄" name="age" initialValue={FormData?.age} required>
<ConfigProvider theme={customTheme}>
<InputNumber
value={FormData?.age}
onChange={(value) => {
setFormData({
...FormData,
age: Number(value)
})
}}
min={0}
max={120}
step={1}
/>
</ConfigProvider>
</Form.Item>
<Form.Item label={FormData?.type == 1 ? '医生简介' : '个人简介'} name="introduction">
<TextArea
style={{
height: '50px',
backgroundColor: '#fafafa',
padding: '10px',
}}
value={FormData?.introduction || ''}
/>
{
FormData?.phone && <Form.Item name="avatar" label="头像" initialValue={FormData?.avatar} required>
<Button open-type="chooseAvatar" style={{height: '58px'}} onChooseAvatar={uploadAvatar}>
<Avatar src={FormData?.avatar || user?.avatar} size="54"/>
</Button>
</Form.Item>
{FormData?.type == 1 && (
<>
<Form.Item label="证书编号" name="userCode" required>
}
<Form.Item name="realName" label="昵称" initialValue="" required>
<Input
placeholder={'请填证书编号'}
value={FormData?.userCode || ''}
onChange={(value) => {
setFormData({
...FormData,
idCard: value
})
}}
type="nickname"
className="info-content__input"
placeholder="请获取微信昵称"
value={FormData?.nickname || ''}
onInput={(e: InputEvent) => getWxNickname(e.detail.value)}
/>
</Form.Item>
<Form.Item
label={'上传证件'}
name="uploadImg1"
required
rules={[{message: '请上传医师资格证书'}
]}
>
<div onClick={uploadImg1}>
<Image src={FormData.uploadImg1 || ''} mode={'scaleToFill'} lazyLoad={false} />
</div>
</Form.Item>
</>
)}
</CellGroup>
</Form>
@ -242,4 +430,4 @@ const AddApply = () => {
);
};
export default AddApply;
export default AddDoctor;

24
src/doctor/index.tsx

@ -58,18 +58,18 @@ const DealerIndex: React.FC = () => {
console.log(getGradientBackground(),'getGradientBackground()')
if (error) {
return (
<View className="p-4">
<View className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
<Text className="text-red-600">{error}</Text>
</View>
<Button type="primary" onClick={refresh}>
</Button>
</View>
)
}
// if (error) {
// return (
// <View className="p-4">
// <View className="bg-red-50 border border-red-200 rounded-lg p-4 mb-4">
// <Text className="text-red-600">{error}</Text>
// </View>
// <Button type="primary" onClick={refresh}>
// 重试
// </Button>
// </View>
// )
// }
return (
<View className="bg-gray-100 min-h-screen">

1
src/hooks/useConfig.ts

@ -41,6 +41,7 @@ export const useConfig = () => {
configWebsiteField().then(data => {
setConfig(data);
Taro.setStorageSync('config', data);
setLoading(false);
}).catch(err => {
setError(err instanceof Error ? err : new Error('获取配置失败'));

3
src/pages/category/category.tsx

@ -23,8 +23,9 @@ function Category() {
const initData = async () => {
try {
setLoading(true)
const home = await listCmsNavigation({home: 1})
// 获取商品分类
const categoryList = await listCmsNavigation({ model: 'goods' })
const categoryList = await listCmsNavigation({ parentId: home[0].navigationId || 0, position: 1 })
if (!categoryList || categoryList.length === 0) {
Taro.showToast({

12
src/pages/index/Banner.tsx

@ -100,7 +100,7 @@ const MyPage = () => {
}
return (
<View className="flex p-2 justify-between" style={{height: `${carouselHeight}px`}}>
<View className="flex p-2 justify-between bg-white" style={{height: `${carouselHeight}px`}}>
{/* 左侧轮播图区域 */}
<View
style={{width: '50%', height: '100%'}}
@ -143,10 +143,10 @@ const MyPage = () => {
{/* 右侧上下图片区域 - 从API获取数据 */}
<View className="flex flex-col" style={{width: '50%', height: '100%'}}>
{/* 上层图片 - 使用今日热卖素材 */}
<View className={'ml-2 bg-white rounded-lg shadow-sm'}>
<View className={'ml-2 bg-white'}>
<View className={'px-3 my-2 font-bold text-sm'}></View>
<View className={'px-3 flex justify-between'} style={{
height: '110px'
height: '94px'
}}>
{
hotToday?.imageList?.map(item => (
@ -162,15 +162,15 @@ const MyPage = () => {
}}
onClick={() => navTo(item.path)}
/>
<View className={'text-xs py-2 text-orange-600 whitespace-nowrap text-center'}>{item.title || '到手价¥9.9'}</View>
<View className={'text-xs my-2 text-orange-600 whitespace-nowrap text-center'}>{item.title || '到手价¥9.9'}</View>
</View>
))
}
</View>
</View>
{/* 下层图片 - 使用社区拼团素材 */}
<View className={'ml-2 bg-white rounded-lg mt-3 shadow-sm'}>
{/* 下层图片 - 使用社区拼团素材 rounded-lg shadow-sm */}
<View className={'ml-2 bg-white mt-2'}>
<View className={'px-3 my-2 font-bold text-sm'}>{item?.overview || item?.categoryName || '推荐文章'}</View>
<View className={'rounded-lg px-3 pb-3'}>
<Image

79
src/shop/category/components/GoodsList.tsx

@ -1,50 +1,59 @@
import {Image} from '@nutui/nutui-react-taro'
import {Share} from '@nutui/icons-react-taro'
import {View, Text} from '@tarojs/components'
import Taro from '@tarojs/taro'
import {ShopGoods} from "@/api/shop/shopGoods/model"
import {CmsNavigation} from "@/api/cms/cmsNavigation/model"
import './GoodsList.scss'
interface GoodsListProps {
data: ShopGoods[]
nav?: CmsNavigation
}
const GoodsList = (props: any) => {
const GoodsList = (props: GoodsListProps) => {
return (
<>
<div className={'py-3'}>
<div className={'flex flex-col justify-between items-center rounded-lg px-2'}>
<View className={'py-3'} style={{paddingTop: '0'}}>
<View className={'bg-gray-50'}>
<View className={'grid grid-cols-2 gap-2 pb-2 p-2'}>
{props.data?.map((item, index) => {
return (
<div key={index} className={'flex flex-col rounded-lg bg-white shadow-sm w-full mb-5'}>
<Image src={item.image} mode={'aspectFit'} lazyLoad={false}
radius="10px 10px 0 0" height="180"
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
<div className={'flex flex-col p-2 rounded-lg'}>
<div>
<div className={'car-no text-sm'}>{item.name}</div>
<div className={'flex justify-between text-xs py-1'}>
<span className={'text-orange-500'}>{item.comments}</span>
<span className={'text-gray-400'}> {item.sales}</span>
</div>
<div className={'flex justify-between items-center py-2'}>
<div className={'flex text-red-500 text-xl items-baseline'}>
<span className={'text-xs'}></span>
<span className={'font-bold text-2xl'}>{item.price}</span>
</div>
<div className={'buy-btn'}>
<div className={'cart-icon'}>
<Share size={20} className={'mx-4 mt-2'}
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
</div>
<div className={'text-white pl-4 pr-5'}
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}>
</div>
</div>
</div>
</div>
</div>
</div>
<View key={index} className={'goods-waterfall-item bg-white'} style={{
borderRadius: '0 0 6px 6px'
}}>
<View className={'goods-card'}>
<Image
src={item.image}
lazyLoad={false}
style={{
borderRadius: '6px 6px 0 0',
height: '180px'
}}
onClick={() => Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}
/>
<View className={'goods-info p-2 flex flex-col'}>
<View className={'goods-title text-sm font-bold'}>{item.name}</View>
<View className={'goods-meta'}>
<Text className={'goods-comments text-gray-400 text-xs'}>{item.comments}</Text>
</View>
<View className={'goods-price-section flex justify-between'}>
<View className={'goods-price'}>
<Text className={'price-unit text-orange-600 font-bold text-lg'}></Text>
<Text className={'price-number text-orange-600 font-bold text-lg'}>{item.price}</Text>
</View>
<View className={'goods-actions'}>
<Text className={'goods-sales text-gray-400 text-xs'}> {item.sales}</Text>
</View>
</View>
</View>
</View>
</View>
)
})}
</div>
</div>
</View>
</View>
</View>
</>
)
}

8
src/shop/goodsDetail/index.tsx

@ -342,7 +342,9 @@ const GoodsDetail = () => {
)}
</View>
<View className={'w-full'}>
<View className={'p-4 bg-white leading-7 '}>
<RichText nodes={goods.content || '内容详情'}/>
</View>
<View className={'h-24'}></View>
</View>
</View>
@ -361,11 +363,11 @@ const GoodsDetail = () => {
</View>
</Popup>
{/*底部购买按钮*/}
<View className={'fixed bg-white w-full bottom-0 left-0 pt-4 pb-8'}>
<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'}>
<Headphones size={18} style={{marginRight: '4px'}}/>
<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'}>

Loading…
Cancel
Save