*/}
diff --git a/src/admin/components/UserCell.tsx b/src/admin/components/UserCell.tsx
index 57bb70c..0928980 100644
--- a/src/admin/components/UserCell.tsx
+++ b/src/admin/components/UserCell.tsx
@@ -36,7 +36,7 @@ const UserCell = () => {
backgroundImage: 'linear-gradient(to right bottom, #54a799, #177b73)',
}}
title={
-
navTo('/doctor/index', true)}>
+ navTo('/dealer/index', true)}>
开通会员
享优惠
diff --git a/src/api/afterSale.ts b/src/api/afterSale.ts
index e285647..faf03f5 100644
--- a/src/api/afterSale.ts
+++ b/src/api/afterSale.ts
@@ -117,7 +117,7 @@ export const STATUS_COLOR_MAP = {
// 申请售后
export const applyAfterSale = async (params: AfterSaleApplyParams): Promise => {
try {
- const response = await request({
+ const response = await request({
url: '/api/after-sale/apply',
method: 'POST',
data: params
@@ -136,7 +136,7 @@ export const getAfterSaleDetail = async (params: {
afterSaleId?: string
}): Promise => {
try {
- const response = await request({
+ const response = await request({
url: '/api/after-sale/detail',
method: 'GET',
data: params
@@ -154,7 +154,7 @@ export const getAfterSaleDetail = async (params: {
// 查询售后列表
export const getAfterSaleList = async (params: AfterSaleListParams): Promise => {
try {
- const response = await request({
+ const response = await request({
url: '/api/after-sale/list',
method: 'GET',
data: params
@@ -170,7 +170,7 @@ export const getAfterSaleList = async (params: AfterSaleListParams): Promise => {
try {
- const response = await request({
+ const response = await request<{ success: boolean; message?: string }>({
url: '/api/after-sale/cancel',
method: 'POST',
data: { afterSaleId }
@@ -312,11 +312,9 @@ export const getAfterSaleSteps = (type: AfterSaleType, status: AfterSaleStatus)
// 根据类型调整步骤
if (type === 'return' || type === 'exchange') {
- baseSteps.splice(2, 1,
- { title: '寄回商品', description: '请将商品寄回指定地址' },
- { title: '处理中', description: '收到商品,正在处理' }
- )
+ baseSteps.splice(2, 0, { title: '等待收货', description: '等待用户寄回商品' })
+ baseSteps.splice(3, 0, { title: '确认收货', description: '商家确认收到退回商品' })
}
return baseSteps
-}
+}
\ No newline at end of file
diff --git a/src/api/cms/cmsArticle/index.ts b/src/api/cms/cmsArticle/index.ts
index 9d95918..608c0e5 100644
--- a/src/api/cms/cmsArticle/index.ts
+++ b/src/api/cms/cmsArticle/index.ts
@@ -204,19 +204,6 @@ export async function getByIds(params?: CmsArticleParam) {
return Promise.reject(new Error(res.message));
}
-/**
- * 根据code查询文章
- */
-export async function getCmsArticleByCode(code: string) {
- const res = await request.get>(
- '/cms/cms-article/getByCode/' + code
- );
- if (res.code === 0 && res.data) {
- return res.data;
- }
- return Promise.reject(new Error(res.message));
-}
-
/**
* 根据code查询文章
*/
diff --git a/src/api/cms/cmsNavigation/index.ts b/src/api/cms/cmsNavigation/index.ts
index cd892e2..7eb316f 100644
--- a/src/api/cms/cmsNavigation/index.ts
+++ b/src/api/cms/cmsNavigation/index.ts
@@ -1,7 +1,6 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { CmsNavigation, CmsNavigationParam } from './model';
-import type {CmsArticle} from "@/api/cms/cmsArticle/model";
/**
* 分页查询网站导航记录表
@@ -123,11 +122,12 @@ export async function getNavigationByPath(params: CmsNavigationParam) {
return Promise.reject(new Error(res.message));
}
+
/**
* 根据code查询导航
*/
export async function getByCode(code: string) {
- const res = await request.get>(
+ const res = await request.get>(
'/cms/cms-navigation/getByCode/' + code
);
if (res.code === 0 && res.data) {
@@ -135,3 +135,4 @@ export async function getByCode(code: string) {
}
return Promise.reject(new Error(res.message));
}
+
diff --git a/src/api/cms/cmsNavigation/model/index.ts b/src/api/cms/cmsNavigation/model/index.ts
index 9452bb2..cdd4f0c 100644
--- a/src/api/cms/cmsNavigation/model/index.ts
+++ b/src/api/cms/cmsNavigation/model/index.ts
@@ -113,7 +113,5 @@ export interface CmsNavigationParam extends PageParam {
parentId?: number;
hide?: number;
model?: string;
- home?: number;
- position?: number;
keywords?: string;
}
diff --git a/src/api/cms/cmsWebsiteField/model/index.ts b/src/api/cms/cmsWebsiteField/model/index.ts
index 00a9874..ec5967c 100644
--- a/src/api/cms/cmsWebsiteField/model/index.ts
+++ b/src/api/cms/cmsWebsiteField/model/index.ts
@@ -1,4 +1,4 @@
-import type { PageParam } from '@/api';
+import type { PageParam } from '@/api/index';
/**
* 应用参数
diff --git a/src/api/invite/index.ts b/src/api/invite/index.ts
index 9dc508e..ba38f75 100644
--- a/src/api/invite/index.ts
+++ b/src/api/invite/index.ts
@@ -111,7 +111,6 @@ export interface InviteRecordParam {
*/
export async function generateMiniProgramCode(data: MiniProgramCodeParam) {
try {
- // return 'http://127.0.0.1:9200/api/wx-login/getOrderQRCodeUnlimited/' + data.scene;
const url = '/wx-login/getOrderQRCodeUnlimited/' + data.scene;
// 由于接口直接返回图片buffer,我们直接构建完整的URL
return `${BaseUrl}${url}`;
diff --git a/src/api/shop/shopDealerApply/model/index.ts b/src/api/shop/shopDealerApply/model/index.ts
index 3187633..8ea27cc 100644
--- a/src/api/shop/shopDealerApply/model/index.ts
+++ b/src/api/shop/shopDealerApply/model/index.ts
@@ -1,4 +1,4 @@
-import type { PageParam } from '@/api';
+import type { PageParam } from '@/api/index';
/**
* 分销商申请记录表
@@ -10,14 +10,6 @@ export interface ShopDealerApply {
userId?: number;
// 姓名
realName?: string;
- // 分销商名称
- dealerName?: string;
- // 分销商编号
- dealerCode?: string;
- // 详细地址
- address?: string;
- // 金额
- money?: number;
// 手机号
mobile?: string;
// 推荐人用户ID
@@ -25,9 +17,7 @@ export interface ShopDealerApply {
// 申请方式(10需后台审核 20无需审核)
applyType?: number;
// 申请时间
- applyTime?: string;
- // 签单时间
- contractTime?: string;
+ applyTime?: number;
// 审核状态 (10待审核 20审核通过 30驳回)
applyStatus?: number;
// 审核时间
@@ -40,14 +30,6 @@ export interface ShopDealerApply {
createTime?: string;
// 修改时间
updateTime?: string;
- // 过期时间
- expirationTime?: string;
- // 备注
- comments?: string;
- // 昵称
- nickName?: string;
- // 推荐人名称
- refereeName?: string;
}
/**
@@ -55,10 +37,7 @@ export interface ShopDealerApply {
*/
export interface ShopDealerApplyParam extends PageParam {
applyId?: number;
- type?: number;
- dealerName?: string;
mobile?: string;
userId?: number;
keywords?: string;
- applyStatus?: number; // 申请状态筛选 (10待审核 20审核通过 30驳回)
}
diff --git a/src/api/shop/shopDealerBank/model/index.ts b/src/api/shop/shopDealerBank/model/index.ts
deleted file mode 100644
index 0fbd93b..0000000
--- a/src/api/shop/shopDealerBank/model/index.ts
+++ /dev/null
@@ -1,45 +0,0 @@
-import type { PageParam } from '@/api';
-
-/**
- * 分销商提现银行卡
- */
-export interface ShopDealerBank {
- // 主键ID
- id?: number;
- // 分销商用户ID
- userId?: number;
- // 开户行名称
- bankName?: string;
- // 银行开户名
- bankAccount?: string;
- // 银行卡号
- bankCard?: string;
- // 申请状态 (10待审核 20审核通过 30驳回)
- applyStatus?: number;
- // 审核时间
- auditTime?: number;
- // 驳回原因
- rejectReason?: string;
- // 是否默认
- isDefault?: boolean;
- // 租户id
- tenantId?: number;
- // 创建时间
- createTime?: string;
- // 修改时间
- updateTime?: string;
- // 类型
- type?: string;
- // 名称
- name?: string;
-}
-
-/**
- * 分销商提现银行卡搜索条件
- */
-export interface ShopDealerBankParam extends PageParam {
- id?: number;
- userId?: number;
- isDefault?: boolean;
- keywords?: string;
-}
diff --git a/src/api/shop/shopDealerCapital/model/index.ts b/src/api/shop/shopDealerCapital/model/index.ts
index e6a6bc2..00d29a7 100644
--- a/src/api/shop/shopDealerCapital/model/index.ts
+++ b/src/api/shop/shopDealerCapital/model/index.ts
@@ -31,5 +31,11 @@ export interface ShopDealerCapital {
*/
export interface ShopDealerCapitalParam extends PageParam {
id?: number;
+ // 仅查询当前分销商的收益/资金明细
+ userId?: number;
+ // 可选:按订单过滤
+ orderId?: number;
+ // 可选:资金流动类型过滤
+ flowType?: number;
keywords?: string;
}
diff --git a/src/api/shop/shopDealerOrder/model/index.ts b/src/api/shop/shopDealerOrder/model/index.ts
index 68b4928..76d74a9 100644
--- a/src/api/shop/shopDealerOrder/model/index.ts
+++ b/src/api/shop/shopDealerOrder/model/index.ts
@@ -8,6 +8,9 @@ export interface ShopDealerOrder {
id?: number;
// 买家用户ID
userId?: number;
+ nickname?: string;
+ // 订单编号(部分接口会直接返回订单号字符串)
+ orderNo?: string;
// 订单ID
orderId?: number;
// 订单总金额(不含运费)
@@ -47,5 +50,7 @@ export interface ShopDealerOrderParam extends PageParam {
secondUserId?: number;
thirdUserId?: number;
userId?: number;
+ // 数据权限/资源ID(通常传当前登录用户ID)
+ resourceId?: number;
keywords?: string;
}
diff --git a/src/api/shop/shopDealerWithdraw/index.ts b/src/api/shop/shopDealerWithdraw/index.ts
index 6968031..398b9be 100644
--- a/src/api/shop/shopDealerWithdraw/index.ts
+++ b/src/api/shop/shopDealerWithdraw/index.ts
@@ -2,6 +2,21 @@ import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
import type { ShopDealerWithdraw, ShopDealerWithdrawParam } from './model';
+// WeChat transfer v3: backend may return `package_info` for MiniProgram to open the
+// "confirm receipt" page via `wx.requestMerchantTransfer`.
+export type ShopDealerWithdrawCreateResult =
+ | string
+ | {
+ package_info?: string;
+ packageInfo?: string;
+ [k: string]: any;
+ }
+ | null
+ | undefined;
+
+// When applyStatus=20, user can "receive" (WeChat confirm receipt flow).
+export type ShopDealerWithdrawReceiveResult = ShopDealerWithdrawCreateResult;
+
/**
* 分页查询分销商提现明细表
*/
@@ -33,11 +48,40 @@ export async function listShopDealerWithdraw(params?: ShopDealerWithdrawParam) {
/**
* 添加分销商提现明细表
*/
-export async function addShopDealerWithdraw(data: ShopDealerWithdraw) {
- const res = await request.post>(
+export async function addShopDealerWithdraw(data: ShopDealerWithdraw): Promise {
+ const res = await request.post>(
'/shop/shop-dealer-withdraw',
data
);
+ if (res.code === 0) {
+ // Some backends return `message`, while WeChat transfer flow returns `data.package_info`.
+ return res.data ?? res.message;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 用户领取(仅当 applyStatus=20 时)- 后台返回 package_info 供小程序调起确认收款页
+ */
+export async function receiveShopDealerWithdraw(id: number): Promise {
+ const res = await request.post>(
+ '/shop/shop-dealer-withdraw/receive/' + id,
+ {}
+ );
+ if (res.code === 0) {
+ return res.data ?? res.message;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 领取成功回调:前端确认收款后通知后台把状态置为 applyStatus=40
+ */
+export async function receiveSuccessShopDealerWithdraw(id: number) {
+ const res = await request.post>(
+ '/shop/shop-dealer-withdraw/receive-success/' + id,
+ {}
+ );
if (res.code === 0) {
return res.message;
}
diff --git a/src/api/shop/shopGift/model/index.ts b/src/api/shop/shopGift/model/index.ts
index 93713a6..f3c7450 100644
--- a/src/api/shop/shopGift/model/index.ts
+++ b/src/api/shop/shopGift/model/index.ts
@@ -1,7 +1,7 @@
import type { PageParam } from '@/api';
/**
- * 礼品卡
+ * 水票
*/
export interface ShopGift {
// 礼品卡ID
diff --git a/src/api/shop/shopOrder/index.ts b/src/api/shop/shopOrder/index.ts
index c2637c4..ca1e805 100644
--- a/src/api/shop/shopOrder/index.ts
+++ b/src/api/shop/shopOrder/index.ts
@@ -118,7 +118,7 @@ export interface WxPayResult {
*/
export async function createOrder(data: OrderCreateRequest) {
const res = await request.post>(
- '/shop/shop-order',
+ '/shop/shop-order',
data
);
if (res.code === 0) {
diff --git a/src/api/shop/shopOrder/model/index.ts b/src/api/shop/shopOrder/model/index.ts
index 92709f8..b8afaa1 100644
--- a/src/api/shop/shopOrder/model/index.ts
+++ b/src/api/shop/shopOrder/model/index.ts
@@ -1,5 +1,5 @@
import type { PageParam } from '@/api/index';
-import {OrderGoods} from "@/api/system/orderGoods/model";
+import type { ShopOrderGoods } from '@/api/shop/shopOrderGoods/model';
/**
* 订单
@@ -27,6 +27,14 @@ export interface ShopOrder {
merchantName?: string;
// 商户编号
merchantCode?: string;
+ // 归属门店ID(shop_store.id)
+ storeId?: number;
+ // 归属门店名称
+ storeName?: string;
+ // 配送员用户ID(优先级派单)
+ riderId?: number;
+ // 发货仓库ID
+ warehouseId?: number;
// 使用的优惠券id
couponId?: number;
// 使用的会员卡id
@@ -61,6 +69,8 @@ export interface ShopOrder {
sendStartTime?: string;
// 配送结束时间
sendEndTime?: string;
+ // 配送员送达拍照(选填)
+ sendEndImg?: string;
// 发货店铺id
expressMerchantId?: number;
// 发货店铺
@@ -146,7 +156,7 @@ export interface ShopOrder {
// 是否已收到赠品
hasTakeGift?: string;
// 订单商品项
- orderGoods?: OrderGoods[];
+ orderGoods?: ShopOrderGoods[];
}
/**
@@ -165,6 +175,14 @@ export interface OrderGoodsItem {
export interface OrderCreateRequest {
// 商品信息列表
goodsItems: OrderGoodsItem[];
+ // 归属门店ID(shop_store.id)
+ storeId?: number;
+ // 归属门店名称(可选)
+ storeName?: string;
+ // 配送员用户ID(优先级派单)
+ riderId?: number;
+ // 发货仓库ID
+ warehouseId?: number;
// 收货地址ID
addressId?: number;
// 支付方式
@@ -197,6 +215,14 @@ export interface OrderGoodsItem {
export interface OrderCreateRequest {
// 商品信息列表
goodsItems: OrderGoodsItem[];
+ // 归属门店ID(shop_store.id)
+ storeId?: number;
+ // 归属门店名称(可选)
+ storeName?: string;
+ // 配送员用户ID(优先级派单)
+ riderId?: number;
+ // 发货仓库ID
+ warehouseId?: number;
// 收货地址ID
addressId?: number;
// 支付方式
@@ -223,6 +249,12 @@ export interface ShopOrderParam extends PageParam {
payType?: number;
isInvoice?: boolean;
userId?: number;
+ // 归属门店ID(shop_store.id)
+ storeId?: number;
+ // 配送员用户ID
+ riderId?: number;
+ // 发货仓库ID
+ warehouseId?: number;
keywords?: string;
deliveryStatus?: number;
statusFilter?: number;
diff --git a/src/api/shop/shopUser/index.ts b/src/api/shop/shopStore/index.ts
similarity index 51%
rename from src/api/shop/shopUser/index.ts
rename to src/api/shop/shopStore/index.ts
index 45184f8..e67303f 100644
--- a/src/api/shop/shopUser/index.ts
+++ b/src/api/shop/shopStore/index.ts
@@ -1,16 +1,14 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
-import type { ShopUser, ShopUserParam } from './model';
+import type { ShopStore, ShopStoreParam } from './model';
/**
- * 分页查询用户记录表
+ * 分页查询门店
*/
-export async function pageShopUser(params: ShopUserParam) {
- const res = await request.get>>(
- '/shop/shop-user/page',
- {
- params
- }
+export async function pageShopStore(params: ShopStoreParam) {
+ const res = await request.get>>(
+ '/shop/shop-store/page',
+ params
);
if (res.code === 0) {
return res.data;
@@ -19,14 +17,12 @@ export async function pageShopUser(params: ShopUserParam) {
}
/**
- * 查询用户记录表列表
+ * 查询门店列表
*/
-export async function listShopUser(params?: ShopUserParam) {
- const res = await request.get>(
- '/shop/shop-user',
- {
- params
- }
+export async function listShopStore(params?: ShopStoreParam) {
+ const res = await request.get>(
+ '/shop/shop-store',
+ params
);
if (res.code === 0 && res.data) {
return res.data;
@@ -35,11 +31,11 @@ export async function listShopUser(params?: ShopUserParam) {
}
/**
- * 添加用户记录表
+ * 添加门店
*/
-export async function addShopUser(data: ShopUser) {
+export async function addShopStore(data: ShopStore) {
const res = await request.post>(
- 'http://127.0.0.1:9200/api/shop/shop-user',
+ '/shop/shop-store',
data
);
if (res.code === 0) {
@@ -49,11 +45,11 @@ export async function addShopUser(data: ShopUser) {
}
/**
- * 修改用户记录表
+ * 修改门店
*/
-export async function updateShopUser(data: ShopUser) {
+export async function updateShopStore(data: ShopStore) {
const res = await request.put>(
- '/shop/shop-user',
+ '/shop/shop-store',
data
);
if (res.code === 0) {
@@ -63,11 +59,11 @@ export async function updateShopUser(data: ShopUser) {
}
/**
- * 删除用户记录表
+ * 删除门店
*/
-export async function removeShopUser(id?: number) {
+export async function removeShopStore(id?: number) {
const res = await request.del>(
- '/shop/shop-user/' + id
+ '/shop/shop-store/' + id
);
if (res.code === 0) {
return res.message;
@@ -76,11 +72,11 @@ export async function removeShopUser(id?: number) {
}
/**
- * 批量删除用户记录表
+ * 批量删除门店
*/
-export async function removeBatchShopUser(data: (number | undefined)[]) {
+export async function removeBatchShopStore(data: (number | undefined)[]) {
const res = await request.del>(
- '/shop/shop-user/batch',
+ '/shop/shop-store/batch',
{
data
}
@@ -92,11 +88,11 @@ export async function removeBatchShopUser(data: (number | undefined)[]) {
}
/**
- * 根据userId查询用户记录表
+ * 根据id查询门店
*/
-export async function getShopUser(userId: number) {
- const res = await request.get>(
- '/shop/shop-user/' + userId
+export async function getShopStore(id: number) {
+ const res = await request.get>(
+ '/shop/shop-store/' + id
);
if (res.code === 0 && res.data) {
return res.data;
diff --git a/src/api/shop/shopStore/model/index.ts b/src/api/shop/shopStore/model/index.ts
new file mode 100644
index 0000000..3e5bcd1
--- /dev/null
+++ b/src/api/shop/shopStore/model/index.ts
@@ -0,0 +1,63 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 门店
+ */
+export interface ShopStore {
+ // 自增ID
+ id?: number;
+ // 店铺名称
+ name?: string;
+ // 门店地址
+ address?: string;
+ // 手机号码
+ phone?: string;
+ // 邮箱
+ email?: string;
+ // 门店经理
+ managerName?: string;
+ // 门店banner
+ shopBanner?: string;
+ // 所在省份
+ province?: string;
+ // 所在城市
+ city?: string;
+ // 所在辖区
+ region?: string;
+ // 经度和纬度
+ lngAndLat?: string;
+ // 位置
+ location?:string;
+ // 区域
+ district?: string;
+ // 轮廓
+ points?: string;
+ // 用户ID
+ userId?: number;
+ // 默认仓库ID(shop_warehouse.id)
+ warehouseId?: number;
+ // 默认仓库名称(可选)
+ warehouseName?: string;
+ // 状态
+ status?: number;
+ // 备注
+ comments?: string;
+ // 排序号
+ sortNumber?: number;
+ // 是否删除
+ isDelete?: number;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+ // 修改时间
+ updateTime?: string;
+}
+
+/**
+ * 门店搜索条件
+ */
+export interface ShopStoreParam extends PageParam {
+ id?: number;
+ keywords?: string;
+}
diff --git a/src/api/shop/shopDealerBank/index.ts b/src/api/shop/shopStoreRider/index.ts
similarity index 52%
rename from src/api/shop/shopDealerBank/index.ts
rename to src/api/shop/shopStoreRider/index.ts
index eca6fea..c411aef 100644
--- a/src/api/shop/shopDealerBank/index.ts
+++ b/src/api/shop/shopStoreRider/index.ts
@@ -1,13 +1,13 @@
import request from '@/utils/request';
import type { ApiResult, PageResult } from '@/api';
-import type { ShopDealerBank, ShopDealerBankParam } from './model';
+import type { ShopStoreRider, ShopStoreRiderParam } from './model';
/**
- * 分页查询分销商银行卡
+ * 分页查询配送员
*/
-export async function pageShopDealerBank(params: ShopDealerBankParam) {
- const res = await request.get>>(
- '/shop/shop-dealer-bank/page',
+export async function pageShopStoreRider(params: ShopStoreRiderParam) {
+ const res = await request.get>>(
+ '/shop/shop-store-rider/page',
params
);
if (res.code === 0) {
@@ -17,11 +17,11 @@ export async function pageShopDealerBank(params: ShopDealerBankParam) {
}
/**
- * 查询分销商银行卡列表
+ * 查询配送员列表
*/
-export async function listShopDealerBank(params?: ShopDealerBankParam) {
- const res = await request.get>(
- '/shop/shop-dealer-bank',
+export async function listShopStoreRider(params?: ShopStoreRiderParam) {
+ const res = await request.get>(
+ '/shop/shop-store-rider',
params
);
if (res.code === 0 && res.data) {
@@ -31,11 +31,11 @@ export async function listShopDealerBank(params?: ShopDealerBankParam) {
}
/**
- * 添加分销商银行卡
+ * 添加配送员
*/
-export async function addShopDealerBank(data: ShopDealerBank) {
+export async function addShopStoreRider(data: ShopStoreRider) {
const res = await request.post>(
- '/shop/shop-dealer-bank',
+ '/shop/shop-store-rider',
data
);
if (res.code === 0) {
@@ -45,11 +45,11 @@ export async function addShopDealerBank(data: ShopDealerBank) {
}
/**
- * 修改分销商银行卡
+ * 修改配送员
*/
-export async function updateShopDealerBank(data: ShopDealerBank) {
+export async function updateShopStoreRider(data: ShopStoreRider) {
const res = await request.put>(
- '/shop/shop-dealer-bank',
+ '/shop/shop-store-rider',
data
);
if (res.code === 0) {
@@ -59,11 +59,11 @@ export async function updateShopDealerBank(data: ShopDealerBank) {
}
/**
- * 删除分销商银行卡
+ * 删除配送员
*/
-export async function removeShopDealerBank(id?: number) {
+export async function removeShopStoreRider(id?: number) {
const res = await request.del>(
- '/shop/shop-dealer-bank/' + id
+ '/shop/shop-store-rider/' + id
);
if (res.code === 0) {
return res.message;
@@ -72,11 +72,11 @@ export async function removeShopDealerBank(id?: number) {
}
/**
- * 批量删除分销商银行卡
+ * 批量删除配送员
*/
-export async function removeBatchShopDealerBank(data: (number | undefined)[]) {
+export async function removeBatchShopStoreRider(data: (number | undefined)[]) {
const res = await request.del>(
- '/shop/shop-dealer-bank/batch',
+ '/shop/shop-store-rider/batch',
{
data
}
@@ -88,11 +88,11 @@ export async function removeBatchShopDealerBank(data: (number | undefined)[]) {
}
/**
- * 根据id查询分销商银行卡
+ * 根据id查询配送员
*/
-export async function getShopDealerBank(id: number) {
- const res = await request.get>(
- '/shop/shop-dealer-bank/' + id
+export async function getShopStoreRider(id: number) {
+ const res = await request.get>(
+ '/shop/shop-store-rider/' + id
);
if (res.code === 0 && res.data) {
return res.data;
diff --git a/src/api/shop/shopStoreRider/model/index.ts b/src/api/shop/shopStoreRider/model/index.ts
new file mode 100644
index 0000000..26e51f4
--- /dev/null
+++ b/src/api/shop/shopStoreRider/model/index.ts
@@ -0,0 +1,67 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 配送员
+ */
+export interface ShopStoreRider {
+ // 主键ID
+ id?: string;
+ // 配送点ID(shop_dealer.id)
+ dealerId?: number;
+ // 骑手编号(可选)
+ riderNo?: string;
+ // 姓名
+ realName?: string;
+ // 手机号
+ mobile?: string;
+ // 头像
+ avatar?: string;
+ // 身份证号(可选)
+ idCardNo?: string;
+ // 状态:1启用;0禁用
+ status?: number;
+ // 接单状态:0休息/下线;1在线;2忙碌
+ workStatus?: number;
+ // 是否开启自动派单:1是;0否
+ autoDispatchEnabled?: number;
+ // 派单优先级(同小区多骑手时可用,值越大越优先)
+ dispatchPriority?: number;
+ // 最大同时配送单数(0表示不限制)
+ maxOnhandOrders?: number;
+ // 是否计算工资(提成):1计算;0不计算(如三方配送点可设0)
+ commissionCalcEnabled?: number;
+ // 水每桶提成金额(元/桶)
+ waterBucketUnitFee?: string;
+ // 其他商品提成方式:1按订单固定金额;2按订单金额比例;3按商品规则(另表)
+ otherGoodsCommissionType?: number;
+ // 其他商品提成值:固定金额(元)或比例(%)
+ otherGoodsCommissionValue?: string;
+ // 用户ID
+ userId?: number;
+ // 备注
+ comments?: string;
+ // 排序号
+ sortNumber?: number;
+ // 是否删除
+ isDelete?: number;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+ // 修改时间
+ updateTime?: string;
+}
+
+/**
+ * 配送员搜索条件
+ */
+export interface ShopStoreRiderParam extends PageParam {
+ id?: number;
+ keywords?: string;
+ // 配送点/门店ID(后端可能用 dealerId 或 storeId)
+ dealerId?: number;
+ storeId?: number;
+ status?: number;
+ workStatus?: number;
+ autoDispatchEnabled?: number;
+}
diff --git a/src/api/shop/shopStoreUser/index.ts b/src/api/shop/shopStoreUser/index.ts
new file mode 100644
index 0000000..0e500ab
--- /dev/null
+++ b/src/api/shop/shopStoreUser/index.ts
@@ -0,0 +1,101 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { ShopStoreUser, ShopStoreUserParam } from './model';
+
+/**
+ * 分页查询店员
+ */
+export async function pageShopStoreUser(params: ShopStoreUserParam) {
+ const res = await request.get>>(
+ '/shop/shop-store-user/page',
+ params
+ );
+ if (res.code === 0) {
+ return res.data;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 查询店员列表
+ */
+export async function listShopStoreUser(params?: ShopStoreUserParam) {
+ const res = await request.get>(
+ '/shop/shop-store-user',
+ params
+ );
+ if (res.code === 0 && res.data) {
+ return res.data;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 添加店员
+ */
+export async function addShopStoreUser(data: ShopStoreUser) {
+ const res = await request.post>(
+ '/shop/shop-store-user',
+ data
+ );
+ if (res.code === 0) {
+ return res.message;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 修改店员
+ */
+export async function updateShopStoreUser(data: ShopStoreUser) {
+ const res = await request.put>(
+ '/shop/shop-store-user',
+ data
+ );
+ if (res.code === 0) {
+ return res.message;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 删除店员
+ */
+export async function removeShopStoreUser(id?: number) {
+ const res = await request.del>(
+ '/shop/shop-store-user/' + id
+ );
+ if (res.code === 0) {
+ return res.message;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 批量删除店员
+ */
+export async function removeBatchShopStoreUser(data: (number | undefined)[]) {
+ const res = await request.del>(
+ '/shop/shop-store-user/batch',
+ {
+ data
+ }
+ );
+ if (res.code === 0) {
+ return res.message;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 根据id查询店员
+ */
+export async function getShopStoreUser(id: number) {
+ const res = await request.get>(
+ '/shop/shop-store-user/' + id
+ );
+ if (res.code === 0 && res.data) {
+ return res.data;
+ }
+ return Promise.reject(new Error(res.message));
+}
diff --git a/src/api/shop/shopStoreUser/model/index.ts b/src/api/shop/shopStoreUser/model/index.ts
new file mode 100644
index 0000000..46151f5
--- /dev/null
+++ b/src/api/shop/shopStoreUser/model/index.ts
@@ -0,0 +1,36 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 店员
+ */
+export interface ShopStoreUser {
+ // 主键ID
+ id?: number;
+ // 配送点ID(shop_dealer.id)
+ storeId?: number;
+ // 用户ID
+ userId?: number;
+ // 备注
+ comments?: string;
+ // 排序号
+ sortNumber?: number;
+ // 是否删除
+ isDelete?: number;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+ // 修改时间
+ updateTime?: string;
+}
+
+/**
+ * 店员搜索条件
+ */
+export interface ShopStoreUserParam extends PageParam {
+ id?: number;
+ keywords?: string;
+ storeId?: number;
+ userId?: number;
+ isDelete?: number;
+}
diff --git a/src/api/shop/shopUser/model/index.ts b/src/api/shop/shopUser/model/index.ts
deleted file mode 100644
index 30a0e16..0000000
--- a/src/api/shop/shopUser/model/index.ts
+++ /dev/null
@@ -1,165 +0,0 @@
-import type { PageParam } from '@/api';
-
-/**
- * 用户记录表
- */
-export interface ShopUser {
- // 用户id
- userId?: number;
- // 用户类型 0个人用户 1企业用户 2其他
- type?: number;
- // 账号
- username?: string;
- // 密码
- password?: string;
- // 昵称
- nickname?: string;
- // 手机号
- phone?: string;
- // 性别 1男 2女
- sex?: number;
- // 职务
- position?: string;
- // 注册来源客户端 (APP、H5、MP-WEIXIN等)
- platform?: string;
- // 邮箱
- email?: string;
- // 邮箱是否验证, 0否, 1是
- emailVerified?: number;
- // 别名
- alias?: string;
- // 真实姓名
- realName?: string;
- // 证件号码
- idCard?: string;
- // 出生日期
- birthday?: string;
- // 所在国家
- country?: string;
- // 所在省份
- province?: string;
- // 所在城市
- city?: string;
- // 所在辖区
- region?: string;
- // 街道地址
- address?: string;
- // 经度
- longitude?: string;
- // 纬度
- latitude?: string;
- // 用户可用余额
- balance?: string;
- // 已提现金额
- cashedMoney?: string;
- // 用户可用积分
- points?: number;
- // 用户总支付的金额
- payMoney?: string;
- // 实际消费的金额(不含退款)
- expendMoney?: string;
- // 密码
- payPassword?: string;
- // 会员等级ID
- gradeId?: number;
- // 行业分类
- category?: string;
- // 个人简介
- introduction?: string;
- // 机构id
- organizationId?: number;
- // 会员分组ID
- groupId?: number;
- // 头像
- avatar?: string;
- // 背景图
- bgImage?: string;
- // 用户编码
- userCode?: string;
- // 是否已实名认证
- certification?: number;
- // 年龄
- age?: number;
- // 是否线下会员
- offline?: string;
- // 关注数
- followers?: number;
- // 粉丝数
- fans?: number;
- // 点赞数
- likes?: number;
- // 评论数
- commentNumbers?: number;
- // 是否推荐
- recommend?: number;
- // 微信openid
- openid?: string;
- // 微信公众号openid
- officeOpenid?: string;
- // 微信unionID
- unionid?: string;
- // 客户端ID
- clientId?: string;
- // 不允许办卡
- notAllowVip?: string;
- // 是否管理员
- isAdmin?: string;
- // 是否企业管理员
- isOrganizationAdmin?: string;
- // 累计登录次数
- loginNum?: number;
- // 企业ID
- companyId?: number;
- // 可管理的场馆
- merchants?: string;
- // 商户ID
- merchantId?: number;
- // 商户名称
- merchantName?: string;
- // 商户头像
- merchantAvatar?: string;
- // 第三方系统的用户ID
- uid?: number;
- // 专家角色
- expertType?: string;
- // 过期时间
- expireTime?: number;
- // 最后结算时间
- settlementTime?: string;
- // 资质
- aptitude?: string;
- // 行业类型(父级)
- industryParent?: string;
- // 行业类型(子级)
- industryChild?: string;
- // 头衔
- title?: string;
- // 安装的产品ID
- templateId?: number;
- // 插件安装状态(仅对超超管判断) 0未安装 1已安装
- installed?: number;
- // 特长
- speciality?: string;
- // 备注
- comments?: string;
- // 状态, 0在线, 1离线
- status?: number;
- // 是否删除, 0否, 1是
- deleted?: number;
- // 租户id
- tenantId?: number;
- // 注册时间
- createTime?: string;
- // 修改时间
- updateTime?: string;
- // 上传证件1
- uploadImg1?: string;
-}
-
-/**
- * 用户记录表搜索条件
- */
-export interface ShopUserParam extends PageParam {
- userId?: number;
- keywords?: string;
-}
diff --git a/src/api/shop/shopUserAddress/model/index.ts b/src/api/shop/shopUserAddress/model/index.ts
index fff978c..9bf7f50 100644
--- a/src/api/shop/shopUserAddress/model/index.ts
+++ b/src/api/shop/shopUserAddress/model/index.ts
@@ -1,4 +1,4 @@
-import type { PageParam } from '@/api/index';
+import type { PageParam } from '@/api';
/**
* 收货地址
diff --git a/src/api/shop/shopWarehouse/index.ts b/src/api/shop/shopWarehouse/index.ts
new file mode 100644
index 0000000..59a5f6e
--- /dev/null
+++ b/src/api/shop/shopWarehouse/index.ts
@@ -0,0 +1,101 @@
+import request from '@/utils/request';
+import type { ApiResult, PageResult } from '@/api';
+import type { ShopWarehouse, ShopWarehouseParam } from './model';
+
+/**
+ * 分页查询仓库
+ */
+export async function pageShopWarehouse(params: ShopWarehouseParam) {
+ const res = await request.get>>(
+ '/shop/shop-warehouse/page',
+ params
+ );
+ if (res.code === 0) {
+ return res.data;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 查询仓库列表
+ */
+export async function listShopWarehouse(params?: ShopWarehouseParam) {
+ const res = await request.get>(
+ '/shop/shop-warehouse',
+ params
+ );
+ if (res.code === 0 && res.data) {
+ return res.data;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 添加仓库
+ */
+export async function addShopWarehouse(data: ShopWarehouse) {
+ const res = await request.post>(
+ '/shop/shop-warehouse',
+ data
+ );
+ if (res.code === 0) {
+ return res.message;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 修改仓库
+ */
+export async function updateShopWarehouse(data: ShopWarehouse) {
+ const res = await request.put>(
+ '/shop/shop-warehouse',
+ data
+ );
+ if (res.code === 0) {
+ return res.message;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 删除仓库
+ */
+export async function removeShopWarehouse(id?: number) {
+ const res = await request.del>(
+ '/shop/shop-warehouse/' + id
+ );
+ if (res.code === 0) {
+ return res.message;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 批量删除仓库
+ */
+export async function removeBatchShopWarehouse(data: (number | undefined)[]) {
+ const res = await request.del>(
+ '/shop/shop-warehouse/batch',
+ {
+ data
+ }
+ );
+ if (res.code === 0) {
+ return res.message;
+ }
+ return Promise.reject(new Error(res.message));
+}
+
+/**
+ * 根据id查询仓库
+ */
+export async function getShopWarehouse(id: number) {
+ const res = await request.get>(
+ '/shop/shop-warehouse/' + id
+ );
+ if (res.code === 0 && res.data) {
+ return res.data;
+ }
+ return Promise.reject(new Error(res.message));
+}
diff --git a/src/api/shop/shopWarehouse/model/index.ts b/src/api/shop/shopWarehouse/model/index.ts
new file mode 100644
index 0000000..4b96a81
--- /dev/null
+++ b/src/api/shop/shopWarehouse/model/index.ts
@@ -0,0 +1,53 @@
+import type { PageParam } from '@/api';
+
+/**
+ * 仓库
+ */
+export interface ShopWarehouse {
+ // 自增ID
+ id?: number;
+ // 仓库名称
+ name?: string;
+ // 唯一标识
+ code?: string;
+ // 类型 中心仓,区域仓,门店仓
+ type?: string;
+ // 仓库地址
+ address?: string;
+ // 真实姓名
+ realName?: string;
+ // 联系电话
+ phone?: string;
+ // 省份
+ province?: string;
+ // 城市
+ city: undefined,
+ // 区域
+ region: undefined,
+ // 经纬度
+ lngAndLat?: string;
+ // 用户ID
+ userId?: number;
+ // 备注
+ comments?: string;
+ // 排序号
+ sortNumber?: number;
+ // 是否删除
+ isDelete?: number;
+ // 状态
+ status?: number;
+ // 租户id
+ tenantId?: number;
+ // 创建时间
+ createTime?: string;
+ // 修改时间
+ updateTime?: string;
+}
+
+/**
+ * 仓库搜索条件
+ */
+export interface ShopWarehouseParam extends PageParam {
+ id?: number;
+ keywords?: string;
+}
diff --git a/src/api/system/dict/index.ts b/src/api/system/dict/index.ts
index a017f6a..0076c39 100644
--- a/src/api/system/dict/index.ts
+++ b/src/api/system/dict/index.ts
@@ -9,7 +9,9 @@ import {SERVER_API_URL} from "@/utils/server";
export async function listDictionaries(params?: DictParam) {
const res = await request.get>(
SERVER_API_URL + '/system/dict',
- params
+ {
+ params
+ }
);
if (res.code === 0) {
return res.data;
diff --git a/src/api/system/userRole/index.ts b/src/api/system/userRole/index.ts
index 3dfa2e1..ecaefcc 100644
--- a/src/api/system/userRole/index.ts
+++ b/src/api/system/userRole/index.ts
@@ -30,3 +30,18 @@ export async function updateUserRole(data: UserRole) {
}
return Promise.reject(new Error(res.message));
}
+
+/**
+ * 新增用户角色
+ * 说明:部分后端实现为 POST 新增、PUT 修改;这里补齐 API 以便新用户无角色时可以创建默认角色。
+ */
+export async function addUserRole(data: UserRole) {
+ const res = await request.post>(
+ SERVER_API_URL + '/system/user-role',
+ data
+ );
+ if (res.code === 0) {
+ return res.message;
+ }
+ return Promise.reject(new Error(res.message));
+}
diff --git a/src/api/user/index.ts b/src/api/user/index.ts
index 7189f58..8f8b3c6 100644
--- a/src/api/user/index.ts
+++ b/src/api/user/index.ts
@@ -43,6 +43,15 @@ export interface UserOrderStats {
total: number
}
+// 用户卡片统计(个人中心头部:余额/积分/优惠券/水票)
+export interface UserCardStats {
+ balance: string
+ points: number
+ coupons: number
+ giftCards: number
+ lastUpdateTime?: string
+}
+
// 用户完整数据
export interface UserDashboard {
balance: UserBalance
@@ -108,6 +117,17 @@ export async function getUserOrderStats() {
return Promise.reject(new Error(res.message))
}
+/**
+ * 获取用户卡片统计(一次性返回余额/积分/可用优惠券/未使用礼品卡数量)
+ */
+export async function getUserCardStats() {
+ const res = await request.get>('/user/card/stats')
+ if (res.code === 0 && res.data) {
+ return res.data
+ }
+ return Promise.reject(new Error(res.message))
+}
+
/**
* 获取用户完整仪表板数据(一次性获取所有数据)
*/
diff --git a/src/app.config.ts b/src/app.config.ts
index ae304e4..67a1f2b 100644
--- a/src/app.config.ts
+++ b/src/app.config.ts
@@ -4,14 +4,13 @@ export default {
'pages/cart/cart',
'pages/find/find',
'pages/user/user',
- 'pages/category/category'
+ 'pages/category/index'
],
"subpackages": [
{
"root": "passport",
"pages": [
"login",
- "register",
"forget",
"setting",
"agreement",
@@ -34,12 +33,6 @@ export default {
"index"
]
},
- {
- "root": "gift",
- "pages": [
- "index"
- ]
- },
{
"root": "user",
"pages": [
@@ -63,7 +56,9 @@ export default {
"gift/index",
"gift/redeem",
"gift/detail",
+ "gift/add",
"store/verification",
+ "store/orders/index",
"theme/index",
"poster/poster",
"chat/conversation/index",
@@ -73,20 +68,17 @@ export default {
]
},
{
- "root": "doctor",
+ "root": "dealer",
"pages": [
"index",
"apply/add",
"withdraw/index",
"orders/index",
- "orders/add",
+ "capital/index",
"team/index",
"qrcode/index",
"invite-stats/index",
- "info",
- "customer/index",
- "customer/add",
- "customer/trading",
+ "info"
]
},
{
@@ -97,7 +89,21 @@ export default {
'goodsDetail/index',
'orderConfirm/index',
'orderConfirmCart/index',
- 'search/index'
+ 'comments/index',
+ 'search/index']
+ },
+ {
+ "root": "store",
+ "pages": [
+ "index",
+ "orders/index"
+ ]
+ },
+ {
+ "root": "rider",
+ "pages": [
+ "index",
+ "orders/index"
]
},
{
@@ -126,12 +132,6 @@ export default {
selectedIconPath: "assets/tabbar/home-active.png",
text: "首页",
},
- {
- pagePath: "pages/category/category",
- iconPath: "assets/tabbar/category.png",
- selectedIconPath: "assets/tabbar/category-active.png",
- text: "分类",
- },
{
pagePath: "pages/cart/cart",
iconPath: "assets/tabbar/cart.png",
@@ -154,6 +154,9 @@ export default {
permission: {
"scope.userLocation": {
"desc": "你的位置信息将用于小程序位置接口的效果展示"
+ },
+ "scope.writePhotosAlbum": {
+ "desc": "用于保存小程序码到相册,方便分享给好友"
}
}
}
diff --git a/src/assets/tabbar/category-active.png b/src/assets/tabbar/category-active.png
index eebe3d5..a7431dc 100644
Binary files a/src/assets/tabbar/category-active.png and b/src/assets/tabbar/category-active.png differ
diff --git a/src/assets/tabbar/category.png b/src/assets/tabbar/category.png
index 1f955de..cc56063 100644
Binary files a/src/assets/tabbar/category.png and b/src/assets/tabbar/category.png differ
diff --git a/src/assets/tabbar/tv-active.png b/src/assets/tabbar/tv-active.png
deleted file mode 100644
index 4852b4f..0000000
Binary files a/src/assets/tabbar/tv-active.png and /dev/null differ
diff --git a/src/assets/tabbar/tv.png b/src/assets/tabbar/tv.png
deleted file mode 100644
index 2dd6bd0..0000000
Binary files a/src/assets/tabbar/tv.png and /dev/null differ
diff --git a/src/cms/category/components/ArticleList.tsx b/src/cms/category/components/ArticleList.tsx
index ffe5d29..cde08b4 100644
--- a/src/cms/category/components/ArticleList.tsx
+++ b/src/cms/category/components/ArticleList.tsx
@@ -1,25 +1,16 @@
import {Image, Cell} from '@nutui/nutui-react-taro'
-import {View, Text} from '@tarojs/components'
import Taro from '@tarojs/taro'
+import {CmsArticle} from "@/api/cms/cmsArticle/model";
const ArticleList = (props: any) => {
return (
<>
-
- {props.data.map((item: any, index: number) => {
+
+ {props.data.map((item: CmsArticle, index: number) => {
return (
|
- {item.title}
- {item.comments && (
-
- {item.comments}
-
- )}
-
- }
+ title={item.title}
extra={
}
@@ -28,7 +19,7 @@ const ArticleList = (props: any) => {
/>
)
})}
-
+ |
>
)
}
diff --git a/src/cms/category/index.tsx b/src/cms/category/index.tsx
index 0ac2267..75a784b 100644
--- a/src/cms/category/index.tsx
+++ b/src/cms/category/index.tsx
@@ -44,7 +44,7 @@ function Category() {
useShareAppMessage(() => {
return {
- title: `${nav?.categoryName}_WebSoft Inc.`,
+ title: `${nav?.categoryName}_桂乐淘`,
path: `/shop/category/index?id=${categoryId}`,
success: function () {
console.log('分享成功');
diff --git a/src/cms/detail/index.tsx b/src/cms/detail/index.tsx
index f92c890..8d0c717 100644
--- a/src/cms/detail/index.tsx
+++ b/src/cms/detail/index.tsx
@@ -17,7 +17,7 @@ function Detail() {
const reload = async () => {
const item = await getCmsArticle(Number(params.id))
- if (item) {
+ if (item && item.content) {
item.content = wxParse(item.content)
setItem(item)
Taro.setNavigationBarTitle({
@@ -43,6 +43,10 @@ function Detail() {
{item?.title}
{item?.createTime}
+ {/*如果有视频就显示视频 视频沾满宽度*/}
+ {item?.video &&
+
+ }
diff --git a/src/components/GiftCard.tsx b/src/components/GiftCard.tsx
index b6f89e6..55a9824 100644
--- a/src/components/GiftCard.tsx
+++ b/src/components/GiftCard.tsx
@@ -24,7 +24,7 @@ export interface GiftCardProps {
faceValue?: string
/** 商品原价 */
originalPrice?: string
- /** 礼品卡类型:10-实物礼品卡 20-虚拟礼品卡 30-服务礼品卡 */
+ /** 礼品卡类型:10-礼品劵 20-虚拟礼品卡 30-服务礼品卡 */
type?: number
/** 状态:0-未使用 1-已使用 2-失效 */
status?: number
@@ -112,10 +112,10 @@ const GiftCard: React.FC = ({
// 获取礼品卡类型文本
const getTypeText = () => {
switch (type) {
- case 10: return '实物礼品卡'
+ case 10: return '礼品劵'
case 20: return '虚拟礼品卡'
case 30: return '服务礼品卡'
- default: return '礼品卡'
+ default: return '水票'
}
}
diff --git a/src/components/GiftCardGuide.tsx b/src/components/GiftCardGuide.tsx
index 6b1db57..b1e7eb1 100644
--- a/src/components/GiftCardGuide.tsx
+++ b/src/components/GiftCardGuide.tsx
@@ -51,7 +51,7 @@ const GiftCardGuide: React.FC = ({
title: '礼品卡类型说明',
icon: ,
content: [
- '🎁 实物礼品卡:需到指定地址领取商品',
+ '🎁 礼品劵:需到指定地址领取商品',
'💻 虚拟礼品卡:自动发放到账户余额',
'🛎️ 服务礼品卡:联系客服预约服务',
'⏰ 注意查看有效期,过期无法使用'
diff --git a/src/components/GiftCardShare.tsx b/src/components/GiftCardShare.tsx
index ea4e217..0d29302 100644
--- a/src/components/GiftCardShare.tsx
+++ b/src/components/GiftCardShare.tsx
@@ -28,10 +28,10 @@ const GiftCardShare: React.FC = ({
// 获取礼品卡类型文本
const getTypeText = () => {
switch (giftCard.type) {
- case 10: return '实物礼品卡'
+ case 10: return '礼品劵'
case 20: return '虚拟礼品卡'
case 30: return '服务礼品卡'
- default: return '礼品卡'
+ default: return '水票'
}
}
diff --git a/src/components/GoodsList.tsx b/src/components/GoodsList.tsx
index 1ada7ca..3777b6a 100644
--- a/src/components/GoodsList.tsx
+++ b/src/components/GoodsList.tsx
@@ -1,174 +1,162 @@
-import {useEffect, useState} from "react";
-import {Image, Tabs, Empty, Sticky} from '@nutui/nutui-react-taro'
-import {Share} from '@nutui/icons-react-taro'
-import {View, Text} from '@tarojs/components';
-import Taro from "@tarojs/taro";
-import {ShopGoods} from "@/api/shop/shopGoods/model";
-import {pageShopGoods} from "@/api/shop/shopGoods";
+import {Avatar, Cell, Space, Tabs, Button, TabPane, Swiper} from '@nutui/nutui-react-taro'
+import {useEffect, useState, CSSProperties, useRef} from "react";
+import {BszxPay} from "@/api/bszx/bszxPay/model";
+import {InfiniteLoading} from '@nutui/nutui-react-taro'
+import dayjs from "dayjs";
+import {pageShopOrder} from "@/api/shop/shopOrder";
+import {ShopOrder} from "@/api/shop/shopOrder/model";
+import {copyText} from "@/utils/common";
-const BestSellers = (props: {onStickyChange?: (isSticky: boolean) => void}) => {
- const [tab1value, setTab1value] = useState('0')
- const [list, setList] = useState([])
- const [goods, setGoods] = useState()
- const [stickyStatus, setStickyStatus] = useState(false)
+const InfiniteUlStyle: CSSProperties = {
+ marginTop: '84px',
+ height: '82vh',
+ width: '100%',
+ padding: '0',
+ overflowY: 'auto',
+ overflowX: 'hidden',
+}
+const tabs = [
+ {
+ index: 0,
+ key: '全部',
+ title: '全部'
+ },
+ {
+ index: 1,
+ key: '已上架',
+ title: '已上架'
+ },
+ {
+ index: 2,
+ key: '已下架',
+ title: '已下架'
+ },
+ {
+ index: 3,
+ key: '已售罄',
+ title: '已售罄'
+ },
+ {
+ index: 4,
+ key: '警戒库存',
+ title: '警戒库存'
+ },
+ {
+ index: 5,
+ key: '回收站',
+ title: '回收站'
+ },
+]
- const reload = () => {
- pageShopGoods({}).then(res => {
- setList(res?.list || []);
+function GoodsList(props: any) {
+ const [list, setList] = useState([])
+ const [page, setPage] = useState(1)
+ const [hasMore, setHasMore] = useState(true)
+ const swiperRef = useRef | null>(null)
+ const [tabIndex, setTabIndex] = useState(0)
+
+ console.log(props.statusBarHeight, 'ppp')
+ const reload = async () => {
+ pageShopOrder({page}).then(res => {
+ let newList: BszxPay[] | undefined = []
+ if (res?.list && res?.list.length > 0) {
+ newList = list?.concat(res.list)
+ setHasMore(true)
+ } else {
+ newList = res?.list
+ setHasMore(false)
+ }
+ setList(newList || []);
})
}
- // 处理分享点击
- const handleShare = (item: ShopGoods) => {
- setGoods(item);
-
- console.log(goods)
-
- // 显示分享选项菜单
- Taro.showActionSheet({
- itemList: ['分享给好友'],
- success: (res) => {
- if (res.tapIndex === 0) {
- // 分享给好友 - 触发转发
- Taro.showShareMenu({
- withShareTicket: true,
- success: () => {
- // 提示用户点击右上角分享
- Taro.showToast({
- title: '请点击右上角分享给好友',
- icon: 'none',
- duration: 2000
- });
- }
- });
- }
- },
- fail: (err) => {
- console.log('显示分享菜单失败', err);
- }
- });
- }
-
- // 处理粘性布局状态变化
- const onStickyChange = (isSticky: boolean) => {
- setStickyStatus(isSticky)
- // 通知父组件粘性状态变化
- props.onStickyChange?.(isSticky)
- console.log('Tabs 粘性状态:', isSticky ? '已固定' : '取消固定')
- }
-
- // 获取小程序系统信息
- const getSystemInfo = () => {
- const systemInfo = Taro.getSystemInfoSync()
- // 状态栏高度 + 导航栏高度 (一般为44px)
- return (systemInfo.statusBarHeight || 0) + 44
+ const reloadMore = async () => {
+ setPage(page + 1)
+ reload().then();
}
useEffect(() => {
- reload()
+ setPage(2)
+ reload().then()
}, [])
return (
<>
-
- {/* Tabs粘性布局组件 */}
-
- {
- setTab1value(value)
- }}
- style={{
- backgroundColor: 'transparent',
- paddingTop: stickyStatus ? '0' : '8px',
- paddingBottom: stickyStatus ? '8px' : '0',
- }}
- activeType="smile"
- >
-
-
-
-
-
-
-
-
+ {
+ swiperRef.current?.to(page)
+ setTabIndex(page)
+ }}
+ >
+ {
+ tabs?.map((item, index) => {
+ return
+ })
+ }
+
+
>
)
}
-export default BestSellers
+
+export default GoodsList
diff --git a/src/components/SimpleQRCodeModal.tsx b/src/components/SimpleQRCodeModal.tsx
index 167c8cc..1acde23 100644
--- a/src/components/SimpleQRCodeModal.tsx
+++ b/src/components/SimpleQRCodeModal.tsx
@@ -81,7 +81,7 @@ const SimpleQRCodeModal: React.FC = ({
{qrContent ? (
{
- const {user, loginUser} = useUser()
+const AddUserAddress = () => {
+ const {user, loginUser, fetchUserInfo} = useUser()
const [loading, setLoading] = useState(true)
const [FormData, setFormData] = useState()
const formRef = useRef(null)
@@ -127,7 +129,7 @@ const AddDoctor = () => {
}
// 提交表单
- const submitSucceed = async (values: any) => {
+ const submitSucceed = async (values: User) => {
try {
// 验证必填字段
if (!values.phone && !FormData?.phone) {
@@ -176,12 +178,27 @@ const AddDoctor = () => {
}
console.log(values,FormData)
- const roles = await listUserRole({userId: user?.userId})
- console.log(roles, 'roles...')
+ if (!user?.userId) {
+ Taro.showToast({
+ title: '用户信息缺失,请先登录',
+ icon: 'error'
+ });
+ return;
+ }
+
+ let roles: UserRole[] = [];
+ try {
+ roles = await listUserRole({userId: user.userId})
+ console.log(roles, 'roles...')
+ } catch (e) {
+ // 新用户/权限限制时可能查不到角色列表,不影响基础注册流程
+ console.warn('查询用户角色失败,将尝试直接写入默认角色:', e)
+ roles = []
+ }
// 准备提交的数据
await updateUser({
- userId: user?.userId,
+ userId: user.userId,
nickname: values.realName || FormData?.nickname,
phone: values.phone || FormData?.phone,
avatar: values.avatar || FormData?.avatar,
@@ -189,17 +206,52 @@ const AddDoctor = () => {
});
await addShopDealerUser({
- userId: user?.userId,
+ userId: user.userId,
realName: values.realName || FormData?.nickname,
mobile: values.phone || FormData?.phone,
- refereeId: values.refereeId || FormData?.refereeId
+ refereeId: Number(values.refereeId) || Number(FormData?.refereeId)
})
- if (roles.length > 0) {
- await updateUserRole({
- ...roles[0],
- roleId: 1848
- })
+ // 角色为空时这里会导致“注册成功但没有角色”,这里做一次兜底写入默认 user 角色
+ try {
+ // 1) 先尝试通过 roleCode=user 查询角色ID(避免硬编码)
+ // 2) 取不到就回退到旧的默认ID(1848)
+ let userRoleId: number | undefined;
+ try {
+ // 注意:当前 request.get 的封装不支持 axios 风格的 { params: ... },
+ // 某些自动生成的 API 可能无法按参数过滤;这里直接取全量再本地查找更稳。
+ const roleList = await listRoles();
+ userRoleId = roleList?.find(r => r.roleCode === 'user')?.roleId;
+ } catch (_) {
+ // ignore
+ }
+ if (!userRoleId) userRoleId = 1848;
+
+ const baseRolePayload = {
+ userId: user.userId,
+ tenantId: Number(TenantId),
+ roleId: userRoleId
+ };
+
+ // 后端若已创建 user-role 记录则更新;否则尝试“无id更新”触发创建(多数实现会 upsert)
+ if (roles.length > 0) {
+ await updateUserRole({
+ ...roles[0],
+ roleId: userRoleId
+ });
+ } else {
+ try {
+ await addUserRole(baseRolePayload);
+ } catch (_) {
+ // 兼容后端仅支持 PUT upsert 的情况
+ await updateUserRole(baseRolePayload);
+ }
+ }
+
+ // 刷新一次用户信息,确保 roles 写回本地缓存,避免“我的”页显示为空/不一致
+ await fetchUserInfo();
+ } catch (e) {
+ console.warn('写入默认角色失败(不影响注册成功):', e)
}
@@ -209,7 +261,8 @@ const AddDoctor = () => {
});
setTimeout(() => {
- Taro.navigateBack();
+ // “我的”是 tabBar 页面,注册完成后直接切到“我的”
+ Taro.switchTab({ url: '/pages/user/user' });
}, 1000);
} catch (error) {
@@ -382,9 +435,9 @@ const AddDoctor = () => {
>
-
-
-
+ {/**/}
+ {/* */}
+ {/**/}
{
);
};
-export default AddDoctor;
+export default AddUserAddress;
diff --git a/src/dealer/capital/index.config.ts b/src/dealer/capital/index.config.ts
new file mode 100644
index 0000000..8e2e153
--- /dev/null
+++ b/src/dealer/capital/index.config.ts
@@ -0,0 +1,4 @@
+export default definePageConfig({
+ navigationBarTitleText: '收益明细'
+})
+
diff --git a/src/dealer/capital/index.scss b/src/dealer/capital/index.scss
new file mode 100644
index 0000000..5e9d65f
--- /dev/null
+++ b/src/dealer/capital/index.scss
@@ -0,0 +1,2 @@
+/* Intentionally empty: styling is done via utility classes. */
+
diff --git a/src/dealer/capital/index.tsx b/src/dealer/capital/index.tsx
new file mode 100644
index 0000000..b599fb9
--- /dev/null
+++ b/src/dealer/capital/index.tsx
@@ -0,0 +1,199 @@
+import React, {useCallback, useEffect, useState} from 'react'
+import {View, Text, ScrollView} from '@tarojs/components'
+import {Empty, Tag, PullToRefresh, Loading} from '@nutui/nutui-react-taro'
+import Taro from '@tarojs/taro'
+import {pageShopDealerCapital} from '@/api/shop/shopDealerCapital'
+import {useDealerUser} from '@/hooks/useDealerUser'
+import type {ShopDealerCapital} from '@/api/shop/shopDealerCapital/model'
+
+const PAGE_SIZE = 10
+
+const DealerCapital: React.FC = () => {
+ const {dealerUser} = useDealerUser()
+
+ const [loading, setLoading] = useState(false)
+ const [refreshing, setRefreshing] = useState(false)
+ const [loadingMore, setLoadingMore] = useState(false)
+ const [currentPage, setCurrentPage] = useState(1)
+ const [hasMore, setHasMore] = useState(true)
+ const [records, setRecords] = useState([])
+
+ const getFlowTypeText = (flowType?: number) => {
+ switch (flowType) {
+ case 10:
+ return '佣金收入'
+ case 20:
+ return '提现支出'
+ case 30:
+ return '转账支出'
+ case 40:
+ return '转账收入'
+ default:
+ return '资金变动'
+ }
+ }
+
+ const getFlowTypeTag = (flowType?: number) => {
+ // 收入:success;支出:danger;其它:default
+ if (flowType === 10 || flowType === 40) return 'success'
+ if (flowType === 20 || flowType === 30) return 'danger'
+ return 'default'
+ }
+
+ const formatMoney = (flowType?: number, money?: string) => {
+ const isIncome = flowType === 10 || flowType === 40
+ const isExpense = flowType === 20 || flowType === 30
+ const sign = isIncome ? '+' : isExpense ? '-' : ''
+ return `${sign}${money || '0.00'}`
+ }
+
+ const fetchRecords = useCallback(async (page: number = 1, isRefresh: boolean = false) => {
+ if (!dealerUser?.userId) return
+
+ try {
+ if (isRefresh) {
+ setRefreshing(true)
+ } else if (page === 1) {
+ setLoading(true)
+ } else {
+ setLoadingMore(true)
+ }
+
+ const result = await pageShopDealerCapital({
+ page,
+ limit: PAGE_SIZE,
+ // 只显示与当前登录用户相关的收益明细
+ userId: dealerUser.userId
+ })
+
+ const list = result?.list || []
+ if (page === 1) {
+ setRecords(list)
+ } else {
+ setRecords(prev => [...prev, ...list])
+ }
+
+ setHasMore(list.length === PAGE_SIZE)
+ setCurrentPage(page)
+ } catch (error) {
+ console.error('获取收益明细失败:', error)
+ Taro.showToast({
+ title: '获取收益明细失败',
+ icon: 'error'
+ })
+ } finally {
+ setLoading(false)
+ setRefreshing(false)
+ setLoadingMore(false)
+ }
+ }, [dealerUser?.userId])
+
+ const handleRefresh = async () => {
+ await fetchRecords(1, true)
+ }
+
+ const handleLoadMore = async () => {
+ if (!loadingMore && hasMore) {
+ await fetchRecords(currentPage + 1)
+ }
+ }
+
+ useEffect(() => {
+ if (dealerUser?.userId) {
+ fetchRecords(1)
+ }
+ }, [fetchRecords, dealerUser?.userId])
+
+ if (!dealerUser) {
+ return (
+
+
+ 加载中...
+
+ )
+ }
+
+ return (
+
+
+
+
+ {loading && records.length === 0 ? (
+
+
+ 加载中...
+
+ ) : records.length > 0 ? (
+ <>
+ {records.map((item) => (
+
+
+
+ {item.describe || '收益明细'}
+
+
+ {getFlowTypeText(item.flowType)}
+
+
+
+
+
+ 佣金收入
+
+
+ {formatMoney(item.flowType, item.money)}
+
+
+
+
+
+ {/*用户:{item.userId ?? '-'}*/}
+
+
+ {item.createTime || '-'}
+
+
+
+ ))}
+
+ {loadingMore && (
+
+
+ 加载更多...
+
+ )}
+ {!hasMore && records.length > 0 && (
+
+ 没有更多数据了
+
+ )}
+ >
+ ) : (
+
+ )}
+
+
+
+
+ )
+}
+
+export default DealerCapital
diff --git a/src/dealer/index.config.ts b/src/dealer/index.config.ts
new file mode 100644
index 0000000..d456dbd
--- /dev/null
+++ b/src/dealer/index.config.ts
@@ -0,0 +1,3 @@
+export default definePageConfig({
+ navigationBarTitleText: '分销中心'
+})
diff --git a/src/doctor/index.scss b/src/dealer/index.scss
similarity index 100%
rename from src/doctor/index.scss
rename to src/dealer/index.scss
diff --git a/src/doctor/index.tsx b/src/dealer/index.tsx
similarity index 60%
rename from src/doctor/index.tsx
rename to src/dealer/index.tsx
index 834cb7e..bdf8b3e 100644
--- a/src/doctor/index.tsx
+++ b/src/dealer/index.tsx
@@ -3,18 +3,15 @@ import {View, Text} from '@tarojs/components'
import {ConfigProvider, Button, Grid, Avatar} from '@nutui/nutui-react-taro'
import {
User,
- UserAdd,
- Edit,
- Comment,
- QrCode,
- Notice,
- Orderlist,
- Health,
- PickedUp
+ Shopping,
+ Dongdong,
+ ArrowRight,
+ Purse,
+ People
} from '@nutui/icons-react-taro'
import {useDealerUser} from '@/hooks/useDealerUser'
import { useThemeStyles } from '@/hooks/useTheme'
-import {gradientUtils} from '@/styles/gradients'
+import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients'
import Taro from '@tarojs/taro'
const DealerIndex: React.FC = () => {
@@ -33,10 +30,10 @@ const DealerIndex: React.FC = () => {
}
// 格式化金额
- // const formatMoney = (money?: string) => {
- // if (!money) return '0.00'
- // return parseFloat(money).toFixed(2)
- // }
+ const formatMoney = (money?: string) => {
+ if (!money) return '0.00'
+ return parseFloat(money).toFixed(2)
+ }
// 格式化时间
const formatTime = (time?: string) => {
@@ -58,18 +55,18 @@ const DealerIndex: React.FC = () => {
console.log(getGradientBackground(),'getGradientBackground()')
- // if (error) {
- // return (
- //
- //
- // {error}
- //
- //
- //
- // )
- // }
+ if (error) {
+ return (
+
+
+ {error}
+
+
+
+ )
+ }
return (
@@ -106,12 +103,12 @@ const DealerIndex: React.FC = () => {
- {dealerUser?.realName || '医生名称'}
+ {dealerUser?.realName || '分销商'}
- 医生编号: {dealerUser.userId}
+ ID: {dealerUser.userId}
@@ -128,9 +125,80 @@ const DealerIndex: React.FC = () => {
)}
+ {/* 佣金统计卡片 */}
+ {dealerUser && (
+
+
+ 佣金统计
+
+
+
+
+ {formatMoney(dealerUser.money)}
+
+ 可提现
+
+
+
+ {formatMoney(dealerUser.freezeMoney)}
+
+ 冻结中
+
+
+
+ {formatMoney(dealerUser.totalMoney)}
+
+ 累计收益
+
+
+
+ )}
+
+ {/* 团队统计 */}
+ {dealerUser && (
+
+
+ 我的邀请
+ navigateToPage('/dealer/team/index')}
+ >
+ 查看详情
+
+
+
+
+
+
+ {dealerUser.firstNum || 0}
+
+ 一级成员
+
+
+
+ {dealerUser.secondNum || 0}
+
+ 二级成员
+
+
+
+ {dealerUser.thirdNum || 0}
+
+ 三级成员
+
+
+
+ )}
+
{/* 功能导航 */}
- 管理工具
+ 分销工具
{
border: 'none'
} as React.CSSProperties}
>
- navigateToPage('/doctor/customer/index')}>
+ navigateToPage('/dealer/orders/index')}>
-
+
- navigateToPage('/doctor/orders/add')}>
+ navigateToPage('/dealer/withdraw/index')}>
-
+
- navigateToPage('/doctor/team/index')}>
+ navigateToPage('/dealer/team/index')}>
-
+
- navigateToPage('/doctor/orders/index')}>
+ navigateToPage('/dealer/qrcode/index')}>
-
+
-
-
-
-
-
-
-
-
-
- navigateToPage('/doctor/team/index')}>
-
-
-
-
-
-
-
- navigateToPage('/doctor/qrcode/index')}>
-
-
-
-
-
-
-
- navigateToPage('/doctor/apply/add')}>
-
-
-
-
-
-
-
{/* 第二行功能 */}
@@ -217,7 +252,7 @@ const DealerIndex: React.FC = () => {
{/* border: 'none'*/}
{/* } as React.CSSProperties}*/}
{/*>*/}
- {/* navigateToPage('/doctor/invite-stats/index')}>*/}
+ {/* navigateToPage('/dealer/invite-stats/index')}>*/}
{/* */}
{/* */}
{/* */}
diff --git a/src/doctor/info.tsx b/src/dealer/info.tsx
similarity index 99%
rename from src/doctor/info.tsx
rename to src/dealer/info.tsx
index 8e03a69..0f18841 100644
--- a/src/doctor/info.tsx
+++ b/src/dealer/info.tsx
@@ -15,7 +15,7 @@ const DealerInfo: React.FC = () => {
// 跳转到申请页面
const navigateToApply = () => {
Taro.navigateTo({
- url: '/pages/doctor/apply/add'
+ url: '/pages/dealer/apply/add'
})
}
diff --git a/src/doctor/invite-stats/index.config.ts b/src/dealer/invite-stats/index.config.ts
similarity index 100%
rename from src/doctor/invite-stats/index.config.ts
rename to src/dealer/invite-stats/index.config.ts
diff --git a/src/doctor/invite-stats/index.tsx b/src/dealer/invite-stats/index.tsx
similarity index 100%
rename from src/doctor/invite-stats/index.tsx
rename to src/dealer/invite-stats/index.tsx
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/doctor/orders/index.tsx b/src/dealer/orders/index.tsx
similarity index 79%
rename from src/doctor/orders/index.tsx
rename to src/dealer/orders/index.tsx
index 196e715..7fbe6d8 100644
--- a/src/doctor/orders/index.tsx
+++ b/src/dealer/orders/index.tsx
@@ -24,7 +24,8 @@ const DealerOrders: React.FC = () => {
// 获取订单数据
const fetchOrders = useCallback(async (page: number = 1, isRefresh: boolean = false) => {
- if (!dealerUser?.userId) return
+ // 需要当前登录用户ID(用于 resourceId 参数)
+ if (!dealerUser || !dealerUser.userId) return
try {
if (isRefresh) {
@@ -37,14 +38,17 @@ const DealerOrders: React.FC = () => {
const result = await pageShopDealerOrder({
page,
- limit: 10
+ limit: 10,
+ // 后端需要 resourceId=当前登录用户ID 才能正确过滤分销订单
+ resourceId: dealerUser.userId
})
if (result?.list) {
const newOrders = result.list.map(order => ({
...order,
- orderNo: `${order.orderId}`,
- customerName: `用户${order.userId}`,
+ // 优先使用接口返回的订单号;没有则降级展示 orderId
+ orderNo: order.orderNo ?? (order.orderId != null ? String(order.orderId) : undefined),
+ customerName: `${order.nickname}${order.userId}`,
userCommission: order.firstMoney || '0.00'
}))
@@ -102,37 +106,51 @@ const DealerOrders: React.FC = () => {
return 'warning'
}
+ const handleGoCapital = () => {
+ Taro.navigateTo({url: '/dealer/capital/index'})
+ }
+
const renderOrderItem = (order: OrderWithDetails) => (
-
+
- 订单号:{order.orderNo}
+ 订单号:{order.orderNo || '-'}
{getStatusText(order.isSettled, order.isInvalid)}
-
-
- 订单金额:¥{order.orderPrice || '0.00'}
-
-
- 我的佣金:¥{order.userCommission}
-
-
+ {/**/}
+ {/* */}
+ {/* 订单金额:¥{order.orderPrice || '0.00'}*/}
+ {/* */}
+ {/**/}
- 客户:{order.customerName}
+ {order.createTime}
- {order.createTime}
+ 订单金额:¥{order.orderPrice || '0.00'}
)
+ if (!dealerUser) {
+ return (
+
+
+ 加载中...
+
+ )
+ }
+
return (
{
)}
>
) : (
-
+
)}
diff --git a/src/doctor/qrcode/index.config.ts b/src/dealer/qrcode/index.config.ts
similarity index 100%
rename from src/doctor/qrcode/index.config.ts
rename to src/dealer/qrcode/index.config.ts
diff --git a/src/doctor/qrcode/index.tsx b/src/dealer/qrcode/index.tsx
similarity index 76%
rename from src/doctor/qrcode/index.tsx
rename to src/dealer/qrcode/index.tsx
index 268580a..5cf152d 100644
--- a/src/doctor/qrcode/index.tsx
+++ b/src/dealer/qrcode/index.tsx
@@ -11,6 +11,7 @@ import {businessGradients} from '@/styles/gradients'
const DealerQrcode: React.FC = () => {
const [miniProgramCodeUrl, setMiniProgramCodeUrl] = useState('')
const [loading, setLoading] = useState(false)
+ const [saving, setSaving] = useState(false)
// const [inviteStats, setInviteStats] = useState(null)
// const [statsLoading, setStatsLoading] = useState(false)
const {dealerUser} = useDealerUser()
@@ -67,6 +68,66 @@ const DealerQrcode: React.FC = () => {
}
}, [dealerUser?.userId])
+ const isAlbumAuthError = (errMsg?: string) => {
+ if (!errMsg) return false
+ // WeChat uses variants like: "saveImageToPhotosAlbum:fail auth deny",
+ // "saveImageToPhotosAlbum:fail auth denied", "authorize:fail auth deny"
+ return (
+ errMsg.includes('auth deny') ||
+ errMsg.includes('auth denied') ||
+ errMsg.includes('authorize') ||
+ errMsg.includes('scope.writePhotosAlbum')
+ )
+ }
+
+ const ensureWriteAlbumPermission = async (): Promise => {
+ try {
+ const setting = await Taro.getSetting()
+ if (setting?.authSetting?.['scope.writePhotosAlbum']) return true
+
+ await Taro.authorize({scope: 'scope.writePhotosAlbum'})
+ return true
+ } catch (error: any) {
+ const modal = await Taro.showModal({
+ title: '提示',
+ content: '需要您授权保存图片到相册,请在设置中开启相册权限',
+ confirmText: '去设置'
+ })
+ if (modal.confirm) {
+ await Taro.openSetting()
+ }
+ return false
+ }
+ }
+
+ const downloadImageToLocalPath = async (url: string): Promise => {
+ // saveImageToPhotosAlbum must receive a local temp path (e.g. `http://tmp/...` or `wxfile://...`).
+ // Some environments may return a non-existing temp path from getImageInfo, so we verify.
+ if (url.startsWith('http://tmp/') || url.startsWith('wxfile://')) {
+ return url
+ }
+
+ const token = Taro.getStorageSync('access_token')
+ const tenantId = Taro.getStorageSync('TenantId')
+ const header: Record = {}
+ if (token) header.Authorization = token
+ if (tenantId) header.TenantId = tenantId
+
+ // 先下载到本地临时文件再保存到相册
+ const res = await Taro.downloadFile({url, header})
+ if (res.statusCode !== 200 || !res.tempFilePath) {
+ throw new Error(`图片下载失败(${res.statusCode || 'unknown'})`)
+ }
+
+ // Double-check file exists to avoid: saveImageToPhotosAlbum:fail no such file or directory
+ try {
+ await Taro.getFileInfo({filePath: res.tempFilePath})
+ } catch (_) {
+ throw new Error('图片临时文件不存在,请重试')
+ }
+ return res.tempFilePath
+ }
+
// 保存小程序码到相册
const saveMiniProgramCode = async () => {
if (!miniProgramCodeUrl) {
@@ -78,39 +139,64 @@ const DealerQrcode: React.FC = () => {
}
try {
- // 先下载图片到本地
- const res = await Taro.downloadFile({
- url: miniProgramCodeUrl
- })
+ if (saving) return
+ setSaving(true)
+ Taro.showLoading({title: '保存中...'})
- if (res.statusCode === 200) {
- // 保存到相册
- await Taro.saveImageToPhotosAlbum({
- filePath: res.tempFilePath
- })
+ const hasPermission = await ensureWriteAlbumPermission()
+ if (!hasPermission) return
- Taro.showToast({
- title: '保存成功',
- icon: 'success'
- })
+ let filePath = await downloadImageToLocalPath(miniProgramCodeUrl)
+ try {
+ await Taro.saveImageToPhotosAlbum({filePath})
+ } catch (e: any) {
+ const msg = e?.errMsg || e?.message || ''
+ // Fallback: some devices/clients may fail to save directly from a temp path.
+ if (
+ msg.includes('no such file or directory') &&
+ (filePath.startsWith('http://tmp/') || filePath.startsWith('wxfile://'))
+ ) {
+ const saved = (await Taro.saveFile({tempFilePath: filePath})) as unknown as { savedFilePath?: string }
+ if (saved?.savedFilePath) {
+ filePath = saved.savedFilePath
+ }
+ await Taro.saveImageToPhotosAlbum({filePath})
+ } else {
+ throw e
+ }
}
+
+ Taro.showToast({
+ title: '保存成功',
+ icon: 'success'
+ })
} catch (error: any) {
- if (error.errMsg?.includes('auth deny')) {
- Taro.showModal({
+ const errMsg = error?.errMsg || error?.message
+ if (errMsg?.includes('cancel')) {
+ Taro.showToast({title: '已取消', icon: 'none'})
+ return
+ }
+
+ if (isAlbumAuthError(errMsg)) {
+ const modal = await Taro.showModal({
title: '提示',
content: '需要您授权保存图片到相册',
- success: (res) => {
- if (res.confirm) {
- Taro.openSetting()
- }
- }
+ confirmText: '去设置'
})
+ if (modal.confirm) {
+ await Taro.openSetting()
+ }
} else {
- Taro.showToast({
+ // Prefer a modal so we can show the real reason (e.g. domain whitelist / network error).
+ await Taro.showModal({
title: '保存失败',
- icon: 'error'
+ content: errMsg || '保存失败,请稍后重试',
+ showCancel: false
})
}
+ } finally {
+ Taro.hideLoading()
+ setSaving(false)
}
}
@@ -126,7 +212,7 @@ const DealerQrcode: React.FC = () => {
//
// const inviteText = `🎉 邀请您加入我的团队!
//
-// 扫描小程序码或搜索"九云售电云"小程序,即可享受优质商品和服务!
+// 扫描小程序码或搜索"网宿软件"小程序,即可享受优质商品和服务!
//
// 💰 成为我的团队成员,一起赚取丰厚佣金
// 🎁 新用户专享优惠等你来拿
@@ -258,7 +344,7 @@ const DealerQrcode: React.FC = () => {
block
icon={}
onClick={saveMiniProgramCode}
- disabled={!miniProgramCodeUrl || loading}
+ disabled={!miniProgramCodeUrl || loading || saving}
>
保存小程序码到相册
diff --git a/src/dealer/team/index.config.ts b/src/dealer/team/index.config.ts
new file mode 100644
index 0000000..ddd6b66
--- /dev/null
+++ b/src/dealer/team/index.config.ts
@@ -0,0 +1,3 @@
+export default definePageConfig({
+ navigationBarTitleText: '邀请推广'
+})
diff --git a/src/doctor/team/index.tsx b/src/dealer/team/index.tsx
similarity index 99%
rename from src/doctor/team/index.tsx
rename to src/dealer/team/index.tsx
index 36f6b14..af7d8e7 100644
--- a/src/doctor/team/index.tsx
+++ b/src/dealer/team/index.tsx
@@ -407,7 +407,7 @@ const DealerTeam: React.FC = () => {
navTo(`/doctor/apply/add`, true)}]}
+ }} actions={[{text: '立即申请', onClick: () => navTo(`/dealer/apply/add`, true)}]}
/>
)
@@ -431,7 +431,7 @@ const DealerTeam: React.FC = () => {
)}
- navTo(`/doctor/qrcode/index`, true)}/>
+ navTo(`/dealer/qrcode/index`, true)}/>
>
)
}
diff --git a/src/doctor/withdraw/debug.tsx b/src/dealer/withdraw/debug.tsx
similarity index 100%
rename from src/doctor/withdraw/debug.tsx
rename to src/dealer/withdraw/debug.tsx
diff --git a/src/doctor/withdraw/index.config.ts b/src/dealer/withdraw/index.config.ts
similarity index 100%
rename from src/doctor/withdraw/index.config.ts
rename to src/dealer/withdraw/index.config.ts
diff --git a/src/dealer/withdraw/index.tsx b/src/dealer/withdraw/index.tsx
new file mode 100644
index 0000000..c6ce532
--- /dev/null
+++ b/src/dealer/withdraw/index.tsx
@@ -0,0 +1,557 @@
+import React, {useState, useRef, useEffect, useCallback} from 'react'
+import {View, Text} from '@tarojs/components'
+import {
+ Space,
+ Button,
+ Form,
+ Input,
+ CellGroup,
+ Tabs,
+ Tag,
+ Empty,
+ Loading,
+ PullToRefresh
+} from '@nutui/nutui-react-taro'
+import {Wallet} from '@nutui/icons-react-taro'
+import {businessGradients} from '@/styles/gradients'
+import Taro from '@tarojs/taro'
+import {useDealerUser} from '@/hooks/useDealerUser'
+import {
+ pageShopDealerWithdraw,
+ addShopDealerWithdraw,
+ receiveShopDealerWithdraw,
+ receiveSuccessShopDealerWithdraw
+} from '@/api/shop/shopDealerWithdraw'
+import type {ShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw/model'
+
+interface WithdrawRecordWithDetails extends ShopDealerWithdraw {
+ accountDisplay?: string
+ // Backend may include these fields for WeChat "confirm receipt" flow after approval.
+ package_info?: string
+ packageInfo?: string
+ package?: string
+}
+
+const extractPackageInfo = (result: unknown): string | null => {
+ if (typeof result === 'string') return result
+ if (!result || typeof result !== 'object') return null
+ const r = result as any
+ return (
+ r.package_info ??
+ r.packageInfo ??
+ r.package ??
+ null
+ )
+}
+
+const canRequestMerchantTransferConfirm = (): boolean => {
+ try {
+ if (typeof (Taro as any).getEnv === 'function' && (Taro as any).ENV_TYPE) {
+ const env = (Taro as any).getEnv()
+ if (env !== (Taro as any).ENV_TYPE.WEAPP) return false
+ }
+
+ const api =
+ (globalThis as any).wx?.requestMerchantTransfer ||
+ (Taro as any).requestMerchantTransfer
+
+ return typeof api === 'function'
+ } catch {
+ return false
+ }
+}
+
+const requestMerchantTransferConfirm = (packageInfo: string): Promise => {
+ if (!canRequestMerchantTransferConfirm()) {
+ return Promise.reject(new Error('请在微信小程序内完成收款确认'))
+ }
+
+ // Backend may wrap/format base64 with newlines; WeChat API requires a clean string.
+ const cleanPackageInfo = String(packageInfo).replace(/\s+/g, '')
+
+ const api =
+ (globalThis as any).wx?.requestMerchantTransfer ||
+ (Taro as any).requestMerchantTransfer
+
+ if (typeof api !== 'function') {
+ return Promise.reject(new Error('当前环境不支持商家转账收款确认(缺少 requestMerchantTransfer)'))
+ }
+
+ return new Promise((resolve, reject) => {
+ api({
+ // WeChat API uses `package`, backend returns `package_info`.
+ package: cleanPackageInfo,
+ mchId: '1737910695',
+ appId: 'wxad831ba00ad6a026',
+ success: (res: any) => resolve(res),
+ fail: (err: any) => reject(err)
+ })
+ })
+}
+
+// Some backends may return money fields as number; keep internal usage always as string.
+const normalizeMoneyString = (money: unknown) => {
+ if (money === null || money === undefined || money === '') return '0.00'
+ return typeof money === 'string' ? money : String(money)
+}
+
+const DealerWithdraw: React.FC = () => {
+ const [activeTab, setActiveTab] = useState('0')
+ const [loading, setLoading] = useState(false)
+ const [refreshing, setRefreshing] = useState(false)
+ const [submitting, setSubmitting] = useState(false)
+ const [claimingId, setClaimingId] = useState(null)
+ const [availableAmount, setAvailableAmount] = useState('0.00')
+ const [withdrawRecords, setWithdrawRecords] = useState([])
+ const formRef = useRef(null)
+
+ const {dealerUser} = useDealerUser()
+
+ // Tab 切换处理函数
+ const handleTabChange = (value: string | number) => {
+ console.log('Tab切换到:', value)
+ setActiveTab(value)
+
+ // 如果切换到提现记录页面,刷新数据
+ if (String(value) === '1') {
+ fetchWithdrawRecords()
+ }
+ }
+
+ // 获取可提现余额
+ const fetchBalance = useCallback(async () => {
+ console.log(dealerUser, 'dealerUser...')
+ try {
+ setAvailableAmount(normalizeMoneyString(dealerUser?.money))
+ } catch (error) {
+ console.error('获取余额失败:', error)
+ }
+ }, [dealerUser])
+
+ // 获取提现记录
+ const fetchWithdrawRecords = useCallback(async () => {
+ if (!dealerUser?.userId) return
+
+ try {
+ setLoading(true)
+ const result = await pageShopDealerWithdraw({
+ page: 1,
+ limit: 100,
+ userId: dealerUser.userId
+ })
+
+ if (result?.list) {
+ const processedRecords = result.list.map(record => ({
+ ...record,
+ accountDisplay: getAccountDisplay(record)
+ }))
+ setWithdrawRecords(processedRecords)
+ }
+ } catch (error) {
+ console.error('获取提现记录失败:', error)
+ Taro.showToast({
+ title: '获取提现记录失败',
+ icon: 'error'
+ })
+ } finally {
+ setLoading(false)
+ }
+ }, [dealerUser?.userId])
+
+ // 格式化账户显示
+ const getAccountDisplay = (record: ShopDealerWithdraw) => {
+ if (record.payType === 10) {
+ return '微信钱包'
+ } else if (record.payType === 20 && record.alipayAccount) {
+ return `支付宝(${record.alipayAccount.slice(-4)})`
+ } else if (record.payType === 30 && record.bankCard) {
+ return `${record.bankName || '银行卡'}(尾号${record.bankCard.slice(-4)})`
+ }
+ return '未知账户'
+ }
+
+ // 刷新数据
+ const handleRefresh = async () => {
+ setRefreshing(true)
+ await Promise.all([fetchBalance(), fetchWithdrawRecords()])
+ setRefreshing(false)
+ }
+
+ // 初始化加载数据
+ useEffect(() => {
+ if (dealerUser?.userId) {
+ fetchBalance().then()
+ fetchWithdrawRecords().then()
+ }
+ }, [fetchBalance, fetchWithdrawRecords])
+
+ const getStatusText = (status?: number) => {
+ switch (status) {
+ case 40:
+ return '已到账'
+ case 20:
+ return '待领取'
+ case 10:
+ return '待审核'
+ case 30:
+ return '已驳回'
+ default:
+ return '未知'
+ }
+ }
+
+ const getStatusColor = (status?: number) => {
+ switch (status) {
+ case 40:
+ return 'success'
+ case 20:
+ return 'info'
+ case 10:
+ return 'warning'
+ case 30:
+ return 'danger'
+ default:
+ return 'default'
+ }
+ }
+
+ const handleSubmit = async (values: any) => {
+ if (!dealerUser?.userId) {
+ Taro.showToast({
+ title: '用户信息获取失败',
+ icon: 'error'
+ })
+ return
+ }
+
+ // 验证提现金额
+ const amount = parseFloat(String(values.amount))
+ const available = parseFloat(normalizeMoneyString(availableAmount).replace(/,/g, ''))
+
+ if (isNaN(amount) || amount <= 0) {
+ Taro.showToast({
+ title: '请输入有效的提现金额',
+ icon: 'error'
+ })
+ return
+ }
+
+ if (amount < 100) {
+ // Taro.showToast({
+ // title: '最低提现金额为100元',
+ // icon: 'error'
+ // })
+ // return
+ }
+
+ if (amount > available) {
+ Taro.showToast({
+ title: '提现金额超过可用余额',
+ icon: 'error'
+ })
+ return
+ }
+
+ try {
+ setSubmitting(true)
+
+ const withdrawData: ShopDealerWithdraw = {
+ userId: dealerUser.userId,
+ money: values.amount,
+ // Only support WeChat wallet withdrawals.
+ payType: 10,
+ applyStatus: 10, // 待审核
+ platform: 'MiniProgram'
+ }
+
+ // Security flow:
+ // 1) user submits => applyStatus=10 (待审核)
+ // 2) backend审核通过 => applyStatus=20 (待领取)
+ // 3) user goes to records to "领取" => applyStatus=40 (已到账)
+ await addShopDealerWithdraw(withdrawData)
+ Taro.showToast({title: '提现申请已提交,等待审核', icon: 'success'})
+
+ // 重置表单
+ formRef.current?.resetFields()
+
+ // 刷新数据
+ await handleRefresh()
+
+ // 切换到提现记录页面
+ setActiveTab('1')
+
+ } catch (error: any) {
+ console.error('提现申请失败:', error)
+ Taro.showToast({
+ title: error.message || '提现申请失败',
+ icon: 'error'
+ })
+ } finally {
+ setSubmitting(false)
+ }
+ }
+
+ const handleClaim = async (record: WithdrawRecordWithDetails) => {
+ if (!record?.id) {
+ Taro.showToast({title: '记录不存在', icon: 'error'})
+ return
+ }
+
+ if (record.applyStatus !== 20) {
+ Taro.showToast({title: '当前状态不可领取', icon: 'none'})
+ return
+ }
+
+ if (record.payType !== 10) {
+ Taro.showToast({title: '仅支持微信提现领取', icon: 'none'})
+ return
+ }
+
+ if (claimingId !== null) return
+
+ try {
+ setClaimingId(record.id)
+
+ if (!canRequestMerchantTransferConfirm()) {
+ throw new Error('当前环境不支持微信收款确认,请在微信小程序内操作')
+ }
+
+ const receiveResult = await receiveShopDealerWithdraw(record.id)
+ const packageInfo = extractPackageInfo(receiveResult)
+ if (!packageInfo) {
+ throw new Error('后台未返回 package_info,无法领取,请联系管理员')
+ }
+
+ try {
+ await requestMerchantTransferConfirm(packageInfo)
+ } catch (e: any) {
+ const msg = String(e?.errMsg || e?.message || '')
+ if (/cancel/i.test(msg)) {
+ Taro.showToast({title: '已取消领取', icon: 'none'})
+ return
+ }
+ throw new Error(msg || '领取失败,请稍后重试')
+ }
+
+ try {
+ await receiveSuccessShopDealerWithdraw(record.id)
+ Taro.showToast({title: '领取成功', icon: 'success'})
+ } catch (e: any) {
+ console.warn('领取成功,但状态同步失败:', e)
+ Taro.showToast({title: '已收款,状态更新失败,请稍后刷新', icon: 'none'})
+ } finally {
+ await handleRefresh()
+ }
+ } catch (e: any) {
+ console.error('领取失败:', e)
+ Taro.showToast({title: e?.message || '领取失败', icon: 'error'})
+ } finally {
+ setClaimingId(null)
+ }
+ }
+
+ const quickAmounts = ['100', '300', '500', '1000']
+
+ const setQuickAmount = (amount: string) => {
+ formRef.current?.setFieldsValue({amount})
+ }
+
+ const setAllAmount = () => {
+ formRef.current?.setFieldsValue({amount: normalizeMoneyString(availableAmount).replace(/,/g, '')})
+ }
+
+ // 格式化金额
+ const formatMoney = (money?: unknown) => {
+ const n = parseFloat(normalizeMoneyString(money).replace(/,/g, ''))
+ return Number.isFinite(n) ? n.toFixed(2) : '0.00'
+ }
+
+ const renderWithdrawForm = () => (
+
+ {/* 余额卡片 */}
+
+ {/* 装饰背景 - 小程序兼容版本 */}
+
+
+
+
+ {formatMoney(dealerUser?.money)}
+ 可提现余额
+
+
+
+
+
+
+
+ 最低提现金额:¥100 | 手续费:免费
+
+
+
+
+
+
+ )
+
+ const renderWithdrawRecords = () => {
+ console.log('渲染提现记录:', {loading, recordsCount: withdrawRecords.length, dealerUserId: dealerUser?.userId})
+
+ return (
+
+
+ {loading ? (
+
+
+ 加载中...
+
+ ) : withdrawRecords.length > 0 ? (
+ withdrawRecords.map(record => (
+
+
+
+
+ 提现金额:¥{record.money}
+
+ {/**/}
+ {/* 提现账户:{record.accountDisplay}*/}
+ {/**/}
+
+
+ {getStatusText(record.applyStatus)}
+
+
+
+
+ {record.applyStatus === 20 && record.payType === 10 && (
+
+
+
+ )}
+
+
+
+ 创建时间:{record.createTime}
+ {record.auditTime && (
+
+ 审核时间:{record.auditTime}
+
+ )}
+ {record.rejectReason && (
+
+ 驳回原因:{record.rejectReason}
+
+ )}
+
+
+
+
+ ))
+ ) : (
+
+ )}
+
+
+ )
+ }
+
+ if (!dealerUser) {
+ return (
+
+
+ 加载中...
+
+ )
+ }
+
+ return (
+
+
+
+ {renderWithdrawForm()}
+
+
+
+ {renderWithdrawRecords()}
+
+
+
+ )
+}
+
+export default DealerWithdraw
diff --git a/src/doctor/bank/add.config.ts b/src/doctor/bank/add.config.ts
deleted file mode 100644
index e07ffad..0000000
--- a/src/doctor/bank/add.config.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '添加银行卡',
- navigationBarTextStyle: 'black'
-})
diff --git a/src/doctor/bank/add.tsx b/src/doctor/bank/add.tsx
deleted file mode 100644
index ef11e80..0000000
--- a/src/doctor/bank/add.tsx
+++ /dev/null
@@ -1,142 +0,0 @@
-import {useEffect, useState, useRef} from "react";
-import {useRouter} from '@tarojs/taro'
-import {Loading, CellGroup, Input, Form} from '@nutui/nutui-react-taro'
-import Taro from '@tarojs/taro'
-import {
- getShopDealerBank,
- listShopDealerBank,
- updateShopDealerBank,
- addShopDealerBank
-} from "@/api/shop/shopDealerBank";
-import FixedButton from "@/components/FixedButton";
-import {ShopDealerBank} from "@/api/shop/shopDealerBank/model";
-
-const AddUserAddress = () => {
- const {params} = useRouter();
- const [loading, setLoading] = useState(true)
- const [FormData, setFormData] = useState()
- const formRef = useRef(null)
-
- // 判断是编辑还是新增模式
- const isEditMode = !!params.id
- const bankId = params.id ? Number(params.id) : undefined
-
- const reload = async () => {
- // 如果是编辑模式,加载地址数据
- if (isEditMode && bankId) {
- try {
- const bank = await getShopDealerBank(bankId)
- setFormData(bank)
- } catch (error) {
- console.error('加载地址失败:', error)
- Taro.showToast({
- title: '加载地址失败',
- icon: 'error'
- });
- }
- }
- }
-
- // 提交表单
- const submitSucceed = async (values: any) => {
- console.log('.>>>>>>,....')
- try {
- // 准备提交的数据
- const submitData = {
- ...values,
- isDefault: true // 新增或编辑的地址都设为默认地址
- };
-
- console.log('提交数据:', submitData)
-
- // 如果是编辑模式,添加id
- if (isEditMode && bankId) {
- submitData.id = bankId;
- }
-
- // 先处理默认地址逻辑
- const defaultAddress = await listShopDealerBank({isDefault: true});
- if (defaultAddress && defaultAddress.length > 0) {
- // 如果当前编辑的不是默认地址,或者是新增地址,需要取消其他默认地址
- if (!isEditMode || (isEditMode && defaultAddress[0].id !== bankId)) {
- await updateShopDealerBank({
- ...defaultAddress[0],
- isDefault: false
- });
- }
- }
-
- // 执行新增或更新操作
- if (isEditMode) {
- await updateShopDealerBank(submitData);
- } else {
- await addShopDealerBank(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 (
- <>
-
-
- {/* 底部浮动按钮 */}
- formRef.current?.submit()}/>
- >
- );
-};
-
-export default AddUserAddress;
diff --git a/src/doctor/bank/index.config.ts b/src/doctor/bank/index.config.ts
deleted file mode 100644
index 8f92036..0000000
--- a/src/doctor/bank/index.config.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '银行卡管理',
- navigationBarTextStyle: 'black'
-})
diff --git a/src/doctor/bank/index.tsx b/src/doctor/bank/index.tsx
deleted file mode 100644
index 9587c72..0000000
--- a/src/doctor/bank/index.tsx
+++ /dev/null
@@ -1,134 +0,0 @@
-import {useState} from "react";
-import Taro, {useDidShow} from '@tarojs/taro'
-import {Button, Cell, Space, Empty, ConfigProvider} from '@nutui/nutui-react-taro'
-import {CheckNormal, Checked} from '@nutui/icons-react-taro'
-import {View} from '@tarojs/components'
-import {ShopDealerBank} from "@/api/shop/shopDealerBank/model";
-import {listShopDealerBank, removeShopDealerBank, updateShopDealerBank} from "@/api/shop/shopDealerBank";
-import FixedButton from "@/components/FixedButton";
-
-const DealerBank = () => {
- const [list, setList] = useState([])
- const [bank, setAddress] = useState()
-
- const reload = () => {
- listShopDealerBank({})
- .then(data => {
- setList(data || [])
- // 默认地址
- setAddress(data.find(item => item.isDefault))
- })
- .catch(() => {
- Taro.showToast({
- title: '获取地址失败',
- icon: 'error'
- });
- })
- }
-
- const onDefault = async (item: ShopDealerBank) => {
- if (bank) {
- await updateShopDealerBank({
- ...bank,
- isDefault: false
- })
- }
- await updateShopDealerBank({
- id: item.id,
- isDefault: true
- })
- Taro.showToast({
- title: '设置成功',
- icon: 'success'
- });
- reload();
- }
-
- const onDel = async (id?: number) => {
- await removeShopDealerBank(id)
- Taro.showToast({
- title: '删除成功',
- icon: 'success'
- });
- reload();
- }
-
- const selectAddress = async (item: ShopDealerBank) => {
- if (bank) {
- await updateShopDealerBank({
- ...bank,
- isDefault: false
- })
- }
- await updateShopDealerBank({
- id: item.id,
- isDefault: true
- })
- setTimeout(() => {
- Taro.navigateBack()
- }, 500)
- }
-
- useDidShow(() => {
- reload()
- });
-
- if (list.length == 0) {
- return (
-
-
-
-
-
-
-
-
-
- )
- }
-
- return (
-
- {list.map((item, _) => (
-
- selectAddress(item)}>
-
- {item.bankName}
-
-
- {item.bankCard} {item.bankAccount}
-
- |
- onDefault(item)}>
- {item.isDefault ? : }
- 选择
- |
- }
- extra={
- <>
- onDel(item.id)}>
- 删除
-
- >
- }
- />
-
- ))}
- {/* 底部浮动按钮 */}
- Taro.navigateTo({url: '/doctor/bank/add'})} />
-
- );
-};
-
-export default DealerBank;
diff --git a/src/doctor/customer/README.md b/src/doctor/customer/README.md
deleted file mode 100644
index 14cb5ef..0000000
--- a/src/doctor/customer/README.md
+++ /dev/null
@@ -1,108 +0,0 @@
-# 客户管理页面
-
-## 功能概述
-
-这是一个完整的客户管理页面,支持客户数据的展示、筛选和搜索功能。
-
-## 主要功能
-
-### 1. 数据源
-- 使用 `pageUsers` API 从 User 表读取客户数据
-- 支持按状态筛选用户(status: 0 表示正常状态)
-
-### 2. 状态管理
-客户状态包括:
-- **全部** - 显示所有客户
-- **跟进中** - 正在跟进的潜在客户
-- **已签约** - 已经签约的客户
-- **已取消** - 已取消合作的客户
-
-### 3. 顶部Tabs筛选
-- 支持按客户状态筛选
-- 显示每个状态的客户数量统计
-- 实时更新统计数据
-
-### 4. 搜索功能
-支持多字段搜索:
-- 客户姓名(realName)
-- 昵称(nickname)
-- 用户名(username)
-- 手机号(phone)
-- 用户ID(userId)
-
-### 5. 客户信息展示
-每个客户卡片显示:
-- 客户姓名和状态标签
-- 手机号码
-- 注册时间
-- 用户ID、余额、积分等统计信息
-
-## 技术实现
-
-### 组件结构
-```
-CustomerManagement
-├── 搜索栏 (SearchBar)
-├── 状态筛选Tabs
-└── 客户列表
- └── 客户卡片项
-```
-
-### 主要状态
-- `list`: 客户数据列表
-- `loading`: 加载状态
-- `activeTab`: 当前选中的状态Tab
-- `searchValue`: 搜索关键词
-
-### 工具函数
-使用 `@/utils/customerStatus` 工具函数管理客户状态:
-- `getStatusText()`: 获取状态文本
-- `getStatusTagType()`: 获取状态标签类型
-- `getStatusOptions()`: 获取状态选项列表
-
-## 使用的组件
-
-### NutUI 组件
-- `Tabs` / `TabPane`: 状态筛选标签页
-- `SearchBar`: 搜索输入框
-- `Tag`: 状态标签
-- `Loading`: 加载指示器
-- `Space`: 间距布局
-
-### 图标
-- `Phone`: 手机号图标
-- `User`: 用户图标
-
-## 数据流
-
-1. 页面初始化时调用 `fetchCustomerData()` 获取用户数据
-2. 为每个用户添加客户状态(目前使用随机状态,实际项目中应从数据库获取)
-3. 根据当前Tab和搜索条件筛选数据
-4. 渲染客户列表
-
-## 注意事项
-
-### 临时实现
-- 当前使用 `getRandomStatus()` 生成随机客户状态
-- 实际项目中应该:
- 1. 在数据库中添加客户状态字段
- 2. 修改后端API返回真实的客户状态
- 3. 删除随机状态生成函数
-
-### 扩展建议
-1. 添加客户详情页面
-2. 支持客户状态的修改操作
-3. 添加客户添加/编辑功能
-4. 支持批量操作
-5. 添加导出功能
-6. 支持更多筛选条件(注册时间、地区等)
-
-## 文件结构
-```
-src/doctor/customer/
-├── index.tsx # 主页面组件
-└── README.md # 说明文档
-
-src/utils/
-└── customerStatus.ts # 客户状态工具函数
-```
diff --git a/src/doctor/customer/add.config.ts b/src/doctor/customer/add.config.ts
deleted file mode 100644
index 596fab1..0000000
--- a/src/doctor/customer/add.config.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '患者报备',
- navigationBarTextStyle: 'black'
-})
diff --git a/src/doctor/customer/add.tsx b/src/doctor/customer/add.tsx
deleted file mode 100644
index d91c3b0..0000000
--- a/src/doctor/customer/add.tsx
+++ /dev/null
@@ -1,400 +0,0 @@
-import {useEffect, useState, useRef} from "react";
-import {Loading, CellGroup, Cell, Input, Form, Calendar} from '@nutui/nutui-react-taro'
-import {Edit, Calendar as CalendarIcon} from '@nutui/icons-react-taro'
-import Taro from '@tarojs/taro'
-import {useRouter} from '@tarojs/taro'
-import {View, Text} from '@tarojs/components'
-import FixedButton from "@/components/FixedButton";
-import {useUser} from "@/hooks/useUser";
-import {ShopDealerApply} from "@/api/shop/shopDealerApply/model";
-import {
- addShopDealerApply, getShopDealerApply, pageShopDealerApply,
- updateShopDealerApply
-} from "@/api/shop/shopDealerApply";
-import {
- formatDateForDatabase,
- extractDateForCalendar, formatDateForDisplay
-} from "@/utils/dateUtils";
-
-const AddShopDealerApply = () => {
- const {user} = useUser()
- const {params} = useRouter();
- const [loading, setLoading] = useState(true)
- const [FormData, setFormData] = useState()
- const formRef = useRef(null)
- const [isEditMode, setIsEditMode] = useState(false)
- const [existingApply, setExistingApply] = useState(null)
-
- // 日期选择器状态
- const [showApplyTimePicker, setShowApplyTimePicker] = useState(false)
- const [showContractTimePicker, setShowContractTimePicker] = useState(false)
- const [applyTime, setApplyTime] = useState('')
- const [contractTime, setContractTime] = useState('')
-
- // 获取审核状态文字
- const getApplyStatusText = (status?: number) => {
- switch (status) {
- case 10:
- return '待审核'
- case 20:
- return '已签约'
- case 30:
- return '已取消'
- default:
- return '未知状态'
- }
- }
-
- console.log(getApplyStatusText)
-
- // 处理签约时间选择
- const handleApplyTimeConfirm = (param: string) => {
- const selectedDate = param[3] // 选中的日期字符串 (YYYY-M-D)
- const formattedDate = formatDateForDatabase(selectedDate) // 转换为数据库格式
- setApplyTime(selectedDate) // 保存原始格式用于显示
- setShowApplyTimePicker(false)
-
- // 更新表单数据(使用数据库格式)
- if (formRef.current) {
- formRef.current.setFieldsValue({
- applyTime: formattedDate
- })
- }
- }
-
- // 处理合同日期选择
- const handleContractTimeConfirm = (param: string) => {
- const selectedDate = param[3] // 选中的日期字符串 (YYYY-M-D)
- const formattedDate = formatDateForDatabase(selectedDate) // 转换为数据库格式
- setContractTime(selectedDate) // 保存原始格式用于显示
- setShowContractTimePicker(false)
-
- // 更新表单数据(使用数据库格式)
- if (formRef.current) {
- formRef.current.setFieldsValue({
- contractTime: formattedDate
- })
- }
- }
-
- const reload = async () => {
- if (!params.id) {
- return false;
- }
- // 查询当前用户ID是否已有申请记录
- try {
- const dealerApply = await getShopDealerApply(Number(params.id));
- if (dealerApply) {
- setFormData(dealerApply)
- setIsEditMode(true);
- setExistingApply(dealerApply)
-
- // 初始化日期数据(从数据库格式转换为Calendar组件格式)
- if (dealerApply.applyTime) {
- setApplyTime(extractDateForCalendar(dealerApply.applyTime))
- }
- if (dealerApply.contractTime) {
- setContractTime(extractDateForCalendar(dealerApply.contractTime))
- }
-
- Taro.setNavigationBarTitle({title: '签约'})
- }
- } catch (error) {
- setLoading(true)
- console.error('查询申请记录失败:', error);
- setIsEditMode(false);
- setExistingApply(null);
- }
- }
-
- // 提交表单
- // 计算保护期过期时间(7天后)
- const calculateExpirationTime = (): string => {
- const now = new Date();
- const expirationDate = new Date(now);
- expirationDate.setDate(now.getDate() + 7); // 7天后
-
- // 格式化为数据库需要的格式:YYYY-MM-DD HH:mm:ss
- const year = expirationDate.getFullYear();
- const month = String(expirationDate.getMonth() + 1).padStart(2, '0');
- const day = String(expirationDate.getDate()).padStart(2, '0');
- const hours = String(expirationDate.getHours()).padStart(2, '0');
- const minutes = String(expirationDate.getMinutes()).padStart(2, '0');
- const seconds = String(expirationDate.getSeconds()).padStart(2, '0');
-
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
- };
-
- const submitSucceed = async (values: any) => {
- try {
- // 验证必填字段
- if (!values.mobile || values.mobile.trim() === '') {
- Taro.showToast({
- title: '请填写联系方式',
- icon: 'error'
- });
- return;
- }
-
- // 验证手机号格式
- const phoneRegex = /^1[3-9]\d{9}$/;
- if (!phoneRegex.test(values.mobile)) {
- Taro.showToast({
- title: '请填写正确的手机号',
- icon: 'error'
- });
- return;
- }
-
- // 检查客户是否已存在
- const res = await pageShopDealerApply({dealerName: values.dealerName, type: 4, applyStatus: 10});
-
- if (res && res.count > 0) {
- const existingCustomer = res.list[0];
-
- // 检查是否在7天保护期内
- if (!isEditMode && existingCustomer.applyTime) {
- // 将申请时间字符串转换为时间戳进行比较
- const applyTimeStamp = new Date(existingCustomer.applyTime).getTime();
- const currentTimeStamp = new Date().getTime();
- const sevenDaysInMs = 7 * 24 * 60 * 60 * 1000; // 7天的毫秒数
-
- // 如果在7天保护期内,不允许重复添加
- if (currentTimeStamp - applyTimeStamp < sevenDaysInMs) {
- const remainingDays = Math.ceil((sevenDaysInMs - (currentTimeStamp - applyTimeStamp)) / (24 * 60 * 60 * 1000));
-
- Taro.showToast({
- title: `该客户还在保护期,还需等待${remainingDays}天后才能重新添加`,
- icon: 'none',
- duration: 3000
- });
- return false;
- } else {
- // 超过7天保护期,可以重新添加,显示确认对话框
- const modalResult = await new Promise((resolve) => {
- Taro.showModal({
- title: '提示',
- content: '该客户已超过7天保护期,是否重新添加跟进?',
- showCancel: true,
- cancelText: '取消',
- confirmText: '确定',
- success: (modalRes) => {
- resolve(modalRes.confirm);
- },
- fail: () => {
- resolve(false);
- }
- });
- });
-
- if (!modalResult) {
- return false; // 用户取消,不继续执行
- }
- // 用户确认后继续执行添加逻辑
- }
- }
- }
-
-
- // 计算过期时间
- const expirationTime = isEditMode ? existingApply?.expirationTime : calculateExpirationTime();
-
- // 准备提交的数据
- const submitData = {
- ...values,
- type: 4,
- realName: values.realName || user?.nickname,
- mobile: values.mobile,
- refereeId: 33534,
- applyStatus: isEditMode ? 20 : 10,
- auditTime: undefined,
- // 设置保护期过期时间(7天后)
- expirationTime: expirationTime,
- // 确保日期数据正确提交(使用数据库格式)
- applyTime: values.applyTime || (applyTime ? formatDateForDatabase(applyTime) : ''),
- contractTime: values.contractTime || (contractTime ? formatDateForDatabase(contractTime) : '')
- };
-
- // 调试信息
- console.log('=== 提交数据调试 ===');
- console.log('是否编辑模式:', isEditMode);
- console.log('计算的过期时间:', expirationTime);
- console.log('提交的数据:', submitData);
- console.log('==================');
-
- // 如果是编辑模式,添加现有申请的id
- if (isEditMode && existingApply?.applyId) {
- submitData.applyId = existingApply.applyId;
- }
-
- // 执行新增或更新操作
- if (isEditMode) {
- await updateShopDealerApply(submitData);
- } else {
- await addShopDealerApply(submitData);
- }
-
- Taro.showToast({
- title: `${isEditMode ? '更新' : '提交'}成功`,
- icon: 'success'
- });
-
- setTimeout(() => {
- Taro.navigateBack();
- }, 1000);
-
- } catch (error) {
- console.error('提交失败:', error);
- Taro.showToast({
- title: '提交失败,请重试',
- icon: 'error'
- });
- }
- }
-
- // 处理固定按钮点击事件
- const handleFixedButtonClick = () => {
- // 触发表单提交
- formRef.current?.submit();
- };
-
- const submitFailed = (error: any) => {
- console.log(error, 'err...')
- }
-
- useEffect(() => {
- reload().then(() => {
- setLoading(false)
- })
- }, []); // 依赖用户ID,当用户变化时重新加载
-
- if (loading) {
- return 加载中
- }
-
- return (
- <>
-
-
- {/* 签约时间选择器 */}
- setShowApplyTimePicker(false)}
- onConfirm={handleApplyTimeConfirm}
- />
-
- {/* 合同日期选择器 */}
- setShowContractTimePicker(false)}
- onConfirm={handleContractTimeConfirm}
- />
-
- {/* 审核状态显示(仅在编辑模式下显示) */}
- {isEditMode && (
-
- {/**/}
- {/* {getApplyStatusText(FormData?.applyStatus)}*/}
- {/* */}
- {/* }*/}
- {/*/>*/}
- {FormData?.applyStatus === 20 && (
- |
- )}
- {FormData?.applyStatus === 30 && (
- |
- )}
- |
- )}
-
-
- {/* 底部浮动按钮 */}
- {(!isEditMode || FormData?.applyStatus === 10) && (
- }
- text={'立即提交'}
- onClick={handleFixedButtonClick}
- />
- )}
-
- >
- );
-};
-
-export default AddShopDealerApply;
diff --git a/src/doctor/customer/index.config.ts b/src/doctor/customer/index.config.ts
deleted file mode 100644
index 539f8b2..0000000
--- a/src/doctor/customer/index.config.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '患者管理'
-})
diff --git a/src/doctor/customer/index.tsx b/src/doctor/customer/index.tsx
deleted file mode 100644
index 33cec2e..0000000
--- a/src/doctor/customer/index.tsx
+++ /dev/null
@@ -1,583 +0,0 @@
-import {useState, useEffect, useCallback} from 'react'
-import {View, Text} from '@tarojs/components'
-import Taro, {useDidShow} from '@tarojs/taro'
-import {Loading, InfiniteLoading, Empty, Space, Tabs, TabPane, Tag, Button, SearchBar} from '@nutui/nutui-react-taro'
-import {Phone, AngleDoubleLeft} from '@nutui/icons-react-taro'
-import type {ShopDealerApply, ShopDealerApply as UserType} from "@/api/shop/shopDealerApply/model";
-import {
- CustomerStatus,
- getStatusText,
- getStatusTagType,
- getStatusOptions,
- mapApplyStatusToCustomerStatus,
- mapCustomerStatusToApplyStatus
-} from '@/utils/customerStatus';
-import FixedButton from "@/components/FixedButton";
-import navTo from "@/utils/common";
-import {pageShopDealerApply, removeShopDealerApply, updateShopDealerApply} from "@/api/shop/shopDealerApply";
-
-// 扩展User类型,添加客户状态和保护天数
-interface CustomerUser extends UserType {
- customerStatus?: CustomerStatus;
- protectDays?: number; // 剩余保护天数
-}
-
-const CustomerIndex = () => {
- const [list, setList] = useState([])
- const [loading, setLoading] = useState(false)
- const [activeTab, setActiveTab] = useState('all')
- const [searchValue, setSearchValue] = useState('')
- const [displaySearchValue, setDisplaySearchValue] = useState('')
- const [page, setPage] = useState(1)
- const [hasMore, setHasMore] = useState(true)
-
- // Tab配置
- const tabList = getStatusOptions();
-
- // 复制手机号
- const copyPhone = (phone: string) => {
- Taro.setClipboardData({
- data: phone,
- success: () => {
- Taro.showToast({
- title: '手机号已复制',
- icon: 'success',
- duration: 1500
- });
- }
- });
- };
-
- // 一键拨打
- const makePhoneCall = (phone: string) => {
- Taro.makePhoneCall({
- phoneNumber: phone,
- fail: () => {
- Taro.showToast({
- title: '拨打取消',
- icon: 'error'
- });
- }
- });
- };
-
- // 编辑跟进情况
- const editComments = (customer: CustomerUser) => {
- Taro.showModal({
- title: '编辑跟进情况',
- // @ts-ignore
- editable: true,
- placeholderText: '请输入跟进情况',
- content: customer.comments || '',
- success: async (res) => {
- // @ts-ignore
- if (res.confirm && res.content !== undefined) {
- try {
- // 更新跟进情况
- await updateShopDealerApply({
- ...customer,
- // @ts-ignore
- comments: res.content.trim()
- });
-
- Taro.showToast({
- title: '更新成功',
- icon: 'success'
- });
-
- // 刷新列表
- setList([]);
- setPage(1);
- setHasMore(true);
- fetchCustomerData(activeTab, true);
- } catch (error) {
- console.error('更新跟进情况失败:', error);
- Taro.showToast({
- title: '更新失败,请重试',
- icon: 'error'
- });
- }
- }
- }
- });
- };
-
- // 计算剩余保护天数(基于过期时间)
- const calculateProtectDays = (expirationTime?: string, applyTime?: string): number => {
- try {
- // 优先使用过期时间字段
- if (expirationTime) {
- const expDate = new Date(expirationTime.replace(' ', 'T'));
- const now = new Date();
-
- // 计算剩余毫秒数
- const remainingMs = expDate.getTime() - now.getTime();
-
- // 转换为天数,向上取整
- const remainingDays = Math.ceil(remainingMs / (1000 * 60 * 60 * 24));
-
- console.log('=== 基于过期时间计算 ===');
- console.log('过期时间:', expirationTime);
- console.log('当前时间:', now.toLocaleString());
- console.log('剩余天数:', remainingDays);
- console.log('======================');
-
- return Math.max(0, remainingDays);
- }
-
- // 如果没有过期时间,回退到基于申请时间计算
- if (!applyTime) return 0;
-
- const protectionPeriod = 7; // 保护期7天
-
- // 解析申请时间
- let applyDate: Date;
- if (applyTime.includes('T')) {
- applyDate = new Date(applyTime);
- } else {
- applyDate = new Date(applyTime.replace(' ', 'T'));
- }
-
- // 获取当前时间
- const now = new Date();
-
- // 只比较日期部分,忽略时间
- const applyDateOnly = new Date(applyDate.getFullYear(), applyDate.getMonth(), applyDate.getDate());
- const currentDateOnly = new Date(now.getFullYear(), now.getMonth(), now.getDate());
-
- // 计算已经过去的天数
- const timeDiff = currentDateOnly.getTime() - applyDateOnly.getTime();
- const daysPassed = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
-
- // 计算剩余保护天数
- const remainingDays = protectionPeriod - daysPassed;
-
- console.log('=== 基于申请时间计算 ===');
- console.log('申请时间:', applyTime);
- console.log('已过去天数:', daysPassed);
- console.log('剩余保护天数:', remainingDays);
- console.log('======================');
-
- return Math.max(0, remainingDays);
- } catch (error) {
- console.error('日期计算错误:', error);
- return 0;
- }
- };
-
-
- // 获取客户数据
- const fetchCustomerData = useCallback(async (statusFilter?: CustomerStatus, resetPage = false, targetPage?: number) => {
- setLoading(true);
- try {
- const currentPage = resetPage ? 1 : (targetPage || page);
-
- // 构建API参数,根据状态筛选
- const params: any = {
- type: 4,
- page: currentPage
- };
- const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab);
- if (applyStatus !== undefined) {
- params.applyStatus = applyStatus;
- }
-
- const res = await pageShopDealerApply(params);
-
- if (res?.list && res.list.length > 0) {
- // 正确映射状态并计算保护天数
- const mappedList = res.list.map(customer => ({
- ...customer,
- customerStatus: mapApplyStatusToCustomerStatus(customer.applyStatus || 10),
- protectDays: calculateProtectDays(customer.expirationTime, customer.applyTime || customer.createTime || '')
- }));
-
- // 如果是重置页面或第一页,直接设置新数据;否则追加数据
- if (resetPage || currentPage === 1) {
- setList(mappedList);
- } else {
- setList(prevList => prevList.concat(mappedList));
- }
-
- // 正确判断是否还有更多数据
- const hasMoreData = res.list.length >= 10; // 假设每页10条数据
- setHasMore(hasMoreData);
- } else {
- if (resetPage || currentPage === 1) {
- setList([]);
- }
- setHasMore(false);
- }
-
- setPage(currentPage);
- } catch (error) {
- console.error('获取客户数据失败:', error);
- Taro.showToast({
- title: '加载失败,请重试',
- icon: 'none'
- });
- } finally {
- setLoading(false);
- }
- }, [activeTab, page]);
-
- const reloadMore = async () => {
- if (loading || !hasMore) return; // 防止重复加载
- const nextPage = page + 1;
- await fetchCustomerData(activeTab, false, nextPage);
- }
-
-
- // 防抖搜索功能
- useEffect(() => {
- const timer = setTimeout(() => {
- setDisplaySearchValue(searchValue);
- }, 300); // 300ms 防抖
-
- return () => clearTimeout(timer);
- }, [searchValue]);
-
- // 根据搜索条件筛选数据(状态筛选已在API层面处理)
- const getFilteredList = () => {
- let filteredList = list;
-
- // 按搜索关键词筛选
- if (displaySearchValue.trim()) {
- const keyword = displaySearchValue.trim().toLowerCase();
- filteredList = filteredList.filter(customer =>
- (customer.realName && customer.realName.toLowerCase().includes(keyword)) ||
- (customer.dealerName && customer.dealerName.toLowerCase().includes(keyword)) ||
- (customer.dealerCode && customer.dealerCode.toLowerCase().includes(keyword)) ||
- (customer.mobile && customer.mobile.includes(keyword)) ||
- (customer.userId && customer.userId.toString().includes(keyword))
- );
- }
-
- return filteredList;
- };
-
- // 获取各状态的统计数量
- const [statusCounts, setStatusCounts] = useState({
- all: 0,
- pending: 0,
- signed: 0,
- cancelled: 0
- });
-
- // 获取所有状态的统计数量
- const fetchStatusCounts = useCallback(async () => {
- try {
- // 并行获取各状态的数量
- const [allRes, pendingRes, signedRes, cancelledRes] = await Promise.all([
- pageShopDealerApply({type: 4}), // 全部
- pageShopDealerApply({applyStatus: 10, type: 4}), // 跟进中
- pageShopDealerApply({applyStatus: 20, type: 4}), // 已签约
- pageShopDealerApply({applyStatus: 30, type: 4}) // 已取消
- ]);
-
- setStatusCounts({
- all: allRes?.count || 0,
- pending: pendingRes?.count || 0,
- signed: signedRes?.count || 0,
- cancelled: cancelledRes?.count || 0
- });
- } catch (error) {
- console.error('获取状态统计失败:', error);
- }
- }, []);
-
- const getStatusCounts = () => statusCounts;
-
- // 取消操作
- const handleCancel = (customer: ShopDealerApply) => {
- updateShopDealerApply({
- ...customer,
- applyStatus: 30
- }).then(() => {
- Taro.showToast({
- title: '取消成功',
- icon: 'success'
- });
- // 重新加载当前tab的数据
- setList([]);
- setPage(1);
- setHasMore(true);
- fetchCustomerData(activeTab, true).then();
- fetchStatusCounts().then();
- })
- };
-
- // 删除
- const handleDelete = (customer: ShopDealerApply) => {
- removeShopDealerApply(customer.applyId).then(() => {
- Taro.showToast({
- title: '删除成功',
- icon: 'success'
- });
- // 刷新当前tab的数据
- setList([]);
- setPage(1);
- setHasMore(true);
- fetchCustomerData(activeTab, true).then();
- fetchStatusCounts().then();
- })
- }
-
- // 初始化数据
- useEffect(() => {
- fetchCustomerData(activeTab, true).then();
- fetchStatusCounts().then();
- }, []);
-
- // 当activeTab变化时重新获取数据
- useEffect(() => {
- setList([]); // 清空列表
- setPage(1); // 重置页码
- setHasMore(true); // 重置加载状态
- fetchCustomerData(activeTab, true);
- }, [activeTab]);
-
- // 监听页面显示,当从其他页面返回时刷新数据
- useDidShow(() => {
- // 刷新当前tab的数据和统计信息
- setList([]);
- setPage(1);
- setHasMore(true);
- fetchCustomerData(activeTab, true);
- fetchStatusCounts();
- });
-
- // 渲染客户项
- const renderCustomerItem = (customer: CustomerUser) => (
-
-
-
-
-
- {customer.dealerName}
-
- {customer.customerStatus && (
-
- {getStatusText(customer.customerStatus)}
-
- )}
-
-
-
- 联系人:{customer.realName}
-
- {
- e.stopPropagation();
- makePhoneCall(customer.mobile || '');
- }}>联系电话:{customer.mobile}
-
- {
- e.stopPropagation();
- makePhoneCall(customer.mobile || '');
- }}
- />
- {
- e.stopPropagation();
- copyPhone(customer.mobile || '');
- }}
- >
- 复制
-
-
-
-
- 添加时间:{customer.createTime}
-
-
-
-
- {/* 保护天数显示 */}
- {customer.applyStatus === 10 && (
-
- 保护期:
- {customer.protectDays && customer.protectDays > 0 ? (
-
- 剩余{customer.protectDays}天
-
- ) : (
-
- 已过期
-
- )}
-
- )}
-
-
- 报备人:{customer?.nickName}
-
- {customer?.refereeName}
-
-
- {/* 显示 comments 字段 */}
-
- 跟进情况:{customer.comments || '暂无'}
- {
- e.stopPropagation();
- editComments(customer);
- }}
- >
- 编辑
-
-
-
-
-
- {/* 跟进中状态显示操作按钮 */}
- {(customer.applyStatus === 10 && customer.userId == Taro.getStorageSync('UserId')) && (
-
-
-
-
- )}
- {(customer.applyStatus === 30 && customer.userId == Taro.getStorageSync('UserId')) && (
-
-
-
- )}
-
- );
-
- // 渲染客户列表
- const renderCustomerList = () => {
- const filteredList = getFilteredList();
- const isSearching = displaySearchValue.trim().length > 0;
-
- return (
-
- {/* 搜索结果统计 */}
- {isSearching && (
-
-
- 搜索 "{displaySearchValue}" 的结果,共找到 {filteredList.length} 条记录
-
-
- )}
-
-
- {
- // 滚动事件处理
- }}
- onScrollToUpper={() => {
- // 滚动到顶部事件处理
- }}
- loadingText={
- <>
- 加载中...
- >
- }
- loadMoreText={
- filteredList.length === 0 ? (
-
- ) : (
-
- 没有更多了
-
- )
- }
- >
- {loading && filteredList.length === 0 ? (
-
-
- 加载中...
-
- ) : (
- filteredList.map(renderCustomerItem)
- )}
-
-
-
- );
- };
-
- return (
-
- {/* 搜索栏 */}
-
- setSearchValue(value)}
- onClear={() => {
- setSearchValue('');
- setDisplaySearchValue('');
- }}
- clearable
- />
-
-
- {/* 顶部Tabs */}
-
- setActiveTab(value as CustomerStatus)}
- >
- {tabList.map(tab => {
- const counts = getStatusCounts();
- const count = counts[tab.value as keyof typeof counts] || 0;
- return (
- 0 ? `(${count})` : ''}`}
- value={tab.value}
- />
- );
- })}
-
-
-
- {/* 客户列表 */}
- {renderCustomerList()}
-
- Taro.navigateTo({url: '/doctor/customer/add'})}/>
-
- );
-};
-
-export default CustomerIndex;
diff --git a/src/doctor/customer/trading.config.ts b/src/doctor/customer/trading.config.ts
deleted file mode 100644
index 8773b6f..0000000
--- a/src/doctor/customer/trading.config.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '入市查询'
-})
diff --git a/src/doctor/customer/trading.tsx b/src/doctor/customer/trading.tsx
deleted file mode 100644
index 43d8ca4..0000000
--- a/src/doctor/customer/trading.tsx
+++ /dev/null
@@ -1,207 +0,0 @@
-import {useState, useCallback} from 'react'
-import {View, Text} from '@tarojs/components'
-import Taro from '@tarojs/taro'
-import {Loading, InfiniteLoading, Empty, Space, SearchBar} from '@nutui/nutui-react-taro'
-import type {ShopDealerApply as UserType} from "@/api/shop/shopDealerApply/model";
-import {
- CustomerStatus,
- mapApplyStatusToCustomerStatus,
-} from '@/utils/customerStatus';
-import {pageShopDealerApply} from "@/api/shop/shopDealerApply";
-
-// 扩展User类型,添加客户状态
-interface CustomerUser extends UserType {
- customerStatus?: CustomerStatus;
-}
-
-const CustomerTrading = () => {
- const [list, setList] = useState([])
- const [loading, setLoading] = useState(false)
- const [searchValue, setSearchValue] = useState('')
- const [page, setPage] = useState(1)
- const [hasMore, setHasMore] = useState(true)
-
- // 获取客户数据
- const fetchCustomerData = useCallback(async (resetPage = false, targetPage?: number, searchKeyword?: string) => {
- setLoading(true);
- try {
- const currentPage = resetPage ? 1 : (targetPage || page);
-
- // 构建API参数,根据状态筛选
- const params: any = {
- type: 3,
- page: currentPage
- };
-
- // 添加搜索关键词
- if (searchKeyword && searchKeyword.trim()) {
- params.keywords = searchKeyword.trim();
- }
-
- const res = await pageShopDealerApply(params);
-
- if (res?.list && res.list.length > 0) {
- // 正确映射状态
- const mappedList = res.list.map(customer => ({
- ...customer,
- customerStatus: mapApplyStatusToCustomerStatus(customer.applyStatus || 10)
- }));
-
- // 如果是重置页面或第一页,直接设置新数据;否则追加数据
- if (resetPage || currentPage === 1) {
- setList(mappedList);
- } else {
- setList(prevList => prevList.concat(mappedList));
- }
-
- // 正确判断是否还有更多数据
- const hasMoreData = res.list.length >= 10; // 假设每页10条数据
- setHasMore(hasMoreData);
- } else {
- if (resetPage || currentPage === 1) {
- setList([]);
- }
- setHasMore(false);
- }
-
- setPage(currentPage);
- } catch (error) {
- console.error('获取客户数据失败:', error);
- Taro.showToast({
- title: '加载失败,请重试',
- icon: 'none'
- });
- } finally {
- setLoading(false);
- }
- }, [page]);
-
- const reloadMore = async () => {
- if (loading || !hasMore) return; // 防止重复加载
- const nextPage = page + 1;
- await fetchCustomerData(false, nextPage, searchValue);
- }
-
-
- // 获取列表数据(现在使用服务端搜索,不需要客户端过滤)
- const getFilteredList = () => {
- return list;
- };
-
- // 搜索处理函数
- const handleSearch = (keyword: string) => {
- if(keyword.length < 4){
- Taro.showToast({
- title: '请输入至少4个字符',
- icon: 'none'
- });
- return;
- }
- setSearchValue(keyword);
- setList([]);
- setPage(1);
- setHasMore(true);
- fetchCustomerData(true, 1, keyword);
- };
-
- // 清空搜索
- const handleClearSearch = () => {
- setSearchValue('');
- setList([]);
- setPage(1);
- setHasMore(true);
- fetchCustomerData(true, 1, '');
- };
-
- // 渲染客户项
- const renderCustomerItem = (customer: CustomerUser) => (
-
-
-
-
-
- {customer.dealerName}
-
-
-
- {/*统一代码:{customer.dealerCode}*/}
-
- 更新时间:{customer.createTime}
-
-
-
-
-
- );
-
- // 渲染客户列表
- const renderCustomerList = () => {
- const filteredList = getFilteredList();
-
- return (
-
- {
- // 滚动事件处理
- }}
- onScrollToUpper={() => {
- // 滚动到顶部事件处理
- }}
- loadingText={
- <>
- 加载中...
- >
- }
- loadMoreText={
- filteredList.length === 0 ? (
-
- ) : (
-
- 没有更多了
-
- )
- }
- >
- {loading && filteredList.length === 0 ? (
-
-
- 加载中...
-
- ) : (
- filteredList.map(renderCustomerItem)
- )}
-
-
- );
- };
-
- return (
-
- {/* 搜索栏 */}
-
- handleSearch(value)}
- onClear={() => handleClearSearch()}
- />
-
-
- {/* 客户列表 */}
- {renderCustomerList()}
-
-
- );
-};
-
-export default CustomerTrading;
diff --git a/src/doctor/index.config.ts b/src/doctor/index.config.ts
deleted file mode 100644
index f05d7ba..0000000
--- a/src/doctor/index.config.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '医生端'
-})
diff --git a/src/doctor/orders/add.tsx b/src/doctor/orders/add.tsx
deleted file mode 100644
index 36126a3..0000000
--- a/src/doctor/orders/add.tsx
+++ /dev/null
@@ -1,135 +0,0 @@
-import {useEffect, useState, useRef} from "react";
-import {useRouter} from '@tarojs/taro'
-import {Loading, CellGroup, Input, Form, Cell, Avatar} from '@nutui/nutui-react-taro'
-import {ArrowRight} from '@nutui/icons-react-taro'
-import {View, Text} from '@tarojs/components'
-import Taro from '@tarojs/taro'
-import FixedButton from "@/components/FixedButton";
-import {addShopChatMessage} from "@/api/shop/shopChatMessage";
-import {ShopChatMessage} from "@/api/shop/shopChatMessage/model";
-import navTo from "@/utils/common";
-import {getUser} from "@/api/system/user";
-import {User} from "@/api/system/user/model";
-
-const AddMessage = () => {
- const {params} = useRouter();
- const [toUser, setToUser] = useState()
- const [loading, setLoading] = useState(true)
- const [FormData, _] = useState()
- const formRef = useRef(null)
-
- // 判断是编辑还是新增模式
- const isEditMode = !!params.id
- const toUserId = params.id ? Number(params.id) : undefined
-
- const reload = async () => {
- if(toUserId){
- getUser(Number(toUserId)).then(data => {
- setToUser(data)
- })
- }
- }
-
- // 提交表单
- const submitSucceed = async (values: any) => {
- try {
- // 准备提交的数据
- const submitData = {
- ...values
- };
-
- console.log('提交数据:', submitData)
-
- // 参数校验
- if(!toUser){
- Taro.showToast({
- title: `请选择发送对象`,
- icon: 'error'
- });
- return false;
- }
-
- // 判断内容是否为空
- if (!values.content) {
- Taro.showToast({
- title: `请输入内容`,
- icon: 'error'
- });
- return false;
- }
- // 执行新增或更新操作
- await addShopChatMessage({
- toUserId: toUserId,
- formUserId: Taro.getStorageSync('UserId'),
- type: 'text',
- content: values.content
- });
-
- Taro.showToast({
- title: `发送成功`,
- icon: 'success'
- });
-
- setTimeout(() => {
- Taro.navigateBack();
- }, 1000);
-
- } catch (error) {
- console.error('发送失败:', error);
- Taro.showToast({
- title: `发送失败`,
- icon: 'error'
- });
- }
- }
-
- const submitFailed = (error: any) => {
- console.log(error, 'err...')
- }
-
- useEffect(() => {
- reload().then(() => {
- setLoading(false)
- })
- }, [isEditMode]);
-
- if (loading) {
- return 加载中
- }
-
- return (
- <>
-
-
-
- {toUser.alias || toUser.nickname}
- {toUser.mobile}
-
- |
- ) : '选择患者'} extra={(
-
- )}
- onClick={() => navTo(`/doctor/customer/index`, true)}/>
-
-
- {/* 底部浮动按钮 */}
- formRef.current?.submit()}/>
- >
- );
-};
-
-export default AddMessage;
diff --git a/src/doctor/orders/index.config.ts b/src/doctor/orders/index.config.ts
deleted file mode 100644
index ba927a9..0000000
--- a/src/doctor/orders/index.config.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '处方管理'
-})
diff --git a/src/doctor/team/index.config.ts b/src/doctor/team/index.config.ts
deleted file mode 100644
index 539f8b2..0000000
--- a/src/doctor/team/index.config.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '患者管理'
-})
diff --git a/src/doctor/wechat/index.config.ts b/src/doctor/wechat/index.config.ts
deleted file mode 100644
index a3d18db..0000000
--- a/src/doctor/wechat/index.config.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '微信客服'
-})
diff --git a/src/doctor/wechat/index.scss b/src/doctor/wechat/index.scss
deleted file mode 100644
index ea3fb82..0000000
--- a/src/doctor/wechat/index.scss
+++ /dev/null
@@ -1,176 +0,0 @@
-.wechat-service-page {
- min-height: 100vh;
-
- .service-tabs {
- background-color: #fff;
-
- .nut-tabs__titles {
- background-color: #fff;
- }
-
- .nut-tabs__content {
- padding: 0;
- }
- }
-
- .qr-container {
- padding: 20px;
- min-height: calc(100vh - 100px);
-
- .qr-header {
- text-align: center;
- margin-bottom: 30px;
-
- .qr-title {
- display: block;
- font-weight: bold;
- color: #333;
- margin-bottom: 8px;
- }
-
- .qr-description {
- display: block;
- color: #666;
- line-height: 1.5;
- }
- }
-
- .qr-content {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 30px;
-
- .qr-code-wrapper {
- background-color: #fff;
- border-radius: 12px;
- padding: 30px;
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
- text-align: center;
-
- .qr-code-image {
- width: 360px;
- height: 360px;
- border-radius: 8px;
- margin-bottom: 15px;
- }
-
- .wechat-id {
- display: block;
- color: #333;
- font-weight: 500;
- }
- }
-
- .qr-tips {
- background-color: #fff;
- border-radius: 12px;
- padding: 20px;
- width: 100%;
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
-
- .tip-title {
- display: block;
- font-weight: bold;
- color: #333;
- margin-bottom: 15px;
- }
-
- .tip-item {
- display: block;
- color: #666;
- line-height: 1.8;
- margin-bottom: 8px;
- padding-left: 10px;
- position: relative;
-
- &:before {
- content: '•';
- color: #07c160;
- font-weight: bold;
- position: absolute;
- left: 0;
- }
-
- &:last-child {
- margin-bottom: 0;
- }
- }
- }
- }
- }
-}
-
-// 响应式适配
-@media (max-width: 375px) {
- .wechat-service-page {
- .qr-container {
- padding: 15px;
-
- .qr-content {
- .qr-code-wrapper {
- padding: 20px;
-
- .qr-code-image {
- width: 180px;
- height: 180px;
- }
- }
- }
- }
- }
-}
-
-// 深色模式适配
-@media (prefers-color-scheme: dark) {
- .wechat-service-page {
- background-color: #1a1a1a;
-
- .service-tabs {
- .nut-tabs__titles {
- background-color: #2a2a2a;
- border-bottom-color: #333;
- }
- }
-
- .qr-container {
- background-color: #1a1a1a;
-
- .qr-header {
- .qr-title {
- color: #fff;
- }
-
- .qr-description {
- color: #ccc;
- }
- }
-
- .qr-content {
- .qr-code-wrapper {
- background-color: #2a2a2a;
-
- .qr-code-image {
- border-color: #444;
- }
-
- .wechat-id {
- color: #fff;
- }
- }
-
- .qr-tips {
- background-color: #2a2a2a;
-
- .tip-title {
- color: #fff;
- }
-
- .tip-item {
- color: #ccc;
- }
- }
- }
- }
- }
-}
diff --git a/src/doctor/wechat/index.tsx b/src/doctor/wechat/index.tsx
deleted file mode 100644
index 1132e6e..0000000
--- a/src/doctor/wechat/index.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import {useEffect, useState} from 'react'
-import {View, Text, Image} from '@tarojs/components'
-import {Tabs} from '@nutui/nutui-react-taro'
-import Taro from '@tarojs/taro'
-import './index.scss'
-import {listCmsWebsiteField} from "@/api/cms/cmsWebsiteField";
-import {CmsWebsiteField} from "@/api/cms/cmsWebsiteField/model";
-
-const WechatService = () => {
- const [activeTab, setActiveTab] = useState('0')
- const [codes, setCodes] = useState([])
-
- // 长按保存二维码到相册
- const saveQRCodeToAlbum = (imageUrl: string) => {
- // 首先下载图片到本地
- Taro.downloadFile({
- url: imageUrl,
- success: (res) => {
- if (res.statusCode === 200) {
- // 保存图片到相册
- Taro.saveImageToPhotosAlbum({
- filePath: res.tempFilePath,
- success: () => {
- Taro.showToast({
- title: '保存成功',
- icon: 'success',
- duration: 2000
- })
- },
- fail: (error) => {
- console.error('保存失败:', error)
- if (error.errMsg.includes('auth deny')) {
- Taro.showModal({
- title: '提示',
- content: '需要您授权保存图片到相册',
- showCancel: true,
- cancelText: '取消',
- confirmText: '去设置',
- success: (modalRes) => {
- if (modalRes.confirm) {
- Taro.openSetting()
- }
- }
- })
- } else {
- Taro.showToast({
- title: '保存失败',
- icon: 'error',
- duration: 2000
- })
- }
- }
- })
- } else {
- Taro.showToast({
- title: '图片下载失败',
- icon: 'error',
- duration: 2000
- })
- }
- },
- fail: () => {
- Taro.showToast({
- title: '图片下载失败',
- icon: 'error',
- duration: 2000
- })
- }
- })
- }
-
- const renderQRCode = (data: typeof codes[0]) => (
-
-
-
- saveQRCodeToAlbum(`${data.value}`)}
- />
- {data.style && 联系电话:{data.style}}
-
-
-
- 使用说明:
- 1. 长按二维码保存到相册
- 2. 打开微信扫一扫
- 3. 选择相册中的二维码图片
- 4. 添加好友并发送验证消息
-
-
-
- )
-
- useEffect(() => {
- listCmsWebsiteField({name: 'kefu'}).then(data => {
- if (data) {
- setCodes(data)
- }
- })
- }, []);
-
- return (
-
- setActiveTab(`${value}`)}
- className="service-tabs"
- >
- {codes.map((item) => (
-
- {renderQRCode(item)}
-
- ))}
-
-
- )
-}
-
-export default WechatService
diff --git a/src/doctor/withdraw/index.tsx b/src/doctor/withdraw/index.tsx
deleted file mode 100644
index 18f257b..0000000
--- a/src/doctor/withdraw/index.tsx
+++ /dev/null
@@ -1,461 +0,0 @@
-import React, {useState, useEffect, useCallback} from 'react'
-import {View, Text} from '@tarojs/components'
-import {
- Cell,
- Space,
- Button,
- Input,
- CellGroup,
- Tabs,
- Tag,
- Empty,
- ActionSheet,
- Loading,
- PullToRefresh
-} from '@nutui/nutui-react-taro'
-import {Wallet, ArrowRight} from '@nutui/icons-react-taro'
-import {businessGradients} from '@/styles/gradients'
-import Taro from '@tarojs/taro'
-import {useDealerUser} from '@/hooks/useDealerUser'
-import {pageShopDealerWithdraw, addShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw'
-import type {ShopDealerWithdraw} from '@/api/shop/shopDealerWithdraw/model'
-import {ShopDealerBank} from "@/api/shop/shopDealerBank/model";
-import {listShopDealerBank} from "@/api/shop/shopDealerBank";
-import {listCmsWebsiteField} from "@/api/cms/cmsWebsiteField";
-
-interface WithdrawRecordWithDetails extends ShopDealerWithdraw {
- accountDisplay?: string
-}
-
-const DealerWithdraw: React.FC = () => {
- const [activeTab, setActiveTab] = useState('0')
- const [loading, setLoading] = useState(false)
- const [refreshing, setRefreshing] = useState(false)
- const [submitting, setSubmitting] = useState(false)
- const [banks, setBanks] = useState([])
- const [bank, setBank] = useState()
- const [isVisible, setIsVisible] = useState(false)
- const [availableAmount, setAvailableAmount] = useState('0.00')
- const [withdrawRecords, setWithdrawRecords] = useState([])
- const [withdrawAmount, setWithdrawAmount] = useState('')
- const [withdrawValue, setWithdrawValue] = useState('')
-
- const {dealerUser} = useDealerUser()
-
- // Tab 切换处理函数
- const handleTabChange = (value: string | number) => {
- console.log('Tab切换到:', value)
- setActiveTab(value)
-
- // 如果切换到提现记录页面,刷新数据
- if (String(value) === '1') {
- fetchWithdrawRecords().then()
- }
- }
-
- // 获取可提现余额
- const fetchBalance = useCallback(async () => {
- console.log(dealerUser, 'dealerUser...')
- try {
- setAvailableAmount(String(dealerUser?.money || '0.00'))
- } catch (error) {
- console.error('获取余额失败:', error)
- }
- }, [dealerUser])
-
- // 获取提现记录
- const fetchWithdrawRecords = useCallback(async () => {
- if (!dealerUser?.userId) return
-
- try {
- setLoading(true)
- const result = await pageShopDealerWithdraw({
- page: 1,
- limit: 100,
- userId: dealerUser.userId
- })
-
- if (result?.list) {
- const processedRecords = result.list.map(record => ({
- ...record,
- accountDisplay: getAccountDisplay(record)
- }))
- setWithdrawRecords(processedRecords)
- }
- } catch (error) {
- console.error('获取提现记录失败:', error)
- Taro.showToast({
- title: '获取提现记录失败',
- icon: 'error'
- })
- } finally {
- setLoading(false)
- }
- }, [dealerUser?.userId])
-
- function fetchBanks() {
- listShopDealerBank({}).then(data => {
- const list = data.map(d => {
- d.name = d.bankName;
- d.type = d.bankName;
- return d;
- })
- setBanks(list.concat({
- name: '管理银行卡',
- type: 'add'
- }))
- setBank(data[0])
- })
- }
-
- // 格式化账户显示
- const getAccountDisplay = (record: ShopDealerWithdraw) => {
- if (record.payType === 10) {
- return '微信钱包'
- } else if (record.payType === 20 && record.alipayAccount) {
- return `支付宝(${record.alipayAccount.slice(-4)})`
- } else if (record.payType === 30 && record.bankCard) {
- return `${record.bankName || '银行卡'}(尾号${record.bankCard.slice(-4)})`
- }
- return '未知账户'
- }
-
- // 刷新数据
- const handleRefresh = async () => {
- setRefreshing(true)
- await Promise.all([fetchBalance(), fetchWithdrawRecords()])
- setRefreshing(false)
- }
-
- const handleSelect = (item: ShopDealerBank) => {
- if(item.type === 'add'){
- return Taro.navigateTo({
- url: '/doctor/bank/index'
- })
- }
- setBank(item)
- setIsVisible(false)
- }
-
- function fetchCmsField() {
- listCmsWebsiteField({ name: 'WithdrawValue'}).then(res => {
- if(res && res.length > 0){
- const text = res[0].value;
- setWithdrawValue(text || '')
- }
- })
- }
-
- // 初始化加载数据
- useEffect(() => {
- if (dealerUser?.userId) {
- fetchBalance().then()
- fetchWithdrawRecords().then()
- fetchBanks()
- fetchCmsField()
- }
- }, [fetchBalance, fetchWithdrawRecords])
-
- const getStatusText = (status?: number) => {
- switch (status) {
- case 40:
- return '已到账'
- case 20:
- return '审核通过'
- case 10:
- return '待审核'
- case 30:
- return '已驳回'
- default:
- return '未知'
- }
- }
-
- const getStatusColor = (status?: number) => {
- switch (status) {
- case 40:
- return 'success'
- case 20:
- return 'success'
- case 10:
- return 'warning'
- case 30:
- return 'danger'
- default:
- return 'default'
- }
- }
-
- const handleSubmit = async () => {
- if (!dealerUser?.userId) {
- Taro.showToast({
- title: '用户信息获取失败',
- icon: 'error'
- })
- return
- }
-
- if (!bank) {
- Taro.showToast({
- title: '请选择提现银行卡',
- icon: 'error'
- })
- return
- }
-
- // 验证提现金额
- const amount = parseFloat(withdrawAmount)
- const availableStr = String(availableAmount || '0')
- const available = parseFloat(availableStr.replace(/,/g, ''))
-
- if (isNaN(amount) || amount <= 0) {
- Taro.showToast({
- title: '请输入有效的提现金额',
- icon: 'error'
- })
- return
- }
-
- if (amount < 100) {
- Taro.showToast({
- title: '最低提现金额为100元',
- icon: 'error'
- })
- return
- }
-
- if (amount > available) {
- Taro.showToast({
- title: '提现金额超过可用余额',
- icon: 'error'
- })
- return
- }
-
- // 验证银行卡信息
- if (!bank.bankCard || !bank.bankAccount || !bank.bankName) {
- Taro.showToast({
- title: '银行卡信息不完整',
- icon: 'error'
- })
- return
- }
-
- try {
- setSubmitting(true)
-
- const withdrawData: ShopDealerWithdraw = {
- userId: dealerUser.userId,
- money: withdrawAmount,
- payType: 30, // 银行卡提现
- applyStatus: 10, // 待审核
- platform: 'MiniProgram',
- bankCard: bank.bankCard,
- bankAccount: bank.bankAccount,
- bankName: bank.bankName
- }
-
- await addShopDealerWithdraw(withdrawData)
-
- Taro.showToast({
- title: '提现申请已提交',
- icon: 'success'
- })
-
- // 重置表单
- setWithdrawAmount('')
-
- // 刷新数据
- await handleRefresh()
-
- // 切换到提现记录页面
- setActiveTab('1')
-
- } catch (error: any) {
- console.error('提现申请失败:', error)
- Taro.showToast({
- title: error.message || '提现申请失败',
- icon: 'error'
- })
- } finally {
- setSubmitting(false)
- }
- }
-
- // 格式化金额
- const formatMoney = (money?: string) => {
- if (!money) return '0.00'
- return parseFloat(money).toFixed(2)
- }
-
- // 计算预计到账金额
- const calculateExpectedAmount = (amount: string) => {
- if (!amount || isNaN(parseFloat(amount))) return '0.00'
- const withdrawAmount = parseFloat(amount)
- // 提现费率 16% + 3元
- const feeRate = 0.16
- const fixedFee = 3
- const totalFee = withdrawAmount * feeRate + fixedFee
- const expectedAmount = withdrawAmount - totalFee
- return Math.max(0, expectedAmount).toFixed(2)
- }
-
- const renderWithdrawForm = () => (
-
- {/* 余额卡片 */}
-
- {/* 装饰背景 - 小程序兼容版本 */}
-
-
-
-
- {formatMoney(dealerUser?.money)}
- 可提现余额
-
-
-
-
-
-
-
- 最低提现金额:¥100
-
-
-
-
-
-
- ¥
- setWithdrawAmount(value)}
- style={{
- padding: '0 10px',
- fontSize: '20px'
- }}
- />
-
- |
- }
- />
- setIsVisible(true)} extra={
-
- {bank ? {bank.bankName} : 请选择}
-
-
- }/>
- |
- ¥{calculateExpectedAmount(withdrawAmount)}
- | |
- }/>
- 说明:{withdrawValue}}/>
- |
-
-
-
-
-
- )
-
- const renderWithdrawRecords = () => {
- console.log('渲染提现记录:', {loading, recordsCount: withdrawRecords.length, dealerUserId: dealerUser?.userId})
-
- return (
-
-
- {loading ? (
-
-
- 加载中...
-
- ) : withdrawRecords.length > 0 ? (
- withdrawRecords.map(record => (
-
-
-
-
- 提现金额:¥{record.money}
-
-
- 提现账户:{record.accountDisplay}
-
-
-
- {getStatusText(record.applyStatus)}
-
-
-
-
- 申请时间:{record.createTime}
- {record.auditTime && (
-
- 审核时间:{new Date(record.auditTime).toLocaleString()}
-
- )}
- {record.rejectReason && (
-
- 驳回原因:{record.rejectReason}
-
- )}
-
-
- ))
- ) : (
-
- )}
-
-
- )
- }
-
- return (
-
-
-
- {renderWithdrawForm()}
-
-
-
- {renderWithdrawRecords()}
-
-
- setIsVisible(false)}
- />
-
- )
-}
-
-export default DealerWithdraw
diff --git a/src/gift/index.config.ts b/src/gift/index.config.ts
deleted file mode 100644
index 4599c0a..0000000
--- a/src/gift/index.config.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '礼品卡专区',
- navigationBarTextStyle: 'black'
-})
diff --git a/src/gift/index.tsx b/src/gift/index.tsx
deleted file mode 100644
index 2ad7585..0000000
--- a/src/gift/index.tsx
+++ /dev/null
@@ -1,368 +0,0 @@
-import {useState} from "react";
-import Taro, {useDidShow} from '@tarojs/taro'
-import {
- Empty,
- ConfigProvider,
- InfiniteLoading,
- Loading,
- PullToRefresh,
- Tabs,
- TabPane
-} from '@nutui/nutui-react-taro'
-import {View} from '@tarojs/components'
-import {ShopCoupon} from "@/api/shop/shopCoupon/model";
-import {pageShopCoupon} from "@/api/shop/shopCoupon";
-import CouponList from "@/components/CouponList";
-import CouponGuide from "@/components/CouponGuide";
-import CouponFilter from "@/components/CouponFilter";
-import {CouponCardProps} from "@/components/CouponCard";
-import {takeCoupon} from "@/api/shop/shopUserCoupon";
-
-const CouponReceiveCenter = () => {
- const [list, setList] = useState([])
- const [loading, setLoading] = useState(false)
- const [hasMore, setHasMore] = useState(true)
- const [page, setPage] = useState(1)
- const [activeTab, setActiveTab] = useState('0') // 0-全部 1-满减券 2-折扣券 3-免费券
- const [showGuide, setShowGuide] = useState(false)
- const [showFilter, setShowFilter] = useState(false)
- const [filters, setFilters] = useState({
- type: [] as number[],
- minAmount: undefined as number | undefined,
- sortBy: 'createTime' as 'createTime' | 'amount' | 'expireTime',
- sortOrder: 'desc' as 'asc' | 'desc'
- })
-
- // 获取礼品卡类型过滤条件
- const getTypeFilter = () => {
- switch (String(activeTab)) {
- case '0': // 全部
- return {}
- case '1': // 满减券
- return { type: 10 }
- case '2': // 折扣券
- return { type: 20 }
- case '3': // 免费券
- return { type: 30 }
- default:
- return {}
- }
- }
-
- // 根据传入的值获取类型过滤条件
- const getTypeFilterByValue = (value: string | number) => {
- switch (String(value)) {
- case '0': // 全部
- return {}
- case '1': // 满减券
- return { type: 10 }
- case '2': // 折扣券
- return { type: 20 }
- case '3': // 免费券
- return { type: 30 }
- default:
- return {}
- }
- }
-
- // 根据类型过滤条件加载礼品卡
- const loadCouponsByType = async (typeFilter: any) => {
- setLoading(true)
- try {
- const currentPage = 1
- // 获取可领取的礼品卡(启用状态且未过期)
- const res = await pageShopCoupon({
- page: currentPage,
- limit: 10,
- keywords: '',
- enabled: 1, // 启用状态
- isExpire: 0, // 未过期
- ...typeFilter
- })
-
- console.log('API返回数据:', res)
- if (res && res.list) {
- setList(res.list)
- setHasMore(res.list.length === 10)
- setPage(2)
- } else {
- setList([])
- setHasMore(false)
- }
- } catch (error) {
- console.error('获取礼品卡失败:', error)
- Taro.showToast({
- title: '获取礼品卡失败',
- icon: 'error'
- })
- } finally {
- setLoading(false)
- }
- }
-
- const reload = async (isRefresh = false) => {
- if (isRefresh) {
- setPage(1)
- setList([])
- setHasMore(true)
- }
-
- setLoading(true)
- try {
- const currentPage = isRefresh ? 1 : page
- const typeFilter = getTypeFilter()
- console.log('reload - 当前activeTab:', activeTab, '类型过滤:', typeFilter)
-
- // 获取可领取的礼品卡(启用状态且未过期)
- const res = await pageShopCoupon({
- page: currentPage,
- limit: 10,
- keywords: '',
- enabled: 1, // 启用状态
- isExpire: 0, // 未过期
- ...typeFilter,
- // 应用筛选条件
- ...(filters.type.length > 0 && { type: filters.type[0] }),
- sortBy: filters.sortBy,
- sortOrder: filters.sortOrder
- })
-
- console.log('reload - API返回数据:', res)
- if (res && res.list) {
- const newList = isRefresh ? res.list : [...list, ...res.list]
- setList(newList)
-
- // 判断是否还有更多数据
- setHasMore(res.list.length === 10)
-
- if (!isRefresh) {
- setPage(currentPage + 1)
- } else {
- setPage(2)
- }
- } else {
- setHasMore(false)
- }
- } catch (error) {
- console.error('获取礼品卡失败:', error)
- Taro.showToast({
- title: '获取礼品卡失败',
- icon: 'error'
- });
- } finally {
- setLoading(false)
- }
- }
-
- // 下拉刷新
- const handleRefresh = async () => {
- await reload(true)
- }
-
- // Tab切换
- const handleTabChange = (value: string | number) => {
- console.log('Tab切换到:', value)
- setActiveTab(String(value))
- setPage(1)
- setList([])
- setHasMore(true)
-
- // 直接传递类型值,避免异步状态更新问题
- const typeFilter = getTypeFilterByValue(value)
- console.log('类型过滤条件:', typeFilter)
-
- // 立即加载数据
- loadCouponsByType(typeFilter)
- }
-
- // 转换礼品卡数据为CouponCard组件所需格式
- const transformCouponData = (coupon: ShopCoupon): CouponCardProps => {
- let amount = 0
- let type: 10 | 20 | 30 = 10
-
- if (coupon.type === 10) { // 满减券
- type = 10
- amount = parseFloat(coupon.reducePrice || '0')
- } else if (coupon.type === 20) { // 折扣券
- type = 20
- amount = coupon.discount || 0
- } else if (coupon.type === 30) { // 免费券
- type = 30
- amount = 0
- }
-
- return {
- id: coupon.id?.toString(),
- amount,
- type,
- status: 0, // 可领取状态
- minAmount: parseFloat(coupon.minPrice || '0'),
- title: coupon.name || '礼品卡',
- description: coupon.description,
- startTime: coupon.startTime,
- endTime: coupon.endTime,
- showReceiveBtn: true, // 显示领取按钮
- onReceive: () => handleReceiveCoupon(coupon),
- theme: getThemeByType(coupon.type)
- }
- }
-
- // 根据礼品卡类型获取主题色
- const getThemeByType = (type?: number): 'red' | 'orange' | 'blue' | 'purple' | 'green' => {
- switch (type) {
- case 10: return 'red' // 满减券-红色
- case 20: return 'orange' // 折扣券-橙色
- case 30: return 'green' // 免费券-绿色
- default: return 'blue'
- }
- }
-
- // 领取礼品卡
- const handleReceiveCoupon = async (coupon: ShopCoupon) => {
- try {
- // 检查是否已登录
- const userId = Taro.getStorageSync('UserId')
- if (!userId) {
- Taro.showToast({
- title: '请先登录',
- icon: 'error'
- })
- return
- }
-
- // 调用领取接口
- await takeCoupon({
- couponId: coupon.id!,
- userId: userId
- })
-
- Taro.showToast({
- title: '领取成功',
- icon: 'success'
- })
-
- // 刷新列表
- reload(true)
- } catch (error: any) {
- console.error('领取礼品卡失败:', error)
- Taro.showToast({
- title: error.message || '领取失败',
- icon: 'none'
- })
- }
- }
-
- // 筛选条件变更
- const handleFiltersChange = (newFilters: any) => {
- setFilters(newFilters)
- reload(true)
- }
-
- // 查看我的礼品卡
- const handleViewMyCoupons = () => {
- Taro.navigateTo({
- url: '/user/coupon/index'
- })
- }
-
- // 加载更多
- const loadMore = async () => {
- if (!loading && hasMore) {
- await reload(false) // 不刷新,追加数据
- }
- }
-
- useDidShow(() => {
- reload(true).then()
- });
-
- return (
-
- {/* Tab切换 */}
-
-
-
-
-
-
-
-
-
-
-
-
-
- {/* 礼品卡列表 - 占满剩余空间 */}
-
-
-
- {list.length === 0 && !loading ? (
-
-
-
- ) : (
-
-
- 加载中...
-
- }
- loadMoreText={
-
- {list.length === 0 ? "暂无数据" : "没有更多了"}
-
- }
- >
-
-
- )}
-
-
-
-
- {/* 使用指南弹窗 */}
- setShowGuide(false)}
- />
-
- {/* 筛选弹窗 */}
- setShowFilter(false)}
- />
-
- );
-
-};
-
-export default CouponReceiveCenter;
diff --git a/src/hooks/useConfig.ts b/src/hooks/useConfig.ts
index e246713..d572bb4 100644
--- a/src/hooks/useConfig.ts
+++ b/src/hooks/useConfig.ts
@@ -19,7 +19,7 @@ export const useConfig = () => {
const data = await configWebsiteField();
setConfig(data);
Taro.setStorageSync('config', data);
-
+
// 设置主题
if (data.theme && !Taro.getStorageSync('user_theme')) {
Taro.setStorageSync('user_theme', data.theme);
@@ -41,11 +41,10 @@ export const useConfig = () => {
configWebsiteField().then(data => {
setConfig(data);
Taro.setStorageSync('config', data);
-
setLoading(false);
}).catch(err => {
setError(err instanceof Error ? err : new Error('获取配置失败'));
setLoading(false);
});
}};
-};
+};
\ No newline at end of file
diff --git a/src/hooks/useOrderStats.ts b/src/hooks/useOrderStats.ts
index a26c29e..75baa07 100644
--- a/src/hooks/useOrderStats.ts
+++ b/src/hooks/useOrderStats.ts
@@ -1,7 +1,6 @@
import { useState, useEffect, useCallback } from 'react';
-import { UserOrderStats } from '@/api/user';
+import { getUserOrderStats, UserOrderStats } from '@/api/user';
import Taro from '@tarojs/taro';
-import {pageShopOrder} from "@/api/shop/shopOrder";
/**
* 订单统计Hook
@@ -31,20 +30,17 @@ export const useOrderStats = () => {
if(!Taro.getStorageSync('UserId')){
return false;
}
- // TODO 读取订单数量
- const pending = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), statusFilter: 0})
- const paid = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), statusFilter: 1})
- const shipped = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), statusFilter: 3})
- const completed = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), statusFilter: 5})
- const refund = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), statusFilter: 6})
- const total = await pageShopOrder({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId')})
+
+ // 聚合接口:一次请求返回各状态数量(后台按用户做了缓存)
+ const stats = await getUserOrderStats();
+
setOrderStats({
- pending: pending?.count || 0,
- paid: paid?.count || 0,
- shipped: shipped?.count || 0,
- completed: completed?.count || 0,
- refund: refund?.count || 0,
- total: total?.count || 0
+ pending: stats?.pending || 0,
+ paid: stats?.paid || 0,
+ shipped: stats?.shipped || 0,
+ completed: stats?.completed || 0,
+ refund: stats?.refund || 0,
+ total: stats?.total || 0
})
if (showToast) {
diff --git a/src/hooks/useTheme.ts b/src/hooks/useTheme.ts
index f6684da..0621f86 100644
--- a/src/hooks/useTheme.ts
+++ b/src/hooks/useTheme.ts
@@ -1,5 +1,5 @@
-import { useState, useEffect } from 'react'
-import { gradientThemes, GradientTheme, gradientUtils } from '@/styles/gradients'
+import { useState, useEffect, useCallback } from 'react'
+import { gradientThemes, type GradientTheme, gradientUtils } from '@/styles/gradients'
import Taro from '@tarojs/taro'
export interface UseThemeReturn {
@@ -14,28 +14,42 @@ export interface UseThemeReturn {
* 提供主题切换和状态管理功能
*/
export const useTheme = (): UseThemeReturn => {
- const [currentTheme, setCurrentTheme] = useState(gradientThemes[0])
- const [isAutoTheme, setIsAutoTheme] = useState(true)
-
- // 获取当前主题
- const getCurrentTheme = (): GradientTheme => {
- const savedTheme = Taro.getStorageSync('user_theme') || 'auto'
-
- if (savedTheme === 'auto') {
- // 自动主题:根据用户ID生成
- const userId = Taro.getStorageSync('userId') || '1'
- return gradientUtils.getThemeByUserId(userId)
- } else {
- // 手动选择的主题
- return gradientThemes.find(t => t.name === savedTheme) || gradientThemes[0]
+ const getSavedThemeName = useCallback((): string => {
+ try {
+ return Taro.getStorageSync('user_theme') || 'nature'
+ } catch {
+ return 'nature'
}
- }
+ }, [])
+
+ const getStoredUserId = useCallback((): number => {
+ try {
+ const raw = Taro.getStorageSync('UserId') ?? Taro.getStorageSync('userId')
+ const asNumber = typeof raw === 'number' ? raw : parseInt(String(raw || '1'), 10)
+ return Number.isFinite(asNumber) ? asNumber : 1
+ } catch {
+ return 1
+ }
+ }, [])
+
+ const resolveTheme = useCallback(
+ (themeName: string): GradientTheme => {
+ if (themeName === 'auto') {
+ return gradientUtils.getThemeByUserId(getStoredUserId())
+ }
+ return gradientThemes.find(t => t.name === themeName) || gradientUtils.getThemeByName('nature') || gradientThemes[0]
+ },
+ [getStoredUserId]
+ )
+
+ const [isAutoTheme, setIsAutoTheme] = useState(() => getSavedThemeName() === 'auto')
+ const [currentTheme, setCurrentTheme] = useState(() => resolveTheme(getSavedThemeName()))
// 初始化主题
useEffect(() => {
- const savedTheme = Taro.getStorageSync('user_theme') || 'auto'
+ const savedTheme = getSavedThemeName()
setIsAutoTheme(savedTheme === 'auto')
- setCurrentTheme(getCurrentTheme())
+ setCurrentTheme(resolveTheme(savedTheme))
}, [])
// 设置主题
@@ -43,7 +57,7 @@ export const useTheme = (): UseThemeReturn => {
try {
Taro.setStorageSync('user_theme', themeName)
setIsAutoTheme(themeName === 'auto')
- setCurrentTheme(getCurrentTheme())
+ setCurrentTheme(resolveTheme(themeName))
} catch (error) {
console.error('保存主题失败:', error)
}
@@ -51,7 +65,7 @@ export const useTheme = (): UseThemeReturn => {
// 刷新主题(用于自动主题模式下用户信息变更时)
const refreshTheme = () => {
- setCurrentTheme(getCurrentTheme())
+ setCurrentTheme(resolveTheme(getSavedThemeName()))
}
return {
diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts
index 1a662e0..479a0f3 100644
--- a/src/hooks/useUser.ts
+++ b/src/hooks/useUser.ts
@@ -50,7 +50,7 @@ export const useUser = () => {
const inviteParams = getStoredInviteParams()
if (currentPage?.route !== 'dealer/apply/add' && inviteParams?.inviter) {
return Taro.navigateTo({
- url: '/doctor/apply/add'
+ url: '/dealer/apply/add'
});
}
});
diff --git a/src/hooks/useUserData.ts b/src/hooks/useUserData.ts
index fcc0423..4c5e462 100644
--- a/src/hooks/useUserData.ts
+++ b/src/hooks/useUserData.ts
@@ -1,12 +1,10 @@
import { useState, useEffect, useCallback } from 'react'
-import {pageShopUserCoupon} from "@/api/shop/shopUserCoupon";
-import {pageShopGift} from "@/api/shop/shopGift";
import {useUser} from "@/hooks/useUser";
import Taro from '@tarojs/taro'
-import {getUserInfo} from "@/api/layout";
+import { getUserCardStats } from '@/api/user'
interface UserData {
- balance: number
+ balance: string
points: number
coupons: number
giftCards: number
@@ -24,7 +22,7 @@ interface UseUserDataReturn {
loading: boolean
error: string | null
refresh: () => Promise
- updateBalance: (newBalance: number) => void
+ updateBalance: (newBalance: string) => void
updatePoints: (newPoints: number) => void
}
@@ -43,18 +41,14 @@ export const useUserData = (): UseUserDataReturn => {
return;
}
- // 并发请求所有数据
- const [userDataRes, couponsRes, giftCardsRes] = await Promise.all([
- getUserInfo(),
- pageShopUserCoupon({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), status: 0}),
- pageShopGift({ page: 1, limit: 1, userId: Taro.getStorageSync('UserId'), status: 0})
- ])
+ // 聚合接口:一次请求返回余额/积分/优惠券/礼品卡统计(后端可按用户做缓存)
+ const stats = await getUserCardStats()
const newData: UserData = {
- balance: userDataRes?.balance || 0.00,
- points: userDataRes?.points || 0,
- coupons: couponsRes?.count || 0,
- giftCards: giftCardsRes?.count || 0,
+ balance: stats?.balance || '0.00',
+ points: stats?.points || 0,
+ coupons: stats?.coupons || 0,
+ giftCards: stats?.giftCards || 0,
orders: {
pending: 0,
paid: 0,
@@ -78,7 +72,7 @@ export const useUserData = (): UseUserDataReturn => {
}, [fetchUserData])
// 更新余额(本地更新,避免频繁请求)
- const updateBalance = useCallback((newBalance: number) => {
+ const updateBalance = useCallback((newBalance: string) => {
setData(prev => prev ? { ...prev, balance: newBalance } : null)
}, [])
diff --git a/src/pages/cart/cart.tsx b/src/pages/cart/cart.tsx
index 89f07f9..db99bd8 100644
--- a/src/pages/cart/cart.tsx
+++ b/src/pages/cart/cart.tsx
@@ -41,7 +41,7 @@ function Cart() {
useShareAppMessage(() => {
return {
- title: '购物车 - WebSoft Inc.',
+ title: '购物车 - 网宿软件',
success: function () {
console.log('分享成功');
},
diff --git a/src/pages/category/category.config.ts b/src/pages/category/category.config.ts
deleted file mode 100644
index f54b62b..0000000
--- a/src/pages/category/category.config.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '分类'
-})
diff --git a/src/pages/category/category.scss b/src/pages/category/category.scss
deleted file mode 100644
index 30a4e43..0000000
--- a/src/pages/category/category.scss
+++ /dev/null
@@ -1,425 +0,0 @@
-.category-container {
- display: flex;
- height: calc(100vh - 1rpx);
- background-color: #f5f5f5;
-}
-
-/* 左侧分类导航 */
-.category-left {
- width: 200rpx;
- background-color: #fff;
- border-right: 2rpx solid #f0f0f0;
- position: relative;
- z-index: 10;
-
- .category-scroll {
- height: calc(100vh - 1rpx);
- }
-
- .category-item {
- padding: 30rpx 20rpx;
- background-color: #fff;
- transition: all 0.3s ease;
- position: relative;
- cursor: pointer;
-
- &:hover {
- background-color: #f8f8f8;
- }
-
- &.active {
- background-color: #fff;
- color: #ff6b35;
-
- &::before {
- content: '';
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%);
- width: 8rpx;
- height: 50rpx;
- background: linear-gradient(180deg, #ff6b35 0%, #ff8f6b 100%);
- border-radius: 0 8rpx 8rpx 0;
- box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.3);
- }
-
- &::after {
- content: '';
- position: absolute;
- right: 0;
- top: 0;
- bottom: 0;
- width: 2rpx;
- background-color: #fff;
- z-index: 1;
- }
- }
-
- .category-name {
- font-size: 28rpx;
- color: #333;
- font-weight: 500;
- display: block;
- text-align: center;
- line-height: 1.2;
- transition: color 0.3s ease;
- }
-
- .category-count {
- font-size: 20rpx;
- color: #999;
- margin-top: 8rpx;
- display: block;
- text-align: center;
- }
-
- &.active .category-name {
- color: #ff6b35;
- font-weight: 600;
- }
-
- &.active .category-count {
- color: #ff6b35;
- }
- }
-}
-
-/* 右侧商品列表 */
-.category-right {
- flex: 1;
- background-color: #fff;
- position: relative;
-
- .goods-scroll {
- height: calc(100vh - 1rpx);
- }
-
- .goods-section {
- .section-title {
- position: sticky;
- top: 0;
- background: linear-gradient(135deg, #f8f8f8 0%, #f0f0f0 100%);
- padding: 24rpx 30rpx;
- border-bottom: 2rpx solid #f0f0f0;
- z-index: 10;
-
- text {
- font-size: 32rpx;
- font-weight: 600;
- color: #333;
- }
- }
-
- .goods-list {
- padding: 20rpx 30rpx;
- }
-
- .goods-item {
- display: flex;
- padding: 24rpx 0;
- border-bottom: 2rpx solid #f8f8f8;
- transition: all 0.3s ease;
- border-radius: 12rpx;
- margin-bottom: 8rpx;
- cursor: pointer;
-
- &:last-child {
- border-bottom: none;
- margin-bottom: 0;
- }
-
- &:hover {
- background-color: #fafafa;
- transform: translateY(-2rpx);
- box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
- }
-
- .goods-image {
- width: 160rpx;
- height: 160rpx;
- border-radius: 16rpx;
- margin-right: 24rpx;
- flex-shrink: 0;
- box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
- transition: transform 0.3s ease;
-
- &:hover {
- transform: scale(1.05);
- }
- }
-
- .goods-info {
- flex: 1;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
-
- .goods-name {
- font-size: 30rpx;
- color: #333;
- font-weight: 600;
- line-height: 1.4;
- margin-bottom: 8rpx;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 2;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .goods-desc {
- font-size: 24rpx;
- color: #666;
- line-height: 1.3;
- margin-bottom: 12rpx;
- display: -webkit-box;
- -webkit-box-orient: vertical;
- -webkit-line-clamp: 2;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- .goods-price-row {
- display: flex;
- align-items: center;
- margin-bottom: 8rpx;
-
- .goods-price {
- font-size: 36rpx;
- color: #ff6b35;
- font-weight: 700;
- margin-right: 16rpx;
- position: relative;
-
- &::before {
- content: '¥';
- font-size: 24rpx;
- position: relative;
- top: -2rpx;
- }
- }
-
- .goods-original-price {
- font-size: 24rpx;
- color: #999;
- text-decoration: line-through;
- position: relative;
-
- &::before {
- content: '¥';
- font-size: 20rpx;
- }
- }
- }
-
- .goods-stock {
- font-size: 22rpx;
- color: #999;
- background-color: #f8f8f8;
- padding: 4rpx 8rpx;
- border-radius: 4rpx;
- display: inline-block;
- width: fit-content;
- }
- }
- }
-
- .empty-section {
- padding: 120rpx 30rpx;
- text-align: center;
-
- text {
- font-size: 28rpx;
- color: #999;
- position: relative;
-
- &::before {
- content: '📋';
- display: block;
- font-size: 80rpx;
- margin-bottom: 20rpx;
- }
- }
- }
- }
-}
-
-/* 骨架屏样式 */
-.category-skeleton {
- display: flex;
- height: calc(100vh - 1rpx);
-
- .category-left {
- width: 200rpx;
- background-color: #fff;
- border-right: 2rpx solid #f0f0f0;
- padding: 20rpx 0;
-
- .skeleton-category-item {
- padding: 30rpx 20rpx;
-
- .skeleton-text {
- height: 32rpx;
- background-color: #f0f0f0;
- border-radius: 4rpx;
- animation: skeleton-loading 1.5s ease-in-out infinite;
- }
- }
- }
-
- .category-right {
- flex: 1;
- background-color: #fff;
- padding: 30rpx;
-
- .skeleton-goods-item {
- display: flex;
- padding: 24rpx 0;
- border-bottom: 2rpx solid #f8f8f8;
-
- .skeleton-image {
- width: 160rpx;
- height: 160rpx;
- background-color: #f0f0f0;
- border-radius: 16rpx;
- margin-right: 24rpx;
- animation: skeleton-loading 1.5s ease-in-out infinite;
- }
-
- .skeleton-info {
- flex: 1;
-
- .skeleton-text {
- height: 32rpx;
- background-color: #f0f0f0;
- border-radius: 4rpx;
- animation: skeleton-loading 1.5s ease-in-out infinite;
- }
- }
- }
- }
-}
-
-@keyframes skeleton-loading {
- 0% {
- opacity: 1;
- }
- 50% {
- opacity: 0.6;
- }
- 100% {
- opacity: 1;
- }
-}
-
-/* 响应式设计 */
-@media (max-width: 750rpx) {
- .category-left {
- width: 160rpx;
-
- .category-item {
- padding: 24rpx 16rpx;
-
- .category-name {
- font-size: 26rpx;
- }
-
- .category-count {
- font-size: 18rpx;
- }
- }
- }
-
- .category-right {
- .goods-section {
- .goods-list {
- padding: 16rpx 20rpx;
- }
-
- .goods-item {
- padding: 20rpx 0;
-
- .goods-image {
- width: 140rpx;
- height: 140rpx;
- margin-right: 20rpx;
- }
-
- .goods-info {
- .goods-name {
- font-size: 28rpx;
- }
-
- .goods-desc {
- font-size: 22rpx;
- }
-
- .goods-price-row {
- .goods-price {
- font-size: 32rpx;
- }
- }
- }
- }
- }
- }
-}
-
-/* 空状态样式 */
-.empty-container {
- height: calc(100vh - 1rpx);
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #f5f5f5;
-
- .empty-content {
- text-align: center;
- padding: 60rpx 40rpx;
-
- .empty-icon {
- font-size: 120rpx;
- display: block;
- margin-bottom: 30rpx;
- }
-
- .empty-title {
- font-size: 32rpx;
- color: #333;
- font-weight: 600;
- display: block;
- margin-bottom: 16rpx;
- }
-
- .empty-desc {
- font-size: 28rpx;
- color: #666;
- display: block;
- margin-bottom: 40rpx;
- line-height: 1.4;
- }
-
- .empty-action {
- background: linear-gradient(135deg, #ff6b35 0%, #ff8f6b 100%);
- color: #fff;
- padding: 20rpx 40rpx;
- border-radius: 50rpx;
- font-size: 28rpx;
- font-weight: 600;
- box-shadow: 0 4rpx 12rpx rgba(255, 107, 53, 0.3);
- transition: all 0.3s ease;
- cursor: pointer;
-
- &:hover {
- transform: translateY(-2rpx);
- box-shadow: 0 6rpx 16rpx rgba(255, 107, 53, 0.4);
- }
-
- &:active {
- transform: translateY(0);
- box-shadow: 0 2rpx 8rpx rgba(255, 107, 53, 0.3);
- }
- }
- }
-}
diff --git a/src/pages/category/category.tsx b/src/pages/category/category.tsx
deleted file mode 100644
index 02f67ce..0000000
--- a/src/pages/category/category.tsx
+++ /dev/null
@@ -1,271 +0,0 @@
-import Taro from '@tarojs/taro'
-import { useShareAppMessage } from "@tarojs/taro"
-import { useEffect, useState, useRef, useCallback } from "react"
-import { View, Text, ScrollView } from '@tarojs/components'
-import { Image } from '@nutui/nutui-react-taro'
-import {listCmsNavigation} from "@/api/cms/cmsNavigation"
-import { CmsNavigation } from "@/api/cms/cmsNavigation/model"
-import { pageShopGoods } from "@/api/shop/shopGoods"
-import { ShopGoods } from "@/api/shop/shopGoods/model"
-import './category.scss'
-
-function Category() {
- const [loading, setLoading] = useState(true)
- const [categories, setCategories] = useState([])
- const [selectedCategoryId, setSelectedCategoryId] = useState(0)
- const [goods, setGoods] = useState<{ [key: number]: ShopGoods[] }>({})
- const [allGoods, setAllGoods] = useState([])
- const rightScrollRef = useRef(null)
- const [scrollIntoView, setScrollIntoView] = useState('')
- const [isScrollingByClick, setIsScrollingByClick] = useState(false)
-
- // 初始化数据
- const initData = async () => {
- try {
- setLoading(true)
- const home = await listCmsNavigation({home: 1})
- // 获取商品分类
- const categoryList = await listCmsNavigation({ parentId: home[0].navigationId || 0, position: 1 })
-
- if (!categoryList || categoryList.length === 0) {
- Taro.showToast({
- title: '暂无商品分类',
- icon: 'none'
- })
- setLoading(false)
- return
- }
-
- setCategories(categoryList)
- const firstCategory = categoryList[0]
- setSelectedCategoryId(firstCategory.navigationId!)
-
- // 并行获取所有分类的商品数据
- const goodsPromises = categoryList.map((category: CmsNavigation) =>
- pageShopGoods({ categoryId: category.navigationId }).catch(err => {
- console.error(`分类 ${category.title} 商品加载失败:`, err)
- return { list: [] }
- })
- )
-
- const goodsResults = await Promise.all(goodsPromises)
-
- // 组织商品数据
- const goodsByCategory: { [key: number]: ShopGoods[] } = {}
- categoryList.forEach((category: CmsNavigation, index: number) => {
- goodsByCategory[category.navigationId!] = goodsResults[index]?.list || []
- })
-
- setGoods(goodsByCategory)
-
- // 获取所有商品用于搜索等功能
- try {
- const allGoodsRes = await pageShopGoods({})
- setAllGoods(allGoodsRes?.list || [])
- } catch (err) {
- console.error('获取所有商品失败:', err)
- }
-
- Taro.setNavigationBarTitle({
- title: '商品分类'
- })
- } catch (error) {
- console.error('分类数据加载失败:', error)
- Taro.showToast({
- title: '加载失败,请重试',
- icon: 'none'
- })
- } finally {
- setLoading(false)
- }
- }
-
- useEffect(() => {
- initData().then()
- console.log(allGoods,'allGoods')
- }, [])
-
- // 点击左侧分类
- const handleCategoryClick = (categoryId: number) => {
- setIsScrollingByClick(true)
- setSelectedCategoryId(categoryId)
- setScrollIntoView(`category-${categoryId}`)
-
- // 延迟重置滚动标志
- setTimeout(() => {
- setIsScrollingByClick(false)
- }, 1000)
- }
-
- // 右侧滚动时处理分类切换
- const handleRightScroll = useCallback((e: any) => {
- console.log(e,'右侧滚动时处理分类切换')
- if (isScrollingByClick) return
-
- // 这里可以添加逻辑来检测当前滚动到哪个分类
- // 由于小程序限制,暂时简化处理
- }, [isScrollingByClick])
-
- // 跳转商品详情
- const goToGoodsDetail = (goodsId: number) => {
- Taro.navigateTo({ url: `/shop/goodsDetail/index?id=${goodsId}` })
- }
-
- useShareAppMessage(() => {
- return {
- title: '商品分类',
- path: '/pages/category/category',
- success: function () {
- console.log('分享成功')
- },
- fail: function () {
- console.log('分享失败')
- }
- }
- })
-
- // 骨架屏组件
- const CategorySkeleton = () => (
-
-
- {[1, 2, 3, 4, 5].map(i => (
-
-
-
- ))}
-
-
- {[1, 2, 3, 4].map(i => (
-
-
-
-
-
-
-
- ))}
-
-
- )
-
- if (loading) {
- return
- }
-
- // 空状态处理
- if (!categories || categories.length === 0) {
- return (
-
-
- 📋
- 暂无商品分类
- 请稍后再试或联系客服
-
- 重新加载
-
-
-
- )
- }
-
- return (
-
- {/* 左侧分类导航 */}
-
- {categories.map((category) => (
- handleCategoryClick(category.navigationId!)}
- >
- {category.title}
-
- ))}
-
-
-
- {/* 右侧商品列表 */}
-
-
- {categories.map((category) => {
- const categoryGoods = goods[category.navigationId!] || []
- return (
-
-
- {category.title}
-
-
- {categoryGoods.length > 0 ? (
-
- {categoryGoods.map((item) => (
- goToGoodsDetail(item.goodsId!)}
- >
-
-
- {item.name}
- {item.comments && (
- {item.comments}
- )}
-
- ¥{item.price}
- {item.salePrice && Number(item.salePrice) !== Number(item.price) && (
- ¥{item.salePrice}
- )}
-
- {item.stock !== undefined && (
-
- 库存: {item.stock > 0 ? item.stock : '缺货'}
-
- )}
-
-
- ))}
-
- ) : (
-
- 该分类暂无商品
-
- )}
-
- )
- })}
-
-
-
- )
-}
-
-export default Category
diff --git a/src/doctor/orders/add.config.ts b/src/pages/category/index.config.ts
similarity index 62%
rename from src/doctor/orders/add.config.ts
rename to src/pages/category/index.config.ts
index 00434e4..689ba07 100644
--- a/src/doctor/orders/add.config.ts
+++ b/src/pages/category/index.config.ts
@@ -1,4 +1,4 @@
export default definePageConfig({
- navigationBarTitleText: '在线开方',
+ navigationBarTitleText: '文章列表',
navigationBarTextStyle: 'black'
})
diff --git a/src/pages/index/BestSellers.scss b/src/pages/category/index.scss
similarity index 100%
rename from src/pages/index/BestSellers.scss
rename to src/pages/category/index.scss
diff --git a/src/pages/category/index.tsx b/src/pages/category/index.tsx
new file mode 100644
index 0000000..27002d5
--- /dev/null
+++ b/src/pages/category/index.tsx
@@ -0,0 +1,96 @@
+import Taro from '@tarojs/taro'
+import {useShareAppMessage} from "@tarojs/taro"
+import {useEffect, useState} from "react"
+import {useRouter} from '@tarojs/taro'
+import {View} from '@tarojs/components'
+import {getCmsNavigation, listCmsNavigation} from "@/api/cms/cmsNavigation";
+import {CmsNavigation} from "@/api/cms/cmsNavigation/model";
+import {pageCmsArticle} from "@/api/cms/cmsArticle";
+import {CmsArticle} from "@/api/cms/cmsArticle/model";
+import ArticleList from './components/ArticleList'
+import ArticleTabs from "./components/ArticleTabs";
+import './index.scss'
+
+function Category() {
+ const {params} = useRouter();
+ const [categoryId, setCategoryId] = useState(0)
+ const [category, setCategory] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [nav, setNav] = useState()
+ const [list, setList] = useState([])
+
+ const reload = async () => {
+ try {
+ setLoading(true)
+ // 1.加载远程数据
+ const id = Number(params.id || 4328)
+ const nav = await getCmsNavigation(id)
+ const categoryList = await listCmsNavigation({parentId: id})
+ const shopGoods = await pageCmsArticle({categoryId: id})
+
+ // 2.赋值
+ setCategoryId(id)
+ setNav(nav)
+ setList(shopGoods?.list || [])
+ setCategory(categoryList)
+ Taro.setNavigationBarTitle({
+ title: `${nav?.categoryName}`
+ })
+ } catch (error) {
+ console.error('文章分类加载失败:', error)
+ } finally {
+ setLoading(false)
+ }
+ };
+
+ useEffect(() => {
+ reload()
+ }, []);
+
+ useShareAppMessage(() => {
+ return {
+ title: `${nav?.categoryName}_桂乐淘`,
+ path: `/shop/category/index?id=${categoryId}`,
+ success: function () {
+ console.log('分享成功');
+ },
+ fail: function () {
+ console.log('分享失败');
+ }
+ };
+ });
+
+ // 骨架屏组件
+ const ArticleSkeleton = () => (
+
+ {[1, 2, 3, 4, 5].map(i => (
+
+ {/* 左侧文字骨架屏 */}
+
+ {/* 标题骨架屏 */}
+
+ {/* 副标题骨架屏 */}
+
+
+ {/* 右侧图片骨架屏 */}
+
+
+ ))}
+
+ )
+
+ if (loading) {
+ return
+ }
+
+ if(category.length > 0){
+ return
+ }
+
+ return
+}
+
+export default Category
diff --git a/src/pages/index/Banner.tsx b/src/pages/index/Banner.tsx
index b8506b3..f3b04d3 100644
--- a/src/pages/index/Banner.tsx
+++ b/src/pages/index/Banner.tsx
@@ -6,13 +6,38 @@ import {Image} from '@nutui/nutui-react-taro'
import {getCmsAdByCode} from "@/api/cms/cmsAd";
import navTo from "@/utils/common";
import {pageCmsArticle} from "@/api/cms/cmsArticle";
-import {CmsArticle} from "@/api/cms/cmsArticle/model";
+type AdImage = {
+ url?: string
+ path?: string
+ title?: string
+ // Compatible keys (some backends use different fields)
+ src?: string
+ imageUrl?: string
+}
+
+function normalizeAdImages(ad?: CmsAd): AdImage[] {
+ const list = ad?.imageList
+ if (Array.isArray(list) && list.length) return list as AdImage[]
+
+ // Some APIs only return `images` as a JSON string.
+ const raw = ad?.images
+ if (!raw) return []
+ try {
+ const parsed = JSON.parse(raw)
+ return Array.isArray(parsed) ? (parsed as AdImage[]) : []
+ } catch {
+ return []
+ }
+}
+
+function toNumberPx(input: unknown, fallback: number) {
+ const n = typeof input === 'number' ? input : Number.parseInt(String(input ?? ''), 10)
+ return Number.isFinite(n) ? n : fallback
+}
const MyPage = () => {
const [carouselData, setCarouselData] = useState()
- const [hotToday, setHotToday] = useState()
- const [item, setItem] = useState()
const [loading, setLoading] = useState(true)
// const [disableSwiper, setDisableSwiper] = useState(false)
@@ -21,24 +46,21 @@ const MyPage = () => {
// 加载数据
const loadData = async () => {
+ setLoading(true)
try {
- setLoading(true)
- // 轮播图
- const flash = await getCmsAdByCode('flash')
- // 今日热卖
- const hotToday = await getCmsAdByCode('hot_today')
- // 时里动态
- const news = await pageCmsArticle({limit:1,recommend:1})
- // 赋值
- if(flash){
- setCarouselData(flash)
- }
- if(hotToday){
- setHotToday(hotToday)
- }
- if(news && news.list.length > 0){
- setItem(news.list[0])
+ const [flashRes] = await Promise.allSettled([
+ getCmsAdByCode('mp-ad'),
+ getCmsAdByCode('hot_today'),
+ pageCmsArticle({ limit: 1, recommend: 1 }),
+ ])
+
+ if (flashRes.status === 'fulfilled') {
+ console.log('flashflashflash', flashRes.value)
+ setCarouselData(flashRes.value)
+ } else {
+ console.error('Failed to fetch flash:', flashRes.reason)
}
+
} catch (error) {
console.error('Banner数据加载失败:', error)
} finally {
@@ -47,11 +69,13 @@ const MyPage = () => {
}
useEffect(() => {
- loadData()
+ loadData().then()
}, [])
// 轮播图高度,默认300px
- const carouselHeight = carouselData?.height || 300;
+ const carouselHeight = toNumberPx(carouselData?.height, 300)
+ const carouselImages = normalizeAdImages(carouselData)
+ console.log(carouselImages,'carouselImages')
// 骨架屏组件
const BannerSkeleton = () => (
@@ -100,94 +124,42 @@ const MyPage = () => {
}
return (
-
- {/* 左侧轮播图区域 */}
-
-
- {carouselData && carouselData?.imageList?.map((img, index) => (
-
- navTo(`${img.path}`)}
- lazyLoad={false}
- style={{
- height: `${carouselHeight}px`,
- borderRadius: '4px',
- touchAction: 'manipulation' // 关键修改:优化触摸操作
- }}
- />
-
- ))}
-
-
-
- {/* 右侧上下图片区域 - 从API获取数据 */}
-
- {/* 上层图片 - 使用今日热卖素材 */}
-
- 今日热卖
-
- {
- hotToday?.imageList?.map(item => (
-
- navTo(item.path)}
- />
- {item.title || '到手价¥9.9'}
-
- ))
- }
-
-
-
- {/* 下层图片 - 使用社区拼团素材 rounded-lg shadow-sm */}
-
- {item?.overview || item?.categoryName || '推荐文章'}
-
+
+ {carouselImages.map((img, index) => {
+ const src = img.url || img.src || img.imageUrl
+ if (!src) return null
+ return (
+
(img.path ? navTo(`${img.path}`) : undefined)}
lazyLoad={false}
style={{
- borderRadius: '4px'
+ height: `${carouselHeight}px`,
+ borderRadius: '4px',
+ touchAction: 'manipulation' // 关键修改:优化触摸操作
}}
- onClick={() => navTo('cms/detail/index?id=' + item?.articleId)}
/>
-
-
-
-
+
+ )
+ })}
+
)
}
diff --git a/src/pages/index/BestSellers.tsx b/src/pages/index/BestSellers.tsx
index 1ada7ca..1b85144 100644
--- a/src/pages/index/BestSellers.tsx
+++ b/src/pages/index/BestSellers.tsx
@@ -124,6 +124,8 @@ const BestSellers = (props: {onStickyChange?: (isSticky: boolean) => void}) => {
¥
{item.price}
+ 会员价
+ ¥{item.salePrice}
diff --git a/src/pages/index/GoodsList.tsx b/src/pages/index/GoodsList.tsx
index 9292a89..f1eaf02 100644
--- a/src/pages/index/GoodsList.tsx
+++ b/src/pages/index/GoodsList.tsx
@@ -1,15 +1,14 @@
import {useEffect, useState} from "react";
-import {Image, Tabs, Empty, Sticky} from '@nutui/nutui-react-taro'
-import {View, Text} from '@tarojs/components';
-import Taro from "@tarojs/taro";
+import {Image} from '@nutui/nutui-react-taro'
+import {Share} from '@nutui/icons-react-taro'
+import Taro from '@tarojs/taro'
import {ShopGoods} from "@/api/shop/shopGoods/model";
import {pageShopGoods} from "@/api/shop/shopGoods";
-import './GoodsList.scss';
+import './GoodsList.scss'
-const GoodsList = (props: {onStickyChange?: (isSticky: boolean) => void}) => {
- const [tab1value, setTab1value] = useState('0')
+
+const BestSellers = () => {
const [list, setList] = useState([])
- const [stickyStatus, setStickyStatus] = useState(false)
const reload = () => {
pageShopGoods({}).then(res => {
@@ -17,127 +16,52 @@ const GoodsList = (props: {onStickyChange?: (isSticky: boolean) => void}) => {
})
}
- // 处理粘性布局状态变化
- const onStickyChange = (isSticky: boolean) => {
- setStickyStatus(isSticky)
- // 通知父组件粘性状态变化
- props.onStickyChange?.(isSticky)
- console.log('Tabs 粘性状态:', isSticky ? '已固定' : '取消固定')
- }
-
- // 获取小程序系统信息
- const getSystemInfo = () => {
- const systemInfo = Taro.getSystemInfoSync()
- // 状态栏高度 + 导航栏高度 (一般为44px)
- return (systemInfo.statusBarHeight || 0) + 44
- }
-
useEffect(() => {
reload()
}, [])
return (
<>
-
- {/* Tabs粘性布局组件 */}
-
- {
- setTab1value(value)
- }}
- style={{
- backgroundColor: 'transparent',
- paddingTop: stickyStatus ? '0' : '8px',
- paddingBottom: stickyStatus ? '8px' : '0',
- }}
- activeType="smile"
- >
-
-
-
-
-
-
-
-
-
-
- {/* 今日主推 - 瀑布流布局 */}
- {tab1value == '0' && (
-
- {list?.map((item, index) => {
- return (
-
-
- Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}
- />
-
- {item.name}
-
- {item.comments}
-
-
-
- ¥
- {item.price}
-
-
- 已售 {item.sales}
-
-
-
-
-
- )
- })}
-
- )}
-
- {/* 即将到期 */}
- {tab1value == '1' && (
-
- )}
-
- {/* 活动预告 */}
- {tab1value == '2' && (
-
- )}
-
-
+
+
+ {list?.map((item, index) => {
+ return (
+
+
Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
+
+
+
{item.name}
+
+ {item.comments}
+ 已售 {item.sales}
+
+
+
+ ¥
+ {item.price}
+
+
+
+ Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
+
+
Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}>购买
+
+
+
+
+
+
+ )
+ })}
+
+
>
)
}
-export default GoodsList
+export default BestSellers
diff --git a/src/pages/index/Header.scss b/src/pages/index/Header.scss
index 56a75fb..e79ce78 100644
--- a/src/pages/index/Header.scss
+++ b/src/pages/index/Header.scss
@@ -1,5 +1,5 @@
.header-bg{
- background: linear-gradient(to bottom, #FFD700, #FFA500);
+ background: linear-gradient(to bottom, #03605c, #18ae4f);
height: 335px;
width: 100%;
top: 0;
@@ -7,7 +7,7 @@
z-index: 0;
}
.header-bg2{
- background: linear-gradient(to bottom, #FFD700, #FFA500);
+ background: linear-gradient(to bottom, #03605c, #18ae4f);
height: 200px;
width: 100%;
top: 0;
@@ -20,4 +20,4 @@
.header-bg {
height: 100%;
}
-}
+}
\ No newline at end of file
diff --git a/src/pages/index/Header.tsx b/src/pages/index/Header.tsx
index ec151f8..d8a000d 100644
--- a/src/pages/index/Header.tsx
+++ b/src/pages/index/Header.tsx
@@ -1,10 +1,10 @@
import {useEffect, useState} from "react";
import Taro from '@tarojs/taro';
-import {Button, Space, Sticky} from '@nutui/nutui-react-taro'
+import {Button, Sticky, Popup, Cell, CellGroup} from '@nutui/nutui-react-taro'
import {TriangleDown} from '@nutui/icons-react-taro'
import {Avatar, NavBar} from '@nutui/nutui-react-taro'
import {getUserInfo, getWxOpenId} from "@/api/layout";
-import {TenantId} from "@/config/app";
+import {TenantId, TenantName} from "@/config/app";
import {getOrganization} from "@/api/system/organization";
import {myUserVerify} from "@/api/system/userVerify";
import { useShopInfo } from '@/hooks/useShopInfo';
@@ -12,17 +12,141 @@ import {handleInviteRelation, getStoredInviteParams} from "@/utils/invite";
import {View,Text} from '@tarojs/components'
import MySearch from "./MySearch";
import './Header.scss';
+import {User} from "@/api/system/user/model";
+import {getShopStore, listShopStore} from "@/api/shop/shopStore";
+import type {ShopStore} from "@/api/shop/shopStore/model";
+import {getSelectedStoreFromStorage, saveSelectedStoreToStorage} from "@/utils/storeSelection";
const Header = (_: any) => {
// 使用新的useShopInfo Hook
const {
- getWebsiteName,
getWebsiteLogo
} = useShopInfo();
const [IsLogin, setIsLogin] = useState(true)
const [statusBarHeight, setStatusBarHeight] = useState()
const [stickyStatus, setStickyStatus] = useState(false)
+ const [userInfo] = useState()
+
+ // 门店选择:用于首页展示“最近门店”,并在下单时写入订单 storeId
+ const [storePopupVisible, setStorePopupVisible] = useState(false)
+ const [stores, setStores] = useState([])
+ const [selectedStore, setSelectedStore] = useState(getSelectedStoreFromStorage())
+ const [userLocation, setUserLocation] = useState<{lng: number; lat: number} | null>(null)
+
+ const getTenantName = () => {
+ return userInfo?.tenantName || TenantName
+ }
+
+ const parseStoreCoords = (s: ShopStore): {lng: number; lat: number} | null => {
+ const raw = (s.lngAndLat || s.location || '').trim()
+ if (!raw) return null
+
+ const parts = raw.split(/[,\s]+/).filter(Boolean)
+ if (parts.length < 2) return null
+
+ const a = parseFloat(parts[0])
+ const b = parseFloat(parts[1])
+ if (Number.isNaN(a) || Number.isNaN(b)) return null
+
+ // 常见格式是 "lng,lat";这里做一个简单兜底(经度范围更宽)
+ const looksLikeLngLat = Math.abs(a) <= 180 && Math.abs(b) <= 90
+ const looksLikeLatLng = Math.abs(a) <= 90 && Math.abs(b) <= 180
+ if (looksLikeLngLat) return {lng: a, lat: b}
+ if (looksLikeLatLng) return {lng: b, lat: a}
+ return null
+ }
+
+ const distanceMeters = (a: {lng: number; lat: number}, b: {lng: number; lat: number}) => {
+ const toRad = (x: number) => (x * Math.PI) / 180
+ const R = 6371000 // meters
+ const dLat = toRad(b.lat - a.lat)
+ const dLng = toRad(b.lng - a.lng)
+ const lat1 = toRad(a.lat)
+ const lat2 = toRad(b.lat)
+ const sin1 = Math.sin(dLat / 2)
+ const sin2 = Math.sin(dLng / 2)
+ const h = sin1 * sin1 + Math.cos(lat1) * Math.cos(lat2) * sin2 * sin2
+ return 2 * R * Math.asin(Math.min(1, Math.sqrt(h)))
+ }
+
+ const formatDistance = (meters?: number) => {
+ if (meters === undefined || Number.isNaN(meters)) return ''
+ if (meters < 1000) return `${Math.round(meters)}m`
+ return `${(meters / 1000).toFixed(1)}km`
+ }
+
+ const getStoreDistance = (s: ShopStore) => {
+ if (!userLocation) return undefined
+ const coords = parseStoreCoords(s)
+ if (!coords) return undefined
+ return distanceMeters(userLocation, coords)
+ }
+
+ const initStoreSelection = async () => {
+ // 先读取本地已选门店,避免页面首屏抖动
+ const stored = getSelectedStoreFromStorage()
+ if (stored?.id) {
+ setSelectedStore(stored)
+ }
+
+ // 拉取门店列表(失败时允许用户手动重试/继续使用本地门店)
+ let list: ShopStore[] = []
+ try {
+ list = await listShopStore()
+ } catch (e) {
+ console.error('获取门店列表失败:', e)
+ list = []
+ }
+ const usable = (list || []).filter(s => s?.isDelete !== 1)
+ setStores(usable)
+
+ // 尝试获取定位,用于计算最近门店
+ let loc: {lng: number; lat: number} | null = null
+ try {
+ const r = await Taro.getLocation({type: 'gcj02'})
+ loc = {lng: r.longitude, lat: r.latitude}
+ } catch (e) {
+ // 不强制定位授权;无定位时仍允许用户手动选择
+ console.warn('获取定位失败,将不显示最近门店距离:', e)
+ }
+ setUserLocation(loc)
+
+ const ensureStoreDetail = async (s: ShopStore) => {
+ if (!s?.id) return s
+ // 如果后端已经返回默认仓库等字段,就不额外请求
+ if (s.warehouseId) return s
+ try {
+ const full = await getShopStore(s.id)
+ return full || s
+ } catch (_e) {
+ return s
+ }
+ }
+
+ // 若用户没有选过门店,则自动选择最近门店(或第一个)
+ const alreadySelected = stored?.id
+ if (alreadySelected || usable.length === 0) return
+
+ let autoPick: ShopStore | undefined
+ if (loc) {
+ autoPick = [...usable]
+ .map(s => {
+ const coords = parseStoreCoords(s)
+ const d = coords ? distanceMeters(loc, coords) : undefined
+ return {s, d}
+ })
+ .sort((x, y) => (x.d ?? Number.POSITIVE_INFINITY) - (y.d ?? Number.POSITIVE_INFINITY))[0]?.s
+ } else {
+ autoPick = usable[0]
+ }
+
+ if (autoPick?.id) {
+ const full = await ensureStoreDetail(autoPick)
+ setSelectedStore(full)
+ saveSelectedStoreToStorage(full)
+ }
+ }
const reload = async () => {
Taro.getSystemInfo({
@@ -182,6 +306,7 @@ const Header = (_: any) => {
useEffect(() => {
reload().then()
+ initStoreSelection().then()
}, [])
return (
@@ -211,31 +336,95 @@ const Header = (_: any) => {
onBackClick={() => {
}}
left={
+ setStorePopupVisible(true)}
+ >
+
+
+ {selectedStore?.name || '请选择'}
+
+
+
+ }
+ right={
!IsLogin ? (
-
-
-
- ) : (
-
-
- {getWebsiteName()}
-
-
- )}>
- {/**/}
+
+ ) : null
+ }
+ >
+ {getTenantName()}
+
+ setStorePopupVisible(false)}
+ >
+
+
+ 选择门店
+ setStorePopupVisible(false)}
+ >
+ 关闭
+
+
+
+
+ {userLocation ? '已获取定位,按距离排序' : '未获取定位,可手动选择门店'}
+
+
+
+ {[...stores]
+ .sort((a, b) => (getStoreDistance(a) ?? Number.POSITIVE_INFINITY) - (getStoreDistance(b) ?? Number.POSITIVE_INFINITY))
+ .map((s) => {
+ const d = getStoreDistance(s)
+ const isActive = !!selectedStore?.id && selectedStore.id === s.id
+ return (
+
+ {s.name || `门店${s.id}`}
+ {d !== undefined && {formatDistance(d)}}
+ |
+ }
+ description={s.address || ''}
+ onClick={async () => {
+ let storeToSave = s
+ if (s?.id) {
+ try {
+ const full = await getShopStore(s.id)
+ if (full) storeToSave = full
+ } catch (_e) {
+ // keep base item
+ }
+ }
+ setSelectedStore(storeToSave)
+ saveSelectedStoreToStorage(storeToSave)
+ setStorePopupVisible(false)
+ Taro.showToast({title: '门店已切换', icon: 'success'})
+ }}
+ />
+ )
+ })}
+
+
+
>
)
diff --git a/src/pages/index/HeaderWithHook.tsx b/src/pages/index/HeaderWithHook.tsx
deleted file mode 100644
index da16113..0000000
--- a/src/pages/index/HeaderWithHook.tsx
+++ /dev/null
@@ -1,222 +0,0 @@
-import {useEffect, useState} from "react";
-import Taro from '@tarojs/taro';
-import {Button, Space} from '@nutui/nutui-react-taro'
-import {TriangleDown} from '@nutui/icons-react-taro'
-import {Popup, Avatar, NavBar} from '@nutui/nutui-react-taro'
-import {getUserInfo, getWxOpenId} from "@/api/layout";
-import {TenantId} from "@/config/app";
-import {getOrganization} from "@/api/system/organization";
-import {myUserVerify} from "@/api/system/userVerify";
-import {User} from "@/api/system/user/model";
-import { useShopInfo } from '@/hooks/useShopInfo';
-import { useUser } from '@/hooks/useUser';
-import {handleInviteRelation} from "@/utils/invite";
-import MySearch from "./MySearch";
-import './Header.scss';
-
-const Header = (props: any) => {
- // 使用新的hooks
- const {
- shopInfo,
- loading: shopLoading,
- getWebsiteName,
- getWebsiteLogo
- } = useShopInfo();
-
- const {
- user,
- isLoggedIn,
- loading: userLoading
- } = useUser();
-
- const [showBasic, setShowBasic] = useState(false)
- const [statusBarHeight, setStatusBarHeight] = useState()
-
- const reload = async () => {
- Taro.getSystemInfo({
- success: (res) => {
- setStatusBarHeight(res.statusBarHeight)
- },
- })
-
- // 注意:商店信息现在通过useShopInfo自动管理,不需要手动获取
- // 用户信息现在通过useUser自动管理,不需要手动获取
-
- // 如果需要获取openId,可以在用户登录后处理
- if (user && !user.openid) {
- Taro.login({
- success: (res) => {
- getWxOpenId({code: res.code}).then(() => {
- console.log('OpenId获取成功');
- })
- }
- })
- }
-
- // 检查用户认证状态
- if (user?.userId) {
- // 获取组织信息
- getOrganization({userId: user.userId}).then((data) => {
- console.log('组织信息>>>', data)
- }).catch(() => {
- console.log('获取组织信息失败')
- });
-
- // 检查用户认证
- myUserVerify({userId: user.userId}).then((data) => {
- console.log('认证信息>>>', data)
- }).catch(() => {
- console.log('获取认证信息失败')
- });
- }
- }
-
- // 获取手机号授权
- const handleGetPhoneNumber = ({detail}: {detail: {code?: string, encryptedData?: string, iv?: string}}) => {
- 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
- },
- success: async function (res) {
- if (res.data.code == 1) {
- Taro.showToast({
- title: res.data.message,
- icon: 'error',
- duration: 2000
- })
- return false;
- }
- // 登录成功
- Taro.setStorageSync('access_token', res.data.data.access_token)
- Taro.setStorageSync('UserId', res.data.data.user.userId)
-
- // 处理邀请关系
- if (res.data.data.user?.userId) {
- try {
- const inviteSuccess = await handleInviteRelation(res.data.data.user.userId)
- if (inviteSuccess) {
- Taro.showToast({
- title: '邀请关系建立成功',
- icon: 'success',
- duration: 2000
- })
- }
- } catch (error) {
- console.error('处理邀请关系失败:', error)
- }
- }
-
- // 重新加载小程序
- Taro.reLaunch({
- url: '/pages/index/index'
- })
- }
- })
- } else {
- console.log('登录失败!')
- }
- }
- })
- }
-
- useEffect(() => {
- reload().then()
- }, [])
-
- // 显示加载状态
- if (shopLoading || userLoading) {
- return (
-
- );
- }
-
- return (
- <>
-
-
-
- {
- }}
- left={
- !isLoggedIn ? (
-
-
-
-
- ) : (
-
-
-
{getWebsiteName()}
-
-
- )}>
-
- {
- setShowBasic(false)
- }}
- >
-
-
商店信息
-
网站名称: {getWebsiteName()}
-
Logo:
})
-
-
用户信息
-
登录状态: {isLoggedIn ? '已登录' : '未登录'}
- {user && (
- <>
-
用户ID: {user.userId}
-
手机号: {user.phone}
-
昵称: {user.nickname}
- >
- )}
-
-
-
-
- >
- )
-}
-
-export default Header;
diff --git a/src/pages/index/IndexWithHook.tsx b/src/pages/index/IndexWithHook.tsx
deleted file mode 100644
index c537314..0000000
--- a/src/pages/index/IndexWithHook.tsx
+++ /dev/null
@@ -1,189 +0,0 @@
-import Header from './Header';
-import BestSellers from './BestSellers';
-import Taro from '@tarojs/taro';
-import {useShareAppMessage, useShareTimeline} from "@tarojs/taro"
-import {useEffect, useState} from "react";
-import {Sticky} from '@nutui/nutui-react-taro'
-import { useShopInfo } from '@/hooks/useShopInfo';
-import { useUser } from '@/hooks/useUser';
-import Menu from "./Menu";
-import Banner from "./Banner";
-import './index.scss'
-
-const Home = () => {
- const [stickyStatus, setStickyStatus] = useState(false);
-
- // 使用新的hooks
- const {
- shopInfo,
- loading: shopLoading,
- error: shopError,
- getWebsiteName,
- getWebsiteLogo,
- refreshShopInfo
- } = useShopInfo();
-
- const {
- user,
- isLoggedIn,
- loading: userLoading
- } = useUser();
-
- const onSticky = (args: any) => {
- setStickyStatus(args[0].isFixed);
- };
-
- const showAuthModal = () => {
- Taro.showModal({
- title: '授权提示',
- content: '需要获取您的用户信息',
- confirmText: '去授权',
- cancelText: '取消',
- success: (res) => {
- if (res.confirm) {
- // 用户点击确认,打开授权设置页面
- Taro.openSetting({
- success: (settingRes) => {
- if (settingRes.authSetting['scope.userInfo']) {
- console.log('用户已授权');
- } else {
- console.log('用户拒绝授权');
- }
- }
- });
- }
- }
- });
- };
-
- // 分享给好友
- useShareAppMessage(() => {
- return {
- title: `${getWebsiteName()} - 精选商城`,
- path: '/pages/index/index',
- imageUrl: getWebsiteLogo(),
- success: function (res: any) {
- console.log('分享成功', res);
- Taro.showToast({
- title: '分享成功',
- icon: 'success',
- duration: 2000
- });
- },
- fail: function (res: any) {
- console.log('分享失败', res);
- Taro.showToast({
- title: '分享失败',
- icon: 'none',
- duration: 2000
- });
- }
- };
- });
-
- // 分享到朋友圈
- useShareTimeline(() => {
- return {
- title: `${getWebsiteName()} - 精选商城`,
- imageUrl: getWebsiteLogo(),
- success: function (res: any) {
- console.log('分享到朋友圈成功', res);
- },
- fail: function (res: any) {
- console.log('分享到朋友圈失败', res);
- }
- };
- });
-
- useEffect(() => {
- // 设置页面标题
- if (shopInfo?.appName) {
- Taro.setNavigationBarTitle({
- title: shopInfo.appName
- });
- }
- }, [shopInfo]);
-
- useEffect(() => {
- // 检查用户授权状态
- Taro.getSetting({
- success: (res) => {
- if (res.authSetting['scope.userInfo']) {
- console.log('用户已经授权过,可以直接获取用户信息');
- } else {
- console.log('用户未授权,需要弹出授权窗口');
- showAuthModal();
- }
- }
- });
-
- // 获取用户基本信息(头像、昵称等)
- Taro.getUserInfo({
- success: (res) => {
- const avatar = res.userInfo.avatarUrl;
- console.log('用户头像:', avatar);
- },
- fail: (err) => {
- console.log('获取用户信息失败:', err);
- }
- });
- }, []);
-
- // 处理错误状态
- if (shopError) {
- return (
-
-
加载商店信息失败: {shopError}
-
-
- );
- }
-
- // 显示加载状态
- if (shopLoading) {
- return (
-
- );
- }
-
- return (
- <>
- onSticky(args)}>
-
-
-
-
-
-
- {/* 调试信息面板 - 仅在开发环境显示 */}
- {process.env.NODE_ENV === 'development' && (
-
-
商店: {getWebsiteName()}
-
用户: {isLoggedIn ? (user?.nickname || '已登录') : '未登录'}
-
加载: {userLoading ? '用户加载中' : '已完成'}
-
- )}
-
- >
- )
-}
-
-export default Home;
diff --git a/src/pages/index/Menu.tsx b/src/pages/index/Menu.tsx
index 710d2ee..c634965 100644
--- a/src/pages/index/Menu.tsx
+++ b/src/pages/index/Menu.tsx
@@ -34,7 +34,7 @@ const Page = () => {
return (
shopLoading ? (加载中) :
-
+
{
navItems.map((item, index) => (
onNav(item)}>
diff --git a/src/pages/index/MySearch.tsx b/src/pages/index/MySearch.tsx
index 3c6d9a5..2015ee6 100644
--- a/src/pages/index/MySearch.tsx
+++ b/src/pages/index/MySearch.tsx
@@ -30,7 +30,7 @@ function MySearch(props: any) {
return (
-
+
(false)
- // Tabs粘性状态
- const [_, setTabsStickyStatus] = useState
(false)
+ const [activeTab, setActiveTab] = useState('推荐')
+ const [goodsList, setGoodsList] = useState([])
useShareAppMessage(() => {
// 获取当前用户ID,用于生成邀请链接
@@ -85,9 +84,7 @@ function Home() {
// }
// 处理Tabs粘性状态变化
- const handleTabsStickyChange = (isSticky: boolean) => {
- setTabsStickyStatus(isSticky)
- }
+ // const handleTabsStickyChange = (isSticky: boolean) => {}
const reload = () => {
@@ -99,6 +96,10 @@ function Home() {
})
+ pageShopGoods({}).then(res => {
+ setGoodsList(res?.list || [])
+ })
+
// 检查是否有待处理的邀请关系 - 异步处理,不阻塞页面加载
if (hasPendingInvite()) {
console.log('检测到待处理的邀请关系')
@@ -152,16 +153,140 @@ function Home() {
});
}, []);
+ const tabs = useMemo(() => ['推荐', '桶装水', '优惠组合', '购机套餐', '饮水设备'], [])
+
+ const shortcuts = useMemo<
+ Array<{ key: string; title: string; icon: ReactNode; onClick: () => void }>
+ >(
+ () => [
+ {
+ key: 'ticket',
+ title: '我的水票',
+ icon: ,
+ onClick: () => Taro.navigateTo({ url: '/user/gift/index' }),
+ },
+ {
+ key: 'order',
+ title: '立即订水',
+ icon: ,
+ onClick: () => Taro.navigateTo({ url: '/shop/goodsDetail/index?id=10072' }),
+ },
+ {
+ key: 'invite',
+ title: '邀请有礼',
+ icon: ,
+ onClick: () => Taro.navigateTo({ url: '/dealer/qrcode/index' }),
+ },
+ {
+ key: 'coupon',
+ title: '领券中心',
+ icon: ,
+ onClick: () => Taro.navigateTo({ url: '/coupon/index' }),
+ },
+ ],
+ []
+ )
+
+ const visibleGoods = useMemo(() => {
+ // 先按效果图展示两列卡片,数据不够时也保持布局稳定
+ const list = goodsList || []
+ if (list.length <= 6) return list
+ return list.slice(0, 6)
+ }, [goodsList])
+
return (
<>
{/* Header区域 - 现在由Header组件内部处理吸顶逻辑 */}
-
-
-
-
-
+
+ {/* 顶部活动主视觉:使用 Banner 组件 */}
+
+
+ {/* 电子水票 */}
+
+
+ 电子水票
+
+ 您还有 0 张水票
+
+
+
+
+
+ {shortcuts.map((item) => (
+
+ {item.icon}
+ {item.title}
+
+ ))}
+
+
+
+
+ {/* 分类Tabs */}
+
+
+ {tabs.map((tab) => {
+ const active = tab === activeTab
+ return (
+ setActiveTab(tab)}
+ >
+ {tab}
+
+ )
+ })}
+
+
+
+ {/* 商品列表 */}
+
+ {visibleGoods.map((item) => (
+
+
+
+ Taro.navigateTo({ url: `/shop/goodsDetail/index?id=${item.goodsId}` })
+ }
+ />
+
+
+
+ {item.name}
+
+ 已购:{item.sales || 0}人
+
+ ¥
+ {item.price}
+
+
+
+
+
+ Taro.navigateTo({ url: `/shop/goodsDetail/index?id=${item.goodsId}` })
+ }
+ >
+ 立即购买
+
+
+
+
+ ))}
+
+
>
)
}
diff --git a/src/pages/menu/menu.config.ts b/src/pages/menu/menu.config.ts
deleted file mode 100644
index 2cb081e..0000000
--- a/src/pages/menu/menu.config.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-export default definePageConfig({
- navigationBarTitleText: '菜单'
-})
diff --git a/src/pages/menu/menu.tsx b/src/pages/menu/menu.tsx
deleted file mode 100644
index 325e9cf..0000000
--- a/src/pages/menu/menu.tsx
+++ /dev/null
@@ -1,81 +0,0 @@
-import {useEffect, useState} from "react";
-import Taro from '@tarojs/taro';
-import {pageCmsArticle} from "@/api/cms/cmsArticle";
-import {CmsArticle} from "@/api/cms/cmsArticle/model";
-import {NavBar, Cell} from '@nutui/nutui-react-taro';
-import {Text} from '@tarojs/components';
-import {ArrowRight, ImageRectangle, Coupon, Follow} from '@nutui/icons-react-taro'
-import navTo from "@/utils/common";
-
-/**
- * 文章终极列表
- * @constructor
- */
-const Menu = () => {
- const [statusBarHeight, setStatusBarHeight] = useState()
- const [loading, setLoading] = useState(false)
- const [list, setList] = useState()
-
- const reload = async () => {
- setLoading(true)
- const article = await pageCmsArticle({categoryId: 4289, status: 0})
- if (article) {
- setList(article?.list)
- setLoading(false)
- }
- }
-
- useEffect(() => {
- Taro.getSystemInfo({
- success: (res) => {
- setStatusBarHeight(res.statusBarHeight)
- },
- })
- reload().then(() => {
- console.log('初始化完成')
- })
- }, [])
-
- return (
- <>
- {loading && (暂无数据
)}
- {
- }}
- >
- 发现
-
- {list && (
- <>
-
-
- 好物推荐
- |
- } extra={
}/>
-
-
- 权益中心
- |
- }
- extra={
}
- onClick={() => {
- navTo('/shop/shopArticle/index', true)
- }}
- />
-
-
- 我的收藏
- |
- } extra={
}/>
- >
- )}
- >
- )
-}
-export default Menu
diff --git a/src/pages/user/components/IsDealer.tsx b/src/pages/user/components/IsDealer.tsx
index 116e9c0..8e86034 100644
--- a/src/pages/user/components/IsDealer.tsx
+++ b/src/pages/user/components/IsDealer.tsx
@@ -5,26 +5,13 @@ import {ArrowRight, Reward, Setting} from '@nutui/icons-react-taro'
import {useUser} from '@/hooks/useUser'
import {useDealerUser} from "@/hooks/useDealerUser";
import {useThemeStyles} from "@/hooks/useTheme";
-import { useConfig } from "@/hooks/useConfig";
-import {useEffect, useState} from "react";
-import {getCmsAdByCode} from "@/api/cms/cmsAd";
-import {CmsAd} from "@/api/cms/cmsAd/model"; // 使用新的自定义Hook
+import { useConfig } from "@/hooks/useConfig"; // 使用新的自定义Hook
const IsDealer = () => {
const themeStyles = useThemeStyles();
const { config } = useConfig(); // 使用新的Hook
const {isSuperAdmin} = useUser();
const {dealerUser} = useDealerUser()
- const [register, setRegister] = useState()
-
- const reload = async () => {
- const item = await getCmsAdByCode('register')
- setRegister(item)
- }
-
- useEffect(() => {
- reload().then()
- }, []);
/**
* 管理中心
@@ -64,12 +51,12 @@ const IsDealer = () => {
{config?.vipText || '入驻申请'}
+ className={'pl-3 text-orange-100 font-medium'}>{config?.vipText || '开发者中心'}
{/*门店核销*/}
}
extra={}
- onClick={() => navTo('/doctor/index', true)}
+ onClick={() => navTo('/dealer/index', true)}
/>
>
@@ -88,12 +75,12 @@ const IsDealer = () => {
title={
- {register?.name || '注册会员'}
-
+ {config?.vipText || '门店入驻'}
+ {config?.vipComments || ''}
}
extra={}
- onClick={() => navTo(`${register?.path}`, true)}
+ onClick={() => navTo('/dealer/apply/add', true)}
/>
>
diff --git a/src/pages/user/components/UserCard.tsx b/src/pages/user/components/UserCard.tsx
index 0235e65..b7cc16b 100644
--- a/src/pages/user/components/UserCard.tsx
+++ b/src/pages/user/components/UserCard.tsx
@@ -1,4 +1,4 @@
-import {Avatar, Tag, Space} from '@nutui/nutui-react-taro'
+import {Avatar, Tag, Space, Button} from '@nutui/nutui-react-taro'
import {View, Text} from '@tarojs/components'
import {getUserInfo, getWxOpenId} from '@/api/layout';
import Taro from '@tarojs/taro';
@@ -11,15 +11,21 @@ import {useUserData} from "@/hooks/useUserData";
import {getStoredInviteParams} from "@/utils/invite";
import UnifiedQRButton from "@/components/UnifiedQRButton";
import {useThemeStyles} from "@/hooks/useTheme";
+import {getRootDomain} from "@/utils/domain";
const UserCard = forwardRef((_, ref) => {
const {data, refresh} = useUserData()
- const {getDisplayName, getRoleName} = useUser();
+ const {getDisplayName} = useUser();
const [IsLogin, setIsLogin] = useState(false)
const [userInfo, setUserInfo] = useState()
const themeStyles = useThemeStyles();
+ // 角色名称:优先取用户 roles 数组的第一个角色名称
+ const getRoleName = () => {
+ return userInfo?.roles?.[0]?.roleName || userInfo?.roleName || '注册用户'
+ }
+
// 下拉刷新
const handleRefresh = async () => {
await refresh()
@@ -65,6 +71,8 @@ const UserCard = forwardRef((_, ref) => {
setUserInfo(data)
setIsLogin(true);
Taro.setStorageSync('UserId', data.userId)
+ // 登录态已就绪后刷新卡片统计(余额/积分/券/水票)
+ refresh().then()
// 获取openId
if (!data.openid) {
@@ -162,6 +170,8 @@ const UserCard = forwardRef((_, ref) => {
Taro.setStorageSync('UserId', res.data.data.user.userId)
setUserInfo(res.data.data.user)
setIsLogin(true)
+ // 登录态已就绪后刷新卡片统计(余额/积分/券/水票)
+ refresh().then()
}
})
} else {
@@ -179,22 +189,37 @@ const UserCard = forwardRef((_, ref) => {
className={'user-card w-full flex flex-col justify-around rounded-xl'}
>
-
-
+ {IsLogin && (
+ navTo(`/user/profile/profile`)}>
+
+
-
- {getDisplayName()}
- {IsLogin ? (
-
-
-
- {getRoleName()}
-
-
+ />
+
+ {getDisplayName() || '点击登录'}
+ {getRootDomain() && (
+ {getRoleName()}
+ )}
+
- ) : ''}
-
+
+ )}
+ {!IsLogin && (
+
+ )}
((_, ref) => {
// 核销成功,可以显示更多信息或跳转到详情页
Taro.showModal({
title: '核销成功',
- content: `已成功核销的品类:${result.data.goodsName || '礼品卡'},面值¥${result.data.faceValue}`
+ content: `已成功核销的品类:${result.data.goodsName || '水票'},面值¥${result.data.faceValue}`
});
}
}}
@@ -225,22 +250,22 @@ const UserCard = forwardRef((_, ref) => {
navTo('/user/wallet/wallet', true)}>
- 余额
- {data?.balance || '0.00'}
+ 余额
+ {data?.balance || '0.00'}
- 积分
- {data?.points || 0}
+ 积分
+ {data?.points || 0}
navTo('/user/coupon/index', true)}>
- 优惠券
- {data?.coupons || 0}
+ 优惠券
+ {data?.coupons || 0}
navTo('/user/gift/index', true)}>
- 礼品卡
- {data?.giftCards || 0}
+ 水票
+ {data?.giftCards || 0}
diff --git a/src/pages/user/components/UserGrid.tsx b/src/pages/user/components/UserGrid.tsx
index dad57ca..54b6577 100644
--- a/src/pages/user/components/UserGrid.tsx
+++ b/src/pages/user/components/UserGrid.tsx
@@ -11,13 +11,14 @@ import {
People,
// AfterSaleService,
Logout,
- ShoppingAdd,
+ Shop,
+ Jdl,
Service
} from '@nutui/icons-react-taro'
import {useUser} from "@/hooks/useUser";
const UserCell = () => {
- const {logoutUser} = useUser();
+ const {logoutUser, hasRole} = useUser();
const onLogout = () => {
Taro.showModal({
@@ -49,10 +50,49 @@ const UserCell = () => {
border: 'none'
} as React.CSSProperties}
>
- navTo('/user/poster/poster', true)}>
+
+ {hasRole('store') && (
+ navTo('/store/index', true)}>
+
+
+
+
+
+
+ )}
+
+ {hasRole('rider') && (
+ navTo('/rider/index', true)}>
+
+
+
+
+
+
+ )}
+
+ {(hasRole('staff') || hasRole('admin')) && (
+ navTo('/user/store/orders/index', true)}>
+
+
+
+
+
+
+ )}
+
+ navTo('/user/address/index', true)}>
-
-
+
+
+
+
+
+
+ navTo('/user/help/index')}>
+
+
+
@@ -71,14 +111,6 @@ const UserCell = () => {
- navTo('/user/address/index', true)}>
-
-
-
-
-
-
-
navTo('/user/userVerify/index', true)}>
@@ -87,7 +119,7 @@ const UserCell = () => {
- navTo('/doctor/team/index', true)}>
+ navTo('/dealer/team/index', true)}>
@@ -95,7 +127,7 @@ const UserCell = () => {
- {/* navTo('/doctor/qrcode/index', true)}>*/}
+ {/* navTo('/dealer/qrcode/index', true)}>*/}
{/* */}
{/* */}
{/* */}
@@ -111,13 +143,6 @@ const UserCell = () => {
{/* */}
{/**/}
- navTo('/user/help/index')}>
-
-
-
-
-
-
navTo('/user/about/index')}>
@@ -138,8 +163,54 @@ const UserCell = () => {
+ {/**/}
+ {/* 账号管理*/}
+ {/* */}
+ {/* */}
+ {/* navTo('/user/profile/profile', true)}>*/}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+
+ {/* navTo('/user/theme/index', true)}>*/}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+
+ {/* navTo('/user/about/index')}>*/}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+
+ {/* */}
+ {/* */}
+ {/**/}
>
)
}
export default UserCell
-
diff --git a/src/pages/user/user.tsx b/src/pages/user/user.tsx
index 9d49ce2..70b97aa 100644
--- a/src/pages/user/user.tsx
+++ b/src/pages/user/user.tsx
@@ -3,23 +3,19 @@ import {PullToRefresh} from '@nutui/nutui-react-taro'
import UserCard from "./components/UserCard";
import UserOrder from "./components/UserOrder";
import UserFooter from "./components/UserFooter";
-import {useUserData} from "@/hooks/useUserData";
import {View} from '@tarojs/components';
import './user.scss'
import IsDealer from "./components/IsDealer";
-import UserCell from "@/pages/user/components/UserCell";
import {useThemeStyles} from "@/hooks/useTheme";
+import UserGrid from "@/pages/user/components/UserGrid";
function User() {
- const {refresh} = useUserData()
const userCardRef = useRef()
const themeStyles = useThemeStyles();
// 下拉刷新处理
const handleRefresh = async () => {
- await refresh()
- // 如果 UserCard 组件有自己的刷新方法,也可以调用
if (userCardRef.current?.handleRefresh) {
await userCardRef.current.handleRefresh()
}
@@ -55,7 +51,7 @@ function User() {
-
+
)
diff --git a/src/passport/register.tsx b/src/passport/register.tsx
deleted file mode 100644
index 553e0e0..0000000
--- a/src/passport/register.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import {useEffect, useState} from "react";
-import Taro from '@tarojs/taro'
-import {Input, Radio, Button} from '@nutui/nutui-react-taro'
-
-const Register = () => {
- const [isAgree, setIsAgree] = useState(false)
- const reload = () => {
- Taro.hideTabBar()
- }
-
- useEffect(() => {
- reload()
- }, [])
-
- return (
- <>
-
-
免费试用14天,快速上手独立站
-
建站、选品、营销、支付、物流,全部搞定
-
- WebSoft为您提供独立站的解决方案,提供专业、高效、安全的运营服务。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- )
-}
-export default Register
diff --git a/src/passport/sms-login.tsx b/src/passport/sms-login.tsx
index 62da43d..c30933c 100644
--- a/src/passport/sms-login.tsx
+++ b/src/passport/sms-login.tsx
@@ -3,6 +3,7 @@ import Taro from '@tarojs/taro'
import {Input, Button} from '@nutui/nutui-react-taro'
import {loginBySms, sendSmsCaptcha} from "@/api/passport/login";
import {LoginParam} from "@/api/passport/login/model";
+import {checkAndHandleInviteRelation, hasPendingInvite} from "@/utils/invite";
const SmsLogin = () => {
const [loading, setLoading] = useState(false)
@@ -131,6 +132,15 @@ const SmsLogin = () => {
code: formData.code
})
+ // 登录成功后(可能是新注册用户),检查是否存在待处理的邀请关系并尝试绑定
+ if (hasPendingInvite()) {
+ try {
+ await checkAndHandleInviteRelation()
+ } catch (e) {
+ console.error('短信登录后处理邀请关系失败:', e)
+ }
+ }
+
Taro.showToast({
title: '登录成功',
icon: 'success'
diff --git a/src/passport/unified-qr/index.tsx b/src/passport/unified-qr/index.tsx
index 9e87c7f..c229ad6 100644
--- a/src/passport/unified-qr/index.tsx
+++ b/src/passport/unified-qr/index.tsx
@@ -193,7 +193,7 @@ const UnifiedQRPage: React.FC = () => {
{result.type === ScanType.VERIFICATION && result.data && (
- 礼品卡:{result.data.goodsName || '未知商品'}
+ 水票:{result.data.goodsName || '未知商品'}
面值:¥{result.data.faceValue}
diff --git a/src/rider/index.config.ts b/src/rider/index.config.ts
new file mode 100644
index 0000000..1293fcb
--- /dev/null
+++ b/src/rider/index.config.ts
@@ -0,0 +1,3 @@
+export default definePageConfig({
+ navigationBarTitleText: '配送中心'
+})
diff --git a/src/rider/index.scss b/src/rider/index.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/rider/index.tsx b/src/rider/index.tsx
new file mode 100644
index 0000000..0a023fe
--- /dev/null
+++ b/src/rider/index.tsx
@@ -0,0 +1,295 @@
+import React from 'react'
+import {View, Text} from '@tarojs/components'
+import {ConfigProvider, Button, Grid, Avatar} from '@nutui/nutui-react-taro'
+import {
+ User,
+ Shopping,
+ Dongdong,
+ ArrowRight,
+ Purse,
+ People
+} from '@nutui/icons-react-taro'
+import {useDealerUser} from '@/hooks/useDealerUser'
+import { useThemeStyles } from '@/hooks/useTheme'
+import {businessGradients, cardGradients, gradientUtils} from '@/styles/gradients'
+import Taro from '@tarojs/taro'
+
+const DealerIndex: React.FC = () => {
+ const {
+ dealerUser,
+ error,
+ refresh,
+ } = useDealerUser()
+
+ // 使用主题样式
+ const themeStyles = useThemeStyles()
+
+ // 导航到各个功能页面
+ const navigateToPage = (url: string) => {
+ Taro.navigateTo({url})
+ }
+
+ // 格式化金额
+ const formatMoney = (money?: string) => {
+ if (!money) return '0.00'
+ return parseFloat(money).toFixed(2)
+ }
+
+ // 格式化时间
+ const formatTime = (time?: string) => {
+ if (!time) return '-'
+ return new Date(time).toLocaleDateString()
+ }
+
+ // 获取用户主题
+ const userTheme = gradientUtils.getThemeByUserId(dealerUser?.userId)
+
+ // 获取渐变背景
+ const getGradientBackground = (themeColor?: string) => {
+ if (themeColor) {
+ const darkerColor = gradientUtils.adjustColorBrightness(themeColor, -30)
+ return gradientUtils.createGradient(themeColor, darkerColor)
+ }
+ return userTheme.background
+ }
+
+ console.log(getGradientBackground(),'getGradientBackground()')
+
+ if (error) {
+ return (
+
+
+ {error}
+
+
+
+ )
+ }
+
+ return (
+
+
+ {/*头部信息*/}
+ {dealerUser && (
+
+ {/* 装饰性背景元素 - 小程序兼容版本 */}
+
+
+
+
+ }
+ className="mr-4"
+ style={{
+ border: '2px solid rgba(255, 255, 255, 0.3)'
+ }}
+ />
+
+
+ {dealerUser?.realName || '分销商'}
+
+
+ ID: {dealerUser.userId}
+
+
+
+ 加入时间
+
+ {formatTime(dealerUser.createTime)}
+
+
+
+
+ )}
+
+ {/* 佣金统计卡片 */}
+ {dealerUser && (
+
+
+ 工资统计
+
+
+
+
+ {formatMoney(dealerUser.money)}
+
+ 工资收入
+
+
+
+ {formatMoney(dealerUser.freezeMoney)}
+
+ 桶数
+
+
+
+ {formatMoney(dealerUser.totalMoney)}
+
+ 累计收入
+
+
+
+ )}
+
+ {/* 团队统计 */}
+ {dealerUser && (
+
+
+ 我的邀请
+ navigateToPage('/dealer/team/index')}
+ >
+ 查看详情
+
+
+
+
+
+
+ {dealerUser.firstNum || 0}
+
+ 一级成员
+
+
+
+ {dealerUser.secondNum || 0}
+
+ 二级成员
+
+
+
+ {dealerUser.thirdNum || 0}
+
+ 三级成员
+
+
+
+ )}
+
+ {/* 功能导航 */}
+
+ 配送工具
+
+
+ navigateToPage('/rider/orders/index')}>
+
+
+
+
+
+
+
+ navigateToPage('/rider/withdraw/index')}>
+
+
+
+
+
+
+
+ navigateToPage('/rider/team/index')}>
+
+
+
+
+
+
+
+ navigateToPage('/rider/qrcode/index')}>
+
+
+
+
+
+
+
+
+ {/* 第二行功能 */}
+ {/**/}
+ {/* navigateToPage('/dealer/invite-stats/index')}>*/}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+
+ {/* /!* 预留其他功能位置 *!/*/}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/* */}
+ {/**/}
+
+
+
+
+ {/* 底部安全区域 */}
+
+
+ )
+}
+
+export default DealerIndex
diff --git a/src/rider/orders/index.config.ts b/src/rider/orders/index.config.ts
new file mode 100644
index 0000000..9606465
--- /dev/null
+++ b/src/rider/orders/index.config.ts
@@ -0,0 +1,4 @@
+export default {
+ navigationBarTitleText: '配送订单',
+ navigationBarTextStyle: 'black'
+}
diff --git a/src/rider/orders/index.tsx b/src/rider/orders/index.tsx
new file mode 100644
index 0000000..80b53a5
--- /dev/null
+++ b/src/rider/orders/index.tsx
@@ -0,0 +1,390 @@
+import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
+import Taro from '@tarojs/taro'
+import { Tabs, TabPane, Cell, Space, Button, Dialog, Image, Empty, InfiniteLoading} from '@nutui/nutui-react-taro'
+import {View, Text} from '@tarojs/components'
+import dayjs from 'dayjs'
+import {pageShopOrder, updateShopOrder} from '@/api/shop/shopOrder'
+import type {ShopOrder, ShopOrderParam} from '@/api/shop/shopOrder/model'
+import {uploadFile} from '@/api/system/file'
+
+export default function RiderOrders() {
+
+ const riderId = useMemo(() => {
+ const v = Number(Taro.getStorageSync('UserId'))
+ return Number.isFinite(v) && v > 0 ? v : undefined
+ }, [])
+
+ const pageRef = useRef(1)
+ const [tabIndex, setTabIndex] = useState(0)
+ const [list, setList] = useState([])
+ const [hasMore, setHasMore] = useState(true)
+ const [loading, setLoading] = useState(false)
+ const [error, setError] = useState(null)
+
+ const [deliverDialogVisible, setDeliverDialogVisible] = useState(false)
+ const [deliverSubmitting, setDeliverSubmitting] = useState(false)
+ const [deliverOrder, setDeliverOrder] = useState(null)
+ const [deliverImg, setDeliverImg] = useState(undefined)
+
+ // 前端展示用:后台可配置实际自动确认收货时长
+ const AUTO_CONFIRM_RECEIVE_HOURS_FALLBACK = 24
+
+ const riderTabs = useMemo(
+ () => [
+ {index: 0, title: '全部', statusFilter: -1},
+ {index: 1, title: '配送中', statusFilter: 3}, // 后端:deliveryStatus=20
+ {index: 2, title: '待客户确认', statusFilter: 3}, // 同上,前端再按 sendEndTime 细分
+ {index: 3, title: '已完成', statusFilter: 5}, // 后端:orderStatus=1
+ ],
+ []
+ )
+
+ const isAbnormalOrder = (order: ShopOrder) => {
+ const s = order.orderStatus
+ return s === 2 || s === 3 || s === 4 || s === 5 || s === 6 || s === 7
+ }
+
+ const getOrderStatusText = (order: ShopOrder) => {
+ if (order.orderStatus === 2) return '已取消'
+ if (order.orderStatus === 3) return '取消中'
+ if (order.orderStatus === 4) return '退款申请中'
+ if (order.orderStatus === 5) return '退款被拒绝'
+ if (order.orderStatus === 6) return '退款成功'
+ if (order.orderStatus === 7) return '客户申请退款'
+ if (!order.payStatus) return '未付款'
+ if (order.orderStatus === 1) return '已完成'
+
+ // 配送员页:用 sendEndTime 表示“已送达收货点”
+ if (order.deliveryStatus === 20) {
+ if (order.sendEndTime) return '待客户确认收货'
+ return '配送中'
+ }
+ if (order.deliveryStatus === 10) return '待发货'
+ if (order.deliveryStatus === 30) return '部分发货'
+ return '处理中'
+ }
+
+ const getOrderStatusColor = (order: ShopOrder) => {
+ if (isAbnormalOrder(order)) return 'text-orange-500'
+ if (order.orderStatus === 1) return 'text-green-600'
+ if (order.sendEndTime) return 'text-purple-600'
+ return 'text-blue-600'
+ }
+
+ const canConfirmDelivered = (order: ShopOrder) => {
+ if (!order.payStatus) return false
+ if (order.orderStatus === 1) return false
+ if (isAbnormalOrder(order)) return false
+ // 只允许在“配送中”阶段确认送达
+ if (order.deliveryStatus !== 20) return false
+ return !order.sendEndTime
+ }
+
+ const filterByTab = useCallback(
+ (orders: ShopOrder[]) => {
+ if (tabIndex === 1) {
+ // 配送中:未确认送达
+ return orders.filter(o => o.deliveryStatus === 20 && !o.sendEndTime && !isAbnormalOrder(o) && o.orderStatus !== 1)
+ }
+ if (tabIndex === 2) {
+ // 待客户确认:已确认送达
+ return orders.filter(o => o.deliveryStatus === 20 && !!o.sendEndTime && !isAbnormalOrder(o) && o.orderStatus !== 1)
+ }
+ if (tabIndex === 3) {
+ return orders.filter(o => o.orderStatus === 1)
+ }
+ return orders
+ },
+ [tabIndex]
+ )
+
+ const reload = useCallback(
+ async (resetPage = false) => {
+ if (!riderId) return
+ setLoading(true)
+ setError(null)
+
+ const currentPage = resetPage ? 1 : pageRef.current
+ const currentTab = riderTabs.find(t => t.index === tabIndex) || riderTabs[0]
+
+ const params: ShopOrderParam = {
+ page: currentPage,
+ riderId,
+ statusFilter: currentTab.statusFilter,
+ }
+
+ try {
+ const res = await pageShopOrder(params)
+ const incoming = (res?.list || []) as ShopOrder[]
+ setList(prev => (resetPage ? incoming : prev.concat(incoming)))
+ setHasMore(incoming.length >= 10)
+ pageRef.current = currentPage
+ } catch (e) {
+ console.error('加载配送订单失败:', e)
+ setError('加载失败,请重试')
+ setHasMore(false)
+ } finally {
+ setLoading(false)
+ }
+ },
+ [riderId, riderTabs, tabIndex]
+ )
+
+ const reloadMore = useCallback(async () => {
+ if (loading || !hasMore) return
+ pageRef.current += 1
+ await reload(false)
+ }, [hasMore, loading, reload])
+
+ const openDeliverDialog = (order: ShopOrder) => {
+ setDeliverOrder(order)
+ setDeliverImg(order.sendEndImg)
+ setDeliverDialogVisible(true)
+ }
+
+ const handleChooseDeliverImg = async () => {
+ try {
+ const file = await uploadFile()
+ setDeliverImg(file?.url)
+ } catch (e) {
+ console.error('上传送达照片失败:', e)
+ Taro.showToast({title: '上传失败,请重试', icon: 'none'})
+ }
+ }
+
+ const handleConfirmDelivered = async () => {
+ if (!deliverOrder?.orderId) return
+ if (deliverSubmitting) return
+ setDeliverSubmitting(true)
+ try {
+ await updateShopOrder({
+ orderId: deliverOrder.orderId,
+ // 用于前端/后端识别“配送员已送达收货点”
+ sendEndTime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
+ sendEndImg: deliverImg,
+ })
+ Taro.showToast({title: '已确认送达', icon: 'success'})
+ setDeliverDialogVisible(false)
+ setDeliverOrder(null)
+ setDeliverImg(undefined)
+ pageRef.current = 1
+ await reload(true)
+ } catch (e) {
+ console.error('确认送达失败:', e)
+ Taro.showToast({title: '确认送达失败', icon: 'none'})
+ } finally {
+ setDeliverSubmitting(false)
+ }
+ }
+
+ useEffect(() => {
+ }, [])
+
+ useEffect(() => {
+ pageRef.current = 1
+ void reload(true)
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [tabIndex, riderId])
+
+ if (!riderId) {
+ return (
+
+ 请先登录
+
+ )
+ }
+
+ const displayList = filterByTab(list)
+
+ return (
+
+
+
+ setTabIndex(Number(paneKey))}
+ >
+ {riderTabs.map(t => (
+
+ ))}
+
+
+
+ {error ? (
+
+ {error}
+
+
+ ) : (
+ 加载中>}
+ loadMoreText={
+ displayList.length === 0 ? (
+
+ ) : (
+ 没有更多了
+ )
+ }
+ >
+ {displayList.map((o, idx) => {
+ const phoneToCall = o.phone || o.mobile
+ const flow1Done = !!o.riderId
+ const flow2Done = o.deliveryStatus === 20 || o.deliveryStatus === 30
+ const flow3Done = !!o.sendEndTime
+ const flow4Done = o.orderStatus === 1
+ // 直接使用订单分页接口返回的 orderGoods
+ const goodsList = o.orderGoods || []
+ const goodsNameList = goodsList
+ .map(g => g?.goodsName || (g as any)?.goodsTitle || (g as any)?.title || (g as any)?.name)
+ .filter(Boolean) as string[]
+ const goodsSummary = goodsNameList.length
+ ? `${goodsNameList.slice(0, 3).join('、')}${goodsList.length > 3 ? ` 等${goodsList.length}件` : ''}`
+ : (o.title || '-')
+
+ const autoConfirmAt = o.sendEndTime
+ ? dayjs(o.sendEndTime).add(AUTO_CONFIRM_RECEIVE_HOURS_FALLBACK, 'hour')
+ : null
+ const autoConfirmLeftMin = autoConfirmAt ? autoConfirmAt.diff(dayjs(), 'minute') : null
+
+ return (
+ o.orderId && Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${o.orderId}`})}
+ >
+
+
+ {o.orderNo || `订单#${o.orderId}`}
+ {getOrderStatusText(o)}
+
+
+
+ 下单时间:{o.createTime ? dayjs(o.createTime).format('YYYY-MM-DD HH:mm') : '-'}
+
+
+
+
+ 收货点:
+ {o.selfTakeMerchantName || o.address || '-'}
+
+
+ 客户:
+ {o.realName || '-'} {o.phone ? `(${o.phone})` : ''}
+
+
+ 金额:
+ ¥{o.payPrice || o.totalPrice || '-'}
+ 数量:
+ {o.totalNum ?? '-'}
+
+
+ 商品:
+ {goodsSummary}
+
+ {o.sendEndTime && (
+
+ 送达时间:
+ {dayjs(o.sendEndTime).format('YYYY-MM-DD HH:mm')}
+
+ )}
+
+
+ {/* 配送流程 */}
+
+ 流程:
+ 1 派单
+ {'>'}
+ 2 配送中
+ {'>'}
+ 3 送达收货点
+ {'>'}
+ 4 客户确认收货
+
+ {o.sendEndTime && o.orderStatus !== 1 && autoConfirmAt && (
+
+ 若客户未确认,预计 {autoConfirmAt.format('YYYY-MM-DD HH:mm')} 自动确认收货(以后台配置为准)
+ {typeof autoConfirmLeftMin === 'number' && autoConfirmLeftMin > 0 ? `,约剩余 ${Math.ceil(autoConfirmLeftMin / 60)} 小时` : ''}
+
+ )}
+
+
+
+
+ {!!phoneToCall && (
+
+ )}
+ {canConfirmDelivered(o) && (
+
+ )}
+
+
+
+ |
+ )
+ })}
+
+ )}
+
+
+
+
+
+ )
+}
diff --git a/src/shop/category/components/GoodsList.tsx b/src/shop/category/components/GoodsList.tsx
index bf2eb4a..f84ae9e 100644
--- a/src/shop/category/components/GoodsList.tsx
+++ b/src/shop/category/components/GoodsList.tsx
@@ -1,60 +1,54 @@
import {Image} from '@nutui/nutui-react-taro'
+import {Share} from '@nutui/icons-react-taro'
import {View, Text} from '@tarojs/components'
import Taro from '@tarojs/taro'
-import {ShopGoods} from "@/api/shop/shopGoods/model"
-import {CmsNavigation} from "@/api/cms/cmsNavigation/model"
import './GoodsList.scss'
-interface GoodsListProps {
- data: ShopGoods[]
- nav?: CmsNavigation
-}
-const GoodsList = (props: GoodsListProps) => {
+const GoodsList = (props: any) => {
return (
<>
-
-
-
- {props.data?.map((item, index) => {
- return (
-
-
- Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}
- />
-
- {item.name}
-
- {item.comments}
+
+
+ {props.data?.map((item: any, index: number) => {
+ return (
+
+ Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
+
+
+ {item.name}
+
+ {item.comments}
+ 已售 {item.sales}
+
+
+
+ ¥
+ {item.price}
+ 会员价
+ ¥{item.salePrice}
-
-
- ¥
- {item.price}
+
+
+ Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}/>
-
- 已售 {item.sales}
+ Taro.navigateTo({url: '/shop/goodsDetail/index?id=' + item.goodsId})}>购买
- )
- })}
-
+
+ )
+ })}
>
)
}
-export default GoodsList
\ No newline at end of file
+export default GoodsList
diff --git a/src/shop/category/index.tsx b/src/shop/category/index.tsx
index b397525..7899e20 100644
--- a/src/shop/category/index.tsx
+++ b/src/shop/category/index.tsx
@@ -42,7 +42,7 @@ function Category() {
useShareAppMessage(() => {
return {
- title: `${nav?.categoryName}_WebSoft Inc.`,
+ title: `${nav?.categoryName}_桂乐淘`,
path: `/shop/category/index?id=${categoryId}`,
success: function () {
console.log('分享成功');
diff --git a/src/passport/register.config.ts b/src/shop/comments/index.config.ts
similarity index 62%
rename from src/passport/register.config.ts
rename to src/shop/comments/index.config.ts
index 77ed0bd..6d901e3 100644
--- a/src/passport/register.config.ts
+++ b/src/shop/comments/index.config.ts
@@ -1,4 +1,4 @@
export default definePageConfig({
- navigationBarTitleText: '注册账号',
+ navigationBarTitleText: '全部评论',
navigationBarTextStyle: 'black'
})
diff --git a/src/shop/comments/index.tsx b/src/shop/comments/index.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/shop/goodsDetail/index.tsx b/src/shop/goodsDetail/index.tsx
index be35024..df66a4b 100644
--- a/src/shop/goodsDetail/index.tsx
+++ b/src/shop/goodsDetail/index.tsx
@@ -15,6 +15,7 @@ import SpecSelector from "@/components/SpecSelector";
import "./index.scss";
import {useCart} from "@/hooks/useCart";
import {useConfig} from "@/hooks/useConfig";
+import {parseInviteParams, saveInviteParams, trackInviteSource} from "@/utils/invite";
const GoodsDetail = () => {
const [statusBarHeight, setStatusBarHeight] = useState(44);
@@ -39,6 +40,24 @@ const GoodsDetail = () => {
const {cartCount, addToCart} = useCart()
const {config} = useConfig()
+ // 如果从分享链接进入(携带 inviter/source/t),且当前未登录,则暂存邀请信息用于注册后绑定关系
+ useEffect(() => {
+ try {
+ const currentUserId = Taro.getStorageSync('UserId')
+ if (currentUserId) return
+
+ const inviteParams = parseInviteParams({query: router?.params})
+ if (inviteParams?.inviter) {
+ saveInviteParams(inviteParams)
+ trackInviteSource(inviteParams.source || 'share', parseInt(inviteParams.inviter))
+ }
+ } catch (e) {
+ // 邀请参数解析/存储失败不影响正常浏览商品
+ console.error('商品详情页处理邀请参数失败:', e)
+ }
+ // router 在 Taro 中可能不稳定;这里仅在 goodsId 变化时尝试处理一次即可
+ }, [goodsId])
+
// 处理加入购物车
const handleAddToCart = () => {
if (!goods) return;
@@ -186,10 +205,16 @@ const GoodsDetail = () => {
// 分享给好友
useShareAppMessage(() => {
+ const inviter = Taro.getStorageSync('UserId')
+ const sharePath =
+ inviter
+ ? `/shop/goodsDetail/index?id=${goodsId}&inviter=${inviter}&source=goods_share&t=${Date.now()}`
+ : `/shop/goodsDetail/index?id=${goodsId}`
+
return {
title: goods?.name || '精选商品',
- path: `/shop/goodsDetail/index?id=${goodsId}`,
- imageUrl: goods?.image, // 分享图片
+ path: sharePath,
+ imageUrl: goods?.image ? `${goods.image}?x-oss-process=image/resize,w_500,h_400,m_fill` : undefined, // 分享图片,调整为5:4比例
success: function (res: any) {
console.log('分享成功', res);
Taro.showToast({
@@ -280,8 +305,10 @@ const GoodsDetail = () => {
<>
- ¥
- {goods.price}
+ ¥
+ {goods.price}
+ 会员价
+ ¥{goods.salePrice}
已售 {goods.sales}
@@ -342,9 +369,7 @@ const GoodsDetail = () => {
)}
-
-
-
+
diff --git a/src/shop/orderConfirm/index.tsx b/src/shop/orderConfirm/index.tsx
index 58fe1c1..6ac3bd5 100644
--- a/src/shop/orderConfirm/index.tsx
+++ b/src/shop/orderConfirm/index.tsx
@@ -74,20 +74,20 @@ const OrderConfirm = () => {
const getGoodsTotal = () => {
if (!goods) return 0
const price = parseFloat(goods.price || '0')
- const total = price * quantity
+ // const total = price * quantity
// 🔍 详细日志,用于排查数值精度问题
- console.log('💵 商品总价计算:', {
- goodsPrice: goods.price,
- goodsPriceType: typeof goods.price,
- parsedPrice: price,
- quantity: quantity,
- total: total,
- totalFixed2: total.toFixed(2),
- totalString: total.toString()
- })
+ // console.log('💵 商品总价计算:', {
+ // goodsPrice: goods.price,
+ // goodsPriceType: typeof goods.price,
+ // parsedPrice: price,
+ // quantity: quantity,
+ // total: total,
+ // totalFixed2: total.toFixed(2),
+ // totalString: total.toString()
+ // })
- return total
+ return price * quantity
}
// 计算优惠券折扣
@@ -418,9 +418,9 @@ const OrderConfirm = () => {
couponId: parseInt(String(bestCoupon.id), 10)
}
);
-
+
console.log('🎯 使用推荐优惠券的订单数据:', updatedOrderData);
-
+
// 执行支付
await PaymentHandler.pay(updatedOrderData, currentPaymentType);
return; // 提前返回,避免重复执行支付
@@ -437,7 +437,7 @@ const OrderConfirm = () => {
quantity,
address.id,
{
- comments: goods.name,
+ comments: '网宿软件',
deliveryType: 0,
buyerRemarks: orderRemark,
// 🔧 确保 couponId 是正确的数字类型,且不传递 undefined
diff --git a/src/shop/orderDetail/index.tsx b/src/shop/orderDetail/index.tsx
index c51595e..6350bcb 100644
--- a/src/shop/orderDetail/index.tsx
+++ b/src/shop/orderDetail/index.tsx
@@ -1,5 +1,5 @@
import {useEffect, useState} from "react";
-import {Cell, CellGroup, Image, Space, Button} from '@nutui/nutui-react-taro'
+import {Cell, CellGroup, Image, Space, Button, Dialog} from '@nutui/nutui-react-taro'
import Taro from '@tarojs/taro'
import {View} from '@tarojs/components'
import {ShopOrder} from "@/api/shop/shopOrder/model";
@@ -13,6 +13,7 @@ import './index.scss'
const OrderDetail = () => {
const [order, setOrder] = useState(null);
const [orderGoodsList, setOrderGoodsList] = useState([]);
+ const [confirmReceiveDialogVisible, setConfirmReceiveDialogVisible] = useState(false)
const router = Taro.getCurrentInstance().router;
const orderId = router?.params?.orderId;
@@ -40,6 +41,52 @@ const OrderDetail = () => {
}
};
+ // 申请退款
+ const handleApplyRefund = async () => {
+ if (order) {
+ try {
+ // 更新订单状态为"退款申请中"
+ await updateShopOrder({
+ orderId: order.orderId,
+ orderStatus: 4 // 退款申请中
+ });
+
+ // 更新本地状态
+ setOrder(prev => prev ? {...prev, orderStatus: 4} : null);
+
+ // 跳转到退款申请页面
+ Taro.navigateTo({
+ url: `/user/order/refund/index?orderId=${order.orderId}&orderNo=${order.orderNo}`
+ });
+ } catch (error) {
+ console.error('更新订单状态失败:', error);
+ Taro.showToast({
+ title: '操作失败,请重试',
+ icon: 'none'
+ });
+ }
+ }
+ };
+
+ // 确认收货(客户)
+ const handleConfirmReceive = async () => {
+ if (!order?.orderId) return
+ try {
+ setConfirmReceiveDialogVisible(false)
+ await updateShopOrder({
+ orderId: order.orderId,
+ deliveryStatus: order.deliveryStatus, // 10未发货 20已发货 30部分发货
+ orderStatus: 1 // 已完成
+ })
+ Taro.showToast({title: '确认收货成功', icon: 'success'})
+ setOrder(prev => (prev ? {...prev, orderStatus: 1} : prev))
+ } catch (e) {
+ console.error('确认收货失败:', e)
+ Taro.showToast({title: '确认收货失败', icon: 'none'})
+ setConfirmReceiveDialogVisible(true)
+ }
+ }
+
const getOrderStatusText = (order: ShopOrder) => {
// 优先检查订单状态
if (order.orderStatus === 2) return '已取消';
@@ -54,8 +101,15 @@ const OrderDetail = () => {
// 已付款后检查发货状态
if (order.deliveryStatus === 10) return '待发货';
- if (order.deliveryStatus === 20) return '待收货';
- if (order.deliveryStatus === 30) return '已收货';
+ if (order.deliveryStatus === 20) {
+ // 若订单有配送员,则以配送员送达时间作为“可确认收货”的依据
+ if (order.riderId) {
+ if (order.sendEndTime && order.orderStatus !== 1) return '待确认收货';
+ return '配送中';
+ }
+ return '待收货';
+ }
+ if (order.deliveryStatus === 30) return '部分发货';
// 最后检查订单完成状态
if (order.orderStatus === 1) return '已完成';
@@ -106,6 +160,15 @@ const OrderDetail = () => {
return 加载中...
;
}
+ const currentUserId = Number(Taro.getStorageSync('UserId'))
+ const isOwner = !!currentUserId && currentUserId === order.userId
+ const canConfirmReceive =
+ isOwner &&
+ order.payStatus &&
+ order.orderStatus !== 1 &&
+ order.deliveryStatus === 20 &&
+ (!order.riderId || !!order.sendEndTime)
+
return (
{/* 支付倒计时显示 - 详情页实时更新 */}
@@ -162,12 +225,26 @@ const OrderDetail = () => {
{!order.payStatus && }
{!order.payStatus && }
- {order.orderStatus === 1 && }
- {order.deliveryStatus === 20 &&
- }
+ {order.orderStatus === 1 && }
+ {canConfirmReceive && (
+
+ )}
+
+
);
};
diff --git a/src/store/index.config.ts b/src/store/index.config.ts
new file mode 100644
index 0000000..5ec6c4d
--- /dev/null
+++ b/src/store/index.config.ts
@@ -0,0 +1,3 @@
+export default definePageConfig({
+ navigationBarTitleText: '门店中心'
+})
diff --git a/src/store/index.scss b/src/store/index.scss
new file mode 100644
index 0000000..e69de29
diff --git a/src/store/index.tsx b/src/store/index.tsx
new file mode 100644
index 0000000..37ef85a
--- /dev/null
+++ b/src/store/index.tsx
@@ -0,0 +1,281 @@
+import React, {useCallback, useState} from 'react'
+import {View, Text} from '@tarojs/components'
+import {Avatar, Button, ConfigProvider, Grid} from '@nutui/nutui-react-taro'
+import {Location, Scan, Shop, Shopping, User} from '@nutui/icons-react-taro'
+import Taro, {useDidShow} from '@tarojs/taro'
+import {useThemeStyles} from '@/hooks/useTheme'
+import {useUser} from '@/hooks/useUser'
+import {getSelectedStoreFromStorage} from '@/utils/storeSelection'
+import {listShopStoreUser} from '@/api/shop/shopStoreUser'
+import {getShopStore} from '@/api/shop/shopStore'
+import type {ShopStore as ShopStoreModel} from '@/api/shop/shopStore/model'
+
+const StoreIndex: React.FC = () => {
+ const themeStyles = useThemeStyles()
+ const {isLoggedIn, loading: userLoading, getAvatarUrl, getDisplayName, getRoleName, hasRole} = useUser()
+
+ const [boundStoreId, setBoundStoreId] = useState(undefined)
+ const [selectedStore, setSelectedStore] = useState(getSelectedStoreFromStorage())
+ const [store, setStore] = useState(selectedStore)
+ const [loading, setLoading] = useState(false)
+ const [error, setError] = useState(null)
+
+ const storeId = boundStoreId || selectedStore?.id
+
+ const parseStoreCoords = (s: ShopStoreModel): {lng: number; lat: number} | null => {
+ const raw = (s.lngAndLat || s.location || '').trim()
+ if (!raw) return null
+
+ const parts = raw.split(/[,\s]+/).filter(Boolean)
+ if (parts.length < 2) return null
+
+ const a = parseFloat(parts[0])
+ const b = parseFloat(parts[1])
+ if (Number.isNaN(a) || Number.isNaN(b)) return null
+
+ // 常见格式是 "lng,lat";这里做一个简单兜底
+ const looksLikeLngLat = Math.abs(a) <= 180 && Math.abs(b) <= 90
+ const looksLikeLatLng = Math.abs(a) <= 90 && Math.abs(b) <= 180
+ if (looksLikeLngLat) return {lng: a, lat: b}
+ if (looksLikeLatLng) return {lng: b, lat: a}
+ return null
+ }
+
+ const navigateToPage = (url: string) => {
+ if (!isLoggedIn) {
+ Taro.showToast({title: '请先登录', icon: 'none', duration: 1500})
+ return
+ }
+ Taro.navigateTo({url})
+ }
+
+ const refresh = useCallback(async () => {
+ setError(null)
+ setLoading(true)
+ try {
+ const latestSelectedStore = getSelectedStoreFromStorage()
+ setSelectedStore(latestSelectedStore)
+
+ const userIdRaw = Number(Taro.getStorageSync('UserId'))
+ const userId = Number.isFinite(userIdRaw) && userIdRaw > 0 ? userIdRaw : undefined
+
+ let foundStoreId: number | undefined = undefined
+ if (userId) {
+ // 优先按“店员绑定关系”确定门店归属
+ try {
+ const list = await listShopStoreUser({userId})
+ const first = (list || []).find(i => i?.isDelete !== 1 && i?.storeId)
+ foundStoreId = first?.storeId
+ setBoundStoreId(foundStoreId)
+ } catch {
+ // fallback to SelectedStore
+ foundStoreId = undefined
+ setBoundStoreId(undefined)
+ }
+ } else {
+ foundStoreId = undefined
+ setBoundStoreId(undefined)
+ }
+
+ const nextStoreId = (foundStoreId || latestSelectedStore?.id)
+ if (!nextStoreId) {
+ setStore(latestSelectedStore)
+ return
+ }
+
+ // 获取门店详情(用于展示门店名称/地址/仓库等)
+ const full = await getShopStore(nextStoreId)
+ setStore(full || (latestSelectedStore?.id === nextStoreId ? latestSelectedStore : ({id: nextStoreId} as ShopStoreModel)))
+ } catch (e: any) {
+ const msg = e?.message || '获取门店信息失败'
+ setError(msg)
+ } finally {
+ setLoading(false)
+ }
+ }, [])
+
+ // 返回/切换到该页面时,同步最新的已选门店与绑定门店
+ useDidShow(() => {
+ refresh().catch(() => {})
+ })
+
+ const openStoreLocation = () => {
+ if (!store?.id) {
+ return Taro.showToast({title: '请先选择门店', icon: 'none'})
+ }
+ const coords = parseStoreCoords(store)
+ if (!coords) {
+ return Taro.showToast({title: '门店未配置定位', icon: 'none'})
+ }
+ Taro.openLocation({
+ latitude: coords.lat,
+ longitude: coords.lng,
+ name: store.name || '门店',
+ address: store.address || ''
+ })
+ }
+
+ if (!isLoggedIn && !userLoading) {
+ return (
+
+
+ 请先登录后再进入门店中心
+
+
+
+
+
+ )
+ }
+
+ return (
+
+ {/* 头部信息 */}
+
+
+
+
+
+
+ }
+ className="mr-4"
+ style={{border: '2px solid rgba(255, 255, 255, 0.3)'}}
+ />
+
+
+ {getDisplayName()}
+
+
+ {hasRole('store') ? '门店' : hasRole('rider') ? '配送员' : getRoleName()}
+
+
+
+
+
+
+ {/* 门店信息 */}
+
+
+ 当前门店
+ Taro.switchTab({url: '/pages/index/index'})}
+ >
+ 切换门店
+
+
+
+ {!storeId ? (
+
+
+ 未选择门店,请先去首页选择门店。
+
+
+
+
+
+ ) : (
+
+
+ {store?.name || `门店ID: ${storeId}`}
+
+ {!!store?.address && (
+
+ {store.address}
+
+ )}
+ {!!store?.warehouseName && (
+
+ 默认仓库:{store.warehouseName}
+
+ )}
+ {!!error && (
+
+ {error}
+
+ )}
+
+ )}
+
+
+ {/* 功能入口 */}
+
+ 门店工具
+
+
+ navigateToPage('/store/orders/index')}>
+
+
+
+
+
+
+
+ navigateToPage('/user/store/verification')}>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Taro.switchTab({url: '/pages/index/index'})}>
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default StoreIndex
diff --git a/src/store/orders/index.config.ts b/src/store/orders/index.config.ts
new file mode 100644
index 0000000..4de19d9
--- /dev/null
+++ b/src/store/orders/index.config.ts
@@ -0,0 +1,4 @@
+export default {
+ navigationBarTitleText: '门店订单',
+ navigationBarTextStyle: 'black'
+}
diff --git a/src/store/orders/index.tsx b/src/store/orders/index.tsx
new file mode 100644
index 0000000..bcd5e8f
--- /dev/null
+++ b/src/store/orders/index.tsx
@@ -0,0 +1,83 @@
+import {useEffect, useMemo, useState} from 'react'
+import Taro from '@tarojs/taro'
+import {Button} from '@nutui/nutui-react-taro'
+import {View, Text} from '@tarojs/components'
+import OrderList from '@/user/order/components/OrderList'
+import {getSelectedStoreFromStorage} from '@/utils/storeSelection'
+import {listShopStoreUser} from '@/api/shop/shopStoreUser'
+
+export default function StoreOrders() {
+ const [boundStoreId, setBoundStoreId] = useState(undefined)
+
+ const isLoggedIn = useMemo(() => {
+ return !!Taro.getStorageSync('access_token') && !!Taro.getStorageSync('UserId')
+ }, [])
+
+ const selectedStore = useMemo(() => getSelectedStoreFromStorage(), [])
+ const storeId = boundStoreId || selectedStore?.id
+
+ useEffect(() => {
+ }, [])
+
+ useEffect(() => {
+ // 优先按“店员绑定关系”确定门店归属:门店看到的是自己的订单
+ const userId = Number(Taro.getStorageSync('UserId'))
+ if (!Number.isFinite(userId) || userId <= 0) return
+ listShopStoreUser({userId}).then(list => {
+ const first = (list || []).find(i => i?.isDelete !== 1 && i?.storeId)
+ if (first?.storeId) setBoundStoreId(first.storeId)
+ }).catch(() => {
+ // fallback to SelectedStore
+ })
+ }, [])
+
+ if (!isLoggedIn) {
+ return (
+
+
+ 请先登录
+
+
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
+ 当前门店:
+
+ {boundStoreId
+ ? (selectedStore?.id === boundStoreId ? (selectedStore?.name || `门店ID: ${boundStoreId}`) : `门店ID: ${boundStoreId}`)
+ : (selectedStore?.name || '未选择门店')}
+
+
+
+ {!storeId ? (
+
+
+ 请先在首页左上角选择门店,再查看门店订单。
+
+
+
+
+
+ ) : (
+
+ )}
+
+
+ )
+}
diff --git a/src/styles/gradients.ts b/src/styles/gradients.ts
index 2ad1cb4..11ab23a 100644
--- a/src/styles/gradients.ts
+++ b/src/styles/gradients.ts
@@ -40,9 +40,9 @@ export const gradientThemes: GradientTheme[] = [
},
{
name: 'nature',
- primary: '#43e97b',
- secondary: '#38f9d7',
- background: 'linear-gradient(135deg, #43e97b 0%, #38f9d7 100%)',
+ primary: '#03605c',
+ secondary: '#18ae4f',
+ background: 'linear-gradient(135deg, #03605c 0%, #18ae4f 100%)',
textColor: '#ffffff',
description: '自然绿青 - 生机与成长'
},
diff --git a/src/types/giftCard.ts b/src/types/giftCard.ts
index 9178651..3736e25 100644
--- a/src/types/giftCard.ts
+++ b/src/types/giftCard.ts
@@ -4,7 +4,7 @@
/** 礼品卡类型枚举 */
export enum GiftCardType {
- /** 实物礼品卡 */
+ /** 礼品劵 */
PHYSICAL = 10,
/** 虚拟礼品卡 */
VIRTUAL = 20,
diff --git a/src/user/about/index.tsx b/src/user/about/index.tsx
index fec73d9..5011331 100644
--- a/src/user/about/index.tsx
+++ b/src/user/about/index.tsx
@@ -9,15 +9,14 @@ import {listCmsNavigation} from "@/api/cms/cmsNavigation";
import {View, RichText} from '@tarojs/components'
import {listCmsDesign} from "@/api/cms/cmsDesign";
import {CmsDesign} from "@/api/cms/cmsDesign/model";
-import {type Config} from "@/api/cms/cmsWebsiteField/model";
-import {configWebsiteField} from "@/api/cms/cmsWebsiteField";
+import { useConfig } from "@/hooks/useConfig"; // 使用新的自定义Hook
const Helper = () => {
const [nav, setNav] = useState()
const [design, setDesign] = useState()
const [category, setCategory] = useState([])
- const [config, setConfig] = useState()
+ const { config } = useConfig(); // 使用新的Hook
const reload = async () => {
const navs = await listCmsNavigation({model: 'page', parentId: 0});
@@ -33,9 +32,7 @@ const Helper = () => {
category[index].articles = await listCmsArticle({categoryId: item.navigationId});
})
setCategory(category)
- // 查询字段
- const configInfo = await configWebsiteField({})
- setConfig(configInfo)
+ // 注意:config 现在通过 useConfig Hook 获取,不再在这里调用 configWebsiteField
}
}
diff --git a/src/user/chat/message/add.tsx b/src/user/chat/message/add.tsx
index cadf847..a455fd0 100644
--- a/src/user/chat/message/add.tsx
+++ b/src/user/chat/message/add.tsx
@@ -110,7 +110,7 @@ const AddMessage = () => {
) : '选择发送对象'} extra={(
)}
- onClick={() => navTo(`/doctor/team/index`, true)}/>
+ onClick={() => navTo(`/dealer/team/index`, true)}/>
@@ -400,7 +400,7 @@ const GiftCardManage = () => {
- {/* 礼品卡列表 */}
+ {/* 水票列表 */}
{
@@ -450,14 +450,14 @@ const GiftCardManage = () => {
- 暂无未使用礼品卡
+ 暂无未使用水票
))
) : (
@@ -722,6 +759,7 @@ function OrderList(props: OrderListProps) {
实付金额:¥{item.payPrice}
{/* 操作按钮 */}
+ {!isReadOnly && (
{/* 待付款状态:显示取消订单和立即支付 */}
{(!item.payStatus) && item.orderStatus !== 2 && (
@@ -738,20 +776,20 @@ function OrderList(props: OrderListProps) {
)}
{/* 待发货状态:显示申请退款 */}
- {/*{item.payStatus && item.deliveryStatus === 10 && item.orderStatus !== 2 && item.orderStatus !== 4 && (*/}
- {/* */}
- {/*)}*/}
+ {item.payStatus && item.deliveryStatus === 10 && item.orderStatus !== 2 && item.orderStatus !== 4 && (
+
+ )}
{/* 待收货状态:显示查看物流和确认收货 */}
- {item.deliveryStatus === 20 && item.orderStatus !== 2 && (
+ {item.deliveryStatus === 20 && (!item.riderId || !!item.sendEndTime) && item.orderStatus !== 2 && (
- {/**/}
+
*/}
- {/**/}
+
)}
@@ -795,6 +833,7 @@ function OrderList(props: OrderListProps) {
}}>再次购买
)}
+ )}
)
diff --git a/src/user/order/order.tsx b/src/user/order/order.tsx
index a9c985d..d018696 100644
--- a/src/user/order/order.tsx
+++ b/src/user/order/order.tsx
@@ -4,7 +4,7 @@ import {Space, NavBar, Button, Input} from '@nutui/nutui-react-taro'
import {Search, Filter, ArrowLeft} from '@nutui/icons-react-taro'
import {View} from '@tarojs/components';
import OrderList from "./components/OrderList";
-import {useRouter} from '@tarojs/taro'
+import {useDidShow, useRouter} from '@tarojs/taro'
import {ShopOrderParam} from "@/api/shop/shopOrder/model";
import './order.scss'
@@ -72,6 +72,17 @@ function Order() {
reload().then()
}, []);
+ // 页面从其它页面返回/重新展示时,刷新一次列表数据
+ // 典型场景:微信支付取消后返回到待支付列表,需要重新拉取订单/商品信息,避免使用旧数据再次支付失败
+ useDidShow(() => {
+ const statusFilter =
+ params.statusFilter != undefined && params.statusFilter !== ''
+ ? parseInt(params.statusFilter)
+ : -1;
+ // 同步路由上的statusFilter,并触发子组件重新拉取列表
+ setSearchParams(prev => ({ ...prev, statusFilter }));
+ });
+
return (
diff --git a/src/user/order/refund/index.tsx b/src/user/order/refund/index.tsx
index da16d92..b484c09 100644
--- a/src/user/order/refund/index.tsx
+++ b/src/user/order/refund/index.tsx
@@ -13,6 +13,8 @@ import {
Empty,
InputNumber
} from '@nutui/nutui-react-taro'
+import { applyAfterSale } from '@/api/afterSale'
+import { updateShopOrder } from '@/api/shop/shopOrder'
import './index.scss'
// 订单商品信息
@@ -232,23 +234,55 @@ const RefundPage: React.FC = () => {
setSubmitting(true)
- // 模拟API调用
- await new Promise(resolve => setTimeout(resolve, 2000))
+ // 构造请求参数
+ const params = {
+ orderId: orderId || '',
+ type: 'refund' as const,
+ reason: refundApp.refundReason,
+ description: refundApp.refundDescription,
+ amount: refundApp.refundAmount,
+ contactPhone: refundApp.contactPhone,
+ evidenceImages: refundApp.evidenceImages,
+ goodsItems: refundApp.refundGoods.filter(item => item.refundNum > 0).map(item => ({
+ goodsId: item.goodsId,
+ quantity: item.refundNum
+ }))
+ }
- Taro.showToast({
- title: '退款申请提交成功',
- icon: 'success'
- })
+ // 调用API提交退款申请
+ const result = await applyAfterSale(params)
+
+ if (result.success) {
+ // 更新订单状态为"退款申请中"
+ if (orderId) {
+ try {
+ await updateShopOrder({
+ orderId: parseInt(orderId),
+ orderStatus: 4 // 退款申请中
+ })
+ } catch (updateError) {
+ console.error('更新订单状态失败:', updateError)
+ // 即使更新订单状态失败,也继续执行后续操作
+ }
+ }
- // 延迟返回上一页
- setTimeout(() => {
- Taro.navigateBack()
- }, 1500)
+ Taro.showToast({
+ title: '退款申请提交成功',
+ icon: 'success'
+ })
+
+ // 延迟返回上一页
+ setTimeout(() => {
+ Taro.navigateBack()
+ }, 1500)
+ } else {
+ throw new Error(result.message || '提交失败')
+ }
} catch (error) {
console.error('提交退款申请失败:', error)
Taro.showToast({
- title: '提交失败,请重试',
+ title: error instanceof Error ? error.message : '提交失败,请重试',
icon: 'none'
})
} finally {
@@ -420,4 +454,4 @@ const RefundPage: React.FC = () => {
)
}
-export default RefundPage
+export default RefundPage
\ No newline at end of file
diff --git a/src/user/store/orders/index.config.ts b/src/user/store/orders/index.config.ts
new file mode 100644
index 0000000..4de19d9
--- /dev/null
+++ b/src/user/store/orders/index.config.ts
@@ -0,0 +1,4 @@
+export default {
+ navigationBarTitleText: '门店订单',
+ navigationBarTextStyle: 'black'
+}
diff --git a/src/user/store/orders/index.tsx b/src/user/store/orders/index.tsx
new file mode 100644
index 0000000..d555dcc
--- /dev/null
+++ b/src/user/store/orders/index.tsx
@@ -0,0 +1,73 @@
+import {useEffect, useMemo, useState} from 'react'
+import Taro from '@tarojs/taro'
+import {NavBar, Button} from '@nutui/nutui-react-taro'
+import {ArrowLeft} from '@nutui/icons-react-taro'
+import {View, Text} from '@tarojs/components'
+import OrderList from '@/user/order/components/OrderList'
+import {getSelectedStoreFromStorage} from '@/utils/storeSelection'
+import {listShopStoreUser} from '@/api/shop/shopStoreUser'
+
+export default function StoreOrders() {
+ const [statusBarHeight, setStatusBarHeight] = useState(0)
+
+ const [boundStoreId, setBoundStoreId] = useState(undefined)
+ const store = useMemo(() => getSelectedStoreFromStorage(), [])
+ const storeId = boundStoreId || store?.id
+
+ useEffect(() => {
+ Taro.getSystemInfo({
+ success: (res) => setStatusBarHeight(res.statusBarHeight ?? 0)
+ })
+ }, [])
+
+ useEffect(() => {
+ // 优先按“店员绑定关系”确定门店归属:门店看到的是自己的订单
+ const userId = Number(Taro.getStorageSync('UserId'))
+ if (!Number.isFinite(userId) || userId <= 0) return
+ listShopStoreUser({userId}).then(list => {
+ const first = (list || []).find(i => i?.isDelete !== 1 && i?.storeId)
+ if (first?.storeId) setBoundStoreId(first.storeId)
+ }).catch(() => {
+ // fallback to SelectedStore
+ })
+ }, [])
+
+ return (
+
+
+ Taro.navigateBack()}/>}
+ >
+ 门店订单
+
+
+
+
+ 当前门店:
+ {store?.name || (boundStoreId ? `门店ID: ${boundStoreId}` : '未选择门店')}
+
+
+ {!storeId ? (
+
+
+ 请先在首页左上角选择门店,再查看门店订单。
+
+
+
+
+
+ ) : (
+
+ )}
+
+
+ )
+}
diff --git a/src/user/store/verification.tsx b/src/user/store/verification.tsx
index cb2cdb6..b5ea72a 100644
--- a/src/user/store/verification.tsx
+++ b/src/user/store/verification.tsx
@@ -161,13 +161,13 @@ const StoreVerification: React.FC = () => {
const getTypeText = (type: number) => {
switch (type) {
case 10:
- return '实物礼品卡'
+ return '礼品劵'
case 20:
return '虚拟礼品卡'
case 30:
return '服务礼品卡'
default:
- return '礼品卡'
+ return '水票'
}
}
diff --git a/src/user/theme/index.tsx b/src/user/theme/index.tsx
index ec41396..5abe11d 100644
--- a/src/user/theme/index.tsx
+++ b/src/user/theme/index.tsx
@@ -11,13 +11,13 @@ const ThemeSelector: React.FC = () => {
// 获取当前主题
useEffect(() => {
- const savedTheme = Taro.getStorageSync('user_theme') || 'auto'
+ const savedTheme = Taro.getStorageSync('user_theme') || 'nature'
setSelectedTheme(savedTheme)
if (savedTheme === 'auto') {
// 自动主题:根据用户ID生成
- const userId = Taro.getStorageSync('userId') || '1'
- const theme = gradientUtils.getThemeByUserId(userId)
+ const userId = Taro.getStorageSync('UserId') ?? Taro.getStorageSync('userId') ?? '1'
+ const theme = gradientUtils.getThemeByUserId(typeof userId === 'number' ? userId : parseInt(String(userId), 10))
setCurrentTheme(theme)
} else {
// 手动选择的主题
@@ -33,8 +33,8 @@ const ThemeSelector: React.FC = () => {
setSelectedTheme(themeName)
if (themeName === 'auto') {
- const userId = Taro.getStorageSync('userId') || '1'
- const theme = gradientUtils.getThemeByUserId(userId)
+ const userId = Taro.getStorageSync('UserId') ?? Taro.getStorageSync('userId') ?? '1'
+ const theme = gradientUtils.getThemeByUserId(typeof userId === 'number' ? userId : parseInt(String(userId), 10))
setCurrentTheme(theme)
} else {
const theme = gradientThemes.find(t => t.name === themeName)
@@ -61,8 +61,8 @@ const ThemeSelector: React.FC = () => {
// 预览主题
const previewTheme = (themeName: string) => {
if (themeName === 'auto') {
- const userId = Taro.getStorageSync('userId') || '1'
- const theme = gradientUtils.getThemeByUserId(userId)
+ const userId = Taro.getStorageSync('UserId') ?? Taro.getStorageSync('userId') ?? '1'
+ const theme = gradientUtils.getThemeByUserId(typeof userId === 'number' ? userId : parseInt(String(userId), 10))
setCurrentTheme(theme)
} else {
const theme = gradientThemes.find(t => t.name === themeName)
diff --git a/src/utils/customerStatus.ts b/src/utils/customerStatus.ts
deleted file mode 100644
index d77f63b..0000000
--- a/src/utils/customerStatus.ts
+++ /dev/null
@@ -1,103 +0,0 @@
-/**
- * 客户状态管理工具函数
- */
-
-// 客户状态类型定义
-export type CustomerStatus = 'all' | 'pending' | 'signed' | 'cancelled';
-
-// 客户状态配置
-export const CUSTOMER_STATUS_CONFIG = {
- all: {
- label: '全部',
- color: '#666666',
- tagType: 'default' as const
- },
- pending: {
- label: '跟进中',
- color: '#ff8800',
- tagType: 'warning' as const
- },
- signed: {
- label: '已签约',
- color: '#52c41a',
- tagType: 'success' as const
- },
- cancelled: {
- label: '已取消',
- color: '#999999',
- tagType: 'default' as const
- }
-};
-
-/**
- * 获取状态文本
- */
-export const getStatusText = (status: CustomerStatus): string => {
- return CUSTOMER_STATUS_CONFIG[status]?.label || '';
-};
-
-/**
- * 获取状态标签类型
- */
-export const getStatusTagType = (status: CustomerStatus) => {
- return CUSTOMER_STATUS_CONFIG[status]?.tagType || 'default';
-};
-
-/**
- * 获取状态颜色
- */
-export const getStatusColor = (status: CustomerStatus): string => {
- return CUSTOMER_STATUS_CONFIG[status]?.color || '#666666';
-};
-
-/**
- * 获取所有状态选项
- */
-export const getStatusOptions = () => {
- return Object.entries(CUSTOMER_STATUS_CONFIG).map(([value, config]) => ({
- value: value as CustomerStatus,
- label: config.label
- }));
-};
-
-/**
- * 将数字状态映射为字符串状态
- */
-export const mapApplyStatusToCustomerStatus = (applyStatus: number): CustomerStatus => {
- switch (applyStatus) {
- case 10:
- return 'pending'; // 跟进中
- case 20:
- return 'signed'; // 已签约
- case 30:
- return 'cancelled'; // 已取消
- default:
- return 'pending'; // 默认为跟进中
- }
-};
-
-/**
- * 将字符串状态映射为数字状态
- */
-export const mapCustomerStatusToApplyStatus = (customerStatus: CustomerStatus): number | undefined => {
- switch (customerStatus) {
- case 'pending':
- return 10; // 跟进中
- case 'signed':
- return 20; // 已签约
- case 'cancelled':
- return 30; // 已取消
- case 'all':
- return undefined; // 全部,不筛选
- default:
- return undefined;
- }
-};
-
-/**
- * 临时函数:生成随机状态(实际项目中应该删除,从数据库获取真实状态)
- */
-export const getRandomStatus = (): CustomerStatus => {
- const statuses: CustomerStatus[] = ['pending', 'signed', 'cancelled'];
- return statuses[Math.floor(Math.random() * statuses.length)];
-};
diff --git a/src/utils/dateUtils.ts b/src/utils/dateUtils.ts
deleted file mode 100644
index 126e75f..0000000
--- a/src/utils/dateUtils.ts
+++ /dev/null
@@ -1,126 +0,0 @@
-/**
- * 日期格式化工具函数
- * 用于处理各种日期格式转换
- */
-
-/**
- * 格式化日期为数据库格式 YYYY-MM-DD HH:mm:ss
- * @param dateStr 输入的日期字符串,支持多种格式
- * @returns 数据库格式的日期字符串
- */
-export const formatDateForDatabase = (dateStr: string): string => {
- if (!dateStr) return ''
-
- let parts: string[] = []
-
- // 处理不同的日期格式
- if (dateStr.includes('/')) {
- // 处理 YYYY/MM/DD 或 YYYY/M/D 格式
- parts = dateStr.split('/')
- } else if (dateStr.includes('-')) {
- // 处理 YYYY-MM-DD 或 YYYY-M-D 格式
- parts = dateStr.split('-')
- } else {
- return dateStr
- }
-
- if (parts.length !== 3) return dateStr
-
- const year = parts[0]
- const month = parts[1].padStart(2, '0')
- const day = parts[2].padStart(2, '0')
-
- return `${year}-${month}-${day} 00:00:00`
-}
-
-/**
- * 从数据库格式提取日期部分用于Calendar组件显示
- * @param dateTimeStr 数据库格式的日期时间字符串
- * @returns Calendar组件需要的格式 (YYYY-M-D)
- */
-export const extractDateForCalendar = (dateTimeStr: string): string => {
- if (!dateTimeStr) return ''
-
- // 处理不同的输入格式
- let dateStr = ''
- if (dateTimeStr.includes(' ')) {
- // 从 "YYYY-MM-DD HH:mm:ss" 格式中提取日期部分
- dateStr = dateTimeStr.split(' ')[0]
- } else {
- dateStr = dateTimeStr
- }
-
- // 转换为Calendar组件需要的格式 (YYYY-M-D)
- if (dateStr.includes('-')) {
- const parts = dateStr.split('-')
- if (parts.length === 3) {
- const year = parts[0]
- const month = parseInt(parts[1]).toString() // 去掉前导0
- const day = parseInt(parts[2]).toString() // 去掉前导0
- return `${year}-${month}-${day}`
- }
- }
-
- return dateStr
-}
-
-/**
- * 格式化日期为用户友好的显示格式 YYYY-MM-DD
- * @param dateStr 输入的日期字符串
- * @returns 用户友好的日期格式
- */
-export const formatDateForDisplay = (dateStr: string): string => {
- if (!dateStr) return ''
-
- // 如果是数据库格式,先提取日期部分
- let dateOnly = dateStr
- if (dateStr.includes(' ')) {
- dateOnly = dateStr.split(' ')[0]
- }
-
- // 如果已经是标准格式,直接返回
- if (/^\d{4}-\d{2}-\d{2}$/.test(dateOnly)) {
- return dateOnly
- }
-
- // 处理其他格式
- let parts: string[] = []
- if (dateOnly.includes('/')) {
- parts = dateOnly.split('/')
- } else if (dateOnly.includes('-')) {
- parts = dateOnly.split('-')
- } else {
- return dateStr
- }
-
- if (parts.length !== 3) return dateStr
-
- const year = parts[0]
- const month = parts[1].padStart(2, '0')
- const day = parts[2].padStart(2, '0')
-
- return `${year}-${month}-${day}`
-}
-
-/**
- * 获取当前日期的字符串格式
- * @param format 'database' | 'display' | 'calendar'
- * @returns 格式化的当前日期
- */
-export const getCurrentDate = (format: 'database' | 'display' | 'calendar' = 'display'): string => {
- const now = new Date()
- const year = now.getFullYear()
- const month = now.getMonth() + 1
- const day = now.getDate()
-
- switch (format) {
- case 'database':
- return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')} 00:00:00`
- case 'display':
- return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
- case 'calendar':
- return `${year}-${month}-${day}`
- default:
- return `${year}-${month.toString().padStart(2, '0')}-${day.toString().padStart(2, '0')}`
- }
-}
diff --git a/src/utils/invite.ts b/src/utils/invite.ts
index 34975d6..cfd88a4 100644
--- a/src/utils/invite.ts
+++ b/src/utils/invite.ts
@@ -235,6 +235,7 @@ export function getSourceDisplayName(source: string): string {
'qrcode': '小程序码',
'link': '分享链接',
'share': '好友分享',
+ 'goods_share': '商品分享',
'poster': '海报分享',
'unknown': '未知来源'
}
diff --git a/src/utils/orderGoods.ts b/src/utils/orderGoods.ts
new file mode 100644
index 0000000..ad12e37
--- /dev/null
+++ b/src/utils/orderGoods.ts
@@ -0,0 +1,32 @@
+import type { ShopOrderGoods } from '@/api/shop/shopOrderGoods/model';
+
+/**
+ * Normalize order goods data returned by the order/page API.
+ *
+ * In practice different backends may return different field names (orderGoods/orderGoodsList/goodsList...),
+ * and the item fields can also differ (goodsName/title/name, totalNum/quantity, etc.).
+ *
+ * We normalize them to ShopOrderGoods so list pages can render without doing N+1 requests per order.
+ */
+export const normalizeOrderGoodsList = (order: any): ShopOrderGoods[] => {
+ const raw =
+ order?.orderGoods ||
+ order?.orderGoodsList ||
+ order?.goodsList ||
+ order?.goods ||
+ [];
+ if (!Array.isArray(raw)) return [];
+
+ return raw.map((g: any) => ({
+ ...g,
+ goodsId: g?.goodsId ?? g?.itemId ?? g?.goods_id,
+ skuId: g?.skuId ?? g?.sku_id,
+ // When the API returns minimal fields, fall back to order title to avoid blank names.
+ goodsName: g?.goodsName ?? g?.goodsTitle ?? g?.title ?? g?.name ?? order?.title ?? '商品',
+ image: g?.image ?? g?.goodsImage ?? g?.cover ?? g?.pic,
+ spec: g?.spec ?? g?.specInfo ?? g?.spec_name,
+ totalNum: g?.totalNum ?? g?.quantity ?? g?.num ?? g?.count,
+ price: g?.price ?? g?.payPrice ?? g?.goodsPrice ?? g?.unitPrice
+ }));
+};
+
diff --git a/src/utils/payment.ts b/src/utils/payment.ts
index 29b5b4f..de0e5d1 100644
--- a/src/utils/payment.ts
+++ b/src/utils/payment.ts
@@ -1,6 +1,10 @@
import Taro from '@tarojs/taro';
import { createOrder, WxPayResult } from '@/api/shop/shopOrder';
import { OrderCreateRequest } from '@/api/shop/shopOrder/model';
+import { getSelectedStoreFromStorage, getSelectedStoreIdFromStorage } from '@/utils/storeSelection';
+import type { ShopStoreRider } from '@/api/shop/shopStoreRider/model';
+import type { ShopWarehouse } from '@/api/shop/shopWarehouse/model';
+import request from '@/utils/request';
/**
* 支付类型枚举
@@ -24,6 +28,9 @@ export interface PaymentCallback {
* 统一支付处理类
*/
export class PaymentHandler {
+ // 简单缓存,避免频繁请求(小程序单次运行生命周期内有效)
+ private static storeRidersCache = new Map();
+ private static warehousesCache: ShopWarehouse[] | null = null;
/**
* 执行支付
@@ -39,6 +46,36 @@ export class PaymentHandler {
Taro.showLoading({ title: '支付中...' });
try {
+ // 若调用方未指定门店,则自动注入“已选门店”,用于订单门店归属/统计。
+ if (orderData.storeId === undefined || orderData.storeId === null) {
+ const storeId = getSelectedStoreIdFromStorage();
+ if (storeId) {
+ orderData.storeId = storeId;
+ }
+ }
+ if (!orderData.storeName) {
+ const store = getSelectedStoreFromStorage();
+ if (store?.name) {
+ orderData.storeName = store.name;
+ }
+ }
+
+ // 自动派单:按门店骑手优先级(dispatchPriority)选择 riderId(不覆盖手动指定)
+ if ((orderData.riderId === undefined || orderData.riderId === null) && orderData.storeId) {
+ const riderUserId = await this.pickRiderUserIdForStore(orderData.storeId);
+ if (riderUserId) {
+ orderData.riderId = riderUserId;
+ }
+ }
+
+ // 仓库选择:若未指定 warehouseId,则按“离门店最近”兜底选择一个(不覆盖手动指定)
+ if ((orderData.warehouseId === undefined || orderData.warehouseId === null) && orderData.storeId) {
+ const warehouseId = await this.pickWarehouseIdForStore(orderData.storeId);
+ if (warehouseId) {
+ orderData.warehouseId = warehouseId;
+ }
+ }
+
// 设置支付类型
orderData.payType = paymentType;
@@ -119,6 +156,126 @@ export class PaymentHandler {
}
}
+ private static parseLngLat(raw: string | undefined): { lng: number; lat: number } | null {
+ const text = (raw || '').trim();
+ if (!text) return null;
+ const parts = text.split(/[,\s]+/).filter(Boolean);
+ if (parts.length < 2) return null;
+ const a = parseFloat(parts[0]);
+ const b = parseFloat(parts[1]);
+ if (Number.isNaN(a) || Number.isNaN(b)) return null;
+ const looksLikeLngLat = Math.abs(a) <= 180 && Math.abs(b) <= 90;
+ const looksLikeLatLng = Math.abs(a) <= 90 && Math.abs(b) <= 180;
+ if (looksLikeLngLat) return { lng: a, lat: b };
+ if (looksLikeLatLng) return { lng: b, lat: a };
+ return null;
+ }
+
+ private static distanceMeters(a: { lng: number; lat: number }, b: { lng: number; lat: number }) {
+ const toRad = (x: number) => (x * Math.PI) / 180;
+ const R = 6371000;
+ const dLat = toRad(b.lat - a.lat);
+ const dLng = toRad(b.lng - a.lng);
+ const lat1 = toRad(a.lat);
+ const lat2 = toRad(b.lat);
+ const sin1 = Math.sin(dLat / 2);
+ const sin2 = Math.sin(dLng / 2);
+ const h = sin1 * sin1 + Math.cos(lat1) * Math.cos(lat2) * sin2 * sin2;
+ return 2 * R * Math.asin(Math.min(1, Math.sqrt(h)));
+ }
+
+ private static async getRidersForStore(storeId: number): Promise {
+ const cached = this.storeRidersCache.get(storeId);
+ if (cached) return cached;
+
+ // 后端字段可能叫 dealerId 或 storeId,这里都带上,服务端忽略未知字段即可。
+ // 这里做一次路径兼容(camel vs kebab),避免接口路径不一致导致整单失败。
+ const list = await this.listByCompatEndpoint(
+ ['/shop/shop-store-rider'],
+ {
+ storeId: storeId,
+ status: 1
+ }
+ );
+
+ const usable = (list || []).filter(r => r?.isDelete !== 1 && (r.status === undefined || r.status === 1));
+ this.storeRidersCache.set(storeId, usable);
+ return usable;
+ }
+
+ private static async pickRiderUserIdForStore(storeId: number): Promise {
+ const riders = await this.getRidersForStore(storeId);
+ if (!riders.length) return undefined;
+
+ // 优先:启用 + 在线 + 自动派单,再按 dispatchPriority 由高到低
+ const score = (r: ShopStoreRider) => {
+ const enabled = (r.status === undefined || r.status === 1) ? 1 : 0;
+ const online = r.workStatus === 1 ? 1 : 0;
+ const auto = r.autoDispatchEnabled === 1 ? 1 : 0;
+ const p = typeof r.dispatchPriority === 'number' ? r.dispatchPriority : 0;
+ return enabled * 1000 + online * 100 + auto * 10 + p;
+ };
+
+ const sorted = [...riders].sort((a, b) => score(b) - score(a));
+ return sorted[0]?.userId;
+ }
+
+ private static async getWarehouses(): Promise {
+ if (this.warehousesCache) return this.warehousesCache;
+ const list = await this.listByCompatEndpoint(
+ ['/shop/shop-warehouse'],
+ {}
+ );
+ const usable = (list || []).filter(w => w?.isDelete !== 1 && (w.status === undefined || w.status === 1));
+ this.warehousesCache = usable;
+ return usable;
+ }
+
+ private static async pickWarehouseIdForStore(storeId: number): Promise {
+ const store = getSelectedStoreFromStorage();
+ if (!store?.id || store.id !== storeId) return undefined;
+
+ // 一门店一默认仓库:优先使用门店自带的 warehouseId
+ if (store.warehouseId) return store.warehouseId;
+
+ const storeCoords = this.parseLngLat(store.lngAndLat || store.location);
+ if (!storeCoords) return undefined;
+
+ const warehouses = await this.getWarehouses();
+ if (!warehouses.length) return undefined;
+
+ // 优先选择“门店仓”,否则选最近的任意仓库
+ const candidates = warehouses.filter(w => w.type?.includes('门店') || w.type?.includes('门店仓'));
+ const list = candidates.length ? candidates : warehouses;
+
+ const withDistance = list
+ .map(w => {
+ const coords = this.parseLngLat(w.lngAndLat);
+ if (!coords) return { w, d: Number.POSITIVE_INFINITY };
+ return { w, d: this.distanceMeters(storeCoords, coords) };
+ })
+ .sort((a, b) => a.d - b.d);
+
+ return withDistance[0]?.w?.id;
+ }
+
+ private static async listByCompatEndpoint(
+ urls: string[],
+ params: Record
+ ): Promise {
+ for (const url of urls) {
+ try {
+ const res: any = await (request as any).get(url, params, { showError: false });
+ if (res?.code === 0 && Array.isArray(res?.data)) {
+ return res.data as T[];
+ }
+ } catch (_e) {
+ // try next
+ }
+ }
+ return [];
+ }
+
/**
* 处理微信支付
*/
diff --git a/src/utils/request.ts b/src/utils/request.ts
index 628a9d6..58b5ac9 100644
--- a/src/utils/request.ts
+++ b/src/utils/request.ts
@@ -53,29 +53,7 @@ const DEFAULT_CONFIG = {
showError: true
};
-// 获取API基础地址的函数
-const getBaseUrl = (): string => {
- // 尝试从本地存储获取后台配置的API地址
- try {
- const configStr = Taro.getStorageSync('config');
- if (configStr) {
- // 如果是字符串,需要解析为对象
- const config = typeof configStr === 'string' ? JSON.parse(configStr) : configStr;
- // console.log('获取后台配置API地址:', config);
- // 注意属性名是 ApiUrl(首字母大写),不是 apiUrl
- if (config && config.ApiUrl) {
- // console.log('使用后台配置的API地址:', config.ApiUrl);
- return config.ApiUrl;
- }
- }
- } catch (error) {
- console.warn('获取后台配置API地址失败:', error);
- }
-
- // 如果后台没有配置API地址,则使用本地配置
- console.log('使用本地配置的API地址:', BaseUrl);
- return BaseUrl;
-};
+let baseUrl = BaseUrl;
// 开发环境配置
if (process.env.NODE_ENV === 'development') {
@@ -325,11 +303,8 @@ export async function request(options: RequestConfig): Promise {
// 构建完整URL
const buildUrl = (url: string): string => {
- // 每次构建URL时都检查最新的baseUrl
- const currentBaseUrl = getBaseUrl();
-
if (url.indexOf('http') === -1) {
- return currentBaseUrl + url;
+ return baseUrl + url;
}
return url;
};
diff --git a/src/utils/storeSelection.ts b/src/utils/storeSelection.ts
new file mode 100644
index 0000000..91438a7
--- /dev/null
+++ b/src/utils/storeSelection.ts
@@ -0,0 +1,27 @@
+import Taro from '@tarojs/taro';
+import type { ShopStore } from '@/api/shop/shopStore/model';
+
+export const SELECTED_STORE_STORAGE_KEY = 'SelectedStore';
+
+export function getSelectedStoreFromStorage(): ShopStore | null {
+ try {
+ const raw = Taro.getStorageSync(SELECTED_STORE_STORAGE_KEY);
+ if (!raw) return null;
+ return (typeof raw === 'string' ? JSON.parse(raw) : raw) as ShopStore;
+ } catch (_e) {
+ return null;
+ }
+}
+
+export function saveSelectedStoreToStorage(store: ShopStore | null) {
+ if (!store) {
+ Taro.removeStorageSync(SELECTED_STORE_STORAGE_KEY);
+ return;
+ }
+ Taro.setStorageSync(SELECTED_STORE_STORAGE_KEY, store);
+}
+
+export function getSelectedStoreIdFromStorage(): number | undefined {
+ return getSelectedStoreFromStorage()?.id;
+}
+