diff --git a/api/businessApi.js b/api/businessApi.js
index 29de5d6..a371721 100644
--- a/api/businessApi.js
+++ b/api/businessApi.js
@@ -113,6 +113,21 @@ export function createBusinessApi(prefix, headerFlag) {
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) {
return bizRequest({ url: `${prefix}-warehouse/list`, method: 'GET', data: params });
},
diff --git a/api/clinicAdmin.js b/api/clinicAdmin.js
index d117dd4..e114b48 100644
--- a/api/clinicAdmin.js
+++ b/api/clinicAdmin.js
@@ -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) {
return clinicRequest({ url: `${PREFIX}-warehouse/list`, method: 'GET', data: params });
}
diff --git a/api/reception.js b/api/reception.js
index cf8704b..c12b8c0 100644
--- a/api/reception.js
+++ b/api/reception.js
@@ -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) {
return req.request({
diff --git a/api/upload.js b/api/upload.js
index ab122df..53af33e 100644
--- a/api/upload.js
+++ b/api/upload.js
@@ -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
*/
@@ -77,6 +87,44 @@ export function extractUploadUrl(res) {
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';
/**
diff --git a/common/js/oss-upload.js b/common/js/oss-upload.js
new file mode 100644
index 0000000..71d9a6e
--- /dev/null
+++ b/common/js/oss-upload.js
@@ -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);
+ }
+ });
+ }
+ });
+}
diff --git a/pages/workbench/index.vue b/pages/workbench/index.vue
index e5dbc1c..52788ec 100644
--- a/pages/workbench/index.vue
+++ b/pages/workbench/index.vue
@@ -86,7 +86,7 @@
{{ acceptingOfflineOrderText(item) }}
+ 推广员:{{ (item.salesperson && item.salesperson.nick_name) || '无' }}
@@ -629,6 +633,10 @@ export default {
const room = item.room_id != null && item.room_id !== '' ? String(item.room_id) : 'noroom';
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) {
const p = item.last_message_preview;
if (p != null && String(p).trim() !== '') return String(p);
diff --git a/subPackages/sub_business_shared/common/display.js b/subPackages/sub_business_shared/common/display.js
index 9231f04..2f166b0 100644
--- a/subPackages/sub_business_shared/common/display.js
+++ b/subPackages/sub_business_shared/common/display.js
@@ -135,6 +135,11 @@ export function erpSyncText(v) {
return '未同步'
}
+export function prescriptionStatusText(status) {
+ const m = { 0: '待审核', 1: '已审核', 2: '已驳回', 3: '已过期' }
+ return m[status] != null ? m[status] : '-'
+}
+
export function freeShippingText(v) {
if (v === 1) return '包邮'
return '不包邮'
@@ -238,6 +243,7 @@ export function mapOrderProductItem(p, prescriptionType) {
: '×' + (p.number != null ? p.number : 0)
const isTcm = prescriptionType === 1
return {
+ id: p.id,
drugName: p.drug_name || drug.drug_name || '药品',
drugNumber: drug.drug_number || '',
imageUrl: resolveDrugImage(p.drug_image || drug.image),
@@ -276,6 +282,9 @@ export function mapOrderDetailDisplay(info) {
if (!info || !info.id) return {}
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 presStatus = info.prescription && info.prescription.status != null
+ ? info.prescription.status
+ : null
return {
orderTypeText: orderTypeText(info.order_type),
marketPrice: formatMoney(info.market_price),
@@ -284,6 +293,7 @@ export function mapOrderDetailDisplay(info) {
showPrescription: mapOrderPrescription(info),
prescriptionId: info.p_id,
canViewPrescription: canView,
+ prescriptionStatusText: canView && presStatus != null ? prescriptionStatusText(presStatus) : '',
statusText: orderStatusText(info.status),
prescriptionTypeText: prescriptionTypeText(info.prescription_type),
deliveryText: deliveryMethodText(info.delivery_method),
@@ -398,16 +408,20 @@ export function warehouseStatusText(status) {
return '-'
}
-export function mapWarehouseRow(item) {
+export function mapWarehouseRow(item, warehouseType = 'store') {
if (!item) return {}
const drug = item.drug || {}
const storeDrug = drug.drug_store_drug || {}
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 {
id: item.id,
drugName: drug.drug_name || '-',
imageUrl: resolveDrugImage(drug.image),
- buyPrice: formatMoney(item.buy_price),
+ buyPrice: formatMoney(supplyRaw),
price: formatMoney(item.price),
suggestPrice: formatMoney(suggestPrice),
stock: item.stock != null ? item.stock : '-',
diff --git a/subPackages/sub_business_shared/common/orderPriceAdjustMixin.js b/subPackages/sub_business_shared/common/orderPriceAdjustMixin.js
new file mode 100644
index 0000000..8eef72a
--- /dev/null
+++ b/subPackages/sub_business_shared/common/orderPriceAdjustMixin.js
@@ -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'
+
diff --git a/subPackages/sub_business_shared/common/pricePercentAdjust.js b/subPackages/sub_business_shared/common/pricePercentAdjust.js
new file mode 100644
index 0000000..e014bf9
--- /dev/null
+++ b/subPackages/sub_business_shared/common/pricePercentAdjust.js
@@ -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)
+}
diff --git a/subPackages/sub_business_shared/components/DrawerPageContainer.vue b/subPackages/sub_business_shared/components/DrawerPageContainer.vue
new file mode 100644
index 0000000..0f23d68
--- /dev/null
+++ b/subPackages/sub_business_shared/components/DrawerPageContainer.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup.vue b/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup.vue
new file mode 100644
index 0000000..97150da
--- /dev/null
+++ b/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup.vue
@@ -0,0 +1,128 @@
+
+
+
+
+
+
+
+
+
diff --git a/subPackages/sub_business_shared/order/components/ErpSyncPopup.vue b/subPackages/sub_business_shared/order/components/ErpSyncPopup.vue
new file mode 100644
index 0000000..2f7d7e0
--- /dev/null
+++ b/subPackages/sub_business_shared/order/components/ErpSyncPopup.vue
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+
diff --git a/subPackages/sub_business_shared/order/components/ExpressCompanyPicker.vue b/subPackages/sub_business_shared/order/components/ExpressCompanyPicker.vue
new file mode 100644
index 0000000..7497c08
--- /dev/null
+++ b/subPackages/sub_business_shared/order/components/ExpressCompanyPicker.vue
@@ -0,0 +1,186 @@
+
+
+
+ {{ summaryText }}
+ ›
+
+
+
+
+
+
+
+
+ 无匹配快递公司
+
+
+ {{ item.name || '-' }}
+ {{ item.code || '' }}
+
+ ✓
+
+
+
+
+
+
+
+
+
+
diff --git a/subPackages/sub_business_shared/order/components/PrescriptionDetailPopup.vue b/subPackages/sub_business_shared/order/components/PrescriptionDetailPopup.vue
index 69cb2c5..37aa029 100644
--- a/subPackages/sub_business_shared/order/components/PrescriptionDetailPopup.vue
+++ b/subPackages/sub_business_shared/order/components/PrescriptionDetailPopup.vue
@@ -1,5 +1,13 @@
-
+
+
+
+
diff --git a/subPackages/sub_business_shared/order/components/ShipOrderPopup.vue b/subPackages/sub_business_shared/order/components/ShipOrderPopup.vue
new file mode 100644
index 0000000..4b146f4
--- /dev/null
+++ b/subPackages/sub_business_shared/order/components/ShipOrderPopup.vue
@@ -0,0 +1,166 @@
+
+
+
+
+
+
+
+
+
diff --git a/subPackages/sub_business_shared/order/detail.vue b/subPackages/sub_business_shared/order/detail.vue
index a4d4418..e60d12f 100644
--- a/subPackages/sub_business_shared/order/detail.vue
+++ b/subPackages/sub_business_shared/order/detail.vue
@@ -21,7 +21,8 @@
医生{{ doctorName }}
处方来源{{ storeName }}
订单来源{{ onlineText(info.is_online) }}
- ERP状态{{ erpSyncText(info.is_sync_erp) }}
+ ERP状态{{ erpSyncText(info.is_sync_erp) }}
+ 处方状态{{ detailExtra.prescriptionStatusText }}
支付时间{{ info.pay_time || '-' }}
下单时间{{ info.created_at || '-' }}
发货备注{{ detailExtra.cancelRemark }}
@@ -29,11 +30,20 @@
费用明细
- 药品总价{{ money(info.items_price) }}
+
+ 药品总价
+ {{ money(info.items_price) }}
+ {{ money(info.items_price) }}
+
供货价{{ detailExtra.marketPrice }}
运费{{ money(info.trans_expenses) }}
挂号费{{ money(info.register_price) }}
是否包邮{{ detailExtra.freeShippingText }}
+ 价格浮动{{ orderPriceDiscountLabel }}
@@ -124,16 +134,33 @@
-
-
-
-
+
+
+
+
+
+
@@ -141,6 +168,9 @@
import BusinessPageLayout from '@/subPackages/sub_business_shared/components/business-page-layout.vue'
import ExpressTimeline from './components/ExpressTimeline.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 {
orderStatusText,
deliveryMethodText,
@@ -153,10 +183,20 @@ import {
import { formatMoney } from '@/subPackages/sub_business_shared/common/format.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 {
- mixins: [businessMixin],
- components: { BusinessPageLayout, ExpressTimeline, PrescriptionDetailPopup },
+ mixins: [businessMixin, orderPriceAdjustMixin],
+ components: {
+ BusinessPageLayout,
+ ExpressTimeline,
+ PrescriptionDetailPopup,
+ ShipOrderPopup,
+ RefundOrderPopup,
+ ErpSyncPopup,
+ OrderPricePercentAdjustPopup,
+ },
data() {
return {
id: 0,
@@ -213,13 +253,19 @@ export default {
return (s && s.position) ? s.position : '-'
},
canRefund() {
- return false
- // if (!this.bizCap.canRefund) return false
- // return Number(this.info.is_pay) === 1
+ if (!this.bizCap.canRefund) return false
+ return Number(this.info.is_pay) === 1
},
canShip() {
- return false
- // return this.bizCap.canShip && Number(this.info.status) === 1 && Number(this.info.is_pay) === 1
+ return this.bizCap.canShip
+ && 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) {
@@ -232,6 +278,7 @@ export default {
if (!w.ok) return
this.info = w.data || {}
this.detailExtra = mapOrderDetailDisplay(this.info)
+ this.loadPriceAdjustMeta(() => this.bizApi.getPriceAdjustConfig(this.info.store_id))
if (this.info.delivery_method === 0) {
this.loadExpress()
}
@@ -242,6 +289,11 @@ export default {
if (w.ok) this.expressInfo = w.data
})
},
+ onSharedPriceAdjustConfirm(payload) {
+ return this.onPriceAdjustConfirm(payload, {
+ adjustOrderPercent: (data) => this.bizApi.adjustOrderPercent(data),
+ })
+ },
money(v) {
return formatMoney(v)
},
@@ -260,33 +312,30 @@ export default {
erpSyncText(v) {
return erpSyncText(v)
},
- refund() {
- uni.showModal({
- title: '确认退款',
- confirmColor: '#F53F3F',
- success: (r) => {
- if (!r.confirm) return
- this.bizApi.refundOrder({ id: this.id, refund_reason: '管理员退款' }).then(w => {
- if (w.ok) {
- uni.showToast({ title: '已提交' })
- this.load()
- }
- })
- },
- })
- },
- 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()
- }
- })
+ openMoreMenu() {
+ const items = ['订单溯源']
+ const handlers = [() => this.goTrace()]
+ if (this.canOrderPercentAdjust && Number(this.info.price_discount) !== 100) {
+ items.push('清除浮动')
+ handlers.push(() => this.clearOrderPriceDiscount({ adjustOrderPercent: (data) => this.bizApi.adjustOrderPercent(data) }))
+ }
+ if (this.canShip) {
+ items.push('发货')
+ handlers.push(() => this.$refs.shipPopup.open(this.info))
+ }
+ 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-link { color: $theme-primary; text-decoration: underline; }
/* ================= 底部动作栏 ================= */
.actions-bar {
@@ -562,6 +612,15 @@ $color-danger: #F53F3F;
.actions-bar button::after { display: none; }
+.btn-left {
+ flex: 1;
+}
+
+.btn-more {
+ width: 220rpx;
+ flex-shrink: 0;
+}
+
/* 主按钮使用 flex: 1 铺满 */
.btn-full {
flex: 1;
diff --git a/subPackages/sub_business_shared/order/index.vue b/subPackages/sub_business_shared/order/index.vue
index 640c7cc..b3e9772 100644
--- a/subPackages/sub_business_shared/order/index.vue
+++ b/subPackages/sub_business_shared/order/index.vue
@@ -3,6 +3,18 @@
+
+
+
+ {{ dateStart || '开始日期' }}
+
+ 至
+
+ {{ dateEnd || '结束日期' }}
+
+
+ 查询
+
订单号
@@ -26,16 +38,6 @@
{{ prescriptionTypeLabels[prescriptionTypeIndex] }}
-
-
- {{ dateStart || '开始日期' }}
-
- 至
-
- {{ dateEnd || '结束日期' }}
-
-
- 查询
@@ -152,6 +154,7 @@ import {
clearOrderFilter,
defaultOrderFilter,
} 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'
const DELIVERY_OPTIONS = [
@@ -207,7 +210,7 @@ export default {
})))
}
})
- this.reload()
+ this.checkDateEndAndReload()
},
onReachBottom() {
this.load(this.page + 1, true)
@@ -236,6 +239,24 @@ export default {
clearOrderFilter()
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() {
const st = this.statusOptions[this.statusIndex]
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 {
display: flex;
align-items: center;
diff --git a/subPackages/sub_business_shared/order/trace/index.vue b/subPackages/sub_business_shared/order/trace/index.vue
index b671dc1..3835c25 100644
--- a/subPackages/sub_business_shared/order/trace/index.vue
+++ b/subPackages/sub_business_shared/order/trace/index.vue
@@ -10,6 +10,7 @@
挂号订单{{ summary.register_order_no }}
处方号{{ summary.prescription_no }}
结算状态{{ summary.is_settled === 1 ? '已结算' : '未结算' }}
+ 审方状态{{ prescriptionStatusLabel }}
@@ -26,7 +27,7 @@
暂无时间线数据
-
+
{{ node.title }}
@@ -43,6 +44,15 @@
加载分账...
+
+ {{ tab.label }}
+
挂号订单
+
+ 合计分账:{{ formatMoney(currentLedgerSumMoney) }}
+
费用类型{{ row.fee_type_txt || '-' }}
@@ -170,6 +183,7 @@ export default {
traceData: null,
activeTab: 'timeline',
ledgerSubTab: 'product',
+ ledgerScope: 'store',
ledgerProduct: null,
ledgerRegister: null,
ledgerLoading: false,
@@ -181,14 +195,24 @@ export default {
{ key: 'ledger', label: '分账' },
{ key: 'reconciliation', label: '对账' },
],
+ ledgerScopeTabs: [
+ { key: 'all', label: '全部' },
+ { key: 'store', label: '门店' },
+ { key: 'platform', label: '平台' },
+ ],
}
},
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() {
return this.traceData?.summary || {}
},
timeline() {
- return this.traceData?.timeline || []
+ const items = this.traceData?.timeline || []
+ return withWxKey(items, 'key', 'tl')
},
links() {
return this.traceData?.links || {}
@@ -196,11 +220,26 @@ export default {
hasRegisterLedger() {
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() {
- const data = this.ledgerSubTab === 'register' ? this.ledgerRegister : this.ledgerProduct
- const items = data?.items || []
+ const items = this.currentLedgerData?.items || []
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() {
const items = this.reconciliationData?.items || []
return withWxKey(items, 'id', 'traceRec')
@@ -228,6 +267,18 @@ export default {
this.bizApi.getOrderTrace({ scene: this.scene, order_id: this.orderId }).then(w => {
if (w.ok) {
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 {
uni.showToast({ title: (w.res && w.res.message) || '加载失败', icon: 'none' })
}
@@ -235,7 +286,7 @@ export default {
},
switchMainTab(key) {
this.activeTab = key
- if (key === 'ledger' && !this.loadedTabs.ledger) {
+ if (key === 'ledger' && !this.loadedTabs[`ledger:${this.ledgerScope}`]) {
this.loadLedger()
} else if (key === 'reconciliation' && !this.loadedTabs.reconciliation) {
this.loadReconciliation()
@@ -244,18 +295,27 @@ export default {
switchLedgerSub(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 registerId = this.links.register_id
if (!productId && !registerId) {
- this.loadedTabs.ledger = true
+ this.loadedTabs[cacheKey] = true
return
}
this.ledgerLoading = true
+ const scope = this.ledgerScope
const reqs = []
if (productId) {
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
})
)
@@ -264,7 +324,7 @@ export default {
}
if (registerId) {
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
})
)
@@ -273,7 +333,7 @@ export default {
}
Promise.all(reqs).finally(() => {
this.ledgerLoading = false
- this.loadedTabs.ledger = true
+ this.loadedTabs[cacheKey] = true
})
},
loadReconciliation() {
@@ -343,6 +403,32 @@ export default {
font-weight: 600;
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; }
.timeline-item {
display: flex;
diff --git a/subPackages/sub_business_shared/warehouse/components/PriceEditModal.vue b/subPackages/sub_business_shared/warehouse/components/PriceEditModal.vue
index 3271177..dcfee87 100644
--- a/subPackages/sub_business_shared/warehouse/components/PriceEditModal.vue
+++ b/subPackages/sub_business_shared/warehouse/components/PriceEditModal.vue
@@ -1,15 +1,27 @@
- 修改售价
+ {{ isPlatform ? '修改价格' : '修改售价' }}
{{ drugName }}
- 建议售价 {{ suggestPrice }}
-
+ 建议售价 {{ suggestPrice }}
+
+ 供货价
+
+
+
+ {{ isPlatform ? '建议售价' : '售价' }}
+
+
@@ -32,10 +44,14 @@ export default {
return {
show: false,
priceInput: '',
+ marketPriceInput: '',
submitting: false,
}
},
computed: {
+ isPlatform() {
+ return this.bizCap.warehouseType === 'platform'
+ },
drugName() {
const drug = (this.item && this.item.drug) || {}
return drug.drug_name || ''
@@ -57,6 +73,8 @@ export default {
if (v && this.item) {
const raw = this.item.price
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
}
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)
},
},
@@ -104,8 +134,16 @@ export default {
margin-top: 8rpx;
text-align: center;
}
+.field-block {
+ margin-top: 24rpx;
+}
+.field-label {
+ display: block;
+ font-size: 24rpx;
+ color: #666;
+ margin-bottom: 8rpx;
+}
.modal-input {
- margin-top: 32rpx;
padding: 20rpx 24rpx;
background: #f5f5f5;
border-radius: 12rpx;
diff --git a/subPackages/sub_business_shared/warehouse/detail.vue b/subPackages/sub_business_shared/warehouse/detail.vue
index 564cfc8..c63b1fd 100644
--- a/subPackages/sub_business_shared/warehouse/detail.vue
+++ b/subPackages/sub_business_shared/warehouse/detail.vue
@@ -44,7 +44,7 @@
- 修改售价
+ {{ isPlatformWarehouse ? '修改价格' : '修改售价' }}
{
+ 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) {
uni.showToast({ title: '修改成功' })
this.closePriceEdit()
diff --git a/subPackages/sub_business_shared/warehouse/index.vue b/subPackages/sub_business_shared/warehouse/index.vue
index 63cf2c4..82d563a 100644
--- a/subPackages/sub_business_shared/warehouse/index.vue
+++ b/subPackages/sub_business_shared/warehouse/index.vue
@@ -101,7 +101,7 @@ export default {
},
methods: {
rowDisplay(item) {
- return mapWarehouseRow(item)
+ return mapWarehouseRow(item, this.bizCap.warehouseType || 'store')
},
reload() {
this.page = 1
@@ -131,10 +131,13 @@ export default {
this.priceModalVisible = false
this.priceEditItem = null
},
- onPriceConfirm(price) {
+ onPriceConfirm(payload) {
if (!this.priceEditItem || this.priceSubmitting) return
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) {
uni.showToast({ title: '修改成功' })
this.closePriceEdit()
diff --git a/subPackages/sub_clinic_admin/common/display.js b/subPackages/sub_clinic_admin/common/display.js
index 9231f04..2f166b0 100644
--- a/subPackages/sub_clinic_admin/common/display.js
+++ b/subPackages/sub_clinic_admin/common/display.js
@@ -135,6 +135,11 @@ export function erpSyncText(v) {
return '未同步'
}
+export function prescriptionStatusText(status) {
+ const m = { 0: '待审核', 1: '已审核', 2: '已驳回', 3: '已过期' }
+ return m[status] != null ? m[status] : '-'
+}
+
export function freeShippingText(v) {
if (v === 1) return '包邮'
return '不包邮'
@@ -238,6 +243,7 @@ export function mapOrderProductItem(p, prescriptionType) {
: '×' + (p.number != null ? p.number : 0)
const isTcm = prescriptionType === 1
return {
+ id: p.id,
drugName: p.drug_name || drug.drug_name || '药品',
drugNumber: drug.drug_number || '',
imageUrl: resolveDrugImage(p.drug_image || drug.image),
@@ -276,6 +282,9 @@ export function mapOrderDetailDisplay(info) {
if (!info || !info.id) return {}
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 presStatus = info.prescription && info.prescription.status != null
+ ? info.prescription.status
+ : null
return {
orderTypeText: orderTypeText(info.order_type),
marketPrice: formatMoney(info.market_price),
@@ -284,6 +293,7 @@ export function mapOrderDetailDisplay(info) {
showPrescription: mapOrderPrescription(info),
prescriptionId: info.p_id,
canViewPrescription: canView,
+ prescriptionStatusText: canView && presStatus != null ? prescriptionStatusText(presStatus) : '',
statusText: orderStatusText(info.status),
prescriptionTypeText: prescriptionTypeText(info.prescription_type),
deliveryText: deliveryMethodText(info.delivery_method),
@@ -398,16 +408,20 @@ export function warehouseStatusText(status) {
return '-'
}
-export function mapWarehouseRow(item) {
+export function mapWarehouseRow(item, warehouseType = 'store') {
if (!item) return {}
const drug = item.drug || {}
const storeDrug = drug.drug_store_drug || {}
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 {
id: item.id,
drugName: drug.drug_name || '-',
imageUrl: resolveDrugImage(drug.image),
- buyPrice: formatMoney(item.buy_price),
+ buyPrice: formatMoney(supplyRaw),
price: formatMoney(item.price),
suggestPrice: formatMoney(suggestPrice),
stock: item.stock != null ? item.stock : '-',
diff --git a/subPackages/sub_clinic_admin/order/components/PrescriptionDetailPopup.vue b/subPackages/sub_clinic_admin/order/components/PrescriptionDetailPopup.vue
index 9b7740e..56c8d8b 100644
--- a/subPackages/sub_clinic_admin/order/components/PrescriptionDetailPopup.vue
+++ b/subPackages/sub_clinic_admin/order/components/PrescriptionDetailPopup.vue
@@ -1,5 +1,13 @@
-
+
+