You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1012 lines
35 KiB

  1. <template>
  2. <div class="app-container">
  3. <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
  4. <el-form-item label="药品名称" prop="medicineName">
  5. <el-input
  6. v-model="queryParams.medicineName"
  7. placeholder="请输入药品名称"
  8. clearable
  9. size="small"
  10. @keyup.enter.native="handleQuery"
  11. />
  12. </el-form-item>
  13. <el-form-item label="规格" prop="specification">
  14. <el-input
  15. v-model="queryParams.specification"
  16. placeholder="请输入规格"
  17. clearable
  18. size="small"
  19. @keyup.enter.native="handleQuery"
  20. />
  21. </el-form-item>
  22. <el-form-item label="当前价格" prop="price">
  23. <el-input
  24. v-model="queryParams.price"
  25. placeholder="请输入当前价格"
  26. clearable
  27. size="small"
  28. @keyup.enter.native="handleQuery"
  29. />
  30. </el-form-item>
  31. <el-form-item label="店铺名称" prop="storeName">
  32. <el-input
  33. v-model="queryParams.storeName"
  34. placeholder="请输入店铺名称"
  35. clearable
  36. size="small"
  37. @keyup.enter.native="handleQuery"
  38. />
  39. </el-form-item>
  40. <el-form-item label="店铺地址" prop="storeAddress">
  41. <el-input
  42. v-model="queryParams.storeAddress"
  43. placeholder="请输入店铺地址"
  44. clearable
  45. size="small"
  46. @keyup.enter.native="handleQuery"
  47. />
  48. </el-form-item>
  49. <el-form-item>
  50. <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
  51. <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
  52. </el-form-item>
  53. </el-form>
  54. <el-row :gutter="10" class="mb8">
  55. <el-col :span="1.5">
  56. <el-button
  57. type="primary"
  58. plain
  59. icon="el-icon-plus"
  60. size="mini"
  61. @click="handleAdd"
  62. v-hasPermi="['system:recommendation:add']"
  63. >新增</el-button>
  64. </el-col>
  65. <el-col :span="1.5">
  66. <el-button
  67. type="success"
  68. plain
  69. icon="el-icon-edit"
  70. size="mini"
  71. :disabled="single"
  72. @click="handleUpdate"
  73. v-hasPermi="['system:recommendation:edit']"
  74. >修改</el-button>
  75. </el-col>
  76. <el-col :span="1.5">
  77. <el-button
  78. type="danger"
  79. plain
  80. icon="el-icon-delete"
  81. size="mini"
  82. :disabled="multiple"
  83. @click="handleDelete"
  84. v-hasPermi="['system:recommendation:remove']"
  85. >删除</el-button>
  86. </el-col>
  87. <el-col :span="1.5">
  88. <el-button
  89. type="warning"
  90. plain
  91. icon="el-icon-download"
  92. size="mini"
  93. @click="handleExport"
  94. v-hasPermi="['system:recommendation:export']"
  95. >导出</el-button>
  96. </el-col>
  97. <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
  98. </el-row>
  99. <el-table v-loading="loading" :data="recommendationList" @selection-change="handleSelectionChange">
  100. <el-table-column type="selection" width="55" align="center" />
  101. <el-table-column label="药品图片" align="center" prop="images" width="100">
  102. <template slot-scope="scope">
  103. <image-preview :src="scope.row.images" :width="50" :height="50" />
  104. </template>
  105. </el-table-column>
  106. <el-table-column label="轮播图片" align="center" prop="imageUrl" width="100">
  107. <template slot-scope="scope">
  108. <image-preview :src="scope.row.imageUrl" :width="50" :height="50" />
  109. </template>
  110. </el-table-column>
  111. <el-table-column label="药品名称" align="center" prop="medicineName" :show-overflow-tooltip="true" />
  112. <el-table-column label="药品类型" align="center" prop="medicineType">
  113. <template slot-scope="scope">
  114. <dict-tag :options="dict.type.medicine_type" :value="scope.row.medicineType"/>
  115. </template>
  116. </el-table-column>
  117. <el-table-column label="规格" align="center" prop="specification" :show-overflow-tooltip="true" />
  118. <el-table-column label="当前价格" align="center" prop="price">
  119. <template slot-scope="scope">
  120. <span>¥{{ scope.row.price }}</span>
  121. </template>
  122. </el-table-column>
  123. <el-table-column label="原价" align="center" prop="originalPrice">
  124. <template slot-scope="scope">
  125. <span v-if="scope.row.originalPrice">¥{{ scope.row.originalPrice }}</span>
  126. <span v-else>-</span>
  127. </template>
  128. </el-table-column>
  129. <el-table-column label="已售数量" align="center" prop="soldQuantity" />
  130. <el-table-column label="销售类型" align="center" prop="salesType">
  131. <template slot-scope="scope">
  132. <dict-tag :options="dict.type.sales_type" :value="scope.row.salesType"/>
  133. </template>
  134. </el-table-column>
  135. <el-table-column label="适用症状" align="center" prop="indications" :show-overflow-tooltip="true" />
  136. <el-table-column label="生产厂家" align="center" prop="manufacturer" :show-overflow-tooltip="true" />
  137. <el-table-column label="专家推荐" align="center" width="200">
  138. <template slot-scope="scope">
  139. <div v-if="scope.row.expertId">
  140. <div style="font-weight: bold;">{{ scope.row.expertName }}</div>
  141. <div style="font-size: 12px; color: #999;">{{ scope.row.expertExpert }}</div>
  142. </div>
  143. <span v-else style="color: #999;">-</span>
  144. </template>
  145. </el-table-column>
  146. <el-table-column label="推荐店铺" align="center" width="200">
  147. <template slot-scope="scope">
  148. <div v-if="scope.row.storeName">
  149. <div style="font-weight: bold;">{{ scope.row.storeName }}</div>
  150. <div style="font-size: 12px; color: #999;">{{ scope.row.storeAddress || '-' }}</div>
  151. </div>
  152. <span v-else style="color: #999;">-</span>
  153. </template>
  154. </el-table-column>
  155. <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
  156. <template slot-scope="scope">
  157. <el-button
  158. size="mini"
  159. type="text"
  160. icon="el-icon-edit"
  161. @click="handleUpdate(scope.row)"
  162. v-hasPermi="['system:recommendation:edit']"
  163. >修改</el-button>
  164. <el-button
  165. size="mini"
  166. type="text"
  167. icon="el-icon-delete"
  168. @click="handleDelete(scope.row)"
  169. v-hasPermi="['system:recommendation:remove']"
  170. >删除</el-button>
  171. </template>
  172. </el-table-column>
  173. </el-table>
  174. <pagination
  175. v-show="total>0"
  176. :total="total"
  177. :page.sync="queryParams.pageNum"
  178. :limit.sync="queryParams.pageSize"
  179. @pagination="getList"
  180. />
  181. <!-- 添加或修改药品推荐对话框 -->
  182. <el-dialog :title="title" :visible.sync="open" width="900px" append-to-body>
  183. <el-form ref="form" :model="form" :rules="rules" label-width="100px">
  184. <el-tabs v-model="activeTab">
  185. <el-tab-pane label="基本信息" name="basic">
  186. <!-- 药品图片上传 -->
  187. <el-form-item label="药品图片" prop="images">
  188. <el-upload
  189. ref="medicineUpload"
  190. class="upload-demo"
  191. :action="uploadUrl"
  192. :headers="uploadHeaders"
  193. :auto-upload="false"
  194. :file-list="medicineFileList"
  195. :on-change="handleMedicineFileChange"
  196. :on-remove="handleMedicineFileRemove"
  197. :on-success="handleMedicineUploadSuccess"
  198. :before-upload="beforeAvatarUpload"
  199. list-type="picture-card"
  200. :limit="1"
  201. accept=".jpg,.jpeg,.png,.gif,.bmp"
  202. >
  203. <i class="el-icon-plus"></i>
  204. <div slot="tip" class="el-upload__tip" style="margin-top: 10px;">
  205. 支持jpgjpegpnggifbmp格式单张图片不超过2MB只能上传1张
  206. </div>
  207. </el-upload>
  208. </el-form-item>
  209. <el-form-item label="药品图片" prop="images">
  210. <image-upload
  211. v-model="form.images"
  212. :limit="10"
  213. :file-size="2"
  214. :is-show-tip="true"
  215. :accept="['.jpg', '.jpeg', '.png', '.gif', '.bmp']"
  216. help-text="支持jpg、jpeg、png、gif、bmp格式,单张图片不超过2MB,最多上传10张"
  217. />
  218. </el-form-item>
  219. <!-- 轮播图片上传 -->
  220. <el-form-item label="轮播图片" prop="imageUrl">
  221. <el-upload
  222. ref="carouselUpload"
  223. class="upload-demo"
  224. :action="uploadUrl"
  225. :headers="uploadHeaders"
  226. :auto-upload="false"
  227. :file-list="carouselFileList"
  228. :on-change="handleCarouselFileChange"
  229. :on-remove="handleCarouselFileRemove"
  230. :on-success="handleCarouselUploadSuccess"
  231. :before-upload="beforeAvatarUpload"
  232. list-type="picture-card"
  233. :limit="5"
  234. multiple
  235. accept=".jpg,.jpeg,.png,.gif,.bmp"
  236. >
  237. <i class="el-icon-plus"></i>
  238. <div slot="tip" class="el-upload__tip" style="margin-top: 10px;">
  239. 支持jpgjpegpnggifbmp格式单张图片不超过2MB最多上传5张用于首页轮播展示
  240. </div>
  241. </el-upload>
  242. </el-form-item>
  243. <el-form-item label="轮播图片" prop="imageUrl">
  244. <image-upload
  245. v-model="form.imageUrl"
  246. :limit="10"
  247. :file-size="2"
  248. :is-show-tip="true"
  249. :accept="['.jpg', '.jpeg', '.png', '.gif', '.bmp']"
  250. help-text="支持jpg、jpeg、png、gif、bmp格式,单张图片不超过2MB,最多上传10张"
  251. />
  252. </el-form-item>
  253. <el-row>
  254. <el-col :span="12">
  255. <el-form-item label="药品名称" prop="medicineName">
  256. <el-input v-model="form.medicineName" placeholder="请输入药品名称" />
  257. </el-form-item>
  258. </el-col>
  259. <el-col :span="12">
  260. <el-form-item label="药品类型" prop="medicineType">
  261. <el-select v-model="form.medicineType" placeholder="请选择药品类型" clearable style="width: 100%;">
  262. <el-option
  263. v-for="dict in dict.type.medicine_type"
  264. :key="dict.value"
  265. :label="dict.label"
  266. :value="dict.value"
  267. />
  268. </el-select>
  269. </el-form-item>
  270. </el-col>
  271. </el-row>
  272. <el-row>
  273. <el-col :span="12">
  274. <el-form-item label="规格" prop="specification">
  275. <el-input v-model="form.specification" placeholder="请输入规格" />
  276. </el-form-item>
  277. </el-col>
  278. <el-col :span="12">
  279. <el-form-item label="销售类型" prop="salesType">
  280. <el-select v-model="form.salesType" placeholder="请选择销售类型" clearable style="width: 100%;">
  281. <el-option
  282. v-for="dict in dict.type.sales_type"
  283. :key="dict.value"
  284. :label="dict.label"
  285. :value="dict.value"
  286. />
  287. </el-select>
  288. </el-form-item>
  289. </el-col>
  290. </el-row>
  291. <el-row>
  292. <el-col :span="12">
  293. <el-form-item label="当前价格" prop="price">
  294. <el-input v-model="form.price" placeholder="请输入当前价格" type="number" min="0">
  295. <template slot="append"></template>
  296. </el-input>
  297. </el-form-item>
  298. </el-col>
  299. <el-col :span="12">
  300. <el-form-item label="原价" prop="originalPrice">
  301. <el-input v-model="form.originalPrice" placeholder="请输入原价" type="number" min="0">
  302. <template slot="append"></template>
  303. </el-input>
  304. </el-form-item>
  305. </el-col>
  306. </el-row>
  307. <el-row>
  308. <el-col :span="12">
  309. <el-form-item label="已售数量" prop="soldQuantity">
  310. <el-input v-model="form.soldQuantity" placeholder="请输入已售数量" type="number" min="0" />
  311. </el-form-item>
  312. </el-col>
  313. <el-col :span="12">
  314. <el-form-item label="生产厂家" prop="manufacturer">
  315. <el-input v-model="form.manufacturer" placeholder="请输入生产厂家" />
  316. </el-form-item>
  317. </el-col>
  318. </el-row>
  319. <el-row>
  320. <el-col :span="12">
  321. <el-form-item label="贮藏方式" prop="storageMethod">
  322. <el-input v-model="form.storageMethod" placeholder="请输入贮藏方式" />
  323. </el-form-item>
  324. </el-col>
  325. <el-col :span="12">
  326. <el-form-item label="有效期" prop="expiryDate">
  327. <el-input v-model="form.expiryDate" placeholder="请输入有效期" />
  328. </el-form-item>
  329. </el-col>
  330. </el-row>
  331. <el-form-item label="适用症状" prop="indications">
  332. <el-input v-model="form.indications" type="textarea" placeholder="请输入适用症状" />
  333. </el-form-item>
  334. <el-form-item label="用法用量" prop="usageDosage">
  335. <el-input v-model="form.usageDosage" type="textarea" placeholder="请输入用法用量" />
  336. </el-form-item>
  337. <el-form-item label="注意事项" prop="precautions">
  338. <el-input v-model="form.precautions" type="textarea" placeholder="请输入注意事项" />
  339. </el-form-item>
  340. </el-tab-pane>
  341. <el-tab-pane label="专家推荐" name="expert">
  342. <el-form-item label="推荐专家" prop="expertId">
  343. <el-select
  344. v-model="form.expertId"
  345. placeholder="请选择推荐专家"
  346. clearable
  347. filterable
  348. style="width: 100%;"
  349. >
  350. <el-option
  351. v-for="expert in expertList"
  352. :key="expert.expertId"
  353. :label="expert.realName + ' - ' + expert.expert"
  354. :value="expert.expertId"
  355. />
  356. </el-select>
  357. <div style="font-size: 12px; color: #999; margin-top: 5px;">
  358. 提示选择为您推荐药品的专家
  359. </div>
  360. </el-form-item>
  361. <el-form-item label="推荐理由" prop="recommendReason">
  362. <el-input v-model="form.recommendReason" type="textarea" placeholder="请输入专家推荐理由" rows="4" />
  363. </el-form-item>
  364. <el-form-item label="推荐时间" prop="recommendTime">
  365. <el-date-picker
  366. v-model="form.recommendTime"
  367. type="date"
  368. value-format="yyyy-MM-dd"
  369. placeholder="请选择推荐时间"
  370. style="width: 100%;"
  371. />
  372. </el-form-item>
  373. </el-tab-pane>
  374. <el-tab-pane label="推荐店铺" name="store">
  375. <el-row>
  376. <el-col :span="12">
  377. <el-form-item label="店铺名称" prop="storeName">
  378. <el-input v-model="form.storeName" placeholder="请输入店铺名称" />
  379. </el-form-item>
  380. </el-col>
  381. <el-col :span="12">
  382. <el-form-item label="店铺电话" prop="storePhone">
  383. <el-input v-model="form.storePhone" placeholder="请输入店铺电话" />
  384. </el-form-item>
  385. </el-col>
  386. </el-row>
  387. <el-form-item label="店铺地址" prop="storeAddress">
  388. <el-input v-model="form.storeAddress" type="textarea" placeholder="请输入店铺地址" />
  389. </el-form-item>
  390. <el-form-item label="营业时间" prop="businessHours">
  391. <el-input v-model="form.businessHours" placeholder="例如:8:00-22:00" />
  392. </el-form-item>
  393. <el-row>
  394. <el-col :span="12">
  395. <el-form-item label="经度" prop="longitude">
  396. <el-input v-model="form.longitude" placeholder="请输入经度" type="number">
  397. <template slot="append">°</template>
  398. </el-input>
  399. </el-form-item>
  400. </el-col>
  401. <el-col :span="12">
  402. <el-form-item label="纬度" prop="latitude">
  403. <el-input v-model="form.latitude" placeholder="请输入纬度" type="number">
  404. <template slot="append">°</template>
  405. </el-input>
  406. </el-form-item>
  407. </el-col>
  408. </el-row>
  409. <el-form-item label="备注" prop="storeRemark">
  410. <el-input v-model="form.storeRemark" type="textarea" placeholder="请输入店铺备注信息" rows="3" />
  411. </el-form-item>
  412. <div style="font-size: 12px; color: #999; margin-top: 10px;">
  413. <i class="el-icon-info"></i> 提示经纬度用于地图定位如不清楚可留空
  414. </div>
  415. </el-tab-pane>
  416. </el-tabs>
  417. </el-form>
  418. <div slot="footer" class="dialog-footer">
  419. <el-button type="primary" @click="submitForm"> </el-button>
  420. <el-button @click="cancel"> </el-button>
  421. </div>
  422. </el-dialog>
  423. </div>
  424. </template>
  425. <script>
  426. import { listRecommendation, getRecommendation, delRecommendation, addRecommendation, updateRecommendation } from "@/api/system/recommendation";
  427. import { listExperts } from "@/api/vet/experts";
  428. import { getToken } from "@/utils/auth";
  429. export default {
  430. name: "Recommendation",
  431. dicts: ['medicine_type', 'sales_type'],
  432. data() {
  433. return {
  434. // 遮罩层
  435. loading: true,
  436. // 选中数组
  437. ids: [],
  438. // 非单个禁用
  439. single: true,
  440. // 非多个禁用
  441. multiple: true,
  442. // 显示搜索条件
  443. showSearch: true,
  444. // 总条数
  445. total: 0,
  446. // 药品推荐表格数据
  447. recommendationList: [],
  448. // 专家列表
  449. expertList: [],
  450. // 弹出层标题
  451. title: "",
  452. // 是否显示弹出层
  453. open: false,
  454. // 当前激活的标签页
  455. activeTab: "basic",
  456. // 药品图片文件列表(单张)
  457. medicineFileList: [],
  458. // 轮播图片文件列表(最多5张)
  459. carouselFileList: [],
  460. // 上传URL
  461. uploadUrl: process.env.VUE_APP_BASE_API + "/common/upload",
  462. // 上传的请求头
  463. uploadHeaders: {
  464. Authorization: 'Bearer ' + getToken()
  465. },
  466. // 查询参数
  467. queryParams: {
  468. pageNum: 1,
  469. pageSize: 10,
  470. medicineName: undefined,
  471. medicineType: undefined,
  472. specification: undefined,
  473. price: undefined,
  474. originalPrice: undefined,
  475. soldQuantity: undefined,
  476. indications: undefined,
  477. usageDosage: undefined,
  478. precautions: undefined,
  479. storageMethod: undefined,
  480. expiryDate: undefined,
  481. manufacturer: undefined,
  482. salesType: null,
  483. expertId: undefined,
  484. recommendReason: undefined,
  485. recommendTime: undefined,
  486. storeName: undefined,
  487. storeAddress: undefined,
  488. },
  489. // 表单参数
  490. form: {
  491. id: undefined,
  492. medicineName: undefined,
  493. medicineType: undefined,
  494. specification: undefined,
  495. price: undefined,
  496. originalPrice: undefined,
  497. soldQuantity: undefined,
  498. indications: undefined,
  499. usageDosage: undefined,
  500. precautions: undefined,
  501. storageMethod: undefined,
  502. expiryDate: undefined,
  503. manufacturer: undefined,
  504. salesType: null,
  505. expertId: undefined,
  506. recommendReason: undefined,
  507. recommendTime: undefined,
  508. images: undefined, // 药品图片URL(单张)
  509. imageUrl: undefined, // 轮播图片URL(多张,用逗号分隔)
  510. storeName: undefined,
  511. storeAddress: undefined,
  512. storePhone: undefined,
  513. businessHours: undefined,
  514. storeRemark: undefined,
  515. longitude: undefined,
  516. latitude: undefined,
  517. },
  518. // 表单校验
  519. rules: {
  520. medicineName: [
  521. { required: true, message: "药品名称不能为空", trigger: "blur" }
  522. ],
  523. medicineType: [
  524. { required: true, message: "药品类型不能为空", trigger: "change" }
  525. ],
  526. price: [
  527. { required: true, message: "当前价格不能为空", trigger: "blur" },
  528. { pattern: /^\d+(\.\d{1,2})?$/, message: "请输入正确的价格格式", trigger: "blur" }
  529. ],
  530. specification: [
  531. { required: true, message: "规格不能为空", trigger: "blur" }
  532. ]
  533. }
  534. };
  535. },
  536. created() {
  537. this.getList();
  538. this.getExpertList();
  539. },
  540. methods: {
  541. /** 查询药品推荐列表 */
  542. getList() {
  543. this.loading = true;
  544. listRecommendation(this.queryParams).then(response => {
  545. this.recommendationList = response.rows;
  546. this.total = response.total;
  547. this.loading = false;
  548. });
  549. },
  550. /** 获取专家列表 */
  551. getExpertList() {
  552. listExperts({
  553. pageNum: 1,
  554. pageSize: 1000,
  555. status: '0'
  556. }).then(response => {
  557. if (response.code === 200) {
  558. this.expertList = response.rows || [];
  559. }
  560. }).catch(() => {
  561. this.expertList = [
  562. { expertId: 1, realName: '张医生', title: '主任医师' },
  563. { expertId: 2, realName: '李医生', title: '副主任医师' }
  564. ];
  565. });
  566. },
  567. // ========== 通用方法 ==========
  568. // 文件上传前的验证
  569. beforeAvatarUpload(file) {
  570. const isImage = /\.(jpg|jpeg|png|gif|bmp)$/i.test(file.name);
  571. const isLt2M = file.size / 1024 / 1024 < 2;
  572. if (!isImage) {
  573. this.$modal.msgError("只能上传图片格式文件!");
  574. return false;
  575. }
  576. if (!isLt2M) {
  577. this.$modal.msgError("上传图片大小不能超过 2MB!");
  578. return false;
  579. }
  580. return true;
  581. },
  582. // ========== 药品图片相关方法 ==========
  583. // 药品图片文件列表改变时(限制1张)
  584. handleMedicineFileChange(file, fileList) {
  585. if (fileList.length > 1) {
  586. this.$modal.msgWarning("药品图片只能上传1张");
  587. fileList.splice(-1, 1);
  588. return;
  589. }
  590. this.medicineFileList = fileList;
  591. // 更新表单的 images 字段
  592. if (fileList.length > 0) {
  593. const url = fileList[0].url || (fileList[0].response && fileList[0].response.url);
  594. this.form.images = url || '';
  595. } else {
  596. this.form.images = '';
  597. }
  598. },
  599. // 药品图片文件移除时
  600. handleMedicineFileRemove(file, fileList) {
  601. this.medicineFileList = fileList;
  602. // 更新表单的 images 字段
  603. this.form.images = '';
  604. },
  605. // 药品图片上传成功回调
  606. handleMedicineUploadSuccess(response, file, fileList) {
  607. if (response.code === 200) {
  608. const url = response.fileName || response.url;
  609. // 更新表单的 images 字段
  610. this.form.images = url;
  611. this.$modal.msgSuccess("药品图片上传成功");
  612. } else {
  613. this.$modal.msgError("药品图片上传失败: " + response.msg);
  614. }
  615. },
  616. // ========== 轮播图片相关方法 ==========
  617. // 轮播图片文件列表改变时(限制5张)
  618. handleCarouselFileChange(file, fileList) {
  619. if (fileList.length > 5) {
  620. this.$modal.msgWarning("最多只能上传5张轮播图片");
  621. fileList.splice(-1, 1);
  622. return;
  623. }
  624. this.carouselFileList = fileList;
  625. // 更新表单的 imageUrl 字段(多个URL用逗号分隔)
  626. const urls = fileList.map(item => item.url || (item.response && item.response.url)).filter(url => url);
  627. this.form.imageUrl = urls.join(',');
  628. },
  629. // 轮播图片文件移除时
  630. handleCarouselFileRemove(file, fileList) {
  631. this.carouselFileList = fileList;
  632. // 更新表单的 imageUrl 字段
  633. const urls = fileList.map(item => item.url || (item.response && item.response.url)).filter(url => url);
  634. this.form.imageUrl = urls.join(',');
  635. },
  636. // 轮播图片上传成功回调
  637. handleCarouselUploadSuccess(response, file, fileList) {
  638. if (response.code === 200) {
  639. // 更新表单的 imageUrl 字段
  640. const urls = fileList.map(item => item.url || (item.response && item.response.url)).filter(url => url);
  641. this.form.imageUrl = urls.join(',');
  642. this.$modal.msgSuccess("轮播图片上传成功");
  643. } else {
  644. this.$modal.msgError("轮播图片上传失败: " + response.msg);
  645. }
  646. },
  647. // ========== 上传所有文件 ==========
  648. uploadAllFiles() {
  649. return new Promise((resolve, reject) => {
  650. // 上传药品图片
  651. const medicinePromise = this.uploadMedicineFile();
  652. // 上传轮播图片
  653. const carouselPromise = this.uploadCarouselFiles();
  654. Promise.all([medicinePromise, carouselPromise])
  655. .then(([medicineImage, carouselImages]) => {
  656. // 确保表单字段是最新的
  657. this.form.images = medicineImage;
  658. this.form.imageUrl = carouselImages;
  659. resolve({
  660. images: medicineImage,
  661. imageUrl: carouselImages
  662. });
  663. })
  664. .catch(error => {
  665. this.$modal.msgError("文件上传失败: " + error.message);
  666. reject(error);
  667. });
  668. });
  669. },
  670. // 上传药品图片(单张)
  671. uploadMedicineFile() {
  672. return new Promise((resolve, reject) => {
  673. if (this.medicineFileList.length === 0) {
  674. resolve(this.form.images || '');
  675. return;
  676. }
  677. const fileToUpload = this.medicineFileList.find(file => file.raw);
  678. if (!fileToUpload) {
  679. // 如果没有新文件要上传,直接使用现有的URL
  680. const existingUrl = this.medicineFileList[0]?.url || (this.medicineFileList[0]?.response && this.medicineFileList[0]?.response.url);
  681. const result = existingUrl || '';
  682. this.form.images = result;
  683. resolve(result);
  684. return;
  685. }
  686. const formData = new FormData();
  687. formData.append('file', fileToUpload.raw);
  688. const xhr = new XMLHttpRequest();
  689. xhr.open('POST', this.uploadUrl, true);
  690. xhr.setRequestHeader('Authorization', 'Bearer ' + getToken());
  691. xhr.onload = () => {
  692. if (xhr.status === 200) {
  693. try {
  694. const response = JSON.parse(xhr.responseText);
  695. if (response.code === 200) {
  696. const result = response.fileName || response.url;
  697. this.form.images = result;
  698. resolve(result);
  699. } else {
  700. reject(new Error(response.msg || '上传失败'));
  701. }
  702. } catch (e) {
  703. reject(new Error('解析响应失败'));
  704. }
  705. } else {
  706. reject(new Error('上传失败,状态码:' + xhr.status));
  707. }
  708. };
  709. xhr.onerror = () => {
  710. reject(new Error('网络错误'));
  711. };
  712. xhr.send(formData);
  713. });
  714. },
  715. // 上传轮播图片(多张)
  716. uploadCarouselFiles() {
  717. return new Promise((resolve, reject) => {
  718. if (this.carouselFileList.length === 0) {
  719. resolve(this.form.imageUrl || '');
  720. return;
  721. }
  722. const filesToUpload = this.carouselFileList.filter(file => file.raw);
  723. if (filesToUpload.length === 0) {
  724. // 如果没有新文件要上传,直接使用现有的URL
  725. const existingUrls = this.carouselFileList.map(item => item.url || (item.response && item.response.url)).filter(url => url);
  726. const result = existingUrls.join(',');
  727. this.form.imageUrl = result;
  728. resolve(result);
  729. return;
  730. }
  731. const uploadPromises = filesToUpload.map(file => {
  732. return new Promise((uploadResolve, uploadReject) => {
  733. const formData = new FormData();
  734. formData.append('file', file.raw);
  735. const xhr = new XMLHttpRequest();
  736. xhr.open('POST', this.uploadUrl, true);
  737. xhr.setRequestHeader('Authorization', 'Bearer ' + getToken());
  738. xhr.onload = () => {
  739. if (xhr.status === 200) {
  740. try {
  741. const response = JSON.parse(xhr.responseText);
  742. if (response.code === 200) {
  743. uploadResolve(response.fileName || response.url);
  744. } else {
  745. uploadReject(new Error(response.msg || '上传失败'));
  746. }
  747. } catch (e) {
  748. uploadReject(new Error('解析响应失败'));
  749. }
  750. } else {
  751. uploadReject(new Error('上传失败,状态码:' + xhr.status));
  752. }
  753. };
  754. xhr.onerror = () => {
  755. uploadReject(new Error('网络错误'));
  756. };
  757. xhr.send(formData);
  758. });
  759. });
  760. Promise.all(uploadPromises)
  761. .then(urls => {
  762. const existingUrls = this.carouselFileList.filter(file => !file.raw && file.url).map(file => file.url);
  763. const allUrls = [...existingUrls, ...urls];
  764. const result = allUrls.join(',');
  765. this.form.imageUrl = result;
  766. resolve(result);
  767. })
  768. .catch(error => {
  769. this.$modal.msgError("轮播图片上传失败: " + error.message);
  770. reject(error);
  771. });
  772. });
  773. },
  774. // ========== 表单操作 ==========
  775. // 取消按钮
  776. cancel() {
  777. this.open = false;
  778. this.reset();
  779. },
  780. // 表单重置
  781. reset() {
  782. this.form = {
  783. id: undefined,
  784. medicineName: undefined,
  785. medicineType: undefined,
  786. specification: undefined,
  787. price: undefined,
  788. originalPrice: undefined,
  789. soldQuantity: undefined,
  790. indications: undefined,
  791. usageDosage: undefined,
  792. precautions: undefined,
  793. storageMethod: undefined,
  794. expiryDate: undefined,
  795. manufacturer: undefined,
  796. salesType: null,
  797. expertId: undefined,
  798. recommendReason: undefined,
  799. recommendTime: undefined,
  800. images: undefined,
  801. imageUrl: undefined,
  802. storeName: undefined,
  803. storeAddress: undefined,
  804. storePhone: undefined,
  805. businessHours: undefined,
  806. storeRemark: undefined,
  807. longitude: undefined,
  808. latitude: undefined,
  809. };
  810. this.medicineFileList = [];
  811. this.carouselFileList = [];
  812. this.activeTab = "basic";
  813. this.resetForm("form");
  814. },
  815. /** 搜索按钮操作 */
  816. handleQuery() {
  817. this.queryParams.pageNum = 1;
  818. this.getList();
  819. },
  820. /** 重置按钮操作 */
  821. resetQuery() {
  822. this.resetForm("queryForm");
  823. this.handleQuery();
  824. },
  825. // 多选框选中数据
  826. handleSelectionChange(selection) {
  827. this.ids = selection.map(item => item.id);
  828. this.single = selection.length !== 1;
  829. this.multiple = !selection.length;
  830. },
  831. /** 新增按钮操作 */
  832. handleAdd() {
  833. this.reset();
  834. this.open = true;
  835. this.title = "添加药品推荐";
  836. },
  837. /** 修改按钮操作 */
  838. async handleUpdate(row) {
  839. this.reset();
  840. const id = row.id || this.ids[0];
  841. getRecommendation(id).then(response => {
  842. this.form = response.data;
  843. // 初始化药品图片文件列表(单张)
  844. if (this.form.images) {
  845. this.medicineFileList = [{
  846. name: 'medicine_image.jpg',
  847. url: this.form.images,
  848. status: 'success'
  849. }];
  850. }
  851. // 初始化轮播图片文件列表(多张)
  852. if (this.form.imageUrl) {
  853. const imageUrls = this.form.imageUrl.split(',');
  854. this.carouselFileList = imageUrls.map((url, index) => ({
  855. name: `carousel_${index + 1}.jpg`,
  856. url: url.trim(),
  857. status: 'success'
  858. }));
  859. }
  860. this.open = true;
  861. this.title = "修改药品推荐";
  862. });
  863. },
  864. /** 提交按钮 */
  865. async submitForm() {
  866. this.$refs["form"].validate(async valid => {
  867. if (valid) {
  868. try {
  869. // 上传所有文件
  870. const { images, imageUrl } = await this.uploadAllFiles();
  871. const formData = {
  872. ...this.form,
  873. price: this.form.price ? Number(this.form.price) : undefined,
  874. originalPrice: this.form.originalPrice ? Number(this.form.originalPrice) : undefined,
  875. soldQuantity: this.form.soldQuantity ? parseInt(this.form.soldQuantity) : undefined,
  876. longitude: this.form.longitude ? Number(this.form.longitude) : undefined,
  877. latitude: this.form.latitude ? Number(this.form.latitude) : undefined,
  878. images: images,
  879. imageUrl: imageUrl
  880. };
  881. if (this.form.id !== undefined) {
  882. updateRecommendation(formData).then(response => {
  883. this.$modal.msgSuccess("修改成功");
  884. this.open = false;
  885. this.getList();
  886. });
  887. } else {
  888. addRecommendation(formData).then(response => {
  889. this.$modal.msgSuccess("新增成功");
  890. this.open = false;
  891. this.getList();
  892. });
  893. }
  894. } catch (error) {
  895. this.$modal.msgError("提交失败,请重试");
  896. }
  897. }
  898. });
  899. },
  900. /** 删除按钮操作 */
  901. handleDelete(row) {
  902. const ids = row.id || this.ids;
  903. this.$modal.confirm('是否确认删除药品推荐编号为"' + ids + '"的数据项?').then(function() {
  904. return delRecommendation(ids);
  905. }).then(() => {
  906. this.getList();
  907. this.$modal.msgSuccess("删除成功");
  908. }).catch(() => {});
  909. },
  910. /** 导出按钮操作 */
  911. handleExport() {
  912. this.download('system/recommendation/export', {
  913. ...this.queryParams
  914. }, `recommendation_${new Date().getTime()}.xlsx`);
  915. }
  916. }
  917. };
  918. </script>
  919. <style scoped>
  920. /* 图片上传样式 */
  921. .upload-demo {
  922. width: 100%;
  923. }
  924. .upload-demo .el-upload {
  925. width: 100px;
  926. height: 100px;
  927. line-height: 100px;
  928. }
  929. .upload-demo .el-upload-list__item {
  930. width: 100px;
  931. height: 100px;
  932. }
  933. </style>