소스 검색

重构开方: 病证治法 和 推导逻辑

cc12458 6 달 전
부모
커밋
b0b861e21c

+ 18 - 5
package-lock.json

@@ -2318,11 +2318,18 @@
       "dev": true
     },
     "axios": {
-      "version": "0.21.1",
-      "resolved": "https://r.cnpmjs.org/axios/download/axios-0.21.1.tgz",
-      "integrity": "sha1-IlY0gZYvTWvemnbVFu8OXTwJsrg=",
+      "version": "0.22.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-0.22.0.tgz",
+      "integrity": "sha512-Z0U3uhqQeg1oNcihswf4ZD57O3NrR1+ZXhxaROaWpDmsDTx7T2HNBV2ulBtie2hwJptu8UvgnJoK+BIqdzh/1w==",
       "requires": {
-        "follow-redirects": "^1.10.0"
+        "follow-redirects": "^1.14.4"
+      },
+      "dependencies": {
+        "follow-redirects": {
+          "version": "1.15.11",
+          "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+          "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ=="
+        }
       }
     },
     "babel-helper-vue-jsx-merge-props": {
@@ -5186,7 +5193,8 @@
     "follow-redirects": {
       "version": "1.14.1",
       "resolved": "https://registry.npmmirror.com/follow-redirects/download/follow-redirects-1.14.1.tgz",
-      "integrity": "sha1-2RFN7Qoc/dM04WTmZirQK/2R/0M="
+      "integrity": "sha1-2RFN7Qoc/dM04WTmZirQK/2R/0M=",
+      "dev": true
     },
     "for-in": {
       "version": "1.0.2",
@@ -11171,6 +11179,11 @@
       "integrity": "sha1-HuO8mhbsv1EYvjNLsV+cRvgvWCU=",
       "dev": true
     },
+    "vue2-teleport-polyfill": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/vue2-teleport-polyfill/-/vue2-teleport-polyfill-1.1.4.tgz",
+      "integrity": "sha512-mrPIwPoDdaLB4kk4DC4a+uRbkIf+pD92sEaOfqMDKMPELV4WfqGuf3xDV4/kHb4A7p3LpcGchH2fz3WA2k20zw=="
+    },
     "vuescroll": {
       "version": "4.17.3",
       "resolved": "https://r.cnpmjs.org/vuescroll/download/vuescroll-4.17.3.tgz",

+ 2 - 1
package.json

@@ -8,7 +8,7 @@
     "build:test": "vue-cli-service build --mode test"
   },
   "dependencies": {
-    "axios": "^0.21.1",
+    "axios": "^0.22.0",
     "core-js": "^3.6.5",
     "dayjs": "^1.11.13",
     "echarts": "^4.8.0",
@@ -17,6 +17,7 @@
     "vue": "^2.6.11",
     "vue-print-nb": "^1.7.4",
     "vue-router": "^3.2.0",
+    "vue2-teleport-polyfill": "^1.1.4",
     "vuescroll": "^4.17.3",
     "vuex": "^3.4.0"
   },

+ 12 - 3
src/api/knowledge.js

@@ -1,7 +1,10 @@
 import request from '@/utils/request1.js'
 import request1 from '@/utils/request.js'
 
-// 获取中医 病名
+/**
+ * 获取中医 病名
+ * @deprecated {@link getDiseaseListMethod}
+ */
 export function getCDiseaseName(data) {
     return request({
         url: '/basis/knowlib/diseaseQuery',
@@ -20,7 +23,10 @@ export function getXDiseaseName(data) {
 };
 
 
-// 获取中医证型
+/**
+ * 获取中医 证型
+ * @deprecated {@link getSymptomListMethod}
+ */
 export function getCCardType(data) {
     return request({
         url: '/basis/knowlib/symptomQuery',
@@ -293,7 +299,10 @@ export function getRationalMedForPlat(data) {
     })
 };
 
-// 推导处方接口
+/**
+ * 推导处方接口
+ * @deprecated {@link deducePrescriptionMethod}
+ */
 export function inferRecipe(data) {
     return request({
         url: '/basis/knowlib/treatmentScheme',

+ 307 - 0
src/api/prescription.js

@@ -0,0 +1,307 @@
+import http from '@/api/request';
+import {CC_Dosage2Basis} from '@/utils/medicine';
+import {getHerbalMedicineModel, getPrescriptionModel} from '@/model/prescription.model';
+
+export function getDiseaseListMethod(page = 1, size = 10, query = {}) {
+    return http({
+        url: `/basis/knowlib/diseaseQuery`,
+        method: 'post',
+        data: {
+            pageid: page,
+            pagesize: size,
+            serchtype: /* 1: 拼音码 2: 五笔码;搜索中文时可传空*/ /\w+/.test(query.keyword) ? '1' : '',
+            ...query,
+        },
+        meta: {share: true},
+    }).then(res => {
+        return {
+            total: res.data.TotalRecordCount,
+            list: res.data.diseases.map(item => ({id: item.disid, name: item.disname, description: item.content})),
+        };
+    }, () => { throw {message: `错误,请重试!`}; });
+}
+
+export function getSymptomListMethod(page = 1, size = 10, query = {}) {
+    return http({
+        url: `/basis/knowlib/symptomQuery`,
+        method: 'post',
+        data: {
+            pageid: page,
+            pagesize: size,
+            serchtype: /* 1: 拼音码 2: 五笔码;搜索中文时可传空*/ /\w+/.test(query.keyword) ? '1' : '',
+            ...query,
+        },
+        meta: {share: true},
+    }).then(res => {
+        if (res.code !== 0) throw {message: res.message || '未知错误'};
+        return {
+            total: res.data.TotalRecordCount,
+            list: res.data.sysptoms.map(item => ({
+                id: item.symid, name: item.symname,
+                recommend: !!item.isMatched, therapies: Array.isArray(item.therapys) ? item.therapys : [],
+            })),
+        };
+    }, () => { throw {message: `错误,请重试!`}; });
+}
+
+/**
+ * 推导处方接口
+ * @param {string} business 业务类型,多个用逗号分隔
+ *   - '1' 中药处方
+ *   - '2' 中药制剂
+ *   - '3' 适宜技术
+ *   示例: '1' 或 '1,2' 或 '1,2,3'
+ * @param {object} illness 疾病信息对象
+ * @param {string} illness.disease.id 疾病ID
+ * @param {string} illness.disease.name 疾病名称
+ * @param {string} illness.symptom.id 证型ID
+ * @param {string} illness.symptom.name 证型名称
+ * @param {null} illness.therapy.id 治法ID(当前为null)
+ * @param {string} illness.therapy.name 治法名称
+ * @returns {Promise<Object>} 返回一个 Promise,resolve 后得到可迭代的处方结果对象
+ * @returns {Promise<Object>} 返回对象结构:
+ *   - 对象实现了 Symbol.iterator,可以使用 for...of 遍历
+ *   - 对象包含以业务类型为键的属性('1', '2', '3'等)
+ *   - 每个属性的值为该业务类型对应的处方数组(合并了经验方和方案方)
+ *   - index 为首个有推荐处方的索引
+ * @returns {Promise<Array>} 返回对象[type] 每个业务类型对应的处方数组
+ * @example
+ * // 使用示例
+ * const result = await deducePrescriptionMethod('1,2,3', {
+ *   disease: { id: '123', name: '感冒' },
+ *   symptom: { id: '456', name: '风寒证' },
+ *   therapy: { id: null, name: '解表散寒' }
+ * });
+ *
+ * // 方式1: 直接访问属性
+ * const prescriptions1 = result['1']; // 中药处方数组
+ * const prescriptions2 = result['2']; // 中药制剂数组
+ * const prescriptions2 = result['3']; // 适宜技术数组
+ *
+ * // 方式2: 使用迭代器遍历(按 business 顺序)
+ * for (const prescriptions of result) {
+ *   console.log(prescriptions); // 依次输出每个类型的处方数组
+ * }
+ *
+ * // 方式3: 展开为数组(按 business 顺序)
+ * const allPrescriptions = [...result]; // 所有类型的处方数组
+ */
+export function deducePrescriptionMethod(business = '1,2,3', illness) {
+    const [_, prefix, special, suffix] = business.match(/^([^1]+)?(?:(1)(?:,|$))?([^1]*)?$/) || ['', business];
+    const request = (business) => {
+        if (!business) return {expList: [], schemes: []};
+        return http({
+            url: `/basis/knowlib/treatmentScheme`,
+            method: 'post',
+            data: {
+                businesstype: business.endsWith(',') ? business.slice(0, -1) : business,
+                disid: illness.disease.id,
+                symid: illness.symptom.id,
+                therapy: illness.therapy.name,
+            },
+        }).then(res => {
+            const {expList, schemes} = res.data;
+            return {schemes, expList: business === '1' ? {pres: expList, businesstype: '1'} : []};
+        });
+    };
+    return Promise.all([
+        request(special),
+        request(`${prefix}${suffix}`),
+    ]).then(([s, b]) => {
+        return {
+            expList: [s.expList, b.expList].flat(),
+            schemes: [s.schemes, b.schemes].flat(),
+        };
+    }).then(data => {
+        const {schemes, expList} = data;
+        const types = business.split(',');
+        const results = {
+            * [Symbol.iterator]() { for (const type of types) yield this[type]; },
+        };
+        for (const type of types) {
+            const e = expList.find(item => item.businesstype === type) || {pres: []};
+            const s = schemes.find(item => item.businesstype === type) || {pres: []};
+            results[type] = [e.pres, s.pres].flat(1).map(prescription => getPrescriptionModel(prescription, type));
+            if (results.index == null && results[type].length) results.index = types.findIndex(t => t === type);
+        }
+        return results;
+    }, () => { throw {message: `错误,请重试!`}; });
+}
+
+export function getPrescriptionMethod(data, type) {
+    // if (Array.isArray(data.items) && data.items.length) return data;
+    if (type == null) type = data.$type;
+    let request = Promise.reject('');
+    if (type === '1') {
+        if (data.preid) request = http.post(`/basis/knowlib/prescriptionInfoQuery`, {preid: data.preid}).then(res => {
+            try { Object.assign(res.data, {preid: data.preid}); } catch (_) {}
+            return res;
+        });
+        else if (data.pid) request = http.get(`/basis/expertexperienceMgr/${data.pid}`);
+    }
+
+    return request.then(
+        (res) => Object.assign(data, getPrescriptionModel(res.data, type)),
+        () => { throw {message: `错误,请重试!`}; },
+    );
+}
+
+/**
+ * 获取推导处方详情
+ * @deprecated
+ * @param data
+ * @return {Promise<*>}
+ */
+export async function deducePrescriptionByMethod(data) {
+
+    switch (data.type) {
+        case '1': {
+            const prescription = await deducePrescriptionBy1Method(data.id);
+            return Object.assign(data, prescription);
+        }
+        default:
+            return data;
+    }
+}
+
+/**
+ * 获取 中药处方 详情
+ * @param id
+ * @return {*}
+ */
+export function deducePrescriptionBy1Method(id) {
+    return http({
+        url: `/basis/knowlib/prescriptionInfoQuery`,
+        method: 'post',
+        data: {preid: id},
+    }).then(res => {
+        if (!Array.isArray(res.data.items)) throw {message: `处方 ${id} 不存在药品`};
+        return {
+            ...res.data, type: '1',
+            id, name: res.data.prename,
+        };
+    }, () => { throw {message: `错误,请重试!`}; });
+}
+
+/**
+ * 转方 中药处方 - 方剂
+ * @param medicines 药品
+ * @param {object} platform 平台数据
+ * @param {string} platform.pharmacy 平台药房 - value@type
+ * @return {*}
+ */
+export function transformPlatformMedicinesMethod(medicines, platform) {
+    const [value, type] = platform.pharmacy.split('@');
+    return http({
+        url: `/test/outpatient/changePre`,
+        method: 'post',
+        data: {
+            pharmacyid: value, type,
+            drugIds: medicines.map(medicine => `${medicine.$id}&${medicine.$dosage || 0}&${medicine.$usage || ''}`),
+        },
+        meta: {share: true},
+    }).then(res => {
+        if (Array.isArray(res.data)) for (let item of res.data) {
+            if (typeof item === 'string') item = {$id: item};
+            else item = getHerbalMedicineModel(item);
+            const medicine = medicines.find(medicine => medicine.$id === item.$raw);
+            if (medicine) Object.assign(medicine, item);
+        }
+        return medicines;
+    }, () => { throw {message: `错误,请重试!`}; });
+}
+
+/**
+ * 切换 药房药品
+ * @param medicines 药品
+ * @param {object} platform 平台数据
+ * @param {string} platform.pharmacy 平台药房 - value@type
+ * @param {string} [platform.last = ''] 上次平台药房 - value@type
+ */
+export function togglePharmacyMedicinesMethod(medicines, platform) {
+    const [value, type] = platform.pharmacy.split('@');
+    const [oldValue = value, oldType = type] = (platform.last || '').split('@');
+    return http({
+        url: `/test/outpatient/changeDrug`,
+        method: 'post',
+        data: {
+            pharmacyid: value, type, oldType,
+            drugIds: medicines.filter(row => !row.editing).map(medicine => `${medicine.$id}&${medicine.$dosage || 0}&${medicine.$usage || /* 传空会导致剂量归 1 */ ' '}`),
+        },
+        meta: {share: true},
+    }).then(res => {
+        if (Array.isArray(res.data)) for (let item of res.data) {
+            if (typeof item === 'string') item = {$id: item};
+            else item = getHerbalMedicineModel(item);
+            const medicine = medicines.find(medicine => medicine.$id === item.$raw);
+            if (medicine) Object.assign(medicine, item);
+        }
+        return medicines;
+    }, () => { throw {message: `错误,请重试!`}; });
+}
+
+let checkReasonableSafeMedicinesController;
+
+/**
+ * 检测合理安全用药
+ * @param medicines 药品
+ * @param {object} platform 平台数据
+ * @param {string} platform.organization 平台机构 - ID
+ */
+export function checkReasonableSafeMedicinesMethod(medicines, platform = {}) {
+    if (checkReasonableSafeMedicinesController) checkReasonableSafeMedicinesController.abort();
+    checkReasonableSafeMedicinesController = new AbortController();
+    const request = medicines.map((medicine, index, medicines) => {
+        if (medicine.editing) return medicine;
+        return http({
+            // pid ? 平台药品 : 基础药品
+            url: medicine.pid ? `/basis/knowlib/getMatInfo1` : `/basis/knowlib/getMatInfoByPlat1`,
+            method: 'post',
+            data: {
+                matID: medicine.$id, orgId: platform.organization,
+                matIds: medicines.filter(row => !row.editing).map(medicine => medicine.$id),
+            },
+            signal: checkReasonableSafeMedicinesController.signal,
+        }).then(res => {
+            for (const [key, value] of Object.entries(res.data)) if (!value) delete res.data[key];
+            Object.assign(medicine, res.data, {
+                $dosage: medicine.$dosage || res.data.dose,
+                $unit: medicine.$unit || res.data.unit,
+                $usage: medicine.$usage || res.data.useage,
+            });
+            medicine.$dosageRange = `${medicine['matmindosage']}-${medicine['matmaxdosage']}`;
+            return medicine;
+        }, () => medicine);
+    });
+    return Promise.all(request);
+}
+
+/**
+ *
+ * @param {number} page 页码
+ * @param {number} size 页数
+ * @param {object} query 查询参数
+ * @param {string} query.keyword 查询 - 关键词
+ * @param {string} query.pharmacy 平台药房 - value@type
+ * @return {*}
+ */
+export function searchMedicinesMethod(page, size, query) {
+    const [value, type] = query.pharmacy.split('@');
+    return http({
+        url: `/test/outpatient/selectDrug`,
+        method: 'post',
+        data: {
+            pharmacyid: value, type,
+            key: query.keyword,
+        },
+        params: {page, limit: size},
+        meta: {share: true},
+    }).then(res => {
+        return {
+            total: res.data.TotalRecordCount, list: res.data.Items.map(item => Object.assign(item, {
+                $id: item.pid,
+                $name: item.ypmc,
+            })),
+        };
+    }, () => { throw {message: `错误,请重试!`}; });
+}

+ 83 - 0
src/api/request.js

@@ -0,0 +1,83 @@
+import axios from 'axios';
+import Vue from 'vue';
+
+const ignoreResponseTransformMatch = [
+    '/pre_interface/V5.0/insertBasicInfo',
+    '/pre_interface/V5.0/getPreItem',
+    '/pre_interface/V5.0/setStatus',
+    '/captchaImage',
+];
+
+const magicCode = [-5, -9, -8];
+
+const shareRequest = new Map();
+
+const http = axios.create({
+    baseURL: process.env.VUE_APP_BASE_API,
+    withCredentials: true,
+    timeout: 60 * 1000,
+});
+
+http.interceptors.request.use(config => {
+    const token = localStorage.getItem('token');
+    if (token) config.headers.Authorization = token;
+    if (config['meta'] && config['meta'].share && !config.signal) {
+        const key = config.url;
+        let controller = shareRequest.get(key);
+        if (controller) controller.abort();
+        shareRequest.set(key, controller = new AbortController());
+        config.signal = controller.signal;
+    }
+    return config;
+});
+
+http.interceptors.response.use(
+    response => {
+        const res = response.data;
+        // 设置 cookie
+        if (response.headers['access-control-expose-headers']) {
+            const cookie = response.headers['access-control-expose-headers'].split(';')[0];
+            if (cookie) document.cookie = cookie;
+        }
+        // 直接返回响应数据
+        try {
+            const {url, meta = {}} = response.config;
+            if (meta.share && shareRequest.has(url)) shareRequest.delete(url);
+            if (ignoreResponseTransformMatch.includes(url) || meta.ignoreResponseTransform) return res;
+        } catch (e) {}
+
+        const {
+            ResultCode = 0, ResultInfo, Data,
+            code = ResultCode, message = ResultInfo, data = Data,
+        } = res;
+        if (code === 0) return {code, data, message};
+        try {
+            if (magicCode.includes(code)) {
+                Vue.prototype.$router1.replace('/');
+                throw message;
+            } else if (code === 401) {
+                const isAllow = sessionStorage.getItem('isAllow') || 'true';
+                if (isAllow) Vue.prototype.$router1.replace('/');
+                throw `登录过期,请重新登录`;
+            } else {
+                throw message || `错误,请重试!`;
+            }
+        } catch (error) {
+            Vue.prototype.$message({
+                message: error,
+                type: 'error',
+                showClose: true,
+            });
+        }
+        return Promise.reject(new Error(message || `错误,请重试!`));
+    },
+    error => {
+        if (error.message !== 'canceled') Vue.prototype.$message({
+            message: error.message,
+            type: 'error',
+        });
+        return Promise.reject(error);
+    },
+);
+
+export default http;

BIN
src/assets/del.png


파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 244 - 574
src/components/ChineseMedicine.vue


+ 1 - 1
src/components/SuitScience.vue

@@ -1,6 +1,6 @@
 // 适宜技术处方
 <template>
-  <div>
+  <div id="suitScience">
     <!-- 中成药方 和适宜技术处方 -->
     <div class="chinese_medicine">
       <div class="flex flex-row-right">

+ 66 - 232
src/components/TCMDiagnosis.vue

@@ -7,31 +7,9 @@
         <span>病名:</span>
       </div>
       <div class="value">
-        <el-popover placement="bottom" width="180" trigger="focus" :close-delay="100">
-          <el-input
-            :class="{invalid: name && (!zy_dise_id || invalid_dis)}"
-            :size="size"
-            slot="reference"
-            :placeholder="key1?key1:'中医病名'"
-            v-model="key1"
-            ref="zybm"
-            id="zybm"
-            @input="getOriginNameData(key1)"
-          >
-            <div slot="suffix" class="suffix">
-              <i class="el-icon-arrow-down" v-if="!key1"></i>
-              <i class="el-icon-circle-close" v-else @click="clearBm"></i>
-            </div>
-          </el-input>
-          <ul class="option-list">
-            <li
-              v-for="(item,index) in cDiseaseNameL"
-              :key="index"
-              :class="[zy_dise_id==item.disid?'active':'']"
-              @click="handleBm(item)"
-            >{{ item.disname }}</li>
-          </ul>
-        </el-popover>
+        <SearchSelect ref="disease" placeholder="中医病名" :remote="getDiseaseListMethod"
+                      :value="disease" @update:value="updateDisease"
+        />
       </div>
     </div>
     <!-- 证型 -->
@@ -41,30 +19,14 @@
         <span>证型:</span>
       </div>
       <div class="value">
-        <el-popover placement="bottom" width="180" trigger="focus" :close-delay="100">
-          <el-input
-            :size="size" :class="{invalid: syndrome && invalid_symptom}"
-            slot="reference"
-            :placeholder="key2?key2:'中医证型'"
-            v-model="key2"
-            ref="zhengxing"
-            id="zyzx"
-            @input="getOriginZxData(key2)"
-          >
-            <div slot="suffix" class="suffix">
-              <i class="el-icon-arrow-down" v-if="!key2"></i>
-              <i class="el-icon-circle-close" v-else @click="clearZx"></i>
-            </div>
-          </el-input>
-          <ul class="option-list">
-            <li
-              v-for="(item,index) in cCardTypeL"
-              :key="index"
-              :class="[zhengxingid==item.symid?'active':'',item.isMatched?'matched':'']"
-              @click="handleZx(item)"
-            >{{ item.symname }}</li>
-          </ul>
-        </el-popover>
+        <SearchSelect ref="symptom" placeholder="中医证型" lazy :query="{ disid: disease.id }"
+                      :remote="getSymptomListMethod"
+                      :value="symptom" @update:value="updateSymptom"
+        >
+          <template #item="{ label, item }">
+            <div :class="{recommend: item.recommend}">{{ label }}</div>
+          </template>
+        </SearchSelect>
       </div>
     </div>
     <!-- 治法 -->
@@ -74,23 +36,21 @@
         <span>治法:</span>
       </div>
       <div class="value">
-        <el-select :size="size" v-model="therapy" ref="zhifa" :automatic-dropdown="true">
-          <el-option v-for="(item,index) in therapyList" :key="index" :value="item"></el-option>
-        </el-select>
+        <SearchSelect ref="therapy" :options="therapies"
+                      :value="therapy" @update:value="updateTherapy"
+        />
       </div>
     </div>
   </div>
 </template>
 <script>
-import {
-  getCDiseaseName,
-  getXDiseaseName,
-  getCCardType
-} from "@/api/knowledge.js";
 import {addDiagnosisData2} from '@/api/diagnosis.js';
 import { addRecipeFrom } from "@/api/dataAnalysis.js";
-import { mapGetters } from "vuex";
+import {getDiseaseListMethod, getSymptomListMethod} from '@/api/prescription';
+import {mapGetters, mapMutations, mapState} from 'vuex';
+import SearchSelect from '@/components/ui/SearchSelect.vue';
 export default {
+  components: {SearchSelect},
   props: {
     title: {
       type: String,
@@ -113,6 +73,9 @@ export default {
   },
   data() {
     return {
+      therapies: [],
+
+
       invalid_dis: false,
       invalid_symptom: false,
 
@@ -122,7 +85,7 @@ export default {
       syndrome: "", // 证型
       zhengxingid: "", // 证型id
 
-      therapy: "", // 治法
+      therapy2: "", // 治法
 
       cDiseaseNameL: [], // 病名列表
       cCardTypeL: [], // 证型列表
@@ -136,46 +99,12 @@ export default {
       isEnter2: false // 是否进入证型输入宽
     };
   },
-  mounted() {
-    this.getNamemedData();
-    this.getCardType();
-
-    let _this = this;
-    document.getElementById("zybm").addEventListener("keydown", function(e) {
-      if (e.key === "Enter") {
-        _this.handleBmEnter();
-      }
-    });
-    document.getElementById("zyzx").addEventListener("keydown", function(e) {
-      if (e.key === "Enter") {
-        _this.handleZxEnter();
-      }
-    });
-  },
   watch: {
-    key1: {
-      handler: function() {
-        if (this.key1 == "") {
-          this.invalid_dis = false;
-          this.zy_dise_id = "";
-          this.name = "";
-        }
-      }
-    },
-    key2: {
-      handler: function() {
-        if (this.key2 == "") {
-          this.invalid_symptom = false;
-          this.syndrome = "";
-          this.zhengxingid = "";
-        }
-      }
-    },
     patiensMsg: {
       immediate: true,
       deep: true,
       handler: function() {
-        if (this.isInit) return;
+        /*if (this.isInit) return;
         if (!this.patiensMsg) return;
         if (Object.keys(this.patiensMsg).length === 0) return;
         // 中医诊断
@@ -190,23 +119,41 @@ export default {
         this.key2 = this.syndrome || "";
         this.getNamemedData(this.name, "");
         this.getCardType(this.syndrome, "");
-        this.isInit = true;
+        this.isInit = true;*/
       }
     }
   },
   computed: {
+    ...mapState('session', {
+      disease: state => state['illness'].disease,
+      symptom: state => state['illness'].symptom,
+      therapy: state => state['illness'].therapy,
+    }),
     ...mapGetters(["getPatiensInfo", "getOutpatientDiagnosis"])
   },
   methods: {
-    // 病名enter事件
-    handleBmEnter() {
-      if (this.cDiseaseNameL.length == 0) return;
-      this.handleBm(this.cDiseaseNameL[0]);
+    getDiseaseListMethod,
+    getSymptomListMethod,
+    updateDisease(value) {
+      if (!value) {
+        this.updateSymptom();
+        value = {id: null, name: ''};
+      }
+      this.$store.commit('session/disease', value);
+      this.$refs.symptom.reset(!!value.id);
+    },
+    updateSymptom(value) {
+      if (!value) {
+        this.updateTherapy();
+        value = {id: null, name: '', therapies: []};
+      }
+      this.$store.commit('session/symptom', value);
+      this.therapies = value.therapies.map((name, index) => ({id: name, name}));
+      this.$refs.therapy.reset(!!value.id);
     },
-    // 症行enter事件
-    handleZxEnter() {
-      if (this.cCardTypeL.length == 0) return;
-      this.handleZx(this.cCardTypeL[0]);
+    updateTherapy(value) {
+      if (!value) value = {id: null, name: ''};
+      this.$store.commit('session/therapy', value);
     },
     // 获取中医诊断信息
     getDiamsg() {
@@ -214,20 +161,20 @@ export default {
       return {
         disid: this.zy_dise_id,
         symptomid: this.zhengxingid,
-        treatment: this.therapy
+        treatment: this.therapy2
       };
     },
     // 获取内容参数
     getParams() {
       return {
         // 病名及id
-        namemedicine: this.name,
-        disid: this.zy_dise_id,
+        namemedicine: this.disease.name,
+        disid: this.disease.id,
         //   证型及id
-        symptomid: this.zhengxingid,
-        syndrometypes: this.syndrome,
+        symptomid: this.symptom.id,
+        syndrometypes: this.symptom.name,
         //   治法
-        treatment: this.therapy
+        treatment: this.therapy.name
       };
     },
     // 赋值内容参数
@@ -236,127 +183,12 @@ export default {
       this.zy_dise_id = data.disid || "";
       this.zhengxingid = data.symptomid || "";
       this.syndrome = data.syndrometypes || "";
-      this.therapy = data.treatment || "";
+      this.therapy2 = data.treatment || "";
       this.key1 = this.name || "";
       this.key2 = this.syndrome || "";
-      this.getNamemedData(this.name, "");
-      this.getCardType(this.syndrome, "");
 
       this.$forceUpdate();
     },
-    // 远程搜索病名
-    getOriginNameData(e) {
-      let pinyin = /^[A-Za-z]+$/g;
-      if (pinyin.test(e)) {
-        // 拼音
-        this.getNamemedData(e, "1");
-      } else {
-        // 中文
-        this.getNamemedData(e, "");
-      }
-    },
-    // 搜索证型
-    getOriginZxData(e) {
-      let pinyin = /^[A-Za-z]+$/g;
-      if (pinyin.test(e)) {
-        // 拼音
-        this.getCardType(e, "1");
-      } else {
-        // 中文
-        this.getCardType(e, "");
-      }
-    },
-    // 病名选中
-    handleBm(item) {
-      this.invalid_dis = false
-      this.zy_dise_id = item.disid;
-      this.name = item.disname;
-      this.key1 = item.disname;
-      this.$refs.zybm.blur();
-
-      this.key2 = '';
-      this.syndrome = "";
-      this.zhengxingid = "";
-      this.therapy = "";
-
-      if (this.name === "") return;
-      // this.getCCardType(this.zy_dise_id);
-      this.getCardType();
-      setTimeout(() => {
-        this.$refs.zhengxing.focus();
-      }, 0);
-    },
-    // 清空病名
-    clearBm() {
-      this.name = "";
-      this.zy_dise_id = "";
-      this.syndrome = "";
-      this.zhengxingid = "";
-      this.therapy = "";
-      this.key1 = "";
-      this.key2 = "";
-    },
-    // 证型选中\
-    handleZx(item) {
-      this.invalid_dis = false
-      this.syndrome = item.symname;
-      this.key2 = item.symname;
-      this.zhengxingid = item.symid;
-      this.$refs.zhengxing.blur();
-
-      this.therapy = "";
-      this.therapyList = item.therapys;
-
-      setTimeout(() => {
-        this.$refs.zhifa.focus();
-      }, 0);
-    },
-    // 清空证型
-    clearZx() {
-      this.syndrome = "";
-      this.zhengxingid = "";
-      this.therapy = "";
-
-      this.key2 = "";
-    },
-    //获取病名数据
-    async getNamemedData(key = "", serchtype = "") {
-      /*
-      serchtype:搜索类型,1为拼音码,2为五笔码,搜索中文时可传空
-      */
-      let params = {
-        pageid: 1,
-        pagesize: 9999,
-        keyword: key,
-        serchtype
-      };
-      let res = await getCDiseaseName(params);
-      if (res.code == 0) {
-        this.cDiseaseNameL = res.data.diseases;
-      }
-    },
-    // 获取证型数据
-    async getCardType(key = "", serchtype = "") {
-      let params = {
-        pageid: 1,
-        pagesize: 20000,
-        keyword: key,
-        serchtype,
-        disid: this.zy_dise_id
-      };
-      let res = await getCCardType(params);
-      if (res.code == 0) {
-        this.cCardTypeL = res.data.sysptoms;
-        setTimeout(() => {
-          this.cCardTypeL.map(item => {
-            if (item.symid == this.zhengxingid) {
-              // console.log(item, "dayin item");
-              this.therapyList = item.therapys;
-            }
-          });
-        }, 200);
-      }
-    },
     // 新增处方来源统计
     async addRecipeFrom() {
       let res = await addRecipeFrom({
@@ -367,20 +199,16 @@ export default {
     async saveDiagnosisData() {
       let params = {
         mainDiagnosis: {
-          disid: this.zy_dise_id,
-          symptomid: this.zhengxingid,
-          treatment: this.therapy,
+          ...this.getParams(),
           maindiagnosis: "0",
           recordsid: this.getPatiensInfo.pid,
           pid: this.patiensMsg.maindiagnosis.pid,
-          namemedicine: this.name,
-          syndrometypes: this.syndrome,
           zhengxing: ""
         }
       };
       try {
-        if (!this.name) throw {message: `请选择${this.title}病名`};
-        if (!this.zy_dise_id) throw {message: `当前疾病编码与医保不匹配,请更换中医病名等诊断信息!`};
+        if (!params.mainDiagnosis.namemedicine) throw {message: `请选择${this.title}病名`};
+        if (!params.mainDiagnosis.disid) throw {message: `当前疾病编码与医保不匹配,请更换中医病名等诊断信息!`};
         let {mainDiagnosis} = await addDiagnosisData2(params);
 
         this.invalid_dis = !mainDiagnosis.disid;
@@ -402,6 +230,12 @@ export default {
 };
 </script>
 <style scoped lang="scss">
+.recommend {
+  color: #111;
+  background: rgba($color: #5386f6, $alpha: 0.4);
+}
+
+
 .el-input.invalid::v-deep {
   input {
     color: #ff0000;

+ 405 - 0
src/components/ui/SearchSelect.vue

@@ -0,0 +1,405 @@
+<script>
+export default {
+  name: 'SearchSelect',
+  model: {
+    prop: 'value',
+    event: 'update:value',
+  },
+  props: {
+    open: Boolean,
+    readonly: Boolean,
+    clickable: Boolean,
+
+    value: {type: String | Array | Object},
+    options: {type: Array, default: () => []},
+    placeholder: {type: String, default: '请选择...'},
+    fields: {type: Object, default: () => ({})},
+    columns: Array,
+    lazy: Boolean,
+    strict: Boolean,
+    remote: Function,
+    transform: Function,
+    query: {type: Object, default: () => ({})},
+    pageSize: {type: Number, default: 10},
+    getController: {type: Function, default: () => new AbortController()},
+  },
+  data() {
+    return {
+      visible: false,
+      pageNumber: 1,
+      total: 0,
+      list: [],
+
+      inputValue: '',
+      lastValue: '',
+      keyword: '',
+      lastKeyWord: '',
+      searchCount: 0,
+      pagination: true,
+
+      active: '',
+      selected: '',
+
+      _visibleTimer: 0,
+    };
+  },
+  computed: {
+    _fields() {
+      return Object.assign({
+        label: 'name',
+        value: 'id',
+        search: 'keyword',
+      }, this.fields);
+    },
+    data() { return this.list.length ? this.list : this.options; },
+    length() { return this.data.length; },
+    searching() { return this.searchCount > 0; },
+  },
+  watch: {
+    open: {
+      immediate: true,
+      handler(value, oldValue) {
+        if (value === oldValue) return;
+        if (value) this.onOpenChange(value);
+        else if (oldValue === false) this.recover();
+      },
+    },
+    value: {
+      immediate: true,
+      deep: false,
+      async handler(model, oldValue) {
+        if (!model) this.inputValue = this.keyword = '';
+        else if (typeof model === 'string') {
+          if (model === oldValue) this.inputValue = model;
+          else {
+            const options = await this.search(model);
+            const index = options.findIndex(option => option[this._fields.label] === model);
+            if (index !== -1) this.onSelectChange(options[index], index);
+            else this.$emit('update:value', null);
+          }
+        } else if (typeof model === 'object') {
+          try { if (model[this._fields.value] === oldValue[this._fields.value]) return; } catch (error) {}
+          if (this.strict) {
+            const options = await this.search(model[this._fields.label]);
+            const index = options.findIndex(option => option[this._fields.value] === model[this._fields.value]);
+            if (index !== -1) this.onSelectChange(options[index], index);
+            else this.$emit('update:value', null);
+          } else {
+            this.inputValue = model[this._fields.label];
+            this.selected = model[this._fields.value];
+          }
+        }
+      },
+    },
+  },
+  methods: {
+    async onOpenChange(value) {
+      this.visible = value;
+      if (value) {
+        this.lastValue = this.inputValue || '';
+        this.inputValue = this.lastKeyWord = this.keyword;
+        if (this.lazy && !this.length) await this.search();
+        if (this.selected) this.$nextTick(() => { this.scroll(this.selected, 'center'); });
+        this.$emit('update:open', value);
+      } else {}
+    },
+    onInputChange(value) {
+      this.inputValue = value;
+      this.pageNumber = 1;
+      this.search();
+    },
+    onInputClear() {
+      if (!this.visible) {
+        this.selected = '';
+        this.$emit('update:value', null);
+      }
+    },
+    onActiveChange(step, value) {
+      if (value == null) value = this.active;
+      value += step;
+      this.active = Math.max(0, Math.min(this.length - 1, value));
+      try { if (step !== 0) this.scroll(this.data[this.active][this._fields.value], step < 0 ? 'start' : 'end'); } catch (error) {}
+    },
+    updateTableRowStyle({row, rowIndex: index}) {
+      const active = index === this.active ? 'option-active' : '';
+      const selected = row[this._fields.value] === this.selected ? 'option-selected' : '';
+      return `${active} ${selected}`;
+    },
+    onTurnChange(step, value) {
+      if (value == null) value = this.pageNumber;
+      value += step;
+      const old = this.pageNumber;
+      this.pageNumber = Math.max(1, Math.min(Math.ceil(this.total / this.pageSize), value));
+      if (old !== this.pageNumber) this.search();
+    },
+    onSelectChange(option, index) {
+      if (index == null) index = this.active;
+      else this.active = index;
+      if (option == null) option = this.data[index];
+      this.selected = option[this._fields.value];
+      this.inputValue = option[this._fields.label];
+      this.$emit('update:value', option);
+      this._visibleTimer = setTimeout(() => { this.visible = false; }, 100);
+    },
+    async search(keyword) {
+      if (typeof this.remote !== 'function') return;
+      this.searchCount = Math.max(1, this.searchCount + 1);
+      try {
+        let data = await this.remote(
+            this.pageNumber,
+            this.pageSize,
+            {...this.query, [this._fields.search]: keyword || this.inputValue || this.lastValue || ''},
+        );
+        if (typeof this.transform === 'function') data = await this.transform(data);
+        this.total = data.total;
+        this.list = data.list;
+        this.active = 0;
+        this.keyword = keyword || this.inputValue || this.lastValue || '';
+        if (this.pageNumber === 1 && this.list.length > this.pageSize) this.pagination = false;
+        const active = this.list[this.active][this._fields.value];
+        this.$nextTick(() => { this.scroll(active, 'start'); });
+      } catch (error) {
+        console.log(error);
+      }
+      this.searchCount -= 1;
+      if (this.visible) this.$nextTick(() => { try { this.$refs.input.focus(); } catch (error) {} });
+
+      return this.list;
+    },
+    scroll(id, block = 'center') {
+      let element = this.$refs.list;
+      try {
+        if (element && element['_isVue']) {
+          const index = this.data.findIndex(option => option[this._fields.value] === id);
+          const parent = element['$el'].querySelector(`.el-table__body-wrapper`);
+          element = parent.querySelector('tbody').children.item(index);
+
+          const {top: offset, height} = parent.getBoundingClientRect();
+          const {top, bottom} = element.getBoundingClientRect();
+
+          let diff = 0;
+          if (block === 'end') diff = height + offset - bottom;
+          else if (block === 'start') diff = top - offset;
+
+          if (diff > 0) return;
+        } else {
+          element = element.querySelector(`[data-value="${id}"]`);
+        }
+        element.scrollIntoView({container: 'nearest', behavior: 'smooth', block: block});
+      } catch (error) {}
+    },
+    recover() {
+      const index = this.data.findIndex(option => option[this._fields.value] === this.selected);
+      this.inputValue = index === -1 ? this.lastValue : this.data[index][this._fields.label] || '';
+      this.lastValue = '';
+      this.onActiveChange(0, index);
+      this.$emit('update:open', false);
+      this.$nextTick(() => { try { this.$refs.input.blur(); } catch (error) {} });
+    },
+    reset(searchOrValue = false, search = false) {
+      if (typeof searchOrValue === 'boolean') [search, searchOrValue] = [searchOrValue, void 0];
+      if (typeof searchOrValue === 'object') {
+        this.inputValue = searchOrValue[this._fields.label];
+        this.selected = searchOrValue[this._fields.value];
+      } else {
+        this.list = [];
+        this.pageNumber = 1;
+        this.inputValue = this.lastKeyWord = this.keyword = '';
+      }
+      if (search) {
+        this.visible = true;
+        if (!this.lazy) this.search();
+      }
+    },
+    focus() {
+      clearTimeout(this._visibleTimer);
+      try { this.$refs.input.focus(); } catch (e) {}
+    },
+    blur() {
+      try { this.$refs.input.blur(); } catch (e) {}
+      this.visible = false;
+    },
+  },
+  mounted() {
+    if (!this.lazy && typeof this.value !== 'string') this.search();
+  },
+};
+</script>
+
+<template>
+  <el-popover
+      v-model="visible" trigger="focus"
+      popper-class="search-select-popover-wrapper" placement="bottom-start"
+      @show="onOpenChange(true)" @hide="onOpenChange(false)"
+      @after-leave="recover()"
+  >
+    <template #reference>
+      <el-input ref="input"
+                class="input-wrapper" :readonly="readonly"
+                :class="{initial: lastKeyWord === inputValue, valid: !!lastValue, clickable: clickable && !visible}"
+                :placeholder="lastValue || placeholder" size="mini" :clearable="!visible && !clickable"
+                v-model.trim="inputValue" @input="onInputChange"
+                @clear="onInputClear()"
+                @keydown.enter.native="onSelectChange()" @keydown.esc.native="onOpenChange(false)"
+                @keydown.down.native="onActiveChange(1)" @keydown.up.native="onActiveChange(-1)"
+                @keydown.left.shift.native="onTurnChange(-1)" @keydown.right.shift.native="onTurnChange(1)"
+      >
+        <i v-if="searching" slot="suffix" class="el-input__icon el-icon-loading" style="color: #5386F6"></i>
+      </el-input>
+    </template>
+    <template #default>
+      <el-table ref="list" v-if="columns" v-loading="searching && !length" class="table-wrapper" max-height="400"
+                border size="mini"
+                :data="data"
+                :row-key="_fields.value" @row-click="onSelectChange($event)"
+                :row-class-name="updateTableRowStyle"
+      >
+        <el-table-column
+            v-for="column in columns" :key="column.title"
+            :prop="column.data" :label="column.title"
+            show-overflow-tooltip
+            v-bind="column"/>
+        <template #empty>
+          <el-empty v-if="!length" v-loading="searching" :image-size="60"
+                    :description="searching ? '查询中...' : '无查询结果'"
+          />
+        </template>
+      </el-table>
+      <template v-else>
+        <div ref="list" class="list-wrapper">
+          <div
+              v-for="(option, index) in data" :key="option[_fields.value]"
+              class="item-wrapper" :data-value="option[_fields.value]"
+              :class="{'option-active': index === active, 'option-selected': option[_fields.value] === selected}"
+              @click="onSelectChange(option, index)"
+              @mouseenter="onActiveChange(0, index)"
+          >
+            <slot
+                name="item"
+                :label="option[_fields.label]" :value="option[_fields.value]" :item="option"
+            >
+              <div>{{ option[_fields.label] }}</div>
+            </slot>
+          </div>
+        </div>
+        <el-empty v-if="!length" v-loading="searching" :image-size="60"
+                  :description="searching ? '查询中...' : '无查询结果'"
+        />
+      </template>
+      <div class="pagination-wrapper">
+        <el-pagination
+            v-if="!!total && pagination"
+            small layout="total,prev, pager, next, slot" :pager-count="5" hide-on-single-page
+            :total="total" :page-size.sync="pageSize" :current-page.sync="pageNumber"
+            @size-change="search()" @current-change="search()"
+        />
+      </div>
+    </template>
+  </el-popover>
+</template>
+
+
+<style lang="scss">
+.search-select-popover-wrapper {
+  $p: 8px;
+  display: flex;
+  flex-direction: column;
+  padding: 0;
+  min-width: 300px;
+  max-width: 500px;
+  max-height: 500px;
+  font-size: 14px;
+
+  .table-wrapper {
+    max-width: 500px;
+    font-size: 14px;
+
+    .el-table__row {
+      cursor: pointer;
+
+      &:hover {
+        background-color: #eef3fe;
+      }
+    }
+
+    & + .pagination-wrapper {
+      padding-bottom: 0;
+
+      .el-pagination {
+        padding-bottom: $p /2;
+      }
+    }
+  }
+  .list-wrapper {
+    flex: auto;
+    padding: $p $p 0;
+    overflow-y: auto;
+    overflow-x: hidden;
+  }
+  .pagination-wrapper {
+    flex: none;
+    padding-bottom: $p;
+
+    .el-pagination {
+      padding: $p / 2 0 0;
+    }
+  }
+
+  .option-active {
+    font-weight: 600;
+    background-color: #eef3fe;
+  }
+
+  .option-selected {
+    font-weight: 600;
+    color: #5386f6;
+  }
+}
+</style>
+<style scoped lang="scss">
+.input-wrapper {
+  font-size: 14px;
+
+  ::v-deep input {
+    cursor: pointer;
+
+    &::placeholder {
+      text-align: left;
+    }
+  }
+
+  &.clickable {
+    ::v-deep input {
+      text-align: center;
+      border: none;
+    }
+  }
+
+  &.initial {
+    ::v-deep input:focus {
+      color: #C0C4CC;
+    }
+  }
+
+  &.valid {
+    ::v-deep input::placeholder {
+      color: #5386f6;
+    }
+  }
+}
+
+.pagination-wrapper {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.item-wrapper {
+  line-height: 24px;
+  cursor: pointer;
+
+  > ::v-deep div {
+    padding: 4px 0;
+  }
+}
+</style>

+ 79 - 0
src/model/prescription.model.js

@@ -0,0 +1,79 @@
+import {CC_Dosage2Basis} from '@/utils/medicine';
+
+/**
+ * 获取处方模型
+ * @param {object} data 处方数据
+ * @param {string} [type] 处方类型
+ *   - '1' 中药处方
+ *   - '2' 中药制剂
+ *   - '3' 适宜技术
+ * @returns {Object & {
+ *   $id: string,
+ *   $name: string,
+ *   $type: string,
+ *   items: object[],
+ *   [key: string]: any
+ * }} 标准化后的处方数据,包含以 $ 开头的核心属性
+ */
+export function getPrescriptionModel(data, type) {
+    if (type == null) type = data.$type;
+
+    let props = {};
+    if (type === '1') {
+        if (data.pid) props = {$id: data.pid, $name: `${data.name}(${data.experttitle})`, items: data['preStiDetails']};
+        else if (data.preid) props = {$id: data.preid, $name: data.prename};
+        else props = {$id: data.$id || data.preid, $name: data.$name || data.prename};
+    } else if (type === '3') props = {$id: data.acupreid, $name: {1: '针灸', 2: '艾灸', 3: '推拿', 4: '拔罐'}[data.acupretype] || '---'};
+    return Object.assign(data, props, {$type: type});
+}
+
+/**
+ * 标准化单个药品数据
+ * @param {object} medicine 原始药品数据
+ * @param {object|function} [force] 强制覆盖字段或处理函数
+ * @returns {Object & {
+ *   $uid?: string,
+ *   $id: string,
+ *   $name: string,
+ *   $size?: string,
+ *   $dosage?: number,
+ *   $dosageRange?: string,
+ *   $baseDosage?: number,
+ *   $unit?: string,
+ *   $usage?: string,
+ *   $inventory?: number,
+ *   $price?: number,
+ *   $raw?: string,
+ *   [key: string]: any
+ * }} 标准化后的药品数据,包含以 $ 开头的核心属性
+ */
+export function getHerbalMedicineModel(medicine, force) {
+    if (typeof force === 'number') force = void 0;
+    const standard = {
+        $uid: medicine.$uid,
+        $id: medicine.$id || medicine.pid || medicine.matid,
+        $name: medicine.ypmc || medicine.matname,
+        $size: medicine.gg,
+        $dosage: +medicine.dose,
+        $unit: medicine.dw || medicine.unit,
+        $usage: medicine.usage || medicine.useage || medicine.specialUsage,
+        $inventory: ~~medicine.kc,
+        $price: +medicine.price,
+    };
+
+    if (medicine['matmindosage'] && medicine['matmaxdosage']) standard.$dosageRange = `${medicine['matmindosage']}-${medicine['matmaxdosage']}`;
+    standard.$raw = medicine.oldYpid || standard.$id;
+
+    for (const [key, value] of Object.entries(medicine)) if (!value) delete medicine[key];
+    for (const [key, value] of Object.entries(standard)) if (value == null) delete standard[key];
+    Object.assign(medicine, standard, typeof force === 'function' ? force(medicine, standard) : force);
+
+    if (!medicine.$price || Number.isNaN(medicine.$price)) medicine.$price = void 0;
+    if (!medicine.$dosage || Number.isNaN(medicine.$dosage)) medicine.$dosage = void 0;
+    else medicine.$baseDosage = CC_Dosage2Basis({
+        dose: medicine.$dosage,
+        xbzxs: medicine.xbzxs || '34'.includes(medicine.zylx) ? medicine.ggnum : 1,
+    });
+
+    return medicine;
+}

+ 5 - 1
src/store/index.js

@@ -1,9 +1,13 @@
 import Vue from 'vue'
 import Vuex from 'vuex'
+import __session from './session';
+
 
 Vue.use(Vuex)
 const files = require.context('./modules', false, /\.js$/)
-const modules = {}
+const modules = {
+    session: __session,
+};
 
 files.keys().forEach(key => {
   modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default

+ 1 - 0
src/store/modules/user.js

@@ -60,6 +60,7 @@ export default {
                 permissions = [...new Set(results.flat(1))];
             } catch (e) {}
             state.commit('setPermissions', permissions);
+            state.commit('session/setCategory', { value: data.ableprescription }, { root: true });
         },
     }
 };

+ 104 - 0
src/store/session.js

@@ -0,0 +1,104 @@
+import {deducePrescriptionMethod} from '@/api/prescription';
+
+class Storage {
+    static key = `session`;
+
+    static get(key, payload) {
+        const value = sessionStorage.getItem(`${this.key}.${key}`);
+        if (!value) return payload;
+        try { return JSON.parse(value); } catch (e) { return payload; }
+    }
+
+    static set(key, payload) {
+        try { sessionStorage.setItem(`${this.key}.${key}`, JSON.stringify(payload)); } catch (e) { sessionStorage.setItem(`${this.key}.${key}`, payload); }
+    }
+}
+
+export default {
+    namespaced: true,
+    state: () => ({
+        categories: Storage.get(`category`, []),
+    }),
+    getters: {
+        business(state) { return state.categories.filter(item => !item.disabled).map(item => item.type).join(',') || null; },
+        selected(state) { return state.categories[state['category'].index] || { prescriptions: [] }; },
+    },
+    mutations: {
+        setCategory(state, {categories, value, filter = true}) {
+            if (!Array.isArray(categories) && !value) value = '0,1,2';
+            if (typeof value === 'string') {
+                const map = {
+                    0: {type: '1', name: '中药处方', disabled: false},
+                    1: {type: '2', name: '中药制剂', disabled: false},
+                    2: {type: '3', name: '适宜技术处方', disabled: false},
+                };
+                categories = value.split(',').map(id => map[id]).filter(item => !!item);
+            }
+            state.categories = categories.filter(item => !(filter && item.disabled)).map((item) => Object.assign({
+                checked: false,
+                prescriptions: [],
+            }, item));
+            Storage.set(`category`, state.categories);
+        },
+        updateCategory(state, {prescriptions}) {
+            for (const category of state.categories) category.prescriptions = prescriptions[category.type];
+        },
+    },
+    actions: {},
+    modules: {
+        category: {
+            state: () => ({
+                index: 0,
+            }),
+            getters: {},
+            mutations: {
+                selectCategory(state, {index}) {
+                    state.index = Math.max(0, index);
+                },
+            },
+        },
+        illness: {
+            state: () => {
+                const data = Storage.get('illness', {});
+                return {
+                    disease: Object.assign({id: null, name: ''}, data.disease),
+                    symptom: Object.assign({id: null, name: ''}, data.symptom),
+                    therapy: Object.assign({id: null, name: ''}, data.therapy),
+                };
+            },
+            getters: {
+                deduceable(state) { return state.disease && !!state.disease.id && state.symptom && !!state.symptom.id; },
+            },
+            mutations: {
+                disease(state, payload) { state.disease = Object.assign({id: null, name: ''}, payload); },
+                symptom(state, payload) { state.symptom = Object.assign({id: null, name: ''}, payload); },
+                therapy(state, payload) { state.therapy = Object.assign({id: null, name: ''}, payload); },
+                updateIllness(state, payload = {}) {
+                    state.disease = Object.assign(state.disease, payload.disease);
+                    state.symptom = Object.assign(state.symptom, payload.symptom);
+                    state.therapy = Object.assign(state.therapy, payload.therapy);
+                    Storage.set('illness', payload);
+                },
+            },
+            actions: {
+                async deduce(context, illness) {
+                    if (illness == null) illness = {};
+                    if (illness.disease == null) illness.disease = context.state.disease;
+                    if (illness.symptom == null) illness.symptom = context.state.symptom;
+                    if (illness.therapy == null) illness.therapy = context.state.therapy;
+                    if (illness.disease.id && illness.symptom.id) {
+                        const prescriptions = await deducePrescriptionMethod(context.getters.business, illness);
+                        context.commit('updateCategory', {prescriptions});
+                        context.commit('updateIllness', illness);
+                        // 当前选中的类别没有推荐处方,则切换到首位
+                        try {
+                            const selected = context.rootGetters['session/selected'];
+                            if (!prescriptions[selected.type].length) context.commit('selectCategory', prescriptions);
+                        } catch (e) {}
+                        return prescriptions;
+                    } else { throw `请完善病名证型信息`; }
+                },
+            },
+        },
+    },
+};

+ 129 - 0
src/tool/prescription.tool.js

@@ -0,0 +1,129 @@
+import {checkReasonableSafeMedicinesMethod} from '@/api/prescription';
+
+const reasonableSafeMap = [{
+    type: '2', label: `慎忌禁用药`, key: 'matsjj', handle(medicine, value) {
+        return {
+            name: medicine['matname'] || medicine.$name,
+            description: `(${{1: '慎用', 2: '忌用', 3: '禁用'}[value] || '---'})`,
+        };
+    },
+}, {
+    type: '3', label: `孕妇慎忌禁`, key: 'matyfsjj', handle(medicine, value) {
+        return {
+            name: medicine['matname'] || medicine.$name,
+            description: `(${{1: '孕妇慎用', 2: '孕妇忌用', 3: '孕妇禁用'}[value] || '---'})`,
+            signName: +value === 3,
+        };
+    },
+}, {
+    type: '4', label: `服药饮食禁忌`, key: 'matysjj', handle(medicine, value) {
+        return {
+            name: medicine['matname'] || medicine.$name,
+            description: `(${value || '无'})`,
+        };
+    },
+}, {
+    type: '5', label: `药物毒性说明`, key: 'matdxsm', handle(medicine, value) {
+        return {
+            name: medicine['matname'] || medicine.$name,
+            description: `(${value || '无'})`,
+            signName: true,
+        };
+    },
+}, {
+    type: '6', label: `病证用药禁忌`, key: 'matbzjj', handle(medicine, value) {
+        return {
+            name: medicine['matname'] || medicine.$name,
+            description: `(${value || '无'})`,
+        };
+    },
+}, {
+    type: '7', label: `十八反`, key: 'matsbf', handle(medicine, value) {
+        return {
+            name: medicine['matname'] || medicine.$name,
+            description: `反 ${value}`, signName: true,
+        };
+    },
+}, {
+    type: '8', label: `十九畏`, key: 'matsjw', handle(medicine, value) {
+        return {
+            name: medicine['matname'] || medicine.$name,
+            description: `畏 ${value}`, signName: true,
+        };
+    },
+}, {
+    type: '9', label: `用药不宜`, key: 'matby', handle(medicine, value) {
+        return {
+            name: medicine['matname'] || medicine.$name,
+            description: `不宜与 ${value} 同用`,
+        };
+    },
+}, {
+    type: '10', label: `超剂量药品`, key: ['matmindosage', 'matmaxdosage', '$dosage'], handle(medicine, [min, max]) {
+        const dosage = medicine.$baseDosage;
+        return dosage < min || dosage > max ? {
+            name: medicine['matname'] || medicine.$name, description: `(${min}~${max})`, signDescription: true,
+        } : void 0;
+    },
+}];
+
+/**
+ * @description
+ *  2:慎 忌 禁
+ *  3:孕慎 孕忌 孕禁
+ *  4:服药饮食禁忌
+ *  5:药物毒性说明
+ *  6:病证用药禁忌
+ *  7:十八反
+ *  8:十九畏
+ *  9:用药不宜
+ *  10:超剂量药品
+ * @param medicines
+ * @param {object} config
+ * @param {string} [config.filter]
+ * @param {string} [config.sort = '10,2,3,4,5,6,7,8,9']
+ * @param {boolean} [config.force = true]
+ * @param {object} [config.platform ]平台数据
+ * @param {string} [config.platform.organization] 平台机构 - ID
+ */
+export async function getReasonableSafeData(medicines, config = {}) {
+    // const
+    const filter = (config.filter || '').split(',').filter(Boolean);
+    const sort = (config.filter || config.sort || '10,2,3,4,5,6,7,8,9').split(',').filter(Boolean);
+    const {force = true} = config;
+
+    if (force) await checkReasonableSafeMedicinesMethod(medicines, config.platform);
+
+    const rs = {
+        * [Symbol.iterator]() { for (const key of sort) if (this[key]) yield this[key]; },
+        toString() {
+            let html = ``;
+            for (const {label, collection} of this) {
+                html += `
+                  <div class="item"> \
+                    <div class="label">${label}:</div> \
+                    ${collection.map(item => `<div>
+                        <span class="name ${item.signName ? 'sign' : ''}">${item.name}</span> \
+                        <span class="description ${item.signDescription ? 'sign' : ''}">${item.description}</span> \
+                    </div>`).join('')}
+                  </div>
+                `;
+            }
+            return html;
+        },
+    };
+    for (const medicine of medicines) {
+        if (medicine.editing) continue;
+        for (const {key, type, label, handle, ...props} of reasonableSafeMap) {
+            if (filter.length && !filter.includes(type)) continue;
+            const keys = Array.isArray(key) ? key : [key];
+            const values = keys.map(key => medicine[key]);
+            if (values.some(value => !value)) continue;
+            const data = handle(medicine, typeof key === 'string' ? values[0] : values);
+            if (data == null) continue;
+            const child = rs[type] || (rs[type] = {...props, type, label, collection: []});
+            child.collection.push(data);
+        }
+    }
+    return rs;
+}

+ 1 - 2
src/utils/request1.js

@@ -68,8 +68,7 @@ service.interceptors.response.use(
         }
     },
     error => {
-
-        Vue.prototype.$message({
+        if (error.message !== 'canceled') Vue.prototype.$message({
             message: error.message,
             type: 'error'
         })

+ 17 - 0
src/utils/uuid.js

@@ -0,0 +1,17 @@
+let randomUUID = () => {
+    try {
+        if (typeof crypto?.randomUUID === 'function') randomUUID = crypto.randomUUID.bind(crypto);
+    } catch (e) {
+        randomUUID = () => {
+            return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+                const r = (Math.random() * 16) | 0;
+                const v = c === 'x' ? r : (r & 0x3) | 0x8;
+                return v.toString(16);
+            });
+        };
+    }
+    return randomUUID();
+};
+
+
+export default randomUUID;

+ 279 - 187
src/views/diagnosis/Prescribing.vue

@@ -68,7 +68,7 @@
           <span></span>
           <div>中医诊断</div>
           <!-- inferRecipe(2) -->
-          <el-button size="mini" type="warning" @click="tcmClick()" style="margin-left:10px;">推导</el-button>
+          <el-button size="mini" type="warning" @click="deduceHandle()" style="margin-left:10px;">推导</el-button>
         </div>
       </div>
       <!-- <TCMDiagnosis v-if="Object.keys(patiensMsg).length>0" :patiensMsg="patiensMsg" ref="TCM"></TCMDiagnosis> -->
@@ -228,7 +228,7 @@
         ></TCMDiagnosis>
         <!-- 推导按钮 -->
         <div class="deduce">
-          <el-button size="mini" type="primary" @click="tcmClick()">推导</el-button>
+          <el-button size="mini" type="primary" @click="deduceHandle()">推导</el-button>
 
           <el-popover
             placement="top-start"
@@ -285,12 +285,18 @@
       </div>
       <div class="flex-vertical-between center-header">
         <div class="center-tab flex-vertical-center-l">
-          <template v-for="(item,index) in contentTabs">
-            <div v-if="!item.hide" :key="item.id" class="flex-center" :class="{active: container_i === index}"
-                 @click="changeContainer(item, index)">
-              <span :style="{color:item.color}">{{ item.name }}</span>
-            </div>
-          </template>
+          <div v-for="(category, index) in categories" :key="category.type"
+               class="category-tab" :class="{ active: category.type === selected.type }"
+               @click="selectCategory({index})"
+          >
+            <span :class="{ highlight: category.prescriptions.length }">{{ category.name }}</span>
+          </div>
+          <!--          <template v-for="(item,index) in contentTabs">
+                      <div v-if="!item.hide" :key="item.id" class="flex-center" :class="{active: container_i === index}"
+                           @click="changeContainer(item, index)">
+                        <span :style="{color:item.color}">{{ item.name }}</span>
+                      </div>
+                    </template>-->
         </div>
         <div class="header-total flex-vertical-center-r flex-wrap" v-if="false">
           <div v-if="contentTabs[0].check">
@@ -383,24 +389,43 @@
 
       <!-- 表格数据展示 -->
       <div class="center-table">
-        <chinese-medicine
-          @find="findDrug($event)"
-          v-show="container_i===0"
-          id="chineseM"
-          ref="chineseM"
-          :totalAllMoney.sync="chineseM.allMoney"
-          @submit="submitRecipe1()"
-          @clear="clearContainer('0')"
-          :showSubmit="showSubmit"
-          :isShrink="isShrink"
-          :isDaiJian="Number(isDaiJian)"
-          :isPs="isPs"
-          @updateDp="onUpdateDp"
-        ></chinese-medicine>
+        <template v-for="category in categories">
+          <category1 ref="category1" :key="category.type"
+                     v-if="category.type === '1'"
+                     v-show="category.type === selected.type"
+                     :category="category" :is-shrink="isShrink"
+          />
+          <category2 ref="category2" :key="category.type"
+                     v-if="category.type === '2'"
+                     v-show="category.type === selected.type"
+                     :category="category" :is-shrink="isShrink"
+          />
+          <category3 ref="category3" :key="category.type"
+                     v-if="category.type === '3'"
+                     v-show="category.type === selected.type"
+                     :category="category" :is-shrink="isShrink"
+          />
+        </template>
+
+
+        <!--        <chinese-medicine
+                  @find="findDrug($event)"
+                  v-show="false"
+                  id="chineseM"
+                  ref="chineseM"
+                  :totalAllMoney.sync="chineseM.allMoney"
+                  @submit="submitRecipe1()"
+                  @clear="clearContainer('0')"
+                  :showSubmit="showSubmit"
+                  :isShrink="isShrink"
+                  :isDaiJian="Number(isDaiJian)"
+                  :isPs="isPs"
+                  @updateDp="onUpdateDp"
+                ></chinese-medicine>-->
 
         <medicineChinese
           @find="findDrug($event)"
-          v-show="container_i==1"
+          v-show="false"
           ref="medicineC"
           id="medicineC"
           @submit="submitRecipe1()"
@@ -411,7 +436,7 @@
         ></medicineChinese>
         <suitScience
           @find="find($event)"
-          v-show="container_i==2"
+          v-show="false"
           ref="suitScience"
           id="suitScience"
           @submit="submitRecipe1()"
@@ -422,7 +447,10 @@
       </div>
     </div>
 
-    <div class="pre-right" v-show="container_i!=1 && !isShrink">
+
+    <div v-show="!isShrink" class="pre-right-container"></div>
+
+    <div class="pre-right" v-show="false">
       <!-- 标题信息 -->
       <div class="pre-title flex-vertical-between">
         <div class="flex-vertical-center-l title-container">
@@ -433,7 +461,7 @@
       </div>
 
       <!-- 推荐方剂 -->
-      <div class="recommend flex-center" v-if="rRecomendR.length>0">
+      <div class="recommend flex-center" v-if="selected.prescriptions.length">
         <!-- <div class="arrow-left flex-center" @click="changeRbanner('reduce')">
           <img src="../../assets/arrow-left.png" alt="">
         </div>-->
@@ -444,19 +472,14 @@
           <div class="r-banner-body">
             <div
               class="r-body-item flex-vertical-between"
-              v-for="(item1,index1) in rRecomendR"
-              :key="'c'+index1"
+              v-for="item1 in selected.prescriptions" :key="item1.id"
             >
               <div class="med-name med-name-bg"
                    :class="{active: tjRecipeId && (tjRecipeId==item1.preid ||tjRecipeId==item1.pid || tjRecipeId==item1.acupreid)}"
-                v-if="item1.showType==0"
-                @click.stop="getPreDetal(item1.preid || item1,1)"
+                   v-if="true"
+                   @click.stop="deducePrescriptionHandle(item1)"
               >
-                <span v-if="item1.prename">{{item1.prename}}</span>
-                <span
-                  v-else
-                >{{item1.acupretype==1?'针灸':item1.acupretype==2?'艾灸':item1.acupretype==3?'推拿':'拔罐'}}</span>
-                <!-- {{item1.prename?item1.prename+'('+item1.book?item1.book:'暂无'+')': item1.acupretype==1?'针灸':item1.acupretype==2?'艾灸':item1.acupretype==3?'推拿':'拔罐'}} -->
+                <span>{{ item1.name }}</span>
               </div>
               <!-- 经验方 -->
               <div class="med-name med-name-bg"
@@ -468,128 +491,129 @@
                 <span>{{item1.name}}({{item1.experttitle}})</span>
               </div>
 
-              <!-- <div class="med-btns flex-vertical-center-r"> -->
-              <!-- 专家经验换方 -->
-              <!-- <div class="flex-center" v-if="item1.showType==1" @click.self.stop="turnRecipe3(item1)">换方</div>
-                <div class="flex-center bg-yellow" v-if="item1.showType==1" @click.self.stop="joinRecipe3(item1)">合方
-                </div>
+                  &lt;!&ndash; <div class="med-btns flex-vertical-center-r"> &ndash;&gt;
+                  &lt;!&ndash; 专家经验换方 &ndash;&gt;
+                  &lt;!&ndash; <div class="flex-center" v-if="item1.showType==1" @click.self.stop="turnRecipe3(item1)">换方</div>
+                    <div class="flex-center bg-yellow" v-if="item1.showType==1" @click.self.stop="joinRecipe3(item1)">合方
+                    </div>
 
-                <div class="flex-center" v-if="item1.showType==0"
-                  @click.self.stop="inferChange(item1,item1.prename?'':1,item1.prename?'':1)">换方</div>
-                <div class="bg-yellow flex-center" v-if="item1.showType==0"
-                  @click.self.stop="inferChange1(item1,item1.prename?'':1,item1.prename?'':1)">合方
-              </div>-->
-              <!-- </div> -->
+                    <div class="flex-center" v-if="item1.showType==0"
+                      @click.self.stop="inferChange(item1,item1.prename?'':1,item1.prename?'':1)">换方</div>
+                    <div class="bg-yellow flex-center" v-if="item1.showType==0"
+                      @click.self.stop="inferChange1(item1,item1.prename?'':1,item1.prename?'':1)">合方
+                  </div>&ndash;&gt;
+                  &lt;!&ndash; </div> &ndash;&gt;
+                </div>
+              </div>
+              &lt;!&ndash; </el-carousel-item> &ndash;&gt;
+              &lt;!&ndash; </el-carousel> &ndash;&gt;
             </div>
+            &lt;!&ndash; <div class="arrow-left flex-center" @click="changeRbanner('add')">
+              <img src="../../assets/arrow-right.png" alt="">
+            </div>&ndash;&gt;
           </div>
-          <!-- </el-carousel-item> -->
-          <!-- </el-carousel> -->
-        </div>
-        <!-- <div class="arrow-left flex-center" @click="changeRbanner('add')">
-          <img src="../../assets/arrow-right.png" alt="">
-        </div>-->
-      </div>
 
-      <!-- 安全合理用药检测 -->
-      <div class="pre-title mr-t10" v-if="container_i!=2">
-        <div class="flex-vertical-center-l title-container">
-          <span></span>
-          <div>安全合理用药检测</div>
-        </div>
-        <div class="patiens-msg mr-t10" v-if="rationalMed.length>0">
-          <div class="p" v-if="raDoseShow">
-            <!-- {{item.matname}}({{item.matbzjj}}) -->
-            <!-- <div class="patiens-name">{{item.matname}}</div> -->
-            <div class="patiens-cate">超剂量药品:</div>
-            <div class="patiens-desc" v-for="(item,index) in rationalMed10" :key="index">
-              <span v-if="item.showDose">
-                <span class="matname">{{item.matname}}</span>
-                <span style="color:red;">({{item.matmindosage}}-{{item.matmaxdosage}})</span>
-              </span>
+          <div id="test"></div>
+          &lt;!&ndash; 安全合理用药检测 &ndash;&gt;
+          <div class="pre-title mr-t10" v-if="container_i!=2">
+            <div class="flex-vertical-center-l title-container">
+              <span></span>
+              <div>安全合理用药检测</div>
             </div>
-          </div>
+            <div class="patiens-msg mr-t10" v-if="rationalMed.length>0">
+              <div class="p" v-if="raDoseShow">
+                &lt;!&ndash; {{item.matname}}({{item.matbzjj}}) &ndash;&gt;
+                &lt;!&ndash; <div class="patiens-name">{{item.matname}}</div> &ndash;&gt;
+                <div class="patiens-cate">超剂量药品:</div>
+                <div class="patiens-desc" v-for="(item,index) in rationalMed10" :key="index">
+                  <span v-if="item.showDose">
+                    <span class="matname">{{item.matname}}</span>
+                    <span style="color:red;">({{item.matmindosage}}-{{item.matmaxdosage}})</span>
+                  </span>
+                </div>
+              </div>
 
-          <div class="p" v-if="rationalMed2.length>0">
-            <div class="patiens-cate">慎忌禁用药:</div>
-            <div class="patiens-desc" v-for="(item,index) in rationalMed2" :key="index">
-              <span v-if="item.matsjj">
-                <span class="matname">{{item.matname}}</span>
-                ({{item.matsjj |ftsjj}})
-              </span>
-            </div>
-          </div>
+              <div class="p" v-if="rationalMed2.length>0">
+                <div class="patiens-cate">慎忌禁用药:</div>
+                <div class="patiens-desc" v-for="(item,index) in rationalMed2" :key="index">
+                  <span v-if="item.matsjj">
+                    <span class="matname">{{item.matname}}</span>
+                    ({{item.matsjj |ftsjj}})
+                  </span>
+                </div>
+              </div>
 
-          <div class="p" v-if="rationalMed3.length>0">
-            <div class="patiens-cate">孕妇慎忌禁:</div>
-            <div class="patiens-desc" v-for="(item,index) in rationalMed3" :key="index">
-              <span v-if="item.matyfsjj">
-                <span class="matname">{{item.matname}}</span>
-                <span :style="{color:item.matyfsj==3?'red':''}">({{item.matyfsjj |fyfsjj}})</span>
-              </span>
-            </div>
-          </div>
+              <div class="p" v-if="rationalMed3.length>0">
+                <div class="patiens-cate">孕妇慎忌禁:</div>
+                <div class="patiens-desc" v-for="(item,index) in rationalMed3" :key="index">
+                  <span v-if="item.matyfsjj">
+                    <span class="matname">{{item.matname}}</span>
+                    <span :style="{color:item.matyfsj==3?'red':''}">({{item.matyfsjj |fyfsjj}})</span>
+                  </span>
+                </div>
+              </div>
 
-          <div class="p" v-if="rationalMed4.length>0">
-            <div class="patiens-cate">服药饮食禁忌:</div>
-            <div class="patiens-desc" v-for="(item,index) in rationalMed4" :key="index">
-              <span v-if="item.matysjj">
-                <span class="matname">{{item.matname}}</span>
-                ({{item.matysjj?item.matysjj:'无'}})
-              </span>
-            </div>
-          </div>
+              <div class="p" v-if="rationalMed4.length>0">
+                <div class="patiens-cate">服药饮食禁忌:</div>
+                <div class="patiens-desc" v-for="(item,index) in rationalMed4" :key="index">
+                  <span v-if="item.matysjj">
+                    <span class="matname">{{item.matname}}</span>
+                    ({{item.matysjj?item.matysjj:'无'}})
+                  </span>
+                </div>
+              </div>
 
-          <div class="p" v-if="rationalMed5.length>0">
-            <div class="patiens-cate">药物毒性说明:</div>
-            <div class="patiens-desc" v-for="(item,index) in rationalMed5" :key="index">
-              <span v-if="item.matdxsm">
-                <span class="matname" style="color:red;">{{item.matname}}</span>
-                ({{item.matdxsm?item.matdxsm:'无'}})
-              </span>
-            </div>
-          </div>
+              <div class="p" v-if="rationalMed5.length>0">
+                <div class="patiens-cate">药物毒性说明:</div>
+                <div class="patiens-desc" v-for="(item,index) in rationalMed5" :key="index">
+                  <span v-if="item.matdxsm">
+                    <span class="matname" style="color:red;">{{item.matname}}</span>
+                    ({{item.matdxsm?item.matdxsm:'无'}})
+                  </span>
+                </div>
+              </div>
 
-          <div class="p" v-if="rationalMed6.length>0">
-            <div class="patiens-cate">病证用药禁忌:</div>
-            <div class="patiens-desc" v-for="(item,index) in rationalMed6" :key="index">
-              <span v-if="item.matbzjj">
-                <span class="matname">{{item.matname}}</span>
-                ({{item.matbzjj?item.matbzjj:'无'}})
-              </span>
-            </div>
-          </div>
+              <div class="p" v-if="rationalMed6.length>0">
+                <div class="patiens-cate">病证用药禁忌:</div>
+                <div class="patiens-desc" v-for="(item,index) in rationalMed6" :key="index">
+                  <span v-if="item.matbzjj">
+                    <span class="matname">{{item.matname}}</span>
+                    ({{item.matbzjj?item.matbzjj:'无'}})
+                  </span>
+                </div>
+              </div>
 
-          <div class="p" v-if="rationalMed7.length>0">
-            <div class="patiens-cate">十八反:</div>
-            <div class="patiens-desc" v-for="(item,index) in rationalMed7" :key="index">
-              <!-- <span v-if='item.matsbf'>{{fsbf(item.matsbf,item.matname)}}</span> -->
-              <span v-if="item.matsbf">
-                <span style="color:red;">{{item.matname}}</span>
-                反{{item.matsbf}}
-              </span>
-            </div>
-          </div>
+              <div class="p" v-if="rationalMed7.length>0">
+                <div class="patiens-cate">十八反:</div>
+                <div class="patiens-desc" v-for="(item,index) in rationalMed7" :key="index">
+                  &lt;!&ndash; <span v-if='item.matsbf'>{{fsbf(item.matsbf,item.matname)}}</span> &ndash;&gt;
+                  <span v-if="item.matsbf">
+                    <span style="color:red;">{{item.matname}}</span>
+                    反{{item.matsbf}}
+                  </span>
+                </div>
+              </div>
 
-          <div class="p" v-if="rationalMed8.length>0">
-            <div class="patiens-cate">十九畏:</div>
-            <div class="patiens-desc" v-for="(item,index) in rationalMed8" :key="index">
-              <!-- <span v-if="item.matsjw">{{fsjw(item.matsjw,item.matname)}}</span> -->
-              <span v-if="item.matsjw">
-                <span style="color:red;">{{item.matname}}</span>
-                畏{{item.matsjw}}
-              </span>
-            </div>
-          </div>
+              <div class="p" v-if="rationalMed8.length>0">
+                <div class="patiens-cate">十九畏:</div>
+                <div class="patiens-desc" v-for="(item,index) in rationalMed8" :key="index">
+                  &lt;!&ndash; <span v-if="item.matsjw">{{fsjw(item.matsjw,item.matname)}}</span> &ndash;&gt;
+                  <span v-if="item.matsjw">
+                    <span style="color:red;">{{item.matname}}</span>
+                    畏{{item.matsjw}}
+                  </span>
+                </div>
+              </div>
 
-          <div class="p" v-if="rationalMed9.length>0">
-            <div class="patiens-cate">用药不宜:</div>
-            <div class="patiens-desc" v-for="(item,index) in rationalMed9" :key="index">
-              <span v-if="item.matby">{{fmatby(item.matby,item.matname) }}</span>
+              <div class="p" v-if="rationalMed9.length>0">
+                <div class="patiens-cate">用药不宜:</div>
+                <div class="patiens-desc" v-for="(item,index) in rationalMed9" :key="index">
+                  <span v-if="item.matby">{{fmatby(item.matby,item.matname) }}</span>
+                </div>
+              </div>
             </div>
           </div>
         </div>
-      </div>
-    </div>
 
     <!-- 方剂详情 -->
     <Popup
@@ -1128,62 +1152,62 @@
   </div>
 </template>
 <script>
-import Popup from "@/components/Propup.vue";
+import Popup from '@/components/Propup.vue';
 
-import chineseMedicine from "@/components/ChineseMedicine.vue";
-import medicineChinese from "@/components/MedicineAndChina.vue";
-import suitScience from "@/components/SuitScience.vue";
+import chineseMedicine from '@/components/ChineseMedicine.vue';
+import medicineChinese from '@/components/MedicineAndChina.vue';
+import suitScience from '@/components/SuitScience.vue';
 
-import submitRecipe from "./components/submitRecipe.vue";
+import submitRecipe from './components/submitRecipe.vue';
 // import TCMDiagnosis from "./components/TCMDiagnosis.vue";
-import TCMDiagnosis from "../../components/TCMDiagnosis.vue";
-import doctorCase from "./components/doctorCase.vue";
-import prescription from "./components/prescription.vue";
-import UnifyPrescription from "./components/prescription-unify.vue";
-import TongueAnalysis from "./components/tongue-analysis.vue";
-import safeDrug from "./components/safeDrug.vue";
-import medAdress from "./components/medAddress.vue";
-import medAdressNew from "./components/medAddressNew.vue";
+import TCMDiagnosis from '../../components/TCMDiagnosis.vue';
+import doctorCase from './components/doctorCase.vue';
+import prescription from './components/prescription.vue';
+import UnifyPrescription from './components/prescription-unify.vue';
+import TongueAnalysis from './components/tongue-analysis.vue';
+import safeDrug from './components/safeDrug.vue';
+import medAdress from './components/medAddress.vue';
+import medAdressNew from './components/medAddressNew.vue';
 import {
-  getPatiensBasisM,
-  getTongueAndFaceAnalysisRecords,
-  addRecipe,
-  getRecipeShowData,
-  getRecipeDataByid,
-  getAgreeRecipe,
-  getSeeDByID,
-  getDrugDetail,
-  getAccordDetail,
   changeBasisPre,
-  updateRecipe,
   changeExpre,
-  recipeIsPay,
+  getAccordDetail,
+  getAgreeRecipe,
+  getDrugDetail,
+  getPatiensBasisM,
   getPreNumber,
+  getRecipeDataByid,
   getRecipePriview,
-  getMaxMinDoaseNumber
-} from "@/api/diagnosis.js";
-import { numberToUpperCase } from "@/utils/format.js";
-import { addRecipeFrom } from "@/api/dataAnalysis.js";
+  getRecipeShowData,
+  getSeeDByID,
+  getTongueAndFaceAnalysisRecords,
+  recipeIsPay,
+} from '@/api/diagnosis.js';
+import {numberToUpperCase} from '@/utils/format.js';
+import {addRecipeFrom} from '@/api/dataAnalysis.js';
 import {
-  getPrescriptionsList,
+  changeAndJoin,
+  getAcupointD,
+  getDCaseDetail,
   getDoctorCaseL,
+  getMedDetail,
+  getPreDetal,
+  getPrescriptionsList,
   getRationalMed,
   getRationalMedForPlat,
   inferRecipe,
-  getDCaseDetail,
-  changeAndJoin,
-  getPreDetal,
-  getAcupointD,
-  getMedDetail
-} from "@/api/knowledge.js";
+} from '@/api/knowledge.js';
 
-import { mapState, mapGetters, mapActions, mapMutations } from "vuex";
-import { getExperinceDetail } from "@/api/business.js";
-import { setTimeout } from "timers";
-import prescribing from "../../utils/minix/prescribing";
+import {mapActions, mapGetters, mapMutations, mapState} from 'vuex';
+import {getExperinceDetail} from '@/api/business.js';
+import {setTimeout} from 'timers';
+import prescribing from '../../utils/minix/prescribing';
 
-import {formatPicture} from "@/utils/picture";
+import {formatPicture} from '@/utils/picture';
 import {CC_Dosage2Basis} from '@/utils/medicine';
+import {deducePrescriptionByMethod} from '@/api/prescription';
+import {getHerbalMedicineModel} from '@/model/prescription.model';
+import randomUUID from '@/utils/uuid';
 
 export default {
   mixins: [prescribing],
@@ -1192,6 +1216,9 @@ export default {
     chineseMedicine,
     medicineChinese,
     suitScience,
+    category1: chineseMedicine,
+    category2: medicineChinese,
+    category3: suitScience,
     TCMDiagnosis,
     submitRecipe,
     prescription,
@@ -1312,7 +1339,7 @@ export default {
       // 右侧推荐方剂
       rRecomendR: [],
       // 中间顶部tab
-      container_i: 0,
+      container_i: -1,
       /* 下列顺便不能轻易变动 */
       contentTabs: [
         {
@@ -1375,6 +1402,7 @@ export default {
   created() {
     try {
       const tabs = this.getuserinfo.ableprescription.split(',');
+      console.log(tabs, 'log:tabs');
       for (const id of tabs) try { this.contentTabs.find(item => item.id === id).hide = false; } catch (e) {}
     } catch (e) {
 
@@ -1412,6 +1440,8 @@ export default {
     }
   },
   methods: {
+    ...mapMutations('session', ['selectCategory']),
+    ...mapActions('session', ['deduce']),
     // 处理路由参数
     async _processRouteQuery() {
       const query = this.$route.query || {};
@@ -1449,6 +1479,45 @@ export default {
         else if (this.getEditPreNo()) await this.getRecipeDataByid(this.getEditPreNo());
       }
     },
+    /**
+     * 推导治疗方案
+     */
+    async deduceHandle() {
+      const hide = this.showLoading(`正在获取推荐数据`);
+      try {
+        const data = await this.deduce();
+        console.log('log:', this.selected);
+      } catch (e) {
+        console.log(e);
+        if (typeof e === 'string') this.$message.warning(e);
+        else this.$message.error(e.message);
+      }
+      hide();
+    },
+    /**
+     * 推导处方
+     * @return {Promise<void>}
+     */
+    async deducePrescriptionHandle(prescription, append = false) {
+      prescription = await deducePrescriptionByMethod(prescription);
+      prescription.items = prescription.items.map(item => getHerbalMedicineModel(item, { $uid: randomUUID()}));
+      switch (prescription.type) {
+        case '1':
+          return this.$refs.category1[0].setPrescription(prescription, append);
+        case '2':
+          return this.$refs.category2[0].setPrescription(prescription, append);
+        case '3':
+          return this.$refs.category3[0].setPrescription(prescription, append);
+      }
+    },
+    showLoading(text) {
+      const loading = this.$loading({
+        lock: true, text,
+        spinner: 'el-icon-loading',
+        background: 'rgba(0, 0, 0, 0.7)',
+      });
+      return () => loading.close();
+    },
 
     // 中医诊断推导点击
     tcmClick(isauto = true) {
@@ -1475,18 +1544,18 @@ export default {
       this.rDoctorCase.loadMore = true;
       this.getDoctorCaseL();
       // --end
-      const current = this.contentTabs[this.container_i];
+      const current = this.categories[0];
       if (!current) return;
 
       if (isauto && current.id !== '1') {
-        this.inferRecipe(2, current.businesstype);
+        this.inferRecipe(2, current.type);
       }
 
       setTimeout(() => {
-        for (const tab of this.contentTabs) {
+        for (const tab of this.categories) {
           if (tab.hide) continue;
           const type = tab.id === '1' && tab.id === current.id ? 3 : 4;
-          this.inferRecipe(type, tab.businesstype).then(() => {
+          this.inferRecipe(type, tab.type).then(() => {
             if (tab.id === current.id ) {
               if (current.id === '0') this.isTuiDaoZy = true
               else if (current.id === '1') this.isTuiDaoZj = true
@@ -4421,7 +4490,10 @@ export default {
         this.showDrug = true;
       }
     },
-    // 获取方剂详情
+    /**
+     * 获取方剂详情
+     * @deprecated {@link deducePrescriptionHandle}
+     */
     async getPreDetal(id, type = "") {
       if (id instanceof Object) {
         // 适宜技术处方
@@ -4548,6 +4620,10 @@ export default {
         : 0
       ).toFixed(2);
     },
+    ...mapState('session', {
+      categories: state => state.categories,
+    }),
+    ...mapGetters('session', ['selected']),
     ...mapGetters(["getPatiensInfo", "getuserinfo", "getDrugInfo", "getRecipeId", "getIsSee", "getPreNo"])
   },
   filters: {
@@ -4858,11 +4934,20 @@ export default {
         }
       }
 
+      .category-tab {
+        display: flex;
+        align-items: center;
+        justify-content: center;
+      }
       .active {
         background: #5386f6;
         color: #fff;
       }
 
+      .highlight {
+        color: red;
+      }
+
       .checked {
         img {
           width: 16px;
@@ -5283,6 +5368,13 @@ export default {
       }
     }
   }
+
+  .pre-right-container {
+    display: flex;
+    flex-direction: column;
+    height: calc(100vh - 100px);
+    overflow: hidden;
+  }
 }
 
 .table-choose3 {

+ 2 - 2
vue.config.js

@@ -15,8 +15,8 @@ module.exports = {
                 // target: `http://cloudclinicapi.9czn.cn/`, // 测试地址
                 // target: `http://115.236.184.102:8081/`, // 萧山物理机
                 // target: 'http://192.168.204.124:8081/', // 客户正式服务器"
-                // target: `http://121.43.162.141:80/`, // [萧山] 测试环境的后端地址
-                target: `https://wx.hzliuzhi.com:4433/fz/`, // [测试] 测试环境的后端地址
+                target: `http://121.43.162.141:80/`, // [萧山] 测试环境的后端地址
+                // target: `https://wx.hzliuzhi.com:4433/fz/`, // [测试] 测试环境的后端地址
                 // target: `http://www.hzxunmai.com/`, // 正式域名
                 // target: `http://10.250.11.48:8080/`, // 正式域名
 

이 변경점에서 너무 많은 파일들이 변경되어 몇몇 파일들은 표시되지 않았습니다.