WxmallPackage.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <template>
  2. <div class="layout_container wxmall-package">
  3. <header class="round-header">
  4. <span>上架时间:</span>
  5. <CyDateRange />
  6. <span class="ml-12">
  7. 套餐状态:
  8. </span>
  9. <el-select v-model="inquiry.state" clearable class="w-80">
  10. <el-option label="已上架" value="ACTIVATED" />
  11. <el-option label="已下架" value="DEACTIVATED" />
  12. </el-select>
  13. <el-divider direction="vertical" />
  14. <el-button icon="Search" type="primary" @click="queryPackages">查询</el-button>
  15. <el-button icon="Plus" color="green" @click="beforeAddPackage">上架</el-button>
  16. </header>
  17. <div class="layout_main layout_el-table">
  18. <el-table :data="packageList" stripe>
  19. <el-table-column prop="name" label="套餐名称" />
  20. <el-table-column prop="originPrice" label="套餐原价" />
  21. <el-table-column prop="discountPrice" label="套餐优惠价" />
  22. <el-table-column prop="description" label="套餐描述" />
  23. <el-table-column prop="createTime" label="创建时间" />
  24. <el-table-column prop="createStaffName" label="创建人" />
  25. <el-table-column label="状态">
  26. <template #default="{row}">
  27. <el-switch
  28. v-model="row.state"
  29. inline-prompt
  30. style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
  31. active-text="已上架"
  32. inactive-text="已下架"
  33. active-value="ACTIVATED"
  34. inactive-value="DEACTIVATED"
  35. @change="changePackageState(row)"
  36. ></el-switch>
  37. </template>
  38. </el-table-column>
  39. <el-table-column label="操作">
  40. <template #default="{row}">
  41. <el-button
  42. icon="View"
  43. type="primary"
  44. plain
  45. @click="seePackageDetail(row.id)"
  46. >
  47. 查看
  48. </el-button>
  49. <el-button
  50. icon="Edit"
  51. type="warning"
  52. plain
  53. @click="beforeEditPackage(row.id)"
  54. >
  55. 编辑
  56. </el-button>
  57. </template>
  58. </el-table-column>
  59. </el-table>
  60. </div>
  61. <el-drawer v-model="showCreation" :title="drawerTitle" size="50%">
  62. <el-form :model="mainPackage" label-width="auto" :rules="packageMainRules">
  63. <el-form-item label="套餐名称" prop="name">
  64. <el-input
  65. v-model="mainPackage.name"
  66. maxlength="32"
  67. show-word-limit
  68. :disabled="disableEdit"
  69. />
  70. </el-form-item>
  71. <el-form-item label="套餐描述" prop="description">
  72. <el-input
  73. v-model="mainPackage.description"
  74. maxlength="256"
  75. show-word-limit
  76. :disabled="disableEdit"
  77. type="textarea"
  78. :rows="3"
  79. />
  80. </el-form-item>
  81. <el-form-item label="套餐图片" required prop="fileList">
  82. <el-upload
  83. :disabled="disableEdit"
  84. class="avatar-uploader"
  85. :show-file-list="false"
  86. ref="uploadRef"
  87. v-model:file-list="fileList"
  88. :action="apiUrl + '/wxmallPackage/createNewPackage'"
  89. :headers="header"
  90. :data="creation"
  91. :limit="1"
  92. :on-change="handleSelectImage"
  93. :on-exceed="handleExceed"
  94. accept="image/jpeg,image/jpg,image/png"
  95. :auto-upload="false"
  96. :on-success="onSubmitSuccess"
  97. >
  98. <el-image v-if="imageUrl" fit="cover" :src="imageUrl" class="avatar-image" />
  99. <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
  100. <template #tip>
  101. <div class="gray-text">
  102. 请选择小于 200Kb 的 jpg/png 格式的图片。
  103. </div>
  104. </template>
  105. </el-upload>
  106. </el-form-item>
  107. </el-form>
  108. <el-divider>
  109. <el-tag>套餐明细</el-tag>
  110. </el-divider>
  111. <el-form
  112. v-for="(item, index) in packageItemList"
  113. :key="index"
  114. :model="packageItemList[index]"
  115. inline
  116. :disabled="disableEdit"
  117. label-width="auto"
  118. >
  119. <el-form-item label="名称" required>
  120. <el-input v-model="item.hisName" readonly @click="displaySearchPanel(index)"/>
  121. </el-form-item>
  122. <el-form-item label="原价">
  123. <el-input disabled v-model="item.originPrice" type="number" class="w-60" />
  124. </el-form-item>
  125. <el-form-item label="优惠价" required>
  126. <el-input v-model="item.discountPrice" type="number" class="w-60" />
  127. </el-form-item>
  128. <el-form-item label="数量" required>
  129. <el-input v-model.number="item.quantity" class="w-60" />
  130. </el-form-item>
  131. <el-form-item v-if="!disableEdit">
  132. <el-button
  133. v-if="index === packageItemList.length - 1"
  134. icon="Plus"
  135. type="success"
  136. @click="addNewItemLine(index)"
  137. >
  138. 添加
  139. </el-button>
  140. <el-button
  141. icon="Minus"
  142. type="danger"
  143. @click="deleteItemLine(index)"
  144. >
  145. 删除
  146. </el-button>
  147. </el-form-item>
  148. </el-form>
  149. <template #footer>
  150. <div v-if="disableEdit">
  151. <el-button @click="mode = 'edit'" type="primary" size="default">编辑</el-button>
  152. </div>
  153. <div style="flex: auto" v-else>
  154. <el-button @click="showCreation = false" size="default">取消</el-button>
  155. <el-button @click="submitUpload" size="default" type="primary">提交</el-button>
  156. </div>
  157. </template>
  158. </el-drawer>
  159. <Search
  160. v-if="showSearch"
  161. med-type="01"
  162. target="JY"
  163. title="收费项目查询"
  164. width="520px"
  165. show-mall-type
  166. @close="showSearch = false"
  167. @click-item="handleClickSearchItem"
  168. />
  169. </div>
  170. </template>
  171. <script setup>
  172. import {
  173. getPackagesByCondition,
  174. getOriginalPrice,
  175. getPackageDetail,
  176. createNewPackage2,
  177. updatePackageState
  178. } from "@/api/outpatient/wxmall-package.js";
  179. import useDateRange from "@/utils/cy-use/useDateRange";
  180. import {ElMessage, genFileId} from "element-plus";
  181. import {useUserStore} from "@/pinia/user-store";
  182. import env from "@/utils/setting";
  183. import Search from "@/components/search/Index.vue";
  184. const apiUrl = env.VITE_BASE_URL;
  185. const {CyDateRange, dateRange} = useDateRange({
  186. shortcutsIndex: 2,
  187. clearable: false,
  188. })
  189. const inquiry = reactive({
  190. start: '',
  191. end: '',
  192. state: null
  193. })
  194. const packageList = ref([])
  195. function queryPackages() {
  196. inquiry.start = dateRange.value.start
  197. inquiry.end = dateRange.value.end
  198. getPackagesByCondition(inquiry).then(data => {
  199. packageList.value = data
  200. })
  201. }
  202. function changePackageState(row) {
  203. updatePackageState(row).then(data => {
  204. ElMessage.success(data)
  205. })
  206. }
  207. const packageMainRules = reactive({
  208. name: [ { required: true, message: '请输入套餐名称', trigger: 'blur' } ],
  209. description: [ { required: true, message: '请输入套餐描述', trigger: 'blur' } ],
  210. })
  211. const mode = ref('see')
  212. const drawerTitle = computed(() => {
  213. if (mode.value === 'add') {
  214. return '上架套餐'
  215. }
  216. if (mode.value === 'see') {
  217. return '套餐详情'
  218. }
  219. return '编辑套餐'
  220. })
  221. const disableEdit = computed(() => {
  222. return mode.value === 'see'
  223. })
  224. const showCreation = ref(false)
  225. const mainPackage = ref({})
  226. const uploadRef = ref()
  227. const fileList = ref([])
  228. const imageUrl = ref('');
  229. function beforeAddPackage() {
  230. mode.value = 'add';
  231. showCreation.value = true
  232. }
  233. function handleSelectImage({raw}) {
  234. if (raw.type !== "image/jpeg" && raw.type !== "image/png") {
  235. ElMessage.error("请选择 jpg/png 格式的图片!");
  236. uploadRef.value.clearFiles();
  237. return
  238. }
  239. if (raw.size / 1024 > 200) {
  240. ElMessage.error("图片大小不能超过 200Kb!");
  241. uploadRef.value.clearFiles();
  242. return;
  243. }
  244. imageUrl.value = URL.createObjectURL(raw);
  245. }
  246. function handleExceed(files) {
  247. uploadRef.value.clearFiles();
  248. const file = files[0];
  249. file.uid = genFileId();
  250. uploadRef.value.handleStart(file);
  251. }
  252. const packageItemList = ref([{quantity: 1}])
  253. const showSearch = ref(false)
  254. const currentIndex = ref(0)
  255. function displaySearchPanel(index) {
  256. currentIndex.value = index
  257. showSearch.value = true
  258. }
  259. async function handleClickSearchItem(val) {
  260. packageItemList.value[currentIndex.value].hisCode = val.code
  261. packageItemList.value[currentIndex.value].hisName = val.name
  262. packageItemList.value[currentIndex.value].type = val.type
  263. if (val.type === 'ZL') {
  264. packageItemList.value[currentIndex.value].originPrice = val.price
  265. packageItemList.value[currentIndex.value].discountPrice = val.price
  266. } else {
  267. await getOriginalPrice(val.type, val.code).then(price => {
  268. packageItemList.value[currentIndex.value].originPrice = price
  269. packageItemList.value[currentIndex.value].discountPrice = price
  270. })
  271. }
  272. showSearch.value = false;
  273. }
  274. function addNewItemLine(index) {
  275. let item = packageItemList.value[index]
  276. if (!isValidItem(item)) {
  277. return
  278. }
  279. packageItemList.value.push({quantity: 1});
  280. }
  281. function deleteItemLine(index) {
  282. packageItemList.value.splice(index, 1);
  283. }
  284. function isValidItem(item) {
  285. if (!item.hisCode) {
  286. ElMessage.error('请先填写项目名称')
  287. return false
  288. }
  289. if (!item.discountPrice || item.discountPrice < 0) {
  290. ElMessage.error('项目优惠价不能小于 0')
  291. return false
  292. }
  293. if (!item.quantity || item.quantity < 1) {
  294. ElMessage.error('项目数量不能小于 1,且必须为 整数')
  295. return false
  296. }
  297. return true;
  298. }
  299. const addedItemCode = ref([])
  300. const header = {
  301. token: useUserStore().getToken
  302. }
  303. const creation = ref({
  304. mainPackageJson: null,
  305. packageItemListJson: null
  306. })
  307. function submitUpload() {
  308. addedItemCode.value = []
  309. if (!mainPackage.value.name) {
  310. ElMessage.error('套餐名称不能为空')
  311. return
  312. }
  313. if (!mainPackage.value.description) {
  314. ElMessage.error('套餐描述不能为空')
  315. return
  316. }
  317. if (fileList.value.length === 0 && mode.value === 'add') {
  318. ElMessage.error('套餐图片不能为空')
  319. return
  320. }
  321. if (packageItemList.value.length === 0) {
  322. ElMessage.error('套餐明细不能为空')
  323. return
  324. }
  325. for (let i = 0; i < packageItemList.value.length; i++) {
  326. let item = packageItemList.value[i]
  327. if (addedItemCode.value.indexOf(item.hisCode) !== -1) {
  328. ElMessage.error('请勿添加重复项')
  329. return
  330. }
  331. addedItemCode.value.push(item.hisCode)
  332. if (!isValidItem(item)) {
  333. return
  334. }
  335. }
  336. creation.value.mainPackageJson = JSON.stringify(mainPackage.value);
  337. creation.value.packageItemListJson = JSON.stringify(packageItemList.value)
  338. nextTick(() => {
  339. if (fileList.value.length > 0) {
  340. uploadRef.value.submit()
  341. } else {
  342. createNewPackage2(creation.value).then(res => {
  343. onSubmitSuccess(res)
  344. })
  345. }
  346. })
  347. }
  348. function resetFormData() {
  349. mainPackage.value = {}
  350. packageItemList.value = [{quantity: 1}]
  351. creation.value = {
  352. mainPackageJson: null,
  353. packageItemListJson: null
  354. }
  355. }
  356. function onSubmitSuccess(res) {
  357. resetFormData()
  358. nextTick(() => {
  359. imageUrl.value = ''
  360. uploadRef.value.clearFiles();
  361. ElMessage.success('提交成功')
  362. if (mode.value === 'add') {
  363. packageList.value.push(res.data)
  364. } else if (mode.value === 'edit') {
  365. queryPackages()
  366. }
  367. showCreation.value = false
  368. })
  369. }
  370. function seePackageDetail(id) {
  371. getPackageDetail(id).then(res => {
  372. mode.value = 'see';
  373. mainPackage.value = res
  374. packageItemList.value = res.items
  375. imageUrl.value = res.thumbPath
  376. showCreation.value = true
  377. })
  378. }
  379. function beforeEditPackage(id) {
  380. getPackageDetail(id).then(res => {
  381. mode.value = 'edit';
  382. mainPackage.value = res
  383. packageItemList.value = res.items
  384. imageUrl.value = res.thumbPath
  385. showCreation.value = true
  386. })
  387. }
  388. </script>
  389. <style lang="scss">
  390. .wxmall-package {
  391. .ml-12 {
  392. margin-left: 12px;
  393. }
  394. .w-80 {
  395. width: 80px;
  396. }
  397. .w-60 {
  398. width: 60px;
  399. }
  400. .gray-text {
  401. color: gray;
  402. }
  403. .avatar-uploader {
  404. .el-upload {
  405. border: 1px dashed var(--el-border-color);
  406. border-radius: 6px;
  407. cursor: pointer;
  408. position: relative;
  409. overflow: hidden;
  410. transition: var(--el-transition-duration-fast);
  411. }
  412. .el-upload:hover {
  413. border-color: var(--el-color-primary);
  414. }
  415. .avatar-uploader-icon {
  416. font-size: 28px;
  417. color: #8c939d;
  418. width: 100px;
  419. height: 100px;
  420. text-align: center;
  421. }
  422. .avatar-image {
  423. width: 100px;
  424. height: 100px;
  425. }
  426. }
  427. .el-form--inline .el-form-item {
  428. margin-right: 16px;
  429. }
  430. }
  431. </style>