diff --git a/.cursor/rules/Code-Standards.md b/.cursor/rules/Code-Standards.md new file mode 100644 index 0000000..4e74dd2 --- /dev/null +++ b/.cursor/rules/Code-Standards.md @@ -0,0 +1,12 @@ +--- +description: +alwaysApply: true +--- + +1. 写代码的时候要补充详细的中文注释(每个方法是干嘛的,为什么要这样写),如果是工作区,则所有项目都适用该规则 +2. 注意不要生成太多的空行,上一部分代码和下一部分代码中间的空行不要大于2行 +3. 有封装好的方法、组件需要复用,不要重复造轮子 +4. 小程序端的抽屉全部需要用page-container来防止用户意外退出页面(记得使用v-if而不是v-show) +5. 数据库的(created_at、updated_at、deleted_at)统一使用时间戳,不要使用字符串,并且我在查询器中一级格式化成字符串了,无需再次格式化 +6. 微信小程序 `.vue` 模板(会编译成 WXML)禁止使用 `??`、`?.` 等现代运算符,否则会报 `unexpected token '?'`;模板里用 `||` / 显式判断,脚本里可用 `??`/`?.` +7. 微信小程序模板里 `:class` / `:style` 禁止写 `fn(arg)` 方法调用(如 `:class="sexClass(p)"` 会编译失败);用对象/数组字面量(如 `:class="{ male: p.sex == 1 }"`),或把结果先算进 data/computed 再绑定 diff --git a/.cursor/rules/Code-Standards.mdc b/.cursor/rules/Code-Standards.mdc index fa8db67..4e74dd2 100644 --- a/.cursor/rules/Code-Standards.mdc +++ b/.cursor/rules/Code-Standards.mdc @@ -8,3 +8,5 @@ alwaysApply: true 3. 有封装好的方法、组件需要复用,不要重复造轮子 4. 小程序端的抽屉全部需要用page-container来防止用户意外退出页面(记得使用v-if而不是v-show) 5. 数据库的(created_at、updated_at、deleted_at)统一使用时间戳,不要使用字符串,并且我在查询器中一级格式化成字符串了,无需再次格式化 +6. 微信小程序 `.vue` 模板(会编译成 WXML)禁止使用 `??`、`?.` 等现代运算符,否则会报 `unexpected token '?'`;模板里用 `||` / 显式判断,脚本里可用 `??`/`?.` +7. 微信小程序模板里 `:class` / `:style` 禁止写 `fn(arg)` 方法调用(如 `:class="sexClass(p)"` 会编译失败);用对象/数组字面量(如 `:class="{ male: p.sex == 1 }"`),或把结果先算进 data/computed 再绑定 diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index a4e4183..7107774 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -15,7 +15,7 @@ diff --git a/api/clinicAdmin.js b/api/clinicAdmin.js index e114b48..88dac30 100644 --- a/api/clinicAdmin.js +++ b/api/clinicAdmin.js @@ -5,8 +5,9 @@ const PREFIX = '/newApi/clinic-admin'; /** 解析 Laravel jok 或 Yii 响应 */ export function unwrapClinicRes(res) { if (!res) return null; - if (res.code === 0 && res.result !== undefined) return res.result; - if (res.errcode != null && res.errcode !== -1) return res.data; + // code 可能是 0 / "0" + if (Number(res.code) === 0 && res.result !== undefined) return res.result; + if (res.errcode != null && Number(res.errcode) !== -1) return res.data; if (res.result !== undefined) return res.result; return res.data != null ? res.data : res; } @@ -20,7 +21,10 @@ function clinicRequest(options) { }, }).then((res) => { const data = unwrapClinicRes(res); - const ok = res && (res.code === 0 || (res.errcode != null && Number(res.errcode) !== -1)); + const ok = !!(res && ( + Number(res.code) === 0 + || (res.errcode != null && Number(res.errcode) !== -1) + )); return { res, data, ok }; }); } diff --git a/api/platformStore.js b/api/platformStore.js index a4e0813..861bcb6 100644 --- a/api/platformStore.js +++ b/api/platformStore.js @@ -1,110 +1,24 @@ -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 }); -} +/** + * 兼容旧引用:平台门店 API 统一走 storeManage(按 loginMode 切前缀) + */ +export { + getStoreList as getPlatformStoreList, + getStoreDetail as getPlatformStoreDetail, + updateStore as updatePlatformStore, + openStorePcWindows, + openStoreQrCode, + updateStoreShippingFree, + updateStoreSubscribeStatus, + updateStoreSeeRate, + updateStoreAllowInsurance, + updateStoreClinicType, + toggleSalespersonSeePrice, + getStoreDrugsByType, + updateStoreDrugPrices, + getDoctorOption, + getInternetMedicalStoreOption, + bindOnlineConsultation, +} from './storeManage.js'; export const DRUG_TYPE_OPTIONS = [ { label: '中药', value: 1 }, @@ -114,3 +28,13 @@ export const DRUG_TYPE_OPTIONS = [ { label: '非药品', value: 6 }, { label: '医疗器械', value: 7 }, ]; + +/** @deprecated 请使用 updateStore */ +export async function updateStoreExternalField(data) { + const { updateStore } = await import('./storeManage.js'); + return updateStore(data); +} + +export async function deletePlatformStore() { + return { ok: false, data: null, res: null }; +} diff --git a/api/reception.js b/api/reception.js index 3ad3791..f999c6c 100644 --- a/api/reception.js +++ b/api/reception.js @@ -250,6 +250,18 @@ export function addWestPrescription(data) { }) } +/** + * 按药品查可选配送仓(药店开方选仓) + * 走 PC 同源 admin JWT 接口;医生小程序侧若无权限需后端另开 doctor 路由,此处与 delivery-warehouse-drug 对齐 + */ +export function getDeliveryWarehouseOptionsByDrugs(data) { + return req.request({ + url: '/newApi/doctor-reception-wx/delivery-warehouse-options-by-drugs', + method: 'GET', + data + }) +} + // 检查中药相冲 export function checkChineseMedicineConflictApi(data) { return req.request({ diff --git a/api/storeManage.js b/api/storeManage.js new file mode 100644 index 0000000..e77cf2c --- /dev/null +++ b/api/storeManage.js @@ -0,0 +1,152 @@ +/** + * 门店管理 API:按 loginMode 切换平台超管 / 业务员前缀 + * 业务员与超管复用同一套门店列表/详情页 + */ +import { req } from '@/common/js/index.js'; +import { unwrapClinicRes } from '@/api/clinicAdmin.js'; + +function getStoreContext() { + const mode = uni.getStorageSync('loginMode'); + if (mode === 'salesperson') { + return { + prefix: '/newApi/salesperson-store', + configPrefix: '', + headerKey: '_salesperson', + isSalesperson: true, + }; + } + return { + prefix: '/newApi/platform-admin-store', + configPrefix: '/newApi/platform-admin-salesperson-store-config', + headerKey: '_platformAdmin', + isSalesperson: false, + }; +} + +function storeRequest(options) { + const ctx = getStoreContext(); + return req.request({ + ...options, + header: { + ...(options.header || {}), + [ctx.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 }; + }); +} + +/** 业务员是否允许编辑门店(系统配置开关) */ +export function getSalespersonStoreEditPermission() { + return storeRequest({ + url: '/newApi/salesperson-store/edit-permission', + method: 'GET', + }); +} + +export function getStoreList(params) { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/list`, method: 'GET', data: params }); +} + +export function getStoreDetail(id) { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/detail`, method: 'GET', data: { id } }); +} + +export function updateStore(data) { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/update`, method: 'POST', data }); +} + +export function openStorePcWindows(id) { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/open-pc-windows`, method: 'POST', data: { id } }); +} + +export function openStoreQrCode(id) { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/open-qr-code`, method: 'POST', data: { id } }); +} + +export function updateStoreShippingFree(id) { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/update-shipping-free`, method: 'POST', data: { id } }); +} + +export function updateStoreSubscribeStatus(id) { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/update-subscribe-status`, method: 'POST', data: { id } }); +} + +export function updateStoreSeeRate(id) { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/update-see-rate`, method: 'POST', data: { id } }); +} + +export function updateStoreAllowInsurance(id) { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/update-allow-insurance-category`, method: 'POST', data: { id } }); +} + +export function updateStoreClinicType(id, clinicType) { + const { prefix } = getStoreContext(); + return storeRequest({ + url: `${prefix}/update-clinic-type`, + method: 'POST', + data: { id, clinic_type: clinicType }, + }); +} + +export function toggleSalespersonSeePrice(storeId) { + const { configPrefix, isSalesperson } = getStoreContext(); + if (isSalesperson || !configPrefix) { + return Promise.resolve({ ok: false, data: null, res: null }); + } + return storeRequest({ + url: `${configPrefix}/toggle-see-price`, + method: 'POST', + data: { store_id: storeId }, + }); +} + +export function getStoreDrugsByType(storeId, type) { + const { prefix } = getStoreContext(); + return storeRequest({ + url: `${prefix}/get-store-drugs-by-type`, + method: 'GET', + data: { store_id: storeId, type }, + }); +} + +export function updateStoreDrugPrices(data) { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/update-store-drug-prices`, method: 'POST', data }); +} + +export function getDoctorOption(storeId) { + const { prefix } = getStoreContext(); + return storeRequest({ + url: `${prefix}/doctor-option`, + method: 'GET', + data: storeId ? { store_id: storeId } : {}, + }); +} + +export function getInternetMedicalStoreOption() { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/internet-medical-store-option`, method: 'GET' }); +} + +export function bindOnlineConsultation(data) { + const { prefix } = getStoreContext(); + return storeRequest({ url: `${prefix}/bind-online-consultation`, method: 'POST', data }); +} + +export function isSalespersonStoreMode() { + return uni.getStorageSync('loginMode') === 'salesperson'; +} + +export { getStoreContext }; diff --git a/api/userPatientProfile.js b/api/userPatientProfile.js new file mode 100644 index 0000000..5802f76 --- /dev/null +++ b/api/userPatientProfile.js @@ -0,0 +1,132 @@ +/** + * 用户管理 / 就诊人档案 API(诊所管理员 / 平台超管) + */ +import { req } from '@/common/js/index.js'; +import { unwrapClinicRes } from '@/api/clinicAdmin.js'; + +function getProfileContext() { + const mode = uni.getStorageSync('loginMode'); + if (mode === 'clinic_admin') { + return { + prefix: '/newApi/clinic-admin-user-patient-profile', + headerKey: '_clinicAdmin', + }; + } + return { + prefix: '/newApi/platform-admin-user-patient-profile', + headerKey: '_platformAdmin', + }; +} + +function profileRequest(options) { + const ctx = getProfileContext(); + return req.request({ + ...options, + header: { + ...(options.header || {}), + [ctx.headerKey]: '1', + }, + }).then((res) => { + const data = unwrapClinicRes(res); + // code 可能是数字 0 或字符串 "0";勿用 === 导致 ok 为 false、列表被丢弃 + const ok = !!(res && ( + Number(res.code) === 0 + || (res.errcode != null && Number(res.errcode) !== -1) + )); + return { res, data, ok }; + }); +} + +/** + * 从 profile 接口包装中取出列表 items(兼容 result/data/items 多层) + */ +export function pickProfileItems(wrap) { + const raw = (wrap && wrap.data) || null; + const body = wrap && wrap.res; + const fromBody = body && (body.result || body.data); + let payload = raw; + // unwrap 后若仍带着 code/result,再剥一层 + if (payload && payload.items == null && payload.result != null) { + payload = payload.result; + } + if (payload == null && fromBody) { + payload = fromBody; + } + if (Array.isArray(payload)) { + return payload; + } + if (payload && Array.isArray(payload.items)) { + return payload.items; + } + if (payload && Array.isArray(payload.list)) { + return payload.list; + } + return []; +} + +export function getUserPatientList(params) { + const { prefix } = getProfileContext(); + return profileRequest({ url: `${prefix}/list`, method: 'GET', data: params }); +} + +/** 会员管理:微信用户分页列表 */ +export function getWxUserList(params) { + const { prefix } = getProfileContext(); + return profileRequest({ url: `${prefix}/user-list`, method: 'GET', data: params }); +} + +/** 某微信用户下的就诊人列表 */ +export function getPatientListByUser(userId) { + const { prefix } = getProfileContext(); + return profileRequest({ + url: `${prefix}/patient-list-by-user`, + method: 'GET', + data: { user_id: userId }, + }); +} + +export function getUserPatientDetail(upId) { + const { prefix } = getProfileContext(); + return profileRequest({ + url: `${prefix}/detail`, + method: 'GET', + data: { up_id: upId }, + }); +} + +export function getUserPatientRegisterList(upId, params) { + const { prefix } = getProfileContext(); + return profileRequest({ + url: `${prefix}/register-list`, + method: 'GET', + data: { up_id: upId, ...params }, + }); +} + +/** 就诊人单次挂号详情(基本信息 + 选药 + 回答记录) */ +export function getUserPatientRegisterDetail(registerId) { + const { prefix } = getProfileContext(); + return profileRequest({ + url: `${prefix}/register-detail`, + method: 'GET', + data: { register_id: registerId }, + }); +} + +export function getUserPatientPrescriptionList(upId, params) { + const { prefix } = getProfileContext(); + return profileRequest({ + url: `${prefix}/prescription-list`, + method: 'GET', + data: { up_id: upId, ...params }, + }); +} + +export function getUserPatientOrderList(upId, params) { + const { prefix } = getProfileContext(); + return profileRequest({ + url: `${prefix}/order-list`, + method: 'GET', + data: { up_id: upId, ...params }, + }); +} diff --git a/common/js/index.js b/common/js/index.js index 7028e0f..e4ccbf0 100644 --- a/common/js/index.js +++ b/common/js/index.js @@ -12,6 +12,7 @@ export const config = { const arr = [ 'mobile', + 'call_mobile', 'express_mobile', 'doctor', 'accept_tel', @@ -23,6 +24,7 @@ const arr = [ ] // 敏感数据(解密后做脱敏;需与下方 switch 分支一致) +// 注意:call_mobile 只解密不脱敏,供回访拨号使用 const sensitiveData = [ 'id_card', 'idcard', diff --git a/components/doctor-login/doctor-login.vue b/components/doctor-login/doctor-login.vue index 10a220b..acfae87 100644 --- a/components/doctor-login/doctor-login.vue +++ b/components/doctor-login/doctor-login.vue @@ -34,6 +34,8 @@ - 微信一键登录 + 一键登录 @@ -221,6 +223,10 @@ export default { this.$toast('请输入手机号'); return; } + if (!this.form.password) { + this.$toast('请输入密码'); + return; + } if (!this.smsCode) { this.$toast('请输入验证码'); return; diff --git a/config/app-env.js b/config/app-env.js index 6068336..75abbb2 100644 --- a/config/app-env.js +++ b/config/app-env.js @@ -13,10 +13,19 @@ const DEV = { wsUrl: 'ws://127.0.0.1:12080/ws', }; +// const PROD = { +// legacyApiBase: 'https://app.xiaokang88.com/service/v1/', +// newApiBase: 'https://api.xiaokang88.com/api/doctor/', +// wsUrl: 'wss://api.ws.g.xiaokang88.com/ws', +// }; + +// const TEST = { const PROD = { - legacyApiBase: 'https://app.xiaokang88.com/service/v1/', - newApiBase: 'https://api.xiaokang88.com/api/doctor/', - wsUrl: 'wss://api.ws.g.xiaokang88.com/ws', + legacyApiBase: 'https://xk.test.saas.api-yii.nailaoyun.cn/service/v1/', + newApiBase: 'https://xk.test.saas.api.nailaoyun.cn/api/doctor/', + // wsUrl: 'wss://api.ws.g.xiaokang88.com/ws', + wsUrl: 'wss://xk.ws.nailaoyun.cn/ws', + // wsUrl: '', }; export function isDoctorWxDev() { diff --git a/pages.json b/pages.json index 0a735a8..72f3e25 100644 --- a/pages.json +++ b/pages.json @@ -441,7 +441,16 @@ }, { "path": "order/detail", - "style": { "navigationBarTitleText": "订单详情", "navigationStyle": "custom" } + "style": { + "navigationBarTitleText": "订单详情", + "navigationStyle": "custom", + "usingComponents": { + "order-price-percent-adjust-popup": "/subPackages/sub_business_shared/components/OrderPricePercentAdjustPopup" + }, + "componentPlaceholder": { + "order-price-percent-adjust-popup": "view" + } + } }, { "path": "order/trace/index", @@ -471,7 +480,11 @@ { "path": "withdrawal/settlement/detail", "style": { "navigationBarTitleText": "结算明细", "navigationStyle": "custom" } }, { "path": "withdrawal/account-change/index", "style": { "navigationBarTitleText": "资金变动", "navigationStyle": "custom" } }, { "path": "warehouse/index", "style": { "navigationBarTitleText": "仓库管理", "navigationStyle": "custom" } }, - { "path": "warehouse/detail", "style": { "navigationBarTitleText": "药品详情", "navigationStyle": "custom" } } + { "path": "warehouse/detail", "style": { "navigationBarTitleText": "药品详情", "navigationStyle": "custom" } }, + { "path": "user-patient/index", "style": { "navigationBarTitleText": "会员管理", "navigationStyle": "custom" } }, + { "path": "user-patient/patients", "style": { "navigationBarTitleText": "就诊人列表", "navigationStyle": "custom" } }, + { "path": "user-patient/detail", "style": { "navigationBarTitleText": "就诊人详情", "navigationStyle": "custom" } }, + { "path": "user-patient/register-detail", "style": { "navigationBarTitleText": "挂号详情", "navigationStyle": "custom" } } ] }, { "root": "subPackages/sub_platform_admin", diff --git a/pages/my/index.vue b/pages/my/index.vue index df293e3..f23efc5 100644 --- a/pages/my/index.vue +++ b/pages/my/index.vue @@ -76,7 +76,7 @@ - + @@ -183,6 +183,9 @@ if (parseInt(this.identity) === 2 && this.$refs.myPharmacist) { this.$refs.myPharmacist.refreshWxBind() } + if (parseInt(this.identity) > 2 && this.$refs.myService) { + this.$refs.myService.refreshWxBind() + } }, async getInfo() { let res = {} diff --git a/pages/my/my-pharmacist.vue b/pages/my/my-pharmacist.vue index 5d0f1ee..4bab351 100644 --- a/pages/my/my-pharmacist.vue +++ b/pages/my/my-pharmacist.vue @@ -18,31 +18,50 @@ - - - - - - + + + + + + + + + - - - - + + + + + + + + + 退出登录 @@ -85,4 +111,13 @@ background-color: #F9FAFB; padding: 16rpx; } + .wx-bind-wrap { + border-bottom: 1rpx solid #f0f0f0; + } + .account-switch-wrap { + padding-top: 8rpx; + } + .logout-wrap { + margin-bottom: 32rpx; + } diff --git a/pages/my/my-service.vue b/pages/my/my-service.vue index 3cee173..4400431 100644 --- a/pages/my/my-service.vue +++ b/pages/my/my-service.vue @@ -11,17 +11,19 @@ + - + + + + - - - + @@ -31,19 +33,35 @@ - + + + + + + 退出登录 + + diff --git a/subPackages/sub_business_shared/components/UserPatientDetail.vue b/subPackages/sub_business_shared/components/UserPatientDetail.vue new file mode 100644 index 0000000..b4427be --- /dev/null +++ b/subPackages/sub_business_shared/components/UserPatientDetail.vue @@ -0,0 +1,556 @@ + + + + + diff --git a/subPackages/sub_business_shared/order/components/ExpressTimeline.vue b/subPackages/sub_business_shared/order/components/ExpressTimeline.vue index 427f9a7..0d7aa14 100644 --- a/subPackages/sub_business_shared/order/components/ExpressTimeline.vue +++ b/subPackages/sub_business_shared/order/components/ExpressTimeline.vue @@ -1,14 +1,25 @@ diff --git a/subPackages/sub_workbench/prescription_v2/components/modals/ChineseMedicineModal.vue b/subPackages/sub_workbench/prescription_v2/components/modals/ChineseMedicineModal.vue index 2596f8a..37d73db 100644 --- a/subPackages/sub_workbench/prescription_v2/components/modals/ChineseMedicineModal.vue +++ b/subPackages/sub_workbench/prescription_v2/components/modals/ChineseMedicineModal.vue @@ -43,6 +43,7 @@ > {{ sel.drug_name || sel.name || '—' }} {{ String(sel.number || 0) }}{{ sel.unit && sel.unit.name ? sel.unit.name : 'g' }} + {{ getWayLabel(sel) }} @@ -70,6 +71,7 @@ + 多仓 + + + + + + + {{ getWayDisplayName(item) }} + + + + + + {{ w.name }} + + + @@ -223,6 +262,11 @@ export default { showDrugPrice: { type: Boolean, default: true + }, + /** 药材级先煎/后下选项(父页 drugUseList.drug_use_way) */ + drugUseWayList: { + type: Array, + default: () => [] } }, data() { @@ -237,13 +281,16 @@ export default { pageSize: 20, hasMore: false, // 常用克数(后端全局配置,每次打开抽屉拉取) - commonQuantities: [] + commonQuantities: [], + /** 当前展开煎法气泡的药材下标,-1 表示收起 */ + wayPanelDrugIndex: -1 }; }, watch: { value(newVal) { this.show = newVal; if (newVal) { + this.wayPanelDrugIndex = -1; this.selectedDrugs = this.mapSelectedDrugs(JSON.parse(JSON.stringify(this.currentDrugs))); this.fetchQuickGrams(); const kw = (this.initialSearchKey || '').trim(); @@ -256,10 +303,13 @@ export default { } else { this.resetList(); } + } else { + this.wayPanelDrugIndex = -1; } }, show(newVal) { if (!newVal) { + this.wayPanelDrugIndex = -1; this.$emit('input', false); } } @@ -334,6 +384,7 @@ export default { this.drugList = []; this.page = 1; this.hasMore = false; + this.wayPanelDrugIndex = -1; }, /** * 加载药品列表 @@ -388,17 +439,27 @@ export default { return matchByIndexId || matchByEntityId || matchByDrugId; }); this.$set(drug, '_quantity', selected ? selected.number : 0); + // 恢复已选煎法(先煎/后下) + const wayId = selected + ? Number(selected.way_id ?? selected.use_ways?.id ?? 0) + : Number(drug.way_id || (drug.drug && drug.drug.way_id) || 0); + this.$set(drug, '_way_id', wayId || 0); return drug; }); if (this.page > 1) { this.drugList = this.drugList.concat(merged); } else { + // 重新搜索/首屏加载后列表下标变化,收起煎法气泡 + this.wayPanelDrugIndex = -1; this.drugList = merged; } this.drugList.forEach(drug => { if (typeof drug._quantity === 'undefined') { this.$set(drug, '_quantity', 0); } + if (typeof drug._way_id === 'undefined') { + this.$set(drug, '_way_id', Number(drug.way_id || (drug.drug && drug.drug.way_id) || 0)); + } }); } catch (error) { console.error('获取药品列表失败:', error); @@ -420,6 +481,7 @@ export default { clearTimeout(this.searchTimer); this.searchTimer = setTimeout(() => { const name = (this.searchKey || '').trim(); + this.wayPanelDrugIndex = -1; if (!name) { this.page = 1; this.hasMore = false; @@ -529,6 +591,10 @@ export default { const indexId = drug.id; const entityId = (drug.drug && drug.drug.id) || drug.id; const wxKeyBase = indexId != null && indexId !== '' ? String(indexId) : String(entityId || ''); + const wayId = Number( + drug._way_id ?? (drug.drug && drug.drug.way_id) ?? drug.way_id ?? 0 + ); + const wayHit = (this.drugUseWayList || []).find((w) => Number(w.id) === wayId); return { index_id: indexId, id: entityId, @@ -536,12 +602,80 @@ export default { number: drug._quantity, price: drug.price || 0, buy_price: drug.buy_price ?? 0, - way_id: (drug.drug && drug.drug.way_id) || drug.way_id || 0, + way_id: wayId || 0, + use_ways: wayHit || null, select_number: 1, _wxKey: wxKeyBase ? `sch-${wxKeyBase}` : `sch${this.selectedDrugs.length}`, }; }, + /** + * 展示非默认煎法文案;煎服/0 不展示(chip)或用「煎服(默认)」在选择行 + */ + getWayLabel(drugOrSel) { + if (!drugOrSel) return ''; + const wayId = Number( + drugOrSel._way_id ?? drugOrSel.way_id ?? drugOrSel.use_ways?.id ?? 0 + ); + if (!wayId) return ''; + const hit = (this.drugUseWayList || []).find((w) => Number(w.id) === wayId); + const name = + (hit && hit.name) || + (drugOrSel.use_ways && drugOrSel.use_ways.name) || + ''; + if (!name || name === '煎服') return ''; + return name; + }, + + /** + * 煎法行展示文案:有先煎/后下等显示标签名,否则「煎服(默认)」 + */ + getWayDisplayName(drugOrSel) { + return this.getWayLabel(drugOrSel) || '煎服(默认)'; + }, + /** + * 判断气泡标签是否为当前药材选中煎法 + */ + isWayActive(drug, wayItem) { + if (!drug || !wayItem) return false; + const wayId = Number(drug._way_id ?? drug.way_id ?? 0); + return wayId > 0 && wayId === Number(wayItem.id); + }, + /** + * 展开/收起当前药材的煎法气泡卡片(同时只开一行) + */ + toggleWayPanel(event) { + const index = Number(event.currentTarget.dataset.index); + if (Number.isNaN(index)) return; + if (!(this.drugUseWayList || []).length) { + this.$toast('暂无煎法选项'); + return; + } + this.wayPanelDrugIndex = this.wayPanelDrugIndex === index ? -1 : index; + }, + /** + * 气泡标签点选煎法:写入 _way_id 并同步已选处方后收起 + */ + selectWayTag(event) { + const ds = event.currentTarget.dataset || {}; + const index = Number(ds.index); + const wayId = Number(ds.wayId ?? ds['way-id'] ?? 0); + const drug = this.drugList[index]; + if (!drug || Number.isNaN(index)) return; + const picked = (this.drugUseWayList || []).find((w) => Number(w.id) === wayId) || null; + this.$set(drug, '_way_id', wayId || 0); + this.$set(drug, 'way_id', wayId || 0); + const existIndex = this.findSelectedIndex(drug); + if (existIndex !== -1) { + this.$set(this.selectedDrugs[existIndex], 'way_id', wayId || 0); + this.$set(this.selectedDrugs[existIndex], 'use_ways', picked); + this.emitSelect(); + } else if (drug._quantity > 0) { + this.tryAutoAddDrug(drug, { showToast: false }); + } + this.wayPanelDrugIndex = -1; + }, + /** * emit select 事件,传出当前完整的 selectedDrugs(深拷贝) */ @@ -563,6 +697,11 @@ export default { const isNew = existIndex === -1; if (existIndex !== -1) { this.$set(this.selectedDrugs[existIndex], 'number', drug._quantity); + // 保持煎法与列表一致 + const wayId = Number(drug._way_id ?? drug.way_id ?? 0); + this.$set(this.selectedDrugs[existIndex], 'way_id', wayId); + const wayHit = (this.drugUseWayList || []).find((w) => Number(w.id) === wayId); + this.$set(this.selectedDrugs[existIndex], 'use_ways', wayHit || null); } else { this.selectedDrugs.push(this.buildSelectedItem(drug)); } @@ -818,6 +957,7 @@ export default { .color-title { color: #2A2E35; } .color-sub { color: #8A92A3; } .color-price { color: #F53F3F; } +.color-way { color: #5b8ff9; } .fs-26 { font-size: 26rpx; } .fs-28 { font-size: 28rpx; } .fs-32 { font-size: 32rpx; } @@ -904,7 +1044,48 @@ export default { font-size: 24rpx; color: #00A88A; margin-left: 8rpx; - white-space: nowrap; +} +.selected-chip-way { + font-size: 22rpx; + color: #5b8ff9; + margin-left: 8rpx; + font-weight: 500; +} +.way-block { + width: 100%; +} +.way-row { + padding: 8rpx 0; +} +.way-picker { + padding: 8rpx 16rpx; + background: #F7F8FA; + border-radius: 8rpx; + border: 1rpx solid #E2E8F0; +} +/* 煎法气泡卡片:标签直接点选,不限 ActionSheet 6 项 */ +.way-bubble-card { + margin-top: 12rpx; + padding: 20rpx; + background: #fff; + border-radius: 16rpx; + border: 1rpx solid #E2E8F0; + box-shadow: 0 8rpx 24rpx rgba(42, 46, 53, 0.1); + display: flex; + flex-wrap: wrap; + gap: 16rpx; +} +.way-bubble-tag { + padding: 10rpx 24rpx; + background-color: #F4F6F8; + color: #6C7380; + border-radius: 30rpx; + font-size: 24rpx; + line-height: 1.4; +} +.way-bubble-tag.active { + background-color: #00A88A; + color: #fff; } .selected-chip-close { @@ -1065,4 +1246,17 @@ export default { padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom)); background-color: #fff; } + +/* 配送仓绑定药品标识 */ +.multi-wh-tag { + flex-shrink: 0; + padding: 0 10rpx; + height: 32rpx; + line-height: 30rpx; + font-size: 20rpx; + color: #2b6de5; + background: rgba(43, 109, 229, 0.08); + border: 1rpx solid rgba(43, 109, 229, 0.45); + border-radius: 999rpx; +} \ No newline at end of file diff --git a/subPackages/sub_workbench/prescription_v2/components/modals/SimpleProductModal.vue b/subPackages/sub_workbench/prescription_v2/components/modals/SimpleProductModal.vue index 4269941..8f05552 100644 --- a/subPackages/sub_workbench/prescription_v2/components/modals/SimpleProductModal.vue +++ b/subPackages/sub_workbench/prescription_v2/components/modals/SimpleProductModal.vue @@ -44,7 +44,10 @@ > - {{item.drug.drug_name || item.drug.name}} + + {{item.drug.drug_name || item.drug.name}} + 多仓 + ¥{{parseFloat(item.price || 0).toFixed(2)}} @@ -357,4 +360,17 @@ export default { padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom)); background-color: #fff; } + +/* 配送仓绑定药品标识 */ +.multi-wh-tag { + flex-shrink: 0; + padding: 0 10rpx; + height: 32rpx; + line-height: 30rpx; + font-size: 20rpx; + color: #2b6de5; + background: rgba(43, 109, 229, 0.08); + border: 1rpx solid rgba(43, 109, 229, 0.45); + border-radius: 999rpx; +} \ No newline at end of file diff --git a/subPackages/sub_workbench/prescription_v2/components/modals/WarehouseSelectDrawer.vue b/subPackages/sub_workbench/prescription_v2/components/modals/WarehouseSelectDrawer.vue new file mode 100644 index 0000000..682f853 --- /dev/null +++ b/subPackages/sub_workbench/prescription_v2/components/modals/WarehouseSelectDrawer.vue @@ -0,0 +1,332 @@ + + + + + diff --git a/subPackages/sub_workbench/prescription_v2/components/modals/WesternMedicineModal.vue b/subPackages/sub_workbench/prescription_v2/components/modals/WesternMedicineModal.vue index 4faa0e6..823c0ff 100644 --- a/subPackages/sub_workbench/prescription_v2/components/modals/WesternMedicineModal.vue +++ b/subPackages/sub_workbench/prescription_v2/components/modals/WesternMedicineModal.vue @@ -46,7 +46,10 @@ > - {{item.drug.drug_name || item.drug.name}} + + {{item.drug.drug_name || item.drug.name}} + 多仓 + ¥{{parseFloat(item.price || 0).toFixed(2)}} @@ -390,4 +393,17 @@ export default { padding: 24rpx 32rpx calc(24rpx + env(safe-area-inset-bottom)); background-color: #fff; } + +/* 配送仓绑定药品标识 */ +.multi-wh-tag { + flex-shrink: 0; + padding: 0 10rpx; + height: 32rpx; + line-height: 30rpx; + font-size: 20rpx; + color: #2b6de5; + background: rgba(43, 109, 229, 0.08); + border: 1rpx solid rgba(43, 109, 229, 0.45); + border-radius: 999rpx; +} \ No newline at end of file diff --git a/subPackages/sub_workbench/prescription_v2/index.vue b/subPackages/sub_workbench/prescription_v2/index.vue index 7ba140b..330eb92 100644 --- a/subPackages/sub_workbench/prescription_v2/index.vue +++ b/subPackages/sub_workbench/prescription_v2/index.vue @@ -279,7 +279,13 @@ - 药材清单 (共{{currentDrugs.length}}味) + + 药材清单 (共{{currentDrugs.length}}味) + 先煎/后下请在「选择中药」中设置 +