Compare commits
10 Commits
25eb1366c5
...
2681ccc94b
| Author | SHA1 | Date | |
|---|---|---|---|
| 2681ccc94b | |||
| d8011065d9 | |||
| e70eb5de69 | |||
| a1f9167a42 | |||
| 4098f2a7e2 | |||
| 5749fab9e8 | |||
| 894b4bf7ce | |||
| 32811faf54 | |||
| a5efb6250f | |||
| ca3ff9dc9e |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -23,3 +23,4 @@ yarn-error.log*
|
|||||||
.vscode/
|
.vscode/
|
||||||
*.swp
|
*.swp
|
||||||
*.swo
|
*.swo
|
||||||
|
/java/
|
||||||
|
|||||||
@@ -2,19 +2,23 @@
|
|||||||
export const ENV_CONFIG = {
|
export const ENV_CONFIG = {
|
||||||
// 开发环境
|
// 开发环境
|
||||||
development: {
|
development: {
|
||||||
API_BASE_URL: 'http://127.0.0.1:9200/api',
|
API_BASE_URL: 'https://clinic-api.websoft.top/api',
|
||||||
|
// API_BASE_URL: 'http://127.0.0.1:9200/api',
|
||||||
|
WS_URL: 'ws://127.0.0.1:9200/api/chat',
|
||||||
APP_NAME: '开发环境',
|
APP_NAME: '开发环境',
|
||||||
DEBUG: 'true',
|
DEBUG: 'true',
|
||||||
},
|
},
|
||||||
// 生产环境
|
// 生产环境
|
||||||
production: {
|
production: {
|
||||||
API_BASE_URL: 'https://mp-api.websoft.top/api',
|
API_BASE_URL: 'https://clinic-api.websoft.top/api',
|
||||||
|
WS_URL: 'wss://clinic-api.websoft.top/api/chat',
|
||||||
APP_NAME: '通源堂健康生态平台',
|
APP_NAME: '通源堂健康生态平台',
|
||||||
DEBUG: 'false',
|
DEBUG: 'false',
|
||||||
},
|
},
|
||||||
// 测试环境
|
// 测试环境
|
||||||
test: {
|
test: {
|
||||||
API_BASE_URL: 'https://mp-api.websoft.top/api',
|
API_BASE_URL: 'https://clinic-api.websoft.top/api',
|
||||||
|
WS_URL: 'wss://clinic-api.websoft.top/api/chat',
|
||||||
APP_NAME: '测试环境',
|
APP_NAME: '测试环境',
|
||||||
DEBUG: 'true',
|
DEBUG: 'true',
|
||||||
}
|
}
|
||||||
@@ -37,6 +41,7 @@ export function getEnvConfig() {
|
|||||||
// 导出环境变量
|
// 导出环境变量
|
||||||
export const {
|
export const {
|
||||||
API_BASE_URL,
|
API_BASE_URL,
|
||||||
|
WS_URL,
|
||||||
APP_NAME,
|
APP_NAME,
|
||||||
DEBUG
|
DEBUG
|
||||||
} = getEnvConfig()
|
} = getEnvConfig()
|
||||||
|
|||||||
@@ -99,3 +99,13 @@ export async function getClinicDoctorUser(id: number) {
|
|||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getClinicDoctorUserByUserId(id: number) {
|
||||||
|
const res = await request.get<ApiResult<ClinicDoctorUser>>(
|
||||||
|
'/clinic/clinic-doctor-user/getByUserId/' + id
|
||||||
|
);
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,101 +0,0 @@
|
|||||||
import request from '@/utils/request';
|
|
||||||
import type { ApiResult, PageResult } from '@/api/index';
|
|
||||||
import type { ClinicOrder, ClinicOrderParam } from './model';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 分页查询处方订单
|
|
||||||
*/
|
|
||||||
export async function pageClinicOrder(params: ClinicOrderParam) {
|
|
||||||
const res = await request.get<ApiResult<PageResult<ClinicOrder>>>(
|
|
||||||
'/clinic/clinic-order/page',
|
|
||||||
params
|
|
||||||
);
|
|
||||||
if (res.code === 0) {
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error(res.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询处方订单列表
|
|
||||||
*/
|
|
||||||
export async function listClinicOrder(params?: ClinicOrderParam) {
|
|
||||||
const res = await request.get<ApiResult<ClinicOrder[]>>(
|
|
||||||
'/clinic/clinic-order',
|
|
||||||
params
|
|
||||||
);
|
|
||||||
if (res.code === 0 && res.data) {
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error(res.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加处方订单
|
|
||||||
*/
|
|
||||||
export async function addClinicOrder(data: ClinicOrder) {
|
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
|
||||||
'/clinic/clinic-order',
|
|
||||||
data
|
|
||||||
);
|
|
||||||
if (res.code === 0) {
|
|
||||||
return res.message;
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error(res.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改处方订单
|
|
||||||
*/
|
|
||||||
export async function updateClinicOrder(data: ClinicOrder) {
|
|
||||||
const res = await request.put<ApiResult<unknown>>(
|
|
||||||
'/clinic/clinic-order',
|
|
||||||
data
|
|
||||||
);
|
|
||||||
if (res.code === 0) {
|
|
||||||
return res.message;
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error(res.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除处方订单
|
|
||||||
*/
|
|
||||||
export async function removeClinicOrder(id?: number) {
|
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
|
||||||
'/clinic/clinic-order/' + id
|
|
||||||
);
|
|
||||||
if (res.code === 0) {
|
|
||||||
return res.message;
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error(res.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量删除处方订单
|
|
||||||
*/
|
|
||||||
export async function removeBatchClinicOrder(data: (number | undefined)[]) {
|
|
||||||
const res = await request.del<ApiResult<unknown>>(
|
|
||||||
'/clinic/clinic-order/batch',
|
|
||||||
{
|
|
||||||
data
|
|
||||||
}
|
|
||||||
);
|
|
||||||
if (res.code === 0) {
|
|
||||||
return res.message;
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error(res.message));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据id查询处方订单
|
|
||||||
*/
|
|
||||||
export async function getClinicOrder(id: number) {
|
|
||||||
const res = await request.get<ApiResult<ClinicOrder>>(
|
|
||||||
'/clinic/clinic-order/' + id
|
|
||||||
);
|
|
||||||
if (res.code === 0 && res.data) {
|
|
||||||
return res.data;
|
|
||||||
}
|
|
||||||
return Promise.reject(new Error(res.message));
|
|
||||||
}
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
import type { PageParam } from '@/api/index';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处方订单
|
|
||||||
*/
|
|
||||||
export interface ClinicOrder {
|
|
||||||
// 订单号
|
|
||||||
orderId?: number;
|
|
||||||
// 订单编号
|
|
||||||
orderNo?: string;
|
|
||||||
// 订单类型,0商城订单 1预定订单/外卖 2会员卡
|
|
||||||
type?: number;
|
|
||||||
// 订单标题
|
|
||||||
title?: string;
|
|
||||||
// 快递/自提
|
|
||||||
deliveryType?: number;
|
|
||||||
// 下单渠道,0小程序预定 1俱乐部训练场 3活动订场
|
|
||||||
channel?: number;
|
|
||||||
// 微信支付交易号号
|
|
||||||
transactionId?: string;
|
|
||||||
// 微信退款订单号
|
|
||||||
refundOrder?: string;
|
|
||||||
// 商户ID
|
|
||||||
merchantId?: number;
|
|
||||||
// 商户名称
|
|
||||||
merchantName?: string;
|
|
||||||
// 商户编号
|
|
||||||
merchantCode?: string;
|
|
||||||
// 使用的优惠券id
|
|
||||||
couponId?: number;
|
|
||||||
// 使用的会员卡id
|
|
||||||
cardId?: string;
|
|
||||||
// 关联管理员id
|
|
||||||
adminId?: number;
|
|
||||||
// 核销管理员id
|
|
||||||
confirmId?: number;
|
|
||||||
// IC卡号
|
|
||||||
icCard?: string;
|
|
||||||
// 真实姓名
|
|
||||||
realName?: string;
|
|
||||||
// 关联收货地址
|
|
||||||
addressId?: number;
|
|
||||||
// 收货地址
|
|
||||||
address?: string;
|
|
||||||
//
|
|
||||||
addressLat?: string;
|
|
||||||
//
|
|
||||||
addressLng?: string;
|
|
||||||
// 买家留言
|
|
||||||
buyerRemarks?: string;
|
|
||||||
// 自提店铺id
|
|
||||||
selfTakeMerchantId?: number;
|
|
||||||
// 自提店铺
|
|
||||||
selfTakeMerchantName?: string;
|
|
||||||
// 配送开始时间
|
|
||||||
sendStartTime?: string;
|
|
||||||
// 配送结束时间
|
|
||||||
sendEndTime?: string;
|
|
||||||
// 发货店铺id
|
|
||||||
expressMerchantId?: number;
|
|
||||||
// 发货店铺
|
|
||||||
expressMerchantName?: string;
|
|
||||||
// 订单总额
|
|
||||||
totalPrice?: string;
|
|
||||||
// 减少的金额,使用VIP会员折扣、优惠券抵扣、优惠券折扣后减去的价格
|
|
||||||
reducePrice?: string;
|
|
||||||
// 实际付款
|
|
||||||
payPrice?: string;
|
|
||||||
// 用于统计
|
|
||||||
price?: string;
|
|
||||||
// 价钱,用于积分赠送
|
|
||||||
money?: string;
|
|
||||||
// 取消时间
|
|
||||||
cancelTime?: string;
|
|
||||||
// 取消原因
|
|
||||||
cancelReason?: string;
|
|
||||||
// 退款金额
|
|
||||||
refundMoney?: string;
|
|
||||||
// 教练价格
|
|
||||||
coachPrice?: string;
|
|
||||||
// 购买数量
|
|
||||||
totalNum?: number;
|
|
||||||
// 教练id
|
|
||||||
coachId?: number;
|
|
||||||
// 商品ID
|
|
||||||
formId?: number;
|
|
||||||
// 支付的用户id
|
|
||||||
payUserId?: number;
|
|
||||||
// 0余额支付,1微信支付,2支付宝支付,3银联支付,4现金支付,5POS机支付,6免费,7积分支付
|
|
||||||
payType?: number;
|
|
||||||
// 微信支付子类型:JSAPI小程序支付,NATIVE扫码支付
|
|
||||||
wechatPayType?: string;
|
|
||||||
// 0余额支付,1微信支付,2支付宝支付,3银联支付,4现金支付,5POS机支付,6免费,7积分支付
|
|
||||||
friendPayType?: number;
|
|
||||||
// 0未付款,1已付款
|
|
||||||
payStatus?: string;
|
|
||||||
// 0未使用,1已完成,2已取消,3取消中,4退款申请中,5退款被拒绝,6退款成功,7客户端申请退款
|
|
||||||
orderStatus?: number;
|
|
||||||
// 发货状态(10未发货 20已发货 30部分发货)
|
|
||||||
deliveryStatus?: number;
|
|
||||||
// 无需发货备注
|
|
||||||
deliveryNote?: string;
|
|
||||||
// 发货时间
|
|
||||||
deliveryTime?: string;
|
|
||||||
// 评价状态(0未评价 1已评价)
|
|
||||||
evaluateStatus?: number;
|
|
||||||
// 评价时间
|
|
||||||
evaluateTime?: string;
|
|
||||||
// 优惠类型:0无、1抵扣优惠券、2折扣优惠券、3、VIP月卡、4VIP年卡,5VIP次卡、6VIP会员卡、7IC月卡、8IC年卡、9IC次卡、10IC会员卡、11免费订单、12VIP充值卡、13IC充值卡、14VIP季卡、15IC季卡
|
|
||||||
couponType?: number;
|
|
||||||
// 优惠说明
|
|
||||||
couponDesc?: string;
|
|
||||||
// 二维码地址,保存订单号,支付成功后才生成
|
|
||||||
qrcode?: string;
|
|
||||||
// vip月卡年卡、ic月卡年卡回退次数
|
|
||||||
returnNum?: number;
|
|
||||||
// vip充值回退金额
|
|
||||||
returnMoney?: string;
|
|
||||||
// 预约详情开始时间数组
|
|
||||||
startTime?: string;
|
|
||||||
// 是否已开具发票:0未开发票,1已开发票,2不能开具发票
|
|
||||||
isInvoice?: string;
|
|
||||||
// 发票流水号
|
|
||||||
invoiceNo?: string;
|
|
||||||
// 商家留言
|
|
||||||
merchantRemarks?: string;
|
|
||||||
// 支付时间
|
|
||||||
payTime?: string;
|
|
||||||
// 退款时间
|
|
||||||
refundTime?: string;
|
|
||||||
// 申请退款时间
|
|
||||||
refundApplyTime?: string;
|
|
||||||
// 过期时间
|
|
||||||
expirationTime?: string;
|
|
||||||
// 自提码
|
|
||||||
selfTakeCode?: string;
|
|
||||||
// 是否已收到赠品
|
|
||||||
hasTakeGift?: string;
|
|
||||||
// 对账情况:0=未对账;1=已对账;3=已对账,金额对不上;4=未查询到该订单
|
|
||||||
checkBill?: number;
|
|
||||||
// 订单是否已结算(0未结算 1已结算)
|
|
||||||
isSettled?: number;
|
|
||||||
// 系统版本号 0当前版本 value=其他版本
|
|
||||||
version?: number;
|
|
||||||
// 用户id
|
|
||||||
userId?: number;
|
|
||||||
// 备注
|
|
||||||
comments?: string;
|
|
||||||
// 排序号
|
|
||||||
sortNumber?: number;
|
|
||||||
// 是否删除, 0否, 1是
|
|
||||||
deleted?: number;
|
|
||||||
// 租户id
|
|
||||||
tenantId?: number;
|
|
||||||
// 修改时间
|
|
||||||
updateTime?: string;
|
|
||||||
// 创建时间
|
|
||||||
createTime?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 处方订单搜索条件
|
|
||||||
*/
|
|
||||||
export interface ClinicOrderParam extends PageParam {
|
|
||||||
orderId?: number;
|
|
||||||
keywords?: string;
|
|
||||||
}
|
|
||||||
@@ -16,6 +16,20 @@ export async function pageClinicPatientUser(params: ClinicPatientUserParam) {
|
|||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询患者
|
||||||
|
*/
|
||||||
|
export async function userPageClinicPatientUser(params: ClinicPatientUserParam) {
|
||||||
|
const res = await request.get<ApiResult<PageResult<ClinicPatientUser>>>(
|
||||||
|
'/clinic/clinic-patient-user/userPage',
|
||||||
|
params
|
||||||
|
);
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询患者列表
|
* 查询患者列表
|
||||||
*/
|
*/
|
||||||
@@ -99,3 +113,13 @@ export async function getClinicPatientUser(id: number) {
|
|||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function clinicPatientUserByPatientUserId(id: number) {
|
||||||
|
const res = await request.get<ApiResult<ClinicPatientUser>>(
|
||||||
|
'/clinic/clinic-patient-user/getByPatientUserId/' + id
|
||||||
|
);
|
||||||
|
if (res.code === 0 && res.data) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message));
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export interface ClinicPatientUser {
|
|||||||
type?: number;
|
type?: number;
|
||||||
// 自增ID
|
// 自增ID
|
||||||
userId?: number;
|
userId?: number;
|
||||||
|
patientUserId?: number;
|
||||||
// 姓名
|
// 姓名
|
||||||
realName?: string;
|
realName?: string;
|
||||||
// 头像
|
// 头像
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ export async function listClinicPrescription(params?: ClinicPrescriptionParam) {
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
export async function addClinicPrescription(data: ClinicPrescription) {
|
export async function addClinicPrescription(data: ClinicPrescription) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await request.post<ApiResult<ClinicPrescription>>(
|
||||||
'/clinic/clinic-prescription',
|
'/clinic/clinic-prescription',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
return res.message;
|
return res.data; // 返回处方数据,包含处方ID
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
@@ -106,3 +106,40 @@ export async function getClinicPrescription(id: number) {
|
|||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付返回数据
|
||||||
|
*/
|
||||||
|
export interface WxPayResult {
|
||||||
|
prepayId: string;
|
||||||
|
orderNo: string;
|
||||||
|
timeStamp: string;
|
||||||
|
nonceStr: string;
|
||||||
|
package: string;
|
||||||
|
signType: string;
|
||||||
|
paySign: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处方订单创建请求
|
||||||
|
*/
|
||||||
|
export interface PrescriptionOrderRequest {
|
||||||
|
// 处方ID
|
||||||
|
prescriptionId: number;
|
||||||
|
// 支付方式 0余额支付, 1微信支付,3支付宝
|
||||||
|
payType: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建处方订单并支付
|
||||||
|
*/
|
||||||
|
export async function createPrescriptionOrder(data: PrescriptionOrderRequest) {
|
||||||
|
const res = await request.post<ApiResult<WxPayResult>>(
|
||||||
|
'/clinic/clinic-prescription/order',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { PageParam } from '@/api/index';
|
import type { PageParam } from '@/api/index';
|
||||||
import {ClinicPrescriptionItem} from "@/api/clinic/clinicPrescriptionItem/model";
|
import {ClinicPrescriptionItem} from "@/api/clinic/clinicPrescriptionItem/model";
|
||||||
|
import {ShopOrder} from "@/api/shop/shopOrder/model";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 处方主表
|
* 处方主表
|
||||||
@@ -10,8 +11,20 @@ export interface ClinicPrescription {
|
|||||||
id?: number;
|
id?: number;
|
||||||
// 患者
|
// 患者
|
||||||
userId?: number;
|
userId?: number;
|
||||||
|
// 姓名
|
||||||
|
realName?: string;
|
||||||
|
// 性别 0男 1女
|
||||||
|
sex?: number;
|
||||||
|
// 年龄
|
||||||
|
age?: string;
|
||||||
|
// 身高
|
||||||
|
height?: string;
|
||||||
|
// 体重
|
||||||
|
weight?: string;
|
||||||
// 医生
|
// 医生
|
||||||
doctorId?: number;
|
doctorId?: number;
|
||||||
|
// 姓名
|
||||||
|
doctorName?: string;
|
||||||
// 订单编号
|
// 订单编号
|
||||||
orderNo?: string;
|
orderNo?: string;
|
||||||
// 关联就诊表
|
// 关联就诊表
|
||||||
@@ -50,6 +63,11 @@ export interface ClinicPrescription {
|
|||||||
updateTime?: string;
|
updateTime?: string;
|
||||||
// 药方信息
|
// 药方信息
|
||||||
items?: ClinicPrescriptionItem[];
|
items?: ClinicPrescriptionItem[];
|
||||||
|
// 支付状态
|
||||||
|
payStatus?: boolean;
|
||||||
|
// 订单状态
|
||||||
|
orderStatus?: number;
|
||||||
|
shopOrder?: ShopOrder
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,5 +76,8 @@ export interface ClinicPrescription {
|
|||||||
*/
|
*/
|
||||||
export interface ClinicPrescriptionParam extends PageParam {
|
export interface ClinicPrescriptionParam extends PageParam {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
doctorId?: number;
|
||||||
|
userId?: number;
|
||||||
keywords?: string;
|
keywords?: string;
|
||||||
|
withDoctor?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,16 @@ export async function addClinicPrescriptionItem(data: ClinicPrescriptionItem) {
|
|||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
export async function batchAddClinicPrescriptionItem(data: ClinicPrescriptionItem[]) {
|
||||||
|
const res = await request.post<ApiResult<unknown>>(
|
||||||
|
'/clinic/clinic-prescription-item/batch',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.message;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改处方明细表
|
* 修改处方明细表
|
||||||
|
|||||||
13
src/api/payment/index.ts
Normal file
13
src/api/payment/index.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import type { ApiResult } from '@/api';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一支付
|
||||||
|
*/
|
||||||
|
export async function pay(data: any) {
|
||||||
|
const res = await request.post<ApiResult<any>>('/payment/create-with-order',data);
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message));
|
||||||
|
}
|
||||||
29
src/api/payment/model/index.ts
Normal file
29
src/api/payment/model/index.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
/**
|
||||||
|
* 首页布局样式
|
||||||
|
*/
|
||||||
|
export interface Layout {
|
||||||
|
// 内容区域的宽度
|
||||||
|
width?: string;
|
||||||
|
// 文字颜色
|
||||||
|
color?: string;
|
||||||
|
// 高亮颜色
|
||||||
|
hover?: string;
|
||||||
|
// 背景颜色
|
||||||
|
backgroundColor?: string;
|
||||||
|
headerStyle?: any;
|
||||||
|
siteNameStyle?: any;
|
||||||
|
showBanner?: boolean;
|
||||||
|
// 背景图片
|
||||||
|
banner?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改密码参数
|
||||||
|
*/
|
||||||
|
export interface UpdatePasswordParam {
|
||||||
|
// 新密码
|
||||||
|
password: string;
|
||||||
|
// 原始密码
|
||||||
|
oldPassword: string;
|
||||||
|
}
|
||||||
@@ -34,12 +34,12 @@ export async function listShopChatConversation(params?: ShopChatConversationPara
|
|||||||
* 添加聊天会话表
|
* 添加聊天会话表
|
||||||
*/
|
*/
|
||||||
export async function addShopChatConversation(data: ShopChatConversation) {
|
export async function addShopChatConversation(data: ShopChatConversation) {
|
||||||
const res = await request.post<ApiResult<unknown>>(
|
const res = await request.post<ApiResult<ShopChatConversation>>(
|
||||||
'/shop/shop-chat-conversation',
|
'/shop/shop-chat-conversation',
|
||||||
data
|
data
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
return res.message;
|
return res.data;
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
@@ -99,3 +99,13 @@ export async function getShopChatConversation(id: number) {
|
|||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function chatConversationByBothUserId(userId: string) {
|
||||||
|
const res = await request.get<ApiResult<ShopChatConversation>>(
|
||||||
|
'/shop/shop-chat-conversation/getByBothUserId/' + userId
|
||||||
|
);
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message));
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ export interface ShopChatMessage {
|
|||||||
type?: string;
|
type?: string;
|
||||||
// 消息内容
|
// 消息内容
|
||||||
content?: string;
|
content?: string;
|
||||||
|
// 会话ID
|
||||||
|
conversationId?: number;
|
||||||
// 屏蔽接收方
|
// 屏蔽接收方
|
||||||
sideTo?: number;
|
sideTo?: number;
|
||||||
// 屏蔽发送方
|
// 屏蔽发送方
|
||||||
@@ -52,6 +54,8 @@ export interface ShopChatMessage {
|
|||||||
createTime?: string;
|
createTime?: string;
|
||||||
// 修改时间
|
// 修改时间
|
||||||
updateTime?: string;
|
updateTime?: string;
|
||||||
|
// 是否本人消息
|
||||||
|
isMine?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,5 +63,6 @@ export interface ShopChatMessage {
|
|||||||
*/
|
*/
|
||||||
export interface ShopChatMessageParam extends PageParam {
|
export interface ShopChatMessageParam extends PageParam {
|
||||||
id?: number;
|
id?: number;
|
||||||
|
conversationId?: number;
|
||||||
keywords?: string;
|
keywords?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ export async function updateShopOrder(data: ShopOrder) {
|
|||||||
data
|
data
|
||||||
);
|
);
|
||||||
if (res.code === 0) {
|
if (res.code === 0) {
|
||||||
return res.message;
|
return res;
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,14 +147,15 @@ export interface ShopOrder {
|
|||||||
hasTakeGift?: string;
|
hasTakeGift?: string;
|
||||||
// 订单商品项
|
// 订单商品项
|
||||||
orderGoods?: OrderGoods[];
|
orderGoods?: OrderGoods[];
|
||||||
|
makePay?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单商品项
|
* 订单商品项
|
||||||
*/
|
*/
|
||||||
export interface OrderGoodsItem {
|
export interface OrderGoodsItem {
|
||||||
goodsId: number;
|
goodsId?: number;
|
||||||
quantity: number;
|
quantity?: number;
|
||||||
skuId?: number;
|
skuId?: number;
|
||||||
specInfo?: string;
|
specInfo?: string;
|
||||||
}
|
}
|
||||||
@@ -164,7 +165,7 @@ export interface OrderGoodsItem {
|
|||||||
*/
|
*/
|
||||||
export interface OrderCreateRequest {
|
export interface OrderCreateRequest {
|
||||||
// 商品信息列表
|
// 商品信息列表
|
||||||
goodsItems: OrderGoodsItem[];
|
goodsItems?: OrderGoodsItem[];
|
||||||
// 收货地址ID
|
// 收货地址ID
|
||||||
addressId?: number;
|
addressId?: number;
|
||||||
// 支付方式
|
// 支付方式
|
||||||
@@ -185,8 +186,8 @@ export interface OrderCreateRequest {
|
|||||||
* 订单商品项
|
* 订单商品项
|
||||||
*/
|
*/
|
||||||
export interface OrderGoodsItem {
|
export interface OrderGoodsItem {
|
||||||
goodsId: number;
|
goodsId?: number;
|
||||||
quantity: number;
|
quantity?: number;
|
||||||
skuId?: number;
|
skuId?: number;
|
||||||
specInfo?: string;
|
specInfo?: string;
|
||||||
}
|
}
|
||||||
@@ -195,8 +196,10 @@ export interface OrderGoodsItem {
|
|||||||
* 创建订单请求
|
* 创建订单请求
|
||||||
*/
|
*/
|
||||||
export interface OrderCreateRequest {
|
export interface OrderCreateRequest {
|
||||||
|
// 订单编号
|
||||||
|
orderNo?: string;
|
||||||
// 商品信息列表
|
// 商品信息列表
|
||||||
goodsItems: OrderGoodsItem[];
|
goodsItems?: OrderGoodsItem[];
|
||||||
// 收货地址ID
|
// 收货地址ID
|
||||||
addressId?: number;
|
addressId?: number;
|
||||||
// 支付方式
|
// 支付方式
|
||||||
@@ -209,6 +212,8 @@ export interface OrderCreateRequest {
|
|||||||
deliveryType?: number;
|
deliveryType?: number;
|
||||||
// 自提店铺ID
|
// 自提店铺ID
|
||||||
selfTakeMerchantId?: number;
|
selfTakeMerchantId?: number;
|
||||||
|
// 订单类型
|
||||||
|
type?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -44,6 +44,17 @@ export async function addShopOrderGoods(data: ShopOrderGoods) {
|
|||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function batchAddShopOrderGoods(data: ShopOrderGoods[]) {
|
||||||
|
const res = await request.post<ApiResult<unknown>>(
|
||||||
|
'/shop/shop-order-goods/batch',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.message;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改商品信息
|
* 修改商品信息
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ export interface Order {
|
|||||||
updateTime?: string;
|
updateTime?: string;
|
||||||
// 创建时间
|
// 创建时间
|
||||||
createTime?: string;
|
createTime?: string;
|
||||||
|
makePay?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -104,8 +104,18 @@ export default {
|
|||||||
"root": "clinic",
|
"root": "clinic",
|
||||||
"pages": [
|
"pages": [
|
||||||
"index",
|
"index",
|
||||||
|
"clinicPatientUser/index",
|
||||||
"clinicPatientUser/add",
|
"clinicPatientUser/add",
|
||||||
"clinicDoctorUser/add"
|
"clinicPatientUser/selectPatient",
|
||||||
|
"clinicPatientUser/prescription",
|
||||||
|
"clinicPatientUser/detail",
|
||||||
|
"clinicDoctorUser/index",
|
||||||
|
"clinicDoctorUser/add",
|
||||||
|
"clinicPrescription/index",
|
||||||
|
"clinicPrescription/add",
|
||||||
|
"clinicPrescription/selectPrescription",
|
||||||
|
"clinicPrescription/confirm",
|
||||||
|
"clinicPrescription/detail"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,158 +1,284 @@
|
|||||||
import {useState, useEffect} from 'react'
|
import {useState, useEffect, useRef} from 'react'
|
||||||
import {View, Text} from '@tarojs/components'
|
import {View, Text} from '@tarojs/components'
|
||||||
import Taro, {useDidShow} from '@tarojs/taro'
|
import Taro, {useDidShow, useRouter, useLoad} from '@tarojs/taro'
|
||||||
import {useRouter} from '@tarojs/taro'
|
import {Input, Button} from '@nutui/nutui-react-taro'
|
||||||
import {
|
import {Voice, FaceMild} from '@nutui/icons-react-taro'
|
||||||
Loading,
|
import {getClinicDoctorUserByUserId} from "@/api/clinic/clinicDoctorUser";
|
||||||
InfiniteLoading,
|
import {addShopChatMessage, listShopChatMessage} from "@/api/shop/shopChatMessage";
|
||||||
Empty,
|
|
||||||
Space,
|
|
||||||
Input,
|
|
||||||
Avatar,
|
|
||||||
Tag,
|
|
||||||
Divider,
|
|
||||||
Button
|
|
||||||
} from '@nutui/nutui-react-taro'
|
|
||||||
import { Voice, FaceMild, AddCircle } from '@nutui/icons-react-taro'
|
|
||||||
import {getClinicDoctorUser} from "@/api/clinic/clinicDoctorUser";
|
|
||||||
import {ClinicDoctorUser} from "@/api/clinic/clinicDoctorUser/model";
|
|
||||||
import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model";
|
|
||||||
import navTo from "@/utils/common";
|
|
||||||
import {pageShopChatMessage} from "@/api/shop/shopChatMessage";
|
|
||||||
import {ShopChatMessage} from "@/api/shop/shopChatMessage/model";
|
import {ShopChatMessage} from "@/api/shop/shopChatMessage/model";
|
||||||
import Line from "@/components/Gap";
|
import {addShopChatConversation, chatConversationByBothUserId} from "@/api/shop/shopChatConversation";
|
||||||
|
// @ts-ignore
|
||||||
|
import {WS_URL} from "@/config/env";
|
||||||
|
import {clinicPatientUserByPatientUserId} from "@/api/clinic/clinicPatientUser";
|
||||||
|
|
||||||
const CustomerIndex = () => {
|
const CustomerIndex = () => {
|
||||||
const {params} = useRouter();
|
const {params} = useRouter()
|
||||||
const [doctor, setDoctor] = useState<ClinicDoctorUser>()
|
const [messages, setMessages] = useState<ShopChatMessage[]>([])
|
||||||
const [list, setList] = useState<ShopChatMessage[]>([])
|
const [conversationId, setConversationId] = useState<number | null>(null)
|
||||||
|
const [friendUserId, setFriendUserId] = useState<string>('')
|
||||||
|
const [messageInput, setMessageInput] = useState<string>('')
|
||||||
|
const [sending, setSending] = useState<boolean>(false)
|
||||||
const [isDoctor, setIsDoctor] = useState<boolean>(false)
|
const [isDoctor, setIsDoctor] = useState<boolean>(false)
|
||||||
const [doctors, setDoctors] = useState<ClinicDoctorUser[]>([])
|
const socketRef = useRef<Taro.SocketTask | null>(null)
|
||||||
const [patientUsers, setPatientUsers] = useState<ClinicPatientUser[]>([])
|
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
|
||||||
const [searchValue, setSearchValue] = useState<string>('')
|
|
||||||
const [displaySearchValue, setDisplaySearchValue] = useState<string>('')
|
|
||||||
const [page, setPage] = useState(1)
|
|
||||||
const [hasMore, setHasMore] = useState(true)
|
|
||||||
|
|
||||||
// 获取列表数据
|
const quickActions = [
|
||||||
const fetchData = async () => {
|
{label: '开方', type: 'prescription'},
|
||||||
setLoading(true);
|
{label: '快捷回复', type: 'quickReply'},
|
||||||
|
{label: '发问诊单', type: 'sendInquiry'}
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleQuickAction = (actionType: string) => {
|
||||||
|
switch (actionType) {
|
||||||
|
case 'prescription':
|
||||||
|
if (friendUserId) {
|
||||||
|
Taro.navigateTo({url: `/doctor/orders/add?id=${friendUserId}`})
|
||||||
|
} else {
|
||||||
|
Taro.showToast({title: '请选择患者', icon: 'none'})
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'quickReply':
|
||||||
|
Taro.showToast({title: '快捷回复敬请期待', icon: 'none'})
|
||||||
|
break
|
||||||
|
case 'sendInquiry':
|
||||||
|
Taro.showToast({title: '问诊单功能敬请期待', icon: 'none'})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fetchData = async (userId?: string) => {
|
||||||
|
if (!userId) return
|
||||||
|
try {
|
||||||
|
console.log("Taro.getStorageSync('Doctor')", Taro.getStorageSync('Doctor'))
|
||||||
if (Taro.getStorageSync('Doctor')) {
|
if (Taro.getStorageSync('Doctor')) {
|
||||||
setIsDoctor(true)
|
setIsDoctor(true)
|
||||||
}
|
}
|
||||||
const doctorUser = await getClinicDoctorUser(Number(params.id))
|
const isDoctorData = Taro.getStorageSync('Doctor')
|
||||||
if (doctorUser) {
|
if (!isDoctorData) {
|
||||||
setDoctor(doctorUser)
|
const doctorUser = await getClinicDoctorUserByUserId(Number(params.userId))
|
||||||
Taro.setNavigationBarTitle({title: `${doctorUser.realName}`});
|
if (doctorUser?.realName) {
|
||||||
|
await Taro.setNavigationBarTitle({title: `${doctorUser.realName}`})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const patient = await clinicPatientUserByPatientUserId(Number(params.userId))
|
||||||
|
if (patient?.realName) {
|
||||||
|
await Taro.setNavigationBarTitle({title: `${patient.realName}`})
|
||||||
}
|
}
|
||||||
const messages = await pageShopChatMessage({})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化数据
|
let conversation = await chatConversationByBothUserId(userId)
|
||||||
|
if (!conversation) {
|
||||||
|
conversation = await addShopChatConversation({
|
||||||
|
friendId: parseInt(userId, 10),
|
||||||
|
content: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conversation?.id) {
|
||||||
|
setConversationId(conversation.id)
|
||||||
|
const messageList = await listShopChatMessage({conversationId: conversation.id})
|
||||||
|
setMessages(messageList || [])
|
||||||
|
} else {
|
||||||
|
setMessages([])
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载聊天数据失败:', error)
|
||||||
|
Taro.showToast({title: '聊天加载失败', icon: 'none'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const connectWebSocket = async (id: number) => {
|
||||||
|
const base = (WS_URL || '').replace(/\/$/, '')
|
||||||
|
if (!base) {
|
||||||
|
console.warn('WS_URL 未配置')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (socketRef.current) {
|
||||||
|
socketRef.current.close({})
|
||||||
|
}
|
||||||
|
|
||||||
|
const userId = Taro.getStorageSync('UserId')
|
||||||
|
const result = Taro.connectSocket({
|
||||||
|
url: `${base}/${id}_${userId}`
|
||||||
|
})
|
||||||
|
|
||||||
|
const socketTask = typeof (result as Promise<any>)?.then === 'function'
|
||||||
|
? await (result as Promise<Taro.SocketTask>)
|
||||||
|
: (result as Taro.SocketTask)
|
||||||
|
|
||||||
|
if (!socketTask) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
socketRef.current = socketTask
|
||||||
|
|
||||||
|
socketTask.onOpen(() => {
|
||||||
|
console.log('WebSocket连接成功')
|
||||||
|
})
|
||||||
|
|
||||||
|
socketTask.onMessage((event) => {
|
||||||
|
console.log('收到消息:', event,)
|
||||||
|
try {
|
||||||
|
if (event.data === '连接成功' || event.data === 'pong') return
|
||||||
|
const payload = typeof event.data === 'string' ? JSON.parse(event.data) : event.data
|
||||||
|
if (!payload) return
|
||||||
|
if (Array.isArray(payload)) {
|
||||||
|
setMessages(prev => [...prev, ...payload])
|
||||||
|
} else {
|
||||||
|
payload.isMine = parseInt(payload.fromUserId) !== parseInt(params?.userId)
|
||||||
|
setMessages(prev => [...prev, payload])
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('解析消息失败:', err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
socketTask.onError((err) => {
|
||||||
|
console.error('WebSocket异常:', err)
|
||||||
|
})
|
||||||
|
|
||||||
|
socketTask.onClose(() => {
|
||||||
|
socketRef.current = null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSendMessage = async () => {
|
||||||
|
const content = messageInput.trim()
|
||||||
|
if (!content) {
|
||||||
|
Taro.showToast({title: '请输入内容', icon: 'none'})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!conversationId || !friendUserId) {
|
||||||
|
Taro.showToast({title: '会话未初始化', icon: 'none'})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (sending) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setSending(true)
|
||||||
|
await addShopChatMessage({
|
||||||
|
content,
|
||||||
|
conversationId,
|
||||||
|
toUserId: parseInt(friendUserId, 10),
|
||||||
|
type: 'text'
|
||||||
|
})
|
||||||
|
|
||||||
|
// const localMessage: ShopChatMessage = {
|
||||||
|
// id: Date.now(),
|
||||||
|
// content,
|
||||||
|
// conversationId,
|
||||||
|
// toUserId: parseInt(friendUserId, 10),
|
||||||
|
// type: 'text',
|
||||||
|
// isMine: true
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// setMessages(prev => [...prev, localMessage])
|
||||||
|
setMessageInput('')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('发送消息失败:', error)
|
||||||
|
Taro.showToast({title: '发送失败,请重试', icon: 'none'})
|
||||||
|
} finally {
|
||||||
|
setSending(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useLoad((options) => {
|
||||||
|
if (options?.userId) {
|
||||||
|
const userId = String(options.userId)
|
||||||
|
setFriendUserId(userId)
|
||||||
|
fetchData(userId)
|
||||||
|
}
|
||||||
|
console.log('Taro.getStorageSync(\'UserId\')', Taro.getStorageSync('UserId'))
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData().then()
|
if (conversationId) {
|
||||||
}, []);
|
connectWebSocket(conversationId).catch(err => {
|
||||||
|
console.error('WebSocket连接失败:', err)
|
||||||
// 监听页面显示,当从其他页面返回时刷新数据
|
})
|
||||||
useDidShow(() => {
|
|
||||||
// 刷新当前tab的数据和统计信息
|
|
||||||
fetchData().then();
|
|
||||||
});
|
|
||||||
|
|
||||||
// 渲染医师项
|
|
||||||
const renderDoctorItem = (item: ClinicDoctorUser) => (
|
|
||||||
<View key={item.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
|
||||||
<View className="flex items-center">
|
|
||||||
<View className="flex-1 flex justify-between items-center">
|
|
||||||
<View className="flex justify-between">
|
|
||||||
<Avatar src={item.avatar} size={'large'}/>
|
|
||||||
<View className={'flex flex-col mx-3'}>
|
|
||||||
<Text className="font-semibold text-gray-800 mr-2">
|
|
||||||
{item.realName} 医师
|
|
||||||
</Text>
|
|
||||||
<View>
|
|
||||||
<Tag background="#f3f3f3" color="#999999">中医内科</Tag>
|
|
||||||
</View>
|
|
||||||
<View className={'my-1'}>
|
|
||||||
<Text className={'text-gray-400 text-xs'}>问诊 1 次</Text>
|
|
||||||
<Divider direction="vertical"/>
|
|
||||||
<Text className={'text-gray-400 text-xs'}>开方 3 次</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<Button type="warning">咨询医生</Button>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
// 渲染患者项
|
|
||||||
const renderPatientUserItem = (item: ClinicPatientUser) => (
|
|
||||||
<View key={item.userId} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
|
||||||
<View className="flex items-center">
|
|
||||||
<View className="flex-1 flex justify-between items-center">
|
|
||||||
<View className="flex justify-between">
|
|
||||||
<Avatar src={item.avatar} size={'large'}/>
|
|
||||||
<View className={'flex flex-col mx-3'}>
|
|
||||||
<Text className="font-semibold text-gray-800 mr-2">
|
|
||||||
{item.realName}
|
|
||||||
</Text>
|
|
||||||
<View>
|
|
||||||
{
|
|
||||||
<Text
|
|
||||||
className={'text-gray-400 text-xs'}>{item.sex}</Text>
|
|
||||||
}
|
}
|
||||||
{
|
return () => {
|
||||||
item.age && (
|
socketRef.current?.close()
|
||||||
<>
|
socketRef.current = null
|
||||||
<Divider direction="vertical"/>
|
|
||||||
<Text className={'text-gray-400 text-xs'}>{item.age}岁</Text>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
{
|
}, [conversationId])
|
||||||
item.weight && (
|
|
||||||
<>
|
|
||||||
<Divider direction="vertical"/>
|
|
||||||
<Text className={'text-gray-400 text-xs'}>{item.weight}</Text>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</View>
|
|
||||||
<View>
|
|
||||||
<Text className={'text-gray-400 text-xs'}>{item.allergyHistory}</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<Button type="warning" onClick={() => navTo(`/doctor/orders/add?id=${item.userId}`)}>开方</Button>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View className="min-h-screen bg-gray-50 w-full">
|
<View className="min-h-screen bg-gray-50 w-full pb-24">
|
||||||
<View className={'p-4'}>
|
<View className="px-4 pt-4 pb-24">
|
||||||
{list?.map(renderPatientUserItem)}
|
{messages.length === 0 ? (
|
||||||
|
<View className="mt-10 text-center text-gray-400 text-sm">
|
||||||
|
<Text>暂时还没有聊天记录</Text>
|
||||||
</View>
|
</View>
|
||||||
<View className={'fixed bottom-0 w-full bg-orange-50 pt-2 pb-8'}>
|
) : (
|
||||||
<View className={'flex flex-1 items-center justify-between'}>
|
messages.map((item, index) => (
|
||||||
<Voice className={'mx-2'} />
|
<View
|
||||||
<Input className={'w-full'} style={{
|
key={item.id || `msg-${index}`}
|
||||||
|
className={`flex mb-4 ${item.isMine ? 'justify-end' : 'justify-start'}`}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
className={`px-3 py-2 rounded-2xl text-sm leading-relaxed break-words ${item.isMine ? 'bg-amber-400 text-white' : 'bg-white text-gray-800'}`}
|
||||||
|
style={{maxWidth: '75%'}}
|
||||||
|
>
|
||||||
|
<Text>{item.content}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="fixed bottom-0 left-0 right-0 bg-orange-50 pt-2 pb-8 px-3 border-t border-orange-100">
|
||||||
|
<View className="flex gap-3 mb-2 overflow-x-auto">
|
||||||
|
{quickActions.map(action => (
|
||||||
|
<View
|
||||||
|
key={action.type}
|
||||||
|
onClick={() => handleQuickAction(action.type)}
|
||||||
|
style={{
|
||||||
|
padding: '6px 12px',
|
||||||
|
borderRadius: '20px',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
fontSize: '14px',
|
||||||
|
color: '#8b5a2b',
|
||||||
|
boxShadow: '0 4px 10px rgba(0,0,0,0.05)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexShrink: 0
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Text>{action.label}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<View className="flex flex-1 items-center justify-between">
|
||||||
|
<Voice className="mx-2"/>
|
||||||
|
<Input
|
||||||
|
className="w-full"
|
||||||
|
style={{
|
||||||
borderRadius: '6px',
|
borderRadius: '6px',
|
||||||
paddingLeft: '12px',
|
paddingLeft: '12px',
|
||||||
paddingRight: '12px'
|
paddingRight: '12px'
|
||||||
}} />
|
}}
|
||||||
<FaceMild size={26} className={'ml-2'} />
|
value={messageInput}
|
||||||
<AddCircle size={26} className={'mx-2'} />
|
onChange={(value) => setMessageInput(value)}
|
||||||
|
confirmType="send"
|
||||||
|
onConfirm={handleSendMessage}
|
||||||
|
/>
|
||||||
|
<FaceMild size={26} className="ml-2"/>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
className="ml-2"
|
||||||
|
loading={sending}
|
||||||
|
onClick={handleSendMessage}
|
||||||
|
>
|
||||||
|
发送
|
||||||
|
</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default CustomerIndex;
|
export default CustomerIndex;
|
||||||
|
|||||||
3
src/clinic/clinicDoctorUser/index.config.ts
Normal file
3
src/clinic/clinicDoctorUser/index.config.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '患者管理'
|
||||||
|
})
|
||||||
363
src/clinic/clinicDoctorUser/index.tsx
Normal file
363
src/clinic/clinicDoctorUser/index.tsx
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
import {useState, useEffect, useCallback} from 'react'
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
|
import Taro, {useDidShow} from '@tarojs/taro'
|
||||||
|
import {Loading, InfiniteLoading, Empty, Space, Button, SearchBar} from '@nutui/nutui-react-taro'
|
||||||
|
import {Phone} from '@nutui/icons-react-taro'
|
||||||
|
import type {ClinicPatientUser as PatientUserType} from "@/api/clinic/clinicPatientUser/model";
|
||||||
|
import FixedButton from "@/components/FixedButton";
|
||||||
|
import navTo from "@/utils/common";
|
||||||
|
import {
|
||||||
|
pageClinicPatientUser,
|
||||||
|
removeClinicPatientUser,
|
||||||
|
updateClinicPatientUser
|
||||||
|
} from "@/api/clinic/clinicPatientUser";
|
||||||
|
|
||||||
|
// 扩展患者类型
|
||||||
|
interface PatientUser extends PatientUserType {
|
||||||
|
}
|
||||||
|
|
||||||
|
const PatientIndex = () => {
|
||||||
|
const [list, setList] = useState<PatientUser[]>([])
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [searchValue, setSearchValue] = useState<string>('')
|
||||||
|
const [displaySearchValue, setDisplaySearchValue] = useState<string>('')
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [hasMore, setHasMore] = useState(true)
|
||||||
|
|
||||||
|
// 复制手机号
|
||||||
|
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 = (patient: PatientUser) => {
|
||||||
|
Taro.showModal({
|
||||||
|
title: '编辑备注',
|
||||||
|
// @ts-ignore
|
||||||
|
editable: true,
|
||||||
|
placeholderText: '请输入备注信息',
|
||||||
|
content: patient.comments || '',
|
||||||
|
success: async (res) => {
|
||||||
|
// @ts-ignore
|
||||||
|
if (res.confirm && res.content !== undefined) {
|
||||||
|
try {
|
||||||
|
// 更新备注
|
||||||
|
await updateClinicPatientUser({
|
||||||
|
...patient,
|
||||||
|
// @ts-ignore
|
||||||
|
comments: res.content.trim()
|
||||||
|
});
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: '更新成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 刷新列表
|
||||||
|
setList([]);
|
||||||
|
setPage(1);
|
||||||
|
setHasMore(true);
|
||||||
|
fetchPatientData(true);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('更新备注失败:', error);
|
||||||
|
Taro.showToast({
|
||||||
|
title: '更新失败,请重试',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取患者数据
|
||||||
|
const fetchPatientData = useCallback(async (resetPage = false, targetPage?: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const currentPage = resetPage ? 1 : (targetPage || page);
|
||||||
|
|
||||||
|
// 构建API参数
|
||||||
|
const params: any = {
|
||||||
|
page: currentPage
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加搜索关键词
|
||||||
|
if (displaySearchValue.trim()) {
|
||||||
|
params.keywords = displaySearchValue.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await pageClinicPatientUser(params);
|
||||||
|
|
||||||
|
if (res?.list && res.list.length > 0) {
|
||||||
|
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
|
||||||
|
if (resetPage || currentPage === 1) {
|
||||||
|
setList(res.list);
|
||||||
|
} else {
|
||||||
|
setList(prevList => prevList.concat(res.list));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正确判断是否还有更多数据
|
||||||
|
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, displaySearchValue]);
|
||||||
|
|
||||||
|
const reloadMore = async () => {
|
||||||
|
if (loading || !hasMore) return; // 防止重复加载
|
||||||
|
const nextPage = page + 1;
|
||||||
|
await fetchPatientData(false, nextPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖搜索功能
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDisplaySearchValue(searchValue);
|
||||||
|
}, 300); // 300ms 防抖
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchValue]);
|
||||||
|
|
||||||
|
// 删除患者
|
||||||
|
const handleDelete = (patient: PatientUser) => {
|
||||||
|
removeClinicPatientUser(patient.id).then(() => {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '删除成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
// 刷新数据
|
||||||
|
setList([]);
|
||||||
|
setPage(1);
|
||||||
|
setHasMore(true);
|
||||||
|
fetchPatientData(true).then();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPatientData(true).then();
|
||||||
|
}, [displaySearchValue]);
|
||||||
|
|
||||||
|
// 监听页面显示,当从其他页面返回时刷新数据
|
||||||
|
useDidShow(() => {
|
||||||
|
// 刷新数据
|
||||||
|
setList([]);
|
||||||
|
setPage(1);
|
||||||
|
setHasMore(true);
|
||||||
|
fetchPatientData(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 渲染患者项
|
||||||
|
const renderPatientItem = (patient: PatientUser) => (
|
||||||
|
<View key={patient.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
||||||
|
<View className="flex items-center mb-3">
|
||||||
|
<View className="flex-1">
|
||||||
|
<View className="flex items-center mb-1">
|
||||||
|
<Space>
|
||||||
|
<Text className="font-semibold text-gray-800 mr-2">
|
||||||
|
{patient.realName || '未命名'}
|
||||||
|
</Text>
|
||||||
|
<Text className={'text-gray-400 font-normal'}>{patient.age}岁</Text>
|
||||||
|
<Text className={'text-gray-400 font-normal'}>{patient.sex == '1' ? '男' : ''}{patient.sex == '2' ? '女' : ''}</Text>
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
<View className="flex items-center mb-1">
|
||||||
|
<Space direction="vertical">
|
||||||
|
<View className="flex items-center">
|
||||||
|
<Text className="text-xs text-gray-500" onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
makePhoneCall(patient.phone || '');
|
||||||
|
}}>联系电话:{patient.phone || '未提供'}</Text>
|
||||||
|
<View className="flex items-center ml-2">
|
||||||
|
<Phone
|
||||||
|
size={12}
|
||||||
|
className="text-green-500 mr-2"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
makePhoneCall(patient.phone || '');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
className="text-xs text-blue-500 cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
copyPhone(patient.phone || '');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
复制
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Text className="text-xs text-gray-500">
|
||||||
|
添加时间:{patient.createTime || '未知'}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 显示 comments 字段 */}
|
||||||
|
<Space className="flex items-center">
|
||||||
|
<Text className="text-xs text-gray-500">备注:{patient.comments || '暂无'}</Text>
|
||||||
|
<Text
|
||||||
|
className="text-xs text-blue-500 cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
editComments(patient);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 操作按钮 */}
|
||||||
|
<Space className="flex justify-end">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => navTo(`/doctor/orders/add?id=${patient.userId}`, true)}
|
||||||
|
style={{marginRight: '8px', backgroundColor: '#ff4d4f', color: 'white'}}
|
||||||
|
>
|
||||||
|
开方
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => navTo(`/doctor/customer/add?id=${patient.id}`, true)}
|
||||||
|
style={{marginRight: '8px', backgroundColor: '#1890ff', color: 'white'}}
|
||||||
|
>
|
||||||
|
详情
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => handleDelete(patient)}
|
||||||
|
style={{backgroundColor: '#ff4d4f', color: 'white'}}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
// 渲染患者列表
|
||||||
|
const renderPatientList = () => {
|
||||||
|
const isSearching = displaySearchValue.trim().length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="flex-1">
|
||||||
|
{/* 搜索结果统计 */}
|
||||||
|
{isSearching && (
|
||||||
|
<View className="bg-white px-4 py-2 border-b border-gray-100">
|
||||||
|
<Text className="text-sm text-gray-600">
|
||||||
|
搜索 "{displaySearchValue}" 的结果,共找到 {list.length} 条记录
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View className="p-4" style={{
|
||||||
|
height: isSearching ? 'calc(90vh - 40px)' : '90vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden'
|
||||||
|
}}>
|
||||||
|
<InfiniteLoading
|
||||||
|
target="scroll"
|
||||||
|
hasMore={hasMore}
|
||||||
|
onLoadMore={reloadMore}
|
||||||
|
onScroll={() => {
|
||||||
|
// 滚动事件处理
|
||||||
|
}}
|
||||||
|
onScrollToUpper={() => {
|
||||||
|
// 滚动到顶部事件处理
|
||||||
|
}}
|
||||||
|
loadingText={
|
||||||
|
<>
|
||||||
|
加载中...
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
loadMoreText={
|
||||||
|
list.length === 0 ? (
|
||||||
|
<Empty
|
||||||
|
style={{backgroundColor: 'transparent'}}
|
||||||
|
description={loading ? "加载中..." : "暂无患者数据"}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<View className={'h-3 flex items-center justify-center'}>
|
||||||
|
<Text className="text-gray-500 text-sm">没有更多了</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{loading && list.length === 0 ? (
|
||||||
|
<View className="flex items-center justify-center py-8">
|
||||||
|
<Loading/>
|
||||||
|
<Text className="text-gray-500 mt-2 ml-2">加载中...</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
list.map(renderPatientItem)
|
||||||
|
)}
|
||||||
|
</InfiniteLoading>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="min-h-screen bg-gray-50">
|
||||||
|
{/* 搜索栏 */}
|
||||||
|
<View className="bg-white py-2 border-b border-gray-100">
|
||||||
|
<SearchBar
|
||||||
|
value={searchValue}
|
||||||
|
placeholder="搜索患者姓名、手机号"
|
||||||
|
onChange={(value) => setSearchValue(value)}
|
||||||
|
onClear={() => {
|
||||||
|
setSearchValue('');
|
||||||
|
setDisplaySearchValue('');
|
||||||
|
}}
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 患者列表 */}
|
||||||
|
{renderPatientList()}
|
||||||
|
|
||||||
|
<FixedButton text={'添加患者'} onClick={() => Taro.navigateTo({url: '/doctor/customer/add'})}/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PatientIndex;
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
import {useEffect, useState, useRef} from "react";
|
|
||||||
import {useRouter} from '@tarojs/taro'
|
|
||||||
import {Button, Loading, CellGroup, Input, TextArea, Form} from '@nutui/nutui-react-taro'
|
|
||||||
import Taro from '@tarojs/taro'
|
|
||||||
import {View} from '@tarojs/components'
|
|
||||||
import {ClinicOrder} from "@/api/clinic/clinicOrder/model";
|
|
||||||
import {getClinicOrder, listClinicOrder, updateClinicOrder, addClinicOrder} from "@/api/clinic/clinicOrder";
|
|
||||||
|
|
||||||
const AddClinicOrder = () => {
|
|
||||||
const {params} = useRouter();
|
|
||||||
const [loading, setLoading] = useState<boolean>(true)
|
|
||||||
const [FormData, setFormData] = useState<ClinicOrder>({})
|
|
||||||
const formRef = useRef<any>(null)
|
|
||||||
|
|
||||||
const reload = async () => {
|
|
||||||
if (params.id) {
|
|
||||||
const data = await getClinicOrder(Number(params.id))
|
|
||||||
setFormData(data)
|
|
||||||
} else {
|
|
||||||
setFormData({})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 提交表单
|
|
||||||
const submitSucceed = async (values: any) => {
|
|
||||||
try {
|
|
||||||
if (params.id) {
|
|
||||||
// 编辑模式
|
|
||||||
await updateClinicOrder({
|
|
||||||
...values,
|
|
||||||
id: Number(params.id)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// 新增模式
|
|
||||||
await addClinicOrder(values)
|
|
||||||
}
|
|
||||||
|
|
||||||
Taro.showToast({
|
|
||||||
title: `操作成功`,
|
|
||||||
icon: 'success'
|
|
||||||
})
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
return Taro.navigateBack()
|
|
||||||
}, 1000)
|
|
||||||
} catch (error) {
|
|
||||||
Taro.showToast({
|
|
||||||
title: `操作失败`,
|
|
||||||
icon: 'error'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitFailed = (error: any) => {
|
|
||||||
console.log(error, 'err...')
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
reload().then(() => {
|
|
||||||
setLoading(false)
|
|
||||||
})
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
return <Loading className={'px-2'}>加载中</Loading>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<Form
|
|
||||||
ref={formRef}
|
|
||||||
divider
|
|
||||||
initialValues={FormData}
|
|
||||||
labelPosition="left"
|
|
||||||
onFinish={(values) => submitSucceed(values)}
|
|
||||||
onFinishFailed={(errors) => submitFailed(errors)}
|
|
||||||
footer={
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'center',
|
|
||||||
width: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button
|
|
||||||
nativeType="submit"
|
|
||||||
type="success"
|
|
||||||
size="large"
|
|
||||||
className={'w-full'}
|
|
||||||
block
|
|
||||||
>
|
|
||||||
{params.id ? '更新' : '保存'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<CellGroup style={{padding: '4px 0'}}>
|
|
||||||
<Form.Item name="orderId" label="订单号" initialValue={FormData.orderId} required>
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
import {useState} from "react";
|
|
||||||
import Taro, {useDidShow} from '@tarojs/taro'
|
|
||||||
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Divider} from '@nutui/nutui-react-taro'
|
|
||||||
import {Dongdong, ArrowRight, CheckNormal, Checked} from '@nutui/icons-react-taro'
|
|
||||||
import {View} from '@tarojs/components'
|
|
||||||
import {ClinicOrder} from "@/api/clinic/clinicOrder/model";
|
|
||||||
import {listClinicOrder, removeClinicOrder, updateClinicOrder} from "@/api/clinic/clinicOrder";
|
|
||||||
|
|
||||||
const ClinicOrderList = () => {
|
|
||||||
const [list, setList] = useState<ClinicOrder[]>([])
|
|
||||||
|
|
||||||
const reload = () => {
|
|
||||||
listClinicOrder({
|
|
||||||
// 添加查询条件
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
setList(data || [])
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
Taro.showToast({
|
|
||||||
title: '获取数据失败',
|
|
||||||
icon: 'error'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const onDel = async (id?: number) => {
|
|
||||||
await removeClinicOrder(id)
|
|
||||||
Taro.showToast({
|
|
||||||
title: '删除成功',
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
useDidShow(() => {
|
|
||||||
reload()
|
|
||||||
});
|
|
||||||
|
|
||||||
if (list.length == 0) {
|
|
||||||
return (
|
|
||||||
<ConfigProvider>
|
|
||||||
<div className={'h-full flex flex-col justify-center items-center'} style={{
|
|
||||||
height: 'calc(100vh - 300px)',
|
|
||||||
}}>
|
|
||||||
<Empty
|
|
||||||
style={{
|
|
||||||
backgroundColor: 'transparent'
|
|
||||||
}}
|
|
||||||
description="暂无数据"
|
|
||||||
/>
|
|
||||||
<Space>
|
|
||||||
<Button onClick={() => Taro.navigateTo({url: '/clinic/clinicOrder/add'})}>新增处方订单</Button>
|
|
||||||
</Space>
|
|
||||||
</div>
|
|
||||||
</ConfigProvider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{list.map((item, _) => (
|
|
||||||
<Cell.Group key={item.
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '处方订单管理',
|
navigationBarTitleText: '开方详情',
|
||||||
navigationBarTextStyle: 'black'
|
navigationBarTextStyle: 'black'
|
||||||
})
|
})
|
||||||
72
src/clinic/clinicPatientUser/detail.scss
Normal file
72
src/clinic/clinicPatientUser/detail.scss
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
.prescription-detail-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f7f8fa;
|
||||||
|
padding: 12px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-size: 25rpx;
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-header-card__time {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2c3d;
|
||||||
|
font-size: 25rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-card {
|
||||||
|
background: #ffffff;
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 25rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-action-row {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
color: #4a5568;
|
||||||
|
font-size: 25rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-row strong {
|
||||||
|
color: #1f2c3d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-medicine-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-medicine-chip {
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 12px;
|
||||||
|
color: #1f2c3d;
|
||||||
|
font-size: 25rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-section-title {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #1f2c3d;
|
||||||
|
font-size: 25rpx;
|
||||||
|
}
|
||||||
159
src/clinic/clinicPatientUser/detail.tsx
Normal file
159
src/clinic/clinicPatientUser/detail.tsx
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
|
import {Button, Cell, CellGroup, Loading, Space, Tag} from '@nutui/nutui-react-taro'
|
||||||
|
import Taro, {useRouter} from '@tarojs/taro'
|
||||||
|
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
||||||
|
import {getClinicPrescription} from "@/api/clinic/clinicPrescription";
|
||||||
|
import {copyText} from "@/utils/common";
|
||||||
|
import './detail.scss'
|
||||||
|
|
||||||
|
const statusMap: Record<number, string> = {
|
||||||
|
0: '待开方',
|
||||||
|
1: '已完成',
|
||||||
|
2: '已支付',
|
||||||
|
3: '已取消'
|
||||||
|
}
|
||||||
|
|
||||||
|
const PrescriptionDetail = () => {
|
||||||
|
const {params} = useRouter()
|
||||||
|
const [detail, setDetail] = useState<ClinicPrescription>()
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const loadDetail = async () => {
|
||||||
|
if (!params.id) return
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
const data = await getClinicPrescription(Number(params.id))
|
||||||
|
setDetail(data)
|
||||||
|
} catch (error) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '加载详情失败',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadDetail()
|
||||||
|
}, [params.id])
|
||||||
|
|
||||||
|
const getPatientDesc = () => {
|
||||||
|
if (!detail) return ''
|
||||||
|
const sexText = detail.sex === 0 ? '男' : detail.sex === 1 ? '女' : ''
|
||||||
|
const ageText = detail.age ? `${detail.age}岁` : ''
|
||||||
|
return [detail.realName, sexText, ageText].filter(Boolean).join(' ')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading || !detail) {
|
||||||
|
return (
|
||||||
|
<View className="flex items-center justify-center h-full py-10">
|
||||||
|
<Loading>加载中...</Loading>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const medicines = detail.items || []
|
||||||
|
// const shopOrder: any = (detail as any).shopOrder
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="prescription-detail-page">
|
||||||
|
<View className="detail-header-card">
|
||||||
|
<View>
|
||||||
|
<Text className="detail-header-card__time">{detail.createTime || ''}</Text>
|
||||||
|
<View className="text-gray-500 mt-1" style={{fontSize: '25rpx'}}>开方</View>
|
||||||
|
</View>
|
||||||
|
<Text className="text-gray-700" style={{fontSize: '25rpx'}}>{detail.doctorName || ''}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/*<View className="detail-card">*/}
|
||||||
|
{/* <Text className="detail-card__title">处方状态</Text>*/}
|
||||||
|
{/* <Text className="detail-status">{statusMap[detail.status || 0] || '待处理'}</Text>*/}
|
||||||
|
{/* {shopOrder?.address && (*/}
|
||||||
|
{/* <Text className="detail-address">*/}
|
||||||
|
{/* {shopOrder.address}*/}
|
||||||
|
{/* </Text>*/}
|
||||||
|
{/* )}*/}
|
||||||
|
{/* <View className="detail-action-row">*/}
|
||||||
|
{/* <Button size="small" plain type="primary" onClick={() => Taro.showToast({title: '敬请期待', icon: 'none'})}>*/}
|
||||||
|
{/* 查看物流*/}
|
||||||
|
{/* </Button>*/}
|
||||||
|
{/* <Button size="small" plain type="success" onClick={() => Taro.showToast({title: '已复制开方信息', icon: 'none'})}>*/}
|
||||||
|
{/* 复诊开方*/}
|
||||||
|
{/* </Button>*/}
|
||||||
|
{/* </View>*/}
|
||||||
|
{/*</View>*/}
|
||||||
|
|
||||||
|
<View className="detail-card">
|
||||||
|
<View className="detail-row" style={{fontSize: '25rpx'}}>
|
||||||
|
<Text>药方编号</Text>
|
||||||
|
<Space>
|
||||||
|
<Text className="text-gray-700" style={{fontSize: '25rpx'}}>{detail.orderNo || '-'}</Text>
|
||||||
|
{detail.orderNo && (
|
||||||
|
<Text className="text-green-600" style={{fontSize: '25rpx'}} onClick={() => copyText(detail.orderNo || '')}>复制</Text>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="detail-card">
|
||||||
|
<Text className="detail-section-title">患者信息</Text>
|
||||||
|
<View className="detail-row">
|
||||||
|
<Text className="detail-cell-text" style={{fontSize: '25rpx'}}>{getPatientDesc()}</Text>
|
||||||
|
<Text className="text-green-600" style={{fontSize: '25rpx'}} onClick={() => Taro.showToast({title: '患者档案敬请期待', icon: 'none'})}>
|
||||||
|
患者档案
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
{detail.diagnosis && (
|
||||||
|
<View className="detail-row">
|
||||||
|
<Text className="detail-cell-text" style={{fontSize: '25rpx'}}>诊断:{detail.diagnosis}</Text>
|
||||||
|
<Text className="text-green-600" style={{fontSize: '25rpx'}} onClick={() => Taro.showToast({title: '诊断详情敬请期待', icon: 'none'})}>
|
||||||
|
查看详情
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
{detail.treatmentPlan && (
|
||||||
|
<Text className="detail-cell-text mt-2" style={{fontSize: '25rpx'}}>治疗方案:{detail.treatmentPlan}</Text>
|
||||||
|
)}
|
||||||
|
{detail.decoctionInstructions && (
|
||||||
|
<Text className="detail-cell-text mt-2" style={{fontSize: '25rpx'}}>用药说明:{detail.decoctionInstructions}</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="detail-card">
|
||||||
|
<View className="flex justify-between mb-2">
|
||||||
|
<Text className="detail-section-title">药方信息</Text>
|
||||||
|
<Text className="text-green-600" style={{fontSize: '25rpx'}} onClick={() => Taro.showToast({title: '已设为常用', icon: 'success'})}>存为常用方</Text>
|
||||||
|
</View>
|
||||||
|
<View className="detail-medicine-list">
|
||||||
|
{medicines.length === 0 && (
|
||||||
|
<Text className="text-gray-400" style={{fontSize: '25rpx'}}>暂无药材信息</Text>
|
||||||
|
)}
|
||||||
|
{medicines.map((item, index) => (
|
||||||
|
<View key={index} className="detail-medicine-chip">
|
||||||
|
{item.medicineName} {item.quantity || item.dosage || ''}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<CellGroup>
|
||||||
|
<Cell title="剂数" extra={`${medicines.length} 味药`} titleStyle={{fontSize: '25rpx'}} descriptionStyle={{fontSize: '25rpx'}}/>
|
||||||
|
<Cell title="服用方式" extra={detail.decoctionInstructions || '遵医嘱'} titleStyle={{fontSize: '25rpx'}} descriptionStyle={{fontSize: '25rpx'}}/>
|
||||||
|
<Cell title="订单备注" extra={detail.comments || '无'} titleStyle={{fontSize: '25rpx'}} descriptionStyle={{fontSize: '25rpx'}}/>
|
||||||
|
<Cell
|
||||||
|
title="合计"
|
||||||
|
extra={(
|
||||||
|
<Space>
|
||||||
|
<Text className="text-red-500 font-semibold">¥{detail.orderPrice || '0.00'}</Text>
|
||||||
|
<Text className="text-green-600" style={{fontSize: '25rpx'}} onClick={() => Taro.showToast({title: '暂无明细', icon: 'none'})}>价格明细</Text>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</CellGroup>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PrescriptionDetail;
|
||||||
@@ -9,12 +9,12 @@ import {
|
|||||||
getStatusText,
|
getStatusText,
|
||||||
getStatusTagType,
|
getStatusTagType,
|
||||||
getStatusOptions,
|
getStatusOptions,
|
||||||
mapApplyStatusToCustomerStatus,
|
|
||||||
mapCustomerStatusToApplyStatus
|
mapCustomerStatusToApplyStatus
|
||||||
} from '@/utils/customerStatus';
|
} from '@/utils/customerStatus';
|
||||||
import FixedButton from "@/components/FixedButton";
|
import FixedButton from "@/components/FixedButton";
|
||||||
import navTo from "@/utils/common";
|
import navTo from "@/utils/common";
|
||||||
import {pageShopDealerApply, removeShopDealerApply, updateShopDealerApply} from "@/api/shop/shopDealerApply";
|
import {pageShopDealerApply, removeShopDealerApply, updateShopDealerApply} from "@/api/shop/shopDealerApply";
|
||||||
|
import {userPageClinicPatientUser} from "@/api/clinic/clinicPatientUser";
|
||||||
|
|
||||||
// 扩展User类型,添加客户状态和保护天数
|
// 扩展User类型,添加客户状态和保护天数
|
||||||
interface CustomerUser extends UserType {
|
interface CustomerUser extends UserType {
|
||||||
@@ -102,69 +102,6 @@ const CustomerIndex = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 计算剩余保护天数(基于过期时间)
|
|
||||||
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) => {
|
const fetchCustomerData = useCallback(async (statusFilter?: CustomerStatus, resetPage = false, targetPage?: number) => {
|
||||||
@@ -174,22 +111,22 @@ const CustomerIndex = () => {
|
|||||||
|
|
||||||
// 构建API参数,根据状态筛选
|
// 构建API参数,根据状态筛选
|
||||||
const params: any = {
|
const params: any = {
|
||||||
type: 4,
|
// type: 4,
|
||||||
page: currentPage
|
page: currentPage,
|
||||||
};
|
};
|
||||||
const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab);
|
const applyStatus = mapCustomerStatusToApplyStatus(statusFilter || activeTab);
|
||||||
if (applyStatus !== undefined) {
|
if (applyStatus !== undefined) {
|
||||||
params.applyStatus = applyStatus;
|
params.applyStatus = applyStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await pageShopDealerApply(params);
|
const res = await userPageClinicPatientUser(params);
|
||||||
|
|
||||||
if (res?.list && res.list.length > 0) {
|
if (res?.list && res.list.length > 0) {
|
||||||
// 正确映射状态并计算保护天数
|
// 正确映射状态并计算保护天数
|
||||||
const mappedList = res.list.map(customer => ({
|
const mappedList = res.list.map(customer => ({
|
||||||
...customer,
|
...customer,
|
||||||
customerStatus: mapApplyStatusToCustomerStatus(customer.applyStatus || 10),
|
// customerStatus: mapApplyStatusToCustomerStatus(customer.applyStatus || 10),
|
||||||
protectDays: calculateProtectDays(customer.expirationTime, customer.applyTime || customer.createTime || '')
|
// protectDays: calculateProtectDays(customer.expirationTime, customer.applyTime || customer.createTime || '')
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
|
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '新增处方订单',
|
navigationBarTitleText: '用药订单',
|
||||||
navigationBarTextStyle: 'black'
|
navigationBarTextStyle: 'black'
|
||||||
})
|
})
|
||||||
415
src/clinic/clinicPatientUser/prescription.tsx
Normal file
415
src/clinic/clinicPatientUser/prescription.tsx
Normal file
@@ -0,0 +1,415 @@
|
|||||||
|
import {useState} from "react";
|
||||||
|
import Taro, {useDidShow} from '@tarojs/taro'
|
||||||
|
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Tag, Popup} from '@nutui/nutui-react-taro'
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
|
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
||||||
|
import {
|
||||||
|
pageClinicPrescription,
|
||||||
|
WxPayResult
|
||||||
|
} from "@/api/clinic/clinicPrescription";
|
||||||
|
import {copyText} from "@/utils/common";
|
||||||
|
import {updateShopOrder} from "@/api/shop/shopOrder";
|
||||||
|
import {listShopUserAddress} from "@/api/shop/shopUserAddress";
|
||||||
|
import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
|
||||||
|
|
||||||
|
const ClinicPrescriptionList = () => {
|
||||||
|
const [list, setList] = useState<ClinicPrescription[]>([])
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [addressList, setAddressList] = useState<ShopUserAddress[]>([])
|
||||||
|
const [addressPopupVisible, setAddressPopupVisible] = useState<boolean>(false)
|
||||||
|
const [addressLoading, setAddressLoading] = useState<boolean>(false)
|
||||||
|
const [addressSaving, setAddressSaving] = useState<boolean>(false)
|
||||||
|
const [selectedAddressId, setSelectedAddressId] = useState<number | null>(null)
|
||||||
|
const [pendingOrder, setPendingOrder] = useState<ClinicPrescription | null>(null)
|
||||||
|
|
||||||
|
const reload = () => {
|
||||||
|
setLoading(true)
|
||||||
|
pageClinicPrescription({
|
||||||
|
userId: Taro.getStorageSync('UserId'),
|
||||||
|
withDoctor: true,
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
setList(data?.list || [])
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '获取数据失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const fetchAddressList = async () => {
|
||||||
|
try {
|
||||||
|
setAddressLoading(true)
|
||||||
|
const data = await listShopUserAddress({
|
||||||
|
userId: Taro.getStorageSync('UserId')
|
||||||
|
})
|
||||||
|
const addressData = data || []
|
||||||
|
setAddressList(addressData)
|
||||||
|
if (addressData.length > 0) {
|
||||||
|
setSelectedAddressId(addressData[0].id || null)
|
||||||
|
} else {
|
||||||
|
setSelectedAddressId(null)
|
||||||
|
}
|
||||||
|
return addressData
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载收货地址失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '加载收货地址失败',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
setAddressList([])
|
||||||
|
setSelectedAddressId(null)
|
||||||
|
return []
|
||||||
|
} finally {
|
||||||
|
setAddressLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeAddressPopup = () => {
|
||||||
|
setAddressPopupVisible(false)
|
||||||
|
setPendingOrder(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatFullAddress = (address?: ShopUserAddress) => {
|
||||||
|
if (!address) return ''
|
||||||
|
return address.fullAddress || `${address.province || ''}${address.city || ''}${address.region || ''}${address.address || ''}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const ensureAddressBeforePay = async (item: ClinicPrescription) => {
|
||||||
|
setPendingOrder(item)
|
||||||
|
const addresses = await fetchAddressList()
|
||||||
|
if (addresses.length === 0) {
|
||||||
|
Taro.showModal({
|
||||||
|
title: '暂无收货地址',
|
||||||
|
content: '请先添加收货地址后再支付',
|
||||||
|
confirmText: '去添加',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
Taro.navigateTo({url: '/user/address/index'})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setAddressPopupVisible(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAddressConfirm = async () => {
|
||||||
|
if (!selectedAddressId) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '请选择收货地址',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!pendingOrder || !(pendingOrder as any).shopOrder || !(pendingOrder as any).shopOrder.orderId) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单信息缺失',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const targetAddress = addressList.find(addr => addr.id === selectedAddressId)
|
||||||
|
if (!targetAddress) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '地址不存在',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setAddressSaving(true)
|
||||||
|
await updateShopOrder({
|
||||||
|
orderId: (pendingOrder as any).shopOrder.orderId,
|
||||||
|
addressId: targetAddress.id,
|
||||||
|
realName: targetAddress.name,
|
||||||
|
address: formatFullAddress(targetAddress)
|
||||||
|
} as any)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '地址已更新',
|
||||||
|
icon: 'success'
|
||||||
|
})
|
||||||
|
const orderToPay = pendingOrder
|
||||||
|
closeAddressPopup()
|
||||||
|
await onPay(orderToPay, true)
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('更新收货地址失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: error?.message || '更新地址失败',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setAddressSaving(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理微信支付
|
||||||
|
*/
|
||||||
|
const handleWechatPay = async (result: WxPayResult): Promise<void> => {
|
||||||
|
console.log('处理微信支付:', result);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('微信支付参数错误');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await Taro.requestPayment({
|
||||||
|
timeStamp: result.timeStamp,
|
||||||
|
nonceStr: result.nonceStr,
|
||||||
|
package: result.package,
|
||||||
|
signType: result.signType as any,
|
||||||
|
paySign: result.paySign,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('微信支付成功');
|
||||||
|
} catch (payError: any) {
|
||||||
|
console.error('微信支付失败:', payError);
|
||||||
|
|
||||||
|
// 处理微信支付特定错误
|
||||||
|
if (payError.errMsg) {
|
||||||
|
if (payError.errMsg.includes('cancel')) {
|
||||||
|
throw new Error('用户取消支付');
|
||||||
|
} else if (payError.errMsg.includes('fail')) {
|
||||||
|
throw new Error('微信支付失败,请重试');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error('微信支付失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一支付入口
|
||||||
|
*/
|
||||||
|
const onPay = async (item: ClinicPrescription | null, skipAddressCheck: boolean = false) => {
|
||||||
|
if (!item) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '处方信息缺失',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!item.id) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '处方信息缺失',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shopOrder = (item as any).shopOrder
|
||||||
|
if (!skipAddressCheck) {
|
||||||
|
if (shopOrder && (!shopOrder.addressId || shopOrder.addressId === 0)) {
|
||||||
|
await ensureAddressBeforePay(item)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Taro.showLoading({title: '支付中...'});
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 调用统一支付接口
|
||||||
|
// @ts-ignore
|
||||||
|
const {data} = await updateShopOrder(
|
||||||
|
{
|
||||||
|
orderId: shopOrder.orderId,
|
||||||
|
makePay: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const result = data as WxPayResult;
|
||||||
|
console.log('订单创建结果:', result);
|
||||||
|
|
||||||
|
// 调用微信支付
|
||||||
|
await handleWechatPay(result);
|
||||||
|
|
||||||
|
// 支付成功
|
||||||
|
// console.log('支付成功,订单号:', result.orderNo);
|
||||||
|
|
||||||
|
// await updateClinicPrescription({
|
||||||
|
// id: item.id,
|
||||||
|
// orderNo: result.orderNo,
|
||||||
|
// status: 2
|
||||||
|
// })
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: '支付成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 延迟刷新列表
|
||||||
|
setTimeout(() => {
|
||||||
|
reload();
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('支付失败:', error);
|
||||||
|
|
||||||
|
// 获取错误信息
|
||||||
|
const errorMessage = error.message || '支付失败,请重试';
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: errorMessage,
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
Taro.hideLoading();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useDidShow(() => {
|
||||||
|
reload()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (list.length === 0 && !loading) {
|
||||||
|
return (
|
||||||
|
<ConfigProvider>
|
||||||
|
<View className={'h-full flex flex-col justify-center items-center'} style={{
|
||||||
|
height: 'calc(100vh - 300px)',
|
||||||
|
}}>
|
||||||
|
<Empty
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
}}
|
||||||
|
description="暂无用药订单"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</ConfigProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View className="p-3">
|
||||||
|
{list.map((item) => (
|
||||||
|
<CellGroup key={item.id} className="mb-3">
|
||||||
|
<Cell
|
||||||
|
title={`${item.id}`}
|
||||||
|
extra={
|
||||||
|
<Tag type={'warning'} className="font-medium">{item?.shopOrder.payStatus == 1 ? '已支付' : '待支付'}</Tag>
|
||||||
|
}
|
||||||
|
onClick={() => copyText(`${item.orderNo}`)}
|
||||||
|
/>
|
||||||
|
{item.diagnosis && (
|
||||||
|
<Cell
|
||||||
|
title="开方信息"
|
||||||
|
extra={
|
||||||
|
<Text className="text-gray-600 text-sm">
|
||||||
|
{item.diagnosis.length > 20
|
||||||
|
? `${item.diagnosis.substring(0, 20)}...`
|
||||||
|
: item.diagnosis}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Cell
|
||||||
|
title={'开方医生'}
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Text className="font-medium">{item.doctorName}</Text>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Cell
|
||||||
|
title="订单金额"
|
||||||
|
extra={
|
||||||
|
<Text className="text-red-500 font-medium">
|
||||||
|
¥{item.orderPrice || '0.00'}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Cell
|
||||||
|
title="开方时间"
|
||||||
|
extra={
|
||||||
|
<Text className="text-gray-500 text-xs">
|
||||||
|
{item.createTime}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Cell>
|
||||||
|
<Space className="w-full justify-end">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="warning"
|
||||||
|
onClick={() => Taro.navigateTo({url: `/clinic/clinicPatientUser/detail?id=${item.id}`})}
|
||||||
|
>
|
||||||
|
查看详情
|
||||||
|
</Button>
|
||||||
|
{item?.shopOrder?.payStatus == 0 && (
|
||||||
|
<Button
|
||||||
|
type={'danger'}
|
||||||
|
size="small"
|
||||||
|
onClick={() => onPay(item)}
|
||||||
|
>
|
||||||
|
去支付
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Cell>
|
||||||
|
</CellGroup>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Popup
|
||||||
|
visible={addressPopupVisible}
|
||||||
|
position="bottom"
|
||||||
|
round
|
||||||
|
onClose={closeAddressPopup}
|
||||||
|
>
|
||||||
|
<View className="p-4 bg-white">
|
||||||
|
<View className="text-lg font-medium mb-3">请选择收货地址</View>
|
||||||
|
{addressLoading ? (
|
||||||
|
<View className="text-center text-gray-500 py-6">加载地址中...</View>
|
||||||
|
) : addressList.length === 0 ? (
|
||||||
|
<View className="text-center text-gray-500 py-6">暂无收货地址,请先添加</View>
|
||||||
|
) : (
|
||||||
|
<View style={{maxHeight: '300px', overflow: 'auto'}}>
|
||||||
|
{addressList.map(address => (
|
||||||
|
<View
|
||||||
|
key={address.id}
|
||||||
|
className={`border rounded-lg p-3 mb-2 ${selectedAddressId === address.id ? 'border-orange-400 bg-orange-50' : 'border-gray-200'}`}
|
||||||
|
onClick={() => setSelectedAddressId(address.id || null)}
|
||||||
|
>
|
||||||
|
<View className="flex justify-between mb-1">
|
||||||
|
<Text className="font-medium">{address.name}</Text>
|
||||||
|
<Text className="text-gray-600">{address.phone}</Text>
|
||||||
|
</View>
|
||||||
|
<Text className="text-gray-500 text-sm">{formatFullAddress(address)}</Text>
|
||||||
|
{address.isDefault && (
|
||||||
|
<Text className="text-orange-500 text-xs mt-1">默认地址</Text>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View className="flex gap-3 mt-4">
|
||||||
|
<Button
|
||||||
|
className="flex-1"
|
||||||
|
onClick={() => Taro.navigateTo({url: '/user/address/index'})}
|
||||||
|
>
|
||||||
|
管理地址
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
className="flex-1"
|
||||||
|
loading={addressSaving}
|
||||||
|
onClick={handleAddressConfirm}
|
||||||
|
>
|
||||||
|
确定
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Popup>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClinicPrescriptionList;
|
||||||
4
src/clinic/clinicPatientUser/selectPatient.config.ts
Normal file
4
src/clinic/clinicPatientUser/selectPatient.config.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '选择患者',
|
||||||
|
navigationBarTextStyle: 'black'
|
||||||
|
})
|
||||||
285
src/clinic/clinicPatientUser/selectPatient.tsx
Normal file
285
src/clinic/clinicPatientUser/selectPatient.tsx
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
import {useState, useEffect, useCallback} from 'react'
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
|
import Taro, {useDidShow} from '@tarojs/taro'
|
||||||
|
import {Loading, InfiniteLoading, Empty, Space, SearchBar, Button} from '@nutui/nutui-react-taro'
|
||||||
|
import {Phone} from '@nutui/icons-react-taro'
|
||||||
|
import type {ClinicPatientUser as PatientUserType} from "@/api/clinic/clinicPatientUser/model";
|
||||||
|
import {
|
||||||
|
userPageClinicPatientUser
|
||||||
|
} from "@/api/clinic/clinicPatientUser";
|
||||||
|
|
||||||
|
// 患者类型
|
||||||
|
interface PatientUser extends PatientUserType {
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectPatient = () => {
|
||||||
|
const [list, setList] = useState<PatientUser[]>([])
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [searchValue, setSearchValue] = useState<string>('')
|
||||||
|
const [displaySearchValue, setDisplaySearchValue] = useState<string>('')
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [hasMore, setHasMore] = useState(true)
|
||||||
|
|
||||||
|
// 复制手机号
|
||||||
|
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 fetchPatientData = useCallback(async (resetPage = false, targetPage?: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const currentPage = resetPage ? 1 : (targetPage || page);
|
||||||
|
|
||||||
|
// 构建API参数
|
||||||
|
const params: any = {
|
||||||
|
page: currentPage
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加搜索关键词
|
||||||
|
if (displaySearchValue.trim()) {
|
||||||
|
params.keywords = displaySearchValue.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await userPageClinicPatientUser(params);
|
||||||
|
|
||||||
|
if (res?.list && res.list.length > 0) {
|
||||||
|
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
|
||||||
|
if (resetPage || currentPage === 1) {
|
||||||
|
setList(res.list);
|
||||||
|
} else {
|
||||||
|
setList(prevList => [...prevList, ...res.list]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正确判断是否还有更多数据
|
||||||
|
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, displaySearchValue]);
|
||||||
|
|
||||||
|
const reloadMore = async () => {
|
||||||
|
if (loading || !hasMore) return; // 防止重复加载
|
||||||
|
const nextPage = page + 1;
|
||||||
|
await fetchPatientData(false, nextPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖搜索功能
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDisplaySearchValue(searchValue);
|
||||||
|
}, 300); // 300ms 防抖
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchValue]);
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPatientData(true).then();
|
||||||
|
}, [displaySearchValue]);
|
||||||
|
|
||||||
|
// 监听页面显示,当从其他页面返回时刷新数据
|
||||||
|
useDidShow(() => {
|
||||||
|
// 刷新数据
|
||||||
|
setList([]);
|
||||||
|
setPage(1);
|
||||||
|
setHasMore(true);
|
||||||
|
fetchPatientData(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 选择患者
|
||||||
|
const selectPatient = (patient: PatientUser) => {
|
||||||
|
// 将选中的患者信息传递回上一个页面
|
||||||
|
const pages = Taro.getCurrentPages();
|
||||||
|
if (pages.length > 1) {
|
||||||
|
const prevPage = pages[pages.length - 2];
|
||||||
|
// @ts-ignore
|
||||||
|
if (prevPage && typeof prevPage.setSelectedPatient === 'function') {
|
||||||
|
// @ts-ignore
|
||||||
|
prevPage.setSelectedPatient(patient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同时存储到本地存储,作为备选方案
|
||||||
|
try {
|
||||||
|
Taro.setStorageSync('selectedPatient', JSON.stringify(patient));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('存储患者信息失败:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.navigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染患者项
|
||||||
|
const renderPatientItem = (patient: PatientUser) => (
|
||||||
|
<View key={patient.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
||||||
|
<View className="flex items-center mb-3">
|
||||||
|
<View className="flex-1">
|
||||||
|
<View className="flex items-center justify-between mb-1">
|
||||||
|
<Text className="font-semibold text-gray-800 mr-2">
|
||||||
|
{patient.realName || '未命名'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View className="flex items-center mb-1">
|
||||||
|
<Space direction="vertical">
|
||||||
|
<View className="flex items-center">
|
||||||
|
<Text className="text-xs text-gray-500" onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
makePhoneCall(patient.phone || '');
|
||||||
|
}}>联系电话:{patient.phone || '未提供'}</Text>
|
||||||
|
<View className="flex items-center ml-2">
|
||||||
|
<Phone
|
||||||
|
size={12}
|
||||||
|
className="text-green-500 mr-2"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
makePhoneCall(patient.phone || '');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
className="text-xs text-blue-500 cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
copyPhone(patient.phone || '');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
复制
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Text className="text-xs text-gray-500">
|
||||||
|
添加时间:{patient.createTime || '未知'}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 显示 comments 字段 */}
|
||||||
|
<View className="flex items-center">
|
||||||
|
<Text className="text-xs text-gray-500">备注:{patient.comments || '暂无'}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 选择按钮 */}
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => selectPatient(patient)}
|
||||||
|
style={{backgroundColor: '#1890ff', color: 'white'}}
|
||||||
|
>
|
||||||
|
选择此患者
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
// 渲染患者列表
|
||||||
|
const renderPatientList = () => {
|
||||||
|
const isSearching = displaySearchValue.trim().length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="flex-1">
|
||||||
|
{/* 搜索结果统计 */}
|
||||||
|
{isSearching && (
|
||||||
|
<View className="bg-white px-4 py-2 border-b border-gray-100">
|
||||||
|
<Text className="text-sm text-gray-600">
|
||||||
|
搜索 "{displaySearchValue}" 的结果,共找到 {list.length} 条记录
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View className="p-4" style={{
|
||||||
|
height: isSearching ? 'calc(90vh - 40px)' : '90vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden'
|
||||||
|
}}>
|
||||||
|
<InfiniteLoading
|
||||||
|
target="scroll"
|
||||||
|
hasMore={hasMore}
|
||||||
|
onLoadMore={reloadMore}
|
||||||
|
loadingText="加载中..."
|
||||||
|
loadMoreText={
|
||||||
|
list.length === 0 ? (
|
||||||
|
<Empty
|
||||||
|
style={{backgroundColor: 'transparent'}}
|
||||||
|
description={loading ? "加载中..." : "暂无患者数据"}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<View className={'h-3 flex items-center justify-center'}>
|
||||||
|
<Text className="text-gray-500 text-sm">没有更多了</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{loading && list.length === 0 ? (
|
||||||
|
<View className="flex items-center justify-center py-8">
|
||||||
|
<Loading/>
|
||||||
|
<Text className="text-gray-500 mt-2 ml-2">加载中...</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
list.map(renderPatientItem)
|
||||||
|
)}
|
||||||
|
</InfiniteLoading>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="min-h-screen bg-gray-50">
|
||||||
|
{/* 搜索栏 */}
|
||||||
|
<View className="bg-white py-2 border-b border-gray-100">
|
||||||
|
<SearchBar
|
||||||
|
value={searchValue}
|
||||||
|
placeholder="搜索患者姓名、手机号"
|
||||||
|
onChange={(value) => setSearchValue(value)}
|
||||||
|
onClear={() => {
|
||||||
|
setSearchValue('');
|
||||||
|
setDisplaySearchValue('');
|
||||||
|
}}
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 患者列表 */}
|
||||||
|
{renderPatientList()}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectPatient;
|
||||||
201
src/clinic/clinicPrescription/_index.tsx
Normal file
201
src/clinic/clinicPrescription/_index.tsx
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
import {useState} from "react";
|
||||||
|
import Taro, {useDidShow} from '@tarojs/taro'
|
||||||
|
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Tag} from '@nutui/nutui-react-taro'
|
||||||
|
import {Del, Edit} from '@nutui/icons-react-taro'
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
|
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
||||||
|
import {
|
||||||
|
pageClinicPrescription,
|
||||||
|
removeClinicPrescription
|
||||||
|
} from "@/api/clinic/clinicPrescription";
|
||||||
|
import FixedButton from "@/components/FixedButton";
|
||||||
|
import {copyText} from "@/utils/common";
|
||||||
|
|
||||||
|
const ClinicPrescriptionList = () => {
|
||||||
|
const [list, setList] = useState<ClinicPrescription[]>([])
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const reload = () => {
|
||||||
|
setLoading(true)
|
||||||
|
pageClinicPrescription({
|
||||||
|
// 添加查询条件
|
||||||
|
doctorId: Taro.getStorageSync('UserId'),
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
setList(data?.list || [])
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '获取数据失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDel = async (item: ClinicPrescription) => {
|
||||||
|
const res = await Taro.showModal({
|
||||||
|
title: '确认删除',
|
||||||
|
content: `确定要删除处方编号「${item.orderNo}」吗?`,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.confirm) {
|
||||||
|
try {
|
||||||
|
await removeClinicPrescription(item.id)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '删除成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
reload();
|
||||||
|
} catch (error) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '删除失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onEdit = (item: ClinicPrescription) => {
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: `/clinic/clinicPrescription/add?id=${item.id}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDetail = (item: ClinicPrescription) => {
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: `/clinic/clinicPrescription/detail?id=${item.id}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSexName = (sex?: number) => {
|
||||||
|
return sex === 0 ? '男' : sex === 1 ? '女' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
useDidShow(() => {
|
||||||
|
reload()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (list.length === 0 && !loading) {
|
||||||
|
return (
|
||||||
|
<ConfigProvider>
|
||||||
|
<View className={'h-full flex flex-col justify-center items-center'} style={{
|
||||||
|
height: 'calc(100vh - 300px)',
|
||||||
|
}}>
|
||||||
|
<Empty
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
}}
|
||||||
|
description="暂无处方数据"
|
||||||
|
/>
|
||||||
|
<Space style={{marginTop: '20px'}}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => Taro.navigateTo({url: '/clinic/clinicPrescription/add'})}
|
||||||
|
>
|
||||||
|
新增处方
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
</ConfigProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View className="p-3">
|
||||||
|
{list.map((item) => (
|
||||||
|
<CellGroup key={item.id} className="mb-3">
|
||||||
|
<Cell
|
||||||
|
title={item.orderNo}
|
||||||
|
extra={
|
||||||
|
<Tag type={'warning'} className="font-medium">待支付</Tag>
|
||||||
|
}
|
||||||
|
onClick={() => copyText(`${item.orderNo}`)}
|
||||||
|
/>
|
||||||
|
<Cell
|
||||||
|
title={'患者名称'}
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Text className="font-medium">{item.realName}</Text>
|
||||||
|
<Text className="font-medium">{item.age}岁</Text>
|
||||||
|
<Text className="font-medium">{getSexName(item.sex)}</Text>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{/*<Cell*/}
|
||||||
|
{/* title="处方类型"*/}
|
||||||
|
{/* extra={*/}
|
||||||
|
{/* <Tag type="info">*/}
|
||||||
|
{/* {getPrescriptionTypeText(item.prescriptionType)}*/}
|
||||||
|
{/* </Tag>*/}
|
||||||
|
{/* }*/}
|
||||||
|
{/*/>*/}
|
||||||
|
{item.diagnosis && (
|
||||||
|
<Cell
|
||||||
|
title="诊断结果"
|
||||||
|
extra={
|
||||||
|
<Text className="text-gray-600 text-sm">
|
||||||
|
{item.diagnosis.length > 20
|
||||||
|
? `${item.diagnosis.substring(0, 20)}...`
|
||||||
|
: item.diagnosis}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Cell
|
||||||
|
title="订单金额"
|
||||||
|
extra={
|
||||||
|
<Text className="text-red-500 font-medium">
|
||||||
|
¥{item.orderPrice || '0.00'}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Cell
|
||||||
|
title="创建时间"
|
||||||
|
extra={
|
||||||
|
<Text className="text-gray-500 text-xs">
|
||||||
|
{item.createTime}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Cell>
|
||||||
|
<Space className="w-full justify-end">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<Edit/>}
|
||||||
|
onClick={() => onDetail(item)}
|
||||||
|
>
|
||||||
|
详情
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<Edit/>}
|
||||||
|
onClick={() => onEdit(item)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<Del/>}
|
||||||
|
onClick={() => onDel(item)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Cell>
|
||||||
|
</CellGroup>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<FixedButton
|
||||||
|
text="开处方"
|
||||||
|
onClick={() => Taro.navigateTo({url: '/clinic/clinicPrescription/add'})}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClinicPrescriptionList;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '新增处方主表
|
navigationBarTitleText: '开处方',
|
||||||
',
|
|
||||||
navigationBarTextStyle: 'black'
|
navigationBarTextStyle: 'black'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,51 +1,273 @@
|
|||||||
import {useEffect, useState, useRef} from "react";
|
import {useEffect, useState, useRef} from "react";
|
||||||
import {useRouter} from '@tarojs/taro'
|
import {useRouter} from '@tarojs/taro'
|
||||||
import {Button, Loading, CellGroup, Input, TextArea, Form} from '@nutui/nutui-react-taro'
|
import {
|
||||||
|
Loading,
|
||||||
|
Button,
|
||||||
|
Form,
|
||||||
|
Cell,
|
||||||
|
Avatar,
|
||||||
|
Input,
|
||||||
|
Space,
|
||||||
|
TextArea
|
||||||
|
} from '@nutui/nutui-react-taro'
|
||||||
|
import {ArrowRight} from '@nutui/icons-react-taro'
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import {View} from '@tarojs/components'
|
import FixedButton from "@/components/FixedButton";
|
||||||
|
import navTo from "@/utils/common";
|
||||||
|
import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model";
|
||||||
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
||||||
import {getClinicPrescription, listClinicPrescription, updateClinicPrescription, addClinicPrescription} from "@/api/clinic/clinicPrescription";
|
import {TenantId} from "@/config/app";
|
||||||
|
import {getClinicPatientUser} from "@/api/clinic/clinicPatientUser";
|
||||||
|
|
||||||
const AddClinicPrescription = () => {
|
// 图片数据接口
|
||||||
|
interface UploadedImageData {
|
||||||
|
url?: string;
|
||||||
|
src?: string;
|
||||||
|
name?: string;
|
||||||
|
uid?: string;
|
||||||
|
message?: string;
|
||||||
|
type?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AddClinicOrder = () => {
|
||||||
const {params} = useRouter();
|
const {params} = useRouter();
|
||||||
const [loading, setLoading] = useState<boolean>(true)
|
const [toUser, setToUser] = useState<ClinicPatientUser>()
|
||||||
const [FormData, setFormData] = useState<ClinicPrescription>({})
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const formRef = useRef<any>(null)
|
const formRef = useRef<any>()
|
||||||
|
const [fileList, setFileList] = useState<UploadedImageData[]>([]) // 图片文件列表
|
||||||
|
|
||||||
|
// 患者和处方状态
|
||||||
|
const [selectedPatient, setSelectedPatient] = useState<ClinicPatientUser>()
|
||||||
|
const [selectedPrescription, setSelectedPrescription] = useState<ClinicPrescription>()
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const [formData, setFormData] = useState<ClinicPrescription>({
|
||||||
|
userId: undefined,
|
||||||
|
doctorId: undefined,
|
||||||
|
diagnosis: '',
|
||||||
|
treatmentPlan: '',
|
||||||
|
orderPrice: '',
|
||||||
|
decoctionInstructions: '',
|
||||||
|
items: [],
|
||||||
|
image: '' // 添加image字段
|
||||||
|
})
|
||||||
|
|
||||||
|
// 判断是编辑还是新增模式
|
||||||
|
const isEditMode = !!params.id
|
||||||
|
const toUserId = params.id ? Number(params.id) : undefined
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
if (params.id) {
|
if (toUserId) {
|
||||||
const data = await getClinicPrescription(Number(params.id))
|
getClinicPatientUser(Number(toUserId)).then(data => {
|
||||||
setFormData(data)
|
setToUser(data)
|
||||||
} else {
|
})
|
||||||
setFormData({})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交表单
|
// 设置选中的患者(供其他页面调用)
|
||||||
const submitSucceed = async (values: any) => {
|
// @ts-ignore
|
||||||
try {
|
const setSelectedPatientFunc = (patient: ClinicPatientUser) => {
|
||||||
if (params.id) {
|
console.log('设置选中的患者:', patient)
|
||||||
// 编辑模式
|
setToUser(patient)
|
||||||
await updateClinicPrescription({
|
setSelectedPatient(patient)
|
||||||
...values,
|
|
||||||
id: Number(params.id)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// 新增模式
|
|
||||||
await addClinicPrescription(values)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置选中的处方(供其他页面调用)
|
||||||
|
// @ts-ignore
|
||||||
|
const setSelectedPrescriptionFunc = (prescription: ClinicPrescription) => {
|
||||||
|
console.log('设置选中的处方:', prescription)
|
||||||
|
setSelectedPrescription(prescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理表单字段变化
|
||||||
|
const handleFormChange = (field: string, value: string) => {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
[field]: value
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择并上传图片
|
||||||
|
const handleChooseImage = () => {
|
||||||
|
if (fileList.length >= 5) { // 修正最大图片数量为5
|
||||||
|
Taro.showToast({
|
||||||
|
title: '最多只能上传5张图片',
|
||||||
|
icon: 'none'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.chooseImage({
|
||||||
|
count: 5 - fileList.length, // 剩余可选择的数量
|
||||||
|
sizeType: ['compressed'],
|
||||||
|
sourceType: ['album', 'camera'],
|
||||||
|
success: (res) => {
|
||||||
|
console.log('选择图片成功:', res)
|
||||||
|
|
||||||
|
// 逐个上传选中的图片
|
||||||
|
res.tempFilePaths.forEach((filePath, index) => {
|
||||||
|
uploadSingleImage(filePath, index)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.log('选择图片失败:', err)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '选择图片失败',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理文件删除
|
||||||
|
const handleFileRemove = (file: any) => {
|
||||||
|
console.log('删除文件:', file)
|
||||||
|
const newFileList = fileList.filter(f => f.uid !== file.uid)
|
||||||
|
setFileList(newFileList)
|
||||||
|
|
||||||
|
// 更新表单数据 - 使用JSON格式存储
|
||||||
|
if (newFileList.length === 0) {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
image: ''
|
||||||
|
}))
|
||||||
|
} else {
|
||||||
|
const imageData: UploadedImageData[] = newFileList.map(f => ({
|
||||||
|
url: f.url,
|
||||||
|
src: f.url,
|
||||||
|
name: f.name,
|
||||||
|
uid: f.uid
|
||||||
|
}))
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
image: JSON.stringify(imageData)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传单张图片
|
||||||
|
const uploadSingleImage = (filePath: any, index: number) => {
|
||||||
|
Taro.uploadFile({
|
||||||
|
url: 'https://server.websoft.top/api/oss/upload',
|
||||||
|
filePath: filePath,
|
||||||
|
name: 'file',
|
||||||
|
header: {
|
||||||
|
'content-type': 'multipart/form-data',
|
||||||
|
TenantId
|
||||||
|
},
|
||||||
|
success: (res) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(res.data);
|
||||||
|
console.log('上传成功', data)
|
||||||
|
if (data.code === 0 && data.data && data.data.url) {
|
||||||
|
// 更新文件列表
|
||||||
|
const newFile = {
|
||||||
|
name: `图片${Date.now()}_${index}`,
|
||||||
|
url: data.data.url,
|
||||||
|
status: 'success',
|
||||||
|
message: '上传成功',
|
||||||
|
type: 'image',
|
||||||
|
uid: `${Date.now()}_${index}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
setFileList(prev => {
|
||||||
|
const newList = [...prev, newFile]
|
||||||
|
// 同时更新表单数据 - 使用JSON格式存储
|
||||||
|
const imageData: UploadedImageData[] = newList.map(f => ({
|
||||||
|
url: f.url,
|
||||||
|
name: f.name,
|
||||||
|
uid: f.uid
|
||||||
|
}))
|
||||||
|
setFormData(prevForm => ({
|
||||||
|
...prevForm,
|
||||||
|
image: JSON.stringify(imageData)
|
||||||
|
}))
|
||||||
|
return newList
|
||||||
|
})
|
||||||
|
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: `操作成功`,
|
title: '上传成功',
|
||||||
icon: 'success'
|
icon: 'success'
|
||||||
})
|
})
|
||||||
|
} else {
|
||||||
setTimeout(() => {
|
|
||||||
return Taro.navigateBack()
|
|
||||||
}, 1000)
|
|
||||||
} catch (error) {
|
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: `操作失败`,
|
title: data.message || '上传失败',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析响应失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '上传失败: 数据格式错误',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
console.log('上传请求失败', err);
|
||||||
|
Taro.showToast({
|
||||||
|
title: '上传失败: 网络错误',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交表单 - 修改为跳转到确认页
|
||||||
|
const submitSucceed = async (values: any) => {
|
||||||
|
try {
|
||||||
|
console.log('提交数据:', values)
|
||||||
|
|
||||||
|
// 参数校验
|
||||||
|
if (!toUser && !selectedPatient) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: `请选择发送对象或患者`,
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!values.diagnosis) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: `请输入诊断结果`,
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!values.treatmentPlan) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: `请输入治疗方案`,
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建订单数据
|
||||||
|
const orderData = {
|
||||||
|
patient: toUser || selectedPatient,
|
||||||
|
prescription: selectedPrescription,
|
||||||
|
diagnosis: values.diagnosis,
|
||||||
|
treatmentPlan: values.treatmentPlan,
|
||||||
|
decoctionInstructions: values.decoctionInstructions || formData.decoctionInstructions,
|
||||||
|
images: fileList,
|
||||||
|
orderPrice: selectedPrescription?.orderPrice || '0.00'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存到本地存储
|
||||||
|
Taro.setStorageSync('tempOrderData', JSON.stringify(orderData))
|
||||||
|
|
||||||
|
console.log('跳转到订单确认页,订单数据:', orderData)
|
||||||
|
|
||||||
|
// 跳转到确认页
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: '/clinic/clinicPrescription/confirm'
|
||||||
|
})
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('数据处理失败:', error);
|
||||||
|
Taro.showToast({
|
||||||
|
title: `数据处理失败: ${error.message || error || '未知错误'}`,
|
||||||
icon: 'error'
|
icon: 'error'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -59,7 +281,45 @@ const AddClinicPrescription = () => {
|
|||||||
reload().then(() => {
|
reload().then(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
}, []);
|
|
||||||
|
// 设置页面实例的方法,供其他页面调用
|
||||||
|
try {
|
||||||
|
// @ts-ignore
|
||||||
|
if (Taro.getCurrentInstance() && Taro.getCurrentInstance().page) {
|
||||||
|
// @ts-ignore
|
||||||
|
Taro.getCurrentInstance().page.setSelectedPatient = setSelectedPatientFunc;
|
||||||
|
// @ts-ignore
|
||||||
|
Taro.getCurrentInstance().page.setSelectedPrescription = setSelectedPrescriptionFunc;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('设置页面实例方法失败:', error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从本地存储获取之前选择的患者和处方
|
||||||
|
try {
|
||||||
|
const storedPatient = Taro.getStorageSync('selectedPatient');
|
||||||
|
if (storedPatient) {
|
||||||
|
const parsedPatient = JSON.parse(storedPatient);
|
||||||
|
setSelectedPatient(parsedPatient);
|
||||||
|
Taro.removeStorageSync('selectedPatient');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析存储的患者数据失败:', error);
|
||||||
|
Taro.removeStorageSync('selectedPatient');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const storedPrescription = Taro.getStorageSync('selectedPrescription');
|
||||||
|
if (storedPrescription) {
|
||||||
|
const parsedPrescription = JSON.parse(storedPrescription);
|
||||||
|
setSelectedPrescription(parsedPrescription);
|
||||||
|
Taro.removeStorageSync('selectedPrescription');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析存储的处方数据失败:', error);
|
||||||
|
Taro.removeStorageSync('selectedPrescription');
|
||||||
|
}
|
||||||
|
}, [isEditMode]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Loading className={'px-2'}>加载中</Loading>
|
return <Loading className={'px-2'}>加载中</Loading>
|
||||||
@@ -67,32 +327,188 @@ const AddClinicPrescription = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{/* 显示已选择的用户(如果有的话) */}
|
||||||
|
<Cell title={(
|
||||||
|
<View className={'flex items-center'}>
|
||||||
|
<Avatar src={toUser?.avatar}/>
|
||||||
|
<View className={'ml-2 flex flex-col'}>
|
||||||
|
<Text>{toUser?.realName || '请选择'}</Text>
|
||||||
|
<Text className={'text-gray-300'}>{toUser?.phone}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)} extra={(
|
||||||
|
<ArrowRight color="#cccccc" className={'mt-2'} size={20}/>
|
||||||
|
)} onClick={() => navTo(`/clinic/clinicPatientUser/selectPatient`, true)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{toUser && (
|
||||||
<Form
|
<Form
|
||||||
ref={formRef}
|
ref={formRef}
|
||||||
divider
|
divider
|
||||||
initialValues={FormData}
|
initialValues={formData}
|
||||||
labelPosition="left"
|
labelPosition={'top'}
|
||||||
onFinish={(values) => submitSucceed(values)}
|
onFinish={(values) => submitSucceed(values)}
|
||||||
onFinishFailed={(errors) => submitFailed(errors)}
|
onFinishFailed={(errors) => submitFailed(errors)}
|
||||||
footer={
|
>
|
||||||
<div
|
{/* 基本信息 */}
|
||||||
|
<Form.Item
|
||||||
|
name="diagnosis"
|
||||||
|
label={'诊断结果'}
|
||||||
|
required
|
||||||
|
rules={[{required: true, message: '请填写诊断结果'}]}
|
||||||
|
initialValue={formData.diagnosis}
|
||||||
|
>
|
||||||
|
<Input placeholder="请输入诊断结果" style={{
|
||||||
|
backgroundColor: '#f9f9f9',
|
||||||
|
padding: '4px',
|
||||||
|
}}/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="treatmentPlan"
|
||||||
|
label={'治疗方案'}
|
||||||
|
required
|
||||||
|
rules={[{required: true, message: '请填写治疗方案'}]}
|
||||||
|
initialValue={formData.treatmentPlan}
|
||||||
|
>
|
||||||
|
<TextArea
|
||||||
|
value={formData.treatmentPlan}
|
||||||
|
onChange={(value) => handleFormChange('treatmentPlan', value)}
|
||||||
|
placeholder="请填写治疗方案"
|
||||||
style={{
|
style={{
|
||||||
|
backgroundColor: '#f9f9f9',
|
||||||
|
padding: '4px',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={'拍照上传'}
|
||||||
|
name="image"
|
||||||
|
rules={[{message: '请上传照片'}]}
|
||||||
|
>
|
||||||
|
<View style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '8px',
|
||||||
|
padding: '0'
|
||||||
|
}}>
|
||||||
|
{/* 显示已上传的图片 */}
|
||||||
|
{fileList.map((file) => (
|
||||||
|
<View key={file.uid} style={{
|
||||||
|
position: 'relative',
|
||||||
|
width: '80px',
|
||||||
|
height: '80px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
border: '1px solid #d9d9d9'
|
||||||
|
}}>
|
||||||
|
<img
|
||||||
|
src={file.url}
|
||||||
|
alt={file.name}
|
||||||
|
style={{
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
objectFit: 'cover'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="default"
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '-8px',
|
||||||
|
right: '-8px',
|
||||||
|
width: '20px',
|
||||||
|
height: '20px',
|
||||||
|
borderRadius: '10px',
|
||||||
|
fontSize: '12px',
|
||||||
|
minWidth: '20px',
|
||||||
|
padding: 0,
|
||||||
|
lineHeight: '20px'
|
||||||
|
}}
|
||||||
|
onClick={() => handleFileRemove(file)}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* 添加图片按钮 */}
|
||||||
|
{fileList.length < 5 && (
|
||||||
|
<View
|
||||||
|
onClick={handleChooseImage}
|
||||||
|
style={{
|
||||||
|
width: '80px',
|
||||||
|
height: '80px',
|
||||||
|
borderRadius: '8px',
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
width: '100%'
|
border: '2px dashed #d9d9d9',
|
||||||
|
backgroundColor: '#fafafa',
|
||||||
|
cursor: 'pointer'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<span style={{fontSize: '20px', color: '#d9d9d9'}}>+</span>
|
||||||
nativeType="submit"
|
<span style={{fontSize: '10px', marginTop: '2px', color: '#666'}}>
|
||||||
type="success"
|
添加图片
|
||||||
size="large"
|
</span>
|
||||||
className={'w-full'}
|
</View>
|
||||||
block
|
)}
|
||||||
>
|
</View>
|
||||||
{params.id ? '更新' : '保存'}
|
<View className="text-xs text-gray-500">
|
||||||
</Button>
|
可上传病例/舌苔/面相等,已上传{fileList.length}张图片
|
||||||
</div>
|
</View>
|
||||||
}
|
</Form.Item>
|
||||||
>
|
</Form>
|
||||||
<CellGroup style={{padding: '4px 0'}}>
|
)}
|
||||||
<Form.Item name="userId" label="患者" initialValue={FormData.userId} required>
|
|
||||||
|
|
||||||
|
|
||||||
|
{/* 选择处方 */}
|
||||||
|
<Cell
|
||||||
|
title="选择处方"
|
||||||
|
extra={selectedPrescription ? (
|
||||||
|
<View className={'flex items-center'}>
|
||||||
|
<Text className={'mr-2'}>{selectedPrescription.treatmentPlan || '未知处方'}</Text>
|
||||||
|
<ArrowRight color="#cccccc" size={18}/>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<Text className={'text-gray-400'}>请选择处方</Text>
|
||||||
|
)}
|
||||||
|
onClick={() => navTo(`/clinic/clinicPrescription/selectPrescription`, true)}
|
||||||
|
/>
|
||||||
|
{/* 药方信息 */}
|
||||||
|
{selectedPrescription && (
|
||||||
|
<>
|
||||||
|
<Cell extra={'药方信息'}>
|
||||||
|
<View className={'flex flex-col'}>
|
||||||
|
<View className={'py-3'}>RP: {selectedPrescription.prescriptionType === 0 ? '中药' : '西药'} 共{selectedPrescription.items?.length}味、共{selectedPrescription.orderPrice}元</View>
|
||||||
|
<Space className={'flex flex-wrap'}>
|
||||||
|
{selectedPrescription.items?.map(item => (
|
||||||
|
<Button>{item.medicineName} 105克</Button>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
</Cell>
|
||||||
|
|
||||||
|
{/* 煎药说明 */}
|
||||||
|
<TextArea
|
||||||
|
value={formData.decoctionInstructions}
|
||||||
|
onChange={(value) => handleFormChange('decoctionInstructions', value)}
|
||||||
|
placeholder="请填写用药说明"
|
||||||
|
rows={2}
|
||||||
|
maxLength={200}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 底部浮动按钮 */}
|
||||||
|
<FixedButton text={'下一步:确认订单信息'} onClick={() => formRef.current?.submit()}/>
|
||||||
|
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AddClinicOrder;
|
||||||
|
|||||||
809
src/clinic/clinicPrescription/components/OrderList.tsx
Normal file
809
src/clinic/clinicPrescription/components/OrderList.tsx
Normal file
@@ -0,0 +1,809 @@
|
|||||||
|
import {Avatar, Cell, Space, Empty, Tabs, Button, TabPane, Image, Dialog} from '@nutui/nutui-react-taro'
|
||||||
|
import {useEffect, useState, useCallback, CSSProperties} from "react";
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
|
import Taro from '@tarojs/taro';
|
||||||
|
import {InfiniteLoading} from '@nutui/nutui-react-taro'
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
import {updateShopOrder, createOrder} from "@/api/shop/shopOrder";
|
||||||
|
import {ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model";
|
||||||
|
import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model";
|
||||||
|
import {copyText} from "@/utils/common";
|
||||||
|
import PaymentCountdown from "@/components/PaymentCountdown";
|
||||||
|
import {PaymentType} from "@/utils/payment";
|
||||||
|
import {goTo} from "@/utils/navigation";
|
||||||
|
import {pageClinicPrescription} from "@/api/clinic/clinicPrescription";
|
||||||
|
import Prescription from "@/clinic/clinicPatientUser/prescription";
|
||||||
|
|
||||||
|
// 判断订单是否支付已过期
|
||||||
|
const isPaymentExpired = (createTime: string, timeoutHours: number = 24): boolean => {
|
||||||
|
if (!createTime) return false;
|
||||||
|
const createTimeObj = dayjs(createTime);
|
||||||
|
const expireTime = createTimeObj.add(timeoutHours, 'hour');
|
||||||
|
const now = dayjs();
|
||||||
|
return now.isAfter(expireTime);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInfiniteUlStyle = (showSearch: boolean = false): CSSProperties => ({
|
||||||
|
marginTop: showSearch ? '0' : '0', // 如果显示搜索框,增加更多的上边距
|
||||||
|
height: showSearch ? '75vh' : '84vh', // 相应调整高度
|
||||||
|
width: '100%',
|
||||||
|
padding: '0',
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden'
|
||||||
|
// 注意:小程序不支持 boxShadow
|
||||||
|
})
|
||||||
|
|
||||||
|
// 统一的订单状态标签配置,与后端 statusFilter 保持一致
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
index: 0,
|
||||||
|
key: '全部',
|
||||||
|
title: '全部',
|
||||||
|
description: '所有订单',
|
||||||
|
statusFilter: -1 // 使用-1表示全部订单
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 1,
|
||||||
|
key: '待付款',
|
||||||
|
title: '待付款',
|
||||||
|
description: '等待付款的订单',
|
||||||
|
statusFilter: 0 // 对应后端:pay_status = false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 2,
|
||||||
|
key: '待发货',
|
||||||
|
title: '待发货',
|
||||||
|
description: '已付款待发货的订单',
|
||||||
|
statusFilter: 1 // 对应后端:pay_status = true AND delivery_status = 10
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 3,
|
||||||
|
key: '待收货',
|
||||||
|
title: '待收货',
|
||||||
|
description: '已发货待收货的订单',
|
||||||
|
statusFilter: 3 // 对应后端:pay_status = true AND delivery_status = 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 4,
|
||||||
|
key: '已完成',
|
||||||
|
title: '已完成',
|
||||||
|
description: '已完成的订单',
|
||||||
|
statusFilter: 5 // 对应后端:order_status = 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
index: 5,
|
||||||
|
key: '退货/售后',
|
||||||
|
title: '退货/售后',
|
||||||
|
description: '退货/售后的订单',
|
||||||
|
statusFilter: 6 // 对应后端:order_status = 6 (已退款)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 扩展订单接口,包含商品信息
|
||||||
|
interface OrderWithGoods extends ShopOrder {
|
||||||
|
orderGoods?: ShopOrderGoods[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OrderListProps {
|
||||||
|
onReload?: () => void;
|
||||||
|
searchParams?: ShopOrderParam;
|
||||||
|
showSearch?: boolean;
|
||||||
|
onSearchParamsChange?: (params: ShopOrderParam) => void; // 新增:通知父组件参数变化
|
||||||
|
}
|
||||||
|
|
||||||
|
function OrderList(props: OrderListProps) {
|
||||||
|
const [list, setList] = useState<OrderWithGoods[]>([])
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [hasMore, setHasMore] = useState(true)
|
||||||
|
// 根据传入的statusFilter设置初始tab索引
|
||||||
|
const getInitialTabIndex = () => {
|
||||||
|
if (props.searchParams?.statusFilter !== undefined) {
|
||||||
|
const tab = tabs.find(t => t.statusFilter === props.searchParams?.statusFilter);
|
||||||
|
return tab ? tab.index : 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
const [tapIndex, setTapIndex] = useState<number>(() => {
|
||||||
|
const initialIndex = getInitialTabIndex();
|
||||||
|
console.log('初始化tapIndex:', initialIndex, '对应statusFilter:', props.searchParams?.statusFilter);
|
||||||
|
return initialIndex;
|
||||||
|
})
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [cancelDialogVisible, setCancelDialogVisible] = useState(false)
|
||||||
|
const [orderToCancel, setOrderToCancel] = useState<ShopOrder | null>(null)
|
||||||
|
const [confirmReceiveDialogVisible, setConfirmReceiveDialogVisible] = useState(false)
|
||||||
|
const [orderToConfirmReceive, setOrderToConfirmReceive] = useState<ShopOrder | null>(null)
|
||||||
|
|
||||||
|
// 获取订单状态文本
|
||||||
|
const getOrderStatusText = (order: ShopOrder) => {
|
||||||
|
|
||||||
|
// 优先检查订单状态
|
||||||
|
if (order.orderStatus === 2) return '已取消';
|
||||||
|
if (order.orderStatus === 4) return '退款申请中';
|
||||||
|
if (order.orderStatus === 5) return '退款被拒绝';
|
||||||
|
if (order.orderStatus === 6) return '退款成功';
|
||||||
|
if (order.orderStatus === 7) return '客户端申请退款';
|
||||||
|
|
||||||
|
// 检查支付状态 (payStatus为boolean类型,false/0表示未付款,true/1表示已付款)
|
||||||
|
if (!order.payStatus) return '等待买家付款';
|
||||||
|
|
||||||
|
// 已付款后检查发货状态
|
||||||
|
if (order.deliveryStatus === 10) return '待发货';
|
||||||
|
if (order.deliveryStatus === 20) return '待收货';
|
||||||
|
if (order.deliveryStatus === 30) return '已完成';
|
||||||
|
|
||||||
|
// 最后检查订单完成状态
|
||||||
|
if (order.orderStatus === 1) return '已完成';
|
||||||
|
if (order.orderStatus === 0) return '未使用';
|
||||||
|
|
||||||
|
return '未知状态';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取订单状态颜色
|
||||||
|
const getOrderStatusColor = (order: ShopOrder) => {
|
||||||
|
// 优先检查订单状态
|
||||||
|
if (order.orderStatus === 2) return 'text-gray-500'; // 已取消
|
||||||
|
if (order.orderStatus === 4) return 'text-orange-500'; // 退款申请中
|
||||||
|
if (order.orderStatus === 5) return 'text-red-500'; // 退款被拒绝
|
||||||
|
if (order.orderStatus === 6) return 'text-green-500'; // 退款成功
|
||||||
|
if (order.orderStatus === 7) return 'text-orange-500'; // 客户端申请退款
|
||||||
|
|
||||||
|
// 检查支付状态
|
||||||
|
if (!order.payStatus) return 'text-orange-500'; // 等待买家付款
|
||||||
|
|
||||||
|
// 已付款后检查发货状态
|
||||||
|
if (order.deliveryStatus === 10) return 'text-blue-500'; // 待发货
|
||||||
|
if (order.deliveryStatus === 20) return 'text-purple-500'; // 待收货
|
||||||
|
if (order.deliveryStatus === 30) return 'text-green-500'; // 已收货
|
||||||
|
|
||||||
|
// 最后检查订单完成状态
|
||||||
|
if (order.orderStatus === 1) return 'text-green-600'; // 已完成
|
||||||
|
if (order.orderStatus === 0) return 'text-gray-500'; // 未使用
|
||||||
|
|
||||||
|
return 'text-gray-600'; // 默认颜色
|
||||||
|
};
|
||||||
|
|
||||||
|
// 使用后端统一的 statusFilter 进行筛选
|
||||||
|
const getOrderStatusParams = (index: string | number) => {
|
||||||
|
let params: ShopOrderParam = {};
|
||||||
|
// 添加用户ID过滤
|
||||||
|
params.userId = Taro.getStorageSync('UserId');
|
||||||
|
|
||||||
|
// 获取当前tab的statusFilter配置
|
||||||
|
const currentTab = tabs.find(tab => tab.index === Number(index));
|
||||||
|
if (currentTab && currentTab.statusFilter !== undefined) {
|
||||||
|
params.statusFilter = currentTab.statusFilter;
|
||||||
|
}
|
||||||
|
// 注意:当statusFilter为undefined时,不要添加到params中,这样API请求就不会包含这个参数
|
||||||
|
|
||||||
|
console.log(`Tab ${index} (${currentTab?.title}) 筛选参数:`, params);
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
|
||||||
|
const reload = useCallback(async (resetPage = false, targetPage?: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(null); // 清除之前的错误
|
||||||
|
const currentPage = resetPage ? 1 : (targetPage || page);
|
||||||
|
const statusParams = getOrderStatusParams(tapIndex);
|
||||||
|
// 合并搜索条件,tab的statusFilter优先级更高
|
||||||
|
const searchConditions: any = {
|
||||||
|
page: currentPage,
|
||||||
|
doctorId: statusParams.userId, // 用户ID
|
||||||
|
// ...props.searchParams, // 搜索关键词等其他条件
|
||||||
|
};
|
||||||
|
|
||||||
|
// statusFilter总是添加到搜索条件中(包括-1表示全部)
|
||||||
|
if (statusParams.statusFilter !== undefined) {
|
||||||
|
searchConditions.statusFilter = statusParams.statusFilter;
|
||||||
|
}
|
||||||
|
console.log('订单筛选条件:', {
|
||||||
|
tapIndex,
|
||||||
|
statusParams,
|
||||||
|
searchConditions,
|
||||||
|
finalStatusFilter: searchConditions.statusFilter
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await pageClinicPrescription(searchConditions);
|
||||||
|
let newList: OrderWithGoods[];
|
||||||
|
|
||||||
|
if (res?.list && res?.list.length > 0) {
|
||||||
|
// 使用函数式更新避免依赖 list
|
||||||
|
setList(res?.list);
|
||||||
|
|
||||||
|
// 正确判断是否还有更多数据
|
||||||
|
const hasMoreData = res.list.length >= 10; // 假设每页10条数据
|
||||||
|
setHasMore(hasMoreData);
|
||||||
|
} else {
|
||||||
|
setList(prevList => resetPage ? [] : prevList);
|
||||||
|
setHasMore(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
setPage(currentPage);
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载订单失败:', error);
|
||||||
|
setLoading(false);
|
||||||
|
setError('加载订单失败,请重试');
|
||||||
|
// 添加错误提示
|
||||||
|
Taro.showToast({
|
||||||
|
title: '加载失败,请重试',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [tapIndex, page, props.searchParams]); // 移除 list 依赖
|
||||||
|
|
||||||
|
const reloadMore = useCallback(async () => {
|
||||||
|
if (loading || !hasMore) return; // 防止重复加载
|
||||||
|
const nextPage = page + 1;
|
||||||
|
setPage(nextPage);
|
||||||
|
await reload(false, nextPage);
|
||||||
|
}, [loading, hasMore, page, reload]);
|
||||||
|
|
||||||
|
// 确认收货 - 显示确认对话框
|
||||||
|
const confirmReceive = (order: ShopOrder) => {
|
||||||
|
setOrderToConfirmReceive(order);
|
||||||
|
setConfirmReceiveDialogVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认收货 - 执行收货操作
|
||||||
|
const handleConfirmReceive = async () => {
|
||||||
|
if (!orderToConfirmReceive) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setConfirmReceiveDialogVisible(false);
|
||||||
|
|
||||||
|
await updateShopOrder({
|
||||||
|
...orderToConfirmReceive,
|
||||||
|
deliveryStatus: 30, // 已收货
|
||||||
|
orderStatus: 1 // 已完成
|
||||||
|
});
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: '确认收货成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
await reload(true); // 重新加载列表
|
||||||
|
props.onReload?.(); // 通知父组件刷新
|
||||||
|
|
||||||
|
// 清空状态
|
||||||
|
setOrderToConfirmReceive(null);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('确认收货失败:', error);
|
||||||
|
Taro.showToast({
|
||||||
|
title: '确认收货失败',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
// 重新显示对话框
|
||||||
|
setConfirmReceiveDialogVisible(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消确认收货对话框
|
||||||
|
const handleCancelReceiveDialog = () => {
|
||||||
|
setConfirmReceiveDialogVisible(false);
|
||||||
|
setOrderToConfirmReceive(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 申请退款 (待发货状态)
|
||||||
|
const applyRefund = (order: ShopOrder) => {
|
||||||
|
// 跳转到退款申请页面
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: `/user/order/refund/index?orderId=${order.orderId}&orderNo=${order.orderNo}`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看物流 (待收货状态)
|
||||||
|
const viewLogistics = (order: ShopOrder) => {
|
||||||
|
// 跳转到物流查询页面
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: `/user/order/logistics/index?orderId=${order.orderId}&orderNo=${order.orderNo}&expressNo=${order.transactionId || ''}&expressCompany=SF`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 再次购买 (已完成状态)
|
||||||
|
const buyAgain = (order: ShopOrder) => {
|
||||||
|
console.log('再次购买:', order);
|
||||||
|
goTo(`/shop/orderConfirm/index?goodsId=${order.orderGoods[0].goodsId}`)
|
||||||
|
// Taro.showToast({
|
||||||
|
// title: '再次购买功能开发中',
|
||||||
|
// icon: 'none'
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
// 评价商品 (已完成状态)
|
||||||
|
const evaluateGoods = (order: ShopOrder) => {
|
||||||
|
// 跳转到评价页面
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: `/user/order/evaluate/index?orderId=${order.orderId}&orderNo=${order.orderNo}`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 查看进度 (退款/售后状态)
|
||||||
|
const viewProgress = (order: ShopOrder) => {
|
||||||
|
// 根据订单状态确定售后类型
|
||||||
|
let afterSaleType = 'refund' // 默认退款
|
||||||
|
|
||||||
|
if (order.orderStatus === 4) {
|
||||||
|
afterSaleType = 'refund' // 退款申请中
|
||||||
|
} else if (order.orderStatus === 7) {
|
||||||
|
afterSaleType = 'return' // 退货申请中
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转到售后进度页面
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: `/user/order/progress/index?orderId=${order.orderId}&orderNo=${order.orderNo}&type=${afterSaleType}`
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 撤销申请 (退款/售后状态)
|
||||||
|
const cancelApplication = (order: ShopOrder) => {
|
||||||
|
console.log('撤销申请:', order);
|
||||||
|
Taro.showToast({
|
||||||
|
title: '撤销申请功能开发中',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消订单
|
||||||
|
const cancelOrder = (order: ShopOrder) => {
|
||||||
|
setOrderToCancel(order);
|
||||||
|
setCancelDialogVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 确认取消订单
|
||||||
|
const handleConfirmCancel = async () => {
|
||||||
|
if (!orderToCancel) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
setCancelDialogVisible(false);
|
||||||
|
|
||||||
|
// 更新订单状态为已取消,而不是删除订单
|
||||||
|
await updateShopOrder({
|
||||||
|
...orderToCancel,
|
||||||
|
orderStatus: 2 // 已取消
|
||||||
|
});
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单已取消',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
void reload(true); // 重新加载列表
|
||||||
|
props.onReload?.(); // 通知父组件刷新
|
||||||
|
} catch (error) {
|
||||||
|
console.error('取消订单失败:', error);
|
||||||
|
Taro.showToast({
|
||||||
|
title: '取消订单失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setOrderToCancel(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 取消对话框的取消操作
|
||||||
|
const handleCancelDialog = () => {
|
||||||
|
setCancelDialogVisible(false);
|
||||||
|
setOrderToCancel(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 立即支付
|
||||||
|
// @ts-ignore
|
||||||
|
const payOrder = async (order: Prescription) => {
|
||||||
|
try {
|
||||||
|
if (!order.id || !order.orderNo) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单信息错误',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查订单是否已过期
|
||||||
|
if (order.createTime && isPaymentExpired(order.createTime)) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单已过期,无法支付',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查订单状态
|
||||||
|
if (order.payStatus) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单已支付',
|
||||||
|
icon: 'none'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order.orderStatus === 2) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单已取消,无法支付',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.showLoading({title: '发起支付...'});
|
||||||
|
|
||||||
|
// 构建商品数据
|
||||||
|
const goodsItems = order.items?.map(goods => ({
|
||||||
|
goodsId: goods.medicineId,
|
||||||
|
quantity: goods.totalNum || 1
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
// 对于已存在的订单,我们需要重新发起支付
|
||||||
|
// 构建支付请求数据,包含完整的商品信息
|
||||||
|
const paymentData = {
|
||||||
|
orderId: order.id,
|
||||||
|
orderNo: order.orderNo,
|
||||||
|
goodsItems: goodsItems,
|
||||||
|
// addressId: order.addressId,
|
||||||
|
payType: PaymentType.WECHAT,
|
||||||
|
type: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('重新支付数据:', paymentData);
|
||||||
|
|
||||||
|
// 直接调用createOrder API进行重新支付
|
||||||
|
const result = await createOrder(paymentData as any);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('支付发起失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证微信支付必要参数
|
||||||
|
if (!result.timeStamp || !result.nonceStr || !result.package || !result.paySign) {
|
||||||
|
throw new Error('微信支付参数不完整');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用微信支付
|
||||||
|
await Taro.requestPayment({
|
||||||
|
timeStamp: result.timeStamp,
|
||||||
|
nonceStr: result.nonceStr,
|
||||||
|
package: result.package,
|
||||||
|
signType: (result.signType || 'MD5') as 'MD5' | 'HMAC-SHA256',
|
||||||
|
paySign: result.paySign,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 支付成功
|
||||||
|
Taro.showToast({
|
||||||
|
title: '支付成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重新加载订单列表
|
||||||
|
void reload(true);
|
||||||
|
props.onReload?.();
|
||||||
|
|
||||||
|
// 跳转到订单页面
|
||||||
|
setTimeout(() => {
|
||||||
|
Taro.navigateTo({url: '/user/order/order'});
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('支付失败:', error);
|
||||||
|
|
||||||
|
let errorMessage = '支付失败,请重试';
|
||||||
|
if (error.message) {
|
||||||
|
if (error.message.includes('cancel')) {
|
||||||
|
errorMessage = '用户取消支付';
|
||||||
|
} else if (error.message.includes('余额不足')) {
|
||||||
|
errorMessage = '账户余额不足';
|
||||||
|
} else {
|
||||||
|
errorMessage = error.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: errorMessage,
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
Taro.hideLoading();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
void reload(true); // 首次加载或tab切换时重置页码
|
||||||
|
}, [tapIndex]); // 只监听tapIndex变化,避免reload依赖循环
|
||||||
|
|
||||||
|
// 监听外部statusFilter变化,同步更新tab索引
|
||||||
|
useEffect(() => {
|
||||||
|
// 获取当前的statusFilter,如果未定义则默认为-1(全部)
|
||||||
|
const currentStatusFilter = props.searchParams?.statusFilter !== undefined
|
||||||
|
? props.searchParams.statusFilter
|
||||||
|
: -1;
|
||||||
|
|
||||||
|
const tab = tabs.find(t => t.statusFilter === currentStatusFilter);
|
||||||
|
const targetTabIndex = tab ? tab.index : 0;
|
||||||
|
|
||||||
|
console.log('外部statusFilter变化:', {
|
||||||
|
statusFilter: currentStatusFilter,
|
||||||
|
originalStatusFilter: props.searchParams?.statusFilter,
|
||||||
|
currentTapIndex: tapIndex,
|
||||||
|
targetTabIndex,
|
||||||
|
shouldUpdate: targetTabIndex !== tapIndex
|
||||||
|
});
|
||||||
|
|
||||||
|
if (targetTabIndex !== tapIndex) {
|
||||||
|
setTapIndex(targetTabIndex);
|
||||||
|
// 不需要调用reload,因为tapIndex变化会触发reload
|
||||||
|
}
|
||||||
|
}, [props.searchParams?.statusFilter, tapIndex]); // 监听statusFilter变化
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Tabs
|
||||||
|
align={'left'}
|
||||||
|
className={'fixed left-0'}
|
||||||
|
style={{
|
||||||
|
zIndex: 998,
|
||||||
|
borderBottom: '1px solid #e5e5e5'
|
||||||
|
}}
|
||||||
|
tabStyle={{
|
||||||
|
backgroundColor: '#ffffff'
|
||||||
|
// 注意:小程序不支持 boxShadow
|
||||||
|
}}
|
||||||
|
value={tapIndex}
|
||||||
|
onChange={(paneKey) => {
|
||||||
|
console.log('Tab切换:', paneKey, '类型:', typeof paneKey);
|
||||||
|
const newTapIndex = Number(paneKey);
|
||||||
|
setTapIndex(newTapIndex);
|
||||||
|
|
||||||
|
// 通知父组件更新 searchParams.statusFilter
|
||||||
|
const currentTab = tabs.find(tab => tab.index === newTapIndex);
|
||||||
|
if (currentTab && props.onSearchParamsChange) {
|
||||||
|
const newSearchParams = {
|
||||||
|
...props.searchParams,
|
||||||
|
statusFilter: currentTab.statusFilter
|
||||||
|
};
|
||||||
|
console.log('通知父组件更新searchParams:', newSearchParams);
|
||||||
|
props.onSearchParamsChange(newSearchParams);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{
|
||||||
|
tabs?.map((item, _) => {
|
||||||
|
return (
|
||||||
|
<TabPane
|
||||||
|
key={item.index}
|
||||||
|
title={loading && tapIndex === item.index ? `${item.title}...` : item.title}
|
||||||
|
></TabPane>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
</Tabs>
|
||||||
|
<View style={getInfiniteUlStyle(props.showSearch)} id="scroll">
|
||||||
|
{error ? (
|
||||||
|
<View className="flex flex-col items-center justify-center h-64">
|
||||||
|
<View className="text-gray-500 mb-4">{error}</View>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
onClick={() => reload(true)}
|
||||||
|
>
|
||||||
|
重新加载
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<InfiniteLoading
|
||||||
|
target="scroll"
|
||||||
|
hasMore={hasMore}
|
||||||
|
onLoadMore={reloadMore}
|
||||||
|
onScroll={() => {
|
||||||
|
|
||||||
|
}}
|
||||||
|
onScrollToUpper={() => {
|
||||||
|
|
||||||
|
}}
|
||||||
|
loadingText={
|
||||||
|
<>
|
||||||
|
加载中
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
loadMoreText={
|
||||||
|
list.length === 0 ? (
|
||||||
|
<Empty style={{backgroundColor: 'transparent'}} description="您还没有订单哦"/>
|
||||||
|
) : (
|
||||||
|
<View className={'h-24'}>
|
||||||
|
没有更多了
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* 订单列表 */}
|
||||||
|
{list.length > 0 && list
|
||||||
|
?.filter((item) => {
|
||||||
|
// 如果是待付款标签页(tapIndex === 1),过滤掉支付已过期的订单
|
||||||
|
if (tapIndex === 1 && !item.payStatus && item.orderStatus !== 2 && item.createTime) {
|
||||||
|
return !isPaymentExpired(item.createTime);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
?.map((item, index) => {
|
||||||
|
return (
|
||||||
|
<Cell key={index} style={{padding: '16px'}}
|
||||||
|
onClick={() => Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${item.orderId}`})}>
|
||||||
|
<Space direction={'vertical'} className={'w-full flex flex-col'}>
|
||||||
|
<View className={'order-no flex justify-between'}>
|
||||||
|
<View className={'flex items-center'}>
|
||||||
|
<Text className={'text-gray-600 font-bold text-sm'}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
copyText(`${item.orderNo}`)
|
||||||
|
}}>{item.orderNo}</Text>
|
||||||
|
</View>
|
||||||
|
{/* 右侧显示合并的状态和倒计时 */}
|
||||||
|
<View className={`${getOrderStatusColor(item)} font-medium`}>
|
||||||
|
{!item.payStatus && item.orderStatus !== 2 ? (
|
||||||
|
<PaymentCountdown
|
||||||
|
createTime={item.createTime}
|
||||||
|
payStatus={item.payStatus}
|
||||||
|
realTime={false}
|
||||||
|
showSeconds={false}
|
||||||
|
mode={'badge'}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
getOrderStatusText(item)
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
className={'create-time text-gray-400 text-xs'}>{dayjs(item.createTime).format('YYYY年MM月DD日 HH:mm:ss')}</View>
|
||||||
|
|
||||||
|
{/* 商品信息 */}
|
||||||
|
<View className={'goods-info'}>
|
||||||
|
{item.orderGoods && item.orderGoods.length > 0 ? (
|
||||||
|
item.orderGoods.map((goods, goodsIndex) => (
|
||||||
|
<View key={goodsIndex} className={'flex items-center mb-2'}>
|
||||||
|
<Image
|
||||||
|
src={goods.image || '/default-goods.png'}
|
||||||
|
width="50"
|
||||||
|
height="50"
|
||||||
|
lazyLoad={false}
|
||||||
|
className={'rounded'}
|
||||||
|
/>
|
||||||
|
<View className={'ml-2 flex flex-col flex-1'}>
|
||||||
|
<Text className={'text-sm font-bold'}>{goods.goodsName}</Text>
|
||||||
|
{goods.spec && <Text className={'text-gray-500 text-xs'}>规格:{goods.spec}</Text>}
|
||||||
|
<Text className={'text-gray-500 text-xs'}>数量:{goods.totalNum}</Text>
|
||||||
|
</View>
|
||||||
|
<Text className={'text-sm'}>¥{goods.price}</Text>
|
||||||
|
</View>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<View className={'flex items-center'}>
|
||||||
|
<Avatar
|
||||||
|
src='/default-goods.png'
|
||||||
|
size={'50'}
|
||||||
|
shape={'square'}
|
||||||
|
/>
|
||||||
|
<View className={'ml-2'}>
|
||||||
|
<Text className={'text-sm'}>{item.title || '订单商品'}</Text>
|
||||||
|
<Text className={'text-gray-400 text-xs'}>{item.totalNum}件商品</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text className={'w-full text-right'}>实付金额:¥{item.payPrice}</Text>
|
||||||
|
|
||||||
|
{/* 操作按钮 */}
|
||||||
|
<Space className={'btn flex justify-end'}>
|
||||||
|
{/* 待付款状态:显示取消订单和立即支付 */}
|
||||||
|
{(!item.payStatus) && item.orderStatus !== 2 && (
|
||||||
|
<Space>
|
||||||
|
<Button size={'small'} onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
void cancelOrder(item);
|
||||||
|
}}>取消订单</Button>
|
||||||
|
{item.showPayButton && (
|
||||||
|
<Button size={'small'} type="primary" onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
void payOrder(item);
|
||||||
|
}}>立即支付</Button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 待发货状态:显示申请退款 */}
|
||||||
|
{/*{item.payStatus && item.deliveryStatus === 10 && item.orderStatus !== 2 && item.orderStatus !== 4 && (*/}
|
||||||
|
{/* <Button size={'small'} onClick={(e) => {*/}
|
||||||
|
{/* e.stopPropagation();*/}
|
||||||
|
{/* applyRefund(item);*/}
|
||||||
|
{/* }}>申请退款</Button>*/}
|
||||||
|
{/*)}*/}
|
||||||
|
|
||||||
|
{/* 待收货状态:显示查看物流和确认收货 */}
|
||||||
|
{item.deliveryStatus === 20 && item.orderStatus !== 2 && (
|
||||||
|
<Space>
|
||||||
|
{/*<Button size={'small'} onClick={(e) => {*/}
|
||||||
|
{/* e.stopPropagation();*/}
|
||||||
|
{/* viewLogistics(item);*/}
|
||||||
|
{/*}}>查看物流</Button>*/}
|
||||||
|
<Button size={'small'} type="primary" onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
confirmReceive(item);
|
||||||
|
}}>确认收货</Button>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 已完成状态:显示再次购买、评价商品、申请退款 */}
|
||||||
|
{item.orderStatus === 1 && (
|
||||||
|
<Space>
|
||||||
|
<Button size={'small'} onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
buyAgain(item);
|
||||||
|
}}>再次购买</Button>
|
||||||
|
{/*<Button size={'small'} onClick={(e) => {*/}
|
||||||
|
{/* e.stopPropagation();*/}
|
||||||
|
{/* evaluateGoods(item);*/}
|
||||||
|
{/*}}>评价商品</Button>*/}
|
||||||
|
{/*<Button size={'small'} onClick={(e) => {*/}
|
||||||
|
{/* e.stopPropagation();*/}
|
||||||
|
{/* applyRefund(item);*/}
|
||||||
|
{/*}}>申请退款</Button>*/}
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 退款/售后状态:显示查看进度和撤销申请 */}
|
||||||
|
{(item.orderStatus === 4 || item.orderStatus === 7) && (
|
||||||
|
<Space>
|
||||||
|
{/*<Button size={'small'} onClick={(e) => {*/}
|
||||||
|
{/* e.stopPropagation();*/}
|
||||||
|
{/* viewProgress(item);*/}
|
||||||
|
{/*}}>查看进度</Button>*/}
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 退款成功状态:显示再次购买 */}
|
||||||
|
{item.orderStatus === 6 && (
|
||||||
|
<Button size={'small'} type="primary" onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
buyAgain(item);
|
||||||
|
}}>再次购买</Button>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Space>
|
||||||
|
</Cell>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</InfiniteLoading>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 取消订单确认对话框 */}
|
||||||
|
<Dialog
|
||||||
|
title="确认取消"
|
||||||
|
visible={cancelDialogVisible}
|
||||||
|
confirmText="确认取消"
|
||||||
|
cancelText="我再想想"
|
||||||
|
onConfirm={handleConfirmCancel}
|
||||||
|
onCancel={handleCancelDialog}
|
||||||
|
>
|
||||||
|
确定要取消这个订单吗?
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
{/* 确认收货确认对话框 */}
|
||||||
|
<Dialog
|
||||||
|
title="确认收货"
|
||||||
|
visible={confirmReceiveDialogVisible}
|
||||||
|
confirmText="确认收货"
|
||||||
|
cancelText="我再想想"
|
||||||
|
onConfirm={handleConfirmReceive}
|
||||||
|
onCancel={handleCancelReceiveDialog}
|
||||||
|
>
|
||||||
|
确定已经收到商品了吗?确认收货后订单将完成。
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OrderList
|
||||||
6
src/clinic/clinicPrescription/confirm.config.ts
Normal file
6
src/clinic/clinicPrescription/confirm.config.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
navigationBarTitleText: '确认处方订单',
|
||||||
|
navigationBarBackgroundColor: '#fff',
|
||||||
|
navigationBarTextStyle: 'black',
|
||||||
|
backgroundColor: '#f5f5f5'
|
||||||
|
}
|
||||||
286
src/clinic/clinicPrescription/confirm.scss
Normal file
286
src/clinic/clinicPrescription/confirm.scss
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
.doctor-order-confirm {
|
||||||
|
min-height: 100vh;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding-bottom: 80px;
|
||||||
|
|
||||||
|
// 页面提示
|
||||||
|
.confirm-tip {
|
||||||
|
background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
|
||||||
|
padding: 12px 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
|
||||||
|
.tip-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #059669;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
.order-confirm-loading {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分组样式
|
||||||
|
.section-group {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
|
.nut-cell-group__title {
|
||||||
|
padding: 12px 16px;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 患者信息
|
||||||
|
.patient-info {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 4px 0;
|
||||||
|
|
||||||
|
.patient-detail {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
|
||||||
|
.patient-name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-phone {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-extra {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9ca3af;
|
||||||
|
|
||||||
|
.age, .idcard {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 诊断信息
|
||||||
|
.diagnosis-content,
|
||||||
|
.treatment-content,
|
||||||
|
.decoction-content {
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
.content-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #374151;
|
||||||
|
line-height: 1.6;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处方信息
|
||||||
|
.prescription-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 4px 0;
|
||||||
|
|
||||||
|
.prescription-type {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #059669;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 药品列表
|
||||||
|
.medicine-list {
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
.medicine-item {
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-bottom: 1px solid #f3f4f6;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medicine-main {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.medicine-name {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medicine-price {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.medicine-sub {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.medicine-spec {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.medicine-subtotal {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #9ca3af;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片画廊
|
||||||
|
.image-gallery {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
.image-item {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
padding-bottom: 100%; // 1:1 aspect ratio
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
|
||||||
|
img, image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 费用明细
|
||||||
|
.price-text {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-price-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 0;
|
||||||
|
|
||||||
|
.total-label {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-amount {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 温馨提示
|
||||||
|
.warm-tips {
|
||||||
|
margin: 12px 16px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #fffbeb;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #fef3c7;
|
||||||
|
|
||||||
|
.tips-title {
|
||||||
|
display: block;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #d97706;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tips-item {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #92400e;
|
||||||
|
line-height: 1.8;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 底部操作栏
|
||||||
|
.fixed-bottom-bar {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
background: #fff;
|
||||||
|
border-top: 1px solid #e5e7eb;
|
||||||
|
padding: 12px 16px 24px;
|
||||||
|
z-index: 999;
|
||||||
|
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
|
||||||
|
.bottom-price {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.price-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-value {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #dc2626;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
|
||||||
|
.nut-button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
410
src/clinic/clinicPrescription/confirm.tsx
Normal file
410
src/clinic/clinicPrescription/confirm.tsx
Normal file
@@ -0,0 +1,410 @@
|
|||||||
|
import {useEffect, useState} from "react";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Cell,
|
||||||
|
CellGroup,
|
||||||
|
Avatar,
|
||||||
|
Tag,
|
||||||
|
Image,
|
||||||
|
Space
|
||||||
|
} from '@nutui/nutui-react-taro'
|
||||||
|
import {Edit} from '@nutui/icons-react-taro'
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
|
import Taro from '@tarojs/taro'
|
||||||
|
import FixedButton from "@/components/FixedButton";
|
||||||
|
import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model";
|
||||||
|
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
||||||
|
import {addClinicPrescription} from "@/api/clinic/clinicPrescription";
|
||||||
|
import {addClinicPrescriptionItem} from "@/api/clinic/clinicPrescriptionItem";
|
||||||
|
import './confirm.scss'
|
||||||
|
|
||||||
|
// 订单数据接口
|
||||||
|
interface OrderData {
|
||||||
|
patient: ClinicPatientUser;
|
||||||
|
prescription?: ClinicPrescription;
|
||||||
|
diagnosis: string;
|
||||||
|
treatmentPlan: string;
|
||||||
|
decoctionInstructions?: string;
|
||||||
|
images?: Array<{
|
||||||
|
url: string;
|
||||||
|
name?: string;
|
||||||
|
uid?: string;
|
||||||
|
}>;
|
||||||
|
orderPrice?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DoctorOrderConfirm = () => {
|
||||||
|
const [orderData, setOrderData] = useState<OrderData | null>(null)
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [submitLoading, setSubmitLoading] = useState<boolean>(false)
|
||||||
|
|
||||||
|
// 计算药品总价
|
||||||
|
const getMedicinePrice = () => {
|
||||||
|
if (!orderData?.prescription?.items) return '0.00'
|
||||||
|
const total = orderData.prescription.items.reduce((sum, item) => {
|
||||||
|
const price = parseFloat(item.unitPrice || '0')
|
||||||
|
const quantity = item.quantity || 1
|
||||||
|
return sum + (price * quantity)
|
||||||
|
}, 0)
|
||||||
|
return total.toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算服务费(可根据实际业务调整)
|
||||||
|
const getServiceFee = () => {
|
||||||
|
return '10.00' // 固定服务费10元
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算订单总价
|
||||||
|
const getTotalPrice = () => {
|
||||||
|
const medicinePrice = parseFloat(getMedicinePrice())
|
||||||
|
const serviceFee = parseFloat(getServiceFee())
|
||||||
|
return (medicinePrice + serviceFee).toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取处方类型文本
|
||||||
|
const getPrescriptionType = () => {
|
||||||
|
if (!orderData?.prescription) return ''
|
||||||
|
return orderData.prescription.prescriptionType === 0 ? '中药' : '西药'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回编辑
|
||||||
|
const handleBack = () => {
|
||||||
|
Taro.navigateBack()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认并发送订单
|
||||||
|
const handleConfirmOrder = async () => {
|
||||||
|
if (!orderData) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单数据缺失',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setSubmitLoading(true)
|
||||||
|
|
||||||
|
const doctorId = Taro.getStorageSync('UserId') // 当前医生ID
|
||||||
|
|
||||||
|
// 第一步:创建处方主表记录
|
||||||
|
console.log('开始创建处方记录...')
|
||||||
|
const prescriptionData: ClinicPrescription = {
|
||||||
|
userId: orderData.patient.userId,
|
||||||
|
doctorId: doctorId,
|
||||||
|
prescriptionType: orderData.prescription?.prescriptionType || 0, // 处方类型
|
||||||
|
diagnosis: orderData.diagnosis, // 诊断结果
|
||||||
|
treatmentPlan: orderData.treatmentPlan, // 治疗方案
|
||||||
|
decoctionInstructions: orderData.decoctionInstructions, // 煎药说明
|
||||||
|
image: orderData.images ? JSON.stringify(orderData.images) : '', // 病例图片
|
||||||
|
orderPrice: getTotalPrice(), // 订单总金额
|
||||||
|
price: getMedicinePrice(), // 药品单价
|
||||||
|
payPrice: getTotalPrice(), // 实付金额
|
||||||
|
status: 0, // 状态:0正常
|
||||||
|
isInvalid: 0, // 未失效
|
||||||
|
isSettled: 0, // 未结算
|
||||||
|
comments: `患者:${orderData.patient.realName},年龄:${orderData.patient.age}岁`
|
||||||
|
}
|
||||||
|
|
||||||
|
const createdPrescription = await addClinicPrescription(prescriptionData)
|
||||||
|
console.log('处方创建成功:', createdPrescription)
|
||||||
|
|
||||||
|
if (!createdPrescription || !createdPrescription.id) {
|
||||||
|
throw new Error('处方创建失败,未返回处方ID')
|
||||||
|
}
|
||||||
|
|
||||||
|
const prescriptionId = createdPrescription.id
|
||||||
|
|
||||||
|
// 第二步:创建处方明细记录(药品列表)
|
||||||
|
if (orderData.prescription?.items && orderData.prescription.items.length > 0) {
|
||||||
|
console.log('开始创建处方明细...')
|
||||||
|
for (const item of orderData.prescription.items) {
|
||||||
|
const prescriptionItemData = {
|
||||||
|
prescriptionId: prescriptionId, // 关联处方ID
|
||||||
|
prescriptionNo: createdPrescription.orderNo, // 处方编号
|
||||||
|
medicineId: item.medicineId,
|
||||||
|
medicineName: item.medicineName,
|
||||||
|
specification: item.specification,
|
||||||
|
dosage: item.dosage,
|
||||||
|
usageFrequency: item.usageFrequency,
|
||||||
|
days: item.days,
|
||||||
|
amount: item.amount,
|
||||||
|
unitPrice: item.unitPrice,
|
||||||
|
quantity: item.quantity,
|
||||||
|
userId: orderData.patient.userId,
|
||||||
|
comments: item.comments
|
||||||
|
}
|
||||||
|
await addClinicPrescriptionItem(prescriptionItemData)
|
||||||
|
}
|
||||||
|
console.log('处方明细创建成功')
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('处方创建完成,处方ID:', prescriptionId)
|
||||||
|
|
||||||
|
// 清除临时数据
|
||||||
|
Taro.removeStorageSync('tempOrderData')
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: '处方已发送给患者',
|
||||||
|
icon: 'success',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
// 跳转到订单列表
|
||||||
|
Taro.redirectTo({
|
||||||
|
url: '/clinic/clinicPrescription/index'
|
||||||
|
})
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('创建处方/订单失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: error.message || '发送失败,请重试',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setSubmitLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
|
||||||
|
// 从本地存储获取订单数据
|
||||||
|
const tempData = Taro.getStorageSync('tempOrderData')
|
||||||
|
|
||||||
|
if (!tempData) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '订单数据缺失,请重新填写',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
Taro.navigateBack()
|
||||||
|
}, 1500)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedData = JSON.parse(tempData)
|
||||||
|
console.log('订单确认页获取数据:', parsedData)
|
||||||
|
|
||||||
|
setOrderData(parsedData)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析订单数据失败:', error)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '数据解析失败',
|
||||||
|
icon: 'error'
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (loading || !orderData) {
|
||||||
|
return (
|
||||||
|
<View className="order-confirm-loading">
|
||||||
|
<Text>加载中...</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* 页面标题提示 */}
|
||||||
|
|
||||||
|
{/* 患者信息 */}
|
||||||
|
<CellGroup>
|
||||||
|
<View className={'p-3'}>患者信息</View>
|
||||||
|
<Cell>
|
||||||
|
<Space>
|
||||||
|
<Avatar
|
||||||
|
src={orderData.patient.avatar}
|
||||||
|
size="large"
|
||||||
|
/>
|
||||||
|
<View className="flex flex-col gap-1">
|
||||||
|
<Text className="font-medium">{orderData.patient.realName}</Text>
|
||||||
|
<Text className="text-gray-500">{orderData.patient.phone}</Text>
|
||||||
|
<Space className="patient-extra">
|
||||||
|
<Text className="text-gray-500">{orderData.patient.age}岁</Text>
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
</Space>
|
||||||
|
</Cell>
|
||||||
|
</CellGroup>
|
||||||
|
|
||||||
|
{/* 诊断信息 */}
|
||||||
|
<CellGroup>
|
||||||
|
<View className={'p-3'}>诊断信息</View>
|
||||||
|
<Cell
|
||||||
|
extra={
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
icon={<Edit size="12"/>}
|
||||||
|
onClick={handleBack}
|
||||||
|
>
|
||||||
|
修改
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<View className="text-gray-500">
|
||||||
|
<Text>{orderData.diagnosis}</Text>
|
||||||
|
</View>
|
||||||
|
</Cell>
|
||||||
|
<View className={'p-3'}>治疗方案</View>
|
||||||
|
<Cell>
|
||||||
|
<View className={'text-gray-500'}>
|
||||||
|
<Text>{orderData.treatmentPlan}</Text>
|
||||||
|
</View>
|
||||||
|
</Cell>
|
||||||
|
</CellGroup>
|
||||||
|
|
||||||
|
{/* 处方信息 */}
|
||||||
|
{orderData.prescription && (
|
||||||
|
<CellGroup>
|
||||||
|
<View className={'p-3'}>处方信息</View>
|
||||||
|
<Cell>
|
||||||
|
<Space>
|
||||||
|
<Text className="text-gray-500">
|
||||||
|
RP: {getPrescriptionType()}
|
||||||
|
</Text>
|
||||||
|
<Tag type="success">
|
||||||
|
共 {orderData.prescription.items?.length || 0} 味药
|
||||||
|
</Tag>
|
||||||
|
</Space>
|
||||||
|
</Cell>
|
||||||
|
|
||||||
|
{/* 药品列表 */}
|
||||||
|
{orderData.prescription.items?.map((item, index) => (
|
||||||
|
<Cell key={index}>
|
||||||
|
<View className={'flex justify-between w-full text-gray-500'}>
|
||||||
|
<Space>
|
||||||
|
<Text className="medicine-name">{item.medicineName}</Text>
|
||||||
|
<Text className="medicine-price">¥{item.unitPrice}</Text>
|
||||||
|
{!!item.specification && (
|
||||||
|
<Text className="medicine-spec">
|
||||||
|
{item.specification || '规格未知'} × {item.quantity || 1}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
<View className="medicine-sub">
|
||||||
|
<Text className="medicine-subtotal">
|
||||||
|
小计:¥{(parseFloat(item.unitPrice || '0') * (item.quantity || 1)).toFixed(2)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Cell>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{/* 煎药说明 */}
|
||||||
|
{orderData.decoctionInstructions && (
|
||||||
|
<>
|
||||||
|
<View className={'p-3'}>煎药说明</View>
|
||||||
|
<Cell>
|
||||||
|
<Text className={'text-gray-500'}>{orderData.decoctionInstructions}</Text>
|
||||||
|
</Cell>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CellGroup>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 上传的图片 */}
|
||||||
|
{orderData.images && orderData.images.length > 0 && (
|
||||||
|
<CellGroup title="病例图片" className="section-group">
|
||||||
|
<Cell>
|
||||||
|
<View className="image-gallery">
|
||||||
|
{orderData.images.map((image, index) => (
|
||||||
|
<View
|
||||||
|
key={image.uid || index}
|
||||||
|
className="image-item"
|
||||||
|
onClick={() => {
|
||||||
|
// 预览图片
|
||||||
|
Taro.previewImage({
|
||||||
|
urls: orderData.images!.map(img => img.url),
|
||||||
|
current: image.url
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
src={image.url}
|
||||||
|
mode="aspectFill"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
</Cell>
|
||||||
|
</CellGroup>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 费用明细 */}
|
||||||
|
<CellGroup>
|
||||||
|
<View className={'p-3'}>费用明细</View>
|
||||||
|
<Cell
|
||||||
|
title={<Text className="text-gray-500">药品费用</Text>}
|
||||||
|
extra={<Text className="price-text">¥{getMedicinePrice()}</Text>}
|
||||||
|
/>
|
||||||
|
<Cell
|
||||||
|
title={<Text className="text-gray-500">服务费</Text>}
|
||||||
|
extra={<Text className="price-text">¥{getServiceFee()}</Text>}
|
||||||
|
/>
|
||||||
|
<Cell extra={
|
||||||
|
(
|
||||||
|
<Space className="total-price-row">
|
||||||
|
<Text className="total-label">订单总计</Text>
|
||||||
|
<Text className="total-amount">¥{getTotalPrice()}</Text>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}>
|
||||||
|
</Cell>
|
||||||
|
</CellGroup>
|
||||||
|
|
||||||
|
{/* 温馨提示 */}
|
||||||
|
<CellGroup>
|
||||||
|
<View className={'p-3'}>📌 温馨提示</View>
|
||||||
|
<View className={'flex flex-col px-3 pb-5 text-gray-500 text-sm'}>
|
||||||
|
<Text>• 请仔细核对订单信息</Text>
|
||||||
|
<Text>• 处方发送后,患者将收到支付通知</Text>
|
||||||
|
<Text>• 患者支付成功后,订单将自动流转至配药环节</Text>
|
||||||
|
<Text>• 如需修改处方信息,请点击对应模块的"修改"按钮</Text>
|
||||||
|
</View>
|
||||||
|
</CellGroup>
|
||||||
|
|
||||||
|
{/* 底部操作按钮 */}
|
||||||
|
<FixedButton
|
||||||
|
text={submitLoading ? '发送中...' : '确认并发送给患者'}
|
||||||
|
icon={<Edit/>}
|
||||||
|
onClick={handleConfirmOrder}
|
||||||
|
/>
|
||||||
|
<View className="fixed-bottom-bar">
|
||||||
|
<View className="bottom-price">
|
||||||
|
<Text className="price-label">订单总计:</Text>
|
||||||
|
<Text className="price-value">¥{getTotalPrice()}</Text>
|
||||||
|
</View>
|
||||||
|
<View className="bottom-actions">
|
||||||
|
<Button
|
||||||
|
size="large"
|
||||||
|
fill="outline"
|
||||||
|
onClick={handleBack}
|
||||||
|
disabled={submitLoading}
|
||||||
|
>
|
||||||
|
返回修改
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
onClick={handleConfirmOrder}
|
||||||
|
loading={submitLoading}
|
||||||
|
disabled={submitLoading}
|
||||||
|
style={{flex: 1}}
|
||||||
|
>
|
||||||
|
{submitLoading ? '发送中...' : '确认并发送给患者'}
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DoctorOrderConfirm
|
||||||
6
src/clinic/clinicPrescription/detail.config.ts
Normal file
6
src/clinic/clinicPrescription/detail.config.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export default {
|
||||||
|
navigationBarTitleText: '开方详情',
|
||||||
|
navigationBarBackgroundColor: '#fff',
|
||||||
|
navigationBarTextStyle: 'black',
|
||||||
|
backgroundColor: '#f5f5f5'
|
||||||
|
}
|
||||||
190
src/clinic/clinicPrescription/detail.tsx
Normal file
190
src/clinic/clinicPrescription/detail.tsx
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
import {useState} from "react";
|
||||||
|
import Taro, {useDidShow} from '@tarojs/taro'
|
||||||
|
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Tag} from '@nutui/nutui-react-taro'
|
||||||
|
import {Del, Edit} from '@nutui/icons-react-taro'
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
|
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
||||||
|
import {
|
||||||
|
pageClinicPrescription,
|
||||||
|
removeClinicPrescription
|
||||||
|
} from "@/api/clinic/clinicPrescription";
|
||||||
|
import FixedButton from "@/components/FixedButton";
|
||||||
|
import {copyText} from "@/utils/common";
|
||||||
|
|
||||||
|
const ClinicPrescriptionList = () => {
|
||||||
|
const [list, setList] = useState<ClinicPrescription[]>([])
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const reload = () => {
|
||||||
|
setLoading(true)
|
||||||
|
pageClinicPrescription({
|
||||||
|
// 添加查询条件
|
||||||
|
doctorId: Taro.getStorageSync('UserId'),
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
setList(data?.list || [])
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '获取数据失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDel = async (item: ClinicPrescription) => {
|
||||||
|
const res = await Taro.showModal({
|
||||||
|
title: '确认删除',
|
||||||
|
content: `确定要删除处方编号「${item.orderNo}」吗?`,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.confirm) {
|
||||||
|
try {
|
||||||
|
await removeClinicPrescription(item.id)
|
||||||
|
Taro.showToast({
|
||||||
|
title: '删除成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
reload();
|
||||||
|
} catch (error) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '删除失败',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onEdit = (item: ClinicPrescription) => {
|
||||||
|
Taro.navigateTo({
|
||||||
|
url: `/clinic/clinicPrescription/add?id=${item.id}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const getSexName = (sex?: number) => {
|
||||||
|
return sex === 0 ? '男' : sex === 1 ? '女' : ''
|
||||||
|
}
|
||||||
|
|
||||||
|
useDidShow(() => {
|
||||||
|
reload()
|
||||||
|
});
|
||||||
|
|
||||||
|
if (list.length === 0 && !loading) {
|
||||||
|
return (
|
||||||
|
<ConfigProvider>
|
||||||
|
<View className={'h-full flex flex-col justify-center items-center'} style={{
|
||||||
|
height: 'calc(100vh - 300px)',
|
||||||
|
}}>
|
||||||
|
<Empty
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'transparent'
|
||||||
|
}}
|
||||||
|
description="暂无处方数据"
|
||||||
|
/>
|
||||||
|
<Space style={{marginTop: '20px'}}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => Taro.navigateTo({url: '/clinic/clinicPrescription/add'})}
|
||||||
|
>
|
||||||
|
新增处方
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
</ConfigProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<View className="p-3">
|
||||||
|
{list.map((item) => (
|
||||||
|
<CellGroup key={item.id} className="mb-3">
|
||||||
|
<Cell
|
||||||
|
title={item.orderNo}
|
||||||
|
extra={
|
||||||
|
<Tag type={'warning'} className="font-medium">待支付</Tag>
|
||||||
|
}
|
||||||
|
onClick={() => copyText(`${item.orderNo}`)}
|
||||||
|
/>
|
||||||
|
<Cell
|
||||||
|
title={'患者名称'}
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Text className="font-medium">{item.realName}</Text>
|
||||||
|
<Text className="font-medium">{item.age}岁</Text>
|
||||||
|
<Text className="font-medium">{getSexName(item.sex)}</Text>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{/*<Cell*/}
|
||||||
|
{/* title="处方类型"*/}
|
||||||
|
{/* extra={*/}
|
||||||
|
{/* <Tag type="info">*/}
|
||||||
|
{/* {getPrescriptionTypeText(item.prescriptionType)}*/}
|
||||||
|
{/* </Tag>*/}
|
||||||
|
{/* }*/}
|
||||||
|
{/*/>*/}
|
||||||
|
{item.diagnosis && (
|
||||||
|
<Cell
|
||||||
|
title="诊断结果"
|
||||||
|
extra={
|
||||||
|
<Text className="text-gray-600 text-sm">
|
||||||
|
{item.diagnosis.length > 20
|
||||||
|
? `${item.diagnosis.substring(0, 20)}...`
|
||||||
|
: item.diagnosis}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<Cell
|
||||||
|
title="订单金额"
|
||||||
|
extra={
|
||||||
|
<Text className="text-red-500 font-medium">
|
||||||
|
¥{item.orderPrice || '0.00'}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Cell
|
||||||
|
title="创建时间"
|
||||||
|
extra={
|
||||||
|
<Text className="text-gray-500 text-xs">
|
||||||
|
{item.createTime}
|
||||||
|
</Text>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Cell>
|
||||||
|
<Space className="w-full justify-end">
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
icon={<Edit/>}
|
||||||
|
onClick={() => onEdit(item)}
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
icon={<Del/>}
|
||||||
|
onClick={() => onDel(item)}
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Cell>
|
||||||
|
</CellGroup>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<FixedButton
|
||||||
|
text="开处方"
|
||||||
|
onClick={() => Taro.navigateTo({url: '/clinic/clinicPrescription/add'})}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClinicPrescriptionList;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '处方主表
|
navigationBarTitleText: '处方管理',
|
||||||
管理',
|
|
||||||
navigationBarTextStyle: 'black'
|
navigationBarTextStyle: 'black'
|
||||||
})
|
})
|
||||||
|
|||||||
72
src/clinic/clinicPrescription/index.scss
Normal file
72
src/clinic/clinicPrescription/index.scss
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
page {
|
||||||
|
background: linear-gradient(to bottom, #f3f3f3, #f9fafb);
|
||||||
|
background-size: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-container {
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
.nut-input {
|
||||||
|
background-color: #f8f9fa !important;
|
||||||
|
border: 1px solid #e5e5e5 !important;
|
||||||
|
border-radius: 4px !important;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
border-color: #007bff !important;
|
||||||
|
box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nut-button {
|
||||||
|
border-radius: 4px !important;
|
||||||
|
|
||||||
|
&--primary {
|
||||||
|
background: linear-gradient(135deg, #007bff, #0056b3) !important;
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--small {
|
||||||
|
padding: 6px 12px !important;
|
||||||
|
font-size: 12px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tabs样式优化
|
||||||
|
.nut-tabs {
|
||||||
|
.nut-tabs__titles {
|
||||||
|
background: #ffffff !important;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
|
||||||
|
|
||||||
|
.nut-tabs__titles-item {
|
||||||
|
font-size: 14px !important;
|
||||||
|
font-weight: 500 !important;
|
||||||
|
|
||||||
|
&--active {
|
||||||
|
color: #007bff !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.nut-tabs__line {
|
||||||
|
background: #007bff !important;
|
||||||
|
height: 3px !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 筛选提示样式
|
||||||
|
.filter-tip {
|
||||||
|
animation: slideDown 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,65 +1,113 @@
|
|||||||
import {useState} from "react";
|
import {useState, useCallback, useRef, useEffect} from "react";
|
||||||
import Taro, {useDidShow} from '@tarojs/taro'
|
import {Space, Button, Input} from '@nutui/nutui-react-taro'
|
||||||
import {Button, Cell, CellGroup, Space, Empty, ConfigProvider, Divider} from '@nutui/nutui-react-taro'
|
import {View} from '@tarojs/components';
|
||||||
import {Dongdong, ArrowRight, CheckNormal, Checked} from '@nutui/icons-react-taro'
|
import OrderList from "./components/OrderList";
|
||||||
import {View} from '@tarojs/components'
|
import {useRouter} from '@tarojs/taro'
|
||||||
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
import {ShopOrderParam} from "@/api/shop/shopOrder/model";
|
||||||
import {listClinicPrescription, removeClinicPrescription, updateClinicPrescription} from "@/api/clinic/clinicPrescription";
|
import './index.scss'
|
||||||
|
|
||||||
const ClinicPrescriptionList = () => {
|
function ClinicPrescriptionList() {
|
||||||
const [list, setList] = useState<ClinicPrescription[]>([])
|
const {params} = useRouter();
|
||||||
|
const [searchParams, setSearchParams] = useState<ShopOrderParam>({
|
||||||
|
statusFilter: params.statusFilter != undefined && params.statusFilter != '' ? parseInt(params.statusFilter) : -1
|
||||||
|
})
|
||||||
|
const [showSearch] = useState(false)
|
||||||
|
const [searchKeyword, setSearchKeyword] = useState('')
|
||||||
|
const searchTimeoutRef = useRef<NodeJS.Timeout>()
|
||||||
|
|
||||||
const reload = () => {
|
const reload = async (where?: ShopOrderParam) => {
|
||||||
listClinicPrescription({
|
console.log(where,'where...')
|
||||||
// 添加查询条件
|
setSearchParams(prev => ({ ...prev, ...where }))
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
setList(data || [])
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
Taro.showToast({
|
|
||||||
title: '获取数据失败',
|
|
||||||
icon: 'error'
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 防抖搜索函数
|
||||||
const onDel = async (id?: number) => {
|
const debouncedSearch = useCallback((keyword: string) => {
|
||||||
await removeClinicPrescription(id)
|
if (searchTimeoutRef.current) {
|
||||||
Taro.showToast({
|
clearTimeout(searchTimeoutRef.current);
|
||||||
title: '删除成功',
|
|
||||||
icon: 'success'
|
|
||||||
});
|
|
||||||
reload();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
useDidShow(() => {
|
searchTimeoutRef.current = setTimeout(() => {
|
||||||
reload()
|
if (keyword.trim()) {
|
||||||
});
|
handleSearch({keywords: keyword.trim()});
|
||||||
|
} else {
|
||||||
|
// 如果搜索关键词为空,清除keywords参数
|
||||||
|
const newSearchParams = { ...searchParams };
|
||||||
|
delete newSearchParams.keywords;
|
||||||
|
setSearchParams(newSearchParams);
|
||||||
|
reload(newSearchParams).then();
|
||||||
|
}
|
||||||
|
}, 500); // 500ms防抖延迟
|
||||||
|
}, [searchParams]);
|
||||||
|
|
||||||
|
// 处理搜索
|
||||||
|
const handleSearch = (where: ShopOrderParam) => {
|
||||||
|
// 合并搜索参数,保留当前的statusFilter
|
||||||
|
const newSearchParams = {
|
||||||
|
...searchParams, // 保留当前的所有参数(包括statusFilter)
|
||||||
|
...where // 应用新的搜索条件
|
||||||
|
};
|
||||||
|
setSearchParams(newSearchParams)
|
||||||
|
reload(newSearchParams).then()
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
reload().then()
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (list.length == 0) {
|
|
||||||
return (
|
return (
|
||||||
<ConfigProvider>
|
<View className="bg-gray-50 min-h-screen">
|
||||||
<div className={'h-full flex flex-col justify-center items-center'} style={{
|
{/* 搜索组件 */}
|
||||||
height: 'calc(100vh - 300px)',
|
{showSearch && (
|
||||||
}}>
|
<View className="bg-white p-3 shadow-sm border-b border-gray-100">
|
||||||
<Empty
|
<View className="flex items-center">
|
||||||
style={{
|
<View className="flex-1 mr-2">
|
||||||
backgroundColor: 'transparent'
|
<Input
|
||||||
|
placeholder="搜索订单号、商品名称"
|
||||||
|
value={searchKeyword}
|
||||||
|
onChange={(value) => {
|
||||||
|
setSearchKeyword(value);
|
||||||
|
debouncedSearch(value); // 使用防抖搜索
|
||||||
|
}}
|
||||||
|
onConfirm={() => {
|
||||||
|
if (searchKeyword.trim()) {
|
||||||
|
handleSearch({keywords: searchKeyword.trim()});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
padding: '8px 12px',
|
||||||
|
border: '1px solid #e5e5e5',
|
||||||
|
borderRadius: '4px',
|
||||||
|
backgroundColor: '#f8f9fa'
|
||||||
}}
|
}}
|
||||||
description="暂无数据"
|
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
<Space>
|
<Space>
|
||||||
<Button onClick={() => Taro.navigateTo({url: '/clinic/clinicPrescription/add'})}>新增处方主表
|
<Button
|
||||||
|
type="primary"
|
||||||
|
onClick={() => {
|
||||||
|
if (searchKeyword.trim()) {
|
||||||
|
handleSearch({keywords: searchKeyword.trim()});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
搜索
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</View>
|
||||||
</ConfigProvider>
|
</View>
|
||||||
)
|
)}
|
||||||
|
|
||||||
|
{/*订单列表*/}
|
||||||
|
<OrderList
|
||||||
|
onReload={() => reload(searchParams)}
|
||||||
|
searchParams={searchParams}
|
||||||
|
showSearch={showSearch}
|
||||||
|
onSearchParamsChange={(newParams) => {
|
||||||
|
console.log('父组件接收到searchParams变化:', newParams);
|
||||||
|
setSearchParams(newParams);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
export default ClinicPrescriptionList;
|
||||||
<>
|
|
||||||
{list.map((item, _) => (
|
|
||||||
<Cell.Group key={item.
|
|
||||||
|
|||||||
4
src/clinic/clinicPrescription/selectPatient.config.ts
Normal file
4
src/clinic/clinicPrescription/selectPatient.config.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '选择患者',
|
||||||
|
navigationBarTextStyle: 'black'
|
||||||
|
})
|
||||||
285
src/clinic/clinicPrescription/selectPatient.tsx
Normal file
285
src/clinic/clinicPrescription/selectPatient.tsx
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
import {useState, useEffect, useCallback} from 'react'
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
|
import Taro, {useDidShow} from '@tarojs/taro'
|
||||||
|
import {Loading, InfiniteLoading, Empty, Space, SearchBar, Button} from '@nutui/nutui-react-taro'
|
||||||
|
import {Phone} from '@nutui/icons-react-taro'
|
||||||
|
import type {ClinicPatientUser as PatientUserType} from "@/api/clinic/clinicPatientUser/model";
|
||||||
|
import {
|
||||||
|
pageClinicPatientUser
|
||||||
|
} from "@/api/clinic/clinicPatientUser";
|
||||||
|
|
||||||
|
// 患者类型
|
||||||
|
interface PatientUser extends PatientUserType {
|
||||||
|
}
|
||||||
|
|
||||||
|
const SelectPatient = () => {
|
||||||
|
const [list, setList] = useState<PatientUser[]>([])
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [searchValue, setSearchValue] = useState<string>('')
|
||||||
|
const [displaySearchValue, setDisplaySearchValue] = useState<string>('')
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [hasMore, setHasMore] = useState(true)
|
||||||
|
|
||||||
|
// 复制手机号
|
||||||
|
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 fetchPatientData = useCallback(async (resetPage = false, targetPage?: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const currentPage = resetPage ? 1 : (targetPage || page);
|
||||||
|
|
||||||
|
// 构建API参数
|
||||||
|
const params: any = {
|
||||||
|
page: currentPage
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加搜索关键词
|
||||||
|
if (displaySearchValue.trim()) {
|
||||||
|
params.keywords = displaySearchValue.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await pageClinicPatientUser(params);
|
||||||
|
|
||||||
|
if (res?.list && res.list.length > 0) {
|
||||||
|
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
|
||||||
|
if (resetPage || currentPage === 1) {
|
||||||
|
setList(res.list);
|
||||||
|
} else {
|
||||||
|
setList(prevList => [...prevList, ...res.list]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正确判断是否还有更多数据
|
||||||
|
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, displaySearchValue]);
|
||||||
|
|
||||||
|
const reloadMore = async () => {
|
||||||
|
if (loading || !hasMore) return; // 防止重复加载
|
||||||
|
const nextPage = page + 1;
|
||||||
|
await fetchPatientData(false, nextPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖搜索功能
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDisplaySearchValue(searchValue);
|
||||||
|
}, 300); // 300ms 防抖
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchValue]);
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPatientData(true).then();
|
||||||
|
}, [displaySearchValue]);
|
||||||
|
|
||||||
|
// 监听页面显示,当从其他页面返回时刷新数据
|
||||||
|
useDidShow(() => {
|
||||||
|
// 刷新数据
|
||||||
|
setList([]);
|
||||||
|
setPage(1);
|
||||||
|
setHasMore(true);
|
||||||
|
fetchPatientData(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 选择患者
|
||||||
|
const selectPatient = (patient: PatientUser) => {
|
||||||
|
// 将选中的患者信息传递回上一个页面
|
||||||
|
const pages = Taro.getCurrentPages();
|
||||||
|
if (pages.length > 1) {
|
||||||
|
const prevPage = pages[pages.length - 2];
|
||||||
|
// @ts-ignore
|
||||||
|
if (prevPage && typeof prevPage.setSelectedPatient === 'function') {
|
||||||
|
// @ts-ignore
|
||||||
|
prevPage.setSelectedPatient(patient);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同时存储到本地存储,作为备选方案
|
||||||
|
try {
|
||||||
|
Taro.setStorageSync('selectedPatient', JSON.stringify(patient));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('存储患者信息失败:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.navigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染患者项
|
||||||
|
const renderPatientItem = (patient: PatientUser) => (
|
||||||
|
<View key={patient.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
||||||
|
<View className="flex items-center mb-3">
|
||||||
|
<View className="flex-1">
|
||||||
|
<View className="flex items-center justify-between mb-1">
|
||||||
|
<Text className="font-semibold text-gray-800 mr-2">
|
||||||
|
{patient.realName || '未命名'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View className="flex items-center mb-1">
|
||||||
|
<Space direction="vertical">
|
||||||
|
<View className="flex items-center">
|
||||||
|
<Text className="text-xs text-gray-500" onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
makePhoneCall(patient.phone || '');
|
||||||
|
}}>联系电话:{patient.phone || '未提供'}</Text>
|
||||||
|
<View className="flex items-center ml-2">
|
||||||
|
<Phone
|
||||||
|
size={12}
|
||||||
|
className="text-green-500 mr-2"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
makePhoneCall(patient.phone || '');
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Text
|
||||||
|
className="text-xs text-blue-500 cursor-pointer"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
copyPhone(patient.phone || '');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
复制
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Text className="text-xs text-gray-500">
|
||||||
|
添加时间:{patient.createTime || '未知'}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 显示 comments 字段 */}
|
||||||
|
<View className="flex items-center">
|
||||||
|
<Text className="text-xs text-gray-500">备注:{patient.comments || '暂无'}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 选择按钮 */}
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => selectPatient(patient)}
|
||||||
|
style={{backgroundColor: '#1890ff', color: 'white'}}
|
||||||
|
>
|
||||||
|
选择此患者
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
// 渲染患者列表
|
||||||
|
const renderPatientList = () => {
|
||||||
|
const isSearching = displaySearchValue.trim().length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="flex-1">
|
||||||
|
{/* 搜索结果统计 */}
|
||||||
|
{isSearching && (
|
||||||
|
<View className="bg-white px-4 py-2 border-b border-gray-100">
|
||||||
|
<Text className="text-sm text-gray-600">
|
||||||
|
搜索 "{displaySearchValue}" 的结果,共找到 {list.length} 条记录
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View className="p-4" style={{
|
||||||
|
height: isSearching ? 'calc(90vh - 40px)' : '90vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden'
|
||||||
|
}}>
|
||||||
|
<InfiniteLoading
|
||||||
|
target="scroll"
|
||||||
|
hasMore={hasMore}
|
||||||
|
onLoadMore={reloadMore}
|
||||||
|
loadingText="加载中..."
|
||||||
|
loadMoreText={
|
||||||
|
list.length === 0 ? (
|
||||||
|
<Empty
|
||||||
|
style={{backgroundColor: 'transparent'}}
|
||||||
|
description={loading ? "加载中..." : "暂无患者数据"}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<View className={'h-3 flex items-center justify-center'}>
|
||||||
|
<Text className="text-gray-500 text-sm">没有更多了</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{loading && list.length === 0 ? (
|
||||||
|
<View className="flex items-center justify-center py-8">
|
||||||
|
<Loading/>
|
||||||
|
<Text className="text-gray-500 mt-2 ml-2">加载中...</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
list.map(renderPatientItem)
|
||||||
|
)}
|
||||||
|
</InfiniteLoading>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="min-h-screen bg-gray-50">
|
||||||
|
{/* 搜索栏 */}
|
||||||
|
<View className="bg-white py-2 border-b border-gray-100">
|
||||||
|
<SearchBar
|
||||||
|
value={searchValue}
|
||||||
|
placeholder="搜索患者姓名、手机号"
|
||||||
|
onChange={(value) => setSearchValue(value)}
|
||||||
|
onClear={() => {
|
||||||
|
setSearchValue('');
|
||||||
|
setDisplaySearchValue('');
|
||||||
|
}}
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 患者列表 */}
|
||||||
|
{renderPatientList()}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectPatient;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export default definePageConfig({
|
||||||
|
navigationBarTitleText: '选择处方',
|
||||||
|
navigationBarTextStyle: 'black'
|
||||||
|
})
|
||||||
237
src/clinic/clinicPrescription/selectPrescription.tsx
Normal file
237
src/clinic/clinicPrescription/selectPrescription.tsx
Normal file
@@ -0,0 +1,237 @@
|
|||||||
|
import {useState, useEffect, useCallback} from 'react'
|
||||||
|
import {View, Text} from '@tarojs/components'
|
||||||
|
import Taro, {useDidShow} from '@tarojs/taro'
|
||||||
|
import {Loading, InfiniteLoading, Empty, Space, SearchBar, Button} from '@nutui/nutui-react-taro'
|
||||||
|
import {
|
||||||
|
pageClinicPrescription
|
||||||
|
} from "@/api/clinic/clinicPrescription";
|
||||||
|
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
||||||
|
|
||||||
|
const SelectPrescription = () => {
|
||||||
|
const [list, setList] = useState<ClinicPrescription[]>([])
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
const [searchValue, setSearchValue] = useState<string>('')
|
||||||
|
const [displaySearchValue, setDisplaySearchValue] = useState<string>('')
|
||||||
|
const [page, setPage] = useState(1)
|
||||||
|
const [hasMore, setHasMore] = useState(true)
|
||||||
|
|
||||||
|
// 获取处方数据
|
||||||
|
const fetchPrescriptionData = useCallback(async (resetPage = false, targetPage?: number) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const currentPage = resetPage ? 1 : (targetPage || page);
|
||||||
|
|
||||||
|
// 构建API参数
|
||||||
|
const params: any = {
|
||||||
|
page: currentPage
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加搜索关键词
|
||||||
|
if (displaySearchValue.trim()) {
|
||||||
|
params.keywords = displaySearchValue.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await pageClinicPrescription(params);
|
||||||
|
|
||||||
|
if (res?.list && res.list.length > 0) {
|
||||||
|
// 如果是重置页面或第一页,直接设置新数据;否则追加数据
|
||||||
|
if (resetPage || currentPage === 1) {
|
||||||
|
setList(res.list);
|
||||||
|
} else {
|
||||||
|
setList(prevList => [...prevList, ...res.list]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正确判断是否还有更多数据
|
||||||
|
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, displaySearchValue]);
|
||||||
|
|
||||||
|
const reloadMore = async () => {
|
||||||
|
if (loading || !hasMore) return; // 防止重复加载
|
||||||
|
const nextPage = page + 1;
|
||||||
|
await fetchPrescriptionData(false, nextPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防抖搜索功能
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setDisplaySearchValue(searchValue);
|
||||||
|
}, 300); // 300ms 防抖
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, [searchValue]);
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPrescriptionData(true).then();
|
||||||
|
}, [displaySearchValue]);
|
||||||
|
|
||||||
|
// 监听页面显示,当从其他页面返回时刷新数据
|
||||||
|
useDidShow(() => {
|
||||||
|
// 刷新数据
|
||||||
|
setList([]);
|
||||||
|
setPage(1);
|
||||||
|
setHasMore(true);
|
||||||
|
fetchPrescriptionData(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 选择处方
|
||||||
|
const selectPrescription = (prescription: ClinicPrescription) => {
|
||||||
|
// 将选中的处方信息传递回上一个页面
|
||||||
|
const pages = Taro.getCurrentPages();
|
||||||
|
if (pages.length > 1) {
|
||||||
|
const prevPage = pages[pages.length - 2];
|
||||||
|
// @ts-ignore
|
||||||
|
if (prevPage && typeof prevPage.setSelectedPrescription === 'function') {
|
||||||
|
// @ts-ignore
|
||||||
|
prevPage.setSelectedPrescription(prescription);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 同时存储到处方存储,作为备选方案
|
||||||
|
try {
|
||||||
|
Taro.setStorageSync('selectedPrescription', JSON.stringify(prescription));
|
||||||
|
} catch (e) {
|
||||||
|
console.error('存储处方信息失败:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Taro.navigateBack();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 渲染处方项
|
||||||
|
const renderPrescriptionItem = (prescription: ClinicPrescription) => (
|
||||||
|
<View key={prescription.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
||||||
|
<View className="flex items-center mb-3">
|
||||||
|
<View className="flex-1">
|
||||||
|
<View className="flex items-center justify-between mb-1">
|
||||||
|
<Text className="font-semibold text-gray-800 mr-2">
|
||||||
|
处方编号: {prescription.orderNo || '无编号'}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View className="flex items-center mb-1">
|
||||||
|
<Space direction="vertical">
|
||||||
|
<Text className="text-xs text-gray-500">
|
||||||
|
处方名称: {prescription.treatmentPlan}
|
||||||
|
</Text>
|
||||||
|
<Text className="text-xs text-gray-500">
|
||||||
|
处方类型: {prescription.prescriptionType === 0 ? '中药' : prescription.prescriptionType === 1 ? '西药' : '未知'}
|
||||||
|
</Text>
|
||||||
|
{/*<Text className="text-xs text-gray-500">*/}
|
||||||
|
{/* 诊断结果: {prescription.diagnosis || '无'}*/}
|
||||||
|
{/*</Text>*/}
|
||||||
|
<Text className="text-xs text-gray-500">
|
||||||
|
创建时间: {prescription.createTime || '未知'}
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 显示备注字段 */}
|
||||||
|
<View className="flex items-center">
|
||||||
|
<Text className="text-xs text-gray-500">备注: {prescription.comments || '暂无'}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 选择按钮 */}
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => selectPrescription(prescription)}
|
||||||
|
style={{backgroundColor: '#1890ff', color: 'white'}}
|
||||||
|
>
|
||||||
|
选择此处方
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
// 渲染处方列表
|
||||||
|
const renderPrescriptionList = () => {
|
||||||
|
const isSearching = displaySearchValue.trim().length > 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="flex-1">
|
||||||
|
{/* 搜索结果统计 */}
|
||||||
|
{isSearching && (
|
||||||
|
<View className="bg-white px-4 py-2 border-b border-gray-100">
|
||||||
|
<Text className="text-sm text-gray-600">
|
||||||
|
搜索 "{displaySearchValue}" 的结果,共找到 {list.length} 条记录
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View className="p-4" style={{
|
||||||
|
height: isSearching ? 'calc(90vh - 40px)' : '90vh',
|
||||||
|
overflowY: 'auto',
|
||||||
|
overflowX: 'hidden'
|
||||||
|
}}>
|
||||||
|
<InfiniteLoading
|
||||||
|
target="scroll"
|
||||||
|
hasMore={hasMore}
|
||||||
|
onLoadMore={reloadMore}
|
||||||
|
loadingText="加载中..."
|
||||||
|
loadMoreText={
|
||||||
|
list.length === 0 ? (
|
||||||
|
<Empty
|
||||||
|
style={{backgroundColor: 'transparent'}}
|
||||||
|
description={loading ? "加载中..." : "暂无处方数据"}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<View className={'h-3 flex items-center justify-center'}>
|
||||||
|
<Text className="text-gray-500 text-sm">没有更多了</Text>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{loading && list.length === 0 ? (
|
||||||
|
<View className="flex items-center justify-center py-8">
|
||||||
|
<Loading/>
|
||||||
|
<Text className="text-gray-500 mt-2 ml-2">加载中...</Text>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
list.map(renderPrescriptionItem)
|
||||||
|
)}
|
||||||
|
</InfiniteLoading>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="min-h-screen bg-gray-50">
|
||||||
|
{/* 搜索栏 */}
|
||||||
|
<View className="bg-white py-2 border-b border-gray-100">
|
||||||
|
<SearchBar
|
||||||
|
value={searchValue}
|
||||||
|
placeholder="搜索处方编号、诊断结果"
|
||||||
|
onChange={(value) => setSearchValue(value)}
|
||||||
|
onClear={() => {
|
||||||
|
setSearchValue('');
|
||||||
|
setDisplaySearchValue('');
|
||||||
|
}}
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* 处方列表 */}
|
||||||
|
{renderPrescriptionList()}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SelectPrescription;
|
||||||
@@ -147,7 +147,7 @@ const DealerIndex: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
</Grid.Item>
|
</Grid.Item>
|
||||||
|
|
||||||
<Grid.Item text={'在线开方'} onClick={() => navigateToPage('/doctor/orders/add')}>
|
<Grid.Item text={'在线开方'} onClick={() => navigateToPage('/clinic/clinicPrescription/add')}>
|
||||||
<View className="text-center">
|
<View className="text-center">
|
||||||
<View className="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
<View className="w-12 h-12 bg-green-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||||
<Edit color="#10b981" size="20"/>
|
<Edit color="#10b981" size="20"/>
|
||||||
@@ -163,7 +163,7 @@ const DealerIndex: React.FC = () => {
|
|||||||
</View>
|
</View>
|
||||||
</Grid.Item>
|
</Grid.Item>
|
||||||
|
|
||||||
<Grid.Item text={'处方管理'} onClick={() => navigateToPage('/doctor/orders/index')}>
|
<Grid.Item text={'处方管理'} onClick={() => navigateToPage('/clinic/clinicPrescription/index')}>
|
||||||
<View className="text-center">
|
<View className="text-center">
|
||||||
<View className="w-12 h-12 bg-orange-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
<View className="w-12 h-12 bg-orange-50 rounded-xl flex items-center justify-center mx-auto mb-2">
|
||||||
<Orderlist color="#f59e0b" size="20"/>
|
<Orderlist color="#f59e0b" size="20"/>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ const AddPatient = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 验证手机号格式
|
// 验证手机号格式
|
||||||
const phoneRegex = /^1[3-9]\d{9}$/;
|
const phoneRegex = /^1[2-9]\d{9}$/;
|
||||||
if (!phoneRegex.test(values.phone)) {
|
if (!phoneRegex.test(values.phone)) {
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '请填写正确的手机号',
|
title: '请填写正确的手机号',
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
export default definePageConfig({
|
export default definePageConfig({
|
||||||
navigationBarTitleText: '在线开方',
|
navigationBarTitleText: '开处方',
|
||||||
navigationBarTextStyle: 'black'
|
navigationBarTextStyle: 'black'
|
||||||
})
|
})
|
||||||
|
|||||||
134
src/doctor/orders/add.scss
Normal file
134
src/doctor/orders/add.scss
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
.usage-card {
|
||||||
|
margin: 12px 16px;
|
||||||
|
margin-top: 4px;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: #ffffff;
|
||||||
|
box-shadow: 0 12px 32px rgba(54, 87, 142, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__icon {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: linear-gradient(135deg, #fceecd, #ffd886);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__icon-text {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #8c5a00;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1c1c1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__dose-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #7c7c7c;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__dose-value {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__dose-value .nut-input {
|
||||||
|
width: 72px;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__dose-value .nut-input input {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1c1c1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__grid-item--picker {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__dose-number {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1c1c1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__dose-unit {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #a1a1a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__grid {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__grid-item {
|
||||||
|
flex: 1;
|
||||||
|
background: #f8f9fb;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__grid-label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #9aa1b2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__grid-value {
|
||||||
|
margin-top: 6px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__grid-text {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #1d1d1f;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__desc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #8c8c8c;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__hint {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4b4b4d;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.usage-card__hint--muted {
|
||||||
|
color: #9ba0ab;
|
||||||
|
background: #f0f2f5;
|
||||||
|
}
|
||||||
@@ -8,9 +8,10 @@ import {
|
|||||||
Avatar,
|
Avatar,
|
||||||
Input,
|
Input,
|
||||||
Space,
|
Space,
|
||||||
TextArea
|
TextArea,
|
||||||
|
Picker as NutPicker
|
||||||
} from '@nutui/nutui-react-taro'
|
} from '@nutui/nutui-react-taro'
|
||||||
import {ArrowRight} from '@nutui/icons-react-taro'
|
import {ArrowRight, ArrowDown} from '@nutui/icons-react-taro'
|
||||||
import {View, Text} from '@tarojs/components'
|
import {View, Text} from '@tarojs/components'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import FixedButton from "@/components/FixedButton";
|
import FixedButton from "@/components/FixedButton";
|
||||||
@@ -18,8 +19,8 @@ import navTo from "@/utils/common";
|
|||||||
import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model";
|
import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model";
|
||||||
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
||||||
import {TenantId} from "@/config/app";
|
import {TenantId} from "@/config/app";
|
||||||
import {getClinicPatientUser} from "@/api/clinic/clinicPatientUser";
|
import {clinicPatientUserByPatientUserId} from "@/api/clinic/clinicPatientUser";
|
||||||
import {addClinicOrder} from "@/api/clinic/clinicOrder";
|
import './add.scss'
|
||||||
|
|
||||||
// 图片数据接口
|
// 图片数据接口
|
||||||
interface UploadedImageData {
|
interface UploadedImageData {
|
||||||
@@ -31,6 +32,215 @@ interface UploadedImageData {
|
|||||||
type?: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const frequencyOptions = ['一次', '两次', '三次', '四次', '五次']
|
||||||
|
const perDoseOptions = Array.from({length: 20}, (_, index) => `${(index + 1) * 5}g`)
|
||||||
|
|
||||||
|
const getFrequencyIndexFromText = (text?: string) => {
|
||||||
|
if (!text) {
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
const numberMatch = text.match(/\d+/)
|
||||||
|
if (numberMatch) {
|
||||||
|
const num = Math.min(Math.max(parseInt(numberMatch[0], 10), 1), 5)
|
||||||
|
return num - 1
|
||||||
|
}
|
||||||
|
const chineseDigits = ['一', '二', '三', '四', '五']
|
||||||
|
const foundIndex = chineseDigits.findIndex(char => text.includes(char))
|
||||||
|
if (foundIndex !== -1) {
|
||||||
|
return foundIndex
|
||||||
|
}
|
||||||
|
return 2
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPerDoseIndexFromText = (text?: string) => {
|
||||||
|
if (!text) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const numberMatch = text.match(/\d+/)
|
||||||
|
if (!numberMatch) {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
const num = Math.min(Math.max(parseInt(numberMatch[0], 10), 5), 100)
|
||||||
|
const normalized = Math.round(num / 5)
|
||||||
|
const index = Math.max(0, Math.min(perDoseOptions.length - 1, normalized - 1))
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UsageSummaryCardProps {
|
||||||
|
prescription: ClinicPrescription;
|
||||||
|
doseCount: string;
|
||||||
|
onDoseChange?: (value: string) => void;
|
||||||
|
frequencyIndex: number;
|
||||||
|
onFrequencyChange?: (value: number) => void;
|
||||||
|
perDoseIndex: number;
|
||||||
|
onPerDoseChange?: (value: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const UsageSummaryCard = ({
|
||||||
|
prescription,
|
||||||
|
doseCount,
|
||||||
|
onDoseChange,
|
||||||
|
frequencyIndex,
|
||||||
|
onFrequencyChange,
|
||||||
|
perDoseIndex,
|
||||||
|
onPerDoseChange
|
||||||
|
}: UsageSummaryCardProps) => {
|
||||||
|
const firstItem = prescription.items?.[0]
|
||||||
|
const [showFrequencyPicker, setShowFrequencyPicker] = useState(false)
|
||||||
|
const [showPerDosePicker, setShowPerDosePicker] = useState(false)
|
||||||
|
|
||||||
|
const frequencyPickerOptions = frequencyOptions.map((label, index) => ({
|
||||||
|
text: label,
|
||||||
|
value: String(index)
|
||||||
|
}))
|
||||||
|
|
||||||
|
const perDosePickerOptions = perDoseOptions.map((label, index) => ({
|
||||||
|
text: label,
|
||||||
|
value: String(index)
|
||||||
|
}))
|
||||||
|
|
||||||
|
// const formatFrequency = (frequency?: string) => {
|
||||||
|
// if (!frequency) {
|
||||||
|
// return {label: '每日', value: '3次'}
|
||||||
|
// }
|
||||||
|
// if (frequency.includes('每日')) {
|
||||||
|
// return {label: '每日', value: frequency.replace('每日', '') || '1次'}
|
||||||
|
// }
|
||||||
|
// if (frequency.includes('每天')) {
|
||||||
|
// return {label: '每天', value: frequency.replace('每天', '') || '1次'}
|
||||||
|
// }
|
||||||
|
// return {label: '频率', value: frequency}
|
||||||
|
// }
|
||||||
|
|
||||||
|
const formatDosage = () => {
|
||||||
|
const dosageText = firstItem?.dosage || firstItem?.specification
|
||||||
|
if (!dosageText) {
|
||||||
|
return '5g'
|
||||||
|
}
|
||||||
|
return dosageText
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatDuration = () => {
|
||||||
|
const totalDoses = Number(doseCount) || 0
|
||||||
|
const perDay = frequencyIndex + 1
|
||||||
|
if (totalDoses <= 0) {
|
||||||
|
return '0天'
|
||||||
|
}
|
||||||
|
const days = Math.max(1, Math.ceil(totalDoses / perDay))
|
||||||
|
return `${days}天`
|
||||||
|
}
|
||||||
|
|
||||||
|
// const summaryDesc = firstItem?.comments || prescription.comments || '请根据患者情况调整剂量,严格按照医嘱服用。'
|
||||||
|
// const decoctionHint = prescription.decoctionInstructions || '设置用药时间、用药禁忌、医嘱等'
|
||||||
|
|
||||||
|
const handleDoseInputChange = (value: string) => {
|
||||||
|
const sanitized = value.replace(/[^0-9]/g, '')
|
||||||
|
onDoseChange?.(sanitized)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className="usage-card">
|
||||||
|
<View className="usage-card__header">
|
||||||
|
<View className="usage-card__icon">
|
||||||
|
<Text className="usage-card__icon-text">用</Text>
|
||||||
|
</View>
|
||||||
|
<Text className="usage-card__title">用法</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="usage-card__dose-row">
|
||||||
|
<Text className="usage-card__label">剂数</Text>
|
||||||
|
<View className="usage-card__dose-value">
|
||||||
|
<Input
|
||||||
|
className="usage-card__dose-input"
|
||||||
|
value={doseCount}
|
||||||
|
type="number"
|
||||||
|
placeholder="填写"
|
||||||
|
style={{
|
||||||
|
minWidth: '60px',
|
||||||
|
backgroundColor: '#f8f9fb',
|
||||||
|
borderRadius: '8px',
|
||||||
|
padding: '2px 6px'
|
||||||
|
}}
|
||||||
|
onChange={(value) => handleDoseInputChange(value)}
|
||||||
|
/>
|
||||||
|
<Text className="usage-card__dose-unit">剂</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View className="usage-card__grid">
|
||||||
|
<View
|
||||||
|
className="usage-card__grid-item usage-card__grid-item--picker"
|
||||||
|
onClick={() => setShowFrequencyPicker(true)}
|
||||||
|
>
|
||||||
|
<Text className="usage-card__grid-label">每日</Text>
|
||||||
|
<View className="usage-card__grid-value">
|
||||||
|
<Text className="usage-card__grid-text">{frequencyOptions[frequencyIndex] || '一次'}</Text>
|
||||||
|
<ArrowDown size={10} color="#C1C1C1"/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
className="usage-card__grid-item usage-card__grid-item--picker"
|
||||||
|
onClick={() => setShowPerDosePicker(true)}
|
||||||
|
>
|
||||||
|
<Text className="usage-card__grid-label">每次</Text>
|
||||||
|
<View className="usage-card__grid-value">
|
||||||
|
<Text className="usage-card__grid-text">{perDoseOptions[perDoseIndex] || formatDosage()}</Text>
|
||||||
|
<ArrowDown size={10} color="#C1C1C1"/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View className="usage-card__grid-item">
|
||||||
|
<Text className="usage-card__grid-label">可服</Text>
|
||||||
|
<View className="usage-card__grid-value">
|
||||||
|
<Text className="usage-card__grid-text">{formatDuration()}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/*<View className="usage-card__desc">*/}
|
||||||
|
{/* <Text>{summaryDesc}</Text>*/}
|
||||||
|
{/*</View>*/}
|
||||||
|
|
||||||
|
{/*<View className="usage-card__hint">*/}
|
||||||
|
{/* <Text>{decoctionHint}</Text>*/}
|
||||||
|
{/*</View>*/}
|
||||||
|
|
||||||
|
{/*<View className="usage-card__hint usage-card__hint--muted">*/}
|
||||||
|
{/* <Text>请输入制作要求,该内容患者不可见</Text>*/}
|
||||||
|
{/*</View>*/}
|
||||||
|
|
||||||
|
<NutPicker
|
||||||
|
visible={showFrequencyPicker}
|
||||||
|
title="请选择每日次数"
|
||||||
|
options={frequencyPickerOptions}
|
||||||
|
value={[String(frequencyIndex)]}
|
||||||
|
defaultValue={[String(frequencyIndex)]}
|
||||||
|
onConfirm={(_, value) => {
|
||||||
|
const selected = Array.isArray(value) && value.length > 0 ? Number(value[0]) : 0
|
||||||
|
onFrequencyChange?.(selected)
|
||||||
|
setShowFrequencyPicker(false)
|
||||||
|
}}
|
||||||
|
onClose={() => setShowFrequencyPicker(false)}
|
||||||
|
onCancel={() => setShowFrequencyPicker(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<NutPicker
|
||||||
|
visible={showPerDosePicker}
|
||||||
|
title="请选择每次用量"
|
||||||
|
options={perDosePickerOptions}
|
||||||
|
value={[String(perDoseIndex)]}
|
||||||
|
defaultValue={[String(perDoseIndex)]}
|
||||||
|
onConfirm={(_, value) => {
|
||||||
|
const selected = Array.isArray(value) && value.length > 0 ? Number(value[0]) : 0
|
||||||
|
onPerDoseChange?.(selected)
|
||||||
|
setShowPerDosePicker(false)
|
||||||
|
}}
|
||||||
|
onClose={() => setShowPerDosePicker(false)}
|
||||||
|
onCancel={() => setShowPerDosePicker(false)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const AddClinicOrder = () => {
|
const AddClinicOrder = () => {
|
||||||
const {params} = useRouter();
|
const {params} = useRouter();
|
||||||
const [toUser, setToUser] = useState<ClinicPatientUser>()
|
const [toUser, setToUser] = useState<ClinicPatientUser>()
|
||||||
@@ -54,13 +264,17 @@ const AddClinicOrder = () => {
|
|||||||
image: '' // 添加image字段
|
image: '' // 添加image字段
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [doseCount, setDoseCount] = useState<string>('1')
|
||||||
|
const [frequencyIndex, setFrequencyIndex] = useState<number>(2)
|
||||||
|
const [perDoseIndex, setPerDoseIndex] = useState<number>(0)
|
||||||
|
|
||||||
// 判断是编辑还是新增模式
|
// 判断是编辑还是新增模式
|
||||||
const isEditMode = !!params.id
|
const isEditMode = !!params.id
|
||||||
const toUserId = params.id ? Number(params.id) : undefined
|
const toUserId = params.id ? Number(params.id) : undefined
|
||||||
|
|
||||||
const reload = async () => {
|
const reload = async () => {
|
||||||
if (toUserId) {
|
if (toUserId) {
|
||||||
getClinicPatientUser(Number(toUserId)).then(data => {
|
clinicPatientUserByPatientUserId(Number(toUserId)).then(data => {
|
||||||
setToUser(data)
|
setToUser(data)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -321,6 +535,24 @@ const AddClinicOrder = () => {
|
|||||||
}
|
}
|
||||||
}, [isEditMode]);
|
}, [isEditMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedPrescription) {
|
||||||
|
const firstItem = selectedPrescription.items?.[0]
|
||||||
|
const inferredDose = firstItem?.quantity || firstItem?.amount || firstItem?.days || selectedPrescription.items?.length
|
||||||
|
if (inferredDose) {
|
||||||
|
setDoseCount(String(inferredDose))
|
||||||
|
} else {
|
||||||
|
setDoseCount('1')
|
||||||
|
}
|
||||||
|
setFrequencyIndex(getFrequencyIndexFromText(firstItem?.usageFrequency))
|
||||||
|
setPerDoseIndex(getPerDoseIndexFromText(firstItem?.dosage || firstItem?.specification))
|
||||||
|
} else {
|
||||||
|
setDoseCount('1')
|
||||||
|
setFrequencyIndex(2)
|
||||||
|
setPerDoseIndex(0)
|
||||||
|
}
|
||||||
|
}, [selectedPrescription])
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Loading className={'px-2'}>加载中</Loading>
|
return <Loading className={'px-2'}>加载中</Loading>
|
||||||
}
|
}
|
||||||
@@ -480,7 +712,7 @@ const AddClinicOrder = () => {
|
|||||||
onClick={() => navTo(`/doctor/orders/selectPrescription`, true)}
|
onClick={() => navTo(`/doctor/orders/selectPrescription`, true)}
|
||||||
/>
|
/>
|
||||||
{/* 药方信息 */}
|
{/* 药方信息 */}
|
||||||
{selectedPrescription && (
|
{selectedPrescription ? (
|
||||||
<>
|
<>
|
||||||
<Cell extra={'药方信息'}>
|
<Cell extra={'药方信息'}>
|
||||||
<View className={'flex flex-col'}>
|
<View className={'flex flex-col'}>
|
||||||
@@ -493,6 +725,16 @@ const AddClinicOrder = () => {
|
|||||||
</View>
|
</View>
|
||||||
</Cell>
|
</Cell>
|
||||||
|
|
||||||
|
<UsageSummaryCard
|
||||||
|
prescription={selectedPrescription}
|
||||||
|
doseCount={doseCount}
|
||||||
|
onDoseChange={setDoseCount}
|
||||||
|
frequencyIndex={frequencyIndex}
|
||||||
|
onFrequencyChange={setFrequencyIndex}
|
||||||
|
perDoseIndex={perDoseIndex}
|
||||||
|
onPerDoseChange={setPerDoseIndex}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* 煎药说明 */}
|
{/* 煎药说明 */}
|
||||||
<TextArea
|
<TextArea
|
||||||
value={formData.decoctionInstructions}
|
value={formData.decoctionInstructions}
|
||||||
@@ -502,7 +744,7 @@ const AddClinicOrder = () => {
|
|||||||
maxLength={200}
|
maxLength={200}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
) : null}
|
||||||
|
|
||||||
{/* 底部浮动按钮 */}
|
{/* 底部浮动按钮 */}
|
||||||
<FixedButton text={'下一步:确认订单信息'} onClick={() => formRef.current?.submit()}/>
|
<FixedButton text={'下一步:确认订单信息'} onClick={() => formRef.current?.submit()}/>
|
||||||
|
|||||||
@@ -14,8 +14,11 @@ import Taro from '@tarojs/taro'
|
|||||||
import FixedButton from "@/components/FixedButton";
|
import FixedButton from "@/components/FixedButton";
|
||||||
import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model";
|
import {ClinicPatientUser} from "@/api/clinic/clinicPatientUser/model";
|
||||||
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
||||||
import {addClinicOrder} from "@/api/clinic/clinicOrder";
|
import {addClinicPrescription} from "@/api/clinic/clinicPrescription";
|
||||||
|
import {batchAddClinicPrescriptionItem} from "@/api/clinic/clinicPrescriptionItem";
|
||||||
import './confirm.scss'
|
import './confirm.scss'
|
||||||
|
import request from "@/utils/request";
|
||||||
|
import {ApiResult} from "@/api";
|
||||||
|
|
||||||
// 订单数据接口
|
// 订单数据接口
|
||||||
interface OrderData {
|
interface OrderData {
|
||||||
@@ -84,51 +87,105 @@ const DoctorOrderConfirm = () => {
|
|||||||
try {
|
try {
|
||||||
setSubmitLoading(true)
|
setSubmitLoading(true)
|
||||||
|
|
||||||
// 构建诊所订单数据
|
const doctorId = Taro.getStorageSync('UserId') // 当前医生ID
|
||||||
const clinicOrderData = {
|
|
||||||
userId: orderData.patient.userId,
|
// 第一步:创建处方主表记录
|
||||||
doctorId: Taro.getStorageSync('UserId'), // 当前医生ID
|
console.log('开始创建处方记录...')
|
||||||
type: 0, // 订单类型:诊所订单
|
const prescriptionData: ClinicPrescription = {
|
||||||
title: `${orderData.patient.realName}的处方订单`,
|
userId: orderData.patient.patientUserId,
|
||||||
totalPrice: getTotalPrice(),
|
doctorId: doctorId,
|
||||||
payPrice: getTotalPrice(),
|
prescriptionType: orderData.prescription?.prescriptionType || 0, // 处方类型
|
||||||
buyerRemarks: orderData.diagnosis,
|
diagnosis: orderData.diagnosis, // 诊断结果
|
||||||
merchantRemarks: orderData.treatmentPlan,
|
treatmentPlan: orderData.treatmentPlan, // 治疗方案
|
||||||
comments: JSON.stringify({
|
decoctionInstructions: orderData.decoctionInstructions, // 煎药说明
|
||||||
diagnosis: orderData.diagnosis,
|
image: orderData.images ? JSON.stringify(orderData.images) : '', // 病例图片
|
||||||
treatmentPlan: orderData.treatmentPlan,
|
orderPrice: getTotalPrice(), // 订单总金额
|
||||||
decoctionInstructions: orderData.decoctionInstructions,
|
price: getMedicinePrice(), // 药品单价
|
||||||
prescriptionId: orderData.prescription?.id,
|
payPrice: getTotalPrice(), // 实付金额
|
||||||
images: orderData.images
|
status: 0, // 状态:0正常
|
||||||
}),
|
isInvalid: 0, // 未失效
|
||||||
payStatus: '0', // 未付款
|
isSettled: 0, // 未结算
|
||||||
orderStatus: 0, // 待支付
|
comments: `患者:${orderData.patient.realName},年龄:${orderData.patient.age}岁`
|
||||||
deliveryStatus: 10, // 未发货
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('提交订单数据:', clinicOrderData)
|
const createdPrescription = await addClinicPrescription(prescriptionData)
|
||||||
|
console.log('处方创建成功:', createdPrescription)
|
||||||
|
|
||||||
// 调用API创建订单
|
if (!createdPrescription || !createdPrescription.id) {
|
||||||
await addClinicOrder(clinicOrderData)
|
throw new Error('处方创建失败,未返回处方ID')
|
||||||
|
}
|
||||||
|
|
||||||
|
const prescriptionId = createdPrescription.id
|
||||||
|
|
||||||
|
// 第二步:创建处方明细记录(药品列表)
|
||||||
|
if (orderData.prescription?.items && orderData.prescription.items.length > 0) {
|
||||||
|
console.log('开始创建处方明细...')
|
||||||
|
const list = []
|
||||||
|
for (const item of orderData.prescription.items) {
|
||||||
|
const prescriptionItemData = {
|
||||||
|
prescriptionId: prescriptionId, // 关联处方ID
|
||||||
|
prescriptionNo: createdPrescription.orderNo, // 处方编号
|
||||||
|
medicineId: item.medicineId,
|
||||||
|
medicineName: item.medicineName,
|
||||||
|
specification: item.specification,
|
||||||
|
dosage: item.dosage,
|
||||||
|
usageFrequency: item.usageFrequency,
|
||||||
|
days: item.days,
|
||||||
|
amount: item.amount,
|
||||||
|
unitPrice: item.unitPrice,
|
||||||
|
quantity: item.quantity,
|
||||||
|
userId: orderData.patient.userId,
|
||||||
|
comments: item.comments
|
||||||
|
}
|
||||||
|
list.push(prescriptionItemData)
|
||||||
|
}
|
||||||
|
await batchAddClinicPrescriptionItem(list)
|
||||||
|
console.log('处方明细创建成功')
|
||||||
|
|
||||||
|
const order: any = {
|
||||||
|
userId: orderData.patient.userId,
|
||||||
|
orderNo: createdPrescription.orderNo,
|
||||||
|
type: 3,
|
||||||
|
title: "药方",
|
||||||
|
totalPrice: getTotalPrice(),
|
||||||
|
goodsItems: []
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
const orderGoodsList = []
|
||||||
|
for (const item of orderData.prescription.items) {
|
||||||
|
const orderGoods = {
|
||||||
|
goodsId: item.medicineId,
|
||||||
|
quantity: item.quantity,
|
||||||
|
}
|
||||||
|
orderGoodsList.push(orderGoods)
|
||||||
|
}
|
||||||
|
order.goodsItems = orderGoodsList
|
||||||
|
|
||||||
|
const res = await request.post<ApiResult<unknown>>('/shop/shop-order', order)
|
||||||
|
if (res.code !== 0) {
|
||||||
|
throw new Error(res.message || '创建商城订单失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log('处方创建完成,处方ID:', prescriptionId)
|
||||||
|
|
||||||
// 清除临时数据
|
// 清除临时数据
|
||||||
Taro.removeStorageSync('tempOrderData')
|
Taro.removeStorageSync('tempOrderData')
|
||||||
|
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '处方已发送给患者',
|
title: '处方已发送给患者',
|
||||||
icon: 'success',
|
icon: 'none',
|
||||||
duration: 2000
|
duration: 2000
|
||||||
})
|
})
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// 跳转到订单列表
|
// 跳转到订单列表
|
||||||
Taro.redirectTo({
|
Taro.redirectTo({
|
||||||
url: '/doctor/orders/index'
|
url: '/clinic/clinicPatientUser/prescription'
|
||||||
})
|
})
|
||||||
}, 2000)
|
}, 2000)
|
||||||
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('创建订单失败:', error)
|
console.error('创建处方/订单失败:', error)
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: error.message || '发送失败,请重试',
|
title: error.message || '发送失败,请重试',
|
||||||
icon: 'error'
|
icon: 'error'
|
||||||
|
|||||||
@@ -2,21 +2,15 @@ import React, {useState, useEffect, useCallback} from 'react'
|
|||||||
import {View, Text, ScrollView} from '@tarojs/components'
|
import {View, Text, ScrollView} from '@tarojs/components'
|
||||||
import {Empty, Tag, PullToRefresh, Loading} from '@nutui/nutui-react-taro'
|
import {Empty, Tag, PullToRefresh, Loading} from '@nutui/nutui-react-taro'
|
||||||
import Taro from '@tarojs/taro'
|
import Taro from '@tarojs/taro'
|
||||||
import {pageShopDealerOrder} from '@/api/shop/shopDealerOrder'
|
|
||||||
import {useDealerUser} from '@/hooks/useDealerUser'
|
import {useDealerUser} from '@/hooks/useDealerUser'
|
||||||
import type {ShopDealerOrder} from '@/api/shop/shopDealerOrder/model'
|
import {ClinicPrescription} from "@/api/clinic/clinicPrescription/model";
|
||||||
|
import {pageClinicPrescription} from "@/api/clinic/clinicPrescription";
|
||||||
interface OrderWithDetails extends ShopDealerOrder {
|
|
||||||
orderNo?: string
|
|
||||||
customerName?: string
|
|
||||||
userCommission?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const DealerOrders: React.FC = () => {
|
const DealerOrders: React.FC = () => {
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
const [refreshing, setRefreshing] = useState<boolean>(false)
|
const [refreshing, setRefreshing] = useState<boolean>(false)
|
||||||
const [loadingMore, setLoadingMore] = useState<boolean>(false)
|
const [loadingMore, setLoadingMore] = useState<boolean>(false)
|
||||||
const [orders, setOrders] = useState<OrderWithDetails[]>([])
|
const [orders, setOrders] = useState<ClinicPrescription[]>([])
|
||||||
const [currentPage, setCurrentPage] = useState<number>(1)
|
const [currentPage, setCurrentPage] = useState<number>(1)
|
||||||
const [hasMore, setHasMore] = useState<boolean>(true)
|
const [hasMore, setHasMore] = useState<boolean>(true)
|
||||||
|
|
||||||
@@ -24,8 +18,7 @@ const DealerOrders: React.FC = () => {
|
|||||||
|
|
||||||
// 获取订单数据
|
// 获取订单数据
|
||||||
const fetchOrders = useCallback(async (page: number = 1, isRefresh: boolean = false) => {
|
const fetchOrders = useCallback(async (page: number = 1, isRefresh: boolean = false) => {
|
||||||
if (!dealerUser?.userId) return
|
console.log('获取订单数据')
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (isRefresh) {
|
if (isRefresh) {
|
||||||
setRefreshing(true)
|
setRefreshing(true)
|
||||||
@@ -35,17 +28,14 @@ const DealerOrders: React.FC = () => {
|
|||||||
setLoadingMore(true)
|
setLoadingMore(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await pageShopDealerOrder({
|
const result = await pageClinicPrescription({
|
||||||
page,
|
page,
|
||||||
limit: 10
|
limit: 10
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result?.list) {
|
if (result?.list) {
|
||||||
const newOrders = result.list.map(order => ({
|
const newOrders = result.list.map(order => ({
|
||||||
...order,
|
...order
|
||||||
orderNo: `${order.orderId}`,
|
|
||||||
customerName: `用户${order.userId}`,
|
|
||||||
userCommission: order.firstMoney || '0.00'
|
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if (page === 1) {
|
if (page === 1) {
|
||||||
@@ -102,7 +92,7 @@ const DealerOrders: React.FC = () => {
|
|||||||
return 'warning'
|
return 'warning'
|
||||||
}
|
}
|
||||||
|
|
||||||
const renderOrderItem = (order: OrderWithDetails) => (
|
const renderOrderItem = (order: ClinicPrescription) => (
|
||||||
<View key={order.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
<View key={order.id} className="bg-white rounded-lg p-4 mb-3 shadow-sm">
|
||||||
<View className="flex justify-between items-start mb-1">
|
<View className="flex justify-between items-start mb-1">
|
||||||
<Text className="font-semibold text-gray-800">
|
<Text className="font-semibold text-gray-800">
|
||||||
@@ -117,15 +107,9 @@ const DealerOrders: React.FC = () => {
|
|||||||
<Text className="text-sm text-gray-400">
|
<Text className="text-sm text-gray-400">
|
||||||
订单金额:¥{order.orderPrice || '0.00'}
|
订单金额:¥{order.orderPrice || '0.00'}
|
||||||
</Text>
|
</Text>
|
||||||
<Text className="text-sm text-orange-500 font-semibold">
|
|
||||||
我的佣金:¥{order.userCommission}
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<View className="flex justify-between items-center">
|
<View className="flex justify-between items-center">
|
||||||
<Text className="text-sm text-gray-400">
|
|
||||||
客户:{order.customerName}
|
|
||||||
</Text>
|
|
||||||
<Text className="text-sm text-gray-400">
|
<Text className="text-sm text-gray-400">
|
||||||
{order.createTime}
|
{order.createTime}
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ const CustomerIndex = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Button type="warning" onClick={() => navTo(`/chat/doctor/index?id=${item.userId}`)}>咨询医生</Button>
|
<Button type="warning" onClick={() => navTo(`/chat/doctor/index?userId=${item.userId}`)}>咨询医生</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -216,7 +216,8 @@ const CustomerIndex = () => {
|
|||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
<Button type="warning" onClick={() => navTo(`/doctor/orders/add?id=${item.userId}`)}>开方</Button>
|
<Button type="warning" onClick={() => navTo(`/doctor/orders/add?id=${item.patientUserId}`)}>开方</Button>
|
||||||
|
<Button type="primary" onClick={() => navTo(`/chat/doctor/index?userId=${item.patientUserId}`)}>聊天</Button>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -350,7 +351,7 @@ const CustomerIndex = () => {
|
|||||||
<View className="bg-white pt-2 border-b border-gray-100">
|
<View className="bg-white pt-2 border-b border-gray-100">
|
||||||
<SearchBar
|
<SearchBar
|
||||||
value={searchValue}
|
value={searchValue}
|
||||||
placeholder="搜索患者名称、手机号"
|
placeholder="搜索医师名称、手机号"
|
||||||
onChange={(value) => setSearchValue(value)}
|
onChange={(value) => setSearchValue(value)}
|
||||||
onClear={() => {
|
onClear={() => {
|
||||||
setSearchValue('');
|
setSearchValue('');
|
||||||
|
|||||||
17
src/pages/index/Role.scss
Normal file
17
src/pages/index/Role.scss
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
.doctor-user{
|
||||||
|
background: url("https://oss.wsdns.cn/20251102/f893dba3f4da479f93e87c6def563d60.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 140px;
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.patient-user{
|
||||||
|
background: url("https://oss.wsdns.cn/20251102/d42d50e672d844bcaf7265aee03f3606.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100%;
|
||||||
|
width: 100%;
|
||||||
|
height: 140px;
|
||||||
|
border-radius: 25px;
|
||||||
|
}
|
||||||
45
src/pages/index/Role.tsx
Normal file
45
src/pages/index/Role.tsx
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import Taro from '@tarojs/taro'
|
||||||
|
import {View} from '@tarojs/components'
|
||||||
|
import {useShopInfo} from "@/hooks/useShopInfo"
|
||||||
|
import './Role.scss'
|
||||||
|
import navTo from "@/utils/common";
|
||||||
|
import {useEffect, useState} from "react";
|
||||||
|
|
||||||
|
const Page = () => {
|
||||||
|
const [isDoctor, setIsDoctor] = useState<boolean>(false)
|
||||||
|
// 使用 useShopInfo hooks 获取导航数据
|
||||||
|
const {
|
||||||
|
error,
|
||||||
|
} = useShopInfo()
|
||||||
|
|
||||||
|
// 获取顶部导航菜单
|
||||||
|
useEffect(() => {
|
||||||
|
setIsDoctor(Taro.getStorageSync('Doctor') || Taro.getStorageSync('Doctor') == 'true')
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 处理错误状态
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<View className={'p-2 text-center text-red-500'}>
|
||||||
|
加载导航菜单失败
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View className={'p-2 z-50 mt-1 gap-2 flex justify-between'}>
|
||||||
|
{isDoctor && (
|
||||||
|
<View className={'doctor-user rounded-lg w-full block'} onClick={() => navTo(`/clinic/index`)}></View>
|
||||||
|
)}
|
||||||
|
{!isDoctor && (
|
||||||
|
<View className={'doctor-user rounded-lg w-full block'} onClick={() => Taro.switchTab({
|
||||||
|
url: `/pages/chat/chat`
|
||||||
|
})}></View>
|
||||||
|
)}
|
||||||
|
<View className={'patient-user rounded-lg bg-white w-full'}
|
||||||
|
onClick={() => navTo(`/clinic/clinicPatientUser/prescription`, true)}></View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default Page
|
||||||
|
|
||||||
@@ -3,11 +3,11 @@ import Taro from '@tarojs/taro';
|
|||||||
import {useShareAppMessage} from "@tarojs/taro"
|
import {useShareAppMessage} from "@tarojs/taro"
|
||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import {getShopInfo} from "@/api/layout";
|
import {getShopInfo} from "@/api/layout";
|
||||||
import Menu from "./Menu";
|
|
||||||
import Banner from "./Banner";
|
import Banner from "./Banner";
|
||||||
import {checkAndHandleInviteRelation, hasPendingInvite} from "@/utils/invite";
|
import {checkAndHandleInviteRelation, hasPendingInvite} from "@/utils/invite";
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
import GoodsList from './GoodsList';
|
import GoodsList from './GoodsList';
|
||||||
|
import Role from "@/pages/index/Role";
|
||||||
|
|
||||||
function Home() {
|
function Home() {
|
||||||
// 吸顶状态
|
// 吸顶状态
|
||||||
@@ -158,7 +158,8 @@ function Home() {
|
|||||||
<Header />
|
<Header />
|
||||||
|
|
||||||
<div className={'flex flex-col mt-12'}>
|
<div className={'flex flex-col mt-12'}>
|
||||||
<Menu/>
|
{/*<Menu/>*/}
|
||||||
|
<Role />
|
||||||
<Banner/>
|
<Banner/>
|
||||||
<GoodsList onStickyChange={handleTabsStickyChange}/>
|
<GoodsList onStickyChange={handleTabsStickyChange}/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -188,6 +188,7 @@ function OrderList(props: OrderListProps) {
|
|||||||
// 合并搜索条件,tab的statusFilter优先级更高
|
// 合并搜索条件,tab的statusFilter优先级更高
|
||||||
const searchConditions: any = {
|
const searchConditions: any = {
|
||||||
page: currentPage,
|
page: currentPage,
|
||||||
|
type: 0,
|
||||||
userId: statusParams.userId, // 用户ID
|
userId: statusParams.userId, // 用户ID
|
||||||
...props.searchParams, // 搜索关键词等其他条件
|
...props.searchParams, // 搜索关键词等其他条件
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ function Index() {
|
|||||||
const submitSucceed = (values: any) => {
|
const submitSucceed = (values: any) => {
|
||||||
console.log('提交表单', values);
|
console.log('提交表单', values);
|
||||||
if (FormData.status != 2 && FormData.status != undefined) return false;
|
if (FormData.status != 2 && FormData.status != undefined) return false;
|
||||||
|
console.log(FormData.type)
|
||||||
if (FormData.type == 0) {
|
if (FormData.type == 0) {
|
||||||
if (!FormData.sfz1 || !FormData.sfz2) {
|
if (!FormData.sfz1 || !FormData.sfz2) {
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
@@ -249,6 +250,21 @@ function Index() {
|
|||||||
// 企业类型
|
// 企业类型
|
||||||
FormData.type == 1 && (
|
FormData.type == 1 && (
|
||||||
<>
|
<>
|
||||||
|
<Form.Item
|
||||||
|
label={'真实姓名'}
|
||||||
|
name="realName"
|
||||||
|
required
|
||||||
|
initialValue={FormData.realName}
|
||||||
|
rules={[{message: '请输入真实姓名'}]}
|
||||||
|
>
|
||||||
|
<Input
|
||||||
|
placeholder={'请输入真实姓名'}
|
||||||
|
type="text"
|
||||||
|
disabled={FormData.status != 2 && FormData.status != undefined}
|
||||||
|
value={FormData?.realName}
|
||||||
|
onChange={(value) => setFormData({...FormData, realName: value})}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={'主体名称'}
|
label={'主体名称'}
|
||||||
name="name"
|
name="name"
|
||||||
|
|||||||
Reference in New Issue
Block a user