ExpressTemplateForm.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <template>
  2. <Dialog :title="dialogTitle" v-model="dialogVisible" width="80%">
  3. <el-form
  4. ref="formRef"
  5. :model="formData"
  6. :rules="formRules"
  7. label-width="80px"
  8. v-loading="formLoading"
  9. >
  10. <el-form-item label="模板名称" prop="name">
  11. <el-input v-model="formData.name" placeholder="请输入模板名称" />
  12. </el-form-item>
  13. <el-form-item label="计费方式" prop="chargeMode">
  14. <el-radio-group v-model="formData.chargeMode" @change="changeChargeMode">
  15. <el-radio
  16. v-for="dict in getIntDictOptions(DICT_TYPE.EXPRESS_CHARGE_MODE)"
  17. :key="dict.value"
  18. :label="dict.value"
  19. >
  20. {{ dict.label }}
  21. </el-radio>
  22. </el-radio-group>
  23. </el-form-item>
  24. <el-form-item label="运费" prop="templateCharge">
  25. <el-table border style="width: 100%" :data="formData.templateCharge">
  26. <el-table-column align="center" label="区域" width="180">
  27. <template #default="{ row }">
  28. <!-- 区域数据太多,用赖加载方式,要不然性能有问题 -->
  29. <el-tree-select
  30. v-model="row.areaIds"
  31. lazy
  32. :load="loadChargeArea"
  33. :props="defaultProps"
  34. multiple
  35. node-key="id"
  36. check-strictly
  37. show-checkbox
  38. check-on-click-node
  39. :render-after-expand="false"
  40. :cache-data="areaCache"
  41. />
  42. </template>
  43. </el-table-column>
  44. <el-table-column
  45. align="center"
  46. :label="columnTitle.startCountTitle"
  47. width="180"
  48. prop="startCount"
  49. >
  50. <template #default="{ row }">
  51. <el-input-number v-model="row.startCount" :min="1" />
  52. </template>
  53. </el-table-column>
  54. <el-table-column width="180" align="center" label="运费(元)" prop="startPrice">
  55. <template #default="{ row }">
  56. <el-input-number v-model="row.startPrice" :min="1" />
  57. </template>
  58. </el-table-column>
  59. <el-table-column
  60. width="180"
  61. align="center"
  62. :label="columnTitle.extraCountTitle"
  63. prop="extraCount"
  64. >
  65. <template #default="{ row }">
  66. <el-input-number v-model="row.extraCount" :min="1" />
  67. </template>
  68. </el-table-column>
  69. <el-table-column width="180" align="center" label="续费(元)" prop="extraPrice">
  70. <template #default="{ row }">
  71. <el-input-number v-model="row.extraPrice" :min="1" />
  72. </template>
  73. </el-table-column>
  74. <el-table-column label="操作" align="center">
  75. <template #default="scope">
  76. <el-button link type="danger" @click="deleteChargeArea(scope.$index)">
  77. 删除
  78. </el-button>
  79. </template>
  80. </el-table-column>
  81. </el-table>
  82. </el-form-item>
  83. <el-form-item>
  84. <el-button type="primary" plain @click="addChargeArea()">
  85. <Icon icon="ep:plus" class="mr-5px" /> 添加区域
  86. </el-button>
  87. </el-form-item>
  88. <el-form-item label="包邮区域" prop="templateFree">
  89. <el-table border style="width: 100%" :data="formData.templateFree">
  90. <el-table-column align="center" label="区域">
  91. <template #default="{ row }">
  92. <!-- 区域数据太多,用赖加载方式,要不然性能有问题 -->
  93. <el-tree-select
  94. v-model="row.areaIds"
  95. multiple
  96. lazy
  97. :load="loadFreeArea"
  98. :props="defaultProps"
  99. node-key="id"
  100. check-strictly
  101. show-checkbox
  102. check-on-click-node
  103. :render-after-expand="true"
  104. :cache-data="areaCache"
  105. />
  106. </template>
  107. </el-table-column>
  108. <el-table-column align="center" :label="columnTitle.freeCountTitle" prop="freeCount">
  109. <template #default="{ row }">
  110. <el-input-number v-model="row.freeCount" :min="1" />
  111. </template>
  112. </el-table-column>
  113. <el-table-column align="center" label="包邮金额(元)" prop="freePrice">
  114. <template #default="{ row }">
  115. <el-input-number v-model="row.freePrice" :min="1" />
  116. </template>
  117. </el-table-column>
  118. <el-table-column label="操作" align="center">
  119. <template #default="scope">
  120. <el-button link type="danger" @click="deleteFreeArea(scope.$index)"> 删除 </el-button>
  121. </template>
  122. </el-table-column>
  123. </el-table>
  124. </el-form-item>
  125. <el-form-item>
  126. <el-button type="primary" plain @click="addFreeArea()">
  127. <Icon icon="ep:plus" class="mr-5px" /> 添加区域
  128. </el-button>
  129. </el-form-item>
  130. <el-form-item label="排序" prop="sort">
  131. <el-input-number v-model="formData.sort" controls-position="right" :min="0" />
  132. </el-form-item>
  133. </el-form>
  134. <template #footer>
  135. <el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
  136. <el-button @click="dialogVisible = false">取 消</el-button>
  137. </template>
  138. </Dialog>
  139. </template>
  140. <script setup lang="ts">
  141. import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  142. import * as DeliveryExpressTemplateApi from '@/api/mall/trade/delivery/expressTemplate'
  143. import { defaultProps } from '@/utils/tree'
  144. import { yuanToFen, fenToYuan } from '@/utils'
  145. import { getChildrenArea, getAreaListByIds } from '@/api/system/area'
  146. import { cloneDeep } from 'lodash-es'
  147. const { t } = useI18n() // 国际化
  148. const message = useMessage() // 消息弹窗
  149. const dialogVisible = ref(false) // 弹窗的是否展示
  150. const dialogTitle = ref('') // 弹窗的标题
  151. const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
  152. const formType = ref('') // 表单的类型:create - 新增;update - 修改
  153. const formData = ref({
  154. id: undefined,
  155. name: '',
  156. chargeMode: 1,
  157. sort: 0,
  158. templateCharge: [],
  159. templateFree: []
  160. })
  161. const columnTitleMap = new Map()
  162. const columnTitle = ref({
  163. startCountTitle: '首件',
  164. extraCountTitle: '续件',
  165. freeCountTitle: '包邮件数'
  166. })
  167. const formRules = reactive({
  168. name: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }],
  169. chargeMode: [{ required: true, message: '配送计费方式不能为空', trigger: 'blur' }],
  170. sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }]
  171. })
  172. const formRef = ref() // 表单 Ref
  173. const areaCache = ref([]) //由于区域节点懒加载,已选区域节点需要缓存展示
  174. /** 打开弹窗 */
  175. const open = async (type: string, id?: number) => {
  176. dialogVisible.value = true
  177. dialogTitle.value = t('action.' + type)
  178. formType.value = type
  179. resetForm()
  180. try {
  181. // 修改时,设置数据
  182. if (id) {
  183. formLoading.value = true
  184. formData.value = await DeliveryExpressTemplateApi.getDeliveryExpressTemplate(id)
  185. columnTitle.value = columnTitleMap.get(formData.value.chargeMode)
  186. const chargeAreaIds = []
  187. const freeAreaIds = []
  188. formData.value.templateCharge.forEach((item) => {
  189. for (let i = 0; i < item.areaIds.length; i++) {
  190. if (!chargeAreaIds.includes(item.areaIds[i])) {
  191. chargeAreaIds.push(item.areaIds[i])
  192. }
  193. }
  194. //前端价格以元展示
  195. item.startPrice = fenToYuan(item.startPrice)
  196. item.extraPrice = fenToYuan(item.extraPrice)
  197. })
  198. formData.value.templateFree.forEach((item) => {
  199. for (let i = 0; i < item.areaIds.length; i++) {
  200. if (!chargeAreaIds.includes(item.areaIds[i]) && !freeAreaIds.includes(item.areaIds[i])) {
  201. freeAreaIds.push(item.areaIds[i])
  202. }
  203. }
  204. item.freePrice = fenToYuan(item.freePrice)
  205. })
  206. //已选的区域节点
  207. const areaIds = chargeAreaIds.concat(freeAreaIds)
  208. //区域节点,懒加载方式。 已选节点需要缓存展示
  209. areaCache.value = await getAreaListByIds(areaIds.join(','))
  210. }
  211. } finally {
  212. formLoading.value = false
  213. }
  214. }
  215. defineExpose({ open }) // 提供 open 方法,用于打开弹窗
  216. /** 提交表单 */
  217. const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
  218. const submitForm = async () => {
  219. // 校验表单
  220. if (!formRef) return
  221. const valid = await formRef.value.validate()
  222. if (!valid) return
  223. // 提交请求
  224. formLoading.value = true
  225. try {
  226. const data = formData.value as DeliveryExpressTemplateApi.DeliveryExpressTemplateVO
  227. data.templateCharge.forEach((item) => {
  228. //前端价格以元展示,提交到后端。用分计算
  229. item.startPrice = yuanToFen(item.startPrice)
  230. item.extraPrice = yuanToFen(item.extraPrice)
  231. })
  232. data.templateFree.forEach((item) => {
  233. item.freePrice = yuanToFen(item.freePrice)
  234. })
  235. if (formType.value === 'create') {
  236. await DeliveryExpressTemplateApi.createDeliveryExpressTemplate(data)
  237. message.success(t('common.createSuccess'))
  238. } else {
  239. await DeliveryExpressTemplateApi.updateDeliveryExpressTemplate(data)
  240. message.success(t('common.updateSuccess'))
  241. }
  242. dialogVisible.value = false
  243. // 发送操作成功的事件
  244. emit('success')
  245. } finally {
  246. formLoading.value = false
  247. }
  248. }
  249. /** 重置表单 */
  250. const resetForm = () => {
  251. formData.value = {
  252. id: undefined,
  253. name: '',
  254. chargeMode: 1,
  255. templateCharge: [
  256. {
  257. areaIds: [1],
  258. startCount: 2,
  259. startPrice: 5,
  260. extraCount: 5,
  261. extraPrice: 10
  262. }
  263. ],
  264. templateFree: [],
  265. sort: 0
  266. }
  267. columnTitle.value = columnTitleMap.get(1)
  268. formRef.value?.resetFields()
  269. }
  270. /** 配送计费方法改变 */
  271. const changeChargeMode = (chargeMode: number) => {
  272. columnTitle.value = columnTitleMap.get(chargeMode)
  273. }
  274. const defaultArea = [{ id: 1, name: '全国', disabled: false }]
  275. /** 初始化数据 */
  276. const initData = async () => {
  277. // TODO 从服务端全量加载数据, 后面看懒加载是不是可以从前端获取数据。 目前从后端获取数据
  278. // formLoading.value = true
  279. // try {
  280. // const data = await getAreaTree()
  281. // areaTree = data
  282. // console.log('areaTree', areaTree)
  283. // } finally {
  284. // formLoading.value = false
  285. // }
  286. //表头标题和计费方式的映射
  287. columnTitleMap.set(1, {
  288. startCountTitle: '首件',
  289. extraCountTitle: '续件',
  290. freeCountTitle: '包邮件数'
  291. })
  292. columnTitleMap.set(2, {
  293. startCountTitle: '首件重量(kg)',
  294. extraCountTitle: '续件重量(kg)',
  295. freeCountTitle: '包邮重量(kg)'
  296. })
  297. columnTitleMap.set(3, {
  298. startCountTitle: '首件体积(m³)',
  299. extraCountTitle: '续件体积(m³)',
  300. freeCountTitle: '包邮体积(m³)'
  301. })
  302. }
  303. /** 懒加载运费区域树 */
  304. const loadChargeArea = async (node, resolve) => {
  305. //已选区域需要禁止再次选择
  306. const areaIds = []
  307. formData.value.templateCharge.forEach((item) => {
  308. if (item.areaIds.length > 0) {
  309. item.areaIds.forEach((areaId) => areaIds.push(areaId))
  310. }
  311. })
  312. if (node.isLeaf) return resolve([])
  313. const length = node.data.length
  314. if (length === 0) {
  315. const data = cloneDeep(defaultArea)
  316. const item = data[0]
  317. if (areaIds.includes(item.id)) {
  318. // TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
  319. //item.disabled = true
  320. }
  321. resolve(data)
  322. } else {
  323. const id = node.data.id
  324. const data = await getChildrenArea(id)
  325. data.forEach((item) => {
  326. if (areaIds.includes(item.id)) {
  327. //item.disabled = true
  328. }
  329. })
  330. resolve(data)
  331. }
  332. }
  333. /** 懒加载包邮区域树 */
  334. const loadFreeArea = async (node, resolve) => {
  335. if (node.isLeaf) return resolve([])
  336. //已选区域需要禁止再次选择
  337. const areaIds = []
  338. formData.value.templateFree.forEach((item) => {
  339. if (item.areaIds.length > 0) {
  340. item.areaIds.forEach((areaId) => areaIds.push(areaId))
  341. }
  342. })
  343. const length = node.data.length
  344. if (length === 0) {
  345. // 为空,从全国开始选择。全国 id == 1
  346. const data = cloneDeep(defaultArea)
  347. const item = data[0]
  348. if (areaIds.includes(item.id)) {
  349. //item.disabled = true
  350. }
  351. resolve(data)
  352. } else {
  353. const id = node.data.id
  354. const data = await getChildrenArea(id)
  355. //已选区域需要禁止再次选择
  356. data.forEach((item) => {
  357. if (areaIds.includes(item.id)) {
  358. // TODO 禁止选中的区域有些问题, 导致修改时候不能重新选择 不知道如何处理。 暂时注释掉 @芋艿 有空瞅瞅
  359. //item.disabled = true
  360. }
  361. })
  362. resolve(data)
  363. }
  364. }
  365. /** 添加计费区域 */
  366. const addChargeArea = () => {
  367. const data = formData.value
  368. data.templateCharge.push({
  369. areaIds: [],
  370. startCount: 1,
  371. startPrice: 1,
  372. extraCount: 1,
  373. extraPrice: 1
  374. })
  375. }
  376. /** 删除计费区域 */
  377. const deleteChargeArea = (index) => {
  378. const data = formData.value
  379. data.templateCharge.splice(index, 1)
  380. }
  381. /** 添加包邮区域 */
  382. const addFreeArea = () => {
  383. const data = formData.value
  384. data.templateFree.push({
  385. areaIds: [],
  386. freeCount: 1,
  387. freePrice: 1
  388. })
  389. }
  390. /** 删除包邮区域 */
  391. const deleteFreeArea = (index) => {
  392. const data = formData.value
  393. data.templateFree.splice(index, 1)
  394. }
  395. /** 初始化 **/
  396. onMounted(() => {
  397. initData()
  398. })
  399. </script>