1. 诊所管理
This commit is contained in:
@@ -113,6 +113,21 @@ export function createBusinessApi(prefix, headerFlag) {
|
|||||||
data: { order_id: orderId },
|
data: { order_id: orderId },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
getExpressCompaniesOption() {
|
||||||
|
return bizRequest({ url: `${prefix}-express-companies/option`, method: 'GET' });
|
||||||
|
},
|
||||||
|
getChinaErpSyncLogs(params) {
|
||||||
|
return bizRequest({ url: `${prefix}-order/china-erp-sync-logs`, method: 'GET', data: params });
|
||||||
|
},
|
||||||
|
syncChinaOrderToErp(data) {
|
||||||
|
return bizRequest({ url: `${prefix}-order/sync-china-erp`, method: 'POST', data });
|
||||||
|
},
|
||||||
|
getPriceAdjustConfig(storeId) {
|
||||||
|
return bizRequest({ url: `${prefix}-order/price-adjust-config`, method: 'GET', data: storeId ? { store_id: storeId } : {} });
|
||||||
|
},
|
||||||
|
adjustOrderPercent(data) {
|
||||||
|
return bizRequest({ url: `${prefix}-order/adjust-order-percent`, method: 'POST', data });
|
||||||
|
},
|
||||||
getWarehouseList(params) {
|
getWarehouseList(params) {
|
||||||
return bizRequest({ url: `${prefix}-warehouse/list`, method: 'GET', data: params });
|
return bizRequest({ url: `${prefix}-warehouse/list`, method: 'GET', data: params });
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -155,6 +155,19 @@ export function getExpressDetailByOrderId(orderId) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getPriceAdjustConfig(storeId) {
|
||||||
|
return clinicRequest({
|
||||||
|
url: `${PREFIX}-order/price-adjust-config`,
|
||||||
|
method: 'GET',
|
||||||
|
data: storeId ? { store_id: storeId } : {},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function adjustOrderPercent(data) {
|
||||||
|
return clinicRequest({ url: `${PREFIX}-order/adjust-order-percent`, method: 'POST', data });
|
||||||
|
}
|
||||||
|
|
||||||
export function getWarehouseList(params) {
|
export function getWarehouseList(params) {
|
||||||
return clinicRequest({ url: `${PREFIX}-warehouse/list`, method: 'GET', data: params });
|
return clinicRequest({ url: `${PREFIX}-warehouse/list`, method: 'GET', data: params });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -314,6 +314,15 @@ export function getCurrentStoreTypeApi(data) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 订单百分比调价配置(开方前)
|
||||||
|
export function getPriceAdjustConfigApi(storeId) {
|
||||||
|
return req.request({
|
||||||
|
url: '/newApi/doctor-reception-wx/price-adjust-config',
|
||||||
|
method: 'GET',
|
||||||
|
data: storeId ? { store_id: storeId } : {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 接诊页匹配推广员传方
|
// 接诊页匹配推广员传方
|
||||||
export function getSalespersonTransferByRegisterApi(registerId) {
|
export function getSalespersonTransferByRegisterApi(registerId) {
|
||||||
return req.request({
|
return req.request({
|
||||||
|
|||||||
@@ -18,6 +18,16 @@ function uploadChatFileByPrefix(prefix, headerKey, params) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOssSignatureByPrefix(prefix, headerKey) {
|
||||||
|
return req.request({
|
||||||
|
url: `${prefix}oss-signature`,
|
||||||
|
method: 'GET',
|
||||||
|
header: {
|
||||||
|
[headerKey]: '1',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 业务员录入/聊天同款:服务端转存 OSS,返回 url
|
* 业务员录入/聊天同款:服务端转存 OSS,返回 url
|
||||||
*/
|
*/
|
||||||
@@ -77,6 +87,44 @@ export function extractUploadUrl(res) {
|
|||||||
return inner.url || inner.path || null;
|
return inner.url || inner.path || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Laravel jok 响应解包 OSS 签名
|
||||||
|
*/
|
||||||
|
export function unwrapOssSignature(res) {
|
||||||
|
const inner = unwrapUploadResult(res);
|
||||||
|
if (!inner) return null;
|
||||||
|
if (!inner.accessKeyId || !inner.policy || !inner.signature || !inner.host || !inner.bucket) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSalespersonOssSignatureApi() {
|
||||||
|
return getOssSignatureByPrefix(SALESPERSON_PREFIX, '_salesperson');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPlatformAdminOssSignatureApi() {
|
||||||
|
return getOssSignatureByPrefix(PLATFORM_PREFIX, '_platformAdmin');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getClinicAdminOssSignatureApi() {
|
||||||
|
return getOssSignatureByPrefix(CLINIC_ADMIN_PREFIX, '_clinicAdmin');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按当前 loginMode 获取 OSS 直传签名
|
||||||
|
*/
|
||||||
|
export function getBusinessOssSignatureApi() {
|
||||||
|
const mode = uni.getStorageSync('loginMode');
|
||||||
|
if (mode === 'platform_admin') {
|
||||||
|
return getPlatformAdminOssSignatureApi();
|
||||||
|
}
|
||||||
|
if (mode === 'clinic_admin') {
|
||||||
|
return getClinicAdminOssSignatureApi();
|
||||||
|
}
|
||||||
|
return getSalespersonOssSignatureApi();
|
||||||
|
}
|
||||||
|
|
||||||
const LEGACY_ADMIN_UPLOAD_URL = 'https://api.xiaokang88.com/open-api/upload/old-admin-img';
|
const LEGACY_ADMIN_UPLOAD_URL = 'https://api.xiaokang88.com/open-api/upload/old-admin-img';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
100
common/js/oss-upload.js
Normal file
100
common/js/oss-upload.js
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* 阿里云 OSS PostObject 直传(对齐 xk-admin oss-upload.ts)
|
||||||
|
*
|
||||||
|
* 运维:微信小程序 uploadFile 合法域名需包含 OSS host
|
||||||
|
* 例如 https://xiaokang88.oss-cn-hangzhou.aliyuncs.com
|
||||||
|
*/
|
||||||
|
import { getBusinessOssSignatureApi, unwrapOssSignature } from '@/api/upload.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filePath 本地临时文件路径
|
||||||
|
* @returns {string} xk_upload_YYYYMMDD/uuid_timestamp.ext
|
||||||
|
*/
|
||||||
|
export function generateObjectName(filePath) {
|
||||||
|
const today = new Date();
|
||||||
|
const year = today.getFullYear();
|
||||||
|
const month = String(today.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(today.getDate()).padStart(2, '0');
|
||||||
|
const dateStr = `${year}${month}${day}`;
|
||||||
|
|
||||||
|
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
||||||
|
const r = (Math.random() * 16) | 0;
|
||||||
|
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
|
||||||
|
const timestamp = Math.floor(Date.now() / 1000);
|
||||||
|
const pathStr = String(filePath || '');
|
||||||
|
const lastDotIndex = pathStr.lastIndexOf('.');
|
||||||
|
const extension = lastDotIndex > 0 ? pathStr.substring(lastDotIndex + 1).toLowerCase() : 'jpg';
|
||||||
|
|
||||||
|
return `xk_upload_${dateStr}/${uuid}_${timestamp}.${extension}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<{ accessKeyId, policy, signature, host, bucket, key, expire }>}
|
||||||
|
*/
|
||||||
|
export async function getOssSignatureFromBackend() {
|
||||||
|
const res = await getBusinessOssSignatureApi();
|
||||||
|
const signature = unwrapOssSignature(res);
|
||||||
|
if (!signature) {
|
||||||
|
throw new Error('获取 OSS 签名失败');
|
||||||
|
}
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{ filePath: string, onProgress?: (percent: number) => void }} options
|
||||||
|
* @returns {Promise<{ url: string, objectName: string }>}
|
||||||
|
*/
|
||||||
|
export async function uploadToOss(options) {
|
||||||
|
const { filePath, onProgress } = options || {};
|
||||||
|
if (!filePath) {
|
||||||
|
throw new Error('文件路径为空');
|
||||||
|
}
|
||||||
|
|
||||||
|
const signature = await getOssSignatureFromBackend();
|
||||||
|
const objectName = generateObjectName(filePath);
|
||||||
|
|
||||||
|
const formData = {
|
||||||
|
key: objectName,
|
||||||
|
policy: signature.policy,
|
||||||
|
OSSAccessKeyId: signature.accessKeyId,
|
||||||
|
signature: signature.signature,
|
||||||
|
success_action_status: '200',
|
||||||
|
'x-oss-object-acl': 'public-read',
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const uploadTask = uni.uploadFile({
|
||||||
|
url: signature.host,
|
||||||
|
filePath,
|
||||||
|
name: 'file',
|
||||||
|
formData,
|
||||||
|
success: (uploadRes) => {
|
||||||
|
const code = uploadRes.statusCode;
|
||||||
|
if (code >= 200 && code < 300 || code === 204) {
|
||||||
|
const host = String(signature.host).replace(/\/$/, '');
|
||||||
|
resolve({
|
||||||
|
url: `${host}/${objectName}`,
|
||||||
|
objectName,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
reject(new Error(`OSS 上传失败,HTTP ${code}`));
|
||||||
|
},
|
||||||
|
fail: (err) => {
|
||||||
|
reject(err instanceof Error ? err : new Error((err && err.errMsg) || 'OSS 上传失败'));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (onProgress && uploadTask && typeof uploadTask.onProgressUpdate === 'function') {
|
||||||
|
uploadTask.onProgressUpdate((res) => {
|
||||||
|
if (res.totalBytesExpectedToSend > 0) {
|
||||||
|
const percent = Math.round((res.totalBytesSent / res.totalBytesExpectedToSend) * 100);
|
||||||
|
onProgress(percent);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
<u-loading slot="loading"></u-loading>
|
<u-loading slot="loading"></u-loading>
|
||||||
</u-image>
|
</u-image>
|
||||||
<u-badge
|
<u-badge
|
||||||
:key="'wb-ub-' + acceptingRowKey(item, aix) + '-' + Number(item.unread_count || 0)"
|
:key="acceptingBadgeKey(item, aix)"
|
||||||
v-if="item.consultation_channel === 'online' && Number(item.unread_count) > 0"
|
v-if="item.consultation_channel === 'online' && Number(item.unread_count) > 0"
|
||||||
:count="Number(item.unread_count)"
|
:count="Number(item.unread_count)"
|
||||||
:offset="[-4, -4]"
|
:offset="[-4, -4]"
|
||||||
@@ -121,6 +121,10 @@
|
|||||||
v-if="item.consultation_channel !== 'online' && acceptingOfflineOrderText(item)"
|
v-if="item.consultation_channel !== 'online' && acceptingOfflineOrderText(item)"
|
||||||
class="wb-sub text-hide m-t-8"
|
class="wb-sub text-hide m-t-8"
|
||||||
>{{ acceptingOfflineOrderText(item) }}</text>
|
>{{ acceptingOfflineOrderText(item) }}</text>
|
||||||
|
<text
|
||||||
|
v-if="item.consultation_channel !== 'online'"
|
||||||
|
class="wb-sub text-hide m-t-8"
|
||||||
|
>推广员:{{ (item.salesperson && item.salesperson.nick_name) || '无' }}</text>
|
||||||
|
|
||||||
<!-- 第三行:状态与操作按钮 -->
|
<!-- 第三行:状态与操作按钮 -->
|
||||||
<view class="flex-row flex-jus-sp flex-ali-center m-t-12">
|
<view class="flex-row flex-jus-sp flex-ali-center m-t-12">
|
||||||
@@ -629,6 +633,10 @@ export default {
|
|||||||
const room = item.room_id != null && item.room_id !== '' ? String(item.room_id) : 'noroom';
|
const room = item.room_id != null && item.room_id !== '' ? String(item.room_id) : 'noroom';
|
||||||
return `${item.consultation_channel || 'off'}-${room}-${rid}-${index}`;
|
return `${item.consultation_channel || 'off'}-${room}-${rid}-${index}`;
|
||||||
},
|
},
|
||||||
|
acceptingBadgeKey(item, index) {
|
||||||
|
const unread = item.unread_count != null ? Number(item.unread_count) : 0;
|
||||||
|
return `wb-ub-${this.acceptingRowKey(item, index)}-${unread}`;
|
||||||
|
},
|
||||||
acceptingPreview(item) {
|
acceptingPreview(item) {
|
||||||
const p = item.last_message_preview;
|
const p = item.last_message_preview;
|
||||||
if (p != null && String(p).trim() !== '') return String(p);
|
if (p != null && String(p).trim() !== '') return String(p);
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ export function erpSyncText(v) {
|
|||||||
return '未同步'
|
return '未同步'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prescriptionStatusText(status) {
|
||||||
|
const m = { 0: '待审核', 1: '已审核', 2: '已驳回', 3: '已过期' }
|
||||||
|
return m[status] != null ? m[status] : '-'
|
||||||
|
}
|
||||||
|
|
||||||
export function freeShippingText(v) {
|
export function freeShippingText(v) {
|
||||||
if (v === 1) return '包邮'
|
if (v === 1) return '包邮'
|
||||||
return '不包邮'
|
return '不包邮'
|
||||||
@@ -238,6 +243,7 @@ export function mapOrderProductItem(p, prescriptionType) {
|
|||||||
: '×' + (p.number != null ? p.number : 0)
|
: '×' + (p.number != null ? p.number : 0)
|
||||||
const isTcm = prescriptionType === 1
|
const isTcm = prescriptionType === 1
|
||||||
return {
|
return {
|
||||||
|
id: p.id,
|
||||||
drugName: p.drug_name || drug.drug_name || '药品',
|
drugName: p.drug_name || drug.drug_name || '药品',
|
||||||
drugNumber: drug.drug_number || '',
|
drugNumber: drug.drug_number || '',
|
||||||
imageUrl: resolveDrugImage(p.drug_image || drug.image),
|
imageUrl: resolveDrugImage(p.drug_image || drug.image),
|
||||||
@@ -276,6 +282,9 @@ export function mapOrderDetailDisplay(info) {
|
|||||||
if (!info || !info.id) return {}
|
if (!info || !info.id) return {}
|
||||||
const freeShip = info.is_free_shipping === 1 || info.free_ship === 1
|
const freeShip = info.is_free_shipping === 1 || info.free_ship === 1
|
||||||
const canView = !!(info.p_id && info.order_type !== 2 && info.order_type !== 3)
|
const canView = !!(info.p_id && info.order_type !== 2 && info.order_type !== 3)
|
||||||
|
const presStatus = info.prescription && info.prescription.status != null
|
||||||
|
? info.prescription.status
|
||||||
|
: null
|
||||||
return {
|
return {
|
||||||
orderTypeText: orderTypeText(info.order_type),
|
orderTypeText: orderTypeText(info.order_type),
|
||||||
marketPrice: formatMoney(info.market_price),
|
marketPrice: formatMoney(info.market_price),
|
||||||
@@ -284,6 +293,7 @@ export function mapOrderDetailDisplay(info) {
|
|||||||
showPrescription: mapOrderPrescription(info),
|
showPrescription: mapOrderPrescription(info),
|
||||||
prescriptionId: info.p_id,
|
prescriptionId: info.p_id,
|
||||||
canViewPrescription: canView,
|
canViewPrescription: canView,
|
||||||
|
prescriptionStatusText: canView && presStatus != null ? prescriptionStatusText(presStatus) : '',
|
||||||
statusText: orderStatusText(info.status),
|
statusText: orderStatusText(info.status),
|
||||||
prescriptionTypeText: prescriptionTypeText(info.prescription_type),
|
prescriptionTypeText: prescriptionTypeText(info.prescription_type),
|
||||||
deliveryText: deliveryMethodText(info.delivery_method),
|
deliveryText: deliveryMethodText(info.delivery_method),
|
||||||
@@ -398,16 +408,20 @@ export function warehouseStatusText(status) {
|
|||||||
return '-'
|
return '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapWarehouseRow(item) {
|
export function mapWarehouseRow(item, warehouseType = 'store') {
|
||||||
if (!item) return {}
|
if (!item) return {}
|
||||||
const drug = item.drug || {}
|
const drug = item.drug || {}
|
||||||
const storeDrug = drug.drug_store_drug || {}
|
const storeDrug = drug.drug_store_drug || {}
|
||||||
const suggestPrice = storeDrug.price != null ? storeDrug.price : drug.price
|
const suggestPrice = storeDrug.price != null ? storeDrug.price : drug.price
|
||||||
|
const isPlatform = warehouseType === 'platform'
|
||||||
|
const supplyRaw = isPlatform
|
||||||
|
? (item.market_price ?? item.buy_price)
|
||||||
|
: (item.buy_price ?? item.market_price)
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
drugName: drug.drug_name || '-',
|
drugName: drug.drug_name || '-',
|
||||||
imageUrl: resolveDrugImage(drug.image),
|
imageUrl: resolveDrugImage(drug.image),
|
||||||
buyPrice: formatMoney(item.buy_price),
|
buyPrice: formatMoney(supplyRaw),
|
||||||
price: formatMoney(item.price),
|
price: formatMoney(item.price),
|
||||||
suggestPrice: formatMoney(suggestPrice),
|
suggestPrice: formatMoney(suggestPrice),
|
||||||
stock: item.stock != null ? item.stock : '-',
|
stock: item.stock != null ? item.stock : '-',
|
||||||
|
|||||||
144
subPackages/sub_business_shared/common/orderPriceAdjustMixin.js
Normal file
144
subPackages/sub_business_shared/common/orderPriceAdjustMixin.js
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
import { normalizeQuickOptions as normalizeQuickOptionsFromUtil } from '@/subPackages/sub_business_shared/common/pricePercentAdjust.js'
|
||||||
|
|
||||||
|
export const orderPriceAdjustMixin = {
|
||||||
|
|
||||||
|
data() {
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
priceAdjustVisible: false,
|
||||||
|
|
||||||
|
priceAdjustQuickOptions: [],
|
||||||
|
|
||||||
|
priceAdjustScopeMode: 'sale_only',
|
||||||
|
|
||||||
|
priceAdjustSubmitting: false,
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
canOrderPercentAdjust() {
|
||||||
|
|
||||||
|
return Number(this.info?.is_pay) !== 1
|
||||||
|
|
||||||
|
&& Number(this.info?.store?.enable_order_price_percent_adjust) === 1
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
orderPriceDiscountLabel() {
|
||||||
|
|
||||||
|
const d = Number(this.info?.price_discount ?? 100)
|
||||||
|
|
||||||
|
if (d === 100) return ''
|
||||||
|
|
||||||
|
return this.info?.price_discount_label || this.formatDiscountLabel(d)
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
formatDiscountLabel(discount) {
|
||||||
|
|
||||||
|
const d = Number(discount) || 100
|
||||||
|
|
||||||
|
if (d <= 0 || d === 100) return ''
|
||||||
|
|
||||||
|
if (d < 100) {
|
||||||
|
|
||||||
|
if (d % 10 === 0 && d >= 10) return `${d / 10}折`
|
||||||
|
|
||||||
|
return `折扣 ${d}%`
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return `涨 ${d - 100}%`
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
normalizeQuickOptions(raw) {
|
||||||
|
return normalizeQuickOptionsFromUtil(raw)
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadPriceAdjustMeta(apiFn) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const w = await apiFn()
|
||||||
|
|
||||||
|
if (w?.ok && w.data) {
|
||||||
|
|
||||||
|
this.priceAdjustScopeMode = w.data.order_price_adjust_scope === 'both' ? 'both' : 'sale_only'
|
||||||
|
|
||||||
|
this.priceAdjustQuickOptions = this.normalizeQuickOptions(w.data.order_discount_quick_options)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
openOrderPriceAdjust() {
|
||||||
|
|
||||||
|
if (!this.canOrderPercentAdjust) return
|
||||||
|
|
||||||
|
this.priceAdjustVisible = true
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
closePriceAdjust() {
|
||||||
|
|
||||||
|
this.priceAdjustVisible = false
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async onPriceAdjustConfirm(payload, api) {
|
||||||
|
|
||||||
|
if (this.priceAdjustSubmitting) return
|
||||||
|
|
||||||
|
this.priceAdjustSubmitting = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
const w = await api.adjustOrderPercent({
|
||||||
|
|
||||||
|
product_order_id: this.info.id,
|
||||||
|
|
||||||
|
price_discount: payload.priceDiscount,
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!w.ok) return
|
||||||
|
|
||||||
|
uni.showToast({ title: payload.priceDiscount === 100 ? '已清除浮动' : '调价成功' })
|
||||||
|
|
||||||
|
this.closePriceAdjust()
|
||||||
|
|
||||||
|
await this.load()
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
|
||||||
|
this.priceAdjustSubmitting = false
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
async clearOrderPriceDiscount(api) {
|
||||||
|
|
||||||
|
return this.onPriceAdjustConfirm({ priceDiscount: 100 }, api)
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export { applyRatioToDrug, applyRatioToDrugs } from '@/subPackages/sub_business_shared/common/pricePercentAdjust.js'
|
||||||
|
|
||||||
77
subPackages/sub_business_shared/common/pricePercentAdjust.js
Normal file
77
subPackages/sub_business_shared/common/pricePercentAdjust.js
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
export const DEFAULT_QUICK_OPTIONS = [
|
||||||
|
{ name: '九五折', value: 95 },
|
||||||
|
{ name: '九折', value: 90 },
|
||||||
|
{ name: '八五折', value: 85 },
|
||||||
|
{ name: '八折', value: 80 },
|
||||||
|
{ name: '涨10%', value: 110 },
|
||||||
|
{ name: '涨20%', value: 120 },
|
||||||
|
]
|
||||||
|
|
||||||
|
const DEFAULT_MARKUP_OPTIONS = [
|
||||||
|
{ name: '涨10%', value: 110 },
|
||||||
|
{ name: '涨20%', value: 120 },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function normalizeQuickOptions(raw) {
|
||||||
|
if (!Array.isArray(raw)) return [...DEFAULT_QUICK_OPTIONS]
|
||||||
|
const list = []
|
||||||
|
for (const item of raw) {
|
||||||
|
if (item && typeof item === 'object' && item.value != null) {
|
||||||
|
const val = Number(item.value)
|
||||||
|
if (val > 0) {
|
||||||
|
list.push({ name: String(item.name || `${val}%`), value: val })
|
||||||
|
}
|
||||||
|
} else if (typeof item === 'number' && item > 0) {
|
||||||
|
list.push({ name: `${item}%`, value: item })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!list.length) return [...DEFAULT_QUICK_OPTIONS]
|
||||||
|
if (!list.some((o) => o.value > 100)) {
|
||||||
|
return [...list, ...DEFAULT_MARKUP_OPTIONS]
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyRatioPrice(originPrice, discount) {
|
||||||
|
const origin = Number(originPrice || 0)
|
||||||
|
const ratio = Number(discount) > 0 ? Number(discount) : 100
|
||||||
|
if (!origin || origin <= 0) return 0
|
||||||
|
return Math.round(origin * (ratio / 100) * 10000) / 10000
|
||||||
|
}
|
||||||
|
|
||||||
|
export function formatPriceDiscountLabel(discount) {
|
||||||
|
const d = Number(discount) || 100
|
||||||
|
if (d <= 0 || d === 100) return null
|
||||||
|
if (d < 100) {
|
||||||
|
if (d % 10 === 0 && d >= 10) return `${d / 10}折`
|
||||||
|
return `折扣 ${d}%`
|
||||||
|
}
|
||||||
|
return `涨 ${d - 100}%`
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyRatioToDrug(drug, discount, scope) {
|
||||||
|
if (!drug) return drug
|
||||||
|
const originPrice = Number(drug.origin_price ?? drug.price ?? 0)
|
||||||
|
const originBuy = Number(drug.origin_buy_price ?? drug.buy_price ?? 0)
|
||||||
|
const next = {
|
||||||
|
...drug,
|
||||||
|
origin_price: drug.origin_price ?? originPrice,
|
||||||
|
origin_buy_price: drug.origin_buy_price ?? originBuy,
|
||||||
|
price: applyRatioPrice(originPrice, discount),
|
||||||
|
}
|
||||||
|
if (scope === 'both' && originBuy > 0) {
|
||||||
|
next.buy_price = applyRatioPrice(originBuy, discount)
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
export function applyRatioToDrugs(drugs, discount, scope) {
|
||||||
|
return (drugs || []).map((d) => applyRatioToDrug(d, discount, scope))
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @deprecated */
|
||||||
|
export function applyPercentToDrug(drug, adjustType, percent, scope) {
|
||||||
|
let discount = Number(percent)
|
||||||
|
if (adjustType === 'markup') discount = Math.round(100 + Number(percent))
|
||||||
|
return applyRatioToDrug(drug, discount, scope)
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<!-- #ifdef MP-WEIXIN -->
|
||||||
|
<page-container
|
||||||
|
v-if="usePageGuard && show"
|
||||||
|
:show="show"
|
||||||
|
:overlay="false"
|
||||||
|
@leave="onPageLeave"
|
||||||
|
/>
|
||||||
|
<!-- #endif -->
|
||||||
|
<u-popup
|
||||||
|
:value="show"
|
||||||
|
mode="bottom"
|
||||||
|
border-radius="24"
|
||||||
|
:height="height"
|
||||||
|
:safe-area-inset-bottom="true"
|
||||||
|
:mask-close-able="maskCloseable"
|
||||||
|
:closeable="closeable"
|
||||||
|
@input="onPopupInput"
|
||||||
|
@close="close"
|
||||||
|
>
|
||||||
|
<view class="drawer-inner" :class="{ 'drawer-inner-tall': tall }">
|
||||||
|
<view v-if="title && !closeable" class="drawer-header">
|
||||||
|
<text class="drawer-title">{{ title }}</text>
|
||||||
|
<text v-if="showClose" class="drawer-close" @click="close">关闭</text>
|
||||||
|
</view>
|
||||||
|
<view v-else-if="title && closeable" class="drawer-header drawer-header-compact">
|
||||||
|
<text class="drawer-title">{{ title }}</text>
|
||||||
|
</view>
|
||||||
|
<slot />
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'DrawerPageContainer',
|
||||||
|
props: {
|
||||||
|
value: { type: Boolean, default: false },
|
||||||
|
title: { type: String, default: '' },
|
||||||
|
showClose: { type: Boolean, default: true },
|
||||||
|
maskCloseable: { type: Boolean, default: true },
|
||||||
|
closeable: { type: Boolean, default: false },
|
||||||
|
usePageGuard: { type: Boolean, default: true },
|
||||||
|
height: { type: String, default: '' },
|
||||||
|
tall: { type: Boolean, default: false },
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
show: {
|
||||||
|
get() { return this.value },
|
||||||
|
set(v) { this.$emit('input', v) },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
close() {
|
||||||
|
this.show = false
|
||||||
|
this.$emit('close')
|
||||||
|
},
|
||||||
|
onPopupInput(v) {
|
||||||
|
this.show = v
|
||||||
|
},
|
||||||
|
onPageLeave() {
|
||||||
|
this.close()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.drawer-inner {
|
||||||
|
background: #fff;
|
||||||
|
max-height: 80vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.drawer-inner-tall {
|
||||||
|
max-height: 85vh;
|
||||||
|
min-height: 55vh;
|
||||||
|
}
|
||||||
|
.drawer-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 28rpx 32rpx;
|
||||||
|
border-bottom: 1rpx solid #f0f0f0;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.drawer-header-compact {
|
||||||
|
padding-top: 48rpx;
|
||||||
|
}
|
||||||
|
.drawer-title { font-size: 32rpx; font-weight: 600; color: #1d2129; }
|
||||||
|
.drawer-close { font-size: 28rpx; color: #6acdbb; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
<template>
|
||||||
|
<u-popup v-model="show" mode="bottom" border-radius="16" height="auto" :closeable="true" @close="onClose">
|
||||||
|
<view class="popup">
|
||||||
|
<view class="title">整单调价</view>
|
||||||
|
<view v-if="currentLabel" class="hint">当前浮动:{{ currentLabel }}</view>
|
||||||
|
|
||||||
|
<view class="row">
|
||||||
|
<text class="label">价格比例(100=原价)</text>
|
||||||
|
<input class="input" type="digit" v-model="discountInput" placeholder="如 90=九折,120=涨20%" />
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="discountQuickOptions.length" class="quick-row">
|
||||||
|
<text class="quick-label">快捷打折</text>
|
||||||
|
<view class="quick-wrap">
|
||||||
|
<text
|
||||||
|
v-for="opt in discountQuickOptions"
|
||||||
|
:key="'d-' + opt.value"
|
||||||
|
class="quick"
|
||||||
|
@click="discountInput = String(opt.value)"
|
||||||
|
>{{ opt.name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view v-if="markupQuickOptions.length" class="quick-row">
|
||||||
|
<text class="quick-label">快捷涨价</text>
|
||||||
|
<view class="quick-wrap">
|
||||||
|
<text
|
||||||
|
v-for="opt in markupQuickOptions"
|
||||||
|
:key="'m-' + opt.value"
|
||||||
|
class="quick markup"
|
||||||
|
@click="discountInput = String(opt.value)"
|
||||||
|
>{{ opt.name }}</text>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view class="actions">
|
||||||
|
<button class="btn cancel" @click="onClose">取消</button>
|
||||||
|
<button class="btn ghost" @click="discountInput = '100'">清除浮动</button>
|
||||||
|
<button class="btn confirm" :loading="submitting" @click="onConfirm">确定</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</u-popup>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { formatPriceDiscountLabel } from '@/subPackages/sub_business_shared/common/pricePercentAdjust.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'OrderPricePercentAdjustPopup',
|
||||||
|
props: {
|
||||||
|
visible: { type: Boolean, default: false },
|
||||||
|
currentDiscount: { type: [Number, String], default: 100 },
|
||||||
|
quickOptions: { type: Array, default: () => [] },
|
||||||
|
submitting: { type: Boolean, default: false },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
discountInput: '100',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentLabel() {
|
||||||
|
return formatPriceDiscountLabel(Number(this.currentDiscount) || 100)
|
||||||
|
},
|
||||||
|
discountQuickOptions() {
|
||||||
|
return (this.quickOptions || []).filter((o) => Number(o.value) < 100)
|
||||||
|
},
|
||||||
|
markupQuickOptions() {
|
||||||
|
return (this.quickOptions || []).filter((o) => Number(o.value) > 100)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible: {
|
||||||
|
immediate: true,
|
||||||
|
handler(v) {
|
||||||
|
this.show = v
|
||||||
|
if (v) {
|
||||||
|
this.syncDiscountInput()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
currentDiscount(v) {
|
||||||
|
if (this.show) {
|
||||||
|
this.syncDiscountInput(v)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
show(v) {
|
||||||
|
if (!v) this.$emit('close')
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
syncDiscountInput(discount) {
|
||||||
|
const d = Number(discount ?? this.currentDiscount) || 100
|
||||||
|
this.discountInput = String(d)
|
||||||
|
},
|
||||||
|
onClose() {
|
||||||
|
this.show = false
|
||||||
|
},
|
||||||
|
onConfirm() {
|
||||||
|
const priceDiscount = parseInt(String(this.discountInput).trim(), 10)
|
||||||
|
if (!priceDiscount || priceDiscount <= 0) {
|
||||||
|
uni.showToast({ title: '请输入有效比例', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit('confirm', { priceDiscount })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.popup { padding: 32rpx 32rpx 48rpx; }
|
||||||
|
.title { font-size: 32rpx; font-weight: 600; text-align: center; }
|
||||||
|
.hint { font-size: 26rpx; color: #666; text-align: center; margin-top: 12rpx; }
|
||||||
|
.row { margin-top: 28rpx; }
|
||||||
|
.label, .quick-label { font-size: 26rpx; color: #666; display: block; margin-bottom: 12rpx; }
|
||||||
|
.input { background: #f5f5f5; border-radius: 12rpx; padding: 20rpx; font-size: 28rpx; }
|
||||||
|
.quick-row { margin-top: 20rpx; }
|
||||||
|
.quick-wrap { display: flex; flex-wrap: wrap; gap: 16rpx; }
|
||||||
|
.quick { padding: 10rpx 24rpx; background: #eef9f6; color: #6acdbb; border-radius: 20rpx; font-size: 24rpx; }
|
||||||
|
.quick.markup { background: #fff7e6; color: #fa8c16; }
|
||||||
|
.actions { display: flex; gap: 16rpx; margin-top: 36rpx; }
|
||||||
|
.btn { flex: 1; font-size: 28rpx; border-radius: 12rpx; }
|
||||||
|
.cancel { background: #f5f5f5; color: #666; }
|
||||||
|
.ghost { background: #fff; color: #6acdbb; border: 1rpx solid #6acdbb; }
|
||||||
|
.confirm { background: #6acdbb; color: #fff; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,175 @@
|
|||||||
|
<template>
|
||||||
|
<drawer-page-container
|
||||||
|
v-model="show"
|
||||||
|
title="ERP 同步记录"
|
||||||
|
height="80%"
|
||||||
|
tall
|
||||||
|
:show-close="true"
|
||||||
|
:closeable="false"
|
||||||
|
@close="onClose"
|
||||||
|
>
|
||||||
|
<view class="popup-wrap">
|
||||||
|
<view class="summary">
|
||||||
|
<text>订单号:{{ orderNo || '—' }}</text>
|
||||||
|
<text class="status-tag" :class="{ synced: isSyncErp === 1 }">{{ isSyncErp === 1 ? '已同步' : '未同步' }}</text>
|
||||||
|
</view>
|
||||||
|
<view v-if="showSyncBtn" class="sync-bar">
|
||||||
|
<button class="btn-sync" :loading="syncing" @click="handleSync">同步到 ERP</button>
|
||||||
|
</view>
|
||||||
|
<scroll-view scroll-y class="log-scroll" @scrolltolower="loadMore">
|
||||||
|
<view v-if="loading && !logs.length" class="hint">加载中...</view>
|
||||||
|
<view v-else-if="!logs.length" class="hint">暂无同步记录</view>
|
||||||
|
<view v-for="row in logs" :key="row.id" class="log-item">
|
||||||
|
<view class="log-head">
|
||||||
|
<text class="log-time">{{ row.created_at }}</text>
|
||||||
|
<text class="log-result" :class="{ ok: row.is_success === 1 }">
|
||||||
|
{{ row.is_success === 1 ? '成功' : '失败' }}
|
||||||
|
</text>
|
||||||
|
</view>
|
||||||
|
<view class="log-preview">{{ row.content_preview || row.content || '—' }}</view>
|
||||||
|
</view>
|
||||||
|
<view v-if="loadingMore" class="hint">加载更多...</view>
|
||||||
|
</scroll-view>
|
||||||
|
</view>
|
||||||
|
</drawer-page-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DrawerPageContainer from '@/subPackages/sub_business_shared/components/DrawerPageContainer.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ErpSyncPopup',
|
||||||
|
components: { DrawerPageContainer },
|
||||||
|
props: {
|
||||||
|
bizApi: { type: Object, required: true },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
orderId: 0,
|
||||||
|
orderNo: '',
|
||||||
|
isSyncErp: 0,
|
||||||
|
logs: [],
|
||||||
|
page: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
loadingMore: false,
|
||||||
|
syncing: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
showSyncBtn() {
|
||||||
|
return this.isSyncErp !== 1
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open(order) {
|
||||||
|
this.orderId = order.id
|
||||||
|
this.orderNo = order.order_no || ''
|
||||||
|
this.isSyncErp = Number(order.is_sync_erp)
|
||||||
|
this.logs = []
|
||||||
|
this.page = 1
|
||||||
|
this.total = 0
|
||||||
|
this.show = true
|
||||||
|
this.loadLogs(false)
|
||||||
|
},
|
||||||
|
onClose() {
|
||||||
|
this.show = false
|
||||||
|
},
|
||||||
|
loadLogs(append) {
|
||||||
|
if (!this.orderId) return
|
||||||
|
if (append) {
|
||||||
|
if (this.logs.length >= this.total) return
|
||||||
|
this.loadingMore = true
|
||||||
|
} else {
|
||||||
|
this.loading = true
|
||||||
|
}
|
||||||
|
const page = append ? this.page + 1 : 1
|
||||||
|
this.bizApi.getChinaErpSyncLogs({
|
||||||
|
order_id: this.orderId,
|
||||||
|
page,
|
||||||
|
pageSize: this.pageSize,
|
||||||
|
}).then(w => {
|
||||||
|
if (!w.ok) return
|
||||||
|
const d = w.data || {}
|
||||||
|
const list = d.list || []
|
||||||
|
this.total = d.total != null ? d.total : list.length
|
||||||
|
this.page = page
|
||||||
|
this.logs = append ? this.logs.concat(list) : list
|
||||||
|
}).finally(() => {
|
||||||
|
this.loading = false
|
||||||
|
this.loadingMore = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
loadMore() {
|
||||||
|
if (!this.loading && !this.loadingMore) {
|
||||||
|
this.loadLogs(true)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSync() {
|
||||||
|
uni.showModal({
|
||||||
|
title: '同步到 ERP',
|
||||||
|
content: `确认将订单 ${this.orderNo || this.orderId} 同步到 MES?`,
|
||||||
|
success: (r) => {
|
||||||
|
if (!r.confirm) return
|
||||||
|
this.syncing = true
|
||||||
|
this.bizApi.syncChinaOrderToErp({ order_id: this.orderId }).then(w => {
|
||||||
|
if (w.ok) {
|
||||||
|
uni.showToast({ title: '同步成功' })
|
||||||
|
this.isSyncErp = 1
|
||||||
|
this.page = 1
|
||||||
|
this.loadLogs(false)
|
||||||
|
this.$emit('success')
|
||||||
|
}
|
||||||
|
}).finally(() => { this.syncing = false })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.popup-wrap {
|
||||||
|
padding: 0 32rpx 32rpx;
|
||||||
|
height: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.summary {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #4E5969;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.status-tag {
|
||||||
|
padding: 4rpx 16rpx;
|
||||||
|
border-radius: 8rpx;
|
||||||
|
background: #FFF3E8;
|
||||||
|
color: #FF7D00;
|
||||||
|
font-size: 22rpx;
|
||||||
|
}
|
||||||
|
.status-tag.synced { background: rgba(106,205,187,.1); color: #6acdbb; }
|
||||||
|
.sync-bar { margin-bottom: 16rpx; flex-shrink: 0; }
|
||||||
|
.btn-sync {
|
||||||
|
margin: 0;
|
||||||
|
background: #6acdbb;
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
.btn-sync::after { display: none; }
|
||||||
|
.log-scroll { flex: 1; min-height: 0; }
|
||||||
|
.hint { text-align: center; color: #86909C; font-size: 26rpx; padding: 32rpx 0; }
|
||||||
|
.log-item { background: #F7F8FA; border-radius: 12rpx; padding: 20rpx; margin-bottom: 16rpx; }
|
||||||
|
.log-head { display: flex; justify-content: space-between; margin-bottom: 8rpx; }
|
||||||
|
.log-time { font-size: 24rpx; color: #86909C; }
|
||||||
|
.log-result { font-size: 24rpx; color: #F53F3F; }
|
||||||
|
.log-result.ok { color: #6acdbb; }
|
||||||
|
.log-preview { font-size: 26rpx; color: #4E5969; word-break: break-all; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<view class="picker-wrap">
|
||||||
|
|
||||||
|
<view class="picker-trigger" @click="openDrawer">
|
||||||
|
|
||||||
|
<text :class="selected ? 'summary-text' : 'placeholder-text'">{{ summaryText }}</text>
|
||||||
|
|
||||||
|
<text class="arrow">›</text>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<drawer-page-container
|
||||||
|
|
||||||
|
v-model="showPopup"
|
||||||
|
|
||||||
|
title="选择快递公司"
|
||||||
|
|
||||||
|
:use-page-guard="false"
|
||||||
|
|
||||||
|
:show-close="true"
|
||||||
|
|
||||||
|
>
|
||||||
|
|
||||||
|
<view class="drawer-body">
|
||||||
|
|
||||||
|
<view class="search-bar">
|
||||||
|
|
||||||
|
<input
|
||||||
|
|
||||||
|
class="search-input"
|
||||||
|
|
||||||
|
v-model="keyword"
|
||||||
|
|
||||||
|
placeholder="搜索公司名称或编码"
|
||||||
|
|
||||||
|
placeholder-style="color:#BDBDBD"
|
||||||
|
|
||||||
|
confirm-type="search"
|
||||||
|
|
||||||
|
/>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<scroll-view scroll-y class="list-scroll">
|
||||||
|
|
||||||
|
<view v-if="!filteredOptions.length" class="empty">无匹配快递公司</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
|
||||||
|
v-for="item in filteredOptions"
|
||||||
|
|
||||||
|
:key="item._wxKey"
|
||||||
|
|
||||||
|
class="option-item"
|
||||||
|
|
||||||
|
@click="onPick(item)"
|
||||||
|
|
||||||
|
>
|
||||||
|
|
||||||
|
<view class="option-main">
|
||||||
|
|
||||||
|
<text class="option-name">{{ item.name || '-' }}</text>
|
||||||
|
|
||||||
|
<text class="option-code">{{ item.code || '' }}</text>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<text v-if="value === item.code" class="check">✓</text>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</scroll-view>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</drawer-page-container>
|
||||||
|
|
||||||
|
</view>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
import DrawerPageContainer from '@/subPackages/sub_business_shared/components/DrawerPageContainer.vue'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
|
||||||
|
name: 'ExpressCompanyPicker',
|
||||||
|
|
||||||
|
components: { DrawerPageContainer },
|
||||||
|
|
||||||
|
props: {
|
||||||
|
|
||||||
|
value: { type: String, default: '' },
|
||||||
|
|
||||||
|
options: { type: Array, default: () => [] },
|
||||||
|
|
||||||
|
disabled: { type: Boolean, default: false },
|
||||||
|
|
||||||
|
placeholder: { type: String, default: '请选择快递公司' },
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
data() {
|
||||||
|
|
||||||
|
return {
|
||||||
|
|
||||||
|
showPopup: false,
|
||||||
|
|
||||||
|
keyword: '',
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
|
||||||
|
selected() {
|
||||||
|
|
||||||
|
const code = this.value
|
||||||
|
|
||||||
|
if (!code) return null
|
||||||
|
|
||||||
|
return this.options.find(o => o.code === code) || null
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
summaryText() {
|
||||||
|
|
||||||
|
if (!this.selected) return this.placeholder
|
||||||
|
|
||||||
|
const name = this.selected.name || '-'
|
||||||
|
|
||||||
|
const code = this.selected.code || ''
|
||||||
|
|
||||||
|
return code ? `${name}(${code})` : name
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
filteredOptions() {
|
||||||
|
|
||||||
|
const kw = (this.keyword || '').trim().toLowerCase()
|
||||||
|
|
||||||
|
if (!kw) return this.options
|
||||||
|
|
||||||
|
return this.options.filter(o => {
|
||||||
|
|
||||||
|
const name = String(o.name || '').toLowerCase()
|
||||||
|
|
||||||
|
const code = String(o.code || '').toLowerCase()
|
||||||
|
|
||||||
|
return name.includes(kw) || code.includes(kw)
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
|
||||||
|
openDrawer() {
|
||||||
|
|
||||||
|
if (this.disabled) return
|
||||||
|
|
||||||
|
this.keyword = ''
|
||||||
|
|
||||||
|
this.showPopup = true
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
onPick(item) {
|
||||||
|
|
||||||
|
this.$emit('input', item.code || '')
|
||||||
|
|
||||||
|
this.$emit('change', item)
|
||||||
|
|
||||||
|
this.showPopup = false
|
||||||
|
|
||||||
@@ -1,5 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<u-popup v-model="show" mode="bottom" border-radius="20" height="85%" :closeable="true" @close="onClose">
|
<drawer-page-container
|
||||||
|
v-model="show"
|
||||||
|
title="处方详情"
|
||||||
|
height="85%"
|
||||||
|
tall
|
||||||
|
:show-close="true"
|
||||||
|
:closeable="true"
|
||||||
|
@close="onClose"
|
||||||
|
>
|
||||||
<view class="popup-wrap">
|
<view class="popup-wrap">
|
||||||
<scroll-view v-if="view" scroll-y class="popup-scroll">
|
<scroll-view v-if="view" scroll-y class="popup-scroll">
|
||||||
<view class="container safe-area-inset-bottom">
|
<view class="container safe-area-inset-bottom">
|
||||||
@@ -164,16 +172,18 @@
|
|||||||
<view v-else-if="loading" class="popup-loading">加载中...</view>
|
<view v-else-if="loading" class="popup-loading">加载中...</view>
|
||||||
<view v-else class="popup-loading">暂无数据</view>
|
<view v-else class="popup-loading">暂无数据</view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</drawer-page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import DrawerPageContainer from '@/subPackages/sub_business_shared/components/DrawerPageContainer.vue'
|
||||||
import { mapPrescriptionDetailView } from '@/subPackages/sub_business_shared/common/display.js'
|
import { mapPrescriptionDetailView } from '@/subPackages/sub_business_shared/common/display.js'
|
||||||
|
|
||||||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [businessMixin],
|
mixins: [businessMixin],
|
||||||
|
components: { DrawerPageContainer },
|
||||||
name: 'PrescriptionDetailPopup',
|
name: 'PrescriptionDetailPopup',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -217,6 +227,7 @@ export default {
|
|||||||
},
|
},
|
||||||
onClose() {
|
onClose() {
|
||||||
this.view = null
|
this.view = null
|
||||||
|
this.show = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,108 @@
|
|||||||
|
<template>
|
||||||
|
<drawer-page-container
|
||||||
|
v-model="show"
|
||||||
|
title="订单退款"
|
||||||
|
height="55%"
|
||||||
|
:show-close="true"
|
||||||
|
:closeable="false"
|
||||||
|
@close="onClose"
|
||||||
|
>
|
||||||
|
<view class="popup-wrap">
|
||||||
|
<view class="order-no" v-if="orderNo">订单号:{{ orderNo }}</view>
|
||||||
|
<view class="field">
|
||||||
|
<text class="label">退款原因</text>
|
||||||
|
<textarea
|
||||||
|
class="textarea"
|
||||||
|
v-model="refundReason"
|
||||||
|
placeholder="请输入退款原因"
|
||||||
|
placeholder-style="color:#BDBDBD"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="footer">
|
||||||
|
<button class="btn-cancel" @click="onClose">取消</button>
|
||||||
|
<button class="btn-submit" :loading="submitting" @click="submit">确认退款</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</drawer-page-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DrawerPageContainer from '@/subPackages/sub_business_shared/components/DrawerPageContainer.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'RefundOrderPopup',
|
||||||
|
components: { DrawerPageContainer },
|
||||||
|
props: {
|
||||||
|
bizApi: { type: Object, required: true },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
orderId: 0,
|
||||||
|
orderNo: '',
|
||||||
|
refundReason: '',
|
||||||
|
submitting: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open(order) {
|
||||||
|
this.orderId = order.id
|
||||||
|
this.orderNo = order.order_no || ''
|
||||||
|
this.refundReason = ''
|
||||||
|
this.show = true
|
||||||
|
},
|
||||||
|
onClose() {
|
||||||
|
this.show = false
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
if (!this.refundReason.trim()) {
|
||||||
|
uni.showToast({ title: '请输入退款原因', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.submitting = true
|
||||||
|
this.bizApi.refundOrder({
|
||||||
|
order_id: this.orderId,
|
||||||
|
refund_reason: this.refundReason.trim(),
|
||||||
|
}).then(w => {
|
||||||
|
if (w.ok) {
|
||||||
|
uni.showToast({ title: '已提交' })
|
||||||
|
this.show = false
|
||||||
|
this.$emit('success')
|
||||||
|
}
|
||||||
|
}).finally(() => { this.submitting = false })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.popup-wrap {
|
||||||
|
padding: 0 32rpx 32rpx;
|
||||||
|
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
.order-no { font-size: 26rpx; color: #86909C; margin-bottom: 24rpx; }
|
||||||
|
.field { margin-bottom: 24rpx; }
|
||||||
|
.label { display: block; font-size: 28rpx; color: #4E5969; margin-bottom: 12rpx; }
|
||||||
|
.textarea {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #F7F8FA;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
min-height: 160rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
.footer { display: flex; gap: 24rpx; margin-top: 32rpx; }
|
||||||
|
.footer button {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
}
|
||||||
|
.footer button::after { display: none; }
|
||||||
|
.btn-cancel { background: #fff; color: #4E5969; border: 1rpx solid #E5E6EB; }
|
||||||
|
.btn-submit { background: #F53F3F; color: #fff; }
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<template>
|
||||||
|
<drawer-page-container
|
||||||
|
v-model="show"
|
||||||
|
title="订单发货"
|
||||||
|
height="70%"
|
||||||
|
tall
|
||||||
|
:show-close="true"
|
||||||
|
:closeable="false"
|
||||||
|
@close="onClose"
|
||||||
|
>
|
||||||
|
<view class="popup-wrap">
|
||||||
|
<view class="order-no" v-if="orderNo">订单号:{{ orderNo }}</view>
|
||||||
|
<view class="form">
|
||||||
|
<view v-if="deliveryMethod === 0" class="field">
|
||||||
|
<text class="label">快递单号</text>
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
v-model="expressNo"
|
||||||
|
placeholder="请输入快递单号"
|
||||||
|
placeholder-style="color:#BDBDBD"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view v-if="deliveryMethod === 0" class="field">
|
||||||
|
<text class="label">快递公司</text>
|
||||||
|
<express-company-picker
|
||||||
|
v-model="expressCompanyCode"
|
||||||
|
:options="companyOptions"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="field">
|
||||||
|
<text class="label">发货备注</text>
|
||||||
|
<textarea
|
||||||
|
class="textarea"
|
||||||
|
v-model="introduce"
|
||||||
|
placeholder="选填"
|
||||||
|
placeholder-style="color:#BDBDBD"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
<view class="footer">
|
||||||
|
<button class="btn-cancel" @click="onClose">取消</button>
|
||||||
|
<button class="btn-submit" :loading="submitting" @click="submit">确认发货</button>
|
||||||
|
</view>
|
||||||
|
</view>
|
||||||
|
</drawer-page-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DrawerPageContainer from '@/subPackages/sub_business_shared/components/DrawerPageContainer.vue'
|
||||||
|
import ExpressCompanyPicker from './ExpressCompanyPicker.vue'
|
||||||
|
import { withWxKey } from '@/utils/wxListKey.js'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'ShipOrderPopup',
|
||||||
|
components: { DrawerPageContainer, ExpressCompanyPicker },
|
||||||
|
props: {
|
||||||
|
bizApi: { type: Object, required: true },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
show: false,
|
||||||
|
orderId: 0,
|
||||||
|
orderNo: '',
|
||||||
|
deliveryMethod: 0,
|
||||||
|
expressNo: '',
|
||||||
|
expressCompanyCode: '',
|
||||||
|
introduce: '',
|
||||||
|
companyOptions: [],
|
||||||
|
submitting: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
open(order) {
|
||||||
|
this.orderId = order.id
|
||||||
|
this.orderNo = order.order_no || ''
|
||||||
|
this.deliveryMethod = Number(order.delivery_method)
|
||||||
|
this.expressNo = ''
|
||||||
|
this.expressCompanyCode = ''
|
||||||
|
this.introduce = ''
|
||||||
|
this.companyOptions = []
|
||||||
|
this.show = true
|
||||||
|
if (this.deliveryMethod === 0) {
|
||||||
|
this.loadCompanies()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
loadCompanies() {
|
||||||
|
this.bizApi.getExpressCompaniesOption().then(w => {
|
||||||
|
const items = (w.data && (w.data.items || w.data)) || []
|
||||||
|
this.companyOptions = withWxKey(Array.isArray(items) ? items : [], 'id', 'ec')
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onClose() {
|
||||||
|
this.show = false
|
||||||
|
},
|
||||||
|
submit() {
|
||||||
|
if (this.deliveryMethod === 0) {
|
||||||
|
if (!this.expressNo.trim()) {
|
||||||
|
uni.showToast({ title: '请输入快递单号', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!this.expressCompanyCode) {
|
||||||
|
uni.showToast({ title: '请选择快递公司', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.submitting = true
|
||||||
|
const payload = {
|
||||||
|
order_id: this.orderId,
|
||||||
|
express_no: this.expressNo.trim(),
|
||||||
|
express_company_code: this.expressCompanyCode,
|
||||||
|
introduce: this.introduce.trim(),
|
||||||
|
}
|
||||||
|
this.bizApi.wareSendOrder(payload).then(w => {
|
||||||
|
if (w.ok) {
|
||||||
|
uni.showToast({ title: '发货成功' })
|
||||||
|
this.show = false
|
||||||
|
this.$emit('success')
|
||||||
|
}
|
||||||
|
}).finally(() => { this.submitting = false })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.popup-wrap {
|
||||||
|
padding: 0 32rpx 32rpx;
|
||||||
|
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||||
|
}
|
||||||
|
.order-no { font-size: 26rpx; color: #86909C; margin-bottom: 24rpx; }
|
||||||
|
.field { margin-bottom: 24rpx; }
|
||||||
|
.label { display: block; font-size: 28rpx; color: #4E5969; margin-bottom: 12rpx; }
|
||||||
|
.input {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #F7F8FA;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
min-height: 88rpx;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
padding: 0 24rpx;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
.textarea {
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #F7F8FA;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
min-height: 160rpx;
|
||||||
|
padding: 24rpx;
|
||||||
|
line-height: 1.5;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
.footer { display: flex; gap: 24rpx; margin-top: 32rpx; }
|
||||||
|
.footer button {
|
||||||
|
flex: 1;
|
||||||
|
margin: 0;
|
||||||
|
height: 88rpx;
|
||||||
|
line-height: 88rpx;
|
||||||
|
border-radius: 44rpx;
|
||||||
|
font-size: 30rpx;
|
||||||
|
}
|
||||||
|
.footer button::after { display: none; }
|
||||||
|
.btn-cancel { background: #fff; color: #4E5969; border: 1rpx solid #E5E6EB; }
|
||||||
|
.btn-submit { background: #6acdbb; color: #fff; }
|
||||||
|
</style>
|
||||||
@@ -21,7 +21,8 @@
|
|||||||
<view class="row"><text>医生</text><text>{{ doctorName }}</text></view>
|
<view class="row"><text>医生</text><text>{{ doctorName }}</text></view>
|
||||||
<view class="row"><text>处方来源</text><text>{{ storeName }}</text></view>
|
<view class="row"><text>处方来源</text><text>{{ storeName }}</text></view>
|
||||||
<view class="row"><text>订单来源</text><text>{{ onlineText(info.is_online) }}</text></view>
|
<view class="row"><text>订单来源</text><text>{{ onlineText(info.is_online) }}</text></view>
|
||||||
<view class="row"><text>ERP状态</text><text>{{ erpSyncText(info.is_sync_erp) }}</text></view>
|
<view class="row" v-if="canViewErp"><text>ERP状态</text><text>{{ erpSyncText(info.is_sync_erp) }}</text></view>
|
||||||
|
<view class="row" v-if="detailExtra.prescriptionStatusText"><text>处方状态</text><text>{{ detailExtra.prescriptionStatusText }}</text></view>
|
||||||
<view class="row"><text>支付时间</text><text>{{ info.pay_time || '-' }}</text></view>
|
<view class="row"><text>支付时间</text><text>{{ info.pay_time || '-' }}</text></view>
|
||||||
<view class="row"><text>下单时间</text><text>{{ info.created_at || '-' }}</text></view>
|
<view class="row"><text>下单时间</text><text>{{ info.created_at || '-' }}</text></view>
|
||||||
<view class="row" v-if="detailExtra.cancelRemark"><text>发货备注</text><text>{{ detailExtra.cancelRemark }}</text></view>
|
<view class="row" v-if="detailExtra.cancelRemark"><text>发货备注</text><text>{{ detailExtra.cancelRemark }}</text></view>
|
||||||
@@ -29,11 +30,20 @@
|
|||||||
|
|
||||||
<view class="sub-block">
|
<view class="sub-block">
|
||||||
<view class="sub-block-title">费用明细</view>
|
<view class="sub-block-title">费用明细</view>
|
||||||
<view class="row"><text>药品总价</text><text>{{ money(info.items_price) }}</text></view>
|
<view class="row">
|
||||||
|
<text>药品总价</text>
|
||||||
|
<text
|
||||||
|
v-if="canOrderPercentAdjust"
|
||||||
|
class="price-link"
|
||||||
|
@click="openOrderPriceAdjust"
|
||||||
|
>{{ money(info.items_price) }}</text>
|
||||||
|
<text v-else>{{ money(info.items_price) }}</text>
|
||||||
|
</view>
|
||||||
<view class="row"><text>供货价</text><text>{{ detailExtra.marketPrice }}</text></view>
|
<view class="row"><text>供货价</text><text>{{ detailExtra.marketPrice }}</text></view>
|
||||||
<view class="row"><text>运费</text><text>{{ money(info.trans_expenses) }}</text></view>
|
<view class="row"><text>运费</text><text>{{ money(info.trans_expenses) }}</text></view>
|
||||||
<view class="row"><text>挂号费</text><text>{{ money(info.register_price) }}</text></view>
|
<view class="row"><text>挂号费</text><text>{{ money(info.register_price) }}</text></view>
|
||||||
<view class="row"><text>是否包邮</text><text>{{ detailExtra.freeShippingText }}</text></view>
|
<view class="row"><text>是否包邮</text><text>{{ detailExtra.freeShippingText }}</text></view>
|
||||||
|
<view class="row" v-if="orderPriceDiscountLabel"><text>价格浮动</text><text>{{ orderPriceDiscountLabel }}</text></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -124,16 +134,33 @@
|
|||||||
|
|
||||||
<!-- 底部悬浮操作栏 -->
|
<!-- 底部悬浮操作栏 -->
|
||||||
<view class="actions-bar" v-if="info.id">
|
<view class="actions-bar" v-if="info.id">
|
||||||
<button class="btn-outline" @click="goTrace">订单追溯</button>
|
<button
|
||||||
<button v-if="canShip" class="btn-primary" @click="ship">发货</button>
|
v-if="detailExtra.canViewPrescription"
|
||||||
<button v-if="canRefund" class="btn-warn" @click="refund">退款</button>
|
class="btn-outline btn-left"
|
||||||
<button v-if="detailExtra.canViewPrescription" class="btn-primary btn-full" @click="openPrescription">查看处方</button>
|
@click="openPrescription"
|
||||||
|
>查看处方</button>
|
||||||
|
<button
|
||||||
|
class="btn-primary btn-more"
|
||||||
|
:class="{ 'btn-full': !detailExtra.canViewPrescription }"
|
||||||
|
@click="openMoreMenu"
|
||||||
|
>更多</button>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 处方弹窗 -->
|
<!-- 处方弹窗 -->
|
||||||
<prescription-detail-popup ref="prescriptionPopup" />
|
<prescription-detail-popup ref="prescriptionPopup" />
|
||||||
|
<ship-order-popup ref="shipPopup" :biz-api="bizApi" @success="load" />
|
||||||
|
<refund-order-popup ref="refundPopup" :biz-api="bizApi" @success="load" />
|
||||||
|
<erp-sync-popup ref="erpPopup" :biz-api="bizApi" @success="load" />
|
||||||
|
<order-price-percent-adjust-popup
|
||||||
|
:visible="priceAdjustVisible"
|
||||||
|
:current-discount="info.price_discount || 100"
|
||||||
|
:quick-options="priceAdjustQuickOptions"
|
||||||
|
:submitting="priceAdjustSubmitting"
|
||||||
|
@close="closePriceAdjust"
|
||||||
|
@confirm="onSharedPriceAdjustConfirm"
|
||||||
|
/>
|
||||||
</business-page-layout>
|
</business-page-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -141,6 +168,9 @@
|
|||||||
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue'
|
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue'
|
||||||
import ExpressTimeline from './components/ExpressTimeline.vue'
|
import ExpressTimeline from './components/ExpressTimeline.vue'
|
||||||
import PrescriptionDetailPopup from './components/PrescriptionDetailPopup.vue'
|
import PrescriptionDetailPopup from './components/PrescriptionDetailPopup.vue'
|
||||||
|
import ShipOrderPopup from './components/ShipOrderPopup.vue'
|
||||||
|
import RefundOrderPopup from './components/RefundOrderPopup.vue'
|
||||||
|
import ErpSyncPopup from './components/ErpSyncPopup.vue'
|
||||||
import {
|
import {
|
||||||
orderStatusText,
|
orderStatusText,
|
||||||
deliveryMethodText,
|
deliveryMethodText,
|
||||||
@@ -153,10 +183,20 @@ import {
|
|||||||
import { formatMoney } from '@/subPackages/sub_business_shared/common/format.js'
|
import { formatMoney } from '@/subPackages/sub_business_shared/common/format.js'
|
||||||
|
|
||||||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||||||
|
import { orderPriceAdjustMixin } from '@/subPackages/sub_business_shared/common/orderPriceAdjustMixin.js'
|
||||||
|
import OrderPricePercentAdjustPopup from '@/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup.vue'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [businessMixin],
|
mixins: [businessMixin, orderPriceAdjustMixin],
|
||||||
components: { BusinessPageLayout, ExpressTimeline, PrescriptionDetailPopup },
|
components: {
|
||||||
|
BusinessPageLayout,
|
||||||
|
ExpressTimeline,
|
||||||
|
PrescriptionDetailPopup,
|
||||||
|
ShipOrderPopup,
|
||||||
|
RefundOrderPopup,
|
||||||
|
ErpSyncPopup,
|
||||||
|
OrderPricePercentAdjustPopup,
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -213,13 +253,19 @@ export default {
|
|||||||
return (s && s.position) ? s.position : '-'
|
return (s && s.position) ? s.position : '-'
|
||||||
},
|
},
|
||||||
canRefund() {
|
canRefund() {
|
||||||
return false
|
if (!this.bizCap.canRefund) return false
|
||||||
// if (!this.bizCap.canRefund) return false
|
return Number(this.info.is_pay) === 1
|
||||||
// return Number(this.info.is_pay) === 1
|
|
||||||
},
|
},
|
||||||
canShip() {
|
canShip() {
|
||||||
return false
|
return this.bizCap.canShip
|
||||||
// return this.bizCap.canShip && Number(this.info.status) === 1 && Number(this.info.is_pay) === 1
|
&& Number(this.info.status) === 1
|
||||||
|
&& Number(this.info.is_pay) === 1
|
||||||
|
},
|
||||||
|
canViewErp() {
|
||||||
|
return !!this.bizCap.canShip
|
||||||
|
},
|
||||||
|
canViewErpMenu() {
|
||||||
|
return this.canViewErp && Number(this.info.prescription_type) === 1
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onLoad(q) {
|
onLoad(q) {
|
||||||
@@ -232,6 +278,7 @@ export default {
|
|||||||
if (!w.ok) return
|
if (!w.ok) return
|
||||||
this.info = w.data || {}
|
this.info = w.data || {}
|
||||||
this.detailExtra = mapOrderDetailDisplay(this.info)
|
this.detailExtra = mapOrderDetailDisplay(this.info)
|
||||||
|
this.loadPriceAdjustMeta(() => this.bizApi.getPriceAdjustConfig(this.info.store_id))
|
||||||
if (this.info.delivery_method === 0) {
|
if (this.info.delivery_method === 0) {
|
||||||
this.loadExpress()
|
this.loadExpress()
|
||||||
}
|
}
|
||||||
@@ -242,6 +289,11 @@ export default {
|
|||||||
if (w.ok) this.expressInfo = w.data
|
if (w.ok) this.expressInfo = w.data
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
onSharedPriceAdjustConfirm(payload) {
|
||||||
|
return this.onPriceAdjustConfirm(payload, {
|
||||||
|
adjustOrderPercent: (data) => this.bizApi.adjustOrderPercent(data),
|
||||||
|
})
|
||||||
|
},
|
||||||
money(v) {
|
money(v) {
|
||||||
return formatMoney(v)
|
return formatMoney(v)
|
||||||
},
|
},
|
||||||
@@ -260,33 +312,30 @@ export default {
|
|||||||
erpSyncText(v) {
|
erpSyncText(v) {
|
||||||
return erpSyncText(v)
|
return erpSyncText(v)
|
||||||
},
|
},
|
||||||
refund() {
|
openMoreMenu() {
|
||||||
uni.showModal({
|
const items = ['订单溯源']
|
||||||
title: '确认退款',
|
const handlers = [() => this.goTrace()]
|
||||||
confirmColor: '#F53F3F',
|
if (this.canOrderPercentAdjust && Number(this.info.price_discount) !== 100) {
|
||||||
success: (r) => {
|
items.push('清除浮动')
|
||||||
if (!r.confirm) return
|
handlers.push(() => this.clearOrderPriceDiscount({ adjustOrderPercent: (data) => this.bizApi.adjustOrderPercent(data) }))
|
||||||
this.bizApi.refundOrder({ id: this.id, refund_reason: '管理员退款' }).then(w => {
|
|
||||||
if (w.ok) {
|
|
||||||
uni.showToast({ title: '已提交' })
|
|
||||||
this.load()
|
|
||||||
}
|
}
|
||||||
})
|
if (this.canShip) {
|
||||||
},
|
items.push('发货')
|
||||||
})
|
handlers.push(() => this.$refs.shipPopup.open(this.info))
|
||||||
},
|
|
||||||
ship() {
|
|
||||||
uni.showModal({
|
|
||||||
title: '确认发货',
|
|
||||||
content: '确定将该订单标记为已发货?',
|
|
||||||
success: (r) => {
|
|
||||||
if (!r.confirm) return
|
|
||||||
this.bizApi.wareSendOrder({ id: this.id, cancel_remark: '' }).then(w => {
|
|
||||||
if (w.ok) {
|
|
||||||
uni.showToast({ title: '发货成功' })
|
|
||||||
this.load()
|
|
||||||
}
|
}
|
||||||
})
|
if (this.canRefund) {
|
||||||
|
items.push('退款')
|
||||||
|
handlers.push(() => this.$refs.refundPopup.open(this.info))
|
||||||
|
}
|
||||||
|
if (this.canViewErpMenu) {
|
||||||
|
items.push('ERP同步记录')
|
||||||
|
handlers.push(() => this.$refs.erpPopup.open(this.info))
|
||||||
|
}
|
||||||
|
uni.showActionSheet({
|
||||||
|
itemList: items,
|
||||||
|
success: (res) => {
|
||||||
|
const fn = handlers[res.tapIndex]
|
||||||
|
if (fn) fn()
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
@@ -534,6 +583,7 @@ $color-danger: #F53F3F;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.price-main { font-size: 28rpx; color: $color-text-title; font-weight: 600; }
|
.price-main { font-size: 28rpx; color: $color-text-title; font-weight: 600; }
|
||||||
|
.price-link { color: $theme-primary; text-decoration: underline; }
|
||||||
|
|
||||||
/* ================= 底部动作栏 ================= */
|
/* ================= 底部动作栏 ================= */
|
||||||
.actions-bar {
|
.actions-bar {
|
||||||
@@ -562,6 +612,15 @@ $color-danger: #F53F3F;
|
|||||||
|
|
||||||
.actions-bar button::after { display: none; }
|
.actions-bar button::after { display: none; }
|
||||||
|
|
||||||
|
.btn-left {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-more {
|
||||||
|
width: 220rpx;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* 主按钮使用 flex: 1 铺满 */
|
/* 主按钮使用 flex: 1 铺满 */
|
||||||
.btn-full {
|
.btn-full {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
<view class="order-list-container">
|
<view class="order-list-container">
|
||||||
<!-- 顶部筛选 -->
|
<!-- 顶部筛选 -->
|
||||||
<page-sticky-header>
|
<page-sticky-header>
|
||||||
|
<view class="quick-filter">
|
||||||
|
<view class="filter-row date-row">
|
||||||
|
<picker mode="date" :value="dateStart" @change="onStart">
|
||||||
|
<view class="filter-picker date-picker">{{ dateStart || '开始日期' }}</view>
|
||||||
|
</picker>
|
||||||
|
<text class="sep">至</text>
|
||||||
|
<picker mode="date" :value="dateEnd" @change="onEnd">
|
||||||
|
<view class="filter-picker date-picker">{{ dateEnd || '结束日期' }}</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
<view class="filter-query-btn" @click="reload">查询</view>
|
||||||
|
</view>
|
||||||
<collapsible-filter-panel title="订单筛选" @clear="resetFilter">
|
<collapsible-filter-panel title="订单筛选" @clear="resetFilter">
|
||||||
<view class="filter-row">
|
<view class="filter-row">
|
||||||
<text class="filter-label">订单号</text>
|
<text class="filter-label">订单号</text>
|
||||||
@@ -26,16 +38,6 @@
|
|||||||
<view class="filter-picker">{{ prescriptionTypeLabels[prescriptionTypeIndex] }}</view>
|
<view class="filter-picker">{{ prescriptionTypeLabels[prescriptionTypeIndex] }}</view>
|
||||||
</picker>
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
<view class="filter-row date-row">
|
|
||||||
<picker mode="date" :value="dateStart" @change="onStart">
|
|
||||||
<view class="filter-picker date-picker">{{ dateStart || '开始日期' }}</view>
|
|
||||||
</picker>
|
|
||||||
<text class="sep">至</text>
|
|
||||||
<picker mode="date" :value="dateEnd" @change="onEnd">
|
|
||||||
<view class="filter-picker date-picker">{{ dateEnd || '结束日期' }}</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
|
||||||
<view class="filter-query-btn" @click="reload">查询</view>
|
|
||||||
</collapsible-filter-panel>
|
</collapsible-filter-panel>
|
||||||
</page-sticky-header>
|
</page-sticky-header>
|
||||||
|
|
||||||
@@ -152,6 +154,7 @@ import {
|
|||||||
clearOrderFilter,
|
clearOrderFilter,
|
||||||
defaultOrderFilter,
|
defaultOrderFilter,
|
||||||
} from '@/subPackages/sub_business_shared/common/filterCache.js'
|
} from '@/subPackages/sub_business_shared/common/filterCache.js'
|
||||||
|
import { defaultTodayDate } from '@/subPackages/sub_business_shared/common/reconciliationParams.js'
|
||||||
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
import businessMixin from '@/subPackages/sub_business_shared/common/businessMixin.js'
|
||||||
|
|
||||||
const DELIVERY_OPTIONS = [
|
const DELIVERY_OPTIONS = [
|
||||||
@@ -207,7 +210,7 @@ export default {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.reload()
|
this.checkDateEndAndReload()
|
||||||
},
|
},
|
||||||
onReachBottom() {
|
onReachBottom() {
|
||||||
this.load(this.page + 1, true)
|
this.load(this.page + 1, true)
|
||||||
@@ -236,6 +239,24 @@ export default {
|
|||||||
clearOrderFilter()
|
clearOrderFilter()
|
||||||
this.reload()
|
this.reload()
|
||||||
},
|
},
|
||||||
|
checkDateEndAndReload() {
|
||||||
|
const today = defaultTodayDate()
|
||||||
|
if (this.dateEnd && this.dateEnd !== today) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '筛选结束日期不是今天,是否更新到今天?',
|
||||||
|
success: (r) => {
|
||||||
|
if (r.confirm) {
|
||||||
|
this.dateEnd = today
|
||||||
|
this.persistOrderFilter()
|
||||||
|
}
|
||||||
|
this.reload()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.reload()
|
||||||
|
},
|
||||||
filterValues() {
|
filterValues() {
|
||||||
const st = this.statusOptions[this.statusIndex]
|
const st = this.statusOptions[this.statusIndex]
|
||||||
const del = DELIVERY_OPTIONS[this.deliveryIndex]
|
const del = DELIVERY_OPTIONS[this.deliveryIndex]
|
||||||
@@ -321,6 +342,13 @@ $color-text-muted: #86909C;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 筛选区 */
|
/* 筛选区 */
|
||||||
|
.quick-filter {
|
||||||
|
background: #fff;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
.filter-row {
|
.filter-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<view class="row" v-if="summary.register_order_no"><text class="label">挂号订单</text><text class="value">{{ summary.register_order_no }}</text></view>
|
<view class="row" v-if="summary.register_order_no"><text class="label">挂号订单</text><text class="value">{{ summary.register_order_no }}</text></view>
|
||||||
<view class="row" v-if="summary.prescription_no"><text class="label">处方号</text><text class="value">{{ summary.prescription_no }}</text></view>
|
<view class="row" v-if="summary.prescription_no"><text class="label">处方号</text><text class="value">{{ summary.prescription_no }}</text></view>
|
||||||
<view class="row"><text class="label">结算状态</text><text class="value">{{ summary.is_settled === 1 ? '已结算' : '未结算' }}</text></view>
|
<view class="row"><text class="label">结算状态</text><text class="value">{{ summary.is_settled === 1 ? '已结算' : '未结算' }}</text></view>
|
||||||
|
<view class="row" v-if="prescriptionStatusLabel"><text class="label">审方状态</text><text class="value status-audit">{{ prescriptionStatusLabel }}</text></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 主 Tab -->
|
<!-- 主 Tab -->
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
<!-- 时间线 -->
|
<!-- 时间线 -->
|
||||||
<view v-show="activeTab === 'timeline'" class="tab-panel">
|
<view v-show="activeTab === 'timeline'" class="tab-panel">
|
||||||
<view v-if="!timeline.length" class="empty">暂无时间线数据</view>
|
<view v-if="!timeline.length" class="empty">暂无时间线数据</view>
|
||||||
<view v-for="(node, idx) in timeline" :key="node.key || idx" class="timeline-item">
|
<view v-for="node in timeline" :key="node._wxKey" class="timeline-item">
|
||||||
<view class="tl-dot" />
|
<view class="tl-dot" />
|
||||||
<view class="tl-body">
|
<view class="tl-body">
|
||||||
<view class="tl-title">{{ node.title }}</view>
|
<view class="tl-title">{{ node.title }}</view>
|
||||||
@@ -43,6 +44,15 @@
|
|||||||
<view v-show="activeTab === 'ledger'" class="tab-panel">
|
<view v-show="activeTab === 'ledger'" class="tab-panel">
|
||||||
<view v-if="ledgerLoading" class="loading-tip">加载分账...</view>
|
<view v-if="ledgerLoading" class="loading-tip">加载分账...</view>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
<view v-if="isPlatformAdmin" class="scope-tabs">
|
||||||
|
<view
|
||||||
|
v-for="tab in ledgerScopeTabs"
|
||||||
|
:key="tab.key"
|
||||||
|
class="scope-tab"
|
||||||
|
:class="{ active: ledgerScope === tab.key }"
|
||||||
|
@click="switchLedgerScope(tab.key)"
|
||||||
|
>{{ tab.label }}</view>
|
||||||
|
</view>
|
||||||
<view v-if="hasRegisterLedger" class="sub-tabs">
|
<view v-if="hasRegisterLedger" class="sub-tabs">
|
||||||
<view
|
<view
|
||||||
class="sub-tab"
|
class="sub-tab"
|
||||||
@@ -55,6 +65,9 @@
|
|||||||
@click="switchLedgerSub('register')"
|
@click="switchLedgerSub('register')"
|
||||||
>挂号订单</view>
|
>挂号订单</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="currentLedgerSumMoney != null" class="ledger-sum">
|
||||||
|
合计分账:{{ formatMoney(currentLedgerSumMoney) }}
|
||||||
|
</view>
|
||||||
<view v-if="currentLedgerItems.length">
|
<view v-if="currentLedgerItems.length">
|
||||||
<view v-for="row in currentLedgerItems" :key="row._wxKey" class="detail-card">
|
<view v-for="row in currentLedgerItems" :key="row._wxKey" class="detail-card">
|
||||||
<view class="row"><text class="label">费用类型</text><text class="value">{{ row.fee_type_txt || '-' }}</text></view>
|
<view class="row"><text class="label">费用类型</text><text class="value">{{ row.fee_type_txt || '-' }}</text></view>
|
||||||
@@ -170,6 +183,7 @@ export default {
|
|||||||
traceData: null,
|
traceData: null,
|
||||||
activeTab: 'timeline',
|
activeTab: 'timeline',
|
||||||
ledgerSubTab: 'product',
|
ledgerSubTab: 'product',
|
||||||
|
ledgerScope: 'store',
|
||||||
ledgerProduct: null,
|
ledgerProduct: null,
|
||||||
ledgerRegister: null,
|
ledgerRegister: null,
|
||||||
ledgerLoading: false,
|
ledgerLoading: false,
|
||||||
@@ -181,14 +195,24 @@ export default {
|
|||||||
{ key: 'ledger', label: '分账' },
|
{ key: 'ledger', label: '分账' },
|
||||||
{ key: 'reconciliation', label: '对账' },
|
{ key: 'reconciliation', label: '对账' },
|
||||||
],
|
],
|
||||||
|
ledgerScopeTabs: [
|
||||||
|
{ key: 'all', label: '全部' },
|
||||||
|
{ key: 'store', label: '门店' },
|
||||||
|
{ key: 'platform', label: '平台' },
|
||||||
|
],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isPlatformAdmin() {
|
||||||
|
if (this.bizCtx && this.bizCtx.mode === 'platform_admin') return true
|
||||||
|
return !!(this.traceData && this.traceData.meta && this.traceData.meta.is_platform_admin)
|
||||||
|
},
|
||||||
summary() {
|
summary() {
|
||||||
return this.traceData?.summary || {}
|
return this.traceData?.summary || {}
|
||||||
},
|
},
|
||||||
timeline() {
|
timeline() {
|
||||||
return this.traceData?.timeline || []
|
const items = this.traceData?.timeline || []
|
||||||
|
return withWxKey(items, 'key', 'tl')
|
||||||
},
|
},
|
||||||
links() {
|
links() {
|
||||||
return this.traceData?.links || {}
|
return this.traceData?.links || {}
|
||||||
@@ -196,11 +220,26 @@ export default {
|
|||||||
hasRegisterLedger() {
|
hasRegisterLedger() {
|
||||||
return !!(this.links.register_id && this.ledgerRegister !== null)
|
return !!(this.links.register_id && this.ledgerRegister !== null)
|
||||||
},
|
},
|
||||||
|
currentLedgerData() {
|
||||||
|
return this.ledgerSubTab === 'register' ? this.ledgerRegister : this.ledgerProduct
|
||||||
|
},
|
||||||
|
currentLedgerSumMoney() {
|
||||||
|
const data = this.currentLedgerData
|
||||||
|
if (!data || data.sum_money == null || data.sum_money === '') return null
|
||||||
|
return data.sum_money
|
||||||
|
},
|
||||||
currentLedgerItems() {
|
currentLedgerItems() {
|
||||||
const data = this.ledgerSubTab === 'register' ? this.ledgerRegister : this.ledgerProduct
|
const items = this.currentLedgerData?.items || []
|
||||||
const items = data?.items || []
|
|
||||||
return withWxKey(items, 'id', 'traceLed')
|
return withWxKey(items, 'id', 'traceLed')
|
||||||
},
|
},
|
||||||
|
prescriptionStatusLabel() {
|
||||||
|
if (!this.summary.prescription_id) return ''
|
||||||
|
const auditNode = (this.timeline || []).find(
|
||||||
|
n => n.key === 'prescription_audited' || n.key === 'prescription_rejected'
|
||||||
|
)
|
||||||
|
if (auditNode && auditNode.status) return auditNode.status
|
||||||
|
return '待审核'
|
||||||
|
},
|
||||||
reconciliationItems() {
|
reconciliationItems() {
|
||||||
const items = this.reconciliationData?.items || []
|
const items = this.reconciliationData?.items || []
|
||||||
return withWxKey(items, 'id', 'traceRec')
|
return withWxKey(items, 'id', 'traceRec')
|
||||||
@@ -228,6 +267,18 @@ export default {
|
|||||||
this.bizApi.getOrderTrace({ scene: this.scene, order_id: this.orderId }).then(w => {
|
this.bizApi.getOrderTrace({ scene: this.scene, order_id: this.orderId }).then(w => {
|
||||||
if (w.ok) {
|
if (w.ok) {
|
||||||
this.traceData = w.data
|
this.traceData = w.data
|
||||||
|
this.loadedTabs = {}
|
||||||
|
const meta = w.data && w.data.meta
|
||||||
|
let scope = 'store'
|
||||||
|
if (this.bizCtx && this.bizCtx.mode === 'platform_admin') {
|
||||||
|
scope = 'all'
|
||||||
|
} else if (meta && meta.is_platform_admin) {
|
||||||
|
scope = 'all'
|
||||||
|
}
|
||||||
|
if (meta && meta.is_platform_admin === false) {
|
||||||
|
scope = 'store'
|
||||||
|
}
|
||||||
|
this.ledgerScope = scope
|
||||||
} else {
|
} else {
|
||||||
uni.showToast({ title: (w.res && w.res.message) || '加载失败', icon: 'none' })
|
uni.showToast({ title: (w.res && w.res.message) || '加载失败', icon: 'none' })
|
||||||
}
|
}
|
||||||
@@ -235,7 +286,7 @@ export default {
|
|||||||
},
|
},
|
||||||
switchMainTab(key) {
|
switchMainTab(key) {
|
||||||
this.activeTab = key
|
this.activeTab = key
|
||||||
if (key === 'ledger' && !this.loadedTabs.ledger) {
|
if (key === 'ledger' && !this.loadedTabs[`ledger:${this.ledgerScope}`]) {
|
||||||
this.loadLedger()
|
this.loadLedger()
|
||||||
} else if (key === 'reconciliation' && !this.loadedTabs.reconciliation) {
|
} else if (key === 'reconciliation' && !this.loadedTabs.reconciliation) {
|
||||||
this.loadReconciliation()
|
this.loadReconciliation()
|
||||||
@@ -244,18 +295,27 @@ export default {
|
|||||||
switchLedgerSub(key) {
|
switchLedgerSub(key) {
|
||||||
this.ledgerSubTab = key
|
this.ledgerSubTab = key
|
||||||
},
|
},
|
||||||
loadLedger() {
|
switchLedgerScope(scope) {
|
||||||
|
if (this.ledgerScope === scope) return
|
||||||
|
this.ledgerScope = scope
|
||||||
|
this.loadedTabs[`ledger:${scope}`] = false
|
||||||
|
this.loadLedger(true)
|
||||||
|
},
|
||||||
|
loadLedger(force = false) {
|
||||||
|
const cacheKey = `ledger:${this.ledgerScope}`
|
||||||
|
if (!force && this.loadedTabs[cacheKey]) return
|
||||||
const productId = this.links.product_order_id
|
const productId = this.links.product_order_id
|
||||||
const registerId = this.links.register_id
|
const registerId = this.links.register_id
|
||||||
if (!productId && !registerId) {
|
if (!productId && !registerId) {
|
||||||
this.loadedTabs.ledger = true
|
this.loadedTabs[cacheKey] = true
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.ledgerLoading = true
|
this.ledgerLoading = true
|
||||||
|
const scope = this.ledgerScope
|
||||||
const reqs = []
|
const reqs = []
|
||||||
if (productId) {
|
if (productId) {
|
||||||
reqs.push(
|
reqs.push(
|
||||||
this.bizApi.getOrderLedgerDetail({ order_id: productId, order_type: 1, scope: 'store' }).then(w => {
|
this.bizApi.getOrderLedgerDetail({ order_id: productId, order_type: 1, scope }).then(w => {
|
||||||
this.ledgerProduct = w.ok ? w.data : null
|
this.ledgerProduct = w.ok ? w.data : null
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -264,7 +324,7 @@ export default {
|
|||||||
}
|
}
|
||||||
if (registerId) {
|
if (registerId) {
|
||||||
reqs.push(
|
reqs.push(
|
||||||
this.bizApi.getOrderLedgerDetail({ order_id: registerId, order_type: 2, scope: 'store' }).then(w => {
|
this.bizApi.getOrderLedgerDetail({ order_id: registerId, order_type: 2, scope }).then(w => {
|
||||||
this.ledgerRegister = w.ok ? w.data : null
|
this.ledgerRegister = w.ok ? w.data : null
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -273,7 +333,7 @@ export default {
|
|||||||
}
|
}
|
||||||
Promise.all(reqs).finally(() => {
|
Promise.all(reqs).finally(() => {
|
||||||
this.ledgerLoading = false
|
this.ledgerLoading = false
|
||||||
this.loadedTabs.ledger = true
|
this.loadedTabs[cacheKey] = true
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
loadReconciliation() {
|
loadReconciliation() {
|
||||||
@@ -343,6 +403,32 @@ export default {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background: #e6f4ff;
|
background: #e6f4ff;
|
||||||
}
|
}
|
||||||
|
.scope-tabs {
|
||||||
|
display: flex;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 12rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.scope-tab {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 16rpx 0;
|
||||||
|
font-size: 26rpx;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.scope-tab.active {
|
||||||
|
color: #1677ff;
|
||||||
|
font-weight: 600;
|
||||||
|
background: #e6f4ff;
|
||||||
|
}
|
||||||
|
.ledger-sum {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 8rpx 8rpx 16rpx;
|
||||||
|
}
|
||||||
|
.status-audit { color: #6acdbb; }
|
||||||
.tab-panel { min-height: 200rpx; }
|
.tab-panel { min-height: 200rpx; }
|
||||||
.timeline-item {
|
.timeline-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -1,15 +1,27 @@
|
|||||||
<template>
|
<template>
|
||||||
<u-popup v-model="show" mode="center" border-radius="16" width="85%" :closeable="true" @close="onClose">
|
<u-popup v-model="show" mode="center" border-radius="16" width="85%" :closeable="true" @close="onClose">
|
||||||
<view class="modal">
|
<view class="modal">
|
||||||
<view class="modal-title">修改售价</view>
|
<view class="modal-title">{{ isPlatform ? '修改价格' : '修改售价' }}</view>
|
||||||
<view v-if="drugName" class="modal-drug">{{ drugName }}</view>
|
<view v-if="drugName" class="modal-drug">{{ drugName }}</view>
|
||||||
<view v-if="suggestPrice" class="modal-hint">建议售价 {{ suggestPrice }}</view>
|
<view v-if="suggestPrice && !isPlatform" class="modal-hint">建议售价 {{ suggestPrice }}</view>
|
||||||
|
<view v-if="isPlatform" class="field-block">
|
||||||
|
<text class="field-label">供货价</text>
|
||||||
|
<input
|
||||||
|
class="modal-input"
|
||||||
|
type="digit"
|
||||||
|
v-model="marketPriceInput"
|
||||||
|
placeholder="请输入供货价"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
|
<view class="field-block">
|
||||||
|
<text class="field-label">{{ isPlatform ? '建议售价' : '售价' }}</text>
|
||||||
<input
|
<input
|
||||||
class="modal-input"
|
class="modal-input"
|
||||||
type="digit"
|
type="digit"
|
||||||
v-model="priceInput"
|
v-model="priceInput"
|
||||||
placeholder="请输入新售价"
|
placeholder="请输入新售价"
|
||||||
/>
|
/>
|
||||||
|
</view>
|
||||||
<view class="modal-actions">
|
<view class="modal-actions">
|
||||||
<button class="btn cancel" @click="onClose">取消</button>
|
<button class="btn cancel" @click="onClose">取消</button>
|
||||||
<button class="btn confirm" :loading="submitting" @click="onConfirm">确定</button>
|
<button class="btn confirm" :loading="submitting" @click="onConfirm">确定</button>
|
||||||
@@ -32,10 +44,14 @@ export default {
|
|||||||
return {
|
return {
|
||||||
show: false,
|
show: false,
|
||||||
priceInput: '',
|
priceInput: '',
|
||||||
|
marketPriceInput: '',
|
||||||
submitting: false,
|
submitting: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
isPlatform() {
|
||||||
|
return this.bizCap.warehouseType === 'platform'
|
||||||
|
},
|
||||||
drugName() {
|
drugName() {
|
||||||
const drug = (this.item && this.item.drug) || {}
|
const drug = (this.item && this.item.drug) || {}
|
||||||
return drug.drug_name || ''
|
return drug.drug_name || ''
|
||||||
@@ -57,6 +73,8 @@ export default {
|
|||||||
if (v && this.item) {
|
if (v && this.item) {
|
||||||
const raw = this.item.price
|
const raw = this.item.price
|
||||||
this.priceInput = raw != null && raw !== '' ? String(raw) : ''
|
this.priceInput = raw != null && raw !== '' ? String(raw) : ''
|
||||||
|
const supply = this.item.market_price ?? this.item.buy_price
|
||||||
|
this.marketPriceInput = supply != null && supply !== '' ? String(supply) : ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -76,6 +94,18 @@ export default {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const rounded = Math.round(price * 100) / 100
|
const rounded = Math.round(price * 100) / 100
|
||||||
|
if (this.isPlatform) {
|
||||||
|
const marketPrice = parseFloat(String(this.marketPriceInput).trim())
|
||||||
|
if (!marketPrice || marketPrice <= 0 || Number.isNaN(marketPrice)) {
|
||||||
|
uni.showToast({ title: '请输入有效供货价', icon: 'none' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.$emit('confirm', {
|
||||||
|
price: rounded,
|
||||||
|
market_price: Math.round(marketPrice * 100) / 100,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
this.$emit('confirm', rounded)
|
this.$emit('confirm', rounded)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -104,8 +134,16 @@ export default {
|
|||||||
margin-top: 8rpx;
|
margin-top: 8rpx;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
.field-block {
|
||||||
|
margin-top: 24rpx;
|
||||||
|
}
|
||||||
|
.field-label {
|
||||||
|
display: block;
|
||||||
|
font-size: 24rpx;
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 8rpx;
|
||||||
|
}
|
||||||
.modal-input {
|
.modal-input {
|
||||||
margin-top: 32rpx;
|
|
||||||
padding: 20rpx 24rpx;
|
padding: 20rpx 24rpx;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
border-radius: 12rpx;
|
border-radius: 12rpx;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@
|
|||||||
<!-- 底部操作按钮组 -->
|
<!-- 底部操作按钮组 -->
|
||||||
<view v-if="info.id" class="action-group">
|
<view v-if="info.id" class="action-group">
|
||||||
<view class="btn-large btn-outline" hover-class="btn-hover-opacity" @click="openPriceEdit">
|
<view class="btn-large btn-outline" hover-class="btn-hover-opacity" @click="openPriceEdit">
|
||||||
修改售价
|
{{ isPlatformWarehouse ? '修改价格' : '修改售价' }}
|
||||||
</view>
|
</view>
|
||||||
<view
|
<view
|
||||||
class="btn-large"
|
class="btn-large"
|
||||||
@@ -86,7 +86,10 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
display() {
|
display() {
|
||||||
return mapWarehouseRow(this.info)
|
return mapWarehouseRow(this.info, this.bizCap.warehouseType || 'store')
|
||||||
|
},
|
||||||
|
isPlatformWarehouse() {
|
||||||
|
return this.bizCap.warehouseType === 'platform'
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
onLoad(q) {
|
onLoad(q) {
|
||||||
@@ -105,10 +108,13 @@ export default {
|
|||||||
closePriceEdit() {
|
closePriceEdit() {
|
||||||
this.priceModalVisible = false
|
this.priceModalVisible = false
|
||||||
},
|
},
|
||||||
onPriceConfirm(price) {
|
onPriceConfirm(payload) {
|
||||||
if (this.priceSubmitting) return
|
if (this.priceSubmitting) return
|
||||||
this.priceSubmitting = true
|
this.priceSubmitting = true
|
||||||
this.bizApi.updateWarehousePrice({ id: this.id, price }).then(w => {
|
const data = typeof payload === 'object' && payload !== null
|
||||||
|
? { id: this.id, ...payload }
|
||||||
|
: { id: this.id, price: payload }
|
||||||
|
this.bizApi.updateWarehousePrice(data).then(w => {
|
||||||
if (w.ok) {
|
if (w.ok) {
|
||||||
uni.showToast({ title: '修改成功' })
|
uni.showToast({ title: '修改成功' })
|
||||||
this.closePriceEdit()
|
this.closePriceEdit()
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export default {
|
|||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
rowDisplay(item) {
|
rowDisplay(item) {
|
||||||
return mapWarehouseRow(item)
|
return mapWarehouseRow(item, this.bizCap.warehouseType || 'store')
|
||||||
},
|
},
|
||||||
reload() {
|
reload() {
|
||||||
this.page = 1
|
this.page = 1
|
||||||
@@ -131,10 +131,13 @@ export default {
|
|||||||
this.priceModalVisible = false
|
this.priceModalVisible = false
|
||||||
this.priceEditItem = null
|
this.priceEditItem = null
|
||||||
},
|
},
|
||||||
onPriceConfirm(price) {
|
onPriceConfirm(payload) {
|
||||||
if (!this.priceEditItem || this.priceSubmitting) return
|
if (!this.priceEditItem || this.priceSubmitting) return
|
||||||
this.priceSubmitting = true
|
this.priceSubmitting = true
|
||||||
this.bizApi.updateWarehousePrice({ id: this.priceEditItem.id, price }).then(w => {
|
const data = typeof payload === 'object' && payload !== null
|
||||||
|
? { id: this.priceEditItem.id, ...payload }
|
||||||
|
: { id: this.priceEditItem.id, price: payload }
|
||||||
|
this.bizApi.updateWarehousePrice(data).then(w => {
|
||||||
if (w.ok) {
|
if (w.ok) {
|
||||||
uni.showToast({ title: '修改成功' })
|
uni.showToast({ title: '修改成功' })
|
||||||
this.closePriceEdit()
|
this.closePriceEdit()
|
||||||
|
|||||||
@@ -135,6 +135,11 @@ export function erpSyncText(v) {
|
|||||||
return '未同步'
|
return '未同步'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function prescriptionStatusText(status) {
|
||||||
|
const m = { 0: '待审核', 1: '已审核', 2: '已驳回', 3: '已过期' }
|
||||||
|
return m[status] != null ? m[status] : '-'
|
||||||
|
}
|
||||||
|
|
||||||
export function freeShippingText(v) {
|
export function freeShippingText(v) {
|
||||||
if (v === 1) return '包邮'
|
if (v === 1) return '包邮'
|
||||||
return '不包邮'
|
return '不包邮'
|
||||||
@@ -238,6 +243,7 @@ export function mapOrderProductItem(p, prescriptionType) {
|
|||||||
: '×' + (p.number != null ? p.number : 0)
|
: '×' + (p.number != null ? p.number : 0)
|
||||||
const isTcm = prescriptionType === 1
|
const isTcm = prescriptionType === 1
|
||||||
return {
|
return {
|
||||||
|
id: p.id,
|
||||||
drugName: p.drug_name || drug.drug_name || '药品',
|
drugName: p.drug_name || drug.drug_name || '药品',
|
||||||
drugNumber: drug.drug_number || '',
|
drugNumber: drug.drug_number || '',
|
||||||
imageUrl: resolveDrugImage(p.drug_image || drug.image),
|
imageUrl: resolveDrugImage(p.drug_image || drug.image),
|
||||||
@@ -276,6 +282,9 @@ export function mapOrderDetailDisplay(info) {
|
|||||||
if (!info || !info.id) return {}
|
if (!info || !info.id) return {}
|
||||||
const freeShip = info.is_free_shipping === 1 || info.free_ship === 1
|
const freeShip = info.is_free_shipping === 1 || info.free_ship === 1
|
||||||
const canView = !!(info.p_id && info.order_type !== 2 && info.order_type !== 3)
|
const canView = !!(info.p_id && info.order_type !== 2 && info.order_type !== 3)
|
||||||
|
const presStatus = info.prescription && info.prescription.status != null
|
||||||
|
? info.prescription.status
|
||||||
|
: null
|
||||||
return {
|
return {
|
||||||
orderTypeText: orderTypeText(info.order_type),
|
orderTypeText: orderTypeText(info.order_type),
|
||||||
marketPrice: formatMoney(info.market_price),
|
marketPrice: formatMoney(info.market_price),
|
||||||
@@ -284,6 +293,7 @@ export function mapOrderDetailDisplay(info) {
|
|||||||
showPrescription: mapOrderPrescription(info),
|
showPrescription: mapOrderPrescription(info),
|
||||||
prescriptionId: info.p_id,
|
prescriptionId: info.p_id,
|
||||||
canViewPrescription: canView,
|
canViewPrescription: canView,
|
||||||
|
prescriptionStatusText: canView && presStatus != null ? prescriptionStatusText(presStatus) : '',
|
||||||
statusText: orderStatusText(info.status),
|
statusText: orderStatusText(info.status),
|
||||||
prescriptionTypeText: prescriptionTypeText(info.prescription_type),
|
prescriptionTypeText: prescriptionTypeText(info.prescription_type),
|
||||||
deliveryText: deliveryMethodText(info.delivery_method),
|
deliveryText: deliveryMethodText(info.delivery_method),
|
||||||
@@ -398,16 +408,20 @@ export function warehouseStatusText(status) {
|
|||||||
return '-'
|
return '-'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mapWarehouseRow(item) {
|
export function mapWarehouseRow(item, warehouseType = 'store') {
|
||||||
if (!item) return {}
|
if (!item) return {}
|
||||||
const drug = item.drug || {}
|
const drug = item.drug || {}
|
||||||
const storeDrug = drug.drug_store_drug || {}
|
const storeDrug = drug.drug_store_drug || {}
|
||||||
const suggestPrice = storeDrug.price != null ? storeDrug.price : drug.price
|
const suggestPrice = storeDrug.price != null ? storeDrug.price : drug.price
|
||||||
|
const isPlatform = warehouseType === 'platform'
|
||||||
|
const supplyRaw = isPlatform
|
||||||
|
? (item.market_price ?? item.buy_price)
|
||||||
|
: (item.buy_price ?? item.market_price)
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
drugName: drug.drug_name || '-',
|
drugName: drug.drug_name || '-',
|
||||||
imageUrl: resolveDrugImage(drug.image),
|
imageUrl: resolveDrugImage(drug.image),
|
||||||
buyPrice: formatMoney(item.buy_price),
|
buyPrice: formatMoney(supplyRaw),
|
||||||
price: formatMoney(item.price),
|
price: formatMoney(item.price),
|
||||||
suggestPrice: formatMoney(suggestPrice),
|
suggestPrice: formatMoney(suggestPrice),
|
||||||
stock: item.stock != null ? item.stock : '-',
|
stock: item.stock != null ? item.stock : '-',
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<u-popup v-model="show" mode="bottom" border-radius="20" height="85%" :closeable="true" @close="onClose">
|
<drawer-page-container
|
||||||
|
v-model="show"
|
||||||
|
title="处方详情"
|
||||||
|
height="85%"
|
||||||
|
tall
|
||||||
|
:show-close="true"
|
||||||
|
:closeable="true"
|
||||||
|
@close="onClose"
|
||||||
|
>
|
||||||
<view class="popup-wrap">
|
<view class="popup-wrap">
|
||||||
<scroll-view v-if="view" scroll-y class="popup-scroll">
|
<scroll-view v-if="view" scroll-y class="popup-scroll">
|
||||||
<view class="container safe-area-inset-bottom">
|
<view class="container safe-area-inset-bottom">
|
||||||
@@ -164,14 +172,16 @@
|
|||||||
<view v-else-if="loading" class="popup-loading">加载中...</view>
|
<view v-else-if="loading" class="popup-loading">加载中...</view>
|
||||||
<view v-else class="popup-loading">暂无数据</view>
|
<view v-else class="popup-loading">暂无数据</view>
|
||||||
</view>
|
</view>
|
||||||
</u-popup>
|
</drawer-page-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import DrawerPageContainer from '@/subPackages/sub_business_shared/components/DrawerPageContainer.vue'
|
||||||
import { getPrescriptionDetail } from '@/api/clinicAdmin.js'
|
import { getPrescriptionDetail } from '@/api/clinicAdmin.js'
|
||||||
import { mapPrescriptionDetailView } from '@/subPackages/sub_clinic_admin/common/display.js'
|
import { mapPrescriptionDetailView } from '@/subPackages/sub_clinic_admin/common/display.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { DrawerPageContainer },
|
||||||
name: 'PrescriptionDetailPopup',
|
name: 'PrescriptionDetailPopup',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -215,6 +225,7 @@ export default {
|
|||||||
},
|
},
|
||||||
onClose() {
|
onClose() {
|
||||||
this.view = null
|
this.view = null
|
||||||
|
this.show = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
<view class="row"><text>医生</text><text>{{ doctorName }}</text></view>
|
<view class="row"><text>医生</text><text>{{ doctorName }}</text></view>
|
||||||
<view class="row"><text>处方来源</text><text>{{ storeName }}</text></view>
|
<view class="row"><text>处方来源</text><text>{{ storeName }}</text></view>
|
||||||
<view class="row"><text>订单来源</text><text>{{ onlineText(info.is_online) }}</text></view>
|
<view class="row"><text>订单来源</text><text>{{ onlineText(info.is_online) }}</text></view>
|
||||||
<view class="row"><text>ERP状态</text><text>{{ erpSyncText(info.is_sync_erp) }}</text></view>
|
<view class="row" v-if="detailExtra.prescriptionStatusText"><text>处方状态</text><text>{{ detailExtra.prescriptionStatusText }}</text></view>
|
||||||
<view class="row"><text>支付时间</text><text>{{ info.pay_time || '-' }}</text></view>
|
<view class="row"><text>支付时间</text><text>{{ info.pay_time || '-' }}</text></view>
|
||||||
<view class="row"><text>下单时间</text><text>{{ info.created_at || '-' }}</text></view>
|
<view class="row"><text>下单时间</text><text>{{ info.created_at || '-' }}</text></view>
|
||||||
<view class="row" v-if="detailExtra.cancelRemark"><text>发货备注</text><text>{{ detailExtra.cancelRemark }}</text></view>
|
<view class="row" v-if="detailExtra.cancelRemark"><text>发货备注</text><text>{{ detailExtra.cancelRemark }}</text></view>
|
||||||
@@ -29,11 +29,20 @@
|
|||||||
|
|
||||||
<view class="sub-block">
|
<view class="sub-block">
|
||||||
<view class="sub-block-title">费用明细</view>
|
<view class="sub-block-title">费用明细</view>
|
||||||
<view class="row"><text>药品总价</text><text>{{ money(info.items_price) }}</text></view>
|
<view class="row">
|
||||||
|
<text>药品总价</text>
|
||||||
|
<text
|
||||||
|
v-if="canOrderPercentAdjust"
|
||||||
|
class="price-link"
|
||||||
|
@click="openOrderPriceAdjust"
|
||||||
|
>{{ money(info.items_price) }}</text>
|
||||||
|
<text v-else>{{ money(info.items_price) }}</text>
|
||||||
|
</view>
|
||||||
<view class="row"><text>供货价</text><text>{{ detailExtra.marketPrice }}</text></view>
|
<view class="row"><text>供货价</text><text>{{ detailExtra.marketPrice }}</text></view>
|
||||||
<view class="row"><text>运费</text><text>{{ money(info.trans_expenses) }}</text></view>
|
<view class="row"><text>运费</text><text>{{ money(info.trans_expenses) }}</text></view>
|
||||||
<view class="row"><text>挂号费</text><text>{{ money(info.register_price) }}</text></view>
|
<view class="row"><text>挂号费</text><text>{{ money(info.register_price) }}</text></view>
|
||||||
<view class="row"><text>是否包邮</text><text>{{ detailExtra.freeShippingText }}</text></view>
|
<view class="row"><text>是否包邮</text><text>{{ detailExtra.freeShippingText }}</text></view>
|
||||||
|
<view class="row" v-if="orderPriceDiscountLabel"><text>价格浮动</text><text>{{ orderPriceDiscountLabel }}</text></view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
@@ -124,6 +133,7 @@
|
|||||||
|
|
||||||
<!-- 底部悬浮操作栏 -->
|
<!-- 底部悬浮操作栏 -->
|
||||||
<view class="actions-bar" v-if="info.id">
|
<view class="actions-bar" v-if="info.id">
|
||||||
|
<button v-if="canOrderPercentAdjust && Number(info.price_discount) !== 100" class="btn-outline" @click="clearOrderPriceDiscount({ adjustOrderPercent })">清除浮动</button>
|
||||||
<button class="btn-outline" @click="goTrace">订单追溯</button>
|
<button class="btn-outline" @click="goTrace">订单追溯</button>
|
||||||
<button v-if="canRefund" class="btn-warn" @click="refund">申请退款</button>
|
<button v-if="canRefund" class="btn-warn" @click="refund">申请退款</button>
|
||||||
<button v-if="detailExtra.canViewPrescription" class="btn-primary btn-full" @click="openPrescription">查看处方</button>
|
<button v-if="detailExtra.canViewPrescription" class="btn-primary btn-full" @click="openPrescription">查看处方</button>
|
||||||
@@ -133,6 +143,14 @@
|
|||||||
|
|
||||||
<!-- 处方弹窗 -->
|
<!-- 处方弹窗 -->
|
||||||
<prescription-detail-popup ref="prescriptionPopup" />
|
<prescription-detail-popup ref="prescriptionPopup" />
|
||||||
|
<order-price-percent-adjust-popup
|
||||||
|
:visible="priceAdjustVisible"
|
||||||
|
:current-discount="info.price_discount || 100"
|
||||||
|
:quick-options="priceAdjustQuickOptions"
|
||||||
|
:submitting="priceAdjustSubmitting"
|
||||||
|
@close="closePriceAdjust"
|
||||||
|
@confirm="(p) => onPriceAdjustConfirm(p, { adjustOrderPercent })"
|
||||||
|
/>
|
||||||
</clinic-page-layout>
|
</clinic-page-layout>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -144,7 +162,11 @@ import {
|
|||||||
getOrderDetail,
|
getOrderDetail,
|
||||||
refundOrder,
|
refundOrder,
|
||||||
getExpressDetailByOrderId,
|
getExpressDetailByOrderId,
|
||||||
|
getPriceAdjustConfig,
|
||||||
|
adjustOrderPercent,
|
||||||
} from '@/api/clinicAdmin.js'
|
} from '@/api/clinicAdmin.js'
|
||||||
|
import { orderPriceAdjustMixin } from '@/subPackages/sub_business_shared/common/orderPriceAdjustMixin.js'
|
||||||
|
import OrderPricePercentAdjustPopup from '@/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup.vue'
|
||||||
import {
|
import {
|
||||||
orderStatusText,
|
orderStatusText,
|
||||||
deliveryMethodText,
|
deliveryMethodText,
|
||||||
@@ -157,7 +179,8 @@ import {
|
|||||||
import { formatMoney } from '@/subPackages/sub_clinic_admin/common/format.js'
|
import { formatMoney } from '@/subPackages/sub_clinic_admin/common/format.js'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: { ClinicPageLayout, ExpressTimeline, PrescriptionDetailPopup },
|
mixins: [orderPriceAdjustMixin],
|
||||||
|
components: { ClinicPageLayout, ExpressTimeline, PrescriptionDetailPopup, OrderPricePercentAdjustPopup },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
id: 0,
|
id: 0,
|
||||||
@@ -227,6 +250,7 @@ export default {
|
|||||||
if (!w.ok) return
|
if (!w.ok) return
|
||||||
this.info = w.data || {}
|
this.info = w.data || {}
|
||||||
this.detailExtra = mapOrderDetailDisplay(this.info)
|
this.detailExtra = mapOrderDetailDisplay(this.info)
|
||||||
|
this.loadPriceAdjustMeta(() => getPriceAdjustConfig(this.info.store_id))
|
||||||
if (this.info.delivery_method === 0) {
|
if (this.info.delivery_method === 0) {
|
||||||
this.loadExpress()
|
this.loadExpress()
|
||||||
}
|
}
|
||||||
@@ -514,6 +538,7 @@ $color-danger: #F53F3F;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.price-main { font-size: 28rpx; color: $color-text-title; font-weight: 600; }
|
.price-main { font-size: 28rpx; color: $color-text-title; font-weight: 600; }
|
||||||
|
.price-link { color: $theme-primary; text-decoration: underline; }
|
||||||
|
|
||||||
/* ================= 底部动作栏 ================= */
|
/* ================= 底部动作栏 ================= */
|
||||||
.actions-bar {
|
.actions-bar {
|
||||||
|
|||||||
@@ -3,6 +3,18 @@
|
|||||||
<view class="order-list-container">
|
<view class="order-list-container">
|
||||||
<!-- 顶部筛选 -->
|
<!-- 顶部筛选 -->
|
||||||
<page-sticky-header>
|
<page-sticky-header>
|
||||||
|
<view class="quick-filter">
|
||||||
|
<view class="filter-row date-row">
|
||||||
|
<picker mode="date" :value="dateStart" @change="onStart">
|
||||||
|
<view class="filter-picker date-picker">{{ dateStart || '开始日期' }}</view>
|
||||||
|
</picker>
|
||||||
|
<text class="sep">至</text>
|
||||||
|
<picker mode="date" :value="dateEnd" @change="onEnd">
|
||||||
|
<view class="filter-picker date-picker">{{ dateEnd || '结束日期' }}</view>
|
||||||
|
</picker>
|
||||||
|
</view>
|
||||||
|
<view class="filter-query-btn" @click="reload">查询</view>
|
||||||
|
</view>
|
||||||
<collapsible-filter-panel title="订单筛选" @clear="resetFilter">
|
<collapsible-filter-panel title="订单筛选" @clear="resetFilter">
|
||||||
<view class="filter-row">
|
<view class="filter-row">
|
||||||
<text class="filter-label">订单号</text>
|
<text class="filter-label">订单号</text>
|
||||||
@@ -26,16 +38,6 @@
|
|||||||
<view class="filter-picker">{{ prescriptionTypeLabels[prescriptionTypeIndex] }}</view>
|
<view class="filter-picker">{{ prescriptionTypeLabels[prescriptionTypeIndex] }}</view>
|
||||||
</picker>
|
</picker>
|
||||||
</view>
|
</view>
|
||||||
<view class="filter-row date-row">
|
|
||||||
<picker mode="date" :value="dateStart" @change="onStart">
|
|
||||||
<view class="filter-picker date-picker">{{ dateStart || '开始日期' }}</view>
|
|
||||||
</picker>
|
|
||||||
<text class="sep">至</text>
|
|
||||||
<picker mode="date" :value="dateEnd" @change="onEnd">
|
|
||||||
<view class="filter-picker date-picker">{{ dateEnd || '结束日期' }}</view>
|
|
||||||
</picker>
|
|
||||||
</view>
|
|
||||||
<view class="filter-query-btn" @click="reload">查询</view>
|
|
||||||
</collapsible-filter-panel>
|
</collapsible-filter-panel>
|
||||||
</page-sticky-header>
|
</page-sticky-header>
|
||||||
|
|
||||||
@@ -153,6 +155,7 @@ import {
|
|||||||
clearOrderFilter,
|
clearOrderFilter,
|
||||||
defaultOrderFilter,
|
defaultOrderFilter,
|
||||||
} from '@/subPackages/sub_clinic_admin/common/filterCache.js'
|
} from '@/subPackages/sub_clinic_admin/common/filterCache.js'
|
||||||
|
import { defaultTodayDate } from '@/subPackages/sub_clinic_admin/common/reconciliationParams.js'
|
||||||
|
|
||||||
const DELIVERY_OPTIONS = [
|
const DELIVERY_OPTIONS = [
|
||||||
{ label: '全部', value: '' },
|
{ label: '全部', value: '' },
|
||||||
@@ -206,7 +209,7 @@ export default {
|
|||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.reload()
|
this.checkDateEndAndReload()
|
||||||
},
|
},
|
||||||
onReachBottom() {
|
onReachBottom() {
|
||||||
this.load(this.page + 1, true)
|
this.load(this.page + 1, true)
|
||||||
@@ -235,6 +238,24 @@ export default {
|
|||||||
clearOrderFilter()
|
clearOrderFilter()
|
||||||
this.reload()
|
this.reload()
|
||||||
},
|
},
|
||||||
|
checkDateEndAndReload() {
|
||||||
|
const today = defaultTodayDate()
|
||||||
|
if (this.dateEnd && this.dateEnd !== today) {
|
||||||
|
uni.showModal({
|
||||||
|
title: '提示',
|
||||||
|
content: '筛选结束日期不是今天,是否更新到今天?',
|
||||||
|
success: (r) => {
|
||||||
|
if (r.confirm) {
|
||||||
|
this.dateEnd = today
|
||||||
|
this.persistOrderFilter()
|
||||||
|
}
|
||||||
|
this.reload()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.reload()
|
||||||
|
},
|
||||||
filterValues() {
|
filterValues() {
|
||||||
const st = this.statusOptions[this.statusIndex]
|
const st = this.statusOptions[this.statusIndex]
|
||||||
const del = DELIVERY_OPTIONS[this.deliveryIndex]
|
const del = DELIVERY_OPTIONS[this.deliveryIndex]
|
||||||
@@ -320,6 +341,13 @@ $color-text-muted: #86909C;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* 筛选区 */
|
/* 筛选区 */
|
||||||
|
.quick-filter {
|
||||||
|
background: #fff;
|
||||||
|
padding: 24rpx;
|
||||||
|
margin-bottom: 16rpx;
|
||||||
|
border-radius: 16rpx;
|
||||||
|
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
.filter-row {
|
.filter-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<view class="row" v-if="summary.register_order_no"><text class="label">挂号订单</text><text class="value">{{ summary.register_order_no }}</text></view>
|
<view class="row" v-if="summary.register_order_no"><text class="label">挂号订单</text><text class="value">{{ summary.register_order_no }}</text></view>
|
||||||
<view class="row" v-if="summary.prescription_no"><text class="label">处方号</text><text class="value">{{ summary.prescription_no }}</text></view>
|
<view class="row" v-if="summary.prescription_no"><text class="label">处方号</text><text class="value">{{ summary.prescription_no }}</text></view>
|
||||||
<view class="row"><text class="label">结算状态</text><text class="value">{{ summary.is_settled === 1 ? '已结算' : '未结算' }}</text></view>
|
<view class="row"><text class="label">结算状态</text><text class="value">{{ summary.is_settled === 1 ? '已结算' : '未结算' }}</text></view>
|
||||||
|
<view class="row" v-if="prescriptionStatusLabel"><text class="label">审方状态</text><text class="value status-audit">{{ prescriptionStatusLabel }}</text></view>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 主 Tab -->
|
<!-- 主 Tab -->
|
||||||
@@ -26,7 +27,7 @@
|
|||||||
<!-- 时间线 -->
|
<!-- 时间线 -->
|
||||||
<view v-show="activeTab === 'timeline'" class="tab-panel">
|
<view v-show="activeTab === 'timeline'" class="tab-panel">
|
||||||
<view v-if="!timeline.length" class="empty">暂无时间线数据</view>
|
<view v-if="!timeline.length" class="empty">暂无时间线数据</view>
|
||||||
<view v-for="(node, idx) in timeline" :key="node.key || idx" class="timeline-item">
|
<view v-for="node in timeline" :key="node._wxKey" class="timeline-item">
|
||||||
<view class="tl-dot" />
|
<view class="tl-dot" />
|
||||||
<view class="tl-body">
|
<view class="tl-body">
|
||||||
<view class="tl-title">{{ node.title }}</view>
|
<view class="tl-title">{{ node.title }}</view>
|
||||||
@@ -55,6 +56,9 @@
|
|||||||
@click="switchLedgerSub('register')"
|
@click="switchLedgerSub('register')"
|
||||||
>挂号订单</view>
|
>挂号订单</view>
|
||||||
</view>
|
</view>
|
||||||
|
<view v-if="currentLedgerSumMoney != null" class="ledger-sum">
|
||||||
|
合计分账:{{ formatMoney(currentLedgerSumMoney) }}
|
||||||
|
</view>
|
||||||
<view v-if="currentLedgerItems.length">
|
<view v-if="currentLedgerItems.length">
|
||||||
<view v-for="row in currentLedgerItems" :key="row._wxKey" class="detail-card">
|
<view v-for="row in currentLedgerItems" :key="row._wxKey" class="detail-card">
|
||||||
<view class="row"><text class="label">费用类型</text><text class="value">{{ row.fee_type_txt || '-' }}</text></view>
|
<view class="row"><text class="label">费用类型</text><text class="value">{{ row.fee_type_txt || '-' }}</text></view>
|
||||||
@@ -190,7 +194,8 @@ export default {
|
|||||||
return this.traceData?.summary || {}
|
return this.traceData?.summary || {}
|
||||||
},
|
},
|
||||||
timeline() {
|
timeline() {
|
||||||
return this.traceData?.timeline || []
|
const items = this.traceData?.timeline || []
|
||||||
|
return withWxKey(items, 'key', 'tl')
|
||||||
},
|
},
|
||||||
links() {
|
links() {
|
||||||
return this.traceData?.links || {}
|
return this.traceData?.links || {}
|
||||||
@@ -198,11 +203,26 @@ export default {
|
|||||||
hasRegisterLedger() {
|
hasRegisterLedger() {
|
||||||
return !!(this.links.register_id && this.ledgerRegister !== null)
|
return !!(this.links.register_id && this.ledgerRegister !== null)
|
||||||
},
|
},
|
||||||
|
currentLedgerData() {
|
||||||
|
return this.ledgerSubTab === 'register' ? this.ledgerRegister : this.ledgerProduct
|
||||||
|
},
|
||||||
|
currentLedgerSumMoney() {
|
||||||
|
const data = this.currentLedgerData
|
||||||
|
if (!data || data.sum_money == null || data.sum_money === '') return null
|
||||||
|
return data.sum_money
|
||||||
|
},
|
||||||
currentLedgerItems() {
|
currentLedgerItems() {
|
||||||
const data = this.ledgerSubTab === 'register' ? this.ledgerRegister : this.ledgerProduct
|
const items = this.currentLedgerData?.items || []
|
||||||
const items = data?.items || []
|
|
||||||
return withWxKey(items, 'id', 'traceLed')
|
return withWxKey(items, 'id', 'traceLed')
|
||||||
},
|
},
|
||||||
|
prescriptionStatusLabel() {
|
||||||
|
if (!this.summary.prescription_id) return ''
|
||||||
|
const auditNode = (this.timeline || []).find(
|
||||||
|
n => n.key === 'prescription_audited' || n.key === 'prescription_rejected'
|
||||||
|
)
|
||||||
|
if (auditNode && auditNode.status) return auditNode.status
|
||||||
|
return '待审核'
|
||||||
|
},
|
||||||
reconciliationItems() {
|
reconciliationItems() {
|
||||||
const items = this.reconciliationData?.items || []
|
const items = this.reconciliationData?.items || []
|
||||||
return withWxKey(items, 'id', 'traceRec')
|
return withWxKey(items, 'id', 'traceRec')
|
||||||
@@ -345,6 +365,13 @@ export default {
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
background: #e6f4ff;
|
background: #e6f4ff;
|
||||||
}
|
}
|
||||||
|
.ledger-sum {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 8rpx 8rpx 16rpx;
|
||||||
|
}
|
||||||
|
.status-audit { color: #6acdbb; }
|
||||||
.tab-panel { min-height: 200rpx; }
|
.tab-panel { min-height: 200rpx; }
|
||||||
.timeline-item {
|
.timeline-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -136,7 +136,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<block v-if="lists.prescription_type==2||lists.prescription_type==5||lists.prescription_type==7">
|
<block v-if="lists.prescription_type==2||lists.prescription_type==5||lists.prescription_type==7">
|
||||||
<view class="item" v-for="(it,index) in infoList.repice" :key="it.id || index">
|
<view class="item" v-for="it in infoList.repice" :key="it._wxKey">
|
||||||
<!-- v-for="(it,index) in item.content" :key="it.id" -->
|
<!-- v-for="(it,index) in item.content" :key="it.id" -->
|
||||||
<view class="name" style="justify-content: space-between;">
|
<view class="name" style="justify-content: space-between;">
|
||||||
<view class="yp_name">
|
<view class="yp_name">
|
||||||
@@ -263,6 +263,7 @@
|
|||||||
prescripRecordDetail,
|
prescripRecordDetail,
|
||||||
getUseWay
|
getUseWay
|
||||||
} from "@/api/all.js";
|
} from "@/api/all.js";
|
||||||
|
import { withWxKey } from '@/utils/wxListKey.js'
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -350,7 +351,7 @@
|
|||||||
return num
|
return num
|
||||||
},
|
},
|
||||||
normalizeRepice(list) {
|
normalizeRepice(list) {
|
||||||
return (list || []).map((it) => {
|
const normalized = (list || []).map((it) => {
|
||||||
let content = it.content
|
let content = it.content
|
||||||
if (typeof content === 'string') {
|
if (typeof content === 'string') {
|
||||||
try {
|
try {
|
||||||
@@ -367,6 +368,7 @@
|
|||||||
content,
|
content,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return withWxKey(normalized, 'id', 'rp')
|
||||||
},
|
},
|
||||||
getInfo() {
|
getInfo() {
|
||||||
prescripRecordDetail({
|
prescripRecordDetail({
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { prepareImagePath } from '@/common/js/image-compress.js'
|
import { prepareImagePath } from '@/common/js/image-compress.js'
|
||||||
import { uploadLegacyAdminImage, uploadBusinessChatFileApi, extractUploadUrl } from '@/api/upload.js'
|
import { uploadToOss } from '@/common/js/oss-upload.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选择并上传图片(压缩 + old-admin-img 接口)
|
* 选择并上传图片(压缩 + OSS 直传)
|
||||||
* @param {{ count?: number }} options count>1 时依次上传多张,返回 url 数组
|
* @param {{ count?: number }} options count>1 时依次上传多张,返回 url 数组
|
||||||
*/
|
*/
|
||||||
export function pickAndUploadImage(options = {}) {
|
export function pickAndUploadImage(options = {}) {
|
||||||
@@ -26,11 +26,8 @@ export function pickAndUploadImage(options = {}) {
|
|||||||
size: files.find((f) => f.path === path)?.size,
|
size: files.find((f) => f.path === path)?.size,
|
||||||
})
|
})
|
||||||
if (!prepared) continue
|
if (!prepared) continue
|
||||||
const url = await uploadLegacyAdminImage({
|
const result = await uploadToOss({ filePath: prepared.path })
|
||||||
filePath: prepared.path,
|
urls.push(result.url)
|
||||||
name: 'file',
|
|
||||||
})
|
|
||||||
urls.push(url)
|
|
||||||
}
|
}
|
||||||
uni.hideLoading()
|
uni.hideLoading()
|
||||||
if (!urls.length) {
|
if (!urls.length) {
|
||||||
@@ -66,20 +63,11 @@ function getUserDataPath() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function uploadSignFilePath(filePath) {
|
async function uploadSignFilePath(filePath) {
|
||||||
let url = null
|
const result = await uploadToOss({ filePath })
|
||||||
try {
|
if (!result || !result.url) {
|
||||||
const res = await uploadBusinessChatFileApi({ filePath, name: 'file' })
|
|
||||||
url = extractUploadUrl(res)
|
|
||||||
} catch (e) {
|
|
||||||
// 回退与证照相同的上传接口
|
|
||||||
}
|
|
||||||
if (!url) {
|
|
||||||
url = await uploadLegacyAdminImage({ filePath, name: 'file' })
|
|
||||||
}
|
|
||||||
if (!url) {
|
|
||||||
throw new Error('上传成功但未返回地址')
|
throw new Error('上传成功但未返回地址')
|
||||||
}
|
}
|
||||||
return url
|
return result.url
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<block v-if="lists.prescription_type==2||lists.prescription_type==5||lists.prescription_type==7">
|
<block v-if="lists.prescription_type==2||lists.prescription_type==5||lists.prescription_type==7">
|
||||||
<view class="item" v-for="(it,index) in infoList.repice" :key="it.id || index">
|
<view class="item" v-for="it in infoList.repice" :key="it._wxKey">
|
||||||
<!-- v-for="(it,index) in item.content" :key="it.id" -->
|
<!-- v-for="(it,index) in item.content" :key="it.id" -->
|
||||||
<view class="name" style="justify-content: space-between;">
|
<view class="name" style="justify-content: space-between;">
|
||||||
<view class="yp_name">
|
<view class="yp_name">
|
||||||
@@ -207,6 +207,7 @@
|
|||||||
prescripDetail,
|
prescripDetail,
|
||||||
getUseWay
|
getUseWay
|
||||||
} from "@/api/all.js";
|
} from "@/api/all.js";
|
||||||
|
import { withWxKey } from '@/utils/wxListKey.js'
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -276,7 +277,7 @@
|
|||||||
return num
|
return num
|
||||||
},
|
},
|
||||||
normalizeRepice(list) {
|
normalizeRepice(list) {
|
||||||
return (list || []).map((it) => {
|
const normalized = (list || []).map((it) => {
|
||||||
let content = it.content
|
let content = it.content
|
||||||
if (typeof content === 'string') {
|
if (typeof content === 'string') {
|
||||||
try {
|
try {
|
||||||
@@ -293,6 +294,7 @@
|
|||||||
content,
|
content,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
return withWxKey(normalized, 'id', 'rp')
|
||||||
},
|
},
|
||||||
getInfo() {
|
getInfo() {
|
||||||
prescripDetail({
|
prescripDetail({
|
||||||
|
|||||||
@@ -376,7 +376,24 @@
|
|||||||
class="fs-24 color-primary m-t-8"
|
class="fs-24 color-primary m-t-8"
|
||||||
>毛利率 {{ grossMarginText }}%</text>
|
>毛利率 {{ grossMarginText }}%</text>
|
||||||
</view>
|
</view>
|
||||||
<text class="fs-30 color-title font-bold">¥{{totalProductCost.toFixed(2)}}</text>
|
<text
|
||||||
|
v-if="canPreSendPriceAdjust"
|
||||||
|
class="fs-30 color-title font-bold price-link"
|
||||||
|
@click="openPreSendOrderPriceAdjust"
|
||||||
|
>¥{{totalProductCost.toFixed(2)}}</text>
|
||||||
|
<text v-else class="fs-30 color-title font-bold">¥{{totalProductCost.toFixed(2)}}</text>
|
||||||
|
</view>
|
||||||
|
|
||||||
|
<view
|
||||||
|
v-if="canPreSendPriceAdjust && currentDrugs.length > 0"
|
||||||
|
class="flex-row flex-jus-sp flex-ali-center m-b-24"
|
||||||
|
>
|
||||||
|
<text class="fs-26 color-sub">当前浮动:{{ preSendDiscountLabel || '无' }}</text>
|
||||||
|
<text
|
||||||
|
v-if="priceDiscount !== 100"
|
||||||
|
class="fs-26 color-primary"
|
||||||
|
@click="clearPreSendPriceDiscount"
|
||||||
|
>清除浮动</text>
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<view
|
<view
|
||||||
@@ -501,6 +518,14 @@
|
|||||||
:border="true" />
|
:border="true" />
|
||||||
</view>
|
</view>
|
||||||
</u-modal>
|
</u-modal>
|
||||||
|
|
||||||
|
<order-price-percent-adjust-popup
|
||||||
|
:visible="priceAdjustVisible"
|
||||||
|
:current-discount="priceDiscount"
|
||||||
|
:quick-options="priceAdjustQuickOptions"
|
||||||
|
@close="closePreSendPriceAdjust"
|
||||||
|
@confirm="onPreSendPriceAdjustConfirm"
|
||||||
|
/>
|
||||||
</view>
|
</view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -519,6 +544,7 @@ import {
|
|||||||
saveWestCommonPrescriptionApi,
|
saveWestCommonPrescriptionApi,
|
||||||
saveChineseCommonPrescriptionApi,
|
saveChineseCommonPrescriptionApi,
|
||||||
getCurrentStoreTypeApi,
|
getCurrentStoreTypeApi,
|
||||||
|
getPriceAdjustConfigApi,
|
||||||
getSalespersonTransferByRegisterApi,
|
getSalespersonTransferByRegisterApi,
|
||||||
getPrescriptionInfoApi,
|
getPrescriptionInfoApi,
|
||||||
getTraditionalChineseMedicineJson
|
getTraditionalChineseMedicineJson
|
||||||
@@ -538,6 +564,8 @@ import WesternMedicineUsageModal from './components/modals/WesternMedicineUsageM
|
|||||||
import ChineseMedicineConfig from './components/ChineseMedicineConfig.vue';
|
import ChineseMedicineConfig from './components/ChineseMedicineConfig.vue';
|
||||||
import TraditionalTermPickerModal from './components/modals/TraditionalTermPickerModal.vue';
|
import TraditionalTermPickerModal from './components/modals/TraditionalTermPickerModal.vue';
|
||||||
import TransferPatientDrawer from './components/modals/TransferPatientDrawer.vue';
|
import TransferPatientDrawer from './components/modals/TransferPatientDrawer.vue';
|
||||||
|
import OrderPricePercentAdjustPopup from '@/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup.vue';
|
||||||
|
import { applyRatioToDrugs, formatPriceDiscountLabel, normalizeQuickOptions } from '@/subPackages/sub_business_shared/common/pricePercentAdjust.js';
|
||||||
import {
|
import {
|
||||||
createClinicSalespersonTransferPrescription,
|
createClinicSalespersonTransferPrescription,
|
||||||
getClinicSalespersonSeePriceConfig,
|
getClinicSalespersonSeePriceConfig,
|
||||||
@@ -574,7 +602,8 @@ export default {
|
|||||||
WesternMedicineUsageModal,
|
WesternMedicineUsageModal,
|
||||||
ChineseMedicineConfig,
|
ChineseMedicineConfig,
|
||||||
TraditionalTermPickerModal,
|
TraditionalTermPickerModal,
|
||||||
TransferPatientDrawer
|
TransferPatientDrawer,
|
||||||
|
OrderPricePercentAdjustPopup,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -639,6 +668,11 @@ export default {
|
|||||||
hasSalespersonTransfer: false,
|
hasSalespersonTransfer: false,
|
||||||
/** 是否可查看毛利率 0/1(xk-api see_rate) */
|
/** 是否可查看毛利率 0/1(xk-api see_rate) */
|
||||||
seeRate: 0,
|
seeRate: 0,
|
||||||
|
enableOrderPricePercentAdjust: 0,
|
||||||
|
priceAdjustVisible: false,
|
||||||
|
priceDiscount: 100,
|
||||||
|
priceAdjustQuickOptions: [],
|
||||||
|
priceAdjustScopeMode: 'sale_only',
|
||||||
sendMode: 0,
|
sendMode: 0,
|
||||||
selectedStoreId: null,
|
selectedStoreId: null,
|
||||||
registerOrderType: null,
|
registerOrderType: null,
|
||||||
@@ -703,6 +737,18 @@ export default {
|
|||||||
allowInsuranceCategory() {
|
allowInsuranceCategory() {
|
||||||
return Number(this.registerStoreInfo?.allow_insurance_category ?? 0) === 1;
|
return Number(this.registerStoreInfo?.allow_insurance_category ?? 0) === 1;
|
||||||
},
|
},
|
||||||
|
canPreSendPriceAdjust() {
|
||||||
|
return Number(this.enableOrderPricePercentAdjust) === 1 && this.currentDrugs.length > 0;
|
||||||
|
},
|
||||||
|
preSendDiscountLabel() {
|
||||||
|
return formatPriceDiscountLabel(this.priceDiscount);
|
||||||
|
},
|
||||||
|
effectivePriceAdjustStoreId() {
|
||||||
|
return this.selectedStoreId
|
||||||
|
|| this.registerStoreInfo?.store_id
|
||||||
|
|| uni.getStorageSync('store_id')
|
||||||
|
|| null;
|
||||||
|
},
|
||||||
totalProductCost() {
|
totalProductCost() {
|
||||||
if (this.currentDrugs.length === 0) return 0;
|
if (this.currentDrugs.length === 0) return 0;
|
||||||
if (this.activeCategory === 1) {
|
if (this.activeCategory === 1) {
|
||||||
@@ -1091,6 +1137,56 @@ export default {
|
|||||||
drug.buy_price
|
drug.buy_price
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
async loadPriceAdjustConfigForStore(storeId) {
|
||||||
|
try {
|
||||||
|
const res = await getPriceAdjustConfigApi(storeId);
|
||||||
|
if (res) {
|
||||||
|
this.priceAdjustScopeMode = res.order_price_adjust_scope === 'both' ? 'both' : 'sale_only';
|
||||||
|
this.priceAdjustQuickOptions = normalizeQuickOptions(res.order_discount_quick_options);
|
||||||
|
if (res.enable_order_price_percent_adjust != null) {
|
||||||
|
this.enableOrderPricePercentAdjust = Number(res.enable_order_price_percent_adjust);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('获取调价配置失败:', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
snapshotDrugOrigins(drugs) {
|
||||||
|
return (drugs || []).map((drug) => ({
|
||||||
|
...drug,
|
||||||
|
origin_price: drug.origin_price ?? drug.price,
|
||||||
|
origin_buy_price: drug.origin_buy_price ?? drug.buy_price,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
prepareDrugForCart(drug) {
|
||||||
|
const [prepared] = applyRatioToDrugs(
|
||||||
|
this.snapshotDrugOrigins([drug]),
|
||||||
|
this.priceDiscount,
|
||||||
|
this.priceAdjustScopeMode,
|
||||||
|
);
|
||||||
|
return prepared;
|
||||||
|
},
|
||||||
|
applyPreSendPriceDiscount(discount) {
|
||||||
|
this.priceDiscount = discount;
|
||||||
|
const scope = this.priceAdjustScopeMode;
|
||||||
|
this.currentDrugs = applyRatioToDrugs(this.snapshotDrugOrigins(this.currentDrugs), discount, scope);
|
||||||
|
uni.showToast({ title: discount === 100 ? '已清除浮动' : '调价成功', icon: 'none' });
|
||||||
|
},
|
||||||
|
clearPreSendPriceDiscount() {
|
||||||
|
this.applyPreSendPriceDiscount(100);
|
||||||
|
},
|
||||||
|
openPreSendOrderPriceAdjust() {
|
||||||
|
if (!this.canPreSendPriceAdjust) return;
|
||||||
|
this.currentDrugs = this.snapshotDrugOrigins(this.currentDrugs);
|
||||||
|
this.priceAdjustVisible = true;
|
||||||
|
},
|
||||||
|
closePreSendPriceAdjust() {
|
||||||
|
this.priceAdjustVisible = false;
|
||||||
|
},
|
||||||
|
onPreSendPriceAdjustConfirm(payload) {
|
||||||
|
this.applyPreSendPriceDiscount(payload.priceDiscount);
|
||||||
|
this.closePreSendPriceAdjust();
|
||||||
|
},
|
||||||
async fetchStoreSeeRate() {
|
async fetchStoreSeeRate() {
|
||||||
try {
|
try {
|
||||||
const storeId = uni.getStorageSync('store_id');
|
const storeId = uni.getStorageSync('store_id');
|
||||||
@@ -1107,6 +1203,8 @@ export default {
|
|||||||
const res = await getCurrentStoreTypeApi(params);
|
const res = await getCurrentStoreTypeApi(params);
|
||||||
if (res) {
|
if (res) {
|
||||||
this.seeRate = Number(res.see_rate ?? 0);
|
this.seeRate = Number(res.see_rate ?? 0);
|
||||||
|
this.enableOrderPricePercentAdjust = Number(res.enable_order_price_percent_adjust ?? 0);
|
||||||
|
await this.loadPriceAdjustConfigForStore(params.store_id || storeId);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取毛利率权限失败:', error);
|
console.error('获取毛利率权限失败:', error);
|
||||||
@@ -1137,6 +1235,9 @@ export default {
|
|||||||
this.selectedStoreId = res.store_id;
|
this.selectedStoreId = res.store_id;
|
||||||
this.sendMode = res.can_change ? 1 : 0;
|
this.sendMode = res.can_change ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
const priceStoreId = this.selectedStoreId || res.store_id || storeId;
|
||||||
|
this.enableOrderPricePercentAdjust = Number(res.enable_order_price_percent_adjust ?? 0);
|
||||||
|
await this.loadPriceAdjustConfigForStore(priceStoreId);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取挂号诊所信息失败:', error);
|
console.error('获取挂号诊所信息失败:', error);
|
||||||
@@ -1172,7 +1273,7 @@ export default {
|
|||||||
this.salespersonTransferPrescriptionId = item.id;
|
this.salespersonTransferPrescriptionId = item.id;
|
||||||
this.activeCategory = 1;
|
this.activeCategory = 1;
|
||||||
parsed.drugList.forEach((drug) => {
|
parsed.drugList.forEach((drug) => {
|
||||||
this.currentDrugs.push({
|
this.currentDrugs.push(this.prepareDrugForCart({
|
||||||
index_id: drug.index_id ?? drug.drug_id ?? drug.id,
|
index_id: drug.index_id ?? drug.drug_id ?? drug.id,
|
||||||
id: drug.drug_id || drug.id,
|
id: drug.drug_id || drug.id,
|
||||||
drug_id: drug.drug_id || drug.id,
|
drug_id: drug.drug_id || drug.id,
|
||||||
@@ -1182,7 +1283,7 @@ export default {
|
|||||||
way_id: drug.way_id || 0,
|
way_id: drug.way_id || 0,
|
||||||
price: drug.price ?? 0,
|
price: drug.price ?? 0,
|
||||||
buy_price: drug.buy_price,
|
buy_price: drug.buy_price,
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
if (parsed.clinical_diagnose) {
|
if (parsed.clinical_diagnose) {
|
||||||
this.diagnoses = [{ id: Date.now(), name: parsed.clinical_diagnose }];
|
this.diagnoses = [{ id: Date.now(), name: parsed.clinical_diagnose }];
|
||||||
@@ -1424,7 +1525,7 @@ export default {
|
|||||||
use_frequency: this.drugUseList.drug_use_frequency?.find((item) => item.id === (d.frequency_id || 0)),
|
use_frequency: this.drugUseList.drug_use_frequency?.find((item) => item.id === (d.frequency_id || 0)),
|
||||||
unit: this.drugUseList.drug_unit?.find((item) => item.id === (d.unit_id || 0))
|
unit: this.drugUseList.drug_unit?.find((item) => item.id === (d.unit_id || 0))
|
||||||
};
|
};
|
||||||
this.currentDrugs.push(newDrug);
|
this.currentDrugs.push(this.prepareDrugForCart(newDrug));
|
||||||
added++;
|
added++;
|
||||||
}
|
}
|
||||||
this.saveToLocalStorage();
|
this.saveToLocalStorage();
|
||||||
@@ -1440,7 +1541,7 @@ export default {
|
|||||||
if (PrescriptionValidator.isDrugExists(this.currentDrugs, product)) {
|
if (PrescriptionValidator.isDrugExists(this.currentDrugs, product)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
this.currentDrugs.push({ ...product });
|
this.currentDrugs.push(this.prepareDrugForCart({ ...product }));
|
||||||
added++;
|
added++;
|
||||||
}
|
}
|
||||||
this.saveToLocalStorage();
|
this.saveToLocalStorage();
|
||||||
@@ -1479,7 +1580,7 @@ export default {
|
|||||||
use_frequency: this.drugUseList.drug_use_frequency?.find(item => item.id === (drug.frequency_id || drug.drug?.frequency_id)),
|
use_frequency: this.drugUseList.drug_use_frequency?.find(item => item.id === (drug.frequency_id || drug.drug?.frequency_id)),
|
||||||
unit: this.drugUseList.drug_unit?.find(item => item.id === (drug.unit_id || drug.drug?.unit_id))
|
unit: this.drugUseList.drug_unit?.find(item => item.id === (drug.unit_id || drug.drug?.unit_id))
|
||||||
};
|
};
|
||||||
this.currentDrugs.push(newDrug);
|
this.currentDrugs.push(this.prepareDrugForCart(newDrug));
|
||||||
const index = this.currentDrugs.length - 1;
|
const index = this.currentDrugs.length - 1;
|
||||||
this.handleEditWesternDrug(this.currentDrugs[index], index);
|
this.handleEditWesternDrug(this.currentDrugs[index], index);
|
||||||
this.saveToLocalStorage();
|
this.saveToLocalStorage();
|
||||||
@@ -1517,7 +1618,11 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleSelectChineseDrug(drugs) {
|
handleSelectChineseDrug(drugs) {
|
||||||
this.currentDrugs = drugs || [];
|
this.currentDrugs = applyRatioToDrugs(
|
||||||
|
this.snapshotDrugOrigins(drugs || []),
|
||||||
|
this.priceDiscount,
|
||||||
|
this.priceAdjustScopeMode,
|
||||||
|
);
|
||||||
this.saveToLocalStorage();
|
this.saveToLocalStorage();
|
||||||
},
|
},
|
||||||
handleOpenSimpleProductModal() { this.showSimpleProductModal = true; },
|
handleOpenSimpleProductModal() { this.showSimpleProductModal = true; },
|
||||||
@@ -1537,7 +1642,7 @@ export default {
|
|||||||
type: product.drug?.type || product.type || this.activeCategory,
|
type: product.drug?.type || product.type || this.activeCategory,
|
||||||
select_number: 1
|
select_number: 1
|
||||||
};
|
};
|
||||||
this.currentDrugs.push(newProduct);
|
this.currentDrugs.push(this.prepareDrugForCart(newProduct));
|
||||||
this.saveToLocalStorage();
|
this.saveToLocalStorage();
|
||||||
this.$toast(`已添加 ${newProduct.drug_name}`);
|
this.$toast(`已添加 ${newProduct.drug_name}`);
|
||||||
},
|
},
|
||||||
@@ -1968,6 +2073,7 @@ export default {
|
|||||||
const params = {
|
const params = {
|
||||||
patient: patientData, drugs: drugsData, diagnosis: clinical_diagnose, medicalAdvice: this.medicalAdvice, total: parseFloat(this.totalPrice), category: this.feeCategory, drug_type: 2, register_id: parseInt(this.registerId), treatment_price: parseFloat(this.treatmentPrice || 0), prescription_type: this.activeCategory, doctor_second_sign: doctorSecondSignValue, send_mode: finalSendMode, custom_store_id: finalSendMode === 1 ? finalStoreId : null,
|
patient: patientData, drugs: drugsData, diagnosis: clinical_diagnose, medicalAdvice: this.medicalAdvice, total: parseFloat(this.totalPrice), category: this.feeCategory, drug_type: 2, register_id: parseInt(this.registerId), treatment_price: parseFloat(this.treatmentPrice || 0), prescription_type: this.activeCategory, doctor_second_sign: doctorSecondSignValue, send_mode: finalSendMode, custom_store_id: finalSendMode === 1 ? finalStoreId : null,
|
||||||
salesperson_transfer_prescription_id: this.salespersonTransferPrescriptionId || undefined,
|
salesperson_transfer_prescription_id: this.salespersonTransferPrescriptionId || undefined,
|
||||||
|
price_discount: this.priceDiscount,
|
||||||
};
|
};
|
||||||
if (this.activeCategory === 1) {
|
if (this.activeCategory === 1) {
|
||||||
params.package_method_id = this.chineseConfig.packageMethodId || null; params.process_rule_id = this.chineseConfig.processRuleId || null; params.process_rule_note_id = this.chineseConfig.processRuleNoteId || null; params.child_process_rule_id = this.chineseConfig.childProcessRuleId || null; params.process_rule_type = this.chineseConfig.ruleType || 1; params.processing_fee = this.processingFee; params.dosage = this.chineseConfig.dosage || 7; params.day_dosage = this.chineseConfig.dayDosage || 2;
|
params.package_method_id = this.chineseConfig.packageMethodId || null; params.process_rule_id = this.chineseConfig.processRuleId || null; params.process_rule_note_id = this.chineseConfig.processRuleNoteId || null; params.child_process_rule_id = this.chineseConfig.childProcessRuleId || null; params.process_rule_type = this.chineseConfig.ruleType || 1; params.processing_fee = this.processingFee; params.dosage = this.chineseConfig.dosage || 7; params.day_dosage = this.chineseConfig.dayDosage || 2;
|
||||||
@@ -2271,6 +2377,7 @@ export default {
|
|||||||
.color-primary { color: #00A88A; }
|
.color-primary { color: #00A88A; }
|
||||||
.color-danger { color: #FF4D4F; }
|
.color-danger { color: #FF4D4F; }
|
||||||
.color-price { color: #F53F3F; }
|
.color-price { color: #F53F3F; }
|
||||||
|
.price-link { color: #6ACDBB; text-decoration: underline; }
|
||||||
.text-gray { color: #6C7380; }
|
.text-gray { color: #6C7380; }
|
||||||
|
|
||||||
.white { background: #fff; }
|
.white { background: #fff; }
|
||||||
|
|||||||
@@ -25,6 +25,9 @@
|
|||||||
<text>{{item.sex %2 != 0 ? '男' : '女'}}</text>
|
<text>{{item.sex %2 != 0 ? '男' : '女'}}</text>
|
||||||
<text>{{item.age + '岁'}}</text>
|
<text>{{item.age + '岁'}}</text>
|
||||||
</view>
|
</view>
|
||||||
|
<view class="flex-row m-t-1">
|
||||||
|
<text class="fs-24 color-gray">推广员:{{ item.salesperson_name || '无' }}</text>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</view>
|
</view>
|
||||||
|
|||||||
Reference in New Issue
Block a user