LocalExpertTech.vue 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060
  1. <template>
  2. <div class="local-expert-tech">
  3. <!-- 顶部筛选 -->
  4. <div class="screening">
  5. <div class="screening-title flex-vertical-center-l">
  6. <img src="~@/assets/filters.png" alt />
  7. </div>
  8. <div class="screening-form flex-vertical-center-l flex-wrap">
  9. <div class="screening-item flex-vertical-center-l">
  10. <span>方名:</span>
  11. <div class="input">
  12. <el-input size="mini" v-model="searchData.name" placeholder="请输入" clearable></el-input>
  13. </div>
  14. </div>
  15. <div class="screening-item flex-vertical-center-l">
  16. <span>涵盖项目:</span>
  17. <div class="input">
  18. <el-select size="mini" v-model="searchData.includeItem" placeholder="请搜索选择" clearable filterable :loading="projectLoading" loading-text="搜索中..." :filter-method="searchProject" @focus="searchProject('')">
  19. <el-option v-for="item in projectOptions" :key="item.pid" :label="item.itemName" :value="item.pid"></el-option>
  20. </el-select>
  21. </div>
  22. </div>
  23. <div class="screening-item flex-vertical-center-l">
  24. <span>中医病名:</span>
  25. <div class="input">
  26. <el-select size="mini" v-model="searchData.chineseDisease" placeholder="请搜索选择" clearable filterable :loading="diseaseLoading" loading-text="搜索中..." :filter-method="searchDisease" @focus="searchDisease('')">
  27. <el-option v-for="(item, idx) in diseaseOptions" :key="idx" :label="item.disname" :value="String(item.disid)"></el-option>
  28. </el-select>
  29. </div>
  30. </div>
  31. <div class="screening-item flex-vertical-center-l">
  32. <span>西医病名:</span>
  33. <div class="input">
  34. <el-select size="mini" v-model="searchData.westernDisease" placeholder="请搜索选择" clearable filterable :loading="westernDiseaseLoading" loading-text="搜索中..." :filter-method="searchWesternDisease" @focus="searchWesternDisease('')">
  35. <el-option v-for="(item, idx) in westernDiseaseOptions" :key="idx" :label="item.westname" :value="String(item.westcode)"></el-option>
  36. </el-select>
  37. </div>
  38. </div>
  39. <div class="screening-item flex-vertical-center-l">
  40. <span>专家:</span>
  41. <div class="input">
  42. <el-input size="mini" v-model="searchData.expert" placeholder="请输入" clearable></el-input>
  43. </div>
  44. </div>
  45. <div class="screening-btn-group">
  46. <el-button type="primary" size="mini" @click="search()">搜索</el-button>
  47. <el-button type="warning" size="mini" @click="clearFilter()">清空</el-button>
  48. <el-button type="primary" size="mini" @click="openEditDialog()">新增</el-button>
  49. </div>
  50. </div>
  51. </div>
  52. <!-- 底部表格数据 -->
  53. <div class="table">
  54. <div class="today-table">
  55. <div class="table-container">
  56. <el-table
  57. :data="tableData"
  58. stripe
  59. style="width: 100%"
  60. border
  61. height="100%"
  62. row-key="pid"
  63. :expand-row-keys="expandRows"
  64. @expand-change="handleExpandChange"
  65. v-loading="tableLoading"
  66. >
  67. <el-table-column type="expand">
  68. <template slot-scope="props">
  69. <div class="expand-table">
  70. <el-table :data="props.row.subList" border size="mini" style="width: 100%">
  71. <el-table-column prop="index" label="序号" width="60" align="center"></el-table-column>
  72. <el-table-column prop="treatItemName" label="项目名称" align="center"></el-table-column>
  73. <el-table-column label="穴位/部位/耳穴/经络" align="center">
  74. <template slot-scope="scope">
  75. {{ formatDetailList(scope.row) }}
  76. </template>
  77. </el-table-column>
  78. <el-table-column prop="isUpdate" label="是否可修改" width="100" align="center">
  79. <template slot-scope="scope">
  80. {{ scope.row.isUpdate === '1' ? '是' : '否' }}
  81. </template>
  82. </el-table-column>
  83. </el-table>
  84. </div>
  85. </template>
  86. </el-table-column>
  87. <el-table-column prop="index" label="序号" width="60" align="center"></el-table-column>
  88. <el-table-column prop="name" label="方名" min-width="120" align="center"></el-table-column>
  89. <el-table-column prop="efficacy" label="功效与适应症" min-width="180" align="center" show-overflow-tooltip></el-table-column>
  90. <el-table-column prop="chineseDisease" label="中医病名" width="120" align="center"></el-table-column>
  91. <el-table-column prop="cardType" label="证型" width="120" align="center"></el-table-column>
  92. <el-table-column prop="westernDisease" label="西医病名" width="120" align="center"></el-table-column>
  93. <el-table-column prop="expert" label="专家" width="100" align="center"></el-table-column>
  94. <el-table-column label="操作" width="180" align="center">
  95. <div class="flex-center operation" slot-scope="scope">
  96. <div class="flex-center" @click="openEditDialog(scope.row)">编辑</div>
  97. <div class="flex-center bg-yellow" @click="handleDelete(scope.row)">删除</div>
  98. <div class="flex-center" @click="handleView(scope.row)">查看</div>
  99. </div>
  100. </el-table-column>
  101. </el-table>
  102. </div>
  103. <div class="flex-vertical-center-r today-page">
  104. <el-pagination
  105. background
  106. layout=" prev, pager, next, jumper, total"
  107. :total="total"
  108. :page-size="limit"
  109. @current-change="sizeC($event)"
  110. ></el-pagination>
  111. </div>
  112. </div>
  113. </div>
  114. <!-- 新增/编辑弹窗 -->
  115. <popup
  116. distanceTop="5vh"
  117. fullscreen
  118. :showBtns="false"
  119. :showDialog.sync="showEditDialog"
  120. :loading="editLoading"
  121. :title="editData.pid ? '编辑本地专家适宜技术' : '新增本地专家适宜技术'"
  122. >
  123. <div slot="body" class="dialog-body-wrapper">
  124. <div class="dialog-back-btn" @click="handleEditBack">返回</div>
  125. <div class="dialog-scroll-area">
  126. <div class="dialog-form flex-wrap">
  127. <div class="form-item flex flex-col-center">
  128. <div class="name"><span style="color: red">*</span> 方名:</div>
  129. <div class="input">
  130. <el-input size="mini" v-model="editData.name" placeholder="请输入"></el-input>
  131. </div>
  132. </div>
  133. <div class="form-item flex flex-col-center">
  134. <span style="opacity:0">*</span>
  135. <div class="name">专家:</div>
  136. <div class="input">
  137. <el-input size="mini" v-model="editData.expert" placeholder="请输入"></el-input>
  138. </div>
  139. </div>
  140. <div class="form-item flex flex-col-center">
  141. <span style="opacity:0">*</span>
  142. <div class="name">专家名衔:</div>
  143. <div class="input">
  144. <el-select size="mini" v-model="editData.expertTitle" placeholder="请选择" clearable>
  145. <el-option
  146. :label="item.value"
  147. :value="item.value"
  148. v-for="(item,index) in titlesList"
  149. :key="index"
  150. ></el-option>
  151. </el-select>
  152. </div>
  153. </div>
  154. <div class="form-item flex flex-col-center">
  155. <span style="opacity:0">*</span>
  156. <div class="name">功效与适应症:</div>
  157. <div class="input">
  158. <el-input size="mini" v-model="editData.efficacy" placeholder="请输入"></el-input>
  159. </div>
  160. </div>
  161. <div class="form-item flex flex-col-center">
  162. <span style="opacity:0">*</span>
  163. <div class="name">中医病名:</div>
  164. <div class="input">
  165. <el-select size="mini" v-model="editData.chineseDisease" placeholder="请搜索选择" clearable filterable :loading="diseaseLoading" loading-text="搜索中..." :filter-method="searchDisease" @focus="searchDisease('')" @change="onDiseaseChange">
  166. <el-option v-for="(item, idx) in diseaseOptions" :key="idx" :label="item.disname" :value="String(item.disid)"></el-option>
  167. </el-select>
  168. </div>
  169. </div>
  170. <div class="form-item flex flex-col-center">
  171. <span style="opacity:0">*</span>
  172. <div class="name">证型:</div>
  173. <div class="input">
  174. <el-select size="mini" v-model="editData.symptomid" placeholder="请搜索选择" clearable filterable :loading="symptomLoading" loading-text="搜索中..." :filter-method="searchSymptom" @focus="searchSymptom('')" @change="onSymptomChange">
  175. <el-option v-for="(item, idx) in symptomOptions" :key="idx" :label="item.symname" :value="item.symid"></el-option>
  176. </el-select>
  177. </div>
  178. </div>
  179. <div class="form-item flex flex-col-center">
  180. <span style="opacity:0">*</span>
  181. <div class="name">治法:</div>
  182. <div class="input">
  183. <el-select size="mini" v-model="editData.therapyCode" placeholder="请搜索选择" clearable filterable :loading="therapyLoading" loading-text="搜索中..." :filter-method="searchTherapy" @focus="searchTherapy('')">
  184. <el-option v-for="(item, idx) in therapyOptions" :key="idx" :label="item.therapyName || item.therapy" :value="item.therapyCode"></el-option>
  185. </el-select>
  186. </div>
  187. </div>
  188. <div class="form-item flex flex-col-center">
  189. <span style="opacity:0">*</span>
  190. <div class="name">西医诊断:</div>
  191. <div class="input">
  192. <el-select size="mini" v-model="editData.westernDisease" placeholder="请搜索选择" clearable filterable multiple collapse-tags :loading="westernDiseaseLoading" loading-text="搜索中..." :filter-method="searchWesternDisease" @focus="searchWesternDisease('')" @change="onWesternDiseaseChange" popper-class="western-disease-select" @visible-change="onWesternDiseaseVisible"> <el-option v-for="(item, idx) in westernDiseaseOptions" :key="idx" :label="item.westname" :value="String(item.westcode)"></el-option>
  193. </el-select>
  194. </div>
  195. </div>
  196. <div class="form-item flex flex-col-center" style="width: 100%">
  197. <span style="opacity:0">*</span>
  198. <div class="name">方解:</div>
  199. <div class="input">
  200. <el-input size="mini" type="textarea" v-model="editData.mechanismPre" placeholder="请输入"></el-input>
  201. </div>
  202. </div>
  203. </div>
  204. <!-- 配穴表格 -->
  205. <AcupointTable
  206. ref="acupointTable"
  207. v-model="editData.acupoints"
  208. :isEditable.sync="editData.isEditable"
  209. :detailTypes.sync="editData.detailTypes"
  210. :statistics.sync="editData.statistics"
  211. :showGuide="true"
  212. @save="onAcupointSave"
  213. />
  214. </div>
  215. </div>
  216. </popup>
  217. <!-- 查看详情弹窗 -->
  218. <popup
  219. distanceTop="5vh"
  220. fullscreen
  221. :showBtns="false"
  222. :showDialog.sync="showViewDialog"
  223. :loading="viewLoading"
  224. title="查看本地专家适宜技术"
  225. >
  226. <div slot="body" class="dialog-body-wrapper">
  227. <div class="dialog-back-btn" @click="showViewDialog = false">返回</div>
  228. <div class="dialog-scroll-area">
  229. <div class="dialog-form flex-wrap">
  230. <div class="form-item flex flex-col-center">
  231. <div class="name">方名:</div>
  232. <div class="input">
  233. <el-input size="mini" v-model="viewData.name" placeholder="-" disabled></el-input>
  234. </div>
  235. </div>
  236. <div class="form-item flex flex-col-center">
  237. <div class="name">专家:</div>
  238. <div class="input">
  239. <el-input size="mini" v-model="viewData.expert" placeholder="-" disabled></el-input>
  240. </div>
  241. </div>
  242. <div class="form-item flex flex-col-center">
  243. <div class="name">专家名衔:</div>
  244. <div class="input">
  245. <el-input size="mini" v-model="viewData.expertTitle" placeholder="-" disabled></el-input>
  246. </div>
  247. </div>
  248. <div class="form-item flex flex-col-center">
  249. <div class="name">功效与适应症:</div>
  250. <div class="input">
  251. <el-input size="mini" v-model="viewData.effect" placeholder="-" disabled></el-input>
  252. </div>
  253. </div>
  254. <div class="form-item flex flex-col-center">
  255. <div class="name">中医病名:</div>
  256. <div class="input">
  257. <el-input size="mini" v-model="viewData.disName" placeholder="-" disabled></el-input>
  258. </div>
  259. </div>
  260. <div class="form-item flex flex-col-center">
  261. <div class="name">证型:</div>
  262. <div class="input">
  263. <el-input size="mini" v-model="viewData.synName" placeholder="-" disabled></el-input>
  264. </div>
  265. </div>
  266. <div class="form-item flex flex-col-center">
  267. <div class="name">治法:</div>
  268. <div class="input">
  269. <el-input size="mini" v-model="viewData.theName" placeholder="-" disabled></el-input>
  270. </div>
  271. </div>
  272. <div class="form-item flex flex-col-center">
  273. <div class="name">西医诊断:</div>
  274. <div class="input">
  275. <el-input size="mini" v-model="viewData.westernDiag" placeholder="-" disabled></el-input>
  276. </div>
  277. </div>
  278. <div class="form-item flex flex-col-center" style="width: 100%">
  279. <div class="name">方解:</div>
  280. <div class="input">
  281. <el-input size="mini" type="textarea" v-model="viewData.mechanismPre" placeholder="-" disabled></el-input>
  282. </div>
  283. </div>
  284. </div>
  285. <SuitableTechDetail ref="techDetail" :detailData="viewData" />
  286. </div>
  287. </div>
  288. </popup>
  289. </div>
  290. </template>
  291. <script>
  292. import popup from "@/components/Propup.vue";
  293. import AcupointTable from "./components/AcupointTable.vue";
  294. import {
  295. getDiseaseListMethod,
  296. getSymptomListMethod,
  297. getTherapyListMethod,
  298. } from "@/request/api.illness.js";
  299. import { getLocalSuitableTechList, deleteLocalSuitableTech, saveLocalSuitableTech, getLocalSuitableTechInfo, getMappedNondrugItemList } from "@/api/technology.js";
  300. import { getXDiseaseName } from "@/api/knowledge.js";
  301. import SuitableTechDetail from "./components/SuitableTechDetail.vue";
  302. export default {
  303. name: 'LocalExpertTech',
  304. components: { popup, AcupointTable, SuitableTechDetail },
  305. data() {
  306. return {
  307. searchData: {
  308. name: '',
  309. includeItem: '',
  310. chineseDisease: '',
  311. westernDisease: '',
  312. expert: ''
  313. },
  314. // 下拉选项
  315. diseaseOptions: [],
  316. westernDiseaseOptions: [],
  317. westernDiseasePage: 1,
  318. westernDiseaseHasMore: true,
  319. symptomOptions: [],
  320. therapyOptions: [],
  321. projectOptions: [],
  322. titlesList: [
  323. { key: "1", value: "国医大师" },
  324. { key: "2", value: "国家级名老中医" },
  325. { key: "3", value: "省级名老中医" },
  326. { key: "4", value: "名医" }
  327. ],
  328. // 下拉加载状态
  329. diseaseLoading: false,
  330. westernDiseaseLoading: false,
  331. symptomLoading: false,
  332. therapyLoading: false,
  333. projectLoading: false,
  334. // 表格数据
  335. tableData: [],
  336. tableLoading: false,
  337. expandRows: [],
  338. page: 1,
  339. limit: 10,
  340. total: 0,
  341. // 弹窗相关
  342. showEditDialog: false,
  343. editLoading: false,
  344. editData: {
  345. statistics: {}
  346. },
  347. // 查看详情弹窗
  348. showViewDialog: false,
  349. viewLoading: false,
  350. viewData: {},
  351. }
  352. },
  353. created() {
  354. this.getList()
  355. },
  356. methods: {
  357. // ========== 中医病名搜索 ==========
  358. async searchDisease(query) {
  359. this.diseaseLoading = true
  360. try {
  361. const { list } = await getDiseaseListMethod(1, 9999, { keyword: query || '' })
  362. this.diseaseOptions = list || []
  363. } catch (e) {
  364. console.error('搜索中医病名失败', e)
  365. } finally {
  366. this.diseaseLoading = false
  367. }
  368. },
  369. onDiseaseChange(disid) {
  370. const selected = this.diseaseOptions.find(item => item.disid === disid)
  371. if (selected) {
  372. this.editData.chineseDiseaseName = selected.disname
  373. this.editData.disCode = selected.disCode
  374. }
  375. // 联动清空证型
  376. this.editData.symptomid = ''
  377. this.editData.symptomName = ''
  378. this.symptomOptions = []
  379. // 联动清空治法
  380. this.editData.therapyCode = ''
  381. this.editData.therapyName = ''
  382. this.therapyOptions = []
  383. if (disid) {
  384. this.searchSymptom('')
  385. }
  386. },
  387. // ========== 证型搜索 ==========
  388. async searchSymptom(query) {
  389. this.symptomLoading = true
  390. try {
  391. const params = { keyword: query || '' }
  392. if (this.editData.chineseDisease) params.disid = this.editData.chineseDisease
  393. if (this.editData.disCode) params.disCode = this.editData.disCode
  394. const { list } = await getSymptomListMethod(1, 9999, params)
  395. this.symptomOptions = list || []
  396. } catch (e) {
  397. console.error('搜索证型失败', e)
  398. } finally {
  399. this.symptomLoading = false
  400. }
  401. },
  402. onSymptomChange(symid) {
  403. const selected = this.symptomOptions.find(item => item.symid === symid)
  404. if (selected) {
  405. this.editData.symptomName = selected.symname
  406. }
  407. // 联动清空治法
  408. this.editData.therapyCode = ''
  409. this.editData.therapyName = ''
  410. this.therapyOptions = []
  411. if (symid) {
  412. this.searchTherapy('')
  413. }
  414. },
  415. // ========== 治法搜索 ==========
  416. async searchTherapy(query) {
  417. this.therapyLoading = true
  418. try {
  419. const params = { keyword: query || '' }
  420. if (this.editData.disCode) params.disCode = this.editData.disCode
  421. if (this.editData.symptomid) {
  422. params.symptomCode = this.editData.symptomid
  423. params.symid = this.editData.symptomid
  424. }
  425. const { list } = await getTherapyListMethod(1, 9999, params)
  426. this.therapyOptions = list || []
  427. } catch (e) {
  428. console.error('搜索治法失败', e)
  429. } finally {
  430. this.therapyLoading = false
  431. }
  432. },
  433. // ========== 西医诊断搜索(分页) ==========
  434. async searchWesternDisease(query) {
  435. this.westernDiseasePage = 1
  436. this.westernDiseaseHasMore = true
  437. this._wdQuery = query || ''
  438. this.westernDiseaseLoading = true
  439. try {
  440. const pinyin = /^[A-Za-z]+$/g
  441. const serchtype = query && pinyin.test(query) ? '1' : ''
  442. const res = await getXDiseaseName({
  443. pageid: 1,
  444. pagesize: 200,
  445. keyword: query || '',
  446. serchtype,
  447. })
  448. if (res.code == 0) {
  449. const list = res.data?.wests || []
  450. // 保留已选中但不在搜索结果中的选项
  451. const selectedCodes = Array.isArray(this.editData.westernDisease)
  452. ? this.editData.westernDisease
  453. : []
  454. const resultCodes = new Set(list.map(i => i.westcode))
  455. const preserved = selectedCodes
  456. .filter(code => !resultCodes.has(code))
  457. .map(code => ({
  458. westcode: code,
  459. westname: this.westernDiseaseNameMap[code] || code,
  460. }))
  461. this.westernDiseaseOptions = [...preserved, ...list]
  462. this.westernDiseaseHasMore = list.length >= 200
  463. }
  464. } catch (e) {
  465. console.error('搜索西医诊断失败', e)
  466. } finally {
  467. this.westernDiseaseLoading = false
  468. }
  469. },
  470. async loadMoreWesternDisease() {
  471. if (this.westernDiseaseLoading || !this.westernDiseaseHasMore) return
  472. this.westernDiseasePage++
  473. this.westernDiseaseLoading = true
  474. try {
  475. const query = this._wdQuery || ''
  476. const pinyin = /^[A-Za-z]+$/g
  477. const serchtype = query && pinyin.test(query) ? '1' : ''
  478. const res = await getXDiseaseName({
  479. pageid: this.westernDiseasePage,
  480. pagesize: 200,
  481. keyword: query,
  482. serchtype,
  483. })
  484. if (res.code == 0) {
  485. const list = res.data?.wests || []
  486. this.westernDiseaseOptions = this.westernDiseaseOptions.concat(list)
  487. this.westernDiseaseHasMore = list.length >= 200
  488. }
  489. } catch (e) {
  490. console.error('加载更多西医诊断失败', e)
  491. } finally {
  492. this.westernDiseaseLoading = false
  493. }
  494. },
  495. onWesternDiseaseVisible(show) {
  496. const wrap = document.querySelector(".western-disease-select .el-scrollbar__wrap")
  497. if (!wrap) return
  498. if (show) {
  499. wrap.addEventListener("scroll", this._wdScroll = () => {
  500. if (wrap.scrollHeight - wrap.scrollTop <= wrap.clientHeight + 5) {
  501. this.loadMoreWesternDisease()
  502. }
  503. })
  504. } else {
  505. wrap.removeEventListener("scroll", this._wdScroll)
  506. }
  507. },
  508. onWesternDiseaseChange(codes) {
  509. const names = codes
  510. .map(code => {
  511. const item = this.westernDiseaseOptions.find(o => o.westcode === code)
  512. return item ? item.westname : ''
  513. })
  514. .filter(Boolean)
  515. this.editData.westernDiseaseName = names.join(',')
  516. },
  517. // 涵盖项目远程模糊搜索
  518. async searchProject(query) {
  519. this.projectLoading = true
  520. try {
  521. const res = await getMappedNondrugItemList({
  522. pageNum: 1,
  523. pageSize: 100,
  524. itemName: query || undefined,
  525. })
  526. if (res.ResultCode === 0) {
  527. this.projectOptions = res.Data?.Items || res.Data || []
  528. }
  529. } catch (e) {
  530. console.error('搜索涵盖项目失败', e)
  531. } finally {
  532. this.projectLoading = false
  533. }
  534. },
  535. search() {
  536. this.page = 1
  537. this.getList()
  538. },
  539. clearFilter() {
  540. this.searchData = {
  541. name: '',
  542. includeItem: '',
  543. chineseDisease: '',
  544. westernDisease: '',
  545. expert: ''
  546. }
  547. this.getList()
  548. },
  549. sizeC(e) {
  550. this.page = e
  551. this.getList()
  552. },
  553. handleExpandChange(row, expandedRows) {
  554. this.expandRows = expandedRows.map(item => item.pid)
  555. },
  556. formatDetailList(row) {
  557. const parts = []
  558. const extractNames = (arr) => {
  559. if (!Array.isArray(arr) || !arr.length) return ''
  560. return arr.map(item => item.name || '').filter(Boolean).join('、')
  561. }
  562. const pointNames = extractNames(row.detailPoint)
  563. if (pointNames) parts.push('穴位: ' + pointNames)
  564. const meridianNames = extractNames(row.detailMeridian)
  565. if (meridianNames) parts.push('经络: ' + meridianNames)
  566. const earNames = extractNames(row.detailEarPoint)
  567. if (earNames) parts.push('耳穴: ' + earNames)
  568. const bodyNames = extractNames(row.detailBodyPart)
  569. if (bodyNames) parts.push('部位: ' + bodyNames)
  570. const otherNames = extractNames(row.detailOther)
  571. if (otherNames) parts.push('其他: ' + otherNames)
  572. return parts.join(';') || '-'
  573. },
  574. handleEditBack() {
  575. if (!this.editData.pid) {
  576. this.$confirm("协定方未保存,返回则会清空当前编辑内容", "提示", {
  577. confirmButtonText: "确定",
  578. cancelButtonText: "取消",
  579. type: "warning",
  580. })
  581. .then(() => {
  582. this.showEditDialog = false;
  583. })
  584. .catch(() => {});
  585. } else {
  586. this.showEditDialog = false;
  587. }
  588. },
  589. async openEditDialog(row) {
  590. if (row) {
  591. this.editLoading = true
  592. let detail = row
  593. try {
  594. const res = await getLocalSuitableTechInfo(row.pid)
  595. if (res.ResultCode === 0) {
  596. detail = res.Data || row
  597. }
  598. } catch (e) {
  599. console.error('获取详情失败', e)
  600. } finally {
  601. this.editLoading = false
  602. }
  603. this.diseaseOptions = detail.disId != null
  604. ? [{ disid: detail.disId, disname: detail.disName, disCode: detail.disCode }]
  605. : []
  606. this.symptomOptions = detail.synCode != null
  607. ? [{ symid: detail.synCode, symname: detail.synName, synCode: detail.synCode }]
  608. : []
  609. this.therapyOptions = detail.theCode != null
  610. ? [{ therapyCode: detail.theCode, therapy: detail.theName, therapyName: detail.theName }]
  611. : []
  612. if (detail.westernCode) {
  613. const codes = String(detail.westernCode).split(',')
  614. const names = detail.westernDiag
  615. ? String(detail.westernDiag).split(',')
  616. : []
  617. this.westernDiseaseOptions = codes.map((code, idx) => ({
  618. westcode: code.trim(),
  619. westname: (names[idx] || '').trim() || code.trim(),
  620. }))
  621. } else {
  622. this.westernDiseaseOptions = []
  623. }
  624. this.editData = {
  625. ...detail,
  626. efficacy: detail.effect || '',
  627. chineseDisease: detail.disId != null ? String(detail.disId) : '',
  628. chineseDiseaseName: detail.disName || '',
  629. disCode: detail.disCode || '',
  630. symptomid: detail.synCode != null ? String(detail.synCode) : '',
  631. symptomName: detail.synName || '',
  632. symptomCode: detail.synCode || '',
  633. therapyCode: detail.theCode != null ? String(detail.theCode) : '',
  634. therapyName: detail.theName || '',
  635. westernDisease: detail.westernCode
  636. ? String(detail.westernCode).split(',').map(s => s.trim())
  637. : [],
  638. westernDiseaseName: detail.westernDiag || '',
  639. acupoints: detail.acupoints || [],
  640. detailTypes: detail.detailTypes || ['穴位'],
  641. statistics: detail.statistics || {},
  642. }
  643. this.showEditDialog = true
  644. // 弹窗显示后回填 treatmentList 到 AcupointTable
  645. this.$nextTick(() => {
  646. if (this.$refs.acupointTable && detail.treatmentList) {
  647. this.$refs.acupointTable.loadFromServerData(detail.treatmentList)
  648. }
  649. })
  650. } else {
  651. this.editData = {
  652. name: '',
  653. treater: '',
  654. expert: '',
  655. expertTitle: '',
  656. efficacy: '',
  657. chineseDisease: '',
  658. chineseDiseaseName: '',
  659. disCode: '',
  660. symptomid: '',
  661. symptomName: '',
  662. symptomCode: '',
  663. therapyCode: '',
  664. therapyName: '',
  665. westernDisease: [],
  666. westernDiseaseName: '',
  667. mechanismPre: '',
  668. isEditable: '1',
  669. detailTypes: ['穴位'],
  670. acupoints: [],
  671. statistics: {},
  672. }
  673. this.showEditDialog = true
  674. }
  675. },
  676. async submitEditData() {
  677. // 保存
  678. this.$refs.acupointTable.handleSave()
  679. },
  680. async onAcupointSave(treatmentList, institutionInfo) {
  681. // 从下拉选项中查找选中项的名称
  682. const selectedDisease = this.diseaseOptions.find(
  683. o => o.disid === this.editData.chineseDisease
  684. )
  685. const selectedSymptom = this.symptomOptions.find(
  686. o => o.symid === this.editData.symptomid
  687. )
  688. const selectedTherapy = this.therapyOptions.find(
  689. o => o.therapyCode === this.editData.therapyCode
  690. )
  691. const params = {
  692. name: this.editData.name,
  693. // 本地专家特有字段
  694. treater: this.editData.treater || '',
  695. expert: this.editData.expert || '',
  696. expertTitle: this.editData.expertTitle || '',
  697. mechanismPre: this.editData.mechanismPre || '',
  698. // 基本信息
  699. effect: this.editData.efficacy || '',
  700. // 疾病信息
  701. disId: this.editData.chineseDisease || undefined,
  702. disName: selectedDisease
  703. ? selectedDisease.disname
  704. : this.editData.chineseDiseaseName || '',
  705. disCode: this.editData.disCode || '',
  706. // 证型信息
  707. synName: selectedSymptom
  708. ? selectedSymptom.symname
  709. : this.editData.symptomName || '',
  710. synCode: this.editData.symptomid || '',
  711. // 治法信息
  712. theCode: this.editData.therapyCode || '',
  713. theName: selectedTherapy
  714. ? selectedTherapy.therapy
  715. : this.editData.therapyName || '',
  716. // 西医诊断
  717. westernCode: Array.isArray(this.editData.westernDisease)
  718. ? this.editData.westernDisease.join(',')
  719. : (this.editData.westernDisease || ''),
  720. westernDiag: this.editData.westernDiseaseName || '',
  721. // 总金额
  722. totalAmount: parseFloat(this.editData.statistics?.totalPrice) || 0,
  723. // 机构信息
  724. ygtid: institutionInfo?.ygtid || this.editData.ygtid || '',
  725. departmentId: institutionInfo?.departmentId || this.editData.departmentId || '',
  726. stitutionsId: institutionInfo?.stitutionsId || this.editData.stitutionsId || '',
  727. stitutionsName: institutionInfo?.stitutionsName || this.editData.stitutionsName || '',
  728. // 治疗明细项列表
  729. treatmentList,
  730. }
  731. if (this.editData.pid) {
  732. params.pid = this.editData.pid
  733. }
  734. try {
  735. const res = await saveLocalSuitableTech(params)
  736. if (res.ResultCode === 0) {
  737. this.$message.success(this.editData.pid ? '编辑成功' : '新增成功')
  738. this.showEditDialog = false
  739. this.getList()
  740. }
  741. } catch (e) {
  742. console.error('保存失败', e)
  743. } finally {
  744. if (this.$refs.acupointTable) {
  745. this.$refs.acupointTable.saveDone()
  746. }
  747. }
  748. },
  749. handleDelete(row) {
  750. this.$confirm('确认删除该条记录?', '提示', {
  751. confirmButtonText: '确定',
  752. cancelButtonText: '取消',
  753. type: 'warning'
  754. }).then(async () => {
  755. try {
  756. const res = await deleteLocalSuitableTech({ pid: row.pid })
  757. if (res.ResultCode === 0) {
  758. this.$message.success('删除成功')
  759. this.getList()
  760. } else {
  761. this.$message.error(res.ResultInfo || '删除失败')
  762. }
  763. } catch (e) {
  764. console.error('删除失败', e)
  765. this.$message.error('删除失败')
  766. }
  767. }).catch(() => {})
  768. },
  769. async handleView(row) {
  770. this.viewLoading = true
  771. this.showViewDialog = true
  772. this.viewData = {}
  773. try {
  774. const res = await getLocalSuitableTechInfo(row.pid)
  775. if (res.ResultCode === 0) {
  776. this.viewData = res.Data || {}
  777. }
  778. } catch (e) {
  779. console.error('获取详情失败', e)
  780. } finally {
  781. this.viewLoading = false
  782. }
  783. },
  784. async getList() {
  785. this.tableLoading = true
  786. try {
  787. const params = {
  788. pageNum: this.page,
  789. pageSize: this.limit,
  790. name: this.searchData.name || undefined,
  791. coverageContent: this.searchData.includeItem || undefined,
  792. disId: this.searchData.chineseDisease || undefined,
  793. disName: this.searchData.chineseDisease
  794. ? (this.diseaseOptions.find(o => String(o.disid) === this.searchData.chineseDisease)?.disname || undefined)
  795. : undefined,
  796. expert: this.searchData.expert || undefined,
  797. westernCode: this.searchData.westernDisease || undefined,
  798. westernDiag: this.searchData.westernDisease
  799. ? (this.westernDiseaseOptions.find(o => String(o.westcode) === this.searchData.westernDisease)?.westname || undefined)
  800. : undefined,
  801. }
  802. const res = await getLocalSuitableTechList(params)
  803. if (res.ResultCode === 0) {
  804. const list = res.Data?.Items || []
  805. this.total = res.Data?.totalRecordCount || 0
  806. this.tableData = list.map((item, index) => ({
  807. ...item,
  808. index: (this.page - 1) * this.limit + index + 1,
  809. efficacy: item.effect || '',
  810. chineseDisease: item.disName || '',
  811. cardType: item.synName || '',
  812. westernDisease: item.westernDiag || '',
  813. expert: item.expert || '',
  814. subList: (item.treatmentList || []).map((sub, sIdx) => ({
  815. ...sub,
  816. index: sIdx + 1,
  817. })),
  818. }))
  819. }
  820. } catch (e) {
  821. console.error('获取列表失败', e)
  822. } finally {
  823. this.tableLoading = false
  824. }
  825. }
  826. }
  827. }
  828. </script>
  829. <style lang="scss" scoped>
  830. @import "../../style/common.scss";
  831. @import "../../style/base.scss";
  832. .local-expert-tech {
  833. width: 100%;
  834. height: 100%;
  835. }
  836. .local-expert-tech ::v-deep .popup-container {
  837. display: flex;
  838. flex-direction: column;
  839. overflow: hidden !important;
  840. height: calc(100vh - 60px) !important;
  841. }
  842. .dialog-body-wrapper {
  843. display: flex;
  844. flex-direction: column;
  845. height: 100%;
  846. }
  847. .dialog-back-btn {
  848. display: flex;
  849. align-items: center;
  850. justify-content: center;
  851. font-size: 14px;
  852. color: #333;
  853. background: #fff;
  854. border: 1px solid #ccc;
  855. border-radius: 4px;
  856. padding: 4px 16px;
  857. cursor: pointer;
  858. margin-bottom: 10px;
  859. width: fit-content;
  860. &:hover {
  861. opacity: 0.8;
  862. }
  863. }
  864. .dialog-form {
  865. display: flex;
  866. flex-wrap: wrap;
  867. flex-shrink: 0;
  868. .form-item {
  869. width: 25%;
  870. box-sizing: border-box;
  871. padding: 0 10px;
  872. margin-bottom: 10px;
  873. .name {
  874. width: 110px;
  875. min-width: 110px;
  876. font-size: 14px;
  877. white-space: nowrap;
  878. text-align: right;
  879. margin-right: 5px;
  880. }
  881. .input {
  882. flex: 1;
  883. min-width: 0;
  884. .el-input,
  885. .el-select {
  886. width: 100%;
  887. }
  888. }
  889. }
  890. }
  891. .dialog-scroll-area {
  892. flex: 1;
  893. min-height: 0;
  894. overflow: hidden;
  895. display: flex;
  896. flex-direction: column;
  897. }
  898. .dialog-scroll-area ::v-deep .recipe-acupoint-wrapper {
  899. flex: 1;
  900. display: flex;
  901. flex-direction: column;
  902. min-height: 0;
  903. }
  904. .dialog-scroll-area ::v-deep .recipe-acupoint {
  905. flex: 1;
  906. min-height: 0;
  907. }
  908. .screening-btn-group {
  909. display: flex;
  910. align-items: center;
  911. margin: 5px 0;
  912. }
  913. .expand-table {
  914. padding: 10px 20px;
  915. }
  916. .table {
  917. padding: 10px 10px;
  918. background: #ffffff;
  919. border-radius: 5px;
  920. margin-top: 10px;
  921. height: 70vh;
  922. .table-container {
  923. height: 90%;
  924. }
  925. .today-table {
  926. height: 100%;
  927. .operation {
  928. display: flex;
  929. align-items: center;
  930. justify-content: space-around;
  931. flex-wrap: wrap;
  932. div {
  933. width: 60px;
  934. height: 24px;
  935. background: #5386f6;
  936. border-radius: 2px;
  937. font-size: 14px;
  938. font-family: PingFang SC;
  939. font-weight: 400;
  940. color: #ffffff;
  941. cursor: pointer;
  942. margin: 5px 0;
  943. }
  944. .bg-yellow {
  945. width: 60px;
  946. height: 24px;
  947. background: #ffae45;
  948. }
  949. }
  950. }
  951. }
  952. </style>
  953. <style lang="scss" scoped>
  954. @media screen and (min-width: 1681px) and (max-width: 1920px) {
  955. .table {
  956. height: 81vh;
  957. .table-container {
  958. height: 94%;
  959. }
  960. }
  961. .today-table::v-deep .el-table td {
  962. padding: 18px 0;
  963. }
  964. .today-table::v-deep .el-table th {
  965. padding: 18px 0;
  966. }
  967. }
  968. @media screen and (min-width: 1601px) and (max-width: 1680px) {
  969. .table {
  970. height: 80vh;
  971. .table-container {
  972. height: 94%;
  973. }
  974. }
  975. .today-table::v-deep .el-table td {
  976. padding: 18px 0;
  977. }
  978. .today-table::v-deep .el-table th {
  979. padding: 18px 0;
  980. }
  981. }
  982. @media screen and (min-width: 1361px) and (max-width: 1600px) {
  983. .table {
  984. height: 72vh;
  985. .table-container {
  986. height: 90%;
  987. }
  988. }
  989. .today-table::v-deep .el-table td {
  990. padding: 5px 0;
  991. }
  992. .today-table::v-deep .el-table th {
  993. padding: 5px 0;
  994. }
  995. }
  996. @media screen and (min-width: 1281px) and (max-width: 1360px) {
  997. .table {
  998. height: 72vh;
  999. .table-container {
  1000. height: 90%;
  1001. }
  1002. }
  1003. .today-table::v-deep .el-table td {
  1004. padding: 5px 0;
  1005. }
  1006. .today-table::v-deep .el-table th {
  1007. padding: 5px 0;
  1008. }
  1009. }
  1010. </style>