DoctorInfoManagement.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441
  1. <template>
  2. <page-layer>
  3. <template #header>
  4. <el-select placeholder="医生科室" v-model="queryParam.deptCode" filterable clearable>
  5. <el-option v-for="item in allMzDepts" :key="item.code" :value="item.code" :label="item.name"></el-option>
  6. </el-select>
  7. <el-input v-model="queryParam.doctorName" style="width: 120px" placeholder="医生姓名" clearable></el-input>
  8. <el-checkbox v-model="queryParam.includeDeleted" style="margin-left: 4px">搜索已删除医生</el-checkbox>
  9. <el-divider direction="vertical"></el-divider>
  10. <el-button type="primary" icon="Search" @click="fetchDoctors">查询</el-button>
  11. <el-button type="primary" icon="Refresh" @click="resetSearch">重置</el-button>
  12. <el-divider direction="vertical"></el-divider>
  13. <el-button icon="Top" @click="manageRecommendOrder">服务号推荐排序</el-button>
  14. <el-button icon="Warning" type="danger" @click="refreshWxDoctorCache">刷新服务号医生数据</el-button>
  15. </template>
  16. <template #main>
  17. <el-table :data="allDoctors" :height="tableHeight" stripe highlight-current-row>
  18. <el-table-column type="index" label="序号" width="50"></el-table-column>
  19. <el-table-column prop="code" label="编号" width="70"></el-table-column>
  20. <el-table-column prop="name" label="姓名" width="80"></el-table-column>
  21. <el-table-column prop="sexName" label="性别" width="60"></el-table-column>
  22. <el-table-column prop="deptName" label="科室" width="160"></el-table-column>
  23. <el-table-column prop="titleName" label="级别" width="160"></el-table-column>
  24. <el-table-column label="服务号首页推荐" width="160">
  25. <template v-slot="scope">
  26. <el-select v-model="scope.row.wxHomepageFlag" style="width: 100px" @change="(wxHomepageFlag) => handleSelectRec(scope.row.code, wxHomepageFlag)">
  27. <el-option v-for="item in yesOrNo" :key="item.code" :value="item.code" :label="item.name"> </el-option>
  28. </el-select>
  29. </template>
  30. </el-table-column>
  31. <el-table-column label="操作">
  32. <template v-slot="scope">
  33. <el-button @click="viewDoctor(scope.row)" text icon="View" type="success">查看</el-button>
  34. <el-divider direction="vertical"></el-divider>
  35. <el-button @click="editDoctor(scope.row)" text icon="Edit" type="primary">编辑</el-button>
  36. <el-divider direction="vertical"></el-divider>
  37. <el-button v-if="queryParam.includeDeleted" @click="undoDelete(scope.row)" text icon="Check" type="info">恢复</el-button>
  38. <el-button v-else @click="beforeDelete(scope.row)" text icon="Delete" type="danger">删除</el-button>
  39. </template>
  40. </el-table-column>
  41. </el-table>
  42. <el-pagination
  43. @size-change="handleSizeChange"
  44. @current-change="handleCurrentChange"
  45. :current-page="queryParam.currentPage"
  46. :page-sizes="[15, 30, 45]"
  47. :page-size="queryParam.pageSize"
  48. layout="total, sizes, prev, pager, next, jumper"
  49. :total="totalSize"
  50. style="margin-top: 5px"
  51. ></el-pagination>
  52. <el-drawer title="" v-model="drawer" size="40%" :with-header="false">
  53. <div v-if="edit" :style="editStyle" class="edit-doctor-info">
  54. <div>基本信息</div>
  55. <div>
  56. 医生头像:请上传一寸照
  57. <el-upload
  58. class="upload-demo"
  59. ref="upload"
  60. :action="apiUrl + '/manageDoctorInfo/uploadPortrait'"
  61. :headers="header"
  62. :file-list="fileList"
  63. :limit="1"
  64. :data="{ code: doctor.code }"
  65. :on-exceed="fileSizeOutLimit"
  66. :before-upload="beforeAvatarUpload"
  67. :on-success="uploadSuccess"
  68. :on-error="uploadError"
  69. :auto-upload="false"
  70. >
  71. <template #trigger>
  72. <el-button type="primary" icon="Picture">选取文件</el-button>
  73. </template>
  74. <el-button style="margin-left: 10px" type="success" icon="Upload" @click="submitUpload">上传到服务器</el-button>
  75. <template #tip>
  76. <div class="el-upload__tip">只能上传 jpg/png 文件,且不超过 500kb</div>
  77. </template>
  78. </el-upload>
  79. </div>
  80. <div style="margin-top: 130px">
  81. <span class="require">*</span>
  82. 医生编号:<el-input class="w200" v-model="doctor.code" disabled></el-input>
  83. </div>
  84. <div>
  85. <span class="require">*</span>
  86. 医生姓名:<el-input class="w200" v-model="doctor.name"></el-input>
  87. </div>
  88. <div>
  89. <span class="require">*</span>
  90. 医生性别:
  91. <el-select v-model="doctor.sex" class="w200">
  92. <el-option v-for="item in sexCodes" :key="item.code" :label="item.name" :value="item.code"></el-option>
  93. </el-select>
  94. </div>
  95. <div>
  96. <span class="require">*</span>
  97. 所属科室:
  98. <el-select v-model="doctor.deptCode" class="w200" clearable filterable>
  99. <el-option v-for="item in allMzDepts" :key="item.code" :label="item.name" :value="item.code"></el-option>
  100. </el-select>
  101. </div>
  102. <div>
  103. <span class="require">*</span>
  104. 医生职称:
  105. <el-select v-model="doctor.titleCode" class="w200" clearable filterable>
  106. <el-option v-for="item in allTitles" :key="item.code" :label="item.name" :value="item.code"></el-option>
  107. </el-select>
  108. </div>
  109. <div style="height: max-content; margin-top: 15px">
  110. <span class="require">*</span>
  111. 医生擅长:
  112. <el-input class="w350" style="font-size: 13px" type="textarea" rows="3" maxlength="100" show-word-limit v-model="doctor.specialty"></el-input>
  113. </div>
  114. <div style="height: max-content; margin-top: 15px">
  115. <span>&nbsp;&nbsp;</span>
  116. 医生介绍:
  117. <el-input class="w350" style="font-size: 13px" type="textarea" rows="7" maxlength="300" show-word-limit v-model="doctor.introduction"></el-input>
  118. </div>
  119. <div style="margin-top: 15px">
  120. <span>&nbsp;&nbsp;</span>
  121. <el-button type="primary" @click="saveDoctor" icon="Money">保存</el-button>
  122. <el-button plain @click="drawer = false" icon="Close">取消</el-button>
  123. </div>
  124. </div>
  125. <div v-else>
  126. <div style="height: 80px; width: 100%; background: #36af6b"></div>
  127. <div>
  128. <div class="inline pl15">
  129. <el-avatar src="https://empty" shape="circle" style="width: 88px; height: 120px; margin-top: -60px" @error="avatarError">
  130. <img :src="'data:image/png;base64,' + doctor.portrait" />
  131. </el-avatar>
  132. </div>
  133. <div class="inline child-pl15" style="width: calc(100% - 150px)">
  134. <div style="margin-top: -30px; font-size: 16px; font-weight: bold; color: white">
  135. {{ doctor.name }}
  136. </div>
  137. <div class="doctor-info">
  138. <div>医生编号:{{ doctor.code }}</div>
  139. <div>医生性别:{{ doctor.sexName }}</div>
  140. <div>所属科室:{{ doctor.deptName }}</div>
  141. <div>医生职称:{{ doctor.titleName }}</div>
  142. <div class="info-title">医生擅长:</div>
  143. <div style="height: max-content">
  144. {{ doctor.specialty }}
  145. </div>
  146. <div class="info-title">医生介绍:</div>
  147. <div style="height: max-content">
  148. {{ doctor.introduction }}
  149. </div>
  150. </div>
  151. </div>
  152. </div>
  153. </div>
  154. </el-drawer>
  155. <el-dialog title="服务号推荐排序" width="60%" v-model="manageOrderDialog">
  156. <el-table :data="recommendDoctors" :height="tableHeight - 200" stripe highlight-current-row>
  157. <el-table-column prop="wxHomepageOrder" label="当前排序"></el-table-column>
  158. <el-table-column prop="code" label="编号"></el-table-column>
  159. <el-table-column prop="name" label="姓名"></el-table-column>
  160. <el-table-column prop="sexName" label="性别"></el-table-column>
  161. <el-table-column prop="deptName" label="科室"></el-table-column>
  162. <el-table-column prop="titleName" label="级别"></el-table-column>
  163. <el-table-column label="操作">
  164. <template v-slot="scope">
  165. <el-button type="primary" plain icon="Edit" @click="modifyOrder(scope.row)">修改排序</el-button>
  166. </template>
  167. </el-table-column>
  168. </el-table>
  169. </el-dialog>
  170. </template>
  171. </page-layer>
  172. </template>
  173. <script setup name="DoctorInfoManagement">
  174. import { onMounted, reactive, ref } from 'vue'
  175. import {
  176. updateDoctorStatus,
  177. getAllDoctors,
  178. getAllMzDept,
  179. getAllRecommendDoctors,
  180. getAllTitles,
  181. getDoctorInfo,
  182. saveDoctorInfo,
  183. updateDoctorWxHomepageFlag,
  184. updateWxHomepageOrder,
  185. } from '@/api/manage-doctor-info'
  186. import store from '@/store'
  187. import { ElMessage, ElMessageBox } from 'element-plus'
  188. import axios from 'axios'
  189. import PageLayer from "@/layout/PageLayer";
  190. const apiUrl = import.meta.env.VITE_BASE_URL
  191. const windowSize = store.state.app.windowSize
  192. const tableHeight = windowSize.h - 50
  193. const allTitles = ref([])
  194. const allMzDepts = ref([])
  195. const allDoctors = ref([])
  196. const totalSize = ref(0)
  197. const yesOrNo = [
  198. { code: 1, name: '推荐' },
  199. { code: 0, name: '不推荐' },
  200. ]
  201. const sexCodes = [
  202. { code: 1, name: '男' },
  203. { code: 2, name: '女' },
  204. { code: 9, name: '未知' },
  205. ]
  206. const queryParam = reactive({
  207. deptCode: '',
  208. doctorName: '',
  209. includeDeleted: false,
  210. currentPage: 1,
  211. pageSize: 15,
  212. })
  213. const handleSizeChange = (val) => {
  214. queryParam.pageSize = val
  215. fetchDoctors()
  216. }
  217. const handleCurrentChange = (val) => {
  218. queryParam.currentPage = val
  219. fetchDoctors()
  220. }
  221. const resetSearch = () => {
  222. queryParam.deptCode = queryParam.doctorName = ''
  223. fetchDoctors()
  224. }
  225. const handleSelectRec = (code, flag) => {
  226. updateDoctorWxHomepageFlag(code, flag).then(() => {
  227. const message = flag === 1 ? '已在微信服务号首页推荐此医生。' : '已取消此医生在微信服务号首页的推荐。'
  228. const type = flag === 1 ? 'success' : 'warning'
  229. ElMessage({
  230. message,
  231. type,
  232. duration: 3000,
  233. showClose: true,
  234. })
  235. })
  236. }
  237. const viewDoctor = (val) => {
  238. getDoctorInfo(val.code).then((res) => {
  239. doctor.value = res
  240. edit.value = false
  241. drawer.value = true
  242. })
  243. }
  244. const editDoctor = (val) => {
  245. getDoctorInfo(val.code).then((res) => {
  246. doctor.value = res
  247. edit.value = true
  248. drawer.value = true
  249. })
  250. }
  251. const doctor = ref({})
  252. const editStyle = {
  253. padding: '0 15px 15px 50px',
  254. height: window.innerHeight + 'px',
  255. overflowY: 'scroll',
  256. }
  257. const edit = ref(true)
  258. const drawer = ref(false)
  259. const fileList = ref([])
  260. const beforeAvatarUpload = (file) => {
  261. const isJPG = file.type === 'image/jpeg' || file.type === 'image/png'
  262. const isLt1M = file.size / 1024 / 1024 < 1
  263. if (!isJPG) {
  264. ElMessage.error('上传头像图片只能是 jpg 或 png 格式!')
  265. }
  266. if (!isLt1M) {
  267. ElMessage.error('上传头像图片大小不能超过 1MB!')
  268. }
  269. return isJPG && isLt1M
  270. }
  271. const upload = ref(null)
  272. const header = {
  273. token: store.state.user.token,
  274. }
  275. const submitUpload = () => {
  276. upload.value.submit()
  277. }
  278. const fileSizeOutLimit = () => {
  279. ElMessage.error('已经选取头像,如须更换请先移除已选取的头像!')
  280. }
  281. const uploadSuccess = () => {
  282. ElMessage.success('上传成功!')
  283. }
  284. const uploadError = () => {
  285. ElMessage.error('上传失败!')
  286. }
  287. const saveDoctor = () => {
  288. saveDoctorInfo(doctor.value).then(() => {
  289. ElMessage.success('保存成功')
  290. })
  291. }
  292. const avatarError = () => {
  293. return true
  294. }
  295. const manageOrderDialog = ref(false)
  296. const recommendDoctors = ref([])
  297. const manageRecommendOrder = () => {
  298. getAllRecommendDoctors().then((res) => {
  299. recommendDoctors.value = res
  300. manageOrderDialog.value = true
  301. })
  302. }
  303. const modifyOrder = (val) => {
  304. ElMessageBox.prompt('请输入新序号', '提示', {
  305. confirmButtonText: '确定',
  306. cancelButtonText: '取消',
  307. inputPattern: /^[1-9]\d*$/,
  308. inputErrorMessage: '请输入大于0的整数',
  309. })
  310. .then(({ value }) => {
  311. updateWxHomepageOrder(val.code, value).then(() => {
  312. val.wxHomepageOrder = value
  313. ElMessage({
  314. message: '保存排序成功',
  315. type: 'success',
  316. duration: 2000,
  317. showClose: true,
  318. })
  319. })
  320. })
  321. .catch(() => {})
  322. }
  323. const refreshWxDoctorCache = () => {
  324. axios('http://192.168.200.3/wxserver/homepage/refreshHomePageDoctors').then((res) => {
  325. if (res.data.code === 200) {
  326. ElMessage({
  327. message: '强制刷新成功',
  328. type: 'success',
  329. duration: 2000,
  330. showClose: true,
  331. })
  332. } else {
  333. ElMessage.error(res.data.message)
  334. }
  335. })
  336. }
  337. const undoDelete = (row) => {
  338. updateDoctorStatus(row.code, 1).then((res) => {
  339. ElMessage({
  340. message: res,
  341. type: 'success',
  342. duration: 2000,
  343. showClose: true,
  344. })
  345. fetchDoctors()
  346. })
  347. }
  348. const beforeDelete = (row) => {
  349. ElMessageBox.confirm('删除后将无法在服务号首页推荐,是否确认删除?', '提示', {
  350. type: 'warning',
  351. confirmButtonText: '删除',
  352. cancelButtonText: '取消',
  353. })
  354. .then(() => {
  355. updateDoctorStatus(row.code, 0).then((res) => {
  356. ElMessage({
  357. message: res,
  358. type: 'success',
  359. duration: 2000,
  360. showClose: true,
  361. })
  362. fetchDoctors()
  363. })
  364. })
  365. .catch(() => {})
  366. }
  367. const fetchDoctors = () => {
  368. getAllDoctors(queryParam).then((res) => {
  369. totalSize.value = res.totalSize
  370. allDoctors.value = res.list
  371. })
  372. }
  373. onMounted(() => {
  374. getAllTitles().then((res) => {
  375. allTitles.value = res
  376. })
  377. getAllMzDept().then((res) => {
  378. allMzDepts.value = res
  379. fetchDoctors()
  380. })
  381. })
  382. </script>
  383. <style scoped>
  384. .inline {
  385. display: inline-block;
  386. vertical-align: top;
  387. }
  388. .pl15 {
  389. padding-left: 50px;
  390. }
  391. .child-pl15 > div {
  392. padding: 0 50px 0 15px;
  393. }
  394. .doctor-info,
  395. .edit-doctor-info {
  396. font-size: 13px;
  397. color: gray;
  398. margin-top: 15px;
  399. }
  400. .doctor-info > div {
  401. height: 30px;
  402. line-height: 30px;
  403. }
  404. .edit-doctor-info > div {
  405. height: 35px;
  406. line-height: 35px;
  407. }
  408. .info-title {
  409. margin-top: 15px;
  410. color: #333333;
  411. }
  412. .require {
  413. color: #db4242;
  414. }
  415. .w200 {
  416. width: 200px;
  417. }
  418. .w350 {
  419. width: 75%;
  420. }
  421. </style>