feat: mall order
							parent
							
								
									acc65c3b06
								
							
						
					
					
						commit
						f86c41b669
					
				|  | @ -189,6 +189,8 @@ export namespace MallOrderApi { | |||
|   export interface DeliveryRequest { | ||||
|     /** 订单编号 */ | ||||
|     id?: number; | ||||
|     /** 发货方式 */ | ||||
|     expressType: string; | ||||
|     /** 物流公司编号 */ | ||||
|     logisticsId: null | number; | ||||
|     /** 物流编号 */ | ||||
|  |  | |||
|  | @ -0,0 +1,218 @@ | |||
| import type { VbenFormSchema } from '#/adapter/form'; | ||||
| import type { VxeGridPropTypes } from '#/adapter/vxe-table'; | ||||
| import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore'; | ||||
| 
 | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| import { getSimpleDeliveryExpressList } from '#/api/mall/trade/delivery/express'; | ||||
| import { getSimpleDeliveryPickUpStoreList } from '#/api/mall/trade/delivery/pickUpStore'; | ||||
| import { | ||||
|   DeliveryTypeEnum, | ||||
|   DICT_TYPE, | ||||
|   getDictOptions, | ||||
|   getRangePickerDefaultProps, | ||||
| } from '#/utils'; | ||||
| 
 | ||||
| const pickUpStoreList = ref<MallDeliveryPickUpStoreApi.PickUpStore[]>([]); | ||||
| 
 | ||||
| getSimpleDeliveryPickUpStoreList().then((res) => { | ||||
|   pickUpStoreList.value = res; | ||||
| }); | ||||
| 
 | ||||
| /** 列表的搜索表单 */ | ||||
| export function useGridFormSchema(): VbenFormSchema[] { | ||||
|   return [ | ||||
|     { | ||||
|       fieldName: 'status', | ||||
|       label: '订单状态', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         options: getDictOptions(DICT_TYPE.TRADE_ORDER_STATUS, 'number'), | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'payChannelCode', | ||||
|       label: '支付方式', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE, 'number'), | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'name', | ||||
|       label: '品牌名称', | ||||
|       component: 'Input', | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'createTime', | ||||
|       label: '创建时间', | ||||
|       component: 'RangePicker', | ||||
|       componentProps: { | ||||
|         ...getRangePickerDefaultProps(), | ||||
|         allowClear: true, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'terminal', | ||||
|       label: '订单来源', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         options: getDictOptions(DICT_TYPE.TERMINAL, 'number'), | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'deliveryType', | ||||
|       label: '配送方式', | ||||
|       component: 'Select', | ||||
|       componentProps: { | ||||
|         options: getDictOptions(DICT_TYPE.TRADE_DELIVERY_TYPE, 'number'), | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'logisticsId', | ||||
|       label: '快递公司', | ||||
|       component: 'ApiSelect', | ||||
|       componentProps: { | ||||
|         api: getSimpleDeliveryExpressList, | ||||
|         fieldNames: { | ||||
|           label: 'name', | ||||
|           value: 'id', | ||||
|         }, | ||||
|       }, | ||||
|       dependencies: { | ||||
|         triggerFields: ['deliveryType'], | ||||
|         show: (values) => values.deliveryType === DeliveryTypeEnum.EXPRESS.type, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'pickUpStoreId', | ||||
|       label: '自提门店', | ||||
|       component: 'ApiSelect', | ||||
|       componentProps: { | ||||
|         api: getSimpleDeliveryPickUpStoreList, | ||||
|         fieldNames: { | ||||
|           label: 'name', | ||||
|           value: 'id', | ||||
|         }, | ||||
|       }, | ||||
|       dependencies: { | ||||
|         triggerFields: ['deliveryType'], | ||||
|         show: (values) => values.deliveryType === DeliveryTypeEnum.PICK_UP.type, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'deliveryType', | ||||
|       label: '核销码', | ||||
|       component: 'Input', | ||||
|       dependencies: { | ||||
|         triggerFields: ['deliveryType'], | ||||
|         show: (values) => values.deliveryType === DeliveryTypeEnum.PICK_UP.type, | ||||
|       }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
| 
 | ||||
| /** 表格列配置 */ | ||||
| export function useGridColumns(): VxeGridPropTypes.Columns { | ||||
|   return [ | ||||
|     { | ||||
|       type: 'expand', | ||||
|       width: 80, | ||||
|       slots: { content: 'expand_content' }, | ||||
|       fixed: 'left', | ||||
|     }, | ||||
|     { | ||||
|       field: 'no', | ||||
|       title: '订单号', | ||||
|       fixed: 'left', | ||||
|       minWidth: 180, | ||||
|     }, | ||||
|     { | ||||
|       field: 'createTime', | ||||
|       title: '下单时间', | ||||
|       formatter: 'formatDateTime', | ||||
|       minWidth: 160, | ||||
|     }, | ||||
|     { | ||||
|       field: 'terminal', | ||||
|       title: '订单来源', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.TERMINAL }, | ||||
|       }, | ||||
|       minWidth: 120, | ||||
|     }, | ||||
|     { | ||||
|       field: 'payChannelCode', | ||||
|       title: '支付方式', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.PAY_CHANNEL_CODE }, | ||||
|       }, | ||||
|       minWidth: 120, | ||||
|     }, | ||||
|     { | ||||
|       field: 'payTime', | ||||
|       title: '支付时间', | ||||
|       formatter: 'formatDateTime', | ||||
|       minWidth: 160, | ||||
|     }, | ||||
|     { | ||||
|       field: 'type', | ||||
|       title: '订单类型', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.TRADE_ORDER_TYPE }, | ||||
|       }, | ||||
|       minWidth: 80, | ||||
|     }, | ||||
| 
 | ||||
|     { | ||||
|       field: 'payPrice', | ||||
|       title: '实际支付', | ||||
|       formatter: 'formatFraction', | ||||
|       minWidth: 180, | ||||
|     }, | ||||
|     { | ||||
|       field: 'user', | ||||
|       title: '买家/收货人', | ||||
|       formatter: ({ row }) => { | ||||
|         if (row.deliveryType === DeliveryTypeEnum.EXPRESS.type) { | ||||
|           return `买家:${row.user?.nickname} / 收货人: ${row.receiverName} ${row.receiverMobile}${row.receiverAreaName}${row.receiverDetailAddress}`; | ||||
|         } | ||||
|         if (row.deliveryType === DeliveryTypeEnum.PICK_UP.type) { | ||||
|           return `门店名称:${pickUpStoreList.value.find((item) => item.id === row.pickUpStoreId)?.name} /
 | ||||
|                   门店手机:${pickUpStoreList.value.find((item) => item.id === row.pickUpStoreId)?.phone} / | ||||
|                   自提门店:${pickUpStoreList.value.find((item) => item.id === row.pickUpStoreId)?.detailAddress} | ||||
|                   `;
 | ||||
|         } | ||||
|         return ''; | ||||
|       }, | ||||
|       minWidth: 180, | ||||
|     }, | ||||
|     { | ||||
|       field: 'deliveryType', | ||||
|       title: '配送方式', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.TRADE_DELIVERY_TYPE }, | ||||
|       }, | ||||
|       minWidth: 80, | ||||
|     }, | ||||
|     { | ||||
|       field: 'status', | ||||
|       title: '订单状态', | ||||
|       cellRender: { | ||||
|         name: 'CellDict', | ||||
|         props: { type: DICT_TYPE.TRADE_ORDER_STATUS }, | ||||
|       }, | ||||
|       minWidth: 80, | ||||
|     }, | ||||
|     { | ||||
|       title: '操作', | ||||
|       width: 180, | ||||
|       fixed: 'right', | ||||
|       slots: { default: 'actions' }, | ||||
|     }, | ||||
|   ]; | ||||
| } | ||||
|  | @ -1,13 +1,110 @@ | |||
| <script lang="ts" setup> | ||||
| import { Page } from '@vben/common-ui'; | ||||
| import type { VxeTableGridOptions } from '#/adapter/vxe-table'; | ||||
| import type { MallOrderApi } from '#/api/mall/trade/order'; | ||||
| 
 | ||||
| import { Button } from 'ant-design-vue'; | ||||
| import { h } from 'vue'; | ||||
| import { useRouter } from 'vue-router'; | ||||
| 
 | ||||
| import { Page, prompt, useVbenModal } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { Image, List, Tag, Textarea } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; | ||||
| import { getOrderPage, updateOrderRemark } from '#/api/mall/trade/order'; | ||||
| import { DictTag } from '#/components/dict-tag'; | ||||
| import { DocAlert } from '#/components/doc-alert'; | ||||
| import { $t } from '#/locales'; | ||||
| import { | ||||
|   DeliveryTypeEnum, | ||||
|   DICT_TYPE, | ||||
|   fenToYuan, | ||||
|   TradeOrderStatusEnum, | ||||
| } from '#/utils'; | ||||
| 
 | ||||
| import { useGridColumns, useGridFormSchema } from './data'; | ||||
| import DeleveryForm from './modules/delevery-form.vue'; | ||||
| 
 | ||||
| const [DeleveryFormModal, deleveryFormModalApi] = useVbenModal({ | ||||
|   connectedComponent: DeleveryForm, | ||||
|   destroyOnClose: true, | ||||
| }); | ||||
| 
 | ||||
| /** 刷新表格 */ | ||||
| function onRefresh() { | ||||
|   gridApi.query(); | ||||
| } | ||||
| const { push } = useRouter(); | ||||
| /** 详情 */ | ||||
| function handleDetail(row: MallOrderApi.Order) { | ||||
|   push({ name: 'TradeOrderDetail', params: { id: row.id } }); | ||||
| } | ||||
| 
 | ||||
| /** 发货 */ | ||||
| function handleDelivery(row: MallOrderApi.Order) { | ||||
|   deleveryFormModalApi.setData(row).open(); | ||||
| } | ||||
| 
 | ||||
| /** 备注 */ | ||||
| function handleRemake(row: MallOrderApi.Order) { | ||||
|   prompt({ | ||||
|     component: () => { | ||||
|       return h(Textarea, { | ||||
|         defaultValue: row.remark, | ||||
|         rows: 3, | ||||
|       }); | ||||
|     }, | ||||
|     content: '请输入订单备注', | ||||
|     title: '订单备注', | ||||
|     modelPropName: 'value', | ||||
|   }).then(async (val) => { | ||||
|     if (val) { | ||||
|       await updateOrderRemark({ | ||||
|         id: row.id as number, | ||||
|         remark: val, | ||||
|       }); | ||||
|       onRefresh(); | ||||
|     } | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| const [Grid, gridApi] = useVbenVxeGrid({ | ||||
|   formOptions: { | ||||
|     schema: useGridFormSchema(), | ||||
|   }, | ||||
|   gridOptions: { | ||||
|     expandConfig: { | ||||
|       trigger: 'row', | ||||
|       expandAll: true, | ||||
|       padding: true, | ||||
|     }, | ||||
|     columns: useGridColumns(), | ||||
|     height: 'auto', | ||||
|     keepSource: true, | ||||
|     proxyConfig: { | ||||
|       ajax: { | ||||
|         query: async ({ page }, formValues) => { | ||||
|           return await getOrderPage({ | ||||
|             pageNo: page.currentPage, | ||||
|             pageSize: page.pageSize, | ||||
|             ...formValues, | ||||
|           }); | ||||
|         }, | ||||
|       }, | ||||
|     }, | ||||
|     rowConfig: { | ||||
|       keyField: 'id', | ||||
|     }, | ||||
|     toolbarConfig: { | ||||
|       refresh: { code: 'query' }, | ||||
|       search: true, | ||||
|     }, | ||||
|   } as VxeTableGridOptions<MallOrderApi.Order>, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Page> | ||||
|   <Page auto-content-height> | ||||
|     <template #doc> | ||||
|       <DocAlert | ||||
|         title="【交易】交易订单" | ||||
|         url="https://doc.iocoder.cn/mall/trade-order/" | ||||
|  | @ -16,23 +113,70 @@ import { DocAlert } from '#/components/doc-alert'; | |||
|         title="【交易】购物车" | ||||
|         url="https://doc.iocoder.cn/mall/trade-cart/" | ||||
|       /> | ||||
|     <Button | ||||
|       danger | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3" | ||||
|     </template> | ||||
|     <DeleveryFormModal @success="onRefresh" /> | ||||
|     <Grid table-title="订单列表"> | ||||
|       <template #expand_content="{ row }"> | ||||
|         <List item-layout="vertical" :data-source="row.items"> | ||||
|           <template #renderItem="{ item }"> | ||||
|             <List.Item> | ||||
|               <List.Item.Meta> | ||||
|                 <template #title> | ||||
|                   {{ item.spuName }} | ||||
|                   <Tag | ||||
|                     color="blue" | ||||
|                     v-for="property in item.properties" | ||||
|                     :key="property.id" | ||||
|                   > | ||||
|       该功能支持 Vue3 + element-plus 版本! | ||||
|     </Button> | ||||
|     <br /> | ||||
|     <Button | ||||
|       type="link" | ||||
|       target="_blank" | ||||
|       href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/mall/trade/order/index" | ||||
|     > | ||||
|       可参考 | ||||
|       https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/mall/trade/order/index | ||||
|       代码,pull request 贡献给我们! | ||||
|     </Button> | ||||
|                     {{ property.propertyName }} : {{ property.valueName }} | ||||
|                   </Tag> | ||||
|                 </template> | ||||
|                 <template #avatar> | ||||
|                   <Image :src="item.picUrl" :width="40" :height="40" /> | ||||
|                 </template> | ||||
|                 <template #description> | ||||
|                   {{ | ||||
|                     `原价:${fenToYuan(item.price)} 元 / 数量:${item.count} 个` | ||||
|                   }} | ||||
|                   | | ||||
|                   <DictTag | ||||
|                     :type="DICT_TYPE.TRADE_ORDER_ITEM_AFTER_SALE_STATUS" | ||||
|                     :value="item.afterSaleStatus" | ||||
|                   /> | ||||
|                 </template> | ||||
|               </List.Item.Meta> | ||||
|             </List.Item> | ||||
|           </template> | ||||
|         </List> | ||||
|       </template> | ||||
|       <template #actions="{ row }"> | ||||
|         <TableAction | ||||
|           :actions="[ | ||||
|             { | ||||
|               label: $t('common.detail'), | ||||
|               type: 'link', | ||||
|               icon: ACTION_ICON.VIEW, | ||||
|               auth: ['trade:order:query'], | ||||
|               onClick: handleDetail.bind(null, row), | ||||
|             }, | ||||
|           ]" | ||||
|           :drop-down-actions="[ | ||||
|             { | ||||
|               label: '发货', | ||||
|               type: 'link', | ||||
|               ifShow: () => | ||||
|                 row.deliveryType === DeliveryTypeEnum.EXPRESS.type && | ||||
|                 row.status === TradeOrderStatusEnum.UNDELIVERED.status, | ||||
|               onClick: handleDelivery.bind(null, row), | ||||
|             }, | ||||
|             { | ||||
|               label: '备注', | ||||
|               type: 'link', | ||||
|               onClick: handleRemake.bind(null, row), | ||||
|             }, | ||||
|           ]" | ||||
|         /> | ||||
|       </template> | ||||
|     </Grid> | ||||
|   </Page> | ||||
| </template> | ||||
|  |  | |||
|  | @ -0,0 +1,130 @@ | |||
| <script lang="ts" setup> | ||||
| import type { MallOrderApi } from '#/api/mall/trade/order'; | ||||
| 
 | ||||
| import { ref } from 'vue'; | ||||
| 
 | ||||
| import { useVbenModal } from '@vben/common-ui'; | ||||
| 
 | ||||
| import { message } from 'ant-design-vue'; | ||||
| 
 | ||||
| import { useVbenForm } from '#/adapter/form'; | ||||
| import { getSimpleDeliveryExpressList } from '#/api/mall/trade/delivery/express'; | ||||
| import { deliveryOrder } from '#/api/mall/trade/order'; | ||||
| import { $t } from '#/locales'; | ||||
| 
 | ||||
| const emit = defineEmits(['success']); | ||||
| 
 | ||||
| const formData = ref<MallOrderApi.DeliveryRequest>(); | ||||
| 
 | ||||
| const [Form, formApi] = useVbenForm({ | ||||
|   commonConfig: { | ||||
|     componentProps: { | ||||
|       class: 'w-full', | ||||
|     }, | ||||
|     formItemClass: 'col-span-2', | ||||
|     labelWidth: 120, | ||||
|   }, | ||||
|   layout: 'horizontal', | ||||
|   schema: [ | ||||
|     { | ||||
|       component: 'Input', | ||||
|       fieldName: 'id', | ||||
|       dependencies: { | ||||
|         triggerFields: [''], | ||||
|         show: () => false, | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'expressType', | ||||
|       label: '发货方式', | ||||
|       component: 'RadioGroup', | ||||
|       componentProps: { | ||||
|         options: [ | ||||
|           { label: '快递', value: 'express' }, | ||||
|           { label: '无需发货', value: 'none' }, | ||||
|         ], | ||||
|         buttonStyle: 'solid', | ||||
|         optionType: 'button', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'logisticsId', | ||||
|       label: '物流公司', | ||||
|       component: 'ApiSelect', | ||||
|       componentProps: { | ||||
|         api: getSimpleDeliveryExpressList, | ||||
|         fieldNames: { | ||||
|           label: 'name', | ||||
|           value: 'id', | ||||
|         }, | ||||
|       }, | ||||
|       dependencies: { | ||||
|         triggerFields: ['expressType'], | ||||
|         show: (values) => values.expressType === 'express', | ||||
|       }, | ||||
|     }, | ||||
|     { | ||||
|       fieldName: 'logisticsNo', | ||||
|       label: '物流单号', | ||||
|       component: 'Input', | ||||
|       dependencies: { | ||||
|         triggerFields: ['expressType'], | ||||
|         show: (values) => values.expressType === 'express', | ||||
|       }, | ||||
|     }, | ||||
|   ], | ||||
|   showDefaultActions: false, | ||||
| }); | ||||
| 
 | ||||
| const [Modal, modalApi] = useVbenModal({ | ||||
|   async onConfirm() { | ||||
|     const { valid } = await formApi.validate(); | ||||
|     if (!valid) { | ||||
|       return; | ||||
|     } | ||||
|     modalApi.lock(); | ||||
|     // 提交表单 | ||||
|     const data = (await formApi.getValues()) as MallOrderApi.DeliveryRequest; | ||||
|     if (data.expressType === 'none') { | ||||
|       // 无需发货的情况 | ||||
|       data.logisticsId = 0; | ||||
|       data.logisticsNo = ''; | ||||
|     } | ||||
|     try { | ||||
|       await deliveryOrder(data); | ||||
|       // 关闭并提示 | ||||
|       await modalApi.close(); | ||||
|       emit('success'); | ||||
|       message.success($t('ui.actionMessage.operationSuccess')); | ||||
|     } finally { | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
|   async onOpenChange(isOpen: boolean) { | ||||
|     if (!isOpen) { | ||||
|       formData.value = undefined; | ||||
|       return; | ||||
|     } | ||||
|     // 加载数据 | ||||
|     const data = modalApi.getData<MallOrderApi.Order>(); | ||||
|     if (!data) { | ||||
|       return; | ||||
|     } | ||||
|     modalApi.lock(); | ||||
|     try { | ||||
|       if (data.logisticsId === 0) { | ||||
|         await formApi.setValues({ expressType: 'none' }); | ||||
|       } | ||||
|       // 设置到 values | ||||
|     } finally { | ||||
|       modalApi.unlock(); | ||||
|     } | ||||
|   }, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <Modal class="w-[30%]" title="发货"> | ||||
|     <Form class="mx-4" /> | ||||
|   </Modal> | ||||
| </template> | ||||
		Loading…
	
		Reference in New Issue
	
	 xingyu4j
						xingyu4j