1. 修复了一些问题
This commit is contained in:
@@ -37,3 +37,35 @@ export function getClinicSalespersonSettlementDetail(id) {
|
||||
export function getClinicSalespersonUserBindList(params) {
|
||||
return clinicSalespersonRequest({ url: `${PREFIX}/user-bind-list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getClinicSalespersonSeePriceConfig() {
|
||||
return clinicSalespersonRequest({ url: `${PREFIX}/see-price-config`, method: 'GET' });
|
||||
}
|
||||
|
||||
export function getClinicSalespersonChineseDrugList(params) {
|
||||
return clinicSalespersonRequest({ url: `${PREFIX}/chinese-drug-list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getClinicSalespersonDiseaseList(params) {
|
||||
return clinicSalespersonRequest({ url: `${PREFIX}/disease-list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getClinicSalespersonProcessRuleList(params) {
|
||||
return clinicSalespersonRequest({ url: `${PREFIX}/process-rule-list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getClinicSalespersonChineseMedicineQuickGrams() {
|
||||
return clinicSalespersonRequest({ url: `${PREFIX}/chinese-medicine-quick-grams`, method: 'GET' });
|
||||
}
|
||||
|
||||
export function createClinicSalespersonTransferPrescription(data) {
|
||||
return clinicSalespersonRequest({ url: `${PREFIX}/transfer-prescription-create`, method: 'POST', data });
|
||||
}
|
||||
|
||||
export function getClinicSalespersonTransferPrescriptionList(params) {
|
||||
return clinicSalespersonRequest({ url: `${PREFIX}/transfer-prescription-list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getClinicSalespersonTransferPrescriptionDetail(id) {
|
||||
return clinicSalespersonRequest({ url: `${PREFIX}/transfer-prescription-detail`, method: 'GET', data: { id } });
|
||||
}
|
||||
|
||||
116
api/platformStore.js
Normal file
116
api/platformStore.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import { req } from '@/common/js/index.js';
|
||||
import { unwrapClinicRes } from '@/api/clinicAdmin.js';
|
||||
|
||||
const STORE_PREFIX = '/newApi/platform-admin-store';
|
||||
const CONFIG_PREFIX = '/newApi/platform-admin-salesperson-store-config';
|
||||
|
||||
function platformRequest(options) {
|
||||
return req.request({
|
||||
...options,
|
||||
header: {
|
||||
...(options.header || {}),
|
||||
_platformAdmin: '1',
|
||||
},
|
||||
}).then((res) => {
|
||||
const data = unwrapClinicRes(res);
|
||||
const ok = res && (res.code === 0 || (res.errcode != null && Number(res.errcode) !== -1));
|
||||
return { res, data, ok };
|
||||
});
|
||||
}
|
||||
|
||||
export function getPlatformStoreList(params) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/list`, method: 'GET', data: params });
|
||||
}
|
||||
|
||||
export function getPlatformStoreDetail(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/detail`, method: 'GET', data: { id } });
|
||||
}
|
||||
|
||||
export function updatePlatformStore(data) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update`, method: 'POST', data });
|
||||
}
|
||||
|
||||
export function deletePlatformStore(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/delete`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function openStorePcWindows(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/open-pc-windows`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function openStoreQrCode(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/open-qr-code`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreShippingFree(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-shipping-free`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreSubscribeStatus(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-subscribe-status`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreSeeRate(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-see-rate`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreAllowInsurance(id) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-allow-insurance-category`, method: 'POST', data: { id } });
|
||||
}
|
||||
|
||||
export function updateStoreClinicType(id, clinicType) {
|
||||
return platformRequest({
|
||||
url: `${STORE_PREFIX}/update-clinic-type`,
|
||||
method: 'POST',
|
||||
data: { id, clinic_type: clinicType },
|
||||
});
|
||||
}
|
||||
|
||||
export function toggleSalespersonSeePrice(storeId) {
|
||||
return platformRequest({
|
||||
url: `${CONFIG_PREFIX}/toggle-see-price`,
|
||||
method: 'POST',
|
||||
data: { store_id: storeId },
|
||||
});
|
||||
}
|
||||
|
||||
export function getStoreDrugsByType(storeId, type) {
|
||||
return platformRequest({
|
||||
url: `${STORE_PREFIX}/get-store-drugs-by-type`,
|
||||
method: 'GET',
|
||||
data: { store_id: storeId, type },
|
||||
});
|
||||
}
|
||||
|
||||
export function updateStoreDrugPrices(data) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-store-drug-prices`, method: 'POST', data });
|
||||
}
|
||||
|
||||
export function getDoctorOption(storeId) {
|
||||
return platformRequest({
|
||||
url: `${STORE_PREFIX}/doctor-option`,
|
||||
method: 'GET',
|
||||
data: storeId ? { store_id: storeId } : {},
|
||||
});
|
||||
}
|
||||
|
||||
export function getInternetMedicalStoreOption() {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/internet-medical-store-option`, method: 'GET' });
|
||||
}
|
||||
|
||||
export function bindOnlineConsultation(data) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/bind-online-consultation`, method: 'POST', data });
|
||||
}
|
||||
|
||||
export function updateStoreExternalField(data) {
|
||||
return platformRequest({ url: `${STORE_PREFIX}/update-store-external-field`, method: 'POST', data });
|
||||
}
|
||||
|
||||
export const DRUG_TYPE_OPTIONS = [
|
||||
{ label: '中药', value: 1 },
|
||||
{ label: '西药', value: 2 },
|
||||
{ label: '保健食品', value: 3 },
|
||||
{ label: '产品服务包', value: 5 },
|
||||
{ label: '非药品', value: 6 },
|
||||
{ label: '医疗器械', value: 7 },
|
||||
];
|
||||
@@ -314,6 +314,15 @@ export function getCurrentStoreTypeApi(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 接诊页匹配推广员传方
|
||||
export function getSalespersonTransferByRegisterApi(registerId) {
|
||||
return req.request({
|
||||
url: '/newApi/transfer-prescription/get-salesperson-by-register',
|
||||
method: 'GET',
|
||||
data: { register_id: registerId }
|
||||
})
|
||||
}
|
||||
|
||||
// 切换诊所
|
||||
export function switchStoreApi(data) {
|
||||
return req.request({
|
||||
|
||||
133
api/salespersonManage.js
Normal file
133
api/salespersonManage.js
Normal file
@@ -0,0 +1,133 @@
|
||||
import { req } from '@/common/js/index.js';
|
||||
import { unwrapClinicRes } from '@/api/clinicAdmin.js';
|
||||
|
||||
const PLATFORM_PREFIX = '/newApi/platform-admin-salesperson';
|
||||
const PLATFORM_CONFIG_PREFIX = '/newApi/platform-admin-salesperson-store-config';
|
||||
const CLINIC_PREFIX = '/newApi/clinic-admin-salesperson';
|
||||
const CLINIC_CONFIG_PREFIX = '/newApi/clinic-admin-salesperson-store-config';
|
||||
|
||||
function createRequest(prefix, headerKey) {
|
||||
return function bizRequest(options) {
|
||||
return req.request({
|
||||
...options,
|
||||
header: {
|
||||
...(options.header || {}),
|
||||
[headerKey]: '1',
|
||||
},
|
||||
}).then((res) => {
|
||||
const data = unwrapClinicRes(res);
|
||||
const ok = res && (res.code === 0 || (res.errcode != null && Number(res.errcode) !== -1));
|
||||
return { res, data, ok };
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
const platformRequest = createRequest(PLATFORM_PREFIX, '_platformAdmin');
|
||||
const clinicRequest = createRequest(CLINIC_PREFIX, '_clinicAdmin');
|
||||
const platformConfigRequest = createRequest(PLATFORM_CONFIG_PREFIX, '_platformAdmin');
|
||||
const clinicConfigRequest = createRequest(CLINIC_CONFIG_PREFIX, '_clinicAdmin');
|
||||
|
||||
export function createSalespersonManageApi(mode) {
|
||||
const isPlatform = mode === 'platform';
|
||||
const request = isPlatform ? platformRequest : clinicRequest;
|
||||
const configRequest = isPlatform ? platformConfigRequest : clinicConfigRequest;
|
||||
const prefix = isPlatform ? PLATFORM_PREFIX : CLINIC_PREFIX;
|
||||
|
||||
return {
|
||||
mode,
|
||||
prefix,
|
||||
|
||||
getList(params) {
|
||||
const url = isPlatform ? `${prefix}/platform-list` : `${prefix}/list`;
|
||||
return request({ url, method: 'GET', data: params });
|
||||
},
|
||||
|
||||
create(data) {
|
||||
const url = isPlatform ? `${prefix}/platform-create` : `${prefix}/create`;
|
||||
return request({ url, method: 'POST', data });
|
||||
},
|
||||
|
||||
update(data) {
|
||||
return request({ url: `${prefix}/update`, method: 'POST', data });
|
||||
},
|
||||
|
||||
getPlatformStoreOptions(keyword) {
|
||||
return request({
|
||||
url: `${prefix}/platform-store-options`,
|
||||
method: 'GET',
|
||||
data: { keyword: keyword || '' },
|
||||
});
|
||||
},
|
||||
|
||||
openAdminAccount(data) {
|
||||
return request({ url: `${prefix}/open-admin-account`, method: 'POST', data });
|
||||
},
|
||||
|
||||
getDrugCommissionList(params) {
|
||||
return request({ url: `${prefix}/drug-commission-list`, method: 'GET', data: params });
|
||||
},
|
||||
|
||||
saveDrugCommission(data) {
|
||||
return request({ url: `${prefix}/save-drug-commission`, method: 'POST', data });
|
||||
},
|
||||
|
||||
getDrugCommissionCopyFrom(fromSalespersonId) {
|
||||
return request({
|
||||
url: `${prefix}/drug-commission-copy-from`,
|
||||
method: 'GET',
|
||||
data: { from_salesperson_id: fromSalespersonId },
|
||||
});
|
||||
},
|
||||
|
||||
getCommissionList(params) {
|
||||
return request({ url: `${prefix}/commission-list`, method: 'GET', data: params });
|
||||
},
|
||||
|
||||
getCommissionOrderList(params) {
|
||||
return request({ url: `${prefix}/commission-order-list`, method: 'GET', data: params });
|
||||
},
|
||||
|
||||
getSettlementPreview(params) {
|
||||
return request({ url: `${prefix}/settlement-preview`, method: 'GET', data: params });
|
||||
},
|
||||
|
||||
confirmSettlement(data) {
|
||||
return request({ url: `${prefix}/settlement`, method: 'POST', data });
|
||||
},
|
||||
|
||||
getSettlementList(params) {
|
||||
return request({ url: `${prefix}/settlement-list`, method: 'GET', data: params });
|
||||
},
|
||||
|
||||
getSettlementDetail(params) {
|
||||
return request({ url: `${prefix}/settlement-detail`, method: 'GET', data: params });
|
||||
},
|
||||
|
||||
getUserBindList(params) {
|
||||
return request({ url: `${prefix}/user-bind-list`, method: 'GET', data: params });
|
||||
},
|
||||
|
||||
toggleSeePrice(storeId) {
|
||||
const configPrefix = isPlatform ? PLATFORM_CONFIG_PREFIX : CLINIC_CONFIG_PREFIX;
|
||||
return configRequest({
|
||||
url: `${configPrefix}/toggle-see-price`,
|
||||
method: 'POST',
|
||||
data: { store_id: storeId },
|
||||
});
|
||||
},
|
||||
|
||||
getSeePriceDetail(storeId) {
|
||||
const configPrefix = isPlatform ? PLATFORM_CONFIG_PREFIX : CLINIC_CONFIG_PREFIX;
|
||||
return configRequest({
|
||||
url: `${configPrefix}/detail`,
|
||||
method: 'GET',
|
||||
data: storeId ? { store_id: storeId } : {},
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getSalespersonManageApiByLoginMode() {
|
||||
const mode = uni.getStorageSync('loginMode');
|
||||
return createSalespersonManageApi(mode === 'platform_admin' ? 'platform' : 'clinic');
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { req } from '@/common/js/index.js';
|
||||
|
||||
const SALESPERSON_PREFIX = '/newApi/salesperson-upload/';
|
||||
const PLATFORM_PREFIX = '/newApi/platform-admin-upload/';
|
||||
const CLINIC_ADMIN_PREFIX = '/newApi/clinic-admin-upload/';
|
||||
|
||||
function uploadChatFileByPrefix(prefix, headerKey, params) {
|
||||
return req.request({
|
||||
@@ -31,6 +32,13 @@ export function uploadPlatformAdminChatFileApi(params) {
|
||||
return uploadChatFileByPrefix(PLATFORM_PREFIX, '_platformAdmin', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 诊所管理员上传(结算凭证等)
|
||||
*/
|
||||
export function uploadClinicAdminChatFileApi(params) {
|
||||
return uploadChatFileByPrefix(CLINIC_ADMIN_PREFIX, '_clinicAdmin', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 按当前 loginMode 选择上传接口
|
||||
*/
|
||||
@@ -39,9 +47,23 @@ export function uploadBusinessChatFileApi(params) {
|
||||
if (mode === 'platform_admin') {
|
||||
return uploadPlatformAdminChatFileApi(params);
|
||||
}
|
||||
if (mode === 'clinic_admin') {
|
||||
return uploadClinicAdminChatFileApi(params);
|
||||
}
|
||||
return uploadSalespersonChatFileApi(params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 推广员管理结算凭证上传(超管/诊所)
|
||||
*/
|
||||
export function uploadSalespersonManageFileApi(params) {
|
||||
const mode = uni.getStorageSync('loginMode');
|
||||
if (mode === 'platform_admin') {
|
||||
return uploadPlatformAdminChatFileApi(params);
|
||||
}
|
||||
return uploadClinicAdminChatFileApi(params);
|
||||
}
|
||||
|
||||
function unwrapUploadResult(res) {
|
||||
if (res == null) return null;
|
||||
if (res.result !== undefined && res.result !== null) return res.result;
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
mode="bottom"
|
||||
length="80%"
|
||||
border-radius="24"
|
||||
z-index="10078"
|
||||
z-index="10090"
|
||||
:closeable="true"
|
||||
:mask-close-able="true"
|
||||
:safe-area-inset-bottom="true"
|
||||
@@ -76,7 +76,7 @@ export default {
|
||||
normalizeAccounts(accounts) {
|
||||
return (accounts || []).map((item, index) => ({
|
||||
...item,
|
||||
_rowKey: `${item.account_type}_${item.id}_${index}`,
|
||||
_rowKey: `${item.account_type}_${item.id}_${item.login_persona || 'admin'}_${index}`,
|
||||
}));
|
||||
},
|
||||
resolveDefaultSelection(accounts, options = {}) {
|
||||
@@ -113,6 +113,10 @@ export default {
|
||||
|
||||
if (Array.isArray(accounts)) {
|
||||
this.accounts = this.normalizeAccounts(accounts);
|
||||
if (!this.accounts.length) {
|
||||
uni.showToast({ title: '暂无可选账号', icon: 'none' });
|
||||
return Promise.resolve();
|
||||
}
|
||||
this.selected = this.resolveDefaultSelection(this.accounts, options);
|
||||
this.showPanel();
|
||||
return Promise.resolve();
|
||||
@@ -171,6 +175,7 @@ export default {
|
||||
switchDoctorWxAccount({
|
||||
account_type: this.selected.account_type,
|
||||
account_id: this.selected.id,
|
||||
login_persona: this.selected.login_persona || undefined,
|
||||
}).then((wrap) => {
|
||||
if (!wrap || !wrap.ok || !wrap.data || !wrap.data.token) {
|
||||
return Promise.reject({
|
||||
|
||||
@@ -162,14 +162,23 @@ export default {
|
||||
return Promise.reject(new Error(res.message || res.msg || '登录失败'));
|
||||
}
|
||||
const result = res.result;
|
||||
if (result.need_select && result.accounts && result.accounts.length) {
|
||||
if (result.need_select) {
|
||||
const accounts = Array.isArray(result.accounts) ? result.accounts : [];
|
||||
if (!accounts.length) {
|
||||
return Promise.reject(new Error('请选择登录账号'));
|
||||
}
|
||||
this.pendingLoginPayload = payload;
|
||||
this.$emit('need-select', {
|
||||
accounts: result.accounts,
|
||||
defaultAccountId: result.default_account_id,
|
||||
defaultAccountType: result.default_account_type,
|
||||
this.loading = false;
|
||||
return new Promise((resolve) => {
|
||||
this.$nextTick(() => {
|
||||
this.$emit('need-select', {
|
||||
accounts,
|
||||
defaultAccountId: result.default_account_id,
|
||||
defaultAccountType: result.default_account_type,
|
||||
});
|
||||
resolve(null);
|
||||
});
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return this.finishLogin(result);
|
||||
})
|
||||
@@ -184,6 +193,7 @@ export default {
|
||||
this.doLogin({
|
||||
account_type: account.account_type,
|
||||
account_id: account.id,
|
||||
login_persona: account.login_persona || undefined,
|
||||
});
|
||||
},
|
||||
async finishLogin(result) {
|
||||
|
||||
21
pages.json
21
pages.json
@@ -476,11 +476,28 @@
|
||||
{ "path": "home/index", "style": { "navigationBarTitleText": "工作台", "navigationStyle": "custom" } },
|
||||
{ "path": "my/index", "style": { "navigationBarTitleText": "我的", "navigationStyle": "custom" } },
|
||||
{ "path": "withdrawal/audit/index", "style": { "navigationBarTitleText": "提现审核", "navigationStyle": "custom" } },
|
||||
{ "path": "store/index", "style": { "navigationBarTitleText": "门店管理", "navigationStyle": "custom" } },
|
||||
{ "path": "store/detail", "style": { "navigationBarTitleText": "门店详情", "navigationStyle": "custom" } },
|
||||
{ "path": "store/edit", "style": { "navigationBarTitleText": "编辑诊所", "navigationStyle": "custom" } },
|
||||
{ "path": "store/drug-price", "style": { "navigationBarTitleText": "药品价格", "navigationStyle": "custom" } },
|
||||
{ "path": "store/consultation", "style": { "navigationBarTitleText": "在线复诊", "navigationStyle": "custom" } },
|
||||
{ "path": "store-input/audit/index", "style": { "navigationBarTitleText": "门店审核", "navigationStyle": "custom" } },
|
||||
{ "path": "store-input/audit/detail", "style": { "navigationBarTitleText": "审核详情", "navigationStyle": "custom" } },
|
||||
{ "path": "doctor-input/audit/index", "style": { "navigationBarTitleText": "医生审核", "navigationStyle": "custom" } },
|
||||
{ "path": "doctor-input/audit/detail", "style": { "navigationBarTitleText": "审核详情", "navigationStyle": "custom" } }
|
||||
]
|
||||
}, {
|
||||
"root": "subPackages/sub_salesperson_manage",
|
||||
"pages": [
|
||||
{ "path": "index", "style": { "navigationBarTitleText": "推广员管理", "navigationStyle": "custom" } },
|
||||
{ "path": "form", "style": { "navigationBarTitleText": "推广员", "navigationStyle": "custom" } },
|
||||
{ "path": "drug-commission/index", "style": { "navigationBarTitleText": "药品佣金", "navigationStyle": "custom" } },
|
||||
{ "path": "leads/index", "style": { "navigationBarTitleText": "获客记录", "navigationStyle": "custom" } },
|
||||
{ "path": "commission/index", "style": { "navigationBarTitleText": "分成与结算", "navigationStyle": "custom" } },
|
||||
{ "path": "settlement/preview", "style": { "navigationBarTitleText": "按时间段结算", "navigationStyle": "custom" } },
|
||||
{ "path": "settlement/confirm", "style": { "navigationBarTitleText": "确认结算", "navigationStyle": "custom" } },
|
||||
{ "path": "settlement/detail", "style": { "navigationBarTitleText": "结算详情", "navigationStyle": "custom" } }
|
||||
]
|
||||
}, {
|
||||
"root": "subPackages/sub_salesperson",
|
||||
"pages": [
|
||||
@@ -501,7 +518,9 @@
|
||||
{ "path": "earnings/index", "style": { "navigationBarTitleText": "我的收益", "navigationStyle": "custom" } },
|
||||
{ "path": "leads/index", "style": { "navigationBarTitleText": "获客记录", "navigationStyle": "custom" } },
|
||||
{ "path": "commission/index", "style": { "navigationBarTitleText": "分成记录", "navigationStyle": "custom" } },
|
||||
{ "path": "settlement/index", "style": { "navigationBarTitleText": "结算记录", "navigationStyle": "custom" } }
|
||||
{ "path": "settlement/index", "style": { "navigationBarTitleText": "结算记录", "navigationStyle": "custom" } },
|
||||
{ "path": "transfer-prescription/form", "style": { "navigationBarTitleText": "传方", "navigationStyle": "custom" } },
|
||||
{ "path": "transfer-prescription/list", "style": { "navigationBarTitleText": "传方记录", "navigationStyle": "custom" } }
|
||||
]
|
||||
}, {
|
||||
"root": "subPackages/sub_agreement",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
ref="accountSwitchPanel"
|
||||
mode="login"
|
||||
@confirm="onAccountPicked"
|
||||
@cancel="pendingAccountSelect = false"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
@@ -16,17 +17,25 @@ import { redirectIfLoggedIn } from '@/utils/loginSession.js';
|
||||
|
||||
export default {
|
||||
components: { DoctorLogin, AccountSwitchPanel },
|
||||
data() {
|
||||
return {
|
||||
pendingAccountSelect: false,
|
||||
};
|
||||
},
|
||||
onShow() {
|
||||
if (this.pendingAccountSelect) return;
|
||||
redirectIfLoggedIn();
|
||||
},
|
||||
methods: {
|
||||
openAccountPicker({ accounts, defaultAccountId, defaultAccountType }) {
|
||||
this.pendingAccountSelect = true;
|
||||
this.$refs.accountSwitchPanel.open(accounts, {
|
||||
defaultAccountId,
|
||||
defaultAccountType,
|
||||
});
|
||||
},
|
||||
onAccountPicked(account) {
|
||||
this.pendingAccountSelect = false;
|
||||
this.$refs.doctorLogin.onAccountPicked(account);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -81,6 +81,10 @@ const BUSINESS_MENUS = [
|
||||
{ key: 'order', label: '商品订单', desc: '全平台订单概览', icon: 'bag-fill', theme: GLOBAL_THEMES[3], path: '/subPackages/sub_clinic_admin/order/index' },
|
||||
]
|
||||
|
||||
const SALESPERSON_MENUS = [
|
||||
{ key: 'salesperson-manage', label: '推广员管理', desc: '添加与结算推广员', icon: 'account-fill', theme: GLOBAL_THEMES[2], path: '/subPackages/sub_salesperson_manage/index?mode=clinic' },
|
||||
]
|
||||
|
||||
const WAREHOUSE_FALLBACK = {
|
||||
key: 'warehouse',
|
||||
label: '仓库管理',
|
||||
@@ -105,6 +109,7 @@ export default {
|
||||
return [
|
||||
{ key: 'finance', title: '财务管理', items: FINANCE_MENUS },
|
||||
{ key: 'business', title: '业务管理', items: BUSINESS_MENUS },
|
||||
{ key: 'salesperson', title: '门店推广员', items: SALESPERSON_MENUS },
|
||||
{ key: 'warehouse', title: '仓库管理', items: this.warehouseMenus },
|
||||
]
|
||||
},
|
||||
|
||||
@@ -123,7 +123,9 @@ export default {
|
||||
profileInfo: null,
|
||||
stats: {},
|
||||
menuItems: [
|
||||
{ key: 'earnings', label: '我的收益', path: `${ROOT}/earnings/index` },
|
||||
{ key: 'transfer', label: '传方', path: '/subPackages/sub_workbench/prescription_v2/index?salesperson_transfer=1&initial_category=1' },
|
||||
// { key: 'transfer-list', label: '传方记录', path: `${ROOT}/transfer-prescription/list` },
|
||||
// { key: 'earnings', label: '我的收益', path: `${ROOT}/earnings/index` },
|
||||
{ key: 'leads', label: '获客记录', path: `${ROOT}/leads/index` },
|
||||
{ key: 'commission', label: '分成记录', path: `${ROOT}/commission/index` },
|
||||
{ key: 'settlement', label: '结算记录', path: `${ROOT}/settlement/index` },
|
||||
@@ -134,6 +136,8 @@ export default {
|
||||
// 动态注入多彩主题和增强文案,使用 uView 图标名称
|
||||
enrichedMenuItems() {
|
||||
const themes = [
|
||||
{ bg: 'linear-gradient(135deg, #ECFDF5, #D1FAE5)', color: '#10B981', desc: '线下中药传方', icon: 'edit-pen-fill' },
|
||||
{ bg: 'linear-gradient(135deg, #F0F9FF, #E0F2FE)', color: '#0EA5E9', desc: '历史传方记录', icon: 'file-text-fill' },
|
||||
// 收益:使用钻石/钱包相关图标
|
||||
{ bg: 'linear-gradient(135deg, #FFF7ED, #FFEDD5)', color: '#F97316', desc: '查看收益明细', icon: 'red-packet-fill' },
|
||||
// 获客:使用目标/数据相关图标
|
||||
|
||||
@@ -1,215 +1,430 @@
|
||||
<template>
|
||||
|
||||
<clinic-salesperson-page-layout title="我的" :show-tabbar="true" :tab-index="1">
|
||||
|
||||
<view class="mine-container">
|
||||
|
||||
<view class="profile-panel">
|
||||
|
||||
<view class="avatar-squircle">
|
||||
|
||||
<text>{{ avatarText }}</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="user-info">
|
||||
|
||||
<text class="user-name">{{ displayName }}</text>
|
||||
|
||||
<view class="role-badge">
|
||||
|
||||
<text class="role-text">推广员</text>
|
||||
|
||||
</view>
|
||||
|
||||
<text v-if="phone" class="phone-line">{{ phone }}</text>
|
||||
|
||||
<text v-if="storeName" class="store-line">{{ storeName }}</text>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class="setting-panel">
|
||||
|
||||
<view class="setting-item">
|
||||
|
||||
<text class="item-label">所属诊所</text>
|
||||
|
||||
<text class="item-value">{{ storeName || '-' }}</text>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class="setting-panel">
|
||||
|
||||
<account-switch-entry />
|
||||
|
||||
<view
|
||||
|
||||
class="setting-item action-item"
|
||||
|
||||
hover-class="item-hover"
|
||||
|
||||
:hover-stay-time="100"
|
||||
|
||||
@click="logout"
|
||||
|
||||
>
|
||||
|
||||
<text class="danger-text">退出登录</text>
|
||||
|
||||
<u-icon name="arrow-right" color="#C9CDD4" size="28" />
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</clinic-salesperson-page-layout>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
import ClinicSalespersonPageLayout from '@/subPackages/sub_clinic_salesperson/components/clinic-salesperson-page-layout.vue'
|
||||
|
||||
import AccountSwitchEntry from '@/components/account-switch/account-switch-entry.vue'
|
||||
|
||||
import { getClinicSalespersonMyInfo } from '@/api/clinicSalesperson.js'
|
||||
|
||||
import { clearLoginStorage } from '@/utils/loginSession.js'
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
components: { ClinicSalespersonPageLayout, AccountSwitchEntry },
|
||||
|
||||
data() {
|
||||
|
||||
return {
|
||||
|
||||
displayName: '',
|
||||
|
||||
phone: '',
|
||||
|
||||
storeName: '',
|
||||
|
||||
loading: false,
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
avatarText() {
|
||||
|
||||
return (this.displayName || '推').slice(0, 1)
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
onShow() {
|
||||
|
||||
this.loadProfile()
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
async loadProfile() {
|
||||
|
||||
if (this.loading) return
|
||||
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
|
||||
const wrap = await getClinicSalespersonMyInfo()
|
||||
|
||||
if (!wrap || !wrap.ok) return
|
||||
|
||||
const info = wrap.data || {}
|
||||
|
||||
this.displayName = info.salesperson?.nick_name || '推广员'
|
||||
|
||||
this.phone = info.salesperson?.phone || ''
|
||||
|
||||
this.storeName = info.store?.name || ''
|
||||
|
||||
uni.setStorageSync('clinic_salesperson_user', info)
|
||||
|
||||
} finally {
|
||||
|
||||
this.loading = false
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
logout() {
|
||||
|
||||
uni.showModal({
|
||||
|
||||
title: '提示',
|
||||
|
||||
content: '确定退出登录?',
|
||||
|
||||
confirmColor: '#f53f3f',
|
||||
|
||||
success: (res) => {
|
||||
|
||||
if (!res.confirm) return
|
||||
|
||||
clearLoginStorage()
|
||||
|
||||
uni.reLaunch({ url: '/pages/login/index' })
|
||||
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
$theme-primary: #6acdbb;
|
||||
|
||||
$theme-dark: #4db6a8;
|
||||
|
||||
$color-page-bg: #f5f7f8;
|
||||
|
||||
$color-text-title: #222b2a;
|
||||
|
||||
$color-text-muted: #86909c;
|
||||
|
||||
$color-border: #f2f3f5;
|
||||
|
||||
$color-danger: #f53f3f;
|
||||
|
||||
|
||||
|
||||
.mine-container {
|
||||
|
||||
padding: 0 0 60rpx;
|
||||
|
||||
background-color: $color-page-bg;
|
||||
<template>
|
||||
|
||||
<clinic-salesperson-page-layout title="我的" :show-tabbar="true" :tab-index="1">
|
||||
|
||||
<view class="mine-container">
|
||||
|
||||
<view class="profile-panel">
|
||||
|
||||
<view class="avatar-squircle">
|
||||
|
||||
<text>{{ avatarText }}</text>
|
||||
|
||||
</view>
|
||||
|
||||
<view class="user-info">
|
||||
|
||||
<text class="user-name">{{ displayName }}</text>
|
||||
|
||||
<view class="role-badge">
|
||||
|
||||
<text class="role-text">推广员</text>
|
||||
|
||||
</view>
|
||||
|
||||
<text v-if="phone" class="phone-line">{{ phone }}</text>
|
||||
|
||||
<text v-if="storeName" class="store-line">{{ storeName }}</text>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class="setting-panel">
|
||||
|
||||
<view class="setting-item">
|
||||
|
||||
<text class="item-label">所属诊所</text>
|
||||
|
||||
<text class="item-value">{{ storeName || '-' }}</text>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
|
||||
<view class="setting-panel">
|
||||
|
||||
<account-switch-entry />
|
||||
|
||||
<view
|
||||
|
||||
class="setting-item action-item"
|
||||
|
||||
hover-class="item-hover"
|
||||
|
||||
:hover-stay-time="100"
|
||||
|
||||
@click="logout"
|
||||
|
||||
>
|
||||
|
||||
<text class="danger-text">退出登录</text>
|
||||
|
||||
<u-icon name="arrow-right" color="#C9CDD4" size="28" />
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</clinic-salesperson-page-layout>
|
||||
|
||||
</template>
|
||||
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
import ClinicSalespersonPageLayout from '@/subPackages/sub_clinic_salesperson/components/clinic-salesperson-page-layout.vue'
|
||||
|
||||
import AccountSwitchEntry from '@/components/account-switch/account-switch-entry.vue'
|
||||
|
||||
import { getClinicSalespersonMyInfo } from '@/api/clinicSalesperson.js'
|
||||
|
||||
import { clearLoginStorage } from '@/utils/loginSession.js'
|
||||
|
||||
|
||||
|
||||
export default {
|
||||
|
||||
components: { ClinicSalespersonPageLayout, AccountSwitchEntry },
|
||||
|
||||
data() {
|
||||
|
||||
return {
|
||||
|
||||
displayName: '',
|
||||
|
||||
phone: '',
|
||||
|
||||
storeName: '',
|
||||
|
||||
loading: false,
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
computed: {
|
||||
|
||||
avatarText() {
|
||||
|
||||
return (this.displayName || '推').slice(0, 1)
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
onShow() {
|
||||
|
||||
this.loadProfile()
|
||||
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
||||
async loadProfile() {
|
||||
|
||||
if (this.loading) return
|
||||
|
||||
this.loading = true
|
||||
|
||||
try {
|
||||
|
||||
const wrap = await getClinicSalespersonMyInfo()
|
||||
|
||||
if (!wrap || !wrap.ok) return
|
||||
|
||||
const info = wrap.data || {}
|
||||
|
||||
this.displayName = info.salesperson?.nick_name || '推广员'
|
||||
|
||||
this.phone = info.salesperson?.phone || ''
|
||||
|
||||
this.storeName = info.store?.name || ''
|
||||
|
||||
uni.setStorageSync('clinic_salesperson_user', info)
|
||||
|
||||
} finally {
|
||||
|
||||
this.loading = false
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
logout() {
|
||||
|
||||
uni.showModal({
|
||||
|
||||
title: '提示',
|
||||
|
||||
content: '确定退出登录?',
|
||||
|
||||
confirmColor: '#f53f3f',
|
||||
|
||||
success: (res) => {
|
||||
|
||||
if (!res.confirm) return
|
||||
|
||||
clearLoginStorage()
|
||||
|
||||
uni.reLaunch({ url: '/pages/login/index' })
|
||||
|
||||
},
|
||||
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
$theme-primary: #6acdbb;
|
||||
|
||||
$theme-dark: #4db6a8;
|
||||
|
||||
$color-page-bg: #f5f7f8;
|
||||
|
||||
$color-text-title: #222b2a;
|
||||
|
||||
$color-text-muted: #86909c;
|
||||
|
||||
$color-border: #f2f3f5;
|
||||
|
||||
$color-danger: #f53f3f;
|
||||
|
||||
|
||||
|
||||
.mine-container {
|
||||
|
||||
padding: 0 0 60rpx;
|
||||
|
||||
background-color: $color-page-bg;
|
||||
|
||||
min-height: 100vh;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.profile-panel {
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
background-color: #fff;
|
||||
|
||||
padding: 60rpx 40rpx 50rpx;
|
||||
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.avatar-squircle {
|
||||
|
||||
width: 128rpx;
|
||||
|
||||
height: 128rpx;
|
||||
|
||||
border-radius: 40rpx;
|
||||
|
||||
background: linear-gradient(135deg, $theme-primary 0%, $theme-dark 100%);
|
||||
|
||||
box-shadow: 0 8rpx 24rpx rgba(106, 205, 187, 0.25);
|
||||
|
||||
color: #fff;
|
||||
|
||||
font-size: 52rpx;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
flex-shrink: 0;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.user-info {
|
||||
|
||||
margin-left: 32rpx;
|
||||
|
||||
display: flex;
|
||||
|
||||
flex-direction: column;
|
||||
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.user-name {
|
||||
|
||||
font-size: 40rpx;
|
||||
|
||||
font-weight: 700;
|
||||
|
||||
color: $color-text-title;
|
||||
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.role-badge {
|
||||
|
||||
align-self: flex-start;
|
||||
|
||||
background-color: rgba(106, 205, 187, 0.1);
|
||||
|
||||
border: 1rpx solid rgba(106, 205, 187, 0.2);
|
||||
|
||||
padding: 4rpx 16rpx;
|
||||
|
||||
border-radius: 8rpx;
|
||||
|
||||
margin-bottom: 12rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.role-text {
|
||||
|
||||
font-size: 22rpx;
|
||||
|
||||
color: $theme-dark;
|
||||
|
||||
font-weight: 600;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.phone-line,
|
||||
|
||||
.store-line {
|
||||
|
||||
font-size: 26rpx;
|
||||
|
||||
color: $color-text-muted;
|
||||
|
||||
margin-top: 8rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.setting-panel {
|
||||
|
||||
background-color: #fff;
|
||||
|
||||
margin-bottom: 24rpx;
|
||||
|
||||
padding: 0 40rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.setting-item {
|
||||
|
||||
display: flex;
|
||||
|
||||
align-items: center;
|
||||
|
||||
justify-content: space-between;
|
||||
|
||||
padding: 36rpx 0;
|
||||
|
||||
border-bottom: 1rpx solid $color-border;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.setting-panel .setting-item:last-child {
|
||||
|
||||
border-bottom: none;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.item-label {
|
||||
|
||||
font-size: 30rpx;
|
||||
|
||||
color: $color-text-title;
|
||||
|
||||
font-weight: 500;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.item-value {
|
||||
|
||||
font-size: 30rpx;
|
||||
|
||||
color: $color-text-muted;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.action-item {
|
||||
|
||||
margin: 0 -40rpx;
|
||||
|
||||
padding: 36rpx 40rpx;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.item-hover {
|
||||
|
||||
background-color: #f7f8fa;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
.danger-text {
|
||||
|
||||
font-size: 30rpx;
|
||||
|
||||
color: $color-danger;
|
||||
|
||||
font-weight: 500;
|
||||
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<view />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const TRANSFER_RX_URL = '/subPackages/sub_workbench/prescription_v2/index?salesperson_transfer=1&initial_category=1'
|
||||
|
||||
export default {
|
||||
onLoad() {
|
||||
uni.redirectTo({ url: TRANSFER_RX_URL })
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,55 @@
|
||||
<template>
|
||||
<clinic-salesperson-page-layout title="传方记录" :show-back="true">
|
||||
<view class="page">
|
||||
<view v-if="!list.length" class="empty">暂无传方记录</view>
|
||||
<view v-for="item in list" :key="item.id" class="row" @click="goDetail(item.id)">
|
||||
<view class="top">
|
||||
<text class="name">{{ item.patient_name }} {{ item.patient_mobile }}</text>
|
||||
<u-tag :text="item.is_imported ? '已导入' : '待导入'" :type="item.is_imported ? 'success' : 'warning'" size="mini" />
|
||||
</view>
|
||||
<text class="diag">诊断:{{ item.clinical_diagnose || '-' }}</text>
|
||||
<text class="time">{{ item.transfer_time_text }}</text>
|
||||
</view>
|
||||
<u-button v-if="list.length" plain type="primary" class="mt" @click="goForm">新建传方</u-button>
|
||||
</view>
|
||||
</clinic-salesperson-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ClinicSalespersonPageLayout from '@/subPackages/sub_clinic_salesperson/components/clinic-salesperson-page-layout.vue'
|
||||
import { getClinicSalespersonTransferPrescriptionList } from '@/api/clinicSalesperson.js'
|
||||
|
||||
export default {
|
||||
components: { ClinicSalespersonPageLayout },
|
||||
data() {
|
||||
return { list: [] }
|
||||
},
|
||||
onShow() {
|
||||
this.loadList()
|
||||
},
|
||||
methods: {
|
||||
async loadList() {
|
||||
const wrap = await getClinicSalespersonTransferPrescriptionList({ page: 1, pageSize: 50 })
|
||||
if (wrap && wrap.ok) {
|
||||
this.list = wrap.data?.items || []
|
||||
}
|
||||
},
|
||||
goForm() {
|
||||
uni.navigateTo({ url: '/subPackages/sub_clinic_salesperson/transfer-prescription/form' })
|
||||
},
|
||||
goDetail(id) {
|
||||
uni.showToast({ title: `传方#${id}`, icon: 'none' })
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.empty { text-align: center; color: #999; padding: 80rpx 0; }
|
||||
.row { background: #fff; border-radius: 12rpx; padding: 24rpx; margin-bottom: 16rpx; }
|
||||
.top { display: flex; justify-content: space-between; align-items: center; }
|
||||
.name { font-size: 28rpx; font-weight: 600; }
|
||||
.diag, .time { display: block; font-size: 24rpx; color: #666; margin-top: 8rpx; }
|
||||
.mt { margin-top: 24rpx; }
|
||||
</style>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template>
|
||||
<page-layout :is-platform="true" :title="title" :show-back="showBack" :show-tabbar="showTabbar" :tab-index="tabIndex">
|
||||
<slot />
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageLayout from '@/subPackages/sub_salesperson_manage/components/PageLayout.vue';
|
||||
|
||||
export default {
|
||||
name: 'StorePageLayout',
|
||||
components: { PageLayout },
|
||||
props: {
|
||||
title: { type: String, default: '' },
|
||||
showBack: { type: Boolean, default: true },
|
||||
showTabbar: { type: Boolean, default: false },
|
||||
tabIndex: { type: Number, default: 0 },
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -105,6 +105,16 @@ export default {
|
||||
key: 'order', label: '商品订单', desc: '全平台订单概览', path: `${shared}/order/index`,
|
||||
icon: 'bag-fill',
|
||||
theme: { bg: 'linear-gradient(135deg, #F0FDF4, #DCFCE7)', color: '#22C55E' } // 安全绿
|
||||
},
|
||||
{
|
||||
key: 'store-manage', label: '门店管理', desc: '诊所列表与价格配置', path: `${root}/store/index`,
|
||||
icon: 'home-fill',
|
||||
theme: { bg: 'linear-gradient(135deg, #ECFEFF, #CFFAFE)', color: '#06B6D4' }
|
||||
},
|
||||
{
|
||||
key: 'salesperson-manage', label: '推广员管理', desc: '全平台推广员', path: '/subPackages/sub_salesperson_manage/index?mode=platform',
|
||||
icon: 'account-fill',
|
||||
theme: { bg: 'linear-gradient(135deg, #FAF5FF, #F3E8FF)', color: '#A855F7' }
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
154
subPackages/sub_platform_admin/store/consultation.vue
Normal file
154
subPackages/sub_platform_admin/store/consultation.vue
Normal file
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<store-page-layout title="在线复诊配置" :show-back="true">
|
||||
<view class="page">
|
||||
<view class="field">
|
||||
<text class="label">互联网诊疗资质</text>
|
||||
<switch :checked="form.is_internet_medical === 1" color="#6ACDBB" @change="onMedicalChange" />
|
||||
</view>
|
||||
|
||||
<view v-if="form.is_internet_medical !== 1 && storeOptions.length" class="field column">
|
||||
<text class="label">委托诊所</text>
|
||||
<picker :range="storeLabels" :value="delegateIndex" @change="onDelegateChange">
|
||||
<view class="picker-val">{{ storeLabels[delegateIndex] || '请选择' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="field column">
|
||||
<text class="label">复诊负责人</text>
|
||||
<picker :range="doctorLabels" :value="doctorIndex" @change="onDoctorChange">
|
||||
<view class="picker-val">{{ doctorLabels[doctorIndex] || '请选择' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="submit-btn" @click="submit">保存配置</view>
|
||||
</view>
|
||||
</store-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StorePageLayout from '@/subPackages/sub_platform_admin/components/store-page-layout.vue';
|
||||
import {
|
||||
getPlatformStoreDetail,
|
||||
getDoctorOption,
|
||||
getInternetMedicalStoreOption,
|
||||
bindOnlineConsultation,
|
||||
} from '@/api/platformStore.js';
|
||||
|
||||
export default {
|
||||
components: { StorePageLayout },
|
||||
data() {
|
||||
return {
|
||||
storeId: 0,
|
||||
form: {
|
||||
id: 0,
|
||||
is_internet_medical: 0,
|
||||
delegate_store_id: null,
|
||||
online_consultation_doctor_id: null,
|
||||
},
|
||||
storeOptions: [],
|
||||
doctorOptions: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
storeLabels() {
|
||||
return this.storeOptions.map((s) => s.name);
|
||||
},
|
||||
doctorLabels() {
|
||||
return this.doctorOptions.map((d) => d.name || d.nick_name || `医生#${d.id}`);
|
||||
},
|
||||
delegateIndex() {
|
||||
if (!this.form.delegate_store_id) return 0;
|
||||
const idx = this.storeOptions.findIndex((s) => Number(s.id) === Number(this.form.delegate_store_id));
|
||||
return idx >= 0 ? idx : 0;
|
||||
},
|
||||
doctorIndex() {
|
||||
if (!this.form.online_consultation_doctor_id) return 0;
|
||||
const idx = this.doctorOptions.findIndex((d) => Number(d.id) === Number(this.form.online_consultation_doctor_id));
|
||||
return idx >= 0 ? idx : 0;
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.storeId = Number(options.id || 0);
|
||||
this.form.id = this.storeId;
|
||||
this.init();
|
||||
},
|
||||
methods: {
|
||||
async init() {
|
||||
const [detailWrap, storeWrap] = await Promise.all([
|
||||
getPlatformStoreDetail(this.storeId),
|
||||
getInternetMedicalStoreOption(),
|
||||
]);
|
||||
if (detailWrap && detailWrap.ok) {
|
||||
const d = detailWrap.data || {};
|
||||
this.form.is_internet_medical = Number(d.is_internet_medical) || 0;
|
||||
this.form.delegate_store_id = d.delegate_store_id || null;
|
||||
this.form.online_consultation_doctor_id = d.online_consultation_doctor_id || null;
|
||||
}
|
||||
this.storeOptions = (storeWrap && storeWrap.ok && storeWrap.data) ? storeWrap.data : [];
|
||||
await this.loadDoctors();
|
||||
},
|
||||
async loadDoctors() {
|
||||
const storeId = this.form.is_internet_medical === 1
|
||||
? this.storeId
|
||||
: (this.form.delegate_store_id || 0);
|
||||
if (!storeId) {
|
||||
this.doctorOptions = [];
|
||||
return;
|
||||
}
|
||||
const wrap = await getDoctorOption(storeId);
|
||||
this.doctorOptions = (wrap && wrap.ok && wrap.data) ? wrap.data : [];
|
||||
},
|
||||
onMedicalChange(e) {
|
||||
this.form.is_internet_medical = e.detail.value ? 1 : 0;
|
||||
if (this.form.is_internet_medical === 1) {
|
||||
this.form.delegate_store_id = null;
|
||||
}
|
||||
this.loadDoctors();
|
||||
},
|
||||
onDelegateChange(e) {
|
||||
const idx = Number(e.detail.value);
|
||||
const item = this.storeOptions[idx];
|
||||
this.form.delegate_store_id = item ? item.id : null;
|
||||
this.form.online_consultation_doctor_id = null;
|
||||
this.loadDoctors();
|
||||
},
|
||||
onDoctorChange(e) {
|
||||
const idx = Number(e.detail.value);
|
||||
const item = this.doctorOptions[idx];
|
||||
this.form.online_consultation_doctor_id = item ? item.id : null;
|
||||
},
|
||||
async submit() {
|
||||
uni.showLoading({ title: '保存中' });
|
||||
try {
|
||||
const wrap = await bindOnlineConsultation({
|
||||
id: this.storeId,
|
||||
is_internet_medical: this.form.is_internet_medical,
|
||||
delegate_store_id: this.form.delegate_store_id || null,
|
||||
online_consultation_doctor_id: this.form.online_consultation_doctor_id || null,
|
||||
});
|
||||
if (wrap && wrap.ok) {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' });
|
||||
setTimeout(() => uni.navigateBack(), 500);
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.field {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 16rpx; font-size: 28rpx;
|
||||
}
|
||||
.field.column { flex-direction: column; align-items: flex-start; gap: 16rpx; }
|
||||
.label { font-weight: 500; }
|
||||
.picker-val { color: #6ACDBB; font-size: 28rpx; }
|
||||
.submit-btn {
|
||||
margin-top: 40rpx; background: linear-gradient(90deg, #00BFA6, #6ACDBB);
|
||||
color: #fff; text-align: center; padding: 24rpx; border-radius: 16rpx; font-size: 30rpx;
|
||||
}
|
||||
</style>
|
||||
200
subPackages/sub_platform_admin/store/detail.vue
Normal file
200
subPackages/sub_platform_admin/store/detail.vue
Normal file
@@ -0,0 +1,200 @@
|
||||
<template>
|
||||
<store-page-layout title="门店详情" :show-back="true">
|
||||
<view class="page" v-if="store.id">
|
||||
<view class="hero-card">
|
||||
<text class="name">{{ store.name }}</text>
|
||||
<text class="sub">ID {{ store.id }} · {{ clinicTypeText(store.clinic_type) }}</text>
|
||||
<text class="addr">{{ store.position || '暂无地址' }}</text>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<text class="section-title">门店配置</text>
|
||||
<view class="switch-row">
|
||||
<text>推广员可见价格</text>
|
||||
<switch :checked="Number(store.salesperson_see_price) === 1" color="#6ACDBB" @change="toggleSeePrice" />
|
||||
</view>
|
||||
<view class="switch-row">
|
||||
<text>包邮</text>
|
||||
<switch :checked="Number(store.is_shipping_free) === 1" color="#6ACDBB" @change="toggleShipping" />
|
||||
</view>
|
||||
<view class="switch-row">
|
||||
<text>订阅价格波动</text>
|
||||
<switch :checked="Number(store.subscribe_price_change) === 0" color="#6ACDBB" @change="toggleSubscribe" />
|
||||
</view>
|
||||
<view class="switch-row">
|
||||
<text>查看毛利率</text>
|
||||
<switch :checked="Number(store.see_rate) === 1" color="#6ACDBB" @change="toggleSeeRate" />
|
||||
</view>
|
||||
<view class="switch-row">
|
||||
<text>开方可选医保</text>
|
||||
<switch :checked="Number(store.allow_insurance_category) === 1" color="#6ACDBB" @change="toggleInsurance" />
|
||||
</view>
|
||||
<view class="picker-row">
|
||||
<text>诊所类型</text>
|
||||
<picker :range="clinicTypeLabels" :value="clinicTypeIndex" @change="onClinicType">
|
||||
<view class="picker-val">{{ clinicTypeLabels[clinicTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<text class="section-title">快捷操作</text>
|
||||
<view class="action-grid">
|
||||
<view class="action-btn" @click="goEdit">编辑资料</view>
|
||||
<view class="action-btn" @click="goDrugPrice">修改药品价格</view>
|
||||
<view class="action-btn" @click="goConsultation">在线复诊配置</view>
|
||||
<view class="action-btn" @click="goPromoters">管理推广员</view>
|
||||
<view class="action-btn" @click="openQr">诊所二维码</view>
|
||||
<view class="action-btn warn" @click="openPc">开通后台</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="store.online_consultation_doctor_name || store.delegate_store_name" class="section">
|
||||
<text class="section-title">在线复诊</text>
|
||||
<text v-if="store.delegate_store_name" class="info-line">委托诊所:{{ store.delegate_store_name }}</text>
|
||||
<text v-if="store.online_consultation_doctor_name" class="info-line">负责人:{{ store.online_consultation_doctor_name }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</store-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StorePageLayout from '@/subPackages/sub_platform_admin/components/store-page-layout.vue';
|
||||
import {
|
||||
getPlatformStoreDetail,
|
||||
toggleSalespersonSeePrice,
|
||||
updateStoreShippingFree,
|
||||
updateStoreSubscribeStatus,
|
||||
updateStoreSeeRate,
|
||||
updateStoreAllowInsurance,
|
||||
updateStoreClinicType,
|
||||
openStorePcWindows,
|
||||
openStoreQrCode,
|
||||
} from '@/api/platformStore.js';
|
||||
import { openQrCodePreview, buildClinicQrPayload } from '@/utils/qrCodePreview.js';
|
||||
|
||||
const ROOT = '/subPackages/sub_platform_admin/store';
|
||||
const MANAGE_ROOT = '/subPackages/sub_salesperson_manage';
|
||||
|
||||
export default {
|
||||
components: { StorePageLayout },
|
||||
data() {
|
||||
return {
|
||||
storeId: 0,
|
||||
store: {},
|
||||
clinicTypeLabels: ['未设置', '西医诊所', '中医诊所'],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
clinicTypeIndex() {
|
||||
return Number(this.store.clinic_type) || 0;
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.storeId = Number(options.id || 0);
|
||||
this.loadDetail();
|
||||
},
|
||||
methods: {
|
||||
clinicTypeText(type) {
|
||||
return this.clinicTypeLabels[Number(type) || 0] || '未设置';
|
||||
},
|
||||
async loadDetail() {
|
||||
if (!this.storeId) return;
|
||||
const wrap = await getPlatformStoreDetail(this.storeId);
|
||||
if (wrap && wrap.ok) {
|
||||
this.store = wrap.data || {};
|
||||
}
|
||||
},
|
||||
async toggleSeePrice() {
|
||||
const wrap = await toggleSalespersonSeePrice(this.storeId);
|
||||
if (wrap && wrap.ok && wrap.data) {
|
||||
this.store.salesperson_see_price = wrap.data.see_price;
|
||||
}
|
||||
},
|
||||
async toggleShipping() {
|
||||
const wrap = await updateStoreShippingFree(this.storeId);
|
||||
if (wrap && wrap.ok) await this.loadDetail();
|
||||
},
|
||||
async toggleSubscribe() {
|
||||
const wrap = await updateStoreSubscribeStatus(this.storeId);
|
||||
if (wrap && wrap.ok) await this.loadDetail();
|
||||
},
|
||||
async toggleSeeRate() {
|
||||
const wrap = await updateStoreSeeRate(this.storeId);
|
||||
if (wrap && wrap.ok) await this.loadDetail();
|
||||
},
|
||||
async toggleInsurance() {
|
||||
const wrap = await updateStoreAllowInsurance(this.storeId);
|
||||
if (wrap && wrap.ok) await this.loadDetail();
|
||||
},
|
||||
async onClinicType(e) {
|
||||
const idx = Number(e.detail.value);
|
||||
if (idx <= 0) return;
|
||||
const wrap = await updateStoreClinicType(this.storeId, idx);
|
||||
if (wrap && wrap.ok) {
|
||||
this.store.clinic_type = idx;
|
||||
uni.showToast({ title: '已更新', icon: 'success' });
|
||||
}
|
||||
},
|
||||
goEdit() {
|
||||
uni.navigateTo({ url: `${ROOT}/edit?id=${this.storeId}` });
|
||||
},
|
||||
goDrugPrice() {
|
||||
uni.navigateTo({ url: `${ROOT}/drug-price?id=${this.storeId}&name=${encodeURIComponent(this.store.name || '')}` });
|
||||
},
|
||||
goConsultation() {
|
||||
uni.navigateTo({ url: `${ROOT}/consultation?id=${this.storeId}` });
|
||||
},
|
||||
goPromoters() {
|
||||
uni.navigateTo({ url: `${MANAGE_ROOT}/index?mode=platform&store_id=${this.storeId}` });
|
||||
},
|
||||
async openQr() {
|
||||
uni.showLoading({ title: '生成中' });
|
||||
try {
|
||||
const wrap = await openStoreQrCode(this.storeId);
|
||||
if (wrap && wrap.ok) {
|
||||
await this.loadDetail();
|
||||
const payload = buildClinicQrPayload(this.store);
|
||||
openQrCodePreview({ type: 'clinic', title: this.store.name, payload: payload.payload });
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
async openPc() {
|
||||
uni.showModal({
|
||||
title: '开通后台',
|
||||
content: '确定为该诊所开通 PC 后台账号?',
|
||||
success: async (res) => {
|
||||
if (!res.confirm) return;
|
||||
const wrap = await openStorePcWindows(this.storeId);
|
||||
if (wrap && wrap.ok) {
|
||||
uni.showToast({ title: '开通成功', icon: 'success' });
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.hero-card { background: linear-gradient(135deg, #6acdbb, #4db6a8); border-radius: 20rpx; padding: 32rpx; color: #fff; margin-bottom: 24rpx; }
|
||||
.name { display: block; font-size: 36rpx; font-weight: 700; }
|
||||
.sub, .addr { display: block; font-size: 24rpx; margin-top: 8rpx; opacity: 0.9; }
|
||||
.section { background: #fff; border-radius: 20rpx; padding: 24rpx; margin-bottom: 20rpx; }
|
||||
.section-title { display: block; font-size: 28rpx; font-weight: 600; margin-bottom: 16rpx; }
|
||||
.switch-row, .picker-row {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 20rpx 0; border-bottom: 1rpx solid #f0f0f0; font-size: 28rpx;
|
||||
}
|
||||
.picker-val { color: #6ACDBB; }
|
||||
.action-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
|
||||
.action-btn {
|
||||
flex: 1; min-width: 40%; text-align: center; padding: 20rpx;
|
||||
background: #f0fbf8; color: #6ACDBB; border-radius: 12rpx; font-size: 26rpx;
|
||||
}
|
||||
.action-btn.warn { background: #FEF3C7; color: #D97706; }
|
||||
.info-line { display: block; font-size: 26rpx; color: #666; margin-bottom: 8rpx; }
|
||||
</style>
|
||||
106
subPackages/sub_platform_admin/store/drug-price.vue
Normal file
106
subPackages/sub_platform_admin/store/drug-price.vue
Normal file
@@ -0,0 +1,106 @@
|
||||
<template>
|
||||
<store-page-layout title="药品价格" :show-back="true">
|
||||
<view class="page">
|
||||
<view class="toolbar">
|
||||
<picker :range="typeLabels" :value="typeIndex" @change="onTypeChange">
|
||||
<view class="type-picker">{{ typeLabels[typeIndex] }}</view>
|
||||
</picker>
|
||||
<view class="save-btn" @click="save">保存</view>
|
||||
</view>
|
||||
|
||||
<view v-if="!list.length && !loading" class="empty">暂无药品</view>
|
||||
<view v-for="item in list" :key="item.id" class="row">
|
||||
<view class="drug-info">
|
||||
<text class="drug-name">{{ item.drug_name || item.name }}</text>
|
||||
<text class="spec">{{ item.drug_spec || item.spec || '' }}</text>
|
||||
</view>
|
||||
<view v-if="dirtyMap[item.id]" class="price-inputs">
|
||||
<input class="price-input" v-model="dirtyMap[item.id].price" type="digit" placeholder="售价" />
|
||||
<input class="price-input" v-model="dirtyMap[item.id].buy_price" type="digit" placeholder="供货价" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</store-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StorePageLayout from '@/subPackages/sub_platform_admin/components/store-page-layout.vue';
|
||||
import { DRUG_TYPE_OPTIONS, getStoreDrugsByType, updateStoreDrugPrices } from '@/api/platformStore.js';
|
||||
|
||||
export default {
|
||||
components: { StorePageLayout },
|
||||
data() {
|
||||
return {
|
||||
storeId: 0,
|
||||
typeIndex: 1,
|
||||
typeLabels: DRUG_TYPE_OPTIONS.map((o) => o.label),
|
||||
list: [],
|
||||
dirtyMap: {},
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.storeId = Number(options.id || 0);
|
||||
this.reload();
|
||||
},
|
||||
methods: {
|
||||
onTypeChange(e) {
|
||||
this.typeIndex = Number(e.detail.value);
|
||||
this.reload();
|
||||
},
|
||||
async reload() {
|
||||
if (!this.storeId) return;
|
||||
this.loading = true;
|
||||
try {
|
||||
const type = DRUG_TYPE_OPTIONS[this.typeIndex].value;
|
||||
const wrap = await getStoreDrugsByType(this.storeId, type);
|
||||
const items = (wrap && wrap.ok && wrap.data) ? (wrap.data.items || wrap.data || []) : [];
|
||||
this.list = Array.isArray(items) ? items : [];
|
||||
const map = {};
|
||||
this.list.forEach((row) => {
|
||||
map[row.id] = {
|
||||
price: row.price != null ? String(row.price) : '',
|
||||
buy_price: row.buy_price != null ? String(row.buy_price) : '',
|
||||
};
|
||||
});
|
||||
this.dirtyMap = map;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async save() {
|
||||
const drugs = this.list.map((row) => ({
|
||||
id: row.id,
|
||||
price: Number(this.dirtyMap[row.id].price) || 0,
|
||||
buy_price: Number(this.dirtyMap[row.id].buy_price) || 0,
|
||||
})).filter((d) => d.id > 0);
|
||||
if (!drugs.length) {
|
||||
uni.showToast({ title: '无数据', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.showLoading({ title: '保存中' });
|
||||
try {
|
||||
const wrap = await updateStoreDrugPrices({ store_id: this.storeId, drugs });
|
||||
if (wrap && wrap.ok) {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' });
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; padding-bottom: 48rpx; }
|
||||
.toolbar { display: flex; gap: 16rpx; margin-bottom: 20rpx; align-items: center; }
|
||||
.type-picker { flex: 1; background: #fff; padding: 20rpx; border-radius: 12rpx; font-size: 26rpx; }
|
||||
.save-btn { background: #6ACDBB; color: #fff; padding: 20rpx 28rpx; border-radius: 12rpx; font-size: 26rpx; }
|
||||
.row { background: #fff; border-radius: 16rpx; padding: 20rpx; margin-bottom: 12rpx; }
|
||||
.drug-name { display: block; font-size: 28rpx; font-weight: 600; }
|
||||
.spec { font-size: 22rpx; color: #999; }
|
||||
.price-inputs { display: flex; gap: 12rpx; margin-top: 12rpx; }
|
||||
.price-input { flex: 1; background: #f5f5f5; border-radius: 8rpx; padding: 12rpx; font-size: 26rpx; text-align: center; }
|
||||
.empty { text-align: center; color: #999; padding: 80rpx 0; }
|
||||
</style>
|
||||
111
subPackages/sub_platform_admin/store/edit.vue
Normal file
111
subPackages/sub_platform_admin/store/edit.vue
Normal file
@@ -0,0 +1,111 @@
|
||||
<template>
|
||||
<store-page-layout title="编辑诊所" :show-back="true">
|
||||
<view class="page">
|
||||
<view class="field">
|
||||
<text class="label">诊所名称</text>
|
||||
<input class="input" v-model="form.name" placeholder="请输入" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">联系人</text>
|
||||
<input class="input" v-model="form.contact" placeholder="请输入" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">联系电话</text>
|
||||
<input class="input" v-model="form.mobile" type="number" placeholder="请输入" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">详细地址</text>
|
||||
<input class="input" v-model="form.position" placeholder="请输入" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">营业开始</text>
|
||||
<input class="input" v-model="form.start_time" placeholder="如 08:00" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">营业结束</text>
|
||||
<input class="input" v-model="form.end_time" placeholder="如 20:00" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">销售倍率</text>
|
||||
<input class="input" v-model="form.z_sale_percent" type="digit" placeholder="请输入" />
|
||||
</view>
|
||||
<view class="submit-btn" @click="submit">保存</view>
|
||||
</view>
|
||||
</store-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StorePageLayout from '@/subPackages/sub_platform_admin/components/store-page-layout.vue';
|
||||
import { getPlatformStoreDetail, updatePlatformStore } from '@/api/platformStore.js';
|
||||
|
||||
export default {
|
||||
components: { StorePageLayout },
|
||||
data() {
|
||||
return {
|
||||
storeId: 0,
|
||||
form: {
|
||||
id: 0,
|
||||
name: '',
|
||||
contact: '',
|
||||
mobile: '',
|
||||
position: '',
|
||||
start_time: '',
|
||||
end_time: '',
|
||||
z_sale_percent: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.storeId = Number(options.id || 0);
|
||||
this.loadDetail();
|
||||
},
|
||||
methods: {
|
||||
async loadDetail() {
|
||||
if (!this.storeId) return;
|
||||
const wrap = await getPlatformStoreDetail(this.storeId);
|
||||
if (!wrap || !wrap.ok) return;
|
||||
const d = wrap.data || {};
|
||||
this.form = {
|
||||
id: d.id,
|
||||
name: d.name || '',
|
||||
contact: d.contact || '',
|
||||
mobile: d.mobile || '',
|
||||
position: d.position || '',
|
||||
start_time: d.start_time || '',
|
||||
end_time: d.end_time || '',
|
||||
z_sale_percent: d.z_sale_percent != null ? String(d.z_sale_percent) : '',
|
||||
};
|
||||
},
|
||||
async submit() {
|
||||
if (!this.form.name) {
|
||||
uni.showToast({ title: '请填写诊所名称', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.showLoading({ title: '保存中' });
|
||||
try {
|
||||
const wrap = await updatePlatformStore({ ...this.form });
|
||||
if (wrap && wrap.ok) {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' });
|
||||
setTimeout(() => uni.navigateBack(), 500);
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.field {
|
||||
display: flex; align-items: center; background: #fff; border-radius: 16rpx;
|
||||
padding: 24rpx; margin-bottom: 16rpx;
|
||||
}
|
||||
.label { width: 180rpx; font-size: 28rpx; flex-shrink: 0; }
|
||||
.input { flex: 1; font-size: 28rpx; text-align: right; }
|
||||
.submit-btn {
|
||||
margin-top: 40rpx; background: linear-gradient(90deg, #00BFA6, #6ACDBB);
|
||||
color: #fff; text-align: center; padding: 24rpx; border-radius: 16rpx; font-size: 30rpx;
|
||||
}
|
||||
</style>
|
||||
114
subPackages/sub_platform_admin/store/index.vue
Normal file
114
subPackages/sub_platform_admin/store/index.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<store-page-layout title="门店管理" :show-back="true">
|
||||
<view class="page">
|
||||
<view class="filter-panel">
|
||||
<input class="filter-input" v-model="filters.name" placeholder="诊所名称" />
|
||||
<input class="filter-input" v-model="filters.shouzimu" placeholder="拼音首字母" />
|
||||
<input class="filter-input" v-model="filters.mobile" placeholder="联系人手机号" />
|
||||
<view class="filter-btn" @click="reload">查询</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="empty">加载中...</view>
|
||||
<view v-else-if="!list.length" class="empty">暂无门店</view>
|
||||
|
||||
<view
|
||||
v-for="item in list"
|
||||
:key="item.id"
|
||||
class="card"
|
||||
hover-class="card-hover"
|
||||
@click="goDetail(item)"
|
||||
>
|
||||
<view class="card-head">
|
||||
<text class="name">{{ item.name }}</text>
|
||||
<text class="type-tag">{{ clinicTypeText(item.clinic_type) }}</text>
|
||||
</view>
|
||||
<text class="addr">{{ item.position || '暂无地址' }}</text>
|
||||
<view class="meta-row">
|
||||
<text>ID {{ item.id }}</text>
|
||||
<text v-if="item.mobile">电话 {{ item.mobile }}</text>
|
||||
</view>
|
||||
<view class="tag-row">
|
||||
<text class="mini-tag" :class="{ on: Number(item.salesperson_see_price) === 1 }">推广员可见价格</text>
|
||||
<text class="mini-tag" :class="{ on: Number(item.is_shipping_free) === 1 }">包邮</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</store-page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StorePageLayout from '@/subPackages/sub_platform_admin/components/store-page-layout.vue';
|
||||
import { getPlatformStoreList } from '@/api/platformStore.js';
|
||||
|
||||
const ROOT = '/subPackages/sub_platform_admin/store';
|
||||
|
||||
export default {
|
||||
components: { StorePageLayout },
|
||||
data() {
|
||||
return {
|
||||
filters: { name: '', shouzimu: '', mobile: '' },
|
||||
list: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
onShow() {
|
||||
this.reload();
|
||||
},
|
||||
methods: {
|
||||
clinicTypeText(type) {
|
||||
if (Number(type) === 1) return '西医诊所';
|
||||
if (Number(type) === 2) return '中医诊所';
|
||||
return '未设置';
|
||||
},
|
||||
async reload() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const wrap = await getPlatformStoreList({
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
...this.filters,
|
||||
});
|
||||
const data = wrap && wrap.ok ? wrap.data : null;
|
||||
if (data && Array.isArray(data.items)) {
|
||||
this.list = data.items;
|
||||
} else if (Array.isArray(data)) {
|
||||
this.list = data;
|
||||
} else {
|
||||
this.list = [];
|
||||
}
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
goDetail(item) {
|
||||
uni.navigateTo({ url: `${ROOT}/detail?id=${item.id}` });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.filter-panel { display: flex; flex-wrap: wrap; gap: 12rpx; margin-bottom: 20rpx; }
|
||||
.filter-input {
|
||||
flex: 1; min-width: 200rpx; background: #fff; border-radius: 12rpx;
|
||||
padding: 16rpx 20rpx; font-size: 26rpx;
|
||||
}
|
||||
.filter-btn {
|
||||
background: #6ACDBB; color: #fff; padding: 16rpx 32rpx; border-radius: 12rpx; font-size: 26rpx;
|
||||
}
|
||||
.empty { text-align: center; color: #999; padding: 80rpx 0; }
|
||||
.card {
|
||||
background: #fff; border-radius: 20rpx; padding: 28rpx; margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.04);
|
||||
}
|
||||
.card-hover { opacity: 0.92; }
|
||||
.card-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8rpx; }
|
||||
.name { font-size: 30rpx; font-weight: 600; flex: 1; }
|
||||
.type-tag { font-size: 22rpx; color: #6ACDBB; background: #f0fbf8; padding: 4rpx 12rpx; border-radius: 8rpx; }
|
||||
.addr { font-size: 24rpx; color: #666; display: block; margin-bottom: 12rpx; }
|
||||
.meta-row { display: flex; justify-content: space-between; font-size: 22rpx; color: #999; margin-bottom: 12rpx; }
|
||||
.tag-row { display: flex; gap: 12rpx; flex-wrap: wrap; }
|
||||
.mini-tag { font-size: 22rpx; color: #999; background: #f5f5f5; padding: 4rpx 12rpx; border-radius: 8rpx; }
|
||||
.mini-tag.on { color: #22C55E; background: #F0FDF4; }
|
||||
</style>
|
||||
235
subPackages/sub_salesperson_manage/commission/index.vue
Normal file
235
subPackages/sub_salesperson_manage/commission/index.vue
Normal file
@@ -0,0 +1,235 @@
|
||||
<template>
|
||||
<page-layout :is-platform="ctx.isPlatform" :title="pageTitle" :show-back="true">
|
||||
<view class="page">
|
||||
<view class="tabs">
|
||||
<view
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
class="tab"
|
||||
:class="{ active: activeTab === tab.key }"
|
||||
@click="switchTab(tab.key)"
|
||||
>{{ tab.label }}</view>
|
||||
</view>
|
||||
|
||||
<view v-if="activeTab !== 'history'" class="toolbar">
|
||||
<input class="search" v-model="orderNo" placeholder="订单号" @confirm="loadOrders" />
|
||||
<view class="search-btn" @click="loadOrders">查询</view>
|
||||
</view>
|
||||
|
||||
<template v-if="activeTab === 'pending'">
|
||||
<view class="settle-actions">
|
||||
<view class="settle-btn" @click="goPeriodSettle">按时间段结算</view>
|
||||
<view class="settle-btn primary" @click="goOrderSettle">结算选中订单</view>
|
||||
</view>
|
||||
<view v-for="item in orderList" :key="item.order_id" class="order-row" @click="toggleSelect(item)">
|
||||
<view class="checkbox" :class="{ checked: selectedIds.includes(item.order_id) }" />
|
||||
<view class="order-info">
|
||||
<text class="order-no">{{ item.order_no }}</text>
|
||||
<text class="sub">明细 {{ item.item_count }} 条 · {{ item.order_pay_at_text }}</text>
|
||||
</view>
|
||||
<text class="amount">¥{{ item.commission_amount }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template v-else-if="activeTab === 'settled'">
|
||||
<view v-for="item in orderList" :key="item.order_id" class="order-row" @click="showOrderDetail(item)">
|
||||
<view class="order-info">
|
||||
<text class="order-no">{{ item.order_no }}</text>
|
||||
<text class="sub">明细 {{ item.item_count }} 条 · {{ item.order_pay_at_text }}</text>
|
||||
</view>
|
||||
<text class="amount">¥{{ item.commission_amount }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<view v-for="item in settlementList" :key="item.id" class="order-row" @click="goSettlementDetail(item.id)">
|
||||
<view class="order-info">
|
||||
<text class="order-no">{{ item.settlement_no }}</text>
|
||||
<text class="sub">{{ item.settlement_type_text }} · {{ item.created_at_text }}</text>
|
||||
</view>
|
||||
<text class="amount">¥{{ item.amount }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<view v-if="!loading && isEmpty" class="empty">暂无数据</view>
|
||||
</view>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageLayout from '../components/PageLayout.vue';
|
||||
import { getSalespersonManageContext, resolveManageMode } from '../common/context.js';
|
||||
|
||||
const ROOT = '/subPackages/sub_salesperson_manage';
|
||||
|
||||
export default {
|
||||
components: { PageLayout },
|
||||
data() {
|
||||
return {
|
||||
ctx: getSalespersonManageContext('clinic'),
|
||||
salespersonId: 0,
|
||||
storeId: 0,
|
||||
nickName: '',
|
||||
activeTab: 'pending',
|
||||
tabs: [
|
||||
{ key: 'pending', label: '待结算' },
|
||||
{ key: 'settled', label: '已结算' },
|
||||
{ key: 'history', label: '结算历史' },
|
||||
],
|
||||
orderNo: '',
|
||||
orderList: [],
|
||||
settlementList: [],
|
||||
selectedIds: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.nickName ? `分成与结算 - ${this.nickName}` : '分成与结算';
|
||||
},
|
||||
isEmpty() {
|
||||
if (this.activeTab === 'history') return !this.settlementList.length;
|
||||
return !this.orderList.length;
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.ctx = getSalespersonManageContext(resolveManageMode(options));
|
||||
this.salespersonId = Number(options.salesperson_id || 0);
|
||||
this.storeId = Number(options.store_id || 0);
|
||||
this.nickName = decodeURIComponent(options.nick_name || '');
|
||||
this.loadData();
|
||||
},
|
||||
methods: {
|
||||
switchTab(key) {
|
||||
this.activeTab = key;
|
||||
this.selectedIds = [];
|
||||
this.loadData();
|
||||
},
|
||||
loadData() {
|
||||
if (this.activeTab === 'history') this.loadSettlements();
|
||||
else this.loadOrders();
|
||||
},
|
||||
buildParams() {
|
||||
const params = {
|
||||
salesperson_id: this.salespersonId,
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
order_no: this.orderNo,
|
||||
};
|
||||
if (this.storeId > 0) params.store_id = this.storeId;
|
||||
if (this.activeTab === 'pending') params.status = 0;
|
||||
if (this.activeTab === 'settled') params.status = 1;
|
||||
return params;
|
||||
},
|
||||
async loadOrders() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const wrap = await this.ctx.api.getCommissionOrderList(this.buildParams());
|
||||
this.orderList = (wrap && wrap.ok && wrap.data && wrap.data.items) ? wrap.data.items : [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async loadSettlements() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const params = { salesperson_id: this.salespersonId, page: 1, pageSize: 50 };
|
||||
if (this.storeId > 0) params.store_id = this.storeId;
|
||||
const wrap = await this.ctx.api.getSettlementList(params);
|
||||
this.settlementList = (wrap && wrap.ok && wrap.data && wrap.data.items) ? wrap.data.items : [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
toggleSelect(item) {
|
||||
const id = item.order_id;
|
||||
const idx = this.selectedIds.indexOf(id);
|
||||
if (idx >= 0) this.selectedIds.splice(idx, 1);
|
||||
else this.selectedIds.push(id);
|
||||
},
|
||||
goPeriodSettle() {
|
||||
uni.navigateTo({
|
||||
url: `${ROOT}/settlement/preview?mode=${this.ctx.mode}&salesperson_id=${this.salespersonId}&store_id=${this.storeId}`,
|
||||
});
|
||||
},
|
||||
async goOrderSettle() {
|
||||
if (!this.selectedIds.length) {
|
||||
uni.showToast({ title: '请勾选订单', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const params = {
|
||||
salesperson_id: this.salespersonId,
|
||||
settlement_type: 2,
|
||||
order_ids: this.selectedIds.join(','),
|
||||
};
|
||||
if (this.storeId > 0) params.store_id = this.storeId;
|
||||
uni.showLoading({ title: '预览中' });
|
||||
try {
|
||||
const wrap = await this.ctx.api.getSettlementPreview(params);
|
||||
if (!wrap || !wrap.ok) return;
|
||||
const preview = wrap.data || {};
|
||||
const q = [
|
||||
`mode=${this.ctx.mode}`,
|
||||
`salesperson_id=${this.salespersonId}`,
|
||||
`store_id=${this.storeId}`,
|
||||
'settlement_type=2',
|
||||
`order_ids=${this.selectedIds.join(',')}`,
|
||||
`amount=${encodeURIComponent(preview.amount || '0.00')}`,
|
||||
`record_count=${preview.record_count || 0}`,
|
||||
].join('&');
|
||||
uni.navigateTo({ url: `${ROOT}/settlement/confirm?${q}` });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
async showOrderDetail(item) {
|
||||
const params = {
|
||||
salesperson_id: this.salespersonId,
|
||||
order_id: item.order_id,
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
};
|
||||
if (this.storeId > 0) params.store_id = this.storeId;
|
||||
const wrap = await this.ctx.api.getCommissionList(params);
|
||||
const items = (wrap && wrap.ok && wrap.data && wrap.data.items) ? wrap.data.items : [];
|
||||
const lines = items.map((r) => `${r.drug_info || ''} ¥${r.commission_amount}`).join('\n');
|
||||
uni.showModal({
|
||||
title: `订单 ${item.order_no}`,
|
||||
content: lines || '无明细',
|
||||
showCancel: false,
|
||||
});
|
||||
},
|
||||
goSettlementDetail(id) {
|
||||
uni.navigateTo({
|
||||
url: `${ROOT}/settlement/detail?mode=${this.ctx.mode}&id=${id}&store_id=${this.storeId}`,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.tabs { display: flex; background: #fff; border-radius: 16rpx; margin-bottom: 20rpx; overflow: hidden; }
|
||||
.tab { flex: 1; text-align: center; padding: 20rpx; font-size: 26rpx; color: #666; }
|
||||
.tab.active { background: #6ACDBB; color: #fff; font-weight: 600; }
|
||||
.toolbar { display: flex; gap: 12rpx; margin-bottom: 16rpx; }
|
||||
.search { flex: 1; background: #fff; border-radius: 12rpx; padding: 16rpx; font-size: 26rpx; }
|
||||
.search-btn { background: #6ACDBB; color: #fff; padding: 16rpx 24rpx; border-radius: 12rpx; font-size: 26rpx; }
|
||||
.settle-actions { display: flex; gap: 12rpx; margin-bottom: 16rpx; }
|
||||
.settle-btn { flex: 1; text-align: center; padding: 16rpx; background: #fff; border-radius: 12rpx; font-size: 26rpx; color: #6ACDBB; border: 1rpx solid #6ACDBB; }
|
||||
.settle-btn.primary { background: #6ACDBB; color: #fff; border: none; }
|
||||
.order-row {
|
||||
display: flex; align-items: center; background: #fff; border-radius: 16rpx;
|
||||
padding: 24rpx; margin-bottom: 12rpx;
|
||||
}
|
||||
.checkbox {
|
||||
width: 36rpx; height: 36rpx; border: 2rpx solid #ddd; border-radius: 8rpx; margin-right: 16rpx;
|
||||
}
|
||||
.checkbox.checked { background: #6ACDBB; border-color: #6ACDBB; }
|
||||
.order-info { flex: 1; }
|
||||
.order-no { display: block; font-size: 28rpx; font-weight: 600; }
|
||||
.sub { font-size: 22rpx; color: #999; }
|
||||
.amount { font-size: 30rpx; color: #F97316; font-weight: 600; }
|
||||
.empty { text-align: center; color: #999; padding: 80rpx 0; }
|
||||
</style>
|
||||
23
subPackages/sub_salesperson_manage/common/context.js
Normal file
23
subPackages/sub_salesperson_manage/common/context.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createSalespersonManageApi } from '@/api/salespersonManage.js';
|
||||
|
||||
const ROOT = '/subPackages/sub_salesperson_manage';
|
||||
|
||||
export function resolveManageMode(options) {
|
||||
const mode = (options && options.mode) || '';
|
||||
if (mode === 'platform' || mode === 'clinic') return mode;
|
||||
const loginMode = uni.getStorageSync('loginMode');
|
||||
return loginMode === 'platform_admin' ? 'platform' : 'clinic';
|
||||
}
|
||||
|
||||
export function getSalespersonManageContext(mode) {
|
||||
const api = createSalespersonManageApi(mode);
|
||||
return {
|
||||
mode,
|
||||
api,
|
||||
isPlatform: mode === 'platform',
|
||||
listPath: `${ROOT}/index?mode=${mode}`,
|
||||
homePath: mode === 'platform'
|
||||
? '/subPackages/sub_platform_admin/home/index'
|
||||
: '/subPackages/sub_clinic_admin/home/index',
|
||||
};
|
||||
}
|
||||
17
subPackages/sub_salesperson_manage/common/labels.js
Normal file
17
subPackages/sub_salesperson_manage/common/labels.js
Normal file
@@ -0,0 +1,17 @@
|
||||
export function splitTypeLabel(type) {
|
||||
return Number(type) === 1 ? '百分比' : '固定金额';
|
||||
}
|
||||
|
||||
export function tcmBaseLabel(type) {
|
||||
return Number(type) === 1 ? '处方总价' : '药店利润';
|
||||
}
|
||||
|
||||
export const SPLIT_TYPE_OPTIONS = [
|
||||
{ label: '元/件(固定金额)', value: 0 },
|
||||
{ label: '百分比(%)', value: 1 },
|
||||
];
|
||||
|
||||
export const TCM_BASE_OPTIONS = [
|
||||
{ label: '药店利润', value: 0 },
|
||||
{ label: '处方总价', value: 1 },
|
||||
];
|
||||
81
subPackages/sub_salesperson_manage/components/PageLayout.vue
Normal file
81
subPackages/sub_salesperson_manage/components/PageLayout.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<view class="manage-page-layout safe-area-inset-bottom">
|
||||
<u-navbar
|
||||
:title="title"
|
||||
:is-back="showBack"
|
||||
:is-fixed="true"
|
||||
:border-bottom="false"
|
||||
title-size="34"
|
||||
title-color="#fff"
|
||||
back-icon-color="#fff"
|
||||
:background="{ background: 'linear-gradient(90deg, #00BFA6 0%, #6ACDBB 93%)' }"
|
||||
/>
|
||||
<view class="page-body">
|
||||
<slot />
|
||||
</view>
|
||||
<u-tabbar
|
||||
v-if="showTabbar"
|
||||
:value="tabIndex"
|
||||
:list="tabList"
|
||||
active-color="#6ACDBB"
|
||||
@change="onTabChange"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getPlatformAdminContext } from '@/subPackages/sub_platform_admin/common/context.js';
|
||||
import { getClinicTabList, switchClinicTab } from '@/subPackages/sub_clinic_admin/common/tabbar.js';
|
||||
|
||||
export default {
|
||||
name: 'SalespersonManagePageLayout',
|
||||
props: {
|
||||
isPlatform: { type: Boolean, default: false },
|
||||
title: { type: String, default: '' },
|
||||
showBack: { type: Boolean, default: true },
|
||||
showTabbar: { type: Boolean, default: false },
|
||||
tabIndex: { type: Number, default: 0 },
|
||||
},
|
||||
provide() {
|
||||
if (this.isPlatform) {
|
||||
return { businessContext: getPlatformAdminContext() };
|
||||
}
|
||||
return {};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tabList: [],
|
||||
};
|
||||
},
|
||||
created() {
|
||||
if (this.showTabbar) {
|
||||
this.tabList = this.isPlatform
|
||||
? (getPlatformAdminContext().tabList || [])
|
||||
: getClinicTabList();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onTabChange(index) {
|
||||
if (index === this.tabIndex) return;
|
||||
const target = this.tabList[index];
|
||||
if (!target || !target.pagePath) return;
|
||||
if (this.isPlatform) {
|
||||
uni.reLaunch({ url: target.pagePath });
|
||||
} else {
|
||||
switchClinicTab(index);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.manage-page-layout {
|
||||
min-height: 100vh;
|
||||
background: #f9fafb;
|
||||
}
|
||||
.page-body {
|
||||
padding-bottom: calc(32rpx + env(safe-area-inset-bottom));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,83 @@
|
||||
<template>
|
||||
<view class="proof-uploader">
|
||||
<view class="label">结算凭证(至少1张)</view>
|
||||
<view class="image-list">
|
||||
<view v-for="(url, idx) in images" :key="url" class="image-item">
|
||||
<image class="thumb" :src="url" mode="aspectFill" @click="preview(idx)" />
|
||||
<view class="remove" @click="remove(idx)">×</view>
|
||||
</view>
|
||||
<view v-if="images.length < maxCount" class="add-btn" @click="chooseImage">
|
||||
<u-icon name="plus" size="40" color="#999" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uploadSalespersonManageFileApi, extractUploadUrl } from '@/api/upload.js';
|
||||
|
||||
export default {
|
||||
name: 'ProofImageUploader',
|
||||
props: {
|
||||
value: { type: Array, default: () => [] },
|
||||
maxCount: { type: Number, default: 5 },
|
||||
},
|
||||
computed: {
|
||||
images: {
|
||||
get() { return this.value || []; },
|
||||
set(v) { this.$emit('input', v); },
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
chooseImage() {
|
||||
const remain = this.maxCount - this.images.length;
|
||||
uni.chooseImage({
|
||||
count: remain,
|
||||
sizeType: ['compressed'],
|
||||
success: async (res) => {
|
||||
const paths = res.tempFilePaths || [];
|
||||
for (const filePath of paths) {
|
||||
try {
|
||||
uni.showLoading({ title: '上传中' });
|
||||
const uploadRes = await uploadSalespersonManageFileApi({ filePath });
|
||||
const url = extractUploadUrl(uploadRes);
|
||||
if (url) {
|
||||
this.$emit('input', [...this.images, url]);
|
||||
}
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '上传失败', icon: 'none' });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
remove(idx) {
|
||||
const next = [...this.images];
|
||||
next.splice(idx, 1);
|
||||
this.$emit('input', next);
|
||||
},
|
||||
preview(idx) {
|
||||
uni.previewImage({ urls: this.images, current: this.images[idx] });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.proof-uploader { margin-top: 24rpx; }
|
||||
.label { font-size: 26rpx; color: #666; margin-bottom: 16rpx; }
|
||||
.image-list { display: flex; flex-wrap: wrap; gap: 16rpx; }
|
||||
.image-item { position: relative; width: 160rpx; height: 160rpx; }
|
||||
.thumb { width: 100%; height: 100%; border-radius: 12rpx; }
|
||||
.remove {
|
||||
position: absolute; top: -8rpx; right: -8rpx;
|
||||
width: 36rpx; height: 36rpx; background: #ef4444; color: #fff;
|
||||
border-radius: 50%; text-align: center; line-height: 36rpx; font-size: 28rpx;
|
||||
}
|
||||
.add-btn {
|
||||
width: 160rpx; height: 160rpx; border: 2rpx dashed #ddd; border-radius: 12rpx;
|
||||
display: flex; align-items: center; justify-content: center; background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<view class="store-picker">
|
||||
<view class="picker-row" @click="openPicker">
|
||||
<text class="label">所属诊所</text>
|
||||
<text class="value" :class="{ placeholder: !selectedLabel }">{{ selectedLabel || '请选择诊所' }}</text>
|
||||
<u-icon name="arrow-right" size="28" color="#999" />
|
||||
</view>
|
||||
<u-popup v-model="visible" mode="bottom" border-radius="24" height="70%">
|
||||
<view class="popup-inner">
|
||||
<view class="popup-title">选择诊所</view>
|
||||
<input class="search-input" v-model="keyword" placeholder="搜索诊所名称" @input="onSearch" />
|
||||
<scroll-view scroll-y class="option-list">
|
||||
<view
|
||||
v-for="item in options"
|
||||
:key="item.id"
|
||||
class="option-item"
|
||||
@click="select(item)"
|
||||
>
|
||||
<text>{{ item.name }}</text>
|
||||
<text v-if="item.position" class="sub">{{ item.position }}</text>
|
||||
</view>
|
||||
<view v-if="!loading && !options.length" class="empty">暂无诊所</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'StoreSearchPicker',
|
||||
props: {
|
||||
value: { type: [Number, String], default: 0 },
|
||||
api: { type: Object, required: true },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
keyword: '',
|
||||
options: [],
|
||||
loading: false,
|
||||
selectedLabel: '',
|
||||
searchTimer: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openPicker() {
|
||||
this.visible = true;
|
||||
this.fetchOptions('');
|
||||
},
|
||||
onSearch() {
|
||||
if (this.searchTimer) clearTimeout(this.searchTimer);
|
||||
this.searchTimer = setTimeout(() => this.fetchOptions(this.keyword.trim()), 300);
|
||||
},
|
||||
async fetchOptions(keyword) {
|
||||
this.loading = true;
|
||||
try {
|
||||
const wrap = await this.api.getPlatformStoreOptions(keyword);
|
||||
this.options = (wrap && wrap.ok && wrap.data) ? wrap.data : [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
select(item) {
|
||||
this.selectedLabel = item.position ? `${item.name}(${item.position})` : item.name;
|
||||
this.$emit('input', item.id);
|
||||
this.visible = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.picker-row {
|
||||
display: flex; align-items: center; padding: 24rpx 0;
|
||||
border-bottom: 1rpx solid #f0f0f0;
|
||||
}
|
||||
.label { width: 180rpx; font-size: 28rpx; color: #333; }
|
||||
.value { flex: 1; font-size: 28rpx; text-align: right; margin-right: 8rpx; }
|
||||
.placeholder { color: #bbb; }
|
||||
.popup-inner { padding: 32rpx; height: 100%; display: flex; flex-direction: column; }
|
||||
.popup-title { font-size: 32rpx; font-weight: 600; margin-bottom: 24rpx; }
|
||||
.search-input {
|
||||
background: #f5f5f5; border-radius: 12rpx; padding: 16rpx 24rpx; margin-bottom: 16rpx;
|
||||
}
|
||||
.option-list { flex: 1; }
|
||||
.option-item {
|
||||
padding: 24rpx 0; border-bottom: 1rpx solid #f0f0f0;
|
||||
display: flex; flex-direction: column;
|
||||
}
|
||||
.sub { font-size: 24rpx; color: #999; margin-top: 8rpx; }
|
||||
.empty { text-align: center; color: #999; padding: 60rpx 0; }
|
||||
</style>
|
||||
175
subPackages/sub_salesperson_manage/drug-commission/index.vue
Normal file
175
subPackages/sub_salesperson_manage/drug-commission/index.vue
Normal file
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<page-layout :is-platform="ctx.isPlatform" title="药品佣金配置" :show-back="true">
|
||||
<view class="page">
|
||||
<view class="toolbar">
|
||||
<input class="search" v-model="keyword" placeholder="搜索药品" @confirm="reload" />
|
||||
<view class="search-btn" @click="reload">搜索</view>
|
||||
</view>
|
||||
|
||||
<view v-if="salespersonId > 0" class="copy-row">
|
||||
<picker :range="copyLabels" :value="copyIndex" @change="onCopyFrom">
|
||||
<view class="copy-picker">{{ copyLabels[copyIndex] || '从其他推广员复制' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view v-if="!list.length && !loading" class="empty">暂无药品</view>
|
||||
<view v-for="item in list" :key="item.drug_id" class="row">
|
||||
<view class="drug-info">
|
||||
<text class="drug-name">{{ item.drug_name }}</text>
|
||||
<text class="spec">{{ item.drug_spec }}</text>
|
||||
<text class="price">售价 ¥{{ item.price }} / 供货 ¥{{ item.buy_price }}</text>
|
||||
</view>
|
||||
<input
|
||||
class="commission-input"
|
||||
v-model="dirtyMap[item.drug_id]"
|
||||
type="digit"
|
||||
placeholder="元/件"
|
||||
@input="onCommissionInput(item.drug_id, $event)"
|
||||
/>
|
||||
</view>
|
||||
|
||||
<view v-if="salespersonId > 0" class="save-btn" @click="save">保存修改</view>
|
||||
<view v-else class="hint">请先保存推广员基本信息后再配置药品佣金</view>
|
||||
</view>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageLayout from '../components/PageLayout.vue';
|
||||
import { getSalespersonManageContext, resolveManageMode } from '../common/context.js';
|
||||
|
||||
export default {
|
||||
components: { PageLayout },
|
||||
data() {
|
||||
return {
|
||||
ctx: getSalespersonManageContext('clinic'),
|
||||
salespersonId: 0,
|
||||
storeId: 0,
|
||||
keyword: '',
|
||||
list: [],
|
||||
loading: false,
|
||||
dirtyMap: {},
|
||||
copyOptions: [],
|
||||
copyIndex: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
copyLabels() {
|
||||
return ['从其他推广员复制', ...this.copyOptions.map((o) => o.label)];
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.ctx = getSalespersonManageContext(resolveManageMode(options));
|
||||
this.salespersonId = Number(options.salesperson_id || 0);
|
||||
this.storeId = Number(options.store_id || 0);
|
||||
this.reload();
|
||||
if (this.salespersonId > 0) this.loadCopyOptions();
|
||||
},
|
||||
methods: {
|
||||
async reload() {
|
||||
if (!this.salespersonId) return;
|
||||
this.loading = true;
|
||||
try {
|
||||
const params = {
|
||||
salesperson_id: this.salespersonId,
|
||||
keyword: this.keyword,
|
||||
page: 1,
|
||||
pageSize: 100,
|
||||
};
|
||||
if (this.storeId > 0) params.store_id = this.storeId;
|
||||
const wrap = await this.ctx.api.getDrugCommissionList(params);
|
||||
const items = (wrap && wrap.ok && wrap.data && wrap.data.items) ? wrap.data.items : [];
|
||||
this.list = items;
|
||||
const map = { ...this.dirtyMap };
|
||||
items.forEach((row) => {
|
||||
if (map[row.drug_id] === undefined) {
|
||||
map[row.drug_id] = row.commission || '';
|
||||
}
|
||||
});
|
||||
this.dirtyMap = map;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async loadCopyOptions() {
|
||||
const wrap = await this.ctx.api.getList({ page: 1, pageSize: 100 });
|
||||
if (!wrap || !wrap.ok) return;
|
||||
this.copyOptions = (wrap.data.items || [])
|
||||
.filter((sp) => Number(sp.id) !== this.salespersonId)
|
||||
.map((sp) => ({
|
||||
label: sp.nick_name || sp.phone || `推广员#${sp.id}`,
|
||||
value: Number(sp.id),
|
||||
}));
|
||||
},
|
||||
onCommissionInput(drugId, e) {
|
||||
this.$set(this.dirtyMap, drugId, e.detail.value);
|
||||
},
|
||||
async onCopyFrom(e) {
|
||||
const idx = Number(e.detail.value);
|
||||
this.copyIndex = idx;
|
||||
if (idx <= 0 || !this.copyOptions[idx - 1]) return;
|
||||
const fromId = this.copyOptions[idx - 1].value;
|
||||
const wrap = await this.ctx.api.getDrugCommissionCopyFrom(fromId);
|
||||
if (!wrap || !wrap.ok) return;
|
||||
const items = wrap.data.items || [];
|
||||
const map = { ...this.dirtyMap };
|
||||
items.forEach((row) => {
|
||||
map[row.drug_id] = row.commission || '';
|
||||
});
|
||||
this.dirtyMap = map;
|
||||
uni.showToast({ title: '已复制', icon: 'success' });
|
||||
},
|
||||
async save() {
|
||||
const items = Object.keys(this.dirtyMap)
|
||||
.filter((drugId) => this.dirtyMap[drugId] !== '' && this.dirtyMap[drugId] != null)
|
||||
.map((drugId) => ({
|
||||
drug_id: Number(drugId),
|
||||
commission: String(this.dirtyMap[drugId]),
|
||||
}));
|
||||
if (!items.length) {
|
||||
uni.showToast({ title: '无修改', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.showLoading({ title: '保存中' });
|
||||
try {
|
||||
const wrap = await this.ctx.api.saveDrugCommission({
|
||||
salesperson_id: this.salespersonId,
|
||||
items,
|
||||
});
|
||||
if (wrap && wrap.ok) {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' });
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; padding-bottom: 120rpx; }
|
||||
.toolbar { display: flex; gap: 12rpx; margin-bottom: 16rpx; }
|
||||
.search { flex: 1; background: #fff; border-radius: 12rpx; padding: 16rpx 20rpx; font-size: 26rpx; }
|
||||
.search-btn { background: #6ACDBB; color: #fff; padding: 16rpx 24rpx; border-radius: 12rpx; font-size: 26rpx; }
|
||||
.copy-row { margin-bottom: 16rpx; }
|
||||
.copy-picker { background: #fff; padding: 20rpx; border-radius: 12rpx; font-size: 26rpx; color: #6ACDBB; }
|
||||
.row {
|
||||
display: flex; align-items: center; background: #fff; border-radius: 16rpx;
|
||||
padding: 20rpx; margin-bottom: 12rpx;
|
||||
}
|
||||
.drug-info { flex: 1; }
|
||||
.drug-name { display: block; font-size: 28rpx; font-weight: 600; }
|
||||
.spec, .price { font-size: 22rpx; color: #999; display: block; margin-top: 4rpx; }
|
||||
.commission-input {
|
||||
width: 140rpx; text-align: center; background: #f5f5f5; border-radius: 8rpx;
|
||||
padding: 12rpx; font-size: 26rpx;
|
||||
}
|
||||
.save-btn {
|
||||
position: fixed; left: 24rpx; right: 24rpx; bottom: 48rpx;
|
||||
background: linear-gradient(90deg, #00BFA6, #6ACDBB); color: #fff;
|
||||
text-align: center; padding: 24rpx; border-radius: 16rpx; font-size: 30rpx;
|
||||
}
|
||||
.hint { text-align: center; color: #999; padding: 40rpx; }
|
||||
.empty { text-align: center; color: #999; padding: 80rpx 0; }
|
||||
</style>
|
||||
172
subPackages/sub_salesperson_manage/form.vue
Normal file
172
subPackages/sub_salesperson_manage/form.vue
Normal file
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<page-layout :is-platform="ctx.isPlatform" :title="isUpdate ? '编辑推广员' : '新增推广员'" :show-back="true">
|
||||
<view class="page">
|
||||
<store-search-picker v-if="ctx.isPlatform && !isUpdate" v-model="form.store_id" :api="ctx.api" />
|
||||
|
||||
<view class="field">
|
||||
<text class="label">手机号</text>
|
||||
<input class="input" v-model="form.phone" type="number" maxlength="11" placeholder="请输入手机号" :disabled="isUpdate" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">名称</text>
|
||||
<input class="input" v-model="form.nick_name" placeholder="请输入名称" />
|
||||
</view>
|
||||
|
||||
<view class="field">
|
||||
<text class="label">西药分成类型</text>
|
||||
<picker :range="splitTypeLabels" :value="splitTypeIndex" @change="onSplitType">
|
||||
<view class="picker-val">{{ splitTypeLabels[splitTypeIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view v-if="Number(form.split_type) === 1" class="field">
|
||||
<text class="label">分成比例(%)</text>
|
||||
<input class="input" v-model="form.split" type="digit" placeholder="0-100" />
|
||||
</view>
|
||||
<view v-else class="field link" @click="goDrugCommission">
|
||||
<text class="label">药品佣金配置</text>
|
||||
<text class="link-text">去配置元/件分成 ></text>
|
||||
</view>
|
||||
|
||||
<view class="section-title">中药推广分成</view>
|
||||
<view class="field">
|
||||
<text class="label">中药分成(%)</text>
|
||||
<input class="input" v-model="form.tcm_split" type="digit" placeholder="0 表示不分成" />
|
||||
</view>
|
||||
<view v-if="Number(form.tcm_split) > 0" class="field">
|
||||
<text class="label">中药分成基数</text>
|
||||
<picker :range="tcmBaseLabels" :value="tcmBaseIndex" @change="onTcmBase">
|
||||
<view class="picker-val">{{ tcmBaseLabels[tcmBaseIndex] }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view class="submit-btn" @click="submit">保存</view>
|
||||
</view>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageLayout from './components/PageLayout.vue';
|
||||
import StoreSearchPicker from './components/StoreSearchPicker.vue';
|
||||
import { getSalespersonManageContext, resolveManageMode } from './common/context.js';
|
||||
import { SPLIT_TYPE_OPTIONS, TCM_BASE_OPTIONS } from './common/labels.js';
|
||||
|
||||
const ROOT = '/subPackages/sub_salesperson_manage';
|
||||
|
||||
export default {
|
||||
components: { PageLayout, StoreSearchPicker },
|
||||
data() {
|
||||
return {
|
||||
ctx: getSalespersonManageContext('clinic'),
|
||||
isUpdate: false,
|
||||
form: {
|
||||
id: 0,
|
||||
store_id: 0,
|
||||
phone: '',
|
||||
nick_name: '',
|
||||
split_type: 0,
|
||||
split: 0,
|
||||
tcm_split: 0,
|
||||
tcm_base_type: 0,
|
||||
},
|
||||
splitTypeLabels: SPLIT_TYPE_OPTIONS.map((o) => o.label),
|
||||
tcmBaseLabels: TCM_BASE_OPTIONS.map((o) => o.label),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
splitTypeIndex() {
|
||||
return SPLIT_TYPE_OPTIONS.findIndex((o) => o.value === Number(this.form.split_type));
|
||||
},
|
||||
tcmBaseIndex() {
|
||||
return TCM_BASE_OPTIONS.findIndex((o) => o.value === Number(this.form.tcm_base_type));
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.ctx = getSalespersonManageContext(resolveManageMode(options));
|
||||
this.isUpdate = !!options.id;
|
||||
if (options.id) {
|
||||
this.form.id = Number(options.id);
|
||||
this.form.store_id = Number(options.store_id || 0);
|
||||
this.loadDetail(options);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async loadDetail(options) {
|
||||
const wrap = await this.ctx.api.getList({ page: 1, pageSize: 100 });
|
||||
if (!wrap || !wrap.ok) return;
|
||||
const item = (wrap.data.items || []).find((r) => Number(r.id) === Number(options.id));
|
||||
if (!item) return;
|
||||
this.form.phone = item.phone || '';
|
||||
this.form.nick_name = item.nick_name || '';
|
||||
this.form.split_type = Number(item.split_type) || 0;
|
||||
this.form.split = item.split || 0;
|
||||
this.form.tcm_split = item.tcm_split || 0;
|
||||
this.form.tcm_base_type = Number(item.tcm_base_type) || 0;
|
||||
if (!this.form.store_id && item.qr_code) {
|
||||
this.form.store_id = item.qr_code.store_id || 0;
|
||||
}
|
||||
},
|
||||
onSplitType(e) {
|
||||
const idx = Number(e.detail.value);
|
||||
this.form.split_type = SPLIT_TYPE_OPTIONS[idx].value;
|
||||
},
|
||||
onTcmBase(e) {
|
||||
const idx = Number(e.detail.value);
|
||||
this.form.tcm_base_type = TCM_BASE_OPTIONS[idx].value;
|
||||
},
|
||||
goDrugCommission() {
|
||||
const id = this.form.id || '';
|
||||
const storeId = this.form.store_id || '';
|
||||
uni.navigateTo({
|
||||
url: `${ROOT}/drug-commission/index?mode=${this.ctx.mode}&salesperson_id=${id}&store_id=${storeId}`,
|
||||
});
|
||||
},
|
||||
async submit() {
|
||||
if (!this.form.phone || !this.form.nick_name) {
|
||||
uni.showToast({ title: '请填写手机号和名称', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
if (this.ctx.isPlatform && !this.isUpdate && !this.form.store_id) {
|
||||
uni.showToast({ title: '请选择诊所', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const payload = { ...this.form };
|
||||
if (Number(payload.split_type) === 0) payload.split = 0;
|
||||
|
||||
uni.showLoading({ title: '保存中' });
|
||||
try {
|
||||
const wrap = this.isUpdate
|
||||
? await this.ctx.api.update(payload)
|
||||
: await this.ctx.api.create(payload);
|
||||
if (wrap && wrap.ok) {
|
||||
if (!this.isUpdate && wrap.data && wrap.data.admin_account_created === false) {
|
||||
uni.showToast({ title: '已创建,请手动开通后台', icon: 'none', duration: 2500 });
|
||||
} else {
|
||||
uni.showToast({ title: '保存成功', icon: 'success' });
|
||||
}
|
||||
setTimeout(() => uni.navigateBack(), 500);
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.field {
|
||||
display: flex; align-items: center; background: #fff; border-radius: 16rpx;
|
||||
padding: 24rpx; margin-bottom: 16rpx;
|
||||
}
|
||||
.field.link { justify-content: space-between; }
|
||||
.label { width: 200rpx; font-size: 28rpx; color: #333; flex-shrink: 0; }
|
||||
.input { flex: 1; font-size: 28rpx; text-align: right; }
|
||||
.picker-val { flex: 1; font-size: 28rpx; text-align: right; color: #333; }
|
||||
.link-text { font-size: 26rpx; color: #6ACDBB; }
|
||||
.section-title { font-size: 26rpx; color: #999; margin: 24rpx 0 12rpx 8rpx; }
|
||||
.submit-btn {
|
||||
margin-top: 40rpx; background: linear-gradient(90deg, #00BFA6, #6ACDBB);
|
||||
color: #fff; text-align: center; padding: 24rpx; border-radius: 16rpx; font-size: 30rpx;
|
||||
}
|
||||
</style>
|
||||
226
subPackages/sub_salesperson_manage/index.vue
Normal file
226
subPackages/sub_salesperson_manage/index.vue
Normal file
@@ -0,0 +1,226 @@
|
||||
<template>
|
||||
<page-layout :is-platform="ctx.isPlatform" title="推广员管理" :show-back="true">
|
||||
<view class="page">
|
||||
<view v-if="ctx.isPlatform" class="filter-panel">
|
||||
<input class="filter-input" v-model="filters.store_keyword" placeholder="诊所名称" />
|
||||
<input class="filter-input" v-model="filters.nick_name" placeholder="推广员名称" />
|
||||
<input class="filter-input" v-model="filters.phone" placeholder="手机号" />
|
||||
<view class="filter-btn" @click="reload">查询</view>
|
||||
</view>
|
||||
|
||||
<view v-if="!ctx.isPlatform" class="see-price-row">
|
||||
<text>推广员可见价格</text>
|
||||
<switch :checked="seePrice === 1" @change="onToggleSeePrice" color="#6ACDBB" />
|
||||
</view>
|
||||
|
||||
<view class="add-bar">
|
||||
<view class="add-btn" @click="goCreate">+ 新增推广员</view>
|
||||
</view>
|
||||
|
||||
<view v-if="loading" class="loading-tip">加载中...</view>
|
||||
<view v-else-if="!list.length" class="empty">暂无推广员</view>
|
||||
|
||||
<view v-for="item in list" :key="item.id" class="card">
|
||||
<view class="card-head">
|
||||
<view>
|
||||
<text class="name">{{ item.nick_name || '未知' }}</text>
|
||||
<text v-if="item.store_name" class="store">{{ item.store_name }}</text>
|
||||
</view>
|
||||
<view class="tags">
|
||||
<text class="tag">{{ splitLabel(item.split_type) }}</text>
|
||||
<text v-if="Number(item.tcm_split) > 0" class="tag tcm">中药 {{ item.tcm_split }}%</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card-body">
|
||||
<text class="phone">{{ item.phone }}</text>
|
||||
<view class="stat-row">
|
||||
<text>累计获客</text>
|
||||
<text class="num">{{ (item.count && item.count.user_number) || 0 }}</text>
|
||||
</view>
|
||||
<view class="stat-row">
|
||||
<text>待结算</text>
|
||||
<text class="num pending">¥{{ (item.count && item.count.pending_amount) || '0.00' }}</text>
|
||||
</view>
|
||||
<view class="stat-row">
|
||||
<text>后台账号</text>
|
||||
<text :class="item.has_admin_account ? 'ok' : 'warn'">{{ item.has_admin_account ? '已开通' : '未开通' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="actions">
|
||||
<view class="action-btn" @click="goEdit(item)">编辑</view>
|
||||
<view class="action-btn primary" @click="goCommission(item)">分成与结算</view>
|
||||
<view v-if="item.qr_code && item.qr_code.qr_code" class="action-btn" @click="openQr(item)">二维码</view>
|
||||
<view class="action-btn" @click="goLeads(item)">获客</view>
|
||||
<view
|
||||
v-if="!item.has_admin_account"
|
||||
class="action-btn warn"
|
||||
@click="openAdminAccount(item)"
|
||||
>开通后台</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageLayout from './components/PageLayout.vue';
|
||||
import { getSalespersonManageContext, resolveManageMode } from './common/context.js';
|
||||
import { splitTypeLabel } from './common/labels.js';
|
||||
import { openQrCodePreview } from '@/utils/qrCodePreview.js';
|
||||
|
||||
const ROOT = '/subPackages/sub_salesperson_manage';
|
||||
|
||||
export default {
|
||||
components: { PageLayout },
|
||||
data() {
|
||||
return {
|
||||
ctx: getSalespersonManageContext('clinic'),
|
||||
filters: { store_keyword: '', nick_name: '', phone: '' },
|
||||
presetStoreId: 0,
|
||||
list: [],
|
||||
loading: false,
|
||||
seePrice: 0,
|
||||
openingId: 0,
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.ctx = getSalespersonManageContext(resolveManageMode(options));
|
||||
this.presetStoreId = Number(options.store_id || 0);
|
||||
if (this.presetStoreId > 0) {
|
||||
this.filters.store_keyword = '';
|
||||
}
|
||||
},
|
||||
onShow() {
|
||||
this.reload();
|
||||
if (!this.ctx.isPlatform) this.loadSeePrice();
|
||||
},
|
||||
methods: {
|
||||
splitLabel: splitTypeLabel,
|
||||
async reload() {
|
||||
this.loading = true;
|
||||
try {
|
||||
const params = {
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
...this.filters,
|
||||
};
|
||||
if (this.presetStoreId > 0) params.store_id = this.presetStoreId;
|
||||
const wrap = await this.ctx.api.getList(params);
|
||||
this.list = (wrap && wrap.ok && wrap.data && wrap.data.items) ? wrap.data.items : [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
async loadSeePrice() {
|
||||
const wrap = await this.ctx.api.getSeePriceDetail();
|
||||
if (wrap && wrap.ok && wrap.data) {
|
||||
this.seePrice = Number(wrap.data.see_price) || 0;
|
||||
}
|
||||
},
|
||||
async onToggleSeePrice(e) {
|
||||
const wrap = await this.ctx.api.toggleSeePrice();
|
||||
if (wrap && wrap.ok && wrap.data) {
|
||||
this.seePrice = Number(wrap.data.see_price) || 0;
|
||||
uni.showToast({ title: this.seePrice === 1 ? '已开启可见价格' : '已关闭可见价格', icon: 'none' });
|
||||
}
|
||||
},
|
||||
goCreate() {
|
||||
uni.navigateTo({ url: `${ROOT}/form?mode=${this.ctx.mode}` });
|
||||
},
|
||||
goEdit(item) {
|
||||
uni.navigateTo({
|
||||
url: `${ROOT}/form?mode=${this.ctx.mode}&id=${item.id}&store_id=${(item.qr_code && item.qr_code.store_id) || ''}`,
|
||||
});
|
||||
},
|
||||
goCommission(item) {
|
||||
const storeId = (item.qr_code && item.qr_code.store_id) || '';
|
||||
uni.navigateTo({
|
||||
url: `${ROOT}/commission/index?mode=${this.ctx.mode}&salesperson_id=${item.id}&nick_name=${encodeURIComponent(item.nick_name || '')}&store_id=${storeId}`,
|
||||
});
|
||||
},
|
||||
goLeads(item) {
|
||||
uni.navigateTo({
|
||||
url: `${ROOT}/leads/index?mode=${this.ctx.mode}&salesperson_id=${item.id}&nick_name=${encodeURIComponent(item.nick_name || '')}`,
|
||||
});
|
||||
},
|
||||
openQr(item) {
|
||||
openQrCodePreview({
|
||||
type: 'salesperson',
|
||||
title: item.nick_name || '推广员二维码',
|
||||
payload: {
|
||||
nick_name: item.nick_name || '',
|
||||
qr_code: {
|
||||
qr_code: (item.qr_code && item.qr_code.qr_code) || '',
|
||||
store: { name: item.store_name || '' },
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
async openAdminAccount(item) {
|
||||
if (this.openingId) return;
|
||||
this.openingId = item.id;
|
||||
try {
|
||||
const wrap = await this.ctx.api.openAdminAccount({
|
||||
salesperson_id: item.id,
|
||||
store_id: item.qr_code && item.qr_code.store_id,
|
||||
});
|
||||
if (wrap && wrap.ok) {
|
||||
uni.showModal({
|
||||
title: '开通成功',
|
||||
content: '默认密码:Xk123456@',
|
||||
showCancel: false,
|
||||
});
|
||||
this.reload();
|
||||
}
|
||||
} finally {
|
||||
this.openingId = 0;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; padding-bottom: 48rpx; }
|
||||
.filter-panel { display: flex; flex-wrap: wrap; gap: 12rpx; margin-bottom: 20rpx; }
|
||||
.filter-input {
|
||||
flex: 1; min-width: 200rpx; background: #fff; border-radius: 12rpx;
|
||||
padding: 16rpx 20rpx; font-size: 26rpx;
|
||||
}
|
||||
.filter-btn {
|
||||
background: #6ACDBB; color: #fff; padding: 16rpx 32rpx; border-radius: 12rpx; font-size: 26rpx;
|
||||
}
|
||||
.see-price-row {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 20rpx; font-size: 28rpx;
|
||||
}
|
||||
.add-bar { margin-bottom: 20rpx; }
|
||||
.add-btn {
|
||||
background: linear-gradient(90deg, #00BFA6, #6ACDBB); color: #fff;
|
||||
text-align: center; padding: 20rpx; border-radius: 16rpx; font-size: 28rpx;
|
||||
}
|
||||
.loading-tip, .empty { text-align: center; color: #999; padding: 80rpx 0; }
|
||||
.card {
|
||||
background: #fff; border-radius: 20rpx; padding: 28rpx; margin-bottom: 20rpx;
|
||||
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.04);
|
||||
}
|
||||
.card-head { display: flex; justify-content: space-between; margin-bottom: 16rpx; }
|
||||
.name { display: block; font-size: 32rpx; font-weight: 600; }
|
||||
.store { font-size: 24rpx; color: #999; }
|
||||
.tags { display: flex; flex-direction: column; gap: 8rpx; align-items: flex-end; }
|
||||
.tag { font-size: 22rpx; background: #EFF6FF; color: #3B82F6; padding: 4rpx 12rpx; border-radius: 8rpx; }
|
||||
.tag.tcm { background: #F0FDF4; color: #22C55E; }
|
||||
.phone { font-size: 26rpx; color: #666; display: block; margin-bottom: 12rpx; }
|
||||
.stat-row { display: flex; justify-content: space-between; font-size: 26rpx; margin-bottom: 8rpx; }
|
||||
.num { font-weight: 600; }
|
||||
.pending { color: #F97316; }
|
||||
.ok { color: #22C55E; }
|
||||
.warn { color: #EF4444; }
|
||||
.actions { display: flex; flex-wrap: wrap; gap: 12rpx; margin-top: 20rpx; }
|
||||
.action-btn {
|
||||
font-size: 24rpx; padding: 10rpx 20rpx; border-radius: 10rpx;
|
||||
background: #f5f5f5; color: #333;
|
||||
}
|
||||
.action-btn.primary { background: #6ACDBB; color: #fff; }
|
||||
.action-btn.warn { background: #FEF3C7; color: #D97706; }
|
||||
</style>
|
||||
71
subPackages/sub_salesperson_manage/leads/index.vue
Normal file
71
subPackages/sub_salesperson_manage/leads/index.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<page-layout :is-platform="ctx.isPlatform" :title="pageTitle" :show-back="true">
|
||||
<view class="page">
|
||||
<view v-if="!list.length && !loading" class="empty">暂无获客记录</view>
|
||||
<view v-for="item in list" :key="item.id" class="row">
|
||||
<view class="user">
|
||||
<text class="name">{{ (item.user && item.user.nickname) || '用户' }}</text>
|
||||
<text class="mobile">{{ (item.user && item.user.mobile) || '' }}</text>
|
||||
</view>
|
||||
<text class="time">{{ item.created_at_text || '' }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageLayout from '../components/PageLayout.vue';
|
||||
import { getSalespersonManageContext, resolveManageMode } from '../common/context.js';
|
||||
|
||||
export default {
|
||||
components: { PageLayout },
|
||||
data() {
|
||||
return {
|
||||
ctx: getSalespersonManageContext('clinic'),
|
||||
salespersonId: 0,
|
||||
nickName: '',
|
||||
list: [],
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
pageTitle() {
|
||||
return this.nickName ? `获客 - ${this.nickName}` : '获客记录';
|
||||
},
|
||||
},
|
||||
onLoad(options) {
|
||||
this.ctx = getSalespersonManageContext(resolveManageMode(options));
|
||||
this.salespersonId = Number(options.salesperson_id || 0);
|
||||
this.nickName = decodeURIComponent(options.nick_name || '');
|
||||
this.loadList();
|
||||
},
|
||||
methods: {
|
||||
async loadList() {
|
||||
if (!this.salespersonId) return;
|
||||
this.loading = true;
|
||||
try {
|
||||
const wrap = await this.ctx.api.getUserBindList({
|
||||
salesperson_id: this.salespersonId,
|
||||
page: 1,
|
||||
pageSize: 50,
|
||||
});
|
||||
this.list = (wrap && wrap.ok && wrap.data && wrap.data.items) ? wrap.data.items : [];
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.empty { text-align: center; color: #999; padding: 80rpx 0; }
|
||||
.row {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
background: #fff; border-radius: 16rpx; padding: 24rpx; margin-bottom: 12rpx;
|
||||
}
|
||||
.name { display: block; font-size: 28rpx; font-weight: 600; }
|
||||
.mobile { font-size: 24rpx; color: #999; }
|
||||
.time { font-size: 24rpx; color: #bbb; }
|
||||
</style>
|
||||
122
subPackages/sub_salesperson_manage/settlement/confirm.vue
Normal file
122
subPackages/sub_salesperson_manage/settlement/confirm.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<page-layout :is-platform="ctx.isPlatform" title="确认结算" :show-back="true">
|
||||
<view class="page">
|
||||
<view class="preview-card">
|
||||
<text>共 {{ recordCount }} 条明细</text>
|
||||
<text class="amount">结算金额 ¥{{ amount }}</text>
|
||||
</view>
|
||||
|
||||
<proof-image-uploader v-model="proofImages" />
|
||||
|
||||
<view class="field">
|
||||
<text class="label">备注</text>
|
||||
<input class="input" v-model="remark" placeholder="选填" />
|
||||
</view>
|
||||
|
||||
<view class="submit-btn" @click="confirm">确认结算</view>
|
||||
</view>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageLayout from '../components/PageLayout.vue';
|
||||
import ProofImageUploader from '../components/ProofImageUploader.vue';
|
||||
import { getSalespersonManageContext, resolveManageMode } from '../common/context.js';
|
||||
|
||||
export default {
|
||||
components: { PageLayout, ProofImageUploader },
|
||||
data() {
|
||||
return {
|
||||
ctx: getSalespersonManageContext('clinic'),
|
||||
salespersonId: 0,
|
||||
storeId: 0,
|
||||
settlementType: 1,
|
||||
periodStart: 0,
|
||||
periodEnd: 0,
|
||||
orderIds: [],
|
||||
amount: '0.00',
|
||||
recordCount: 0,
|
||||
proofImages: [],
|
||||
remark: '',
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.ctx = getSalespersonManageContext(resolveManageMode(options));
|
||||
this.salespersonId = Number(options.salesperson_id || 0);
|
||||
this.storeId = Number(options.store_id || 0);
|
||||
this.settlementType = Number(options.settlement_type || 1);
|
||||
this.periodStart = Number(options.period_start || 0);
|
||||
this.periodEnd = Number(options.period_end || 0);
|
||||
this.amount = decodeURIComponent(options.amount || '0.00');
|
||||
this.recordCount = Number(options.record_count || 0);
|
||||
if (options.order_ids) {
|
||||
this.orderIds = String(options.order_ids).split(',').map(Number).filter(Boolean);
|
||||
}
|
||||
if (!this.recordCount || !this.amount || this.amount === '0.00') {
|
||||
this.loadPreview();
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
buildParams() {
|
||||
const params = {
|
||||
salesperson_id: this.salespersonId,
|
||||
settlement_type: this.settlementType,
|
||||
};
|
||||
if (this.storeId > 0) params.store_id = this.storeId;
|
||||
if (this.settlementType === 1) {
|
||||
params.period_start = this.periodStart;
|
||||
params.period_end = this.periodEnd;
|
||||
} else {
|
||||
params.order_ids = this.orderIds.join(',');
|
||||
}
|
||||
return params;
|
||||
},
|
||||
async loadPreview() {
|
||||
const wrap = await this.ctx.api.getSettlementPreview(this.buildParams());
|
||||
if (wrap && wrap.ok && wrap.data) {
|
||||
this.amount = wrap.data.amount || '0.00';
|
||||
this.recordCount = wrap.data.record_count || 0;
|
||||
}
|
||||
},
|
||||
async confirm() {
|
||||
if (!this.proofImages.length) {
|
||||
uni.showToast({ title: '请上传至少1张结算凭证', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
uni.showLoading({ title: '结算中' });
|
||||
try {
|
||||
const wrap = await this.ctx.api.confirmSettlement({
|
||||
...this.buildParams(),
|
||||
proof_images: this.proofImages,
|
||||
remark: this.remark.trim(),
|
||||
});
|
||||
if (wrap && wrap.ok) {
|
||||
uni.showToast({ title: '结算成功', icon: 'success' });
|
||||
setTimeout(() => uni.navigateBack({ delta: 2 }), 500);
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.preview-card {
|
||||
background: #FFF7ED; border-radius: 16rpx; padding: 28rpx; margin-bottom: 24rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
.amount { display: block; font-size: 36rpx; font-weight: 700; color: #F97316; margin-top: 12rpx; }
|
||||
.field {
|
||||
display: flex; align-items: center; background: #fff; border-radius: 16rpx;
|
||||
padding: 24rpx; margin-top: 24rpx;
|
||||
}
|
||||
.label { width: 100rpx; font-size: 28rpx; }
|
||||
.input { flex: 1; font-size: 28rpx; text-align: right; }
|
||||
.submit-btn {
|
||||
margin-top: 48rpx; background: linear-gradient(90deg, #00BFA6, #6ACDBB); color: #fff;
|
||||
text-align: center; padding: 24rpx; border-radius: 16rpx; font-size: 30rpx;
|
||||
}
|
||||
</style>
|
||||
97
subPackages/sub_salesperson_manage/settlement/detail.vue
Normal file
97
subPackages/sub_salesperson_manage/settlement/detail.vue
Normal file
@@ -0,0 +1,97 @@
|
||||
<template>
|
||||
<page-layout :is-platform="ctx.isPlatform" title="结算详情" :show-back="true">
|
||||
<view class="page" v-if="detail">
|
||||
<view class="card">
|
||||
<text class="no">{{ detail.settlement_no }}</text>
|
||||
<view class="row"><text>结算方式</text><text>{{ detail.settlement_type_text }}</text></view>
|
||||
<view class="row"><text>结算金额</text><text class="amount">¥{{ detail.amount }}</text></view>
|
||||
<view class="row"><text>明细数</text><text>{{ detail.record_count }}</text></view>
|
||||
<view class="row"><text>时间</text><text>{{ detail.created_at_text }}</text></view>
|
||||
<view v-if="detail.remark" class="row"><text>备注</text><text>{{ detail.remark }}</text></view>
|
||||
</view>
|
||||
|
||||
<view v-if="proofImages.length" class="proof-section">
|
||||
<text class="section-title">结算凭证</text>
|
||||
<view class="proof-list">
|
||||
<image
|
||||
v-for="(url, idx) in proofImages"
|
||||
:key="url"
|
||||
class="proof-img"
|
||||
:src="url"
|
||||
mode="aspectFill"
|
||||
@click="previewProof(idx)"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view v-if="lines.length" class="lines-section">
|
||||
<text class="section-title">明细</text>
|
||||
<view v-for="(line, idx) in lines" :key="idx" class="line-row">
|
||||
<text class="line-order">{{ line.order_no }}</text>
|
||||
<text class="line-drug">{{ line.drug_info }}</text>
|
||||
<text class="line-amount">¥{{ line.commission_amount }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageLayout from '../components/PageLayout.vue';
|
||||
import { getSalespersonManageContext, resolveManageMode } from '../common/context.js';
|
||||
|
||||
export default {
|
||||
components: { PageLayout },
|
||||
data() {
|
||||
return {
|
||||
ctx: getSalespersonManageContext('clinic'),
|
||||
detail: null,
|
||||
lines: [],
|
||||
proofImages: [],
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.ctx = getSalespersonManageContext(resolveManageMode(options));
|
||||
this.loadDetail(Number(options.id || 0), Number(options.store_id || 0));
|
||||
},
|
||||
methods: {
|
||||
async loadDetail(id, storeId) {
|
||||
if (!id) return;
|
||||
const params = { id };
|
||||
if (storeId > 0) params.store_id = storeId;
|
||||
const wrap = await this.ctx.api.getSettlementDetail(params);
|
||||
if (!wrap || !wrap.ok) return;
|
||||
this.detail = wrap.data;
|
||||
this.lines = (wrap.data && wrap.data.lines) ? wrap.data.lines : (wrap.data.records || []);
|
||||
const proofs = wrap.data && wrap.data.proof_images;
|
||||
if (Array.isArray(proofs)) {
|
||||
this.proofImages = proofs;
|
||||
} else if (typeof proofs === 'string' && proofs) {
|
||||
try { this.proofImages = JSON.parse(proofs); } catch (e) { this.proofImages = [proofs]; }
|
||||
} else {
|
||||
this.proofImages = [];
|
||||
}
|
||||
},
|
||||
previewProof(idx) {
|
||||
uni.previewImage({ urls: this.proofImages, current: this.proofImages[idx] });
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.card { background: #fff; border-radius: 20rpx; padding: 28rpx; margin-bottom: 24rpx; }
|
||||
.no { display: block; font-size: 32rpx; font-weight: 600; margin-bottom: 20rpx; }
|
||||
.row { display: flex; justify-content: space-between; font-size: 26rpx; margin-bottom: 12rpx; color: #666; }
|
||||
.amount { color: #F97316; font-weight: 600; }
|
||||
.section-title { font-size: 28rpx; font-weight: 600; margin-bottom: 16rpx; display: block; }
|
||||
.proof-list { display: flex; flex-wrap: wrap; gap: 16rpx; margin-bottom: 24rpx; }
|
||||
.proof-img { width: 200rpx; height: 200rpx; border-radius: 12rpx; }
|
||||
.line-row {
|
||||
background: #fff; border-radius: 12rpx; padding: 20rpx; margin-bottom: 12rpx;
|
||||
}
|
||||
.line-order { display: block; font-size: 26rpx; font-weight: 600; }
|
||||
.line-drug { font-size: 24rpx; color: #666; display: block; margin: 8rpx 0; }
|
||||
.line-amount { font-size: 26rpx; color: #F97316; }
|
||||
</style>
|
||||
114
subPackages/sub_salesperson_manage/settlement/preview.vue
Normal file
114
subPackages/sub_salesperson_manage/settlement/preview.vue
Normal file
@@ -0,0 +1,114 @@
|
||||
<template>
|
||||
<page-layout :is-platform="ctx.isPlatform" title="按时间段结算" :show-back="true">
|
||||
<view class="page">
|
||||
<view class="field">
|
||||
<text class="label">开始日期</text>
|
||||
<picker mode="date" :value="dateStart" @change="onStart">
|
||||
<view class="picker-val">{{ dateStart || '请选择' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
<view class="field">
|
||||
<text class="label">结束日期</text>
|
||||
<picker mode="date" :value="dateEnd" @change="onEnd">
|
||||
<view class="picker-val">{{ dateEnd || '请选择' }}</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view v-if="preview" class="preview-card">
|
||||
<text>共 {{ preview.record_count }} 条明细</text>
|
||||
<text v-if="preview.order_count">涉及 {{ preview.order_count }} 个订单</text>
|
||||
<text class="amount">结算金额 ¥{{ preview.amount }}</text>
|
||||
</view>
|
||||
|
||||
<view class="submit-btn" @click="goConfirm">下一步:上传凭证</view>
|
||||
</view>
|
||||
</page-layout>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import PageLayout from '../components/PageLayout.vue';
|
||||
import { getSalespersonManageContext, resolveManageMode } from '../common/context.js';
|
||||
|
||||
const ROOT = '/subPackages/sub_salesperson_manage';
|
||||
|
||||
export default {
|
||||
components: { PageLayout },
|
||||
data() {
|
||||
return {
|
||||
ctx: getSalespersonManageContext('clinic'),
|
||||
salespersonId: 0,
|
||||
storeId: 0,
|
||||
dateStart: '',
|
||||
dateEnd: '',
|
||||
preview: null,
|
||||
};
|
||||
},
|
||||
onLoad(options) {
|
||||
this.ctx = getSalespersonManageContext(resolveManageMode(options));
|
||||
this.salespersonId = Number(options.salesperson_id || 0);
|
||||
this.storeId = Number(options.store_id || 0);
|
||||
},
|
||||
methods: {
|
||||
onStart(e) { this.dateStart = e.detail.value; },
|
||||
onEnd(e) { this.dateEnd = e.detail.value; },
|
||||
toUnixStart(dateStr) {
|
||||
return Math.floor(new Date(`${dateStr} 00:00:00`).getTime() / 1000);
|
||||
},
|
||||
toUnixEnd(dateStr) {
|
||||
return Math.floor(new Date(`${dateStr} 23:59:59`).getTime() / 1000);
|
||||
},
|
||||
async goConfirm() {
|
||||
if (!this.dateStart || !this.dateEnd) {
|
||||
uni.showToast({ title: '请选择时间段', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
const params = {
|
||||
salesperson_id: this.salespersonId,
|
||||
settlement_type: 1,
|
||||
period_start: this.toUnixStart(this.dateStart),
|
||||
period_end: this.toUnixEnd(this.dateEnd),
|
||||
};
|
||||
if (this.storeId > 0) params.store_id = this.storeId;
|
||||
|
||||
uni.showLoading({ title: '预览中' });
|
||||
try {
|
||||
const wrap = await this.ctx.api.getSettlementPreview(params);
|
||||
if (!wrap || !wrap.ok) return;
|
||||
this.preview = wrap.data;
|
||||
const q = [
|
||||
`mode=${this.ctx.mode}`,
|
||||
`salesperson_id=${this.salespersonId}`,
|
||||
`store_id=${this.storeId}`,
|
||||
'settlement_type=1',
|
||||
`period_start=${params.period_start}`,
|
||||
`period_end=${params.period_end}`,
|
||||
`amount=${encodeURIComponent(this.preview.amount || '')}`,
|
||||
`record_count=${this.preview.record_count || 0}`,
|
||||
].join('&');
|
||||
uni.navigateTo({ url: `${ROOT}/settlement/confirm?${q}` });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page { padding: 24rpx; }
|
||||
.field {
|
||||
display: flex; align-items: center; background: #fff; border-radius: 16rpx;
|
||||
padding: 24rpx; margin-bottom: 16rpx;
|
||||
}
|
||||
.label { width: 160rpx; font-size: 28rpx; }
|
||||
.picker-val { flex: 1; text-align: right; font-size: 28rpx; }
|
||||
.preview-card {
|
||||
background: #FFF7ED; border-radius: 16rpx; padding: 28rpx; margin: 24rpx 0;
|
||||
display: flex; flex-direction: column; gap: 8rpx; font-size: 26rpx;
|
||||
}
|
||||
.amount { font-size: 36rpx; font-weight: 700; color: #F97316; margin-top: 8rpx; }
|
||||
.submit-btn {
|
||||
background: linear-gradient(90deg, #00BFA6, #6ACDBB); color: #fff;
|
||||
text-align: center; padding: 24rpx; border-radius: 16rpx; font-size: 30rpx; margin-top: 40rpx;
|
||||
}
|
||||
</style>
|
||||
@@ -71,7 +71,12 @@
|
||||
<view class="selected-indicator" v-if="isInSelected(item)"></view>
|
||||
<d-text :text="getDrugName(item)" className="fs-32 font-bold color-title"></d-text>
|
||||
</view>
|
||||
<d-text :text="`¥${parseFloat(item.price || 0).toFixed(2)}/${getUnitName(item)}`" className="fs-32 font-bold color-price"></d-text>
|
||||
<d-text
|
||||
v-if="showDrugPrice"
|
||||
:text="`¥${parseFloat(item.price || 0).toFixed(2)}/${getUnitName(item)}`"
|
||||
className="fs-32 font-bold color-price"
|
||||
></d-text>
|
||||
<d-text v-else text="价格不可见" className="fs-26 color-sub"></d-text>
|
||||
</view>
|
||||
<view class="drug-info">
|
||||
<view class="specification-row flex-row flex-jus-sp flex-ali-center m-b-24">
|
||||
@@ -174,6 +179,7 @@
|
||||
<script>
|
||||
// 保留所有原有逻辑代码与注释
|
||||
import { getProductListDoctorReception, getChineseMedicineQuickGramsApi } from '@/api/reception.js';
|
||||
import { getClinicSalespersonChineseDrugList, getClinicSalespersonChineseMedicineQuickGrams } from '@/api/clinicSalesperson.js';
|
||||
import { withWxKey } from '@/utils/wxListKey.js';
|
||||
|
||||
export default {
|
||||
@@ -207,6 +213,16 @@ export default {
|
||||
seeRate: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
/** 推广员传方模式:使用推广员中药列表接口 */
|
||||
salespersonTransferMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/** 是否展示药品单价(传方 see_price=0 时为 false) */
|
||||
showDrugPrice: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -254,16 +270,22 @@ export default {
|
||||
*/
|
||||
async fetchQuickGrams() {
|
||||
try {
|
||||
const res = await getChineseMedicineQuickGramsApi();
|
||||
if (res && (res.code === 0 || res.errcode === 0)) {
|
||||
const data = res.data || res.result || {};
|
||||
const list = Array.isArray(data.grams) ? data.grams : (Array.isArray(data) ? data : []);
|
||||
this.commonQuantities = list
|
||||
.map((v) => parseFloat(v))
|
||||
.filter((n) => !Number.isNaN(n) && n > 0);
|
||||
let data = {};
|
||||
if (this.salespersonTransferMode) {
|
||||
const wrap = await getClinicSalespersonChineseMedicineQuickGrams();
|
||||
if (wrap && wrap.ok) {
|
||||
data = wrap.data || {};
|
||||
}
|
||||
} else {
|
||||
this.commonQuantities = [];
|
||||
const res = await getChineseMedicineQuickGramsApi();
|
||||
if (res && (res.code === 0 || res.errcode === 0)) {
|
||||
data = res.data || res.result || {};
|
||||
}
|
||||
}
|
||||
const list = Array.isArray(data.grams) ? data.grams : (Array.isArray(data) ? data : []);
|
||||
this.commonQuantities = list
|
||||
.map((v) => parseFloat(v))
|
||||
.filter((n) => !Number.isNaN(n) && n > 0);
|
||||
} catch (e) {
|
||||
console.error('获取中药快捷克数失败:', e);
|
||||
this.commonQuantities = [];
|
||||
@@ -331,53 +353,53 @@ export default {
|
||||
}
|
||||
this.loading = true;
|
||||
try {
|
||||
const storeId = uni.getStorageSync('store_id') || 11001;
|
||||
const params = {
|
||||
store_id: storeId,
|
||||
type: 1, // 中药
|
||||
name: name,
|
||||
page: this.page,
|
||||
page_size: this.pageSize
|
||||
};
|
||||
if (this.registerId) {
|
||||
params.register_id = this.registerId;
|
||||
}
|
||||
const res = await getProductListDoctorReception(params);
|
||||
|
||||
if (res && (res.code === 0 || res.errcode === 0)) {
|
||||
const list = res.data || res.result || [];
|
||||
this.hasMore = list.length >= this.pageSize;
|
||||
// 合并已选中的药品数量,使用 $set 确保响应式
|
||||
// 匹配优先级:selectedDrugs.index_id === drug.id,再 fallback 到 entity id
|
||||
const merged = list.map(drug => {
|
||||
const entityId = (drug.drug && drug.drug.id) || drug.id;
|
||||
const selected = this.selectedDrugs.find(item => {
|
||||
const matchByIndexId = (item.index_id !== undefined && item.index_id !== null) && item.index_id === drug.id;
|
||||
const matchByEntityId = (item.id !== undefined && item.id !== null) && item.id === entityId;
|
||||
const matchByDrugId = (item.drug_id !== undefined && item.drug_id !== null) && item.drug_id === entityId;
|
||||
return matchByIndexId || matchByEntityId || matchByDrugId;
|
||||
});
|
||||
// 直接使用原对象,使用 $set 添加响应式属性
|
||||
this.$set(drug, '_quantity', selected ? selected.number : 0);
|
||||
return drug;
|
||||
});
|
||||
if (this.page > 1) {
|
||||
this.drugList = this.drugList.concat(merged);
|
||||
} else {
|
||||
this.drugList = merged;
|
||||
}
|
||||
// 确保所有药品都有 _quantity 属性
|
||||
this.drugList.forEach(drug => {
|
||||
if (typeof drug._quantity === 'undefined') {
|
||||
this.$set(drug, '_quantity', 0);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (this.page === 1) {
|
||||
this.drugList = [];
|
||||
let list = [];
|
||||
if (this.salespersonTransferMode) {
|
||||
const wrap = await getClinicSalespersonChineseDrugList({ name });
|
||||
if (wrap && wrap.ok) {
|
||||
list = Array.isArray(wrap.data) ? wrap.data : [];
|
||||
}
|
||||
this.hasMore = false;
|
||||
} else {
|
||||
const storeId = uni.getStorageSync('store_id') || 11001;
|
||||
const params = {
|
||||
store_id: storeId,
|
||||
type: 1,
|
||||
name: name,
|
||||
page: this.page,
|
||||
page_size: this.pageSize
|
||||
};
|
||||
if (this.registerId) {
|
||||
params.register_id = this.registerId;
|
||||
}
|
||||
const res = await getProductListDoctorReception(params);
|
||||
if (res && (res.code === 0 || res.errcode === 0)) {
|
||||
list = res.data || res.result || [];
|
||||
this.hasMore = list.length >= this.pageSize;
|
||||
}
|
||||
}
|
||||
|
||||
const merged = list.map(drug => {
|
||||
const entityId = (drug.drug && drug.drug.id) || drug.id;
|
||||
const selected = this.selectedDrugs.find(item => {
|
||||
const matchByIndexId = (item.index_id !== undefined && item.index_id !== null) && item.index_id === drug.id;
|
||||
const matchByEntityId = (item.id !== undefined && item.id !== null) && item.id === entityId;
|
||||
const matchByDrugId = (item.drug_id !== undefined && item.drug_id !== null) && item.drug_id === entityId;
|
||||
return matchByIndexId || matchByEntityId || matchByDrugId;
|
||||
});
|
||||
this.$set(drug, '_quantity', selected ? selected.number : 0);
|
||||
return drug;
|
||||
});
|
||||
if (this.page > 1) {
|
||||
this.drugList = this.drugList.concat(merged);
|
||||
} else {
|
||||
this.drugList = merged;
|
||||
}
|
||||
this.drugList.forEach(drug => {
|
||||
if (typeof drug._quantity === 'undefined') {
|
||||
this.$set(drug, '_quantity', 0);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取药品列表失败:', error);
|
||||
this.$toast('获取药品列表失败');
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
<script>
|
||||
// 保留所有原有逻辑代码与注释
|
||||
import { getDiseaseList, getMyDiseaseList, addMyDisease, deleteMyDisease } from '@/api/reception.js';
|
||||
import { getClinicSalespersonDiseaseList } from '@/api/clinicSalesperson.js';
|
||||
|
||||
export default {
|
||||
name: 'DiagnosisModal',
|
||||
@@ -156,6 +157,10 @@ export default {
|
||||
selectedDiagnosis: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
salespersonTransferMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -189,8 +194,9 @@ export default {
|
||||
this.allDiagnosisList = [];
|
||||
this.currentPage = 1;
|
||||
this.hasMore = false;
|
||||
// 只加载常用诊断列表,不自动加载搜索列表
|
||||
this.loadDoctorMyDiseaseList();
|
||||
if (!this.salespersonTransferMode) {
|
||||
this.loadDoctorMyDiseaseList();
|
||||
}
|
||||
}
|
||||
},
|
||||
show(newVal) {
|
||||
@@ -209,7 +215,17 @@ export default {
|
||||
*/
|
||||
async loadDiagnosisList(searchKey = '', page = 1, append = false) {
|
||||
try {
|
||||
const res = await getDiseaseList(searchKey, page, this.pageSize);
|
||||
let res;
|
||||
if (this.salespersonTransferMode) {
|
||||
const wrap = await getClinicSalespersonDiseaseList({
|
||||
name: searchKey,
|
||||
page,
|
||||
page_size: this.pageSize,
|
||||
});
|
||||
res = wrap && wrap.ok ? wrap.res : null;
|
||||
} else {
|
||||
res = await getDiseaseList(searchKey, page, this.pageSize);
|
||||
}
|
||||
let list = [];
|
||||
let hasMore = false;
|
||||
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
<template>
|
||||
<u-popup
|
||||
v-model="show"
|
||||
mode="bottom"
|
||||
:closeable="true"
|
||||
@close="handleClose"
|
||||
:safe-area-inset-bottom="true"
|
||||
:mask-close-able="true"
|
||||
z-index="10078"
|
||||
height="75%"
|
||||
>
|
||||
<view class="transfer-patient-drawer page-bg">
|
||||
<view class="modal-header white">
|
||||
<d-text text="收货信息" className="fs-32 font-bold color-title"></d-text>
|
||||
</view>
|
||||
|
||||
<scroll-view class="scroll-container" scroll-y>
|
||||
<view class="form-body">
|
||||
<view class="field">
|
||||
<d-text text="收货人" className="fs-28 font-bold color-title m-b-12"></d-text>
|
||||
<u-input v-model="form.patient_name" placeholder="请输入收货人姓名" border="surround" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<d-text text="手机号" className="fs-28 font-bold color-title m-b-12"></d-text>
|
||||
<u-input v-model="form.patient_mobile" placeholder="请输入手机号" type="number" border="surround" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<d-text text="收货地址" className="fs-28 font-bold color-title m-b-12"></d-text>
|
||||
<u-input v-model="form.patient_address" placeholder="请输入收货地址" border="surround" />
|
||||
</view>
|
||||
<view class="field">
|
||||
<d-text text="处方图片(可选)" className="fs-28 font-bold color-title m-b-12"></d-text>
|
||||
<view class="img-list">
|
||||
<view v-for="(img, idx) in form.prescription_images" :key="idx" class="img-item">
|
||||
<image :src="img" mode="aspectFill" />
|
||||
<u-icon name="close-circle-fill" color="#999" size="36" class="img-remove" @click="removeImage(idx)" />
|
||||
</view>
|
||||
<view class="img-add" @click="chooseImage" v-if="form.prescription_images.length < 6">
|
||||
<u-icon name="plus" size="40" color="#B0B6C2"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="modal-footer flex-row flex-jus-between flex-ali-center white shadow-up">
|
||||
<u-button
|
||||
:throttle-time="0"
|
||||
@click="handleClose"
|
||||
shape="circle"
|
||||
:custom-style="cancelBtnStyle"
|
||||
>取消</u-button>
|
||||
<u-button
|
||||
:throttle-time="0"
|
||||
:loading="submitting"
|
||||
@click="handleConfirm"
|
||||
shape="circle"
|
||||
:custom-style="confirmBtnStyle"
|
||||
>确认提交</u-button>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { uploadLegacyAdminImage } from '@/api/upload.js';
|
||||
|
||||
export default {
|
||||
name: 'TransferPatientDrawer',
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
submitting: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
form: {
|
||||
patient_name: '',
|
||||
patient_mobile: '',
|
||||
patient_address: '',
|
||||
prescription_images: [],
|
||||
},
|
||||
cancelBtnStyle: {
|
||||
backgroundColor: '#F4F6F8',
|
||||
color: '#2A2E35',
|
||||
height: '88rpx',
|
||||
width: '320rpx',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '30rpx',
|
||||
border: 'none',
|
||||
},
|
||||
confirmBtnStyle: {
|
||||
backgroundColor: '#00A88A',
|
||||
color: '#fff',
|
||||
height: '88rpx',
|
||||
width: '320rpx',
|
||||
fontWeight: 'bold',
|
||||
fontSize: '30rpx',
|
||||
boxShadow: '0 8rpx 20rpx rgba(0, 168, 138, 0.25)',
|
||||
border: 'none',
|
||||
},
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(newVal) {
|
||||
this.show = newVal;
|
||||
},
|
||||
show(newVal) {
|
||||
if (!newVal) {
|
||||
this.$emit('input', false);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClose() {
|
||||
this.show = false;
|
||||
this.$emit('input', false);
|
||||
},
|
||||
chooseImage() {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
success: async (res) => {
|
||||
const path = res.tempFilePaths[0];
|
||||
try {
|
||||
uni.showLoading({ title: '上传中' });
|
||||
const url = await uploadLegacyAdminImage({ filePath: path });
|
||||
this.form.prescription_images.push(url);
|
||||
} catch (e) {
|
||||
uni.showToast({ title: '上传失败', icon: 'none' });
|
||||
} finally {
|
||||
uni.hideLoading();
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
removeImage(idx) {
|
||||
this.form.prescription_images.splice(idx, 1);
|
||||
},
|
||||
handleConfirm() {
|
||||
const name = (this.form.patient_name || '').trim();
|
||||
const mobile = (this.form.patient_mobile || '').trim();
|
||||
const address = (this.form.patient_address || '').trim();
|
||||
if (!name || !mobile || !address) {
|
||||
uni.showToast({ title: '请填写收货人、手机号和地址', icon: 'none' });
|
||||
return;
|
||||
}
|
||||
this.$emit('confirm', {
|
||||
patient_name: name,
|
||||
patient_mobile: mobile,
|
||||
patient_address: address,
|
||||
prescription_images: [...this.form.prescription_images],
|
||||
});
|
||||
},
|
||||
resetForm() {
|
||||
this.form = {
|
||||
patient_name: '',
|
||||
patient_mobile: '',
|
||||
patient_address: '',
|
||||
prescription_images: [],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.page-bg { background-color: #F4F6F8; }
|
||||
.transfer-patient-drawer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
.modal-header {
|
||||
padding: 32rpx;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.scroll-container {
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
.form-body {
|
||||
padding: 32rpx;
|
||||
}
|
||||
.field {
|
||||
margin-bottom: 32rpx;
|
||||
}
|
||||
.img-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
.img-item, .img-add {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
border-radius: 12rpx;
|
||||
position: relative;
|
||||
}
|
||||
.img-item image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 12rpx;
|
||||
}
|
||||
.img-remove {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -8rpx;
|
||||
}
|
||||
.img-add {
|
||||
border: 1px dashed #ccc;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #fff;
|
||||
}
|
||||
.modal-footer {
|
||||
padding: 24rpx 32rpx;
|
||||
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
.shadow-up {
|
||||
box-shadow: 0 -4rpx 16rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
</style>
|
||||
@@ -8,11 +8,11 @@
|
||||
/>
|
||||
<!-- #endif -->
|
||||
<!-- 导航栏 -->
|
||||
<u-navbar class="navbar" :is-back="true" :title="navbarTitle" :custom-back="handleCustomBack" title-color="#000" background="{ background: '#fff' }">
|
||||
<u-navbar class="navbar" :is-back="true" :title="navbarTitle" :custom-back="boundCustomBack" title-color="#000" background="{ background: '#fff' }">
|
||||
</u-navbar>
|
||||
|
||||
<!-- 处方类型Tab切换 -->
|
||||
<view class="tabs flex p-32 flex-ali-center flex-jus-sp shadow-sm" style="overflow-x: auto; position: relative; z-index: 10;">
|
||||
<view v-if="!isSalespersonTransferMode" class="tabs flex p-32 flex-ali-center flex-jus-sp shadow-sm" style="overflow-x: auto; position: relative; z-index: 10;">
|
||||
<u-button
|
||||
:throttle-time="0"
|
||||
:plain="true"
|
||||
@@ -31,10 +31,19 @@
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
<view v-if="registerModeHint" class="register-mode-hint mx-32 m-t-12">
|
||||
<view v-if="!isSalespersonTransferMode && registerModeHint" class="register-mode-hint mx-32 m-t-12">
|
||||
<text class="register-mode-hint__text">{{ registerModeHint }}</text>
|
||||
</view>
|
||||
|
||||
<view v-if="!isSalespersonTransferMode && allowInsuranceCategory" class="mx-32 m-t-12 flex flex-ali-center">
|
||||
<u-button size="mini" :type="feeCategory === 1 ? 'success' : 'default'" @click="feeCategory = 1">自费</u-button>
|
||||
<u-button size="mini" class="m-l-16" :type="feeCategory === 2 ? 'success' : 'default'" @click="feeCategory = 2">医保</u-button>
|
||||
</view>
|
||||
|
||||
<view v-if="!isSalespersonTransferMode && activeCategory === 1 && hasSalespersonTransfer" class="mx-32 m-t-12">
|
||||
<u-button size="mini" type="primary" plain @click="openSalespersonTransfer">查看传方</u-button>
|
||||
</view>
|
||||
|
||||
<!-- 诊断输入区域 -->
|
||||
<view class="top white p-32 flex-col m-t-16 radius-12 mx-32">
|
||||
<view class="top_title flex-row flex-ali-center">
|
||||
@@ -192,7 +201,7 @@
|
||||
</view>
|
||||
<view class="flex-row" style="height: 50rpx;">
|
||||
<u-button
|
||||
v-if="!isCommonPrescription && supportsCommonPrescriptionTemplate"
|
||||
v-if="!isSalespersonTransferMode && !isCommonPrescription && supportsCommonPrescriptionTemplate"
|
||||
:throttle-time="0"
|
||||
shape="circle"
|
||||
size="mini"
|
||||
@@ -335,39 +344,48 @@
|
||||
|
||||
<!-- 费用计算区域 -->
|
||||
<view class="fee-summary white radius-12 p-32 mx-32 m-t-24 m-b-40">
|
||||
<view class="flex-row flex-jus-sp flex-ali-center m-b-24">
|
||||
<text class="fs-28 color-sub">诊疗费</text>
|
||||
<view class="flex-row flex-ali-center">
|
||||
<text class="fs-28 m-r-8">¥</text>
|
||||
<u-input
|
||||
v-model="treatmentPrice"
|
||||
placeholder="0.00"
|
||||
input-align="right"
|
||||
:custom-style="priceInputStyle" />
|
||||
<view v-if="isTransferFeeHidden" class="transfer-fee-hidden-tip flex-col">
|
||||
<text class="fs-28 color-title font-bold">本店已关闭推广员查看费用</text>
|
||||
<text class="fs-24 color-sub m-t-8">提交后由接诊医生确认价格</text>
|
||||
</view>
|
||||
<template v-else>
|
||||
<view v-if="!isSalespersonTransferMode" class="flex-row flex-jus-sp flex-ali-center m-b-24">
|
||||
<text class="fs-28 color-sub">诊疗费</text>
|
||||
<view class="flex-row flex-ali-center">
|
||||
<text class="fs-28 m-r-8">¥</text>
|
||||
<u-input
|
||||
v-model="treatmentPrice"
|
||||
placeholder="0.00"
|
||||
input-align="right"
|
||||
:custom-style="priceInputStyle" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="flex-row flex-jus-sp flex-ali-center m-b-24" v-if="processingFee > 0">
|
||||
<text class="fs-28 color-sub">加工费</text>
|
||||
<text class="fs-30 color-title font-bold">¥{{processingFee.toFixed(2)}}</text>
|
||||
</view>
|
||||
|
||||
<view class="flex-row flex-jus-sp flex-ali-center m-b-32">
|
||||
<view class="flex-col">
|
||||
<text class="fs-28 color-sub">商品计费</text>
|
||||
<text
|
||||
v-if="grossMarginText"
|
||||
class="fs-24 color-primary m-t-8"
|
||||
>毛利率 {{ grossMarginText }}%</text>
|
||||
<view
|
||||
v-if="!isSalespersonTransferMode && processingFee > 0"
|
||||
class="flex-row flex-jus-sp flex-ali-center m-b-24">
|
||||
<text class="fs-28 color-sub">加工费</text>
|
||||
<text class="fs-30 color-title font-bold">¥{{processingFee.toFixed(2)}}</text>
|
||||
</view>
|
||||
<text class="fs-30 color-title font-bold">¥{{totalProductCost.toFixed(2)}}</text>
|
||||
</view>
|
||||
|
||||
<!-- 总价显示 -->
|
||||
<view class="total-price-bar flex-row flex-jus-sp flex-ali-center pt-32 border-t-dashed" v-if="currentDrugs.length > 0">
|
||||
<text class="fs-30 font-bold color-title">合计</text>
|
||||
<text class="fs-48 font-bold color-price">¥{{totalPrice}}</text>
|
||||
</view>
|
||||
<view class="flex-row flex-jus-sp flex-ali-center m-b-32">
|
||||
<view class="flex-col">
|
||||
<text class="fs-28 color-sub">商品计费</text>
|
||||
<text
|
||||
v-if="grossMarginText"
|
||||
class="fs-24 color-primary m-t-8"
|
||||
>毛利率 {{ grossMarginText }}%</text>
|
||||
</view>
|
||||
<text class="fs-30 color-title font-bold">¥{{totalProductCost.toFixed(2)}}</text>
|
||||
</view>
|
||||
|
||||
<view
|
||||
class="total-price-bar flex-row flex-jus-sp flex-ali-center pt-32 border-t-dashed"
|
||||
v-if="currentDrugs.length > 0">
|
||||
<text class="fs-30 font-bold color-title">合计</text>
|
||||
<text class="fs-48 font-bold color-price">¥{{totalPrice}}</text>
|
||||
</view>
|
||||
</template>
|
||||
</view>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
@@ -379,7 +397,7 @@
|
||||
|
||||
<!-- 底部操作栏 -->
|
||||
<view class="bottom flex-jus-sp flex-ali-center safe-area-inset-bottom">
|
||||
<view v-if="!isCommonPrescription && supportsCommonPrescriptionTemplate" @click="handleSaveAsCommonPrescription" class="btnText">另存常用方</view>
|
||||
<view v-if="!isSalespersonTransferMode && !isCommonPrescription && supportsCommonPrescriptionTemplate" @click="handleSaveAsCommonPrescription" class="btnText">另存常用方</view>
|
||||
<u-button
|
||||
:throttle-time="0"
|
||||
@click="handleSendPrescription"
|
||||
@@ -388,10 +406,10 @@
|
||||
backgroundColor: isSubmitting ? '#A5DCD2' : '#6ACDBB',
|
||||
color: '#fff',
|
||||
height: '86rpx',
|
||||
width: (isCommonPrescription || !supportsCommonPrescriptionTemplate) ? '686rpx' : '448rpx'
|
||||
width: (isSalespersonTransferMode || isCommonPrescription || !supportsCommonPrescriptionTemplate) ? '686rpx' : '448rpx'
|
||||
}"
|
||||
:disabled="isSubmitting">
|
||||
{{ isCommonPrescription ? '保存常用方' : '发送处方' }}
|
||||
{{ isCommonPrescription ? '保存常用方' : (isSalespersonTransferMode ? '提交传方' : '发送处方') }}
|
||||
</u-button>
|
||||
</view>
|
||||
|
||||
@@ -407,6 +425,7 @@
|
||||
<DiagnosisModal
|
||||
v-model="showDiagnosisModal"
|
||||
:selectedDiagnosis="diagnosisText"
|
||||
:salesperson-transfer-mode="isSalespersonTransferMode"
|
||||
@confirm="handleDiagnosisConfirm"
|
||||
/>
|
||||
|
||||
@@ -439,9 +458,18 @@
|
||||
:initial-search-key="chineseModalInitialKeyword"
|
||||
:register-id="registerId"
|
||||
:see-rate="seeRate"
|
||||
:salesperson-transfer-mode="isSalespersonTransferMode"
|
||||
:show-drug-price="showSalespersonTransferPrice"
|
||||
@select="handleSelectChineseDrug"
|
||||
/>
|
||||
|
||||
<TransferPatientDrawer
|
||||
v-model="showTransferPatientDrawer"
|
||||
:submitting="transferDrawerSubmitting"
|
||||
ref="transferPatientDrawer"
|
||||
@confirm="handleTransferPatientConfirm"
|
||||
/>
|
||||
|
||||
<!-- 简单产品选择弹窗 -->
|
||||
<SimpleProductModal
|
||||
v-model="showSimpleProductModal"
|
||||
@@ -491,9 +519,11 @@ import {
|
||||
saveWestCommonPrescriptionApi,
|
||||
saveChineseCommonPrescriptionApi,
|
||||
getCurrentStoreTypeApi,
|
||||
getSalespersonTransferByRegisterApi,
|
||||
getPrescriptionInfoApi,
|
||||
getTraditionalChineseMedicineJson
|
||||
} from '@/api/reception.js';
|
||||
import { parseTransferPrescriptionContent } from '@/subPackages/sub_online_reception/utils/transferPrescriptionImport.js';
|
||||
import { getChatRoomByRegisterApi, sendToUserHttpApi } from '@/api/chat.js';
|
||||
import { PrescriptionStorage } from './utils/prescriptionStorage.js';
|
||||
import { PrescriptionCalculator } from './utils/prescriptionCalculator.js';
|
||||
@@ -507,8 +537,16 @@ import SimpleProductModal from './components/modals/SimpleProductModal.vue';
|
||||
import WesternMedicineUsageModal from './components/modals/WesternMedicineUsageModal.vue';
|
||||
import ChineseMedicineConfig from './components/ChineseMedicineConfig.vue';
|
||||
import TraditionalTermPickerModal from './components/modals/TraditionalTermPickerModal.vue';
|
||||
import TransferPatientDrawer from './components/modals/TransferPatientDrawer.vue';
|
||||
import {
|
||||
createClinicSalespersonTransferPrescription,
|
||||
getClinicSalespersonSeePriceConfig,
|
||||
getClinicSalespersonProcessRuleList,
|
||||
} from '@/api/clinicSalesperson.js';
|
||||
|
||||
const PENDING_RX_FROM_CHAT = 'xk_pending_rx_from_chat';
|
||||
const SALESPERSON_TRANSFER_STORAGE_KEY = 'salesperson_transfer';
|
||||
const SALESPERSON_HOME_URL = '/subPackages/sub_clinic_salesperson/home/index';
|
||||
|
||||
/** 与 closeTopOverlayIfAny 一致:任意为 true 时启用微信小程序 page-container 返回拦截 */
|
||||
const PRESCRIPTION_OVERLAY_KEYS = [
|
||||
@@ -520,7 +558,8 @@ const PRESCRIPTION_OVERLAY_KEYS = [
|
||||
'showCommonPrescriptionModal',
|
||||
'showDiagnosisModal',
|
||||
'showDoctorOrderModal',
|
||||
'showSaveCommonPrescriptionModal'
|
||||
'showSaveCommonPrescriptionModal',
|
||||
'showTransferPatientDrawer'
|
||||
];
|
||||
|
||||
export default {
|
||||
@@ -534,7 +573,8 @@ export default {
|
||||
SimpleProductModal,
|
||||
WesternMedicineUsageModal,
|
||||
ChineseMedicineConfig,
|
||||
TraditionalTermPickerModal
|
||||
TraditionalTermPickerModal,
|
||||
TransferPatientDrawer
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -594,6 +634,9 @@ export default {
|
||||
isSubmitting: false,
|
||||
identity: uni.getStorageSync('role'),
|
||||
registerStoreInfo: null,
|
||||
feeCategory: 1,
|
||||
salespersonTransferPrescriptionId: 0,
|
||||
hasSalespersonTransfer: false,
|
||||
/** 是否可查看毛利率 0/1(xk-api see_rate) */
|
||||
seeRate: 0,
|
||||
sendMode: 0,
|
||||
@@ -644,15 +687,22 @@ export default {
|
||||
borderRadius: '8rpx',
|
||||
padding: '0 16rpx'
|
||||
},
|
||||
scrollStyle: {
|
||||
height: 'calc(100vh - 600rpx)',
|
||||
marginBottom: '200rpx'
|
||||
},
|
||||
/** 微信小程序 page-container:有抽屉/弹层时为 true,拦截侧滑等返回 */
|
||||
pageBackGuardShow: false
|
||||
pageBackGuardShow: false,
|
||||
/** 程序化隐藏 page-container 时抑制 @leave,避免误触发页面退出确认 */
|
||||
_suppressPageBackGuardLeave: false,
|
||||
/** 推广员传方模式 */
|
||||
isSalespersonTransferMode: false,
|
||||
/** 推广员是否可查看售价 0/1(门店 salesperson see_price) */
|
||||
salespersonSeePrice: 0,
|
||||
showTransferPatientDrawer: false,
|
||||
transferDrawerSubmitting: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
allowInsuranceCategory() {
|
||||
return Number(this.registerStoreInfo?.allow_insurance_category ?? 0) === 1;
|
||||
},
|
||||
totalProductCost() {
|
||||
if (this.currentDrugs.length === 0) return 0;
|
||||
if (this.activeCategory === 1) {
|
||||
@@ -690,6 +740,12 @@ export default {
|
||||
this.chineseConfig.dosage
|
||||
);
|
||||
},
|
||||
showSalespersonTransferPrice() {
|
||||
return !this.isSalespersonTransferMode || Number(this.salespersonSeePrice) === 1;
|
||||
},
|
||||
isTransferFeeHidden() {
|
||||
return this.isSalespersonTransferMode && Number(this.salespersonSeePrice) === 0;
|
||||
},
|
||||
supportsCommonPrescriptionTemplate() {
|
||||
return [1, 2].includes(this.activeCategory);
|
||||
},
|
||||
@@ -702,11 +758,42 @@ export default {
|
||||
return this.isOnlineRevisit ? '当前:在线复诊开方(提交后将向患者发送处方消息)' : '当前:线下开方';
|
||||
},
|
||||
navbarTitle() {
|
||||
if (this.isSalespersonTransferMode) {
|
||||
return '传方';
|
||||
}
|
||||
if (this.registerOrderType === null || this.registerOrderType === undefined || this.registerOrderType === '') {
|
||||
return '开方';
|
||||
}
|
||||
return this.isOnlineRevisit ? '开方 · 在线复诊' : '开方 · 线下';
|
||||
},
|
||||
hasTransferDraft() {
|
||||
if (!this.isSalespersonTransferMode) return false;
|
||||
return this.currentDrugs.length > 0
|
||||
|| this.diagnoses.length > 0
|
||||
|| !!(this.medicalAdvice && String(this.medicalAdvice).trim());
|
||||
},
|
||||
shouldEnablePageBackGuard() {
|
||||
return this.hasPrescriptionOverlay;
|
||||
},
|
||||
scrollStyle() {
|
||||
try {
|
||||
const sys = uni.getSystemInfoSync();
|
||||
const rpxRatio = sys.windowWidth / 750;
|
||||
const topRpx = this.isSalespersonTransferMode ? 500 : 600;
|
||||
const bottomRpx = 200;
|
||||
const heightPx = sys.windowHeight - topRpx * rpxRatio - bottomRpx * rpxRatio;
|
||||
return {
|
||||
height: `${Math.max(Math.floor(heightPx), 200)}px`,
|
||||
marginBottom: '200rpx',
|
||||
};
|
||||
} catch (e) {
|
||||
const topRpx = this.isSalespersonTransferMode ? 500 : 600;
|
||||
return {
|
||||
height: `calc(100vh - ${topRpx}rpx)`,
|
||||
marginBottom: '200rpx',
|
||||
};
|
||||
}
|
||||
},
|
||||
onlineTcmDiseasesLabel() {
|
||||
const data = this.traditionalTcmData;
|
||||
const id = this.onlineTcmDiseasesId;
|
||||
@@ -749,13 +836,27 @@ export default {
|
||||
if (!val) this.chineseModalInitialKeyword = '';
|
||||
},
|
||||
// #ifdef MP-WEIXIN
|
||||
hasPrescriptionOverlay(val) {
|
||||
this.pageBackGuardShow = !!val;
|
||||
shouldEnablePageBackGuard(val) {
|
||||
this.setPageBackGuardShow(!!val, { suppressLeave: !val });
|
||||
this.syncSalespersonTransferUnloadAlert();
|
||||
},
|
||||
hasTransferDraft() {
|
||||
this.syncSalespersonTransferUnloadAlert();
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
created() {
|
||||
// u-navbar 在小程序内 bind($parent) 可能拿不到页面实例,用箭头函数固定 this
|
||||
this.boundCustomBack = () => this.handleCustomBack();
|
||||
},
|
||||
onLoad(options) {
|
||||
this.registerId = options.register_id || '';
|
||||
this.isSalespersonTransferMode = String(options.salesperson_transfer) === '1';
|
||||
if (this.isSalespersonTransferMode) {
|
||||
this.registerId = SALESPERSON_TRANSFER_STORAGE_KEY;
|
||||
this._presetActiveCategory = 1;
|
||||
} else {
|
||||
this.registerId = options.register_id || '';
|
||||
}
|
||||
this.patientId = options.patient_id || '';
|
||||
this.pendingReusePrescriptionId = options.reuse_prescription_id ? String(options.reuse_prescription_id) : '';
|
||||
this.isCommonPrescription = String(options.save_common) === '1';
|
||||
@@ -773,33 +874,86 @@ export default {
|
||||
if (this.activeCategory === 1 && this.chineseConfig.ruleType === 2) {
|
||||
await this.hydrateChineseProcessRuleListsFromConfig();
|
||||
}
|
||||
if (this.activeCategory === 1) await this.checkAndShowTransferTip();
|
||||
if (!this.isSalespersonTransferMode && this.activeCategory === 1) {
|
||||
await this.checkAndShowTransferTip();
|
||||
}
|
||||
},
|
||||
onUnload() {
|
||||
if (this.isCommonPrescription) {
|
||||
uni.removeStorageSync('add');
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
if (this.isSalespersonTransferMode) {
|
||||
uni.disableAlertBeforeUnload();
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
// #ifdef MP-WEIXIN
|
||||
setPageBackGuardShow(show, { suppressLeave = false } = {}) {
|
||||
if (suppressLeave) this._suppressPageBackGuardLeave = true;
|
||||
this.pageBackGuardShow = !!show;
|
||||
},
|
||||
syncSalespersonTransferUnloadAlert() {
|
||||
if (!this.isSalespersonTransferMode) return;
|
||||
if (this.hasTransferDraft && !this.hasPrescriptionOverlay) {
|
||||
uni.enableAlertBeforeUnload({ message: '有未提交的传方内容,确定退出?' });
|
||||
} else {
|
||||
uni.disableAlertBeforeUnload();
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
async initializePageData() {
|
||||
try {
|
||||
this.restoreFromLocalStorage();
|
||||
await this.loadPatientInfo();
|
||||
await this.loadRegisterOrderType();
|
||||
await this.loadBasicConfigData();
|
||||
await this.loadRegisterStoreInfo();
|
||||
if (this.isSalespersonTransferMode) {
|
||||
await this.loadSalespersonTransferConfig();
|
||||
await this.loadBasicConfigDataForTransfer();
|
||||
} else {
|
||||
await this.loadPatientInfo();
|
||||
await this.loadRegisterOrderType();
|
||||
await this.loadBasicConfigData();
|
||||
await this.loadRegisterStoreInfo();
|
||||
}
|
||||
this.loadCurrentCategoryDrugs();
|
||||
if (this.activeCategory === 1 && this.chineseConfig.ruleType === 2) {
|
||||
await this.hydrateChineseProcessRuleListsFromConfig();
|
||||
}
|
||||
await this.maybeApplyReusePrescription();
|
||||
if (this.activeCategory === 1) await this.checkAndShowTransferTip();
|
||||
this.applyPendingExperienceDrugFromChat();
|
||||
if (!this.isSalespersonTransferMode) {
|
||||
await this.maybeApplyReusePrescription();
|
||||
if (this.activeCategory === 1) await this.checkAndShowTransferTip();
|
||||
this.applyPendingExperienceDrugFromChat();
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
this.$nextTick(() => {
|
||||
this.setPageBackGuardShow(this.shouldEnablePageBackGuard);
|
||||
this.syncSalespersonTransferUnloadAlert();
|
||||
});
|
||||
// #endif
|
||||
} catch (error) {
|
||||
console.error('初始化页面数据失败:', error);
|
||||
this.$toast('初始化失败,请重试');
|
||||
}
|
||||
},
|
||||
async loadSalespersonTransferConfig() {
|
||||
try {
|
||||
const wrap = await getClinicSalespersonSeePriceConfig();
|
||||
if (wrap && wrap.ok) {
|
||||
this.salespersonSeePrice = Number(wrap.data?.see_price ?? 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取推广员价格配置失败:', error);
|
||||
}
|
||||
},
|
||||
async loadBasicConfigDataForTransfer() {
|
||||
try {
|
||||
if (this.activeCategory === 1 && this.processRuleList.length === 0) {
|
||||
await this.loadProcessRuleList(0, 0);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载基础配置数据失败:', error);
|
||||
}
|
||||
},
|
||||
restoreFromLocalStorage() {
|
||||
if (this._presetActiveCategory !== null && this._presetActiveCategory !== undefined && !Number.isNaN(this._presetActiveCategory)) {
|
||||
this.activeCategory = this._presetActiveCategory;
|
||||
@@ -972,6 +1126,10 @@ export default {
|
||||
if (res) {
|
||||
this.registerStoreInfo = res;
|
||||
this.seeRate = Number(res.see_rate ?? 0);
|
||||
if (Number(res.allow_insurance_category ?? 0) !== 1) {
|
||||
this.feeCategory = 1;
|
||||
}
|
||||
this.checkSalespersonTransfer();
|
||||
if (res.is_from_transfer === 1 && res.delegate_store_id) {
|
||||
this.selectedStoreId = res.delegate_store_id;
|
||||
this.sendMode = 1;
|
||||
@@ -984,11 +1142,65 @@ export default {
|
||||
console.error('获取挂号诊所信息失败:', error);
|
||||
}
|
||||
},
|
||||
async checkSalespersonTransfer() {
|
||||
if (!this.registerId) {
|
||||
this.hasSalespersonTransfer = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await getSalespersonTransferByRegisterApi(this.registerId);
|
||||
const list = res?.result || res?.data || res;
|
||||
this.hasSalespersonTransfer = Array.isArray(list) && list.length > 0;
|
||||
} catch {
|
||||
this.hasSalespersonTransfer = false;
|
||||
}
|
||||
},
|
||||
async openSalespersonTransfer() {
|
||||
try {
|
||||
const res = await getSalespersonTransferByRegisterApi(this.registerId);
|
||||
const list = res?.result || res?.data || res;
|
||||
if (!Array.isArray(list) || !list.length) {
|
||||
this.$toast('暂无待导入传方');
|
||||
return;
|
||||
}
|
||||
const item = list[0];
|
||||
const parsed = parseTransferPrescriptionContent(item);
|
||||
if (!parsed || !parsed.drugList?.length) {
|
||||
this.$toast('传方数据无效');
|
||||
return;
|
||||
}
|
||||
this.salespersonTransferPrescriptionId = item.id;
|
||||
this.activeCategory = 1;
|
||||
parsed.drugList.forEach((drug) => {
|
||||
this.currentDrugs.push({
|
||||
index_id: drug.index_id ?? drug.drug_id ?? drug.id,
|
||||
id: drug.drug_id || drug.id,
|
||||
drug_id: drug.drug_id || drug.id,
|
||||
drug_name: drug.drug_name || drug.name,
|
||||
name: drug.drug_name || drug.name,
|
||||
number: drug.number || 1,
|
||||
way_id: drug.way_id || 0,
|
||||
price: drug.price ?? 0,
|
||||
buy_price: drug.buy_price,
|
||||
});
|
||||
});
|
||||
if (parsed.clinical_diagnose) {
|
||||
this.diagnoses = [{ id: Date.now(), name: parsed.clinical_diagnose }];
|
||||
}
|
||||
if (parsed.doctor_order) this.medicalAdvice = parsed.doctor_order;
|
||||
this.saveToLocalStorage();
|
||||
this.$toast('传方已导入');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.$toast('加载传方失败');
|
||||
}
|
||||
},
|
||||
async handleSwitchCategory(category) {
|
||||
this.saveToLocalStorage();
|
||||
this.activeCategory = category;
|
||||
this.loadCurrentCategoryDrugs();
|
||||
if (category === 1) {
|
||||
await this.checkSalespersonTransfer();
|
||||
await this.checkAndShowTransferTip();
|
||||
if (this.processRuleList.length === 0) await this.loadProcessRuleList(0, 0);
|
||||
}
|
||||
@@ -1436,8 +1648,16 @@ export default {
|
||||
async loadProcessRuleList(pid = 0, ruleId = 0) {
|
||||
try {
|
||||
const data = ruleId === 0 ? { pid } : { rule_id: ruleId };
|
||||
const res = await getProcessRuleList(data);
|
||||
const list = res?.data || res?.result || res || [];
|
||||
let list = [];
|
||||
if (this.isSalespersonTransferMode) {
|
||||
const wrap = await getClinicSalespersonProcessRuleList(data);
|
||||
if (wrap && wrap.ok) {
|
||||
list = Array.isArray(wrap.data) ? wrap.data : [];
|
||||
}
|
||||
} else {
|
||||
const res = await getProcessRuleList(data);
|
||||
list = res?.data || res?.result || res || [];
|
||||
}
|
||||
if (ruleId !== 0) {
|
||||
this.processRuleNoteList = list;
|
||||
} else if (pid === 0) {
|
||||
@@ -1643,6 +1863,11 @@ export default {
|
||||
const validation = this.validatePrescriptionData();
|
||||
if (!validation.valid) { this.$toast(validation.message); return; }
|
||||
|
||||
if (this.isSalespersonTransferMode) {
|
||||
this.showTransferPatientDrawer = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.isSubmitting = true;
|
||||
try {
|
||||
if (!this.registerStoreInfo) await this.loadRegisterStoreInfo();
|
||||
@@ -1741,7 +1966,8 @@ export default {
|
||||
}
|
||||
|
||||
const params = {
|
||||
patient: patientData, drugs: drugsData, diagnosis: clinical_diagnose, medicalAdvice: this.medicalAdvice, total: parseFloat(this.totalPrice), category: 1, 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,
|
||||
};
|
||||
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;
|
||||
@@ -1848,6 +2074,97 @@ export default {
|
||||
if (this.isCommonPrescription) { uni.removeStorageSync('add'); this.safeNavigateBack(400); }
|
||||
} catch (error) { console.error('保存常用方失败:', error); this.$toast('保存常用方失败,请重试'); }
|
||||
},
|
||||
buildSalespersonTransferPayload(patientData) {
|
||||
const clinicalDiagnose = this.diagnosisText || this.diagnoses.map((item) => item.name).join(',');
|
||||
const drugs = this.currentDrugs.map((drug) => ({
|
||||
drug_id: drug.id || drug.drug_id || drug.index_id,
|
||||
drug_name: drug.drug_name || drug.name,
|
||||
number: drug.number || drug._quantity || 1,
|
||||
way_id: drug.way_id || 0,
|
||||
}));
|
||||
return {
|
||||
patient_name: patientData.patient_name,
|
||||
patient_mobile: patientData.patient_mobile,
|
||||
patient_address: patientData.patient_address,
|
||||
prescription_images: patientData.prescription_images || [],
|
||||
clinical_diagnose: clinicalDiagnose,
|
||||
doctor_order: this.medicalAdvice || '',
|
||||
dosage: this.chineseConfig.dosage || 7,
|
||||
day_dosage: this.chineseConfig.dayDosage || 2,
|
||||
drugs,
|
||||
};
|
||||
},
|
||||
async handleTransferPatientConfirm(patientData) {
|
||||
this.transferDrawerSubmitting = true;
|
||||
try {
|
||||
const wrap = await createClinicSalespersonTransferPrescription(
|
||||
this.buildSalespersonTransferPayload(patientData)
|
||||
);
|
||||
if (wrap && wrap.ok) {
|
||||
PrescriptionStorage.clearAllPrescriptionData(SALESPERSON_TRANSFER_STORAGE_KEY);
|
||||
this.showTransferPatientDrawer = false;
|
||||
if (this.$refs.transferPatientDrawer && this.$refs.transferPatientDrawer.resetForm) {
|
||||
this.$refs.transferPatientDrawer.resetForm();
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
this.setPageBackGuardShow(false, { suppressLeave: true });
|
||||
uni.disableAlertBeforeUnload();
|
||||
// #endif
|
||||
this.$toast('提交成功');
|
||||
setTimeout(() => {
|
||||
uni.navigateTo({ url: '/subPackages/sub_clinic_salesperson/transfer-prescription/list' });
|
||||
}, 500);
|
||||
} else {
|
||||
this.$toast(wrap?.res?.msg || wrap?.res?.message || '提交失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('提交传方失败:', error);
|
||||
this.$toast('提交传方失败,请重试');
|
||||
} finally {
|
||||
this.transferDrawerSubmitting = false;
|
||||
}
|
||||
},
|
||||
tryLeaveSalespersonTransferPage(onConfirm) {
|
||||
if (this.hasTransferDraft) {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: '有未提交的传方内容,确定退出?',
|
||||
success: ({ confirm }) => {
|
||||
if (confirm) {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.setPageBackGuardShow(false, { suppressLeave: true });
|
||||
uni.disableAlertBeforeUnload();
|
||||
// #endif
|
||||
if (typeof onConfirm === 'function') onConfirm();
|
||||
} else {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.setPageBackGuardShow(this.shouldEnablePageBackGuard, {
|
||||
suppressLeave: !this.shouldEnablePageBackGuard,
|
||||
});
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
this.setPageBackGuardShow(false, { suppressLeave: true });
|
||||
uni.disableAlertBeforeUnload();
|
||||
// #endif
|
||||
if (typeof onConfirm === 'function') onConfirm();
|
||||
},
|
||||
navigateToSalespersonWorkbench() {
|
||||
const pages = getCurrentPages();
|
||||
if (pages.length > 1) {
|
||||
this.safeNavigateBack(0);
|
||||
return;
|
||||
}
|
||||
// #ifdef MP-WEIXIN
|
||||
this.setPageBackGuardShow(false, { suppressLeave: true });
|
||||
uni.disableAlertBeforeUnload();
|
||||
// #endif
|
||||
uni.reLaunch({ url: SALESPERSON_HOME_URL });
|
||||
},
|
||||
/** 若有弹层打开则关闭第一个并返回 true(用于导航返回 / 微信 page-container leave 先关抽屉) */
|
||||
closeTopOverlayIfAny() {
|
||||
for (let i = 0; i < PRESCRIPTION_OVERLAY_KEYS.length; i++) {
|
||||
@@ -1863,7 +2180,7 @@ export default {
|
||||
safeNavigateBack(delayMs = 0) {
|
||||
const runNav = () => {
|
||||
// #ifdef MP-WEIXIN
|
||||
this.pageBackGuardShow = false;
|
||||
this.setPageBackGuardShow(false, { suppressLeave: true });
|
||||
setTimeout(() => {
|
||||
uni.navigateBack();
|
||||
}, 16);
|
||||
@@ -1880,14 +2197,31 @@ export default {
|
||||
},
|
||||
// #ifdef MP-WEIXIN
|
||||
onPageBackGuardLeave() {
|
||||
this.closeTopOverlayIfAny();
|
||||
this.$nextTick(() => {
|
||||
this.pageBackGuardShow = this.hasPrescriptionOverlay;
|
||||
});
|
||||
if (this._suppressPageBackGuardLeave) {
|
||||
this._suppressPageBackGuardLeave = false;
|
||||
return;
|
||||
}
|
||||
if (this.closeTopOverlayIfAny()) {
|
||||
this.$nextTick(() => {
|
||||
this.setPageBackGuardShow(this.shouldEnablePageBackGuard, { suppressLeave: true });
|
||||
this.syncSalespersonTransferUnloadAlert();
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setPageBackGuardShow(false);
|
||||
if (this.isSalespersonTransferMode) {
|
||||
this.tryLeaveSalespersonTransferPage(() => this.navigateToSalespersonWorkbench());
|
||||
return;
|
||||
}
|
||||
this.safeNavigateBack(0);
|
||||
},
|
||||
// #endif
|
||||
handleCustomBack() {
|
||||
if (this.closeTopOverlayIfAny()) return;
|
||||
if (this.isSalespersonTransferMode) {
|
||||
this.tryLeaveSalespersonTransferPage(() => this.navigateToSalespersonWorkbench());
|
||||
return;
|
||||
}
|
||||
this.safeNavigateBack(0);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user