From a1cacc04e8b0747e4222d44677f6c84c072e283c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Wed, 13 Aug 2025 10:11:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E4=BB=8E=E6=96=87=E7=AB=A0?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E9=A1=B5=E9=9D=A2=E6=94=B9=E4=B8=BA=E6=96=87?= =?UTF-8?q?=E7=AB=A0=E7=AE=A1=E7=90=86=E9=A1=B5=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改页面配置,设置新的导航栏标题和样式 - 重新设计页面布局,增加搜索栏、文章列表和操作按钮 - 添加文章搜索、分页加载和删除功能 - 优化文章列表项的样式和交互 - 新增礼品卡相关API和组件 - 更新优惠券组件,增加到期提醒和筛选功能 --- .../article}/add.config.ts | 0 .../shopArticle => admin/article}/add.tsx | 0 src/admin/article/index.config.ts | 3 +- src/admin/article/index.tsx | 306 ++++++++++-- src/api/shop/shopArticle/model/index.ts | 2 - src/api/shop/shopCoupon/model/index.ts | 1 + src/api/shop/shopGift/index.ts | 180 +++++++ src/api/shop/shopGift/model/index.ts | 115 +++++ src/app.config.ts | 17 +- src/components/CouponCard.scss | 78 +-- src/components/CouponCard.tsx | 40 +- src/components/CouponExpireNotice.tsx | 174 +++++++ src/components/CouponFilter.tsx | 210 ++++++++ src/components/CouponGuide.tsx | 159 ++++++ src/components/CouponShare.tsx | 182 +++++++ src/components/CouponStats.tsx | 71 +++ src/components/CouponUsageRecord.tsx | 162 ++++++ src/components/GiftCard.scss | 307 ++++++++++++ src/components/GiftCard.tsx | 282 +++++++++++ src/components/GiftCardGuide.tsx | 169 +++++++ src/components/GiftCardList.tsx | 173 +++++++ src/components/GiftCardShare.tsx | 227 +++++++++ src/components/GiftCardStats.tsx | 72 +++ src/components/GiftCardStatsMax.tsx | 87 ++++ src/dealer/components/EarningsCard.tsx | 75 --- src/dealer/components/FunctionMenu.tsx | 102 ---- src/dealer/components/NavigationBar.tsx | 78 --- src/dealer/components/OrderIcon.tsx | 211 -------- src/dealer/components/UserCard.tsx | 135 ----- src/dealer/components/UserCell.tsx | 148 ------ src/dealer/components/UserFooter.tsx | 102 ---- src/dealer/index.scss | 77 --- src/dealer/index.tsx | 87 ++-- src/dealer/orders/index.config.ts | 3 + src/dealer/orders/index.scss | 145 ------ src/dealer/orders/index.tsx | 241 +-------- src/dealer/qrcode/index.config.ts | 3 + src/dealer/qrcode/index.scss | 247 --------- src/dealer/qrcode/index.tsx | 232 +-------- src/dealer/team/index.config.ts | 3 + src/dealer/team/index.scss | 176 ------- src/dealer/team/index.tsx | 245 +-------- src/dealer/withdraw/index.config.ts | 3 + src/dealer/withdraw/index.scss | 75 --- src/dealer/withdraw/index.tsx | 167 ++----- src/pages/user/components/UserCard.tsx | 24 +- src/shop/shopArticle/index.tsx | 272 ---------- .../coupon/add.config.ts} | 2 +- src/user/coupon/add.tsx | 323 ++++++++++++ src/user/coupon/detail.config.ts | 6 + src/user/coupon/detail.tsx | 259 ++++++++++ src/user/coupon/index.config.ts | 5 + src/user/coupon/index.tsx | 470 ++++++++++++++++++ src/user/coupon/receive.config.ts | 5 + src/user/coupon/receive.tsx | 248 +++++++++ src/user/gift/add.config.ts | 4 + src/user/gift/add.tsx | 323 ++++++++++++ src/user/gift/detail.config.ts | 6 + src/user/gift/detail.tsx | 345 +++++++++++++ src/user/gift/index.config.ts | 5 + src/user/gift/index.tsx | 403 +++++++++++++++ src/user/gift/receive.config.ts | 5 + src/user/gift/receive.tsx | 248 +++++++++ src/user/gift/redeem.config.ts | 6 + src/user/gift/redeem.tsx | 266 ++++++++++ src/user/gift/use.config.ts | 6 + src/user/gift/use.tsx | 291 +++++++++++ 67 files changed, 6278 insertions(+), 2816 deletions(-) rename src/{shop/shopArticle => admin/article}/add.config.ts (100%) rename src/{shop/shopArticle => admin/article}/add.tsx (100%) create mode 100644 src/api/shop/shopGift/index.ts create mode 100644 src/api/shop/shopGift/model/index.ts create mode 100644 src/components/CouponExpireNotice.tsx create mode 100644 src/components/CouponFilter.tsx create mode 100644 src/components/CouponGuide.tsx create mode 100644 src/components/CouponShare.tsx create mode 100644 src/components/CouponStats.tsx create mode 100644 src/components/CouponUsageRecord.tsx create mode 100644 src/components/GiftCard.scss create mode 100644 src/components/GiftCard.tsx create mode 100644 src/components/GiftCardGuide.tsx create mode 100644 src/components/GiftCardList.tsx create mode 100644 src/components/GiftCardShare.tsx create mode 100644 src/components/GiftCardStats.tsx create mode 100644 src/components/GiftCardStatsMax.tsx delete mode 100644 src/dealer/components/EarningsCard.tsx delete mode 100644 src/dealer/components/FunctionMenu.tsx delete mode 100644 src/dealer/components/NavigationBar.tsx delete mode 100644 src/dealer/components/OrderIcon.tsx delete mode 100644 src/dealer/components/UserCard.tsx delete mode 100644 src/dealer/components/UserCell.tsx delete mode 100644 src/dealer/components/UserFooter.tsx delete mode 100644 src/dealer/index.scss create mode 100644 src/dealer/orders/index.config.ts delete mode 100644 src/dealer/orders/index.scss create mode 100644 src/dealer/qrcode/index.config.ts delete mode 100644 src/dealer/qrcode/index.scss create mode 100644 src/dealer/team/index.config.ts delete mode 100644 src/dealer/team/index.scss create mode 100644 src/dealer/withdraw/index.config.ts delete mode 100644 src/dealer/withdraw/index.scss delete mode 100644 src/shop/shopArticle/index.tsx rename src/{shop/shopArticle/index.config.ts => user/coupon/add.config.ts} (59%) create mode 100644 src/user/coupon/add.tsx create mode 100644 src/user/coupon/detail.config.ts create mode 100644 src/user/coupon/detail.tsx create mode 100644 src/user/coupon/index.config.ts create mode 100644 src/user/coupon/index.tsx create mode 100644 src/user/coupon/receive.config.ts create mode 100644 src/user/coupon/receive.tsx create mode 100644 src/user/gift/add.config.ts create mode 100644 src/user/gift/add.tsx create mode 100644 src/user/gift/detail.config.ts create mode 100644 src/user/gift/detail.tsx create mode 100644 src/user/gift/index.config.ts create mode 100644 src/user/gift/index.tsx create mode 100644 src/user/gift/receive.config.ts create mode 100644 src/user/gift/receive.tsx create mode 100644 src/user/gift/redeem.config.ts create mode 100644 src/user/gift/redeem.tsx create mode 100644 src/user/gift/use.config.ts create mode 100644 src/user/gift/use.tsx diff --git a/src/shop/shopArticle/add.config.ts b/src/admin/article/add.config.ts similarity index 100% rename from src/shop/shopArticle/add.config.ts rename to src/admin/article/add.config.ts diff --git a/src/shop/shopArticle/add.tsx b/src/admin/article/add.tsx similarity index 100% rename from src/shop/shopArticle/add.tsx rename to src/admin/article/add.tsx diff --git a/src/admin/article/index.config.ts b/src/admin/article/index.config.ts index d74c9f2..87493d8 100644 --- a/src/admin/article/index.config.ts +++ b/src/admin/article/index.config.ts @@ -1,3 +1,4 @@ export default definePageConfig({ - navigationBarTitleText: '文章详情' + navigationBarTitleText: '商品文章管理', + navigationBarTextStyle: 'black' }) diff --git a/src/admin/article/index.tsx b/src/admin/article/index.tsx index f92c890..3cadf4f 100644 --- a/src/admin/article/index.tsx +++ b/src/admin/article/index.tsx @@ -1,53 +1,271 @@ -import Taro from '@tarojs/taro' -import {useEffect, useState} from 'react' -import {useRouter} from '@tarojs/taro' -import {Loading} from '@nutui/nutui-react-taro' -import {View, RichText} from '@tarojs/components' -import {wxParse} from "@/utils/common"; -import {getCmsArticle} from "@/api/cms/cmsArticle"; -import {CmsArticle} from "@/api/cms/cmsArticle/model" -import Line from "@/components/Gap"; -import './index.scss' +import {useState} from "react"; +import Taro, {useDidShow} from '@tarojs/taro' +import {Button, Cell, CellGroup, Empty, ConfigProvider, SearchBar, Tag, InfiniteLoading, Loading, PullToRefresh} from '@nutui/nutui-react-taro' +import {Edit, Del, Eye} from '@nutui/icons-react-taro' +import {View} from '@tarojs/components' +import {CmsArticle} from "@/api/cms/cmsArticle/model"; +import {pageCmsArticle, removeCmsArticle} from "@/api/cms/cmsArticle"; +import FixedButton from "@/components/FixedButton"; +import dayjs from "dayjs"; -function Detail() { - const {params} = useRouter(); - const [loading, setLoading] = useState(true) - // 文章详情 - const [item, setItem] = useState() - const reload = async () => { - const item = await getCmsArticle(Number(params.id)) +const ArticleArticleManage = () => { + const [list, setList] = useState([]) + const [loading, setLoading] = useState(false) + // const [refreshing, setRefreshing] = useState(false) + const [hasMore, setHasMore] = useState(true) + const [searchValue, setSearchValue] = useState('') + const [page, setPage] = useState(1) + const [total, setTotal] = useState(0) - if (item) { - item.content = wxParse(item.content) - setItem(item) - Taro.setNavigationBarTitle({ - title: `${item?.categoryName}` + const reload = async (isRefresh = false) => { + if (isRefresh) { + setPage(1) + setList([]) + setHasMore(true) + } + + setLoading(true) + try { + const currentPage = isRefresh ? 1 : page + const res = await pageCmsArticle({ + page: currentPage, + limit: 10, + keywords: searchValue }) + + if (res && res.list) { + const newList = isRefresh ? res.list : [...list, ...res.list] + setList(newList) + setTotal(res.count || 0) + + // 判断是否还有更多数据 + setHasMore(res.list.length === 10) // 如果返回的数据等于limit,说明可能还有更多 + + if (!isRefresh) { + setPage(currentPage + 1) + } else { + setPage(2) // 刷新后下一页是第2页 + } + } else { + setHasMore(false) + setTotal(0) + } + } catch (error) { + console.error('获取文章失败:', error) + Taro.showToast({ + title: '获取文章失败', + icon: 'error' + }); + } finally { + setLoading(false) } } - useEffect(() => { - reload().then(() => { - setLoading(false) - }); - }, []); - - if (loading) { - return ( - 加载中 - ) + // 搜索功能 + const handleSearch = (value: string) => { + setSearchValue(value) + reload(true) } - return ( -
-
{item?.title}
-
{item?.createTime}
- - - - -
- ) -} + // 下拉刷新 + const handleRefresh = async () => { + // setRefreshing(true) + await reload(true) + // setRefreshing(false) + } -export default Detail + // 删除文章 + const handleDelete = async (id?: number) => { + Taro.showModal({ + title: '确认删除', + content: '确定要删除这篇文章吗?', + success: async (res) => { + if (res.confirm) { + try { + await removeCmsArticle(id) + Taro.showToast({ + title: '删除成功', + icon: 'success' + }); + reload(true); + } catch (error) { + Taro.showToast({ + title: '删除失败', + icon: 'error' + }); + } + } + } + }); + } + + // 编辑文章 + const handleEdit = (item: CmsArticle) => { + Taro.navigateTo({ + url: `/shop/shopArticle/add?id=${item.articleId}` + }); + } + + // 查看文章详情 + const handleView = (item: CmsArticle) => { + // 这里可以跳转到文章详情页面 + Taro.navigateTo({ + url: `/cms/detail/index?id=${item.articleId}` + }) + } + + // 获取状态标签 + const getStatusTag = (status?: number) => { + switch (status) { + case 0: + return 已发布 + case 1: + return 待审核 + case 2: + return 已驳回 + case 3: + return 违规内容 + default: + return 未知 + } + } + + // 加载更多 + const loadMore = async () => { + if (!loading && hasMore) { + await reload(false) // 不刷新,追加数据 + } + } + + useDidShow(() => { + reload(true).then() + }); + + return ( + + {/* 搜索栏 */} + + + + + {/* 统计信息 */} + {total > 0 && ( + + 共找到 {total} 篇文章 + + )} + + {/* 文章列表 */} + + + {list.length === 0 && !loading ? ( + + + + ) : ( + + + 加载中... + + } + loadMoreText={ + + {list.length === 0 ? "暂无数据" : "没有更多了"} + + } + > + {list.map((item, index) => ( + + + + {/* 文章标题和状态 */} + + + + {item.title} + + + {getStatusTag(item.status)} + + + {/* 文章概述 */} + {item.overview && ( + + {item.overview} + + )} + + {/* 文章信息 */} + + + 阅读: {item.actualViews || 0} + {item.price && 价格: ¥{item.price}} + 创建: {dayjs(item.createTime).format('MM-DD HH:mm')} + + + + {/* 操作按钮 */} + + + + + + + + + ))} + + )} + + + + {/* 底部浮动按钮 */} + } + onClick={() => Taro.navigateTo({url: '/shop/shopArticle/add'})} + /> + + ); + +}; + +export default ArticleArticleManage; diff --git a/src/api/shop/shopArticle/model/index.ts b/src/api/shop/shopArticle/model/index.ts index d89b574..18e7b58 100644 --- a/src/api/shop/shopArticle/model/index.ts +++ b/src/api/shop/shopArticle/model/index.ts @@ -84,8 +84,6 @@ export interface ShopArticle { bmUsers?: number; // 用户ID userId?: number; - // 商户ID - merchantId?: number; // 项目ID projectId?: number; // 语言 diff --git a/src/api/shop/shopCoupon/model/index.ts b/src/api/shop/shopCoupon/model/index.ts index 5c4f42b..13aaa22 100644 --- a/src/api/shop/shopCoupon/model/index.ts +++ b/src/api/shop/shopCoupon/model/index.ts @@ -54,6 +54,7 @@ export interface ShopCoupon { limitPerUser?: number; // 是否启用(0禁用 1启用) enabled?: string; + sortBy?: string; } /** diff --git a/src/api/shop/shopGift/index.ts b/src/api/shop/shopGift/index.ts new file mode 100644 index 0000000..8669003 --- /dev/null +++ b/src/api/shop/shopGift/index.ts @@ -0,0 +1,180 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api/index'; +import type { ShopGift, ShopGiftParam, GiftRedeemParam, GiftUseParam } from './model'; + +/** + * 分页查询礼品卡 + */ +export async function pageShopGift(params: ShopGiftParam) { + const res = await request.get>>( + '/shop/shop-gift/page', + params + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 查询礼品卡列表 + */ +export async function listShopGift(params?: ShopGiftParam) { + const res = await request.get>( + '/shop/shop-gift', + params + ); + if (res.code === 0 && res.data) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 添加礼品卡 + */ +export async function addShopGift(data: ShopGift) { + const res = await request.post>( + '/shop/shop-gift', + data + ); + if (res.code === 0) { + return res.message; + } +} + +/** + * 生成礼品卡 + */ +export async function makeShopGift(data: ShopGift) { + const res = await request.post>( + '/shop/shop-gift/make', + data + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 修改礼品卡 + */ +export async function updateShopGift(data: ShopGift) { + const res = await request.put>( + '/shop/shop-gift', + data + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 删除礼品卡 + */ +export async function removeShopGift(id?: number) { + const res = await request.del>( + '/shop/shop-gift/' + id + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 批量删除礼品卡 + */ +export async function removeBatchShopGift(data: (number | undefined)[]) { + const res = await request.del>( + '/shop/shop-gift/batch', + { + data + } + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 根据id查询礼品卡 + */ +export async function getShopGift(id: number) { + const res = await request.get>( + '/shop/shop-gift/' + id + ); + if (res.code === 0 && res.data) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 兑换礼品卡 + */ +export async function redeemGift(params: GiftRedeemParam) { + const res = await request.post>( + '/shop/shop-gift/redeem', + params + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 使用礼品卡 + */ +export async function useGift(params: GiftUseParam) { + const res = await request.post>( + '/shop/shop-gift/use', + params + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 获取用户的礼品卡列表 + */ +export async function getUserGifts(params: ShopGiftParam) { + const res = await request.get>>( + '/shop/shop-gift/page', + params + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 验证礼品卡兑换码 + */ +export async function validateGiftCode(code: string) { + const res = await request.get>( + `/shop/shop-gift/validate/${code}` + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +export async function exportShopGift(ids?: number[]) { + const res = await request.post>( + '/shop/shop-gift/export', + ids + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} diff --git a/src/api/shop/shopGift/model/index.ts b/src/api/shop/shopGift/model/index.ts new file mode 100644 index 0000000..13b4e9b --- /dev/null +++ b/src/api/shop/shopGift/model/index.ts @@ -0,0 +1,115 @@ +import type { PageParam } from '@/api/index'; + +/** + * 礼品卡 + */ +export interface ShopGift { + // 礼品卡ID + id?: number; + // 礼品卡名称 + name?: string; + // 礼品卡描述 + description?: string; + // 礼品卡兑换码 + code?: string; + // 关联商品ID + goodsId?: number; + // 商品名称 + goodsName?: string; + // 商品图片 + goodsImage?: string; + // 礼品卡面值 + faceValue?: string; + // 礼品卡类型 (10实物礼品卡 20虚拟礼品卡 30服务礼品卡) + type?: number; + // 领取时间 + takeTime?: string; + // 使用时间 + useTime?: string; + // 过期时间 + expireTime?: string; + // 有效期天数 + validDays?: number; + // 操作人 + operatorUserId?: number; + // 是否展示 + isShow?: string; + // 状态 (0未使用 1已使用 2已过期 3已失效) + status?: number; + // 使用状态 (0可用 1已使用 2已过期) + useStatus?: number; + // 备注 + comments?: string; + // 使用说明 + instructions?: string; + // 排序号 + sortNumber?: number; + // 拥有者用户ID + userId?: number; + // 发放者用户ID + issuerUserId?: number; + // 是否删除, 0否, 1是 + deleted?: number; + // 租户id + tenantId?: number; + // 创建时间 + createTime?: string; + // 修改时间 + updateTime?: string; + // 数量 + num?: number; + // 已发放数量 + issuedCount?: number; + // 总发放数量 + totalCount?: number; + // 使用门店/地址 + useLocation?: string; + // 客服联系方式 + contactInfo?: string; +} + +/** + * 礼品卡搜索条件 + */ +export interface ShopGiftParam extends PageParam { + id?: number; + keywords?: string; + // 礼品卡类型筛选 + type?: number; + // 状态筛选 + status?: number; + // 使用状态筛选 + useStatus?: number; + // 用户ID筛选 + userId?: number; + // 商品ID筛选 + goodsId?: number; + // 是否过期筛选 + isExpired?: boolean; + // 排序字段 + sortBy?: 'createTime' | 'expireTime' | 'faceValue' | 'useTime'; + // 排序方向 + sortOrder?: 'asc' | 'desc'; +} + +/** + * 礼品卡兑换参数 + */ +export interface GiftRedeemParam { + // 兑换码 + code: string; + // 用户ID + userId?: number; +} + +/** + * 礼品卡使用参数 + */ +export interface GiftUseParam { + // 礼品卡ID + giftId: number; + // 使用地址/门店 + useLocation?: string; + // 使用备注 + useNote?: string; +} diff --git a/src/app.config.ts b/src/app.config.ts index 2e289cc..2709be9 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -38,8 +38,9 @@ export default defineAppConfig({ "help/index", "about/index", "wallet/wallet", - "coupon/coupon", - "points/points" + // "coupon/index", + // "points/points", + "gift/index" ] }, { @@ -54,15 +55,17 @@ export default defineAppConfig({ }, { "root": "shop", - "pages": [ - 'category/index', + "pages": ['category/index', 'orderDetail/index', 'goodsDetail/index', 'orderConfirm/index', 'orderConfirmCart/index', - 'search/index', - 'shopArticle/index', - 'shopArticle/add' + 'search/index'] + }, + { + "root": "admin", + "pages": [ + "article/index", ] } ], diff --git a/src/components/CouponCard.scss b/src/components/CouponCard.scss index fd0a763..76beb0f 100644 --- a/src/components/CouponCard.scss +++ b/src/components/CouponCard.scss @@ -2,12 +2,18 @@ position: relative; display: flex; width: 100%; - height: 100px; - margin-bottom: 12px; - border-radius: 8px; + height: 120px; + margin-bottom: 16px; + border-radius: 12px; overflow: hidden; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); background: #fff; + transition: all 0.3s ease; + + &:active { + transform: scale(0.98); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); + } &.disabled { opacity: 0.6; @@ -15,7 +21,7 @@ .coupon-left { flex-shrink: 0; - width: 100px; + width: 110px; display: flex; flex-direction: column; align-items: center; @@ -24,23 +30,23 @@ position: relative; &.theme-red { - background: linear-gradient(135deg, #f87171 0%, #ef4444 100%); + background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%); } &.theme-orange { - background: linear-gradient(135deg, #fb923c 0%, #f97316 100%); + background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%); } &.theme-blue { - background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%); + background: linear-gradient(135deg, #42a5f5 0%, #2196f3 100%); } &.theme-purple { - background: linear-gradient(135deg, #a78bfa 0%, #8b5cf6 100%); + background: linear-gradient(135deg, #ab47bc 0%, #9c27b0 100%); } &.theme-green { - background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%); + background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%); } .amount-wrapper { @@ -49,22 +55,23 @@ margin-bottom: 8px; .currency { - font-size: 24px; - font-weight: 500; + font-size: 28px; + font-weight: 600; margin-right: 2px; } .amount { - font-size: 30px; + font-size: 36px; font-weight: bold; line-height: 1; } } .condition { - font-size: 24px; + font-size: 22px; opacity: 0.9; - margin-top: 2px; + text-align: center; + line-height: 1.2; } } @@ -112,21 +119,23 @@ display: flex; flex-direction: column; justify-content: space-between; - padding: 12px; + padding: 16px; .coupon-info { flex: 1; .coupon-title { - font-size: 28px; + font-size: 32px; font-weight: 600; color: #1f2937; - margin-bottom: 4px; + margin-bottom: 6px; + line-height: 1.3; } .coupon-validity { - font-size: 24px; + font-size: 26px; color: #6b7280; + line-height: 1.2; } } @@ -136,38 +145,45 @@ align-items: center; .coupon-btn { - min-width: 48px; - height: 24px; - border-radius: 12px; - font-size: 24px; + min-width: 120px; + height: 60px; + border-radius: 30px; + font-size: 26px; border: none; color: #fff; + font-weight: 600; + transition: all 0.2s ease; + + &:active { + transform: scale(0.95); + } &.theme-red { - background: linear-gradient(135deg, #f87171 0%, #ef4444 100%); + background: linear-gradient(135deg, #ff6b6b 0%, #ee5a52 100%); } &.theme-orange { - background: linear-gradient(135deg, #fb923c 0%, #f97316 100%); + background: linear-gradient(135deg, #ffa726 0%, #ff9800 100%); } &.theme-blue { - background: linear-gradient(135deg, #60a5fa 0%, #3b82f6 100%); + background: linear-gradient(135deg, #42a5f5 0%, #2196f3 100%); } &.theme-purple { - background: linear-gradient(135deg, #a78bfa 0%, #8b5cf6 100%); + background: linear-gradient(135deg, #ab47bc 0%, #9c27b0 100%); } &.theme-green { - background: linear-gradient(135deg, #4ade80 0%, #22c55e 100%); + background: linear-gradient(135deg, #66bb6a 0%, #4caf50 100%); } } .status-text { - font-size: 24px; - color: #6b7280; - padding: 4px 8px; + font-size: 26px; + color: #9ca3af; + padding: 8px 12px; + font-weight: 500; } } } diff --git a/src/components/CouponCard.tsx b/src/components/CouponCard.tsx index d06f903..5c82f5e 100644 --- a/src/components/CouponCard.tsx +++ b/src/components/CouponCard.tsx @@ -79,13 +79,41 @@ const CouponCard: React.FC = ({ // 获取使用条件文本 const getConditionText = () => { - if (type === 3) return '无门槛' + if (type === 3) return '免费使用' // 免费券 if (minAmount && minAmount > 0) { - return `满${minAmount}可用` + return `满${minAmount}元可用` } return '无门槛' } + // 格式化有效期显示 + const formatValidityPeriod = () => { + if (!startTime || !endTime) return '' + + const start = new Date(startTime) + const end = new Date(endTime) + const now = new Date() + + // 如果还未开始 + if (now < start) { + return `${start.getMonth() + 1}.${start.getDate()} 开始生效` + } + + // 计算剩余天数 + const diffTime = end.getTime() - now.getTime() + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + + if (diffDays <= 0) { + return '已过期' + } else if (diffDays <= 3) { + return `${diffDays}天后过期` + } else { + return `${end.getMonth() + 1}.${end.getDate()} 过期` + } + } + + + // 格式化日期 const formatDate = (dateStr?: string) => { if (!dateStr) return '' @@ -108,8 +136,8 @@ const CouponCard: React.FC = ({ {/* 左侧金额区域 */} - ¥ - {amount} + {type !== 3 && ¥} + {formatAmount()} {getConditionText()} @@ -130,7 +158,7 @@ const CouponCard: React.FC = ({ {title || (type === 1 ? '满减券' : type === 2 ? '折扣券' : '免费券')} - 有效期:{getValidityText()} + {formatValidityPeriod()} @@ -151,7 +179,7 @@ const CouponCard: React.FC = ({ size="small" onClick={onUse} > - 使用 + 立即使用 )} {status !== 0 && ( diff --git a/src/components/CouponExpireNotice.tsx b/src/components/CouponExpireNotice.tsx new file mode 100644 index 0000000..6c77575 --- /dev/null +++ b/src/components/CouponExpireNotice.tsx @@ -0,0 +1,174 @@ +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Button, Popup } from '@nutui/nutui-react-taro' +import { Clock, Close, Agenda } from '@nutui/icons-react-taro' +import Taro from '@tarojs/taro' + +export interface ExpiringSoon { + id: number + name: string + type: number + amount: string + minAmount?: string + endTime: string + daysLeft: number +} + +export interface CouponExpireNoticeProps { + /** 是否显示提醒 */ + visible: boolean + /** 即将过期的优惠券列表 */ + expiringSoonCoupons: ExpiringSoon[] + /** 关闭回调 */ + onClose: () => void + /** 使用优惠券回调 */ + onUseCoupon: (coupon: ExpiringSoon) => void +} + +const CouponExpireNotice: React.FC = ({ + visible, + expiringSoonCoupons, + onClose, + onUseCoupon +}) => { + // 获取优惠券金额显示 + const getCouponAmountDisplay = (coupon: ExpiringSoon) => { + switch (coupon.type) { + case 10: // 满减券 + return `¥${coupon.amount}` + case 20: // 折扣券 + return `${coupon.amount}折` + case 30: // 免费券 + return '免费' + default: + return `¥${coupon.amount}` + } + } + + // 获取使用条件文本 + const getConditionText = (coupon: ExpiringSoon) => { + if (coupon.type === 30) return '无门槛使用' + if (coupon.minAmount && parseFloat(coupon.minAmount) > 0) { + return `满${coupon.minAmount}元可用` + } + return '无门槛使用' + } + + // 获取到期时间显示 + const getExpireTimeDisplay = (coupon: ExpiringSoon) => { + if (coupon.daysLeft === 0) { + return '今天到期' + } else if (coupon.daysLeft === 1) { + return '明天到期' + } else { + return `${coupon.daysLeft}天后到期` + } + } + + // 去购物 + const handleGoShopping = () => { + onClose() + Taro.navigateTo({ + url: '/pages/index/index' + }) + } + + return ( + + + {/* 头部 */} + + + + + 优惠券即将过期 + + + + + + + + {/* 提醒文案 */} + + + 您有 {expiringSoonCoupons.length} 张优惠券即将过期,请及时使用 + + + + {/* 优惠券列表 */} + + {expiringSoonCoupons.map((coupon, _) => ( + + + + + {coupon.name} + + + {getCouponAmountDisplay(coupon)} + + + + {getConditionText(coupon)} + + + {getExpireTimeDisplay(coupon)} + + + + + + ))} + + + {/* 底部按钮 */} + + + + + + {/* 底部提示 */} + + + 过期的优惠券将无法使用,请及时关注有效期 + + + + + ) +} + +export default CouponExpireNotice diff --git a/src/components/CouponFilter.tsx b/src/components/CouponFilter.tsx new file mode 100644 index 0000000..64e4dca --- /dev/null +++ b/src/components/CouponFilter.tsx @@ -0,0 +1,210 @@ +import React, { useState } from 'react' +import { View, Text } from '@tarojs/components' +import { Button, Popup, Radio, RadioGroup, Divider } from '@nutui/nutui-react-taro' +import { Filter, Close } from '@nutui/icons-react-taro' + +export interface CouponFilterProps { + /** 是否显示筛选器 */ + visible: boolean + /** 当前筛选条件 */ + filters: { + type?: number[] + minAmount?: number + sortBy?: 'createTime' | 'amount' | 'expireTime' + sortOrder?: 'asc' | 'desc' + } + /** 筛选条件变更回调 */ + onFiltersChange: (filters: any) => void + /** 关闭回调 */ + onClose: () => void +} + +const CouponFilter: React.FC = ({ + visible, + filters, + onFiltersChange, + onClose +}) => { + const [tempFilters, setTempFilters] = useState(filters) + + // 优惠券类型选项 + const typeOptions = [ + { label: '全部类型', value: '' }, + { label: '满减券', value: '10' }, + { label: '折扣券', value: '20' }, + { label: '免费券', value: '30' } + ] + + // 最低金额选项 + const minAmountOptions = [ + { label: '不限', value: '' }, + { label: '10元以上', value: '10' }, + { label: '50元以上', value: '50' }, + { label: '100元以上', value: '100' }, + { label: '200元以上', value: '200' } + ] + + // 排序选项 + const sortOptions = [ + { label: '创建时间', value: 'createTime' }, + { label: '优惠金额', value: 'amount' }, + { label: '到期时间', value: 'expireTime' } + ] + + // 排序方向选项 + const sortOrderOptions = [ + { label: '升序', value: 'asc' }, + { label: '降序', value: 'desc' } + ] + + // 重置筛选条件 + const handleReset = () => { + const resetFilters = { + type: [], + minAmount: undefined, + sortBy: 'createTime' as const, + sortOrder: 'desc' as const + } + setTempFilters(resetFilters) + } + + // 应用筛选条件 + const handleApply = () => { + onFiltersChange(tempFilters) + onClose() + } + + // 更新临时筛选条件 + const updateTempFilters = (key: string, value: any) => { + setTempFilters(prev => ({ + ...prev, + [key]: value + })) + } + + return ( + + + {/* 头部 */} + + + + 筛选条件 + + + + + + + {/* 筛选内容 */} + + {/* 优惠券类型 */} + + + 优惠券类型 + + { + updateTempFilters('type', value ? [parseInt(value)] : []) + }} + > + {typeOptions.map(option => ( + + {option.label} + + ))} + + + + + + {/* 最低消费金额 */} + + + 最低消费金额 + + { + updateTempFilters('minAmount', value ? parseInt(value) : undefined) + }} + > + {minAmountOptions.map(option => ( + + {option.label} + + ))} + + + + + + {/* 排序方式 */} + + + 排序方式 + + updateTempFilters('sortBy', value)} + > + {sortOptions.map(option => ( + + {option.label} + + ))} + + + + + + {/* 排序方向 */} + + + 排序方向 + + updateTempFilters('sortOrder', value)} + > + {sortOrderOptions.map(option => ( + + {option.label} + + ))} + + + + + {/* 底部按钮 */} + + + + + + + + + ) +} + +export default CouponFilter diff --git a/src/components/CouponGuide.tsx b/src/components/CouponGuide.tsx new file mode 100644 index 0000000..b473a13 --- /dev/null +++ b/src/components/CouponGuide.tsx @@ -0,0 +1,159 @@ +import React, { useState } from 'react' +import { View, Text } from '@tarojs/components' +import { Button, Popup } from '@nutui/nutui-react-taro' +import { Ask, Ticket, Clock, Gift } from '@nutui/icons-react-taro' + +export interface CouponGuideProps { + /** 是否显示指南 */ + visible: boolean + /** 关闭回调 */ + onClose: () => void +} + +const CouponGuide: React.FC = ({ + visible, + onClose +}) => { + const [currentStep, setCurrentStep] = useState(0) + + const guideSteps = [ + { + title: '如何获取优惠券?', + icon: , + content: [ + '1. 点击"领取"按钮浏览可领取的优惠券', + '2. 关注商家活动和促销信息', + '3. 完成指定任务获得优惠券奖励', + '4. 邀请好友注册获得推荐奖励' + ] + }, + { + title: '如何使用优惠券?', + icon: , + content: [ + '1. 选择心仪商品加入购物车', + '2. 在结算页面选择可用优惠券', + '3. 确认优惠金额后完成支付', + '4. 优惠券使用后不可退回' + ] + }, + { + title: '优惠券使用规则', + icon: , + content: [ + '1. 每张优惠券只能使用一次', + '2. 优惠券有使用期限,过期作废', + '3. 满减券需达到最低消费金额', + '4. 部分优惠券仅限指定商品使用' + ] + }, + { + title: '常见问题解答', + icon: , + content: [ + 'Q: 优惠券可以叠加使用吗?', + 'A: 一般情况下不支持叠加,具体以活动规则为准', + 'Q: 优惠券过期了怎么办?', + 'A: 过期优惠券无法使用,请及时关注有效期', + 'Q: 退款时优惠券会退回吗?', + 'A: 已使用的优惠券不会退回,请谨慎使用' + ] + } + ] + + const handleNext = () => { + if (currentStep < guideSteps.length - 1) { + setCurrentStep(currentStep + 1) + } else { + onClose() + } + } + + const handlePrev = () => { + if (currentStep > 0) { + setCurrentStep(currentStep - 1) + } + } + + const handleSkip = () => { + onClose() + } + + const currentGuide = guideSteps[currentStep] + + return ( + + + {/* 头部 */} + + + {currentGuide.icon} + + + {currentGuide.title} + + + + {/* 内容 */} + + {currentGuide.content.map((item, index) => ( + + + {item} + + + ))} + + + {/* 进度指示器 */} + + {guideSteps.map((_, index) => ( + + ))} + + + {/* 底部按钮 */} + + + {currentStep > 0 && ( + + )} + + + + + + + + ) +} + +export default CouponGuide diff --git a/src/components/CouponShare.tsx b/src/components/CouponShare.tsx new file mode 100644 index 0000000..4339fe9 --- /dev/null +++ b/src/components/CouponShare.tsx @@ -0,0 +1,182 @@ +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Button, Popup } from '@nutui/nutui-react-taro' +import { Share, Wechat, QQ, Weibo, Link, Close } from '@nutui/icons-react-taro' +import Taro from '@tarojs/taro' + +export interface CouponShareProps { + /** 是否显示分享弹窗 */ + visible: boolean + /** 优惠券信息 */ + coupon: { + id: number + name: string + type: number + amount: string + minAmount?: string + description?: string + } + /** 关闭回调 */ + onClose: () => void +} + +const CouponShare: React.FC = ({ + visible, + coupon, + onClose +}) => { + // 生成分享文案 + const generateShareText = () => { + const typeText = coupon.type === 10 ? '满减券' : coupon.type === 20 ? '折扣券' : '免费券' + const amountText = coupon.type === 10 ? `¥${coupon.amount}` : + coupon.type === 20 ? `${coupon.amount}折` : '免费' + const conditionText = coupon.minAmount ? `满${coupon.minAmount}元可用` : '无门槛' + + return `🎁 ${coupon.name}\n💰 ${amountText} ${typeText}\n📋 ${conditionText}\n快来领取吧!` + } + + // 生成分享链接 + const generateShareUrl = () => { + // 这里应该是实际的分享链接,包含优惠券ID等参数 + return `https://your-domain.com/coupon/share?id=${coupon.id}` + } + + // 微信分享 + const handleWechatShare = () => { + Taro.showShareMenu({ + withShareTicket: true, + success: () => { + Taro.showToast({ + title: '分享成功', + icon: 'success' + }) + onClose() + }, + fail: () => { + Taro.showToast({ + title: '分享失败', + icon: 'error' + }) + } + }) + } + + // 复制链接 + const handleCopyLink = () => { + const shareUrl = generateShareUrl() + const shareText = generateShareText() + const fullText = `${shareText}\n\n${shareUrl}` + + Taro.setClipboardData({ + data: fullText, + success: () => { + Taro.showToast({ + title: '已复制到剪贴板', + icon: 'success' + }) + onClose() + }, + fail: () => { + Taro.showToast({ + title: '复制失败', + icon: 'error' + }) + } + }) + } + + // 保存图片分享 + const handleSaveImage = async () => { + try { + // 这里可以生成优惠券图片并保存到相册 + // 实际实现需要canvas绘制优惠券图片 + Taro.showToast({ + title: '功能开发中', + icon: 'none' + }) + } catch (error) { + Taro.showToast({ + title: '保存失败', + icon: 'error' + }) + } + } + + const shareOptions = [ + { + icon: , + label: '微信好友', + onClick: handleWechatShare + }, + { + icon: , + label: '复制链接', + onClick: handleCopyLink + }, + { + icon: , + label: '保存图片', + onClick: handleSaveImage + } + ] + + return ( + + + {/* 头部 */} + + 分享优惠券 + + + + + + {/* 优惠券预览 */} + + {coupon.name} + + + + {coupon.type === 10 ? `¥${coupon.amount}` : + coupon.type === 20 ? `${coupon.amount}折` : '免费'} + + + {coupon.minAmount ? `满${coupon.minAmount}元可用` : '无门槛使用'} + + + + + + + {/* 分享选项 */} + + {shareOptions.map((option, index) => ( + + {option.icon} + {option.label} + + ))} + + + {/* 分享文案预览 */} + + 分享文案预览: + + {generateShareText()} + + + + + ) +} + +export default CouponShare diff --git a/src/components/CouponStats.tsx b/src/components/CouponStats.tsx new file mode 100644 index 0000000..2b81af2 --- /dev/null +++ b/src/components/CouponStats.tsx @@ -0,0 +1,71 @@ +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Gift, Voucher, Clock } from '@nutui/icons-react-taro' + +export interface CouponStatsProps { + /** 可用优惠券数量 */ + availableCount: number + /** 已使用优惠券数量 */ + usedCount: number + /** 已过期优惠券数量 */ + expiredCount: number + /** 点击统计项的回调 */ + onStatsClick?: (type: 'available' | 'used' | 'expired') => void +} + +const CouponStats: React.FC = ({ + availableCount, + usedCount, + expiredCount, + onStatsClick +}) => { + const handleStatsClick = (type: 'available' | 'used' | 'expired') => { + onStatsClick?.(type) + } + + return ( + + 优惠券统计 + + + {/* 可用优惠券 */} + handleStatsClick('available')} + > + + + + {availableCount} + 可用 + + + {/* 已使用优惠券 */} + handleStatsClick('used')} + > + + + + {usedCount} + 已使用 + + + {/* 已过期优惠券 */} + handleStatsClick('expired')} + > + + + + {expiredCount} + 已过期 + + + + ) +} + +export default CouponStats diff --git a/src/components/CouponUsageRecord.tsx b/src/components/CouponUsageRecord.tsx new file mode 100644 index 0000000..90008ee --- /dev/null +++ b/src/components/CouponUsageRecord.tsx @@ -0,0 +1,162 @@ +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Tag } from '@nutui/nutui-react-taro' +import { Voucher, Clock, Agenda } from '@nutui/icons-react-taro' +import dayjs from 'dayjs' + +export interface CouponUsageRecordProps { + /** 优惠券ID */ + couponId: number + /** 优惠券名称 */ + couponName: string + /** 优惠券类型 */ + couponType: number + /** 优惠券金额 */ + couponAmount: string + /** 使用时间 */ + usedTime: string + /** 订单号 */ + orderNo?: string + /** 订单金额 */ + orderAmount?: string + /** 节省金额 */ + savedAmount?: string + /** 使用状态:1-已使用 2-已过期 */ + status: 1 | 2 + /** 点击事件 */ + onClick?: () => void +} + +const CouponUsageRecord: React.FC = ({ + couponName, + couponType, + couponAmount, + usedTime, + orderNo, + orderAmount, + savedAmount, + status, + onClick +}) => { + // 获取优惠券类型文本 + const getCouponTypeText = () => { + switch (couponType) { + case 10: return '满减券' + case 20: return '折扣券' + case 30: return '免费券' + default: return '优惠券' + } + } + + // 获取优惠券金额显示 + const getCouponAmountDisplay = () => { + switch (couponType) { + case 10: // 满减券 + return `¥${couponAmount}` + case 20: // 折扣券 + return `${couponAmount}折` + case 30: // 免费券 + return '免费' + default: + return `¥${couponAmount}` + } + } + + // 获取状态信息 + const getStatusInfo = () => { + switch (status) { + case 1: + return { + text: '已使用', + color: 'success' as const, + icon: + } + case 2: + return { + text: '已过期', + color: 'danger' as const, + icon: + } + default: + return { + text: '未知', + color: 'default' as const, + icon: + } + } + } + + const statusInfo = getStatusInfo() + + return ( + + {/* 头部信息 */} + + + + + {getCouponAmountDisplay()} + + + + {couponName} + {getCouponTypeText()} + + + + + {statusInfo.icon} + + {statusInfo.text} + + + + + {/* 使用详情 */} + + + 使用时间 + + {dayjs(usedTime).format('YYYY-MM-DD HH:mm')} + + + + {orderNo && ( + + 订单号 + {orderNo} + + )} + + {orderAmount && ( + + 订单金额 + ¥{orderAmount} + + )} + + {savedAmount && ( + + 节省金额 + ¥{savedAmount} + + )} + + + {/* 底部操作 */} + {orderNo && ( + + + + 查看订单 + + + )} + + ) +} + +export default CouponUsageRecord diff --git a/src/components/GiftCard.scss b/src/components/GiftCard.scss new file mode 100644 index 0000000..7dc3988 --- /dev/null +++ b/src/components/GiftCard.scss @@ -0,0 +1,307 @@ +.gift-card { + position: relative; + width: 100%; + margin-bottom: 16px; + border-radius: 16px; + overflow: hidden; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + background: #fff; + transition: all 0.3s ease; + + &:active { + transform: scale(0.98); + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12); + } + + &.disabled { + opacity: 0.7; + } + + // 主题色彩 + &.gift-card-gold { + .gift-card-header { + background: #ffd700; + } + .use-btn { + background: #ffd700; + border: none; + color: #333; + } + } + + &.gift-card-silver { + .gift-card-header { + background: #c0c0c0; + } + .use-btn { + background: #c0c0c0; + border: none; + color: #333; + } + } + + &.gift-card-bronze { + .gift-card-header { + background: #cd7f32; + } + .use-btn { + background: #cd7f32; + border: none; + color: #fff; + } + } + + &.gift-card-blue { + .gift-card-header { + background: #4a90e2; + } + .use-btn { + background: #4a90e2; + border: none; + color: #fff; + } + } + + &.gift-card-green { + .gift-card-header { + background: #5cb85c; + } + .use-btn { + background: #5cb85c; + border: none; + color: #fff; + } + } + + &.gift-card-purple { + .gift-card-header { + background: #9b59b6; + } + .use-btn { + background: #9b59b6; + border: none; + color: #fff; + } + } + + .gift-card-header { + display: flex; + align-items: center; + justify-content: space-between; + padding: 16px 20px; + color: #fff; + position: relative; + + .gift-card-logo { + width: 40px; + height: 40px; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + display: flex; + align-items: center; + justify-content: center; + } + + .gift-card-title { + flex: 1; + margin-left: 12px; + + .title-text { + display: block; + font-size: 18px; + font-weight: 600; + line-height: 1.3; + margin-bottom: 2px; + } + + .type-text { + display: block; + font-size: 14px; + opacity: 0.9; + } + } + + .gift-card-status { + .nut-tag { + background: rgba(255, 255, 255, 0.9); + color: #333; + border: none; + } + } + } + + .gift-card-body { + padding: 20px; + + .gift-card-content { + display: flex; + align-items: flex-start; + margin-bottom: 16px; + + .gift-image { + margin-right: 16px; + flex-shrink: 0; + } + + .gift-info { + flex: 1; + + .gift-value { + display: flex; + align-items: baseline; + margin-bottom: 8px; + + .value-label { + font-size: 14px; + color: #666; + margin-right: 8px; + } + + .value-amount { + font-size: 24px; + font-weight: bold; + color: #333; + } + } + + .gift-description { + font-size: 14px; + color: #666; + line-height: 1.4; + margin-bottom: 12px; + } + + .gift-code { + background: #f8f9fa; + border-radius: 8px; + padding: 12px; + border: 1px dashed #ddd; + + .code-label { + display: block; + font-size: 12px; + color: #999; + margin-bottom: 4px; + } + + .code-value { + font-size: 16px; + font-weight: 600; + color: #333; + font-family: 'Courier New', monospace; + letter-spacing: 1px; + } + } + } + } + + .gift-time-info { + .time-item { + display: flex; + align-items: center; + margin-bottom: 6px; + + .time-text { + font-size: 12px; + color: #666; + margin-left: 6px; + } + + &:last-child { + margin-bottom: 0; + } + } + } + } + + .gift-card-footer { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 20px 20px; + + .footer-info { + .contact-info { + display: flex; + align-items: center; + + .contact-text { + font-size: 12px; + color: #999; + margin-left: 4px; + } + } + } + + .footer-actions { + display: flex; + gap: 8px; + + .use-btn { + min-width: 80px; + font-weight: 600; + transition: all 0.2s ease; + + &:active { + transform: scale(0.95); + } + } + } + } + + .gift-card-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: center; + z-index: 10; + + .overlay-badge { + background: rgba(0, 0, 0, 0.7); + color: #fff; + padding: 8px 16px; + border-radius: 20px; + font-size: 16px; + font-weight: 600; + } + } +} + +// 礼品卡基础样式 +.gift-card { + opacity: 1; + transform: translateY(0); +} + +// 使用按钮效果 +.use-btn { + transition: all 0.3s ease; +} + +// 响应式设计 +@media (max-width: 768px) { + .gift-card { + .gift-card-body { + padding: 16px; + + .gift-card-content { + .gift-info { + .gift-value { + .value-amount { + font-size: 20px; + } + } + } + } + } + + .gift-card-footer { + padding: 0 16px 16px; + } + } +} diff --git a/src/components/GiftCard.tsx b/src/components/GiftCard.tsx new file mode 100644 index 0000000..4f36784 --- /dev/null +++ b/src/components/GiftCard.tsx @@ -0,0 +1,282 @@ +import React from 'react' +import { View, Text, Image } from '@tarojs/components' +import { Button, Tag } from '@nutui/nutui-react-taro' +import { Gift, Clock, Location, Phone } from '@nutui/icons-react-taro' +import dayjs from 'dayjs' +import './GiftCard.scss' + +export interface GiftCardProps { + /** 礼品卡ID */ + id: number + /** 礼品卡名称 */ + name: string + /** 礼品卡描述 */ + description?: string + /** 礼品卡兑换码 */ + code?: string + /** 商品图片 */ + goodsImage?: string + /** 礼品卡面值 */ + faceValue?: string + /** 礼品卡类型:10-实物礼品卡 20-虚拟礼品卡 30-服务礼品卡 */ + type?: number + /** 使用状态:0-可用 1-已使用 2-已过期 */ + useStatus?: number + /** 过期时间 */ + expireTime?: string + /** 使用时间 */ + useTime?: string + /** 使用地址 */ + useLocation?: string + /** 客服联系方式 */ + contactInfo?: string + /** 是否显示兑换码 */ + showCode?: boolean + /** 是否显示使用按钮 */ + showUseBtn?: boolean + /** 是否显示详情按钮 */ + showDetailBtn?: boolean + /** 卡片主题色 */ + theme?: 'gold' | 'silver' | 'bronze' | 'blue' | 'green' | 'purple' + /** 使用按钮点击事件 */ + onUse?: () => void + /** 详情按钮点击事件 */ + onDetail?: () => void + /** 卡片点击事件 */ + onClick?: () => void +} + +const GiftCard: React.FC = ({ + id, + name, + description, + code, + goodsImage, + faceValue, + type = 10, + useStatus = 0, + expireTime, + useTime, + useLocation, + contactInfo, + showCode = false, + showUseBtn = false, + showDetailBtn = true, + theme = 'gold', + onUse, + onDetail, + onClick +}) => { + // 获取礼品卡类型文本 + const getTypeText = () => { + switch (type) { + case 10: return '实物礼品卡' + case 20: return '虚拟礼品卡' + case 30: return '服务礼品卡' + default: return '礼品卡' + } + } + + // 获取使用状态信息 + const getStatusInfo = () => { + switch (useStatus) { + case 0: + return { + text: '可使用', + color: 'success' as const, + bgColor: 'bg-green-100', + textColor: 'text-green-600' + } + case 1: + return { + text: '已使用', + color: 'warning' as const, + bgColor: 'bg-gray-100', + textColor: 'text-gray-600' + } + case 2: + return { + text: '已过期', + color: 'danger' as const, + bgColor: 'bg-red-100', + textColor: 'text-red-600' + } + default: + return { + text: '未知', + color: 'default' as const, + bgColor: 'bg-gray-100', + textColor: 'text-gray-600' + } + } + } + + // 获取主题样式类名 + const getThemeClass = () => { + return `gift-card-${theme}` + } + + // 格式化过期时间显示 + const formatExpireTime = () => { + if (!expireTime) return '' + + const expire = dayjs(expireTime) + const now = dayjs() + const diffDays = expire.diff(now, 'day') + + if (diffDays < 0) { + return '已过期' + } else if (diffDays === 0) { + return '今天过期' + } else if (diffDays <= 7) { + return `${diffDays}天后过期` + } else { + return expire.format('YYYY-MM-DD 过期') + } + } + + // 格式化兑换码显示 + const formatCode = () => { + if (!code) return '' + if (!showCode) return code.replace(/(.{4})/g, '$1 ').trim() + return code.replace(/(.{4})/g, '$1 ').trim() + } + + const statusInfo = getStatusInfo() + + return ( + + {/* 卡片头部 */} + + + + + + {name} + {getTypeText()} + + + {statusInfo.text} + + + + {/* 卡片主体 */} + + + {/* 商品图片 */} + {goodsImage && ( + + + + )} + + + {/* 面值 */} + {faceValue && ( + + 面值 + ¥{faceValue} + + )} + + {/* 描述 */} + {description && ( + {description} + )} + + {/* 兑换码 */} + {code && ( + + 兑换码 + {formatCode()} + + )} + + + + {/* 时间信息 */} + + {useStatus === 1 && useTime && ( + + + 使用时间:{dayjs(useTime).format('YYYY-MM-DD HH:mm')} + + )} + + {useStatus === 0 && expireTime && ( + + + {formatExpireTime()} + + )} + + {useLocation && ( + + + 使用地址:{useLocation} + + )} + + + + {/* 卡片底部操作 */} + + + {contactInfo && ( + + + {contactInfo} + + )} + + + + {showDetailBtn && ( + + )} + + {showUseBtn && useStatus === 0 && ( + + )} + + + + {/* 状态遮罩 */} + {useStatus !== 0 && ( + + + {statusInfo.text} + + + )} + + ) +} + +export default GiftCard diff --git a/src/components/GiftCardGuide.tsx b/src/components/GiftCardGuide.tsx new file mode 100644 index 0000000..6b1db57 --- /dev/null +++ b/src/components/GiftCardGuide.tsx @@ -0,0 +1,169 @@ +import React, { useState } from 'react' +import { View, Text } from '@tarojs/components' +import { Button, Popup } from '@nutui/nutui-react-taro' +import { Gift, QrCode, Voucher, Service } from '@nutui/icons-react-taro' + +export interface GiftCardGuideProps { + /** 是否显示指南 */ + visible: boolean + /** 关闭回调 */ + onClose: () => void +} + +const GiftCardGuide: React.FC = ({ + visible, + onClose +}) => { + const [currentStep, setCurrentStep] = useState(0) + + const guideSteps = [ + { + title: '如何获取礼品卡?', + icon: , + content: [ + '1. 通过兑换码兑换礼品卡', + '2. 扫描二维码快速兑换', + '3. 参与活动获得礼品卡奖励', + '4. 朋友赠送的礼品卡' + ] + }, + { + title: '如何兑换礼品卡?', + icon: , + content: [ + '1. 点击"兑换"按钮进入兑换页面', + '2. 输入礼品卡兑换码或扫码输入', + '3. 验证兑换码有效性', + '4. 确认兑换,礼品卡添加到账户' + ] + }, + { + title: '如何使用礼品卡?', + icon: , + content: [ + '1. 选择可用状态的礼品卡', + '2. 点击"立即使用"按钮', + '3. 填写使用信息(地址、备注等)', + '4. 确认使用,完成礼品卡消费' + ] + }, + { + title: '礼品卡类型说明', + icon: , + content: [ + '🎁 实物礼品卡:需到指定地址领取商品', + '💻 虚拟礼品卡:自动发放到账户余额', + '🛎️ 服务礼品卡:联系客服预约服务', + '⏰ 注意查看有效期,过期无法使用' + ] + }, + { + title: '常见问题解答', + icon: , + content: [ + 'Q: 礼品卡可以转赠他人吗?', + 'A: 未使用的礼品卡可以通过分享功能转赠', + 'Q: 礼品卡过期了怎么办?', + 'A: 过期礼品卡无法使用,请及时关注有效期', + 'Q: 使用礼品卡后可以退款吗?', + 'A: 已使用的礼品卡不支持退款操作' + ] + } + ] + + const handleNext = () => { + if (currentStep < guideSteps.length - 1) { + setCurrentStep(currentStep + 1) + } else { + onClose() + } + } + + const handlePrev = () => { + if (currentStep > 0) { + setCurrentStep(currentStep - 1) + } + } + + const handleSkip = () => { + onClose() + } + + const currentGuide = guideSteps[currentStep] + + return ( + + + {/* 头部 */} + + + {currentGuide.icon} + + + {currentGuide.title} + + + + {/* 内容 */} + + {currentGuide.content.map((item, index) => ( + + + {item} + + + ))} + + + {/* 进度指示器 */} + + {guideSteps.map((_, index) => ( + + ))} + + + {/* 底部按钮 */} + + + {currentStep > 0 && ( + + )} + + + + + + + + ) +} + +export default GiftCardGuide diff --git a/src/components/GiftCardList.tsx b/src/components/GiftCardList.tsx new file mode 100644 index 0000000..48a9c6e --- /dev/null +++ b/src/components/GiftCardList.tsx @@ -0,0 +1,173 @@ +import React from 'react' +import { View, ScrollView } from '@tarojs/components' +import GiftCard, { GiftCardProps } from './GiftCard' + +export interface GiftCardListProps { + /** 礼品卡列表数据 */ + gifts: GiftCardProps[] + /** 列表标题 */ + title?: string + /** 布局方式:vertical-垂直布局 horizontal-水平滚动 grid-网格布局 */ + layout?: 'vertical' | 'horizontal' | 'grid' + /** 网格列数(仅在grid布局时有效) */ + columns?: number + /** 是否显示空状态 */ + showEmpty?: boolean + /** 空状态文案 */ + emptyText?: string + /** 礼品卡点击事件 */ + onGiftClick?: (gift: GiftCardProps, index: number) => void + /** 礼品卡使用事件 */ + onGiftUse?: (gift: GiftCardProps, index: number) => void + /** 礼品卡详情事件 */ + onGiftDetail?: (gift: GiftCardProps, index: number) => void +} + +const GiftCardList: React.FC = ({ + gifts = [], + title, + layout = 'vertical', + columns = 2, + showEmpty = true, + emptyText = '暂无礼品卡', + onGiftClick, + onGiftUse, + onGiftDetail +}) => { + const handleGiftClick = (gift: GiftCardProps, index: number) => { + onGiftClick?.(gift, index) + } + + const handleGiftUse = (gift: GiftCardProps, index: number) => { + onGiftUse?.(gift, index) + } + + const handleGiftDetail = (gift: GiftCardProps, index: number) => { + onGiftDetail?.(gift, index) + } + + // 垂直布局 + if (layout === 'vertical') { + return ( + + {title && ( + {title} + )} + + {gifts.length === 0 ? ( + showEmpty && ( + + + + 🎁 + + + {emptyText} + + ) + ) : ( + gifts.map((gift, index) => ( + handleGiftClick(gift, index)} + onUse={() => handleGiftUse(gift, index)} + onDetail={() => handleGiftDetail(gift, index)} + /> + )) + )} + + ) + } + + // 网格布局 + if (layout === 'grid') { + return ( + + {title && ( + {title} + )} + + {gifts.length === 0 ? ( + showEmpty && ( + + + + 🎁 + + + {emptyText} + + ) + ) : ( + + {gifts.map((gift, index) => ( + + handleGiftClick(gift, index)} + onUse={() => handleGiftUse(gift, index)} + onDetail={() => handleGiftDetail(gift, index)} + /> + + ))} + + )} + + ) + } + + // 水平滚动布局 + return ( + + {title && ( + + {title} + + )} + + {gifts.length === 0 ? ( + showEmpty && ( + + + + 🎁 + + + {emptyText} + + ) + ) : ( + + {gifts.map((gift, index) => ( + + handleGiftClick(gift, index)} + onUse={() => handleGiftUse(gift, index)} + onDetail={() => handleGiftDetail(gift, index)} + /> + + ))} + + )} + + ) +} + +export default GiftCardList diff --git a/src/components/GiftCardShare.tsx b/src/components/GiftCardShare.tsx new file mode 100644 index 0000000..c445123 --- /dev/null +++ b/src/components/GiftCardShare.tsx @@ -0,0 +1,227 @@ +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Button, Popup } from '@nutui/nutui-react-taro' +import { Share, Wechat, QQ, Weibo, Link, Close, Gift } from '@nutui/icons-react-taro' +import Taro from '@tarojs/taro' + +export interface GiftCardShareProps { + /** 是否显示分享弹窗 */ + visible: boolean + /** 礼品卡信息 */ + giftCard: { + id: number + name: string + type: number + faceValue: string + code?: string + description?: string + } + /** 关闭回调 */ + onClose: () => void +} + +const GiftCardShare: React.FC = ({ + visible, + giftCard, + onClose +}) => { + // 获取礼品卡类型文本 + const getTypeText = () => { + switch (giftCard.type) { + case 10: return '实物礼品卡' + case 20: return '虚拟礼品卡' + case 30: return '服务礼品卡' + default: return '礼品卡' + } + } + + // 生成分享文案 + const generateShareText = () => { + const typeText = getTypeText() + const valueText = `¥${giftCard.faceValue}` + + return `🎁 ${giftCard.name}\n💰 面值 ${valueText}\n🏷️ ${typeText}\n${giftCard.description ? `📝 ${giftCard.description}\n` : ''}快来领取这份礼品卡吧!` + } + + // 生成分享链接 + const generateShareUrl = () => { + // 这里应该是实际的分享链接,包含礼品卡ID等参数 + return `https://your-domain.com/gift/share?id=${giftCard.id}` + } + + // 微信分享 + const handleWechatShare = () => { + Taro.showShareMenu({ + withShareTicket: true, + success: () => { + Taro.showToast({ + title: '分享成功', + icon: 'success' + }) + onClose() + }, + fail: () => { + Taro.showToast({ + title: '分享失败', + icon: 'error' + }) + } + }) + } + + // 复制链接 + const handleCopyLink = () => { + const shareUrl = generateShareUrl() + const shareText = generateShareText() + const fullText = `${shareText}\n\n${shareUrl}` + + Taro.setClipboardData({ + data: fullText, + success: () => { + Taro.showToast({ + title: '已复制到剪贴板', + icon: 'success' + }) + onClose() + }, + fail: () => { + Taro.showToast({ + title: '复制失败', + icon: 'error' + }) + } + }) + } + + // 复制兑换码 + const handleCopyCode = () => { + if (!giftCard.code) { + Taro.showToast({ + title: '暂无兑换码', + icon: 'none' + }) + return + } + + Taro.setClipboardData({ + data: giftCard.code, + success: () => { + Taro.showToast({ + title: '兑换码已复制', + icon: 'success' + }) + onClose() + }, + fail: () => { + Taro.showToast({ + title: '复制失败', + icon: 'error' + }) + } + }) + } + + // 保存图片分享 + const handleSaveImage = async () => { + try { + // 这里可以生成礼品卡图片并保存到相册 + // 实际实现需要canvas绘制礼品卡图片 + Taro.showToast({ + title: '功能开发中', + icon: 'none' + }) + } catch (error) { + Taro.showToast({ + title: '保存失败', + icon: 'error' + }) + } + } + + const shareOptions = [ + { + icon: , + label: '微信好友', + onClick: handleWechatShare + }, + { + icon: , + label: '复制链接', + onClick: handleCopyLink + }, + { + icon: , + label: '复制兑换码', + onClick: handleCopyCode, + disabled: !giftCard.code + }, + { + icon: , + label: '保存图片', + onClick: handleSaveImage + } + ] + + return ( + + + {/* 头部 */} + + 分享礼品卡 + + + + + + {/* 礼品卡预览 */} + + {giftCard.name} + + + ¥{giftCard.faceValue} + {getTypeText()} + + + + {giftCard.code && ( + + 兑换码 + {giftCard.code} + + )} + + + {/* 分享选项 */} + + {shareOptions.map((option, index) => ( + + {option.icon} + {option.label} + + ))} + + + {/* 分享文案预览 */} + + 分享文案预览: + + {generateShareText()} + + + + + ) +} + +export default GiftCardShare diff --git a/src/components/GiftCardStats.tsx b/src/components/GiftCardStats.tsx new file mode 100644 index 0000000..fac3bd4 --- /dev/null +++ b/src/components/GiftCardStats.tsx @@ -0,0 +1,72 @@ +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Gift, Voucher, Clock } from '@nutui/icons-react-taro' + +export interface GiftCardStatsProps { + /** 可用礼品卡数量 */ + availableCount: number + /** 已使用礼品卡数量 */ + usedCount: number + /** 已过期礼品卡数量 */ + expiredCount: number + /** 礼品卡总价值 */ + totalValue?: number + /** 点击统计项的回调 */ + onStatsClick?: (type: 'available' | 'used' | 'expired' | 'total') => void +} + +const GiftCardStats: React.FC = ({ + availableCount, + usedCount, + expiredCount, + onStatsClick +}) => { + const handleStatsClick = (type: 'available' | 'used' | 'expired' | 'total') => { + onStatsClick?.(type) + } + + return ( + + {/* 紧凑的统计卡片 - 2x2 网格 */} + + {/* 可用礼品卡 */} + handleStatsClick('available')} + > + + + 可用 + + {availableCount} + + + {/* 已使用礼品卡 */} + handleStatsClick('used')} + > + + + 已使用 + + {usedCount} + + + {/* 已过期礼品卡 */} + handleStatsClick('expired')} + > + + + 已过期 + + {expiredCount} + + + + ) +} + +export default GiftCardStats diff --git a/src/components/GiftCardStatsMax.tsx b/src/components/GiftCardStatsMax.tsx new file mode 100644 index 0000000..275e917 --- /dev/null +++ b/src/components/GiftCardStatsMax.tsx @@ -0,0 +1,87 @@ +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Gift, Voucher, Clock, Star } from '@nutui/icons-react-taro' + +export interface GiftCardStatsProps { + /** 可用礼品卡数量 */ + availableCount: number + /** 已使用礼品卡数量 */ + usedCount: number + /** 已过期礼品卡数量 */ + expiredCount: number + /** 礼品卡总价值 */ + totalValue?: number + /** 点击统计项的回调 */ + onStatsClick?: (type: 'available' | 'used' | 'expired' | 'total') => void +} + +const GiftCardStats: React.FC = ({ + availableCount, + usedCount, + expiredCount, + totalValue, + onStatsClick +}) => { + const handleStatsClick = (type: 'available' | 'used' | 'expired' | 'total') => { + onStatsClick?.(type) + } + + return ( + + {/* 紧凑的统计卡片 - 2x2 网格 */} + + {/* 可用礼品卡 */} + handleStatsClick('available')} + > + + + 可用 + + {availableCount} + + + {/* 已使用礼品卡 */} + handleStatsClick('used')} + > + + + 已使用 + + {usedCount} + + + {/* 已过期礼品卡 */} + handleStatsClick('expired')} + > + + + 已过期 + + {expiredCount} + + + {/* 总价值 */} + {totalValue !== undefined && ( + handleStatsClick('total')} + > + + + 总价值 + + ¥{totalValue} + + )} + + + ) +} + +export default GiftCardStats diff --git a/src/dealer/components/EarningsCard.tsx b/src/dealer/components/EarningsCard.tsx deleted file mode 100644 index d6305dd..0000000 --- a/src/dealer/components/EarningsCard.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import { View, Button } from '@tarojs/components' -import { useState, useEffect } from 'react' - -interface EarningsCardProps { - availableAmount?: number - pendingAmount?: number - onWithdraw?: () => void -} - -function EarningsCard({ - availableAmount = 0.00, - pendingAmount = 0.00, - onWithdraw -}: EarningsCardProps) { - - const handleWithdraw = () => { - if (onWithdraw) { - onWithdraw() - } - } - - return ( - - - {/* 装饰性背景元素 */} - - - - - {/* 可提现金额 */} - - - 可提现 {availableAmount.toFixed(2)} 元 - - - 待提现 {pendingAmount.toFixed(2)} 元 - - - - {/* 提现按钮 */} - - - - - - - ) -} - -export default EarningsCard diff --git a/src/dealer/components/FunctionMenu.tsx b/src/dealer/components/FunctionMenu.tsx deleted file mode 100644 index ba12f5b..0000000 --- a/src/dealer/components/FunctionMenu.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import { View } from '@tarojs/components' -import Taro from '@tarojs/taro' - -interface MenuItem { - id: string - title: string - icon: string - color: string - bgColor: string - onClick?: () => void -} - -function FunctionMenu() { - const menuItems: MenuItem[] = [ - { - id: 'withdraw-detail', - title: '提现明细', - icon: '💰', - color: '#F59E0B', - bgColor: '#FEF3C7', - onClick: () => { - Taro.navigateTo({ - url: '/dealer/withdraw/index' - }) - } - }, - { - id: 'distribution-orders', - title: '分销订单', - icon: '📋', - color: '#EF4444', - bgColor: '#FEE2E2', - onClick: () => { - Taro.navigateTo({ - url: '/dealer/orders/index' - }) - } - }, - { - id: 'my-team', - title: '我的团队', - icon: '👥', - color: '#10B981', - bgColor: '#D1FAE5', - onClick: () => { - Taro.navigateTo({ - url: '/dealer/team/index' - }) - } - }, - { - id: 'promotion-qr', - title: '推广二维码', - icon: '📱', - color: '#3B82F6', - bgColor: '#DBEAFE', - onClick: () => { - Taro.navigateTo({ - url: '/dealer/qrcode/index' - }) - } - } - ] - - const handleMenuClick = (item: MenuItem) => { - if (item.onClick) { - item.onClick() - } - } - - return ( - - - - {menuItems.map((item) => ( - handleMenuClick(item)} - > - - {item.icon} - - - {item.title} - - - ))} - - - - ) -} - -export default FunctionMenu diff --git a/src/dealer/components/NavigationBar.tsx b/src/dealer/components/NavigationBar.tsx deleted file mode 100644 index e1b1acb..0000000 --- a/src/dealer/components/NavigationBar.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { View } from '@tarojs/components' -import Taro from '@tarojs/taro' - -interface NavigationBarProps { - title?: string - showBack?: boolean - showMore?: boolean - onBack?: () => void - onMore?: () => void -} - -function NavigationBar({ - title = '分销中心', - showBack = true, - showMore = true, - onBack, - onMore -}: NavigationBarProps) { - - const handleBack = () => { - if (onBack) { - onBack() - } else { - Taro.navigateBack() - } - } - - const handleMore = () => { - if (onMore) { - onMore() - } else { - Taro.showActionSheet({ - itemList: ['分享', '设置', '帮助'] - }) - } - } - - return ( - - {/* 状态栏占位 */} - - - {/* 导航栏 */} - - {/* 左侧返回按钮 */} - - {showBack && ( - - - - )} - - - {/* 中间标题 */} - - {title} - - - {/* 右侧更多按钮 */} - - {showMore && ( - - - - )} - - - - ) -} - -export default NavigationBar diff --git a/src/dealer/components/OrderIcon.tsx b/src/dealer/components/OrderIcon.tsx deleted file mode 100644 index 9cc9a61..0000000 --- a/src/dealer/components/OrderIcon.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import {useEffect, useState} from 'react' -import {navigateTo} from '@tarojs/taro' -import Taro from '@tarojs/taro' -import {Button} from '@tarojs/components'; -import {Image} from '@nutui/nutui-react-taro' -import {getUserInfo, getWxOpenId} from "@/api/layout"; -import {TenantId} from "@/config/app"; -import {User} from "@/api/system/user/model"; -// import News from "./News"; -import {myPageBszxBm} from "@/api/bszx/bszxBm"; -import {listCmsNavigation} from "@/api/cms/cmsNavigation"; - -const OrderIcon = () => { - - const [loading, setLoading] = useState(true) - const [isLogin, setIsLogin] = useState(false) - const [userInfo, setUserInfo] = useState() - const [bmLogs, setBmLogs] = useState() - const [navItems, setNavItems] = useState([]) - - /* 获取用户手机号 */ - const handleGetPhoneNumber = ({detail}) => { - const {code, encryptedData, iv} = detail - Taro.login({ - success: function () { - if (code) { - Taro.request({ - url: 'https://server.websoft.top/api/wx-login/loginByMpWxPhone', - method: 'POST', - data: { - code, - encryptedData, - iv, - notVerifyPhone: true, - refereeId: 0, - sceneType: 'save_referee', - tenantId: TenantId - }, - header: { - 'content-type': 'application/json', - TenantId - }, - success: function (res) { - Taro.setStorageSync('access_token', res.data.data.access_token) - Taro.setStorageSync('UserId', res.data.data.user.userId) - setUserInfo(res.data.data.user) - Taro.setStorageSync('Phone', res.data.data.user.phone) - setIsLogin(true) - Taro.showToast({ - title: '登录成功', - icon: 'success' - }); - } - }) - } else { - console.log('登录失败!') - } - } - }) - } - - const onLogin = (item: any, index: number) => { - if(!isLogin){ - return navigateTo({url: `/pages/category/category?id=${item.navigationId}`}) - }else { - // 报名链接 - if(index == 0){ - console.log(bmLogs,'bmLogs') - if(bmLogs && bmLogs.length > 0){ - return navigateTo({url: `/bszx/bm-cert/bm-cert?id=${bmLogs[0].id}`}) - }else { - navigateTo({url: `/user/profile/profile`}) - } - } - // 善款明细 - if(item.navigationId == 4119){ - return navigateTo({url: `/bszx/pay-record/pay-record`}) - } - return navigateTo({url: `/pages/category/category?id=${item.navigationId}`}) - } - } - - const reload = () => { - // 读取栏目 - listCmsNavigation({parentId: 2828,hide: 0}).then(res => { - console.log(res,'9999') - setNavItems(res); - }) - 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); - console.log(userInfo, 'userInfo...') - Taro.setStorageSync('UserId', data.userId) - // 获取openId - if (!data.openid) { - Taro.login({ - success: (res) => { - getWxOpenId({code: res.code}).then(() => { - }) - } - }) - } - } - }).catch(() => { - console.log('未登录') - }); - } - }); - // 报名日志 - myPageBszxBm({limit: 1}).then(res => { - if (res.list) { - setBmLogs(res.list); - } - }) - setLoading(false); - }; - - 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' - }); - } - } - }); - }; - - useEffect(() => { - Taro.getSetting({ - success: (res) => { - if (res.authSetting['scope.userInfo']) { - // 用户已经授权过,可以直接获取用户信息 - console.log('用户已经授权过,可以直接获取用户信息') - reload(); - } else { - // 用户未授权,需要弹出授权窗口 - console.log('用户未授权,需要弹出授权窗口') - showAuthModal(); - } - } - }); - reload(); - }, []) - - return ( -
-
-
- { - navItems.map((item, index) => ( -
- { - isLogin && !loading ? -
{ - onLogin(item, index) - }}> - -
{item?.title}
-
- : - - } -
- )) - } -
-
- {/*倡议书*/} - {/**/} -
- ) -} -export default OrderIcon diff --git a/src/dealer/components/UserCard.tsx b/src/dealer/components/UserCard.tsx deleted file mode 100644 index 0866e91..0000000 --- a/src/dealer/components/UserCard.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import {Avatar} from '@nutui/nutui-react-taro' -import {getUserInfo, getWxOpenId} from '@/api/layout'; -import Taro from '@tarojs/taro'; -import {useEffect, useState} from "react"; -import {User} from "@/api/system/user/model"; -import {View} from '@tarojs/components' -function UserCard() { - const [IsLogin, setIsLogin] = useState(false) - const [userInfo, setUserInfo] = useState() - const [referrerName, setReferrerName] = useState('平台') - - - 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(() => { - }) - } - }) - } - // 获取推荐人信息 - const referrer = data.nickname || '平台'; - setReferrerName(referrer) - } - }).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' - }); - } - } - }); - }; - - return ( - - - - - - - {IsLogin ? (userInfo?.nickname || '小程序用户') : '小程序.App.网站.系统开发-邓'} - - - 推荐人:{referrerName} - - - - - - - - 已提现金额 - - - 0.00元 - - - - - - ) -} - -export default UserCard; diff --git a/src/dealer/components/UserCell.tsx b/src/dealer/components/UserCell.tsx deleted file mode 100644 index 5701c42..0000000 --- a/src/dealer/components/UserCell.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import {Cell} from '@nutui/nutui-react-taro' -import navTo from "@/utils/common"; -import Taro from '@tarojs/taro' -import {ArrowRight, ShieldCheck, LogisticsError, Location, Reward, Tips, Ask} from '@nutui/icons-react-taro' - -const UserCell = () => { - - const onLogout = () => { - Taro.showModal({ - title: '提示', - content: '确定要退出登录吗?', - success: function (res) { - if (res.confirm) { - Taro.removeStorageSync('access_token') - Taro.removeStorageSync('TenantId') - Taro.removeStorageSync('UserId') - Taro.removeStorageSync('userInfo') - Taro.reLaunch({ - url: '/pages/index/index' - }) - } - } - }) - } - - return ( - <> -
- - -
- 开通会员 -
- 享优惠 -
- } - extra={} - /> - - 我的服务 - - }> - - - 我的钱包 - - } - align="center" - extra={} - onClick={() => { - navTo('/user/wallet/index', true) - }} - /> - - - 收货地址 - - } - align="center" - extra={} - onClick={() => { - navTo('/user/address/index', true) - }} - /> - - - 实名认证 - - } - align="center" - extra={} - onClick={() => { - navTo('/user/userVerify/index', true) - }} - /> - - - 常见问题 - - } - align="center" - extra={} - onClick={() => { - navTo('/user/help/index') - }} - /> - - - 关于我们 - - } - align="center" - extra={} - onClick={() => { - navTo('/user/about/index') - }} - /> - - - 账号管理 - - }> - } - onClick={() => navTo('/user/profile/profile', true)} - /> - } - onClick={onLogout} - /> - - - - ) -} -export default UserCell diff --git a/src/dealer/components/UserFooter.tsx b/src/dealer/components/UserFooter.tsx deleted file mode 100644 index fb74b71..0000000 --- a/src/dealer/components/UserFooter.tsx +++ /dev/null @@ -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(0) - const [FormData, setFormData] = useState( - { - 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 ( - <> -
-
当前版本:{Version}
-
Copyright © { new Date().getFullYear() } {Copyright}
-
- - -
submitByPhone(values)} - footer={ -
- -
- } - > - - - - - - -
-
- - ) -} -export default UserFooter diff --git a/src/dealer/index.scss b/src/dealer/index.scss deleted file mode 100644 index 602c01f..0000000 --- a/src/dealer/index.scss +++ /dev/null @@ -1,77 +0,0 @@ -// 分销中心页面样式 -.dealer-page { - min-height: 100vh; - background: linear-gradient(180deg, #60A5FA 0%, #3B82F6 50%, #1D4ED8 100%); -} - -// 导航栏样式 -.navigation-bar { - position: relative; - z-index: 100; -} - -// 用户卡片样式 -.user-card { - background: white; - border-radius: 16px; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); - margin: 16px; - padding: 16px; -} - -// 收益卡片样式 -.earnings-card { - background: linear-gradient(135deg, #8B5CF6 0%, #A855F7 50%, #C084FC 100%); - border-radius: 16px; - margin: 16px; - padding: 16px; - position: relative; - overflow: hidden; -} - -// 功能菜单样式 -.function-menu { - background: white; - border-radius: 16px; - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); - margin: 16px; - padding: 16px; - - .menu-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 16px; - - .menu-item { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 24px 16px; - border-radius: 12px; - cursor: pointer; - transition: all 0.2s ease; - - &:active { - transform: scale(0.98); - } - - .menu-icon { - width: 48px; - height: 48px; - border-radius: 50%; - background: white; - display: flex; - align-items: center; - justify-content: center; - font-size: 24px; - margin-bottom: 8px; - } - - .menu-text { - font-size: 14px; - font-weight: 500; - } - } - } -} diff --git a/src/dealer/index.tsx b/src/dealer/index.tsx index 8ec3f74..a2cd75a 100644 --- a/src/dealer/index.tsx +++ b/src/dealer/index.tsx @@ -1,64 +1,37 @@ -import {useEffect} from 'react' -import { View } from '@tarojs/components' +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Cell, Button } from '@nutui/nutui-react-taro' import Taro from '@tarojs/taro' -import NavigationBar from "./components/NavigationBar" -import UserCard from "./components/UserCard" -import EarningsCard from "./components/EarningsCard" -import FunctionMenu from "./components/FunctionMenu" -import './index.scss' -function Index() { - - useEffect(() => { - // 设置页面标题 - Taro.setNavigationBarTitle({ - title: '分销中心' - }) - }, []); - - const handleWithdraw = () => { - Taro.showModal({ - title: '提现', - content: '确定要进行提现操作吗?', - success: (res) => { - if (res.confirm) { - Taro.showToast({ - title: '提现申请已提交', - icon: 'success' - }) - } - } - }) - } - - const handleBack = () => { - Taro.navigateBack({ - delta: 1 - }) - } - - const handleMore = () => { - Taro.showActionSheet({ - itemList: ['分享给朋友', '客服咨询', '使用帮助'], - success: (res) => { - const actions = ['分享给朋友', '客服咨询', '使用帮助'] - Taro.showToast({ - title: actions[res.tapIndex], - icon: 'none' - }) - } - }) - } +const DealerIndex: React.FC = () => { return ( - - - - + + 分销中心 + + + Taro.navigateTo({url: '/dealer/team/index'})}>查看} + /> + Taro.navigateTo({url: '/dealer/orders/index'})}>查看} + /> + Taro.navigateTo({url: '/dealer/withdraw/index'})}>提现} + /> + Taro.navigateTo({url: '/dealer/qrcode/index'})}>生成} + /> + ) } -export default Index +export default DealerIndex diff --git a/src/dealer/orders/index.config.ts b/src/dealer/orders/index.config.ts new file mode 100644 index 0000000..3bb5694 --- /dev/null +++ b/src/dealer/orders/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '分销订单' +}) diff --git a/src/dealer/orders/index.scss b/src/dealer/orders/index.scss deleted file mode 100644 index 001d9b5..0000000 --- a/src/dealer/orders/index.scss +++ /dev/null @@ -1,145 +0,0 @@ -.distribution-orders-page { - min-height: 100vh; - background-color: #f5f5f5; - - .loading-container { - display: flex; - justify-content: center; - align-items: center; - height: 200px; - } - - .stats-card { - background: white; - margin: 16px; - border-radius: 12px; - padding: 20px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - - .stats-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 20px; - - .stat-item { - text-align: center; - - .stat-value { - font-size: 20px; // 对应 text-xl - font-weight: bold; - color: #1f2937; - margin-bottom: 4px; - } - - .stat-label { - font-size: 16px; // 对应 text-base - color: #6b7280; - } - } - } - } - - .orders-container { - margin: 0 16px; - - .empty-container { - display: flex; - justify-content: center; - align-items: center; - height: 300px; - } - - .orders-list { - margin-top: 16px; - - .order-item { - background: white; - border-radius: 12px; - padding: 16px; - margin-bottom: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - cursor: pointer; - transition: all 0.2s ease; - - &:active { - transform: scale(0.98); - background-color: #f9f9f9; - } - - .order-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; - - .order-no { - font-size: 14px; // 对应 text-sm - color: #6b7280; - } - - .order-status { - font-size: 14px; // 对应 text-sm - font-weight: 500; - padding: 4px 12px; - border-radius: 20px; - background-color: rgba(0, 0, 0, 0.05); - } - } - - .order-content { - display: flex; - margin-bottom: 12px; - - .product-image { - width: 60px; - height: 60px; - border-radius: 8px; - margin-right: 12px; - background-color: #f3f4f6; - } - - .order-info { - flex: 1; - - .product-name { - font-size: 20px; // 对应 text-xl,主要标题 - font-weight: 500; - color: #1f2937; - margin-bottom: 6px; - line-height: 1.4; - } - - .buyer-info { - font-size: 16px; // 对应 text-base - color: #6b7280; - margin-bottom: 8px; - } - - .amount-info { - display: flex; - justify-content: space-between; - align-items: center; - - .order-amount { - font-size: 16px; // 对应 text-base - color: #6b7280; - } - - .commission { - font-size: 20px; // 对应 text-xl,重要数字 - font-weight: bold; - color: #ef4444; - } - } - } - } - - .order-time { - font-size: 14px; // 对应 text-sm - color: #9ca3af; - text-align: right; - } - } - } - } -} diff --git a/src/dealer/orders/index.tsx b/src/dealer/orders/index.tsx index c4f6e92..960a1f8 100644 --- a/src/dealer/orders/index.tsx +++ b/src/dealer/orders/index.tsx @@ -1,238 +1,15 @@ -import { useState, useEffect } from 'react' -import { View, Image } from '@tarojs/components' -import Taro from '@tarojs/taro' -import { Empty, Tabs, TabPane } from '@nutui/nutui-react-taro' -import './index.scss' - -interface DistributionOrder { - id: string - orderNo: string - productName: string - productImage: string - buyerName: string - orderAmount: number - commission: number - commissionRate: number - status: 'pending' | 'confirmed' | 'settled' - statusText: string - createTime: string - settleTime?: string -} - -function DistributionOrders() { - const [activeTab, setActiveTab] = useState('0') - const [orders, setOrders] = useState([]) - const [loading, setLoading] = useState(true) - const [stats, setStats] = useState({ - totalCommission: 0, - pendingCommission: 0, - settledCommission: 0, - totalOrders: 0 - }) - - useEffect(() => { - Taro.setNavigationBarTitle({ - title: '分销订单' - }) - loadOrders() - }, []) - - const loadOrders = async () => { - try { - setLoading(true) - // 模拟数据 - const mockData: DistributionOrder[] = [ - { - id: '1', - orderNo: 'DD202401150001', - productName: '有机蔬菜礼盒装', - productImage: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', - buyerName: '张***', - orderAmount: 299.00, - commission: 29.90, - commissionRate: 10, - status: 'settled', - statusText: '已结算', - createTime: '2024-01-15 14:30:00', - settleTime: '2024-01-16 10:00:00' - }, - { - id: '2', - orderNo: 'DD202401140002', - productName: '新鲜水果组合', - productImage: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', - buyerName: '李***', - orderAmount: 158.00, - commission: 15.80, - commissionRate: 10, - status: 'confirmed', - statusText: '已确认', - createTime: '2024-01-14 09:20:00' - }, - { - id: '3', - orderNo: 'DD202401130003', - productName: '农家土鸡蛋', - productImage: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', - buyerName: '王***', - orderAmount: 88.00, - commission: 8.80, - commissionRate: 10, - status: 'pending', - statusText: '待确认', - createTime: '2024-01-13 16:45:00' - } - ] - - // 计算统计数据 - const totalCommission = mockData.reduce((sum, order) => sum + order.commission, 0) - const pendingCommission = mockData.filter(o => o.status === 'pending').reduce((sum, order) => sum + order.commission, 0) - const settledCommission = mockData.filter(o => o.status === 'settled').reduce((sum, order) => sum + order.commission, 0) - - setTimeout(() => { - setOrders(mockData) - setStats({ - totalCommission, - pendingCommission, - settledCommission, - totalOrders: mockData.length - }) - setLoading(false) - }, 1000) - } catch (error) { - console.error('加载分销订单失败:', error) - setLoading(false) - } - } - - const getStatusColor = (status: string) => { - switch (status) { - case 'settled': - return '#10B981' - case 'confirmed': - return '#3B82F6' - case 'pending': - return '#F59E0B' - default: - return '#6B7280' - } - } - - const getFilteredOrders = () => { - switch (activeTab) { - case '1': - return orders.filter(order => order.status === 'pending') - case '2': - return orders.filter(order => order.status === 'confirmed') - case '3': - return orders.filter(order => order.status === 'settled') - default: - return orders - } - } - - const handleOrderClick = (order: DistributionOrder) => { - Taro.showModal({ - title: '订单详情', - content: ` -订单号:${order.orderNo} -商品:${order.productName} -购买人:${order.buyerName} -订单金额:¥${order.orderAmount.toFixed(2)} -佣金比例:${order.commissionRate}% -佣金金额:¥${order.commission.toFixed(2)} -下单时间:${order.createTime} -${order.settleTime ? `结算时间:${order.settleTime}` : ''} - `.trim(), - showCancel: false - }) - } - - if (loading) { - return ( - - - 加载中... - - - ) - } +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Cell, Empty } from '@nutui/nutui-react-taro' +const DealerOrders: React.FC = () => { return ( - - {/* 统计卡片 */} - - - - ¥{stats.totalCommission.toFixed(2)} - 累计佣金 - - - ¥{stats.settledCommission.toFixed(2)} - 已结算 - - - ¥{stats.pendingCommission.toFixed(2)} - 待结算 - - - {stats.totalOrders} - 订单数 - - - - - {/* 订单列表 */} - - setActiveTab(value)}> - - - - - - - - {getFilteredOrders().length === 0 ? ( - - - - ) : ( - getFilteredOrders().map((order) => ( - handleOrderClick(order)} - > - - 订单号:{order.orderNo} - - {order.statusText} - - - - - - - {order.productName} - 购买人:{order.buyerName} - - 订单金额:¥{order.orderAmount.toFixed(2)} - 佣金:¥{order.commission.toFixed(2)} - - - - - 下单时间:{order.createTime} - - )) - )} - - + + 分销订单 + + ) } -export default DistributionOrders +export default DealerOrders diff --git a/src/dealer/qrcode/index.config.ts b/src/dealer/qrcode/index.config.ts new file mode 100644 index 0000000..b075b21 --- /dev/null +++ b/src/dealer/qrcode/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '推广二维码' +}) diff --git a/src/dealer/qrcode/index.scss b/src/dealer/qrcode/index.scss deleted file mode 100644 index e39411c..0000000 --- a/src/dealer/qrcode/index.scss +++ /dev/null @@ -1,247 +0,0 @@ -.promotion-qrcode-page { - min-height: 100vh; - background-color: #f5f5f5; - padding: 16px; - - .user-card { - background: white; - border-radius: 12px; - padding: 20px; - margin-bottom: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - display: flex; - align-items: center; - - .user-avatar { - width: 60px; - height: 60px; - border-radius: 50%; - margin-right: 16px; - } - - .user-info { - flex: 1; - - .user-name { - font-size: 20px; // 对应 text-xl - font-weight: bold; - color: #1f2937; - margin-bottom: 6px; - } - - .invite-code { - font-size: 16px; // 对应 text-base - color: #6b7280; - background-color: #f3f4f6; - padding: 6px 10px; - border-radius: 6px; - display: inline-block; - } - } - } - - .qrcode-container { - .qrcode-card { - background: white; - border-radius: 12px; - padding: 24px; - margin-bottom: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - text-align: center; - - .qrcode-header { - margin-bottom: 24px; - - .title { - font-size: 20px; // 对应 text-xl - font-weight: bold; - color: #1f2937; - margin-bottom: 6px; - } - - .subtitle { - font-size: 16px; // 对应 text-base - color: #6b7280; - } - } - - .qrcode-wrapper { - margin-bottom: 20px; - - .qrcode-loading { - width: 200px; - height: 200px; - margin: 0 auto; - display: flex; - align-items: center; - justify-content: center; - background-color: #f9fafb; - border-radius: 12px; - - .loading-text { - color: #6b7280; - } - } - - .qrcode-image-container { - .qrcode-placeholder { - width: 200px; - height: 200px; - margin: 0 auto 12px; - background: white; - border: 2px solid #e5e7eb; - border-radius: 12px; - display: flex; - align-items: center; - justify-content: center; - - .qr-pattern { - width: 160px; - height: 160px; - position: relative; - - .qr-corner { - position: absolute; - width: 30px; - height: 30px; - border: 3px solid #1f2937; - - &.top-left { - top: 0; - left: 0; - border-right: none; - border-bottom: none; - } - - &.top-right { - top: 0; - right: 0; - border-left: none; - border-bottom: none; - } - - &.bottom-left { - bottom: 0; - left: 0; - border-right: none; - border-top: none; - } - } - - .qr-dots { - display: grid; - grid-template-columns: repeat(5, 1fr); - gap: 8px; - padding: 40px 20px; - - .qr-dot { - width: 8px; - height: 8px; - background-color: #1f2937; - border-radius: 1px; - } - } - } - } - - .qrcode-tip { - font-size: 14px; // 对应 text-sm - color: #9ca3af; - } - } - } - - .qrcode-info { - .info-item { - text-align: left; - padding: 12px; - background-color: #f9fafb; - border-radius: 8px; - - .info-label { - font-size: 16px; // 对应 text-base - color: #6b7280; - margin-bottom: 4px; - } - - .info-value { - font-size: 14px; // 对应 text-sm - color: #1f2937; - word-break: break-all; - line-height: 1.4; - } - } - } - } - } - - .action-buttons { - margin-bottom: 24px; - - .action-btn { - width: 100%; - margin-bottom: 12px; - border-radius: 8px; - font-size: 16px; - font-weight: 500; - - &.primary { - background: linear-gradient(135deg, #3b82f6, #1d4ed8); - color: white; - border: none; - } - - &.secondary { - background: white; - color: #3b82f6; - border: 1px solid #3b82f6; - } - - &.tertiary { - background: #f3f4f6; - color: #6b7280; - border: none; - } - } - } - - .usage-tips { - background: white; - border-radius: 12px; - padding: 20px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - - .tips-title { - font-size: 20px; // 对应 text-xl - font-weight: bold; - color: #1f2937; - margin-bottom: 16px; - } - - .tips-list { - .tip-item { - font-size: 16px; // 对应 text-base - color: #6b7280; - line-height: 1.6; - margin-bottom: 8px; - padding-left: 8px; - position: relative; - - &:before { - content: ''; - position: absolute; - left: 0; - top: 8px; - width: 4px; - height: 4px; - background-color: #3b82f6; - border-radius: 50%; - } - - &:last-child { - margin-bottom: 0; - } - } - } - } -} diff --git a/src/dealer/qrcode/index.tsx b/src/dealer/qrcode/index.tsx index 7b2f331..8e4ff92 100644 --- a/src/dealer/qrcode/index.tsx +++ b/src/dealer/qrcode/index.tsx @@ -1,221 +1,31 @@ -import { useState, useEffect } from 'react' -import { View, Canvas, Image } from '@tarojs/components' -import Taro from '@tarojs/taro' -import { Button } from '@nutui/nutui-react-taro' -import './index.scss' - -interface UserInfo { - id: string - nickname: string - avatar: string - inviteCode: string -} - -function PromotionQRCode() { - const [userInfo, setUserInfo] = useState({ - id: '12345', - nickname: '分销达人', - avatar: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', - inviteCode: 'INV12345' - }) - const [qrCodeUrl, setQrCodeUrl] = useState('') - const [loading, setLoading] = useState(false) - - useEffect(() => { - Taro.setNavigationBarTitle({ - title: '推广二维码' - }) - generateQRCode() - }, []) - - const generateQRCode = async () => { - try { - setLoading(true) - // 模拟生成二维码 - // 实际项目中应该调用后端API生成包含邀请码的二维码 - const inviteUrl = `https://your-domain.com/invite?code=${userInfo.inviteCode}` - - // 这里使用一个模拟的二维码图片 - // 实际项目中可以使用二维码生成库或调用API - const mockQRCode = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==' - - setTimeout(() => { - setQrCodeUrl(mockQRCode) - setLoading(false) - }, 1000) - } catch (error) { - console.error('生成二维码失败:', error) - setLoading(false) - Taro.showToast({ - title: '生成失败', - icon: 'error' - }) - } - } - - const handleSaveImage = async () => { - try { - if (!qrCodeUrl) { - Taro.showToast({ - title: '二维码未生成', - icon: 'none' - }) - return - } - - // 在实际项目中,这里应该将二维码保存到相册 - Taro.showModal({ - title: '保存二维码', - content: '是否保存二维码到相册?', - success: (res) => { - if (res.confirm) { - // 实际保存逻辑 - Taro.showToast({ - title: '保存成功', - icon: 'success' - }) - } - } - }) - } catch (error) { - console.error('保存图片失败:', error) - Taro.showToast({ - title: '保存失败', - icon: 'error' - }) - } - } - - const handleShareQRCode = () => { - Taro.showActionSheet({ - itemList: ['分享给朋友', '分享到朋友圈', '复制邀请链接'], - success: (res) => { - const actions = ['分享给朋友', '分享到朋友圈', '复制邀请链接'] - const action = actions[res.tapIndex] - - if (action === '复制邀请链接') { - const inviteUrl = `https://your-domain.com/invite?code=${userInfo.inviteCode}` - Taro.setClipboardData({ - data: inviteUrl, - success: () => { - Taro.showToast({ - title: '链接已复制', - icon: 'success' - }) - } - }) - } else { - Taro.showToast({ - title: action, - icon: 'none' - }) - } - } - }) - } - - const handleRefreshQRCode = () => { - Taro.showModal({ - title: '刷新二维码', - content: '确定要重新生成二维码吗?', - success: (res) => { - if (res.confirm) { - generateQRCode() - } - } - }) - } +import React from 'react' +import { View, Text, Image } from '@tarojs/components' +import { Cell, Button } from '@nutui/nutui-react-taro' +const DealerQrcode: React.FC = () => { return ( - - {/* 用户信息卡片 */} - - - - {userInfo.nickname} - 邀请码:{userInfo.inviteCode} + + 推广二维码 + + + + 二维码占位 - - - {/* 二维码展示区域 */} - - - - 我的专属推广二维码 - 扫码注册成为我的下级 - - - - {loading ? ( - - 生成中... - - ) : ( - - {/* 实际项目中这里应该显示真实的二维码 */} - - - - - - - {Array.from({ length: 25 }).map((_, index) => ( - - ))} - - - - 长按识别二维码 - - )} - - - - - 邀请链接 - https://your-domain.com/invite?code={userInfo.inviteCode} - - - - - - {/* 操作按钮 */} - - - - - - - {/* 使用说明 */} - - 使用说明 - - 1. 分享二维码给好友,好友扫码注册成为您的下级 - 2. 下级用户的消费订单将为您带来佣金收益 - 3. 可以保存二维码图片或复制邀请链接进行推广 - 4. 二维码长期有效,可重复使用 - ) } -export default PromotionQRCode +export default DealerQrcode diff --git a/src/dealer/team/index.config.ts b/src/dealer/team/index.config.ts new file mode 100644 index 0000000..926f186 --- /dev/null +++ b/src/dealer/team/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '我的团队' +}) diff --git a/src/dealer/team/index.scss b/src/dealer/team/index.scss deleted file mode 100644 index b1ec4a3..0000000 --- a/src/dealer/team/index.scss +++ /dev/null @@ -1,176 +0,0 @@ -.my-team-page { - min-height: 100vh; - background-color: #f5f5f5; - - .loading-container { - display: flex; - justify-content: center; - align-items: center; - height: 200px; - } - - .team-stats { - background: white; - margin: 16px; - border-radius: 12px; - padding: 20px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - - .stats-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 20px; - - .stat-item { - text-align: center; - - .stat-value { - font-size: 20px; // 对应 text-xl - font-weight: bold; - color: #1f2937; - margin-bottom: 4px; - } - - .stat-label { - font-size: 16px; // 对应 text-base - color: #6b7280; - } - } - } - } - - .level-stats { - background: white; - margin: 0 16px 16px; - border-radius: 12px; - padding: 16px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - - .level-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 12px 0; - - &:not(:last-child) { - border-bottom: 1px solid #f3f4f6; - } - - .level-info { - display: flex; - justify-content: space-between; - align-items: center; - width: 100%; - - .level-title { - font-size: 20px; // 对应 text-xl - color: #1f2937; - } - - .level-count { - font-size: 20px; // 对应 text-xl - font-weight: bold; - color: #3b82f6; - } - } - } - } - - .members-container { - margin: 0 16px; - - .empty-container { - display: flex; - justify-content: center; - align-items: center; - height: 300px; - } - - .members-list { - margin-top: 16px; - - .member-item { - background: white; - border-radius: 12px; - padding: 16px; - margin-bottom: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - display: flex; - align-items: center; - cursor: pointer; - transition: all 0.2s ease; - - &:active { - transform: scale(0.98); - background-color: #f9f9f9; - } - - .member-info { - flex: 1; - margin-left: 12px; - - .member-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 8px; - - .member-name { - font-size: 20px; // 对应 text-xl,成员名称是重要信息 - font-weight: 500; - color: #1f2937; - } - - .member-level { - font-size: 14px; // 对应 text-sm - padding: 4px 10px; - border-radius: 12px; - color: white; - - &.level-1 { - background-color: #3b82f6; - } - - &.level-2 { - background-color: #10b981; - } - } - } - - .member-stats { - display: flex; - gap: 16px; - margin-bottom: 6px; - - .stat { - font-size: 16px; // 对应 text-base - color: #6b7280; - } - } - - .member-time { - font-size: 14px; // 对应 text-sm - color: #9ca3af; - } - } - - .member-status { - font-size: 14px; // 对应 text-sm - padding: 6px 10px; - border-radius: 12px; - margin-left: 12px; - - &.active { - background-color: #d1fae5; - color: #10b981; - } - - &.inactive { - background-color: #fee2e2; - color: #ef4444; - } - } - } - } - } -} diff --git a/src/dealer/team/index.tsx b/src/dealer/team/index.tsx index 371c3f2..9291bae 100644 --- a/src/dealer/team/index.tsx +++ b/src/dealer/team/index.tsx @@ -1,244 +1,15 @@ -import { useState, useEffect } from 'react' -import { View } from '@tarojs/components' -import Taro from '@tarojs/taro' -import { Avatar, Empty, Tabs, TabPane } from '@nutui/nutui-react-taro' -import './index.scss' - -interface TeamMember { - id: string - nickname: string - avatar: string - joinTime: string - level: number - orderCount: number - totalCommission: number - status: 'active' | 'inactive' -} - -interface TeamStats { - totalMembers: number - activeMembers: number - level1Members: number - level2Members: number - totalCommission: number - monthCommission: number -} - -function MyTeam() { - const [activeTab, setActiveTab] = useState('0') - const [members, setMembers] = useState([]) - const [stats, setStats] = useState({ - totalMembers: 0, - activeMembers: 0, - level1Members: 0, - level2Members: 0, - totalCommission: 0, - monthCommission: 0 - }) - const [loading, setLoading] = useState(true) - - useEffect(() => { - Taro.setNavigationBarTitle({ - title: '我的团队' - }) - loadTeamData() - }, []) - - const loadTeamData = async () => { - try { - setLoading(true) - // 模拟数据 - const mockMembers: TeamMember[] = [ - { - id: '1', - nickname: '张小明', - avatar: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', - joinTime: '2024-01-15', - level: 1, - orderCount: 15, - totalCommission: 150.50, - status: 'active' - }, - { - id: '2', - nickname: '李小红', - avatar: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', - joinTime: '2024-01-10', - level: 1, - orderCount: 8, - totalCommission: 89.20, - status: 'active' - }, - { - id: '3', - nickname: '王小华', - avatar: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', - joinTime: '2024-01-08', - level: 2, - orderCount: 3, - totalCommission: 25.80, - status: 'inactive' - }, - { - id: '4', - nickname: '赵小刚', - avatar: 'https://img12.360buyimg.com/imagetools/jfs/t1/143702/31/16654/116794/5fc6f541Edebf8a57/4138097748889987.png', - joinTime: '2024-01-05', - level: 2, - orderCount: 12, - totalCommission: 98.60, - status: 'active' - } - ] - - // 计算统计数据 - const totalMembers = mockMembers.length - const activeMembers = mockMembers.filter(m => m.status === 'active').length - const level1Members = mockMembers.filter(m => m.level === 1).length - const level2Members = mockMembers.filter(m => m.level === 2).length - const totalCommission = mockMembers.reduce((sum, m) => sum + m.totalCommission, 0) - - setTimeout(() => { - setMembers(mockMembers) - setStats({ - totalMembers, - activeMembers, - level1Members, - level2Members, - totalCommission, - monthCommission: totalCommission * 0.3 // 模拟本月佣金 - }) - setLoading(false) - }, 1000) - } catch (error) { - console.error('加载团队数据失败:', error) - setLoading(false) - } - } - - const getFilteredMembers = () => { - switch (activeTab) { - case '1': - return members.filter(member => member.level === 1) - case '2': - return members.filter(member => member.level === 2) - case '3': - return members.filter(member => member.status === 'active') - default: - return members - } - } - - const handleMemberClick = (member: TeamMember) => { - Taro.showModal({ - title: '成员详情', - content: ` -昵称:${member.nickname} -加入时间:${member.joinTime} -等级:${member.level}级下线 -订单数量:${member.orderCount} -累计佣金:¥${member.totalCommission.toFixed(2)} -状态:${member.status === 'active' ? '活跃' : '不活跃'} - `.trim(), - showCancel: false - }) - } - - if (loading) { - return ( - - - 加载中... - - - ) - } +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Empty } from '@nutui/nutui-react-taro' +const DealerTeam: React.FC = () => { return ( - - {/* 团队统计 */} - - - - {stats.totalMembers} - 团队总人数 - - - {stats.activeMembers} - 活跃成员 - - - ¥{stats.totalCommission.toFixed(2)} - 累计佣金 - - - ¥{stats.monthCommission.toFixed(2)} - 本月佣金 - - - + + 我的团队 - {/* 等级统计 */} - - - - 一级下线 - {stats.level1Members}人 - - - - - 二级下线 - {stats.level2Members}人 - - - - - {/* 成员列表 */} - - setActiveTab(value)}> - - - - - - - - {getFilteredMembers().length === 0 ? ( - - - - ) : ( - getFilteredMembers().map((member) => ( - handleMemberClick(member)} - > - - - - {member.nickname} - - {member.level}级 - - - - 订单:{member.orderCount} - 佣金:¥{member.totalCommission.toFixed(2)} - - 加入时间:{member.joinTime} - - - {member.status === 'active' ? '活跃' : '不活跃'} - - - )) - )} - - + ) } -export default MyTeam +export default DealerTeam diff --git a/src/dealer/withdraw/index.config.ts b/src/dealer/withdraw/index.config.ts new file mode 100644 index 0000000..00a9f9b --- /dev/null +++ b/src/dealer/withdraw/index.config.ts @@ -0,0 +1,3 @@ +export default definePageConfig({ + navigationBarTitleText: '提现申请' +}) diff --git a/src/dealer/withdraw/index.scss b/src/dealer/withdraw/index.scss deleted file mode 100644 index 951507e..0000000 --- a/src/dealer/withdraw/index.scss +++ /dev/null @@ -1,75 +0,0 @@ -.withdraw-detail-page { - min-height: 100vh; - background-color: #f5f5f5; - padding: 16px; - - .loading-container { - display: flex; - justify-content: center; - align-items: center; - height: 200px; - } - - .empty-container { - display: flex; - justify-content: center; - align-items: center; - height: 400px; - } - - .records-list { - .record-item { - background: white; - border-radius: 12px; - padding: 16px; - margin-bottom: 12px; - box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); - cursor: pointer; - transition: all 0.2s ease; - - &:active { - transform: scale(0.98); - background-color: #f9f9f9; - } - - .record-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 12px; - - .amount { - font-size: 20px; // 对应 text-xl,重要金额 - font-weight: bold; - color: #1f2937; - } - - .status { - font-size: 14px; // 对应 text-sm - font-weight: 500; - padding: 4px 12px; - border-radius: 20px; - background-color: rgba(0, 0, 0, 0.05); - } - } - - .record-info { - .time { - font-size: 16px; // 对应 text-base - color: #6b7280; - margin-bottom: 4px; - } - - .remark { - font-size: 16px; // 对应 text-base - color: #ef4444; - margin-top: 8px; - padding: 8px; - background-color: #fef2f2; - border-radius: 6px; - border-left: 3px solid #ef4444; - } - } - } - } -} diff --git a/src/dealer/withdraw/index.tsx b/src/dealer/withdraw/index.tsx index f4a2aaf..a2ce148 100644 --- a/src/dealer/withdraw/index.tsx +++ b/src/dealer/withdraw/index.tsx @@ -1,146 +1,35 @@ -import { useState, useEffect } from 'react' -import { View } from '@tarojs/components' -import Taro from '@tarojs/taro' -import { Empty } from '@nutui/nutui-react-taro' -import './index.scss' - -interface WithdrawRecord { - id: string - amount: number - status: 'pending' | 'success' | 'failed' - statusText: string - createTime: string - completeTime?: string - remark?: string -} - -function WithdrawDetail() { - const [records, setRecords] = useState([]) - const [loading, setLoading] = useState(true) - - useEffect(() => { - Taro.setNavigationBarTitle({ - title: '提现明细' - }) - loadWithdrawRecords() - }, []) - - const loadWithdrawRecords = async () => { - try { - setLoading(true) - // 模拟数据,实际应该调用API - const mockData: WithdrawRecord[] = [ - { - id: '1', - amount: 100.00, - status: 'success', - statusText: '提现成功', - createTime: '2024-01-15 14:30:00', - completeTime: '2024-01-15 16:45:00' - }, - { - id: '2', - amount: 50.00, - status: 'pending', - statusText: '处理中', - createTime: '2024-01-10 09:20:00' - }, - { - id: '3', - amount: 200.00, - status: 'failed', - statusText: '提现失败', - createTime: '2024-01-05 11:15:00', - remark: '银行卡信息有误' - } - ] - - setTimeout(() => { - setRecords(mockData) - setLoading(false) - }, 1000) - } catch (error) { - console.error('加载提现记录失败:', error) - setLoading(false) - } - } - - const getStatusColor = (status: string) => { - switch (status) { - case 'success': - return '#10B981' - case 'pending': - return '#F59E0B' - case 'failed': - return '#EF4444' - default: - return '#6B7280' - } - } - - const handleRecordClick = (record: WithdrawRecord) => { - const content = ` -提现金额:¥${record.amount.toFixed(2)} -申请时间:${record.createTime} -${record.completeTime ? `完成时间:${record.completeTime}` : ''} -${record.remark ? `备注:${record.remark}` : ''} - `.trim() - - Taro.showModal({ - title: '提现详情', - content, - showCancel: false - }) - } - - if (loading) { - return ( - - - 加载中... - - - ) - } +import React from 'react' +import { View, Text } from '@tarojs/components' +import { Cell, Button, Form, Input } from '@nutui/nutui-react-taro' +const DealerWithdraw: React.FC = () => { return ( - - {records.length === 0 ? ( - - + + 提现申请 + +
+ + + + + + + + + + + + + + + + - ) : ( - - {records.map((record) => ( - handleRecordClick(record)} - > - - ¥{record.amount.toFixed(2)} - - {record.statusText} - - - - 申请时间:{record.createTime} - {record.completeTime && ( - 完成时间:{record.completeTime} - )} - {record.remark && ( - 备注:{record.remark} - )} - - - ))} - - )} +
) } -export default WithdrawDetail +export default DealerWithdraw diff --git a/src/pages/user/components/UserCard.tsx b/src/pages/user/components/UserCard.tsx index 2e3fedd..d4ff6b9 100644 --- a/src/pages/user/components/UserCard.tsx +++ b/src/pages/user/components/UserCard.tsx @@ -15,6 +15,7 @@ function UserCard() { const [roleName, setRoleName] = useState('注册用户') const [couponCount, setCouponCount] = useState(0) const [pointsCount, setPointsCount] = useState(0) + const [giftCount, setGiftCount] = useState(0) useEffect(() => { @@ -52,6 +53,11 @@ function UserCard() { .catch((error: any) => { console.error('Points stats error:', error) }) + // 加载礼品劵数量 + setGiftCount(0) + // pageUserGiftLog({userId, page: 1, limit: 1}).then(res => { + // setGiftCount(res.count || 0) + // }) } const reload = () => { @@ -85,7 +91,7 @@ function UserCard() { } // 判断身份 const roleName = Taro.getStorageSync('RoleName'); - if(roleName){ + if (roleName) { setRoleName(roleName) } } @@ -153,7 +159,7 @@ function UserCard() { TenantId }, success: function (res) { - if(res.data.code == 1){ + if (res.data.code == 1) { Taro.showToast({ title: res.data.message, icon: 'error', @@ -216,15 +222,23 @@ function UserCard() {
-
navTo('/user/wallet/wallet', true)}> +
navTo('/user/wallet/wallet', true)}> 余额 ¥ {userInfo?.balance || '0.00'}
-
navTo('/user/coupon/coupon', true)}> +
navTo('/user/coupon/index', true)}> 优惠券 {couponCount}
-
navTo('/user/points/points', true)}> +
navTo('/user/gift/index', true)}> + 礼品卡 + {giftCount} +
+
navTo('/user/points/points', true)}> 积分 {pointsCount}
diff --git a/src/shop/shopArticle/index.tsx b/src/shop/shopArticle/index.tsx deleted file mode 100644 index e063ee5..0000000 --- a/src/shop/shopArticle/index.tsx +++ /dev/null @@ -1,272 +0,0 @@ -import {useState} from "react"; -import Taro, {useDidShow} from '@tarojs/taro' -import {Button, Cell, CellGroup, Empty, ConfigProvider, SearchBar, Tag, InfiniteLoading, Loading, PullToRefresh} from '@nutui/nutui-react-taro' -import {Edit, Del, Eye} from '@nutui/icons-react-taro' -import {View} from '@tarojs/components' -import {ShopArticle} from "@/api/shop/shopArticle/model"; -import {pageShopArticle, removeShopArticle} from "@/api/shop/shopArticle"; -import FixedButton from "@/components/FixedButton"; -import dayjs from "dayjs"; - -const ShopArticleManage = () => { - const [list, setList] = useState([]) - const [loading, setLoading] = useState(false) - const [refreshing, setRefreshing] = useState(false) - const [hasMore, setHasMore] = useState(true) - const [searchValue, setSearchValue] = useState('') - const [page, setPage] = useState(1) - const [total, setTotal] = useState(0) - - const reload = async (isRefresh = false) => { - if (isRefresh) { - setPage(1) - setList([]) - setHasMore(true) - } - - setLoading(true) - try { - const currentPage = isRefresh ? 1 : page - const res = await pageShopArticle({ - page: currentPage, - limit: 10, - keywords: searchValue - }) - - if (res && res.list) { - const newList = isRefresh ? res.list : [...list, ...res.list] - setList(newList) - setTotal(res.count || 0) - - // 判断是否还有更多数据 - setHasMore(res.list.length === 10) // 如果返回的数据等于limit,说明可能还有更多 - - if (!isRefresh) { - setPage(currentPage + 1) - } else { - setPage(2) // 刷新后下一页是第2页 - } - } else { - setHasMore(false) - setTotal(0) - } - } catch (error) { - console.error('获取文章失败:', error) - Taro.showToast({ - title: '获取文章失败', - icon: 'error' - }); - } finally { - setLoading(false) - } - } - - // 搜索功能 - const handleSearch = (value: string) => { - setSearchValue(value) - reload(true) - } - - // 下拉刷新 - const handleRefresh = async () => { - setRefreshing(true) - await reload(true) - setRefreshing(false) - } - - // 删除文章 - const handleDelete = async (id?: number) => { - Taro.showModal({ - title: '确认删除', - content: '确定要删除这篇文章吗?', - success: async (res) => { - if (res.confirm) { - try { - await removeShopArticle(id) - Taro.showToast({ - title: '删除成功', - icon: 'success' - }); - reload(true); - } catch (error) { - Taro.showToast({ - title: '删除失败', - icon: 'error' - }); - } - } - } - }); - } - - // 编辑文章 - const handleEdit = (item: ShopArticle) => { - Taro.navigateTo({ - url: `/shop/shopArticle/add?id=${item.articleId}` - }); - } - - // 查看文章详情 - const handleView = (item: ShopArticle) => { - // 这里可以跳转到文章详情页面 - Taro.navigateTo({ - url: `/cms/detail/index?id=${item.articleId}` - }) - } - - // 获取状态标签 - const getStatusTag = (status?: number) => { - switch (status) { - case 0: - return 已发布 - case 1: - return 待审核 - case 2: - return 已驳回 - case 3: - return 违规内容 - default: - return 未知 - } - } - - // 加载更多 - const loadMore = async () => { - if (!loading && hasMore) { - await reload(false) // 不刷新,追加数据 - } - } - - useDidShow(() => { - reload(true).then() - }); - - return ( - - {/* 搜索栏 */} - - - - - {/* 统计信息 */} - {total > 0 && ( - - 共找到 {total} 篇文章 - - )} - - {/* 文章列表 */} - - - {list.length === 0 && !loading ? ( - - - - ) : ( - - - 加载中... - - } - loadMoreText={ - - {list.length === 0 ? "暂无数据" : "没有更多了"} - - } - > - {list.map((item, index) => ( - - - - {/* 文章标题和状态 */} - - - - {item.title} - - - {getStatusTag(item.status)} - - - {/* 文章概述 */} - {item.overview && ( - - {item.overview} - - )} - - {/* 文章信息 */} - - - 阅读: {item.actualViews || 0} - {item.price && 价格: ¥{item.price}} - 创建: {dayjs(item.createTime).format('MM-DD HH:mm')} - - - - {/* 操作按钮 */} - - - - - - - - - ))} - - )} - - - - {/* 底部浮动按钮 */} - } - onClick={() => Taro.navigateTo({url: '/shop/shopArticle/add'})} - /> - - ); - -}; - -export default ShopArticleManage; diff --git a/src/shop/shopArticle/index.config.ts b/src/user/coupon/add.config.ts similarity index 59% rename from src/shop/shopArticle/index.config.ts rename to src/user/coupon/add.config.ts index 87493d8..5d97955 100644 --- a/src/shop/shopArticle/index.config.ts +++ b/src/user/coupon/add.config.ts @@ -1,4 +1,4 @@ export default definePageConfig({ - navigationBarTitleText: '商品文章管理', + navigationBarTitleText: '新增收货地址', navigationBarTextStyle: 'black' }) diff --git a/src/user/coupon/add.tsx b/src/user/coupon/add.tsx new file mode 100644 index 0000000..0d85f74 --- /dev/null +++ b/src/user/coupon/add.tsx @@ -0,0 +1,323 @@ +import {useEffect, useState, useRef} from "react"; +import {useRouter} from '@tarojs/taro' +import {Button, Loading, CellGroup, Input, TextArea, Form, Switch, InputNumber, Radio, Image} from '@nutui/nutui-react-taro' +import {Edit, Upload as UploadIcon} from '@nutui/icons-react-taro' +import Taro from '@tarojs/taro' +import {View} from '@tarojs/components' +import {ShopArticle} from "@/api/shop/shopArticle/model"; +import {getShopArticle, addShopArticle, updateShopArticle} from "@/api/shop/shopArticle"; +import FixedButton from "@/components/FixedButton"; + +const AddShopArticle = () => { + const {params} = useRouter(); + const [loading, setLoading] = useState(true) + const [formData, setFormData] = useState({ + type: 0, // 默认常规文章 + status: 0, // 默认已发布 + permission: 0, // 默认所有人可见 + recommend: 0, // 默认不推荐 + showType: 10, // 默认小图展示 + virtualViews: 0, // 默认虚拟阅读量 + actualViews: 0, // 默认实际阅读量 + sortNumber: 0 // 默认排序 + }) + const formRef = useRef(null) + + // 判断是编辑还是新增模式 + const isEditMode = !!params.id + const articleId = params.id ? Number(params.id) : undefined + + // 文章类型选项 + const typeOptions = [ + { text: '常规文章', value: 0 }, + { text: '视频文章', value: 1 } + ] + + // 状态选项 + const statusOptions = [ + { text: '已发布', value: 0 }, + { text: '待审核', value: 1 }, + { text: '已驳回', value: 2 }, + { text: '违规内容', value: 3 } + ] + + // 可见性选项 + const permissionOptions = [ + { text: '所有人可见', value: 0 }, + { text: '登录可见', value: 1 }, + { text: '密码可见', value: 2 } + ] + + // 显示方式选项 + const showTypeOptions = [ + { text: '小图展示', value: 10 }, + { text: '大图展示', value: 20 } + ] + + const reload = async () => { + // 如果是编辑模式,加载文章数据 + if (isEditMode && articleId) { + try { + const article = await getShopArticle(articleId) + setFormData(article) + // 更新表单值 + if (formRef.current) { + formRef.current.setFieldsValue(article) + } + } catch (error) { + console.error('加载文章失败:', error) + Taro.showToast({ + title: '加载文章失败', + icon: 'error' + }); + } + } + } + + // 图片上传处理 + const handleImageUpload = async () => { + try { + const res = await Taro.chooseImage({ + count: 1, + sizeType: ['compressed'], + sourceType: ['album', 'camera'] + }); + + if (res.tempFilePaths && res.tempFilePaths.length > 0) { + // 这里应该调用上传接口,暂时使用本地路径 + const imagePath = res.tempFilePaths[0]; + setFormData({ + ...formData, + image: imagePath + }); + + Taro.showToast({ + title: '图片选择成功', + icon: 'success' + }); + } + } catch (error) { + Taro.showToast({ + title: '图片选择失败', + icon: 'error' + }); + } + }; + + // 提交表单 + const submitSucceed = async (values: any) => { + try { + // 准备提交的数据 + const submitData = { + ...formData, + ...values, + }; + + // 如果是编辑模式,添加id + if (isEditMode && articleId) { + submitData.articleId = articleId; + } + + // 执行新增或更新操作 + if (isEditMode) { + await updateShopArticle(submitData); + } else { + await addShopArticle(submitData); + } + + Taro.showToast({ + title: `${isEditMode ? '更新' : '保存'}成功`, + icon: 'success' + }); + + setTimeout(() => { + Taro.navigateBack(); + }, 1000); + + } catch (error) { + console.error('保存失败:', error); + Taro.showToast({ + title: `${isEditMode ? '更新' : '保存'}失败`, + icon: 'error' + }); + } + } + + const submitFailed = (error: any) => { + console.log(error, 'err...') + } + + useEffect(() => { + // 动态设置页面标题 + Taro.setNavigationBarTitle({ + title: isEditMode ? '编辑文章' : '新增文章' + }); + + reload().then(() => { + setLoading(false) + }) + }, [isEditMode]); + + if (loading) { + return 加载中 + } + + return ( + <> +
submitSucceed(values)} + onFinishFailed={(errors) => submitFailed(errors)} + > + {/* 基本信息 */} + + + + + + +