diff --git a/apps/web-ele/src/router/routes/modules/mall.ts b/apps/web-ele/src/router/routes/modules/mall.ts index e17ef4c8c..d84e1c703 100644 --- a/apps/web-ele/src/router/routes/modules/mall.ts +++ b/apps/web-ele/src/router/routes/modules/mall.ts @@ -66,7 +66,7 @@ const routes: RouteRecordRaw[] = [ title: '订单详情', activePath: '/mall/trade/order', }, - component: () => import('#/views/mall/trade/order/modules/detail.vue'), + component: () => import('#/views/mall/trade/order/detail/index.vue'), }, { path: String.raw`after-sale/detail/:id(\d+)`, diff --git a/apps/web-ele/src/views/mall/trade/order/data.ts b/apps/web-ele/src/views/mall/trade/order/data.ts index 89c5e0824..f1d671d9c 100644 --- a/apps/web-ele/src/views/mall/trade/order/data.ts +++ b/apps/web-ele/src/views/mall/trade/order/data.ts @@ -1,16 +1,16 @@ import type { VbenFormSchema } from '#/adapter/form'; -import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { VxeGridPropTypes } from '#/adapter/vxe-table'; import type { MallDeliveryPickUpStoreApi } from '#/api/mall/trade/delivery/pickUpStore'; import { ref } from 'vue'; -import { DeliveryTypeEnum } from '@vben/constants'; +import { DeliveryTypeEnum, DICT_TYPE } from '@vben/constants'; +import { getDictOptions } from '@vben/hooks'; +import { convertToInteger, formatToFraction } from '@vben/utils'; import { getSimpleDeliveryExpressList } from '#/api/mall/trade/delivery/express'; import { getSimpleDeliveryPickUpStoreList } from '#/api/mall/trade/delivery/pickUpStore'; -import { DICT_TYPE } from '@vben/constants'; -import { getDictOptions } from '@vben/hooks'; - +import { getAreaTree } from '#/api/system/area'; import { getRangePickerDefaultProps } from '#/utils'; const pickUpStoreList = ref([]); @@ -28,6 +28,8 @@ export function useGridFormSchema(): VbenFormSchema[] { component: 'Select', componentProps: { options: getDictOptions(DICT_TYPE.TRADE_ORDER_STATUS, 'number'), + placeholder: '请选择订单状态', + clearable: true, }, }, { @@ -36,13 +38,10 @@ export function useGridFormSchema(): VbenFormSchema[] { component: 'Select', componentProps: { options: getDictOptions(DICT_TYPE.PAY_CHANNEL_CODE, 'number'), + placeholder: '请选择支付方式', + clearable: true, }, }, - { - fieldName: 'name', - label: '品牌名称', - component: 'Input', - }, { fieldName: 'createTime', label: '创建时间', @@ -58,6 +57,8 @@ export function useGridFormSchema(): VbenFormSchema[] { component: 'Select', componentProps: { options: getDictOptions(DICT_TYPE.TERMINAL, 'number'), + placeholder: '请选择订单来源', + clearable: true, }, }, { @@ -66,6 +67,8 @@ export function useGridFormSchema(): VbenFormSchema[] { component: 'Select', componentProps: { options: getDictOptions(DICT_TYPE.TRADE_DELIVERY_TYPE, 'number'), + placeholder: '请选择配送方式', + clearable: true, }, }, { @@ -74,8 +77,12 @@ export function useGridFormSchema(): VbenFormSchema[] { component: 'ApiSelect', componentProps: { api: getSimpleDeliveryExpressList, - labelField: 'name', - valueField: 'id', + fieldNames: { + label: 'name', + value: 'id', + }, + placeholder: '请选择快递公司', + clearable: true, }, dependencies: { triggerFields: ['deliveryType'], @@ -88,8 +95,12 @@ export function useGridFormSchema(): VbenFormSchema[] { component: 'ApiSelect', componentProps: { api: getSimpleDeliveryPickUpStoreList, - labelField: 'name', - valueField: 'id', + fieldNames: { + label: 'name', + value: 'id', + }, + placeholder: '请选择自提门店', + clearable: true, }, dependencies: { triggerFields: ['deliveryType'], @@ -100,16 +111,56 @@ export function useGridFormSchema(): VbenFormSchema[] { fieldName: 'pickUpVerifyCode', label: '核销码', component: 'Input', + componentProps: { + placeholder: '请输入核销码', + clearable: true, + }, dependencies: { triggerFields: ['deliveryType'], show: (values) => values.deliveryType === DeliveryTypeEnum.PICK_UP.type, }, }, + { + fieldName: 'no', + label: '订单号', + component: 'Input', + componentProps: { + placeholder: '请输入订单号', + clearable: true, + }, + }, + { + fieldName: 'userId', + label: '用户 UID', + component: 'Input', + componentProps: { + placeholder: '请输入用户 UID', + clearable: true, + }, + }, + { + fieldName: 'userNickname', + label: '用户昵称', + component: 'Input', + componentProps: { + placeholder: '请输入用户昵称', + clearable: true, + }, + }, + { + fieldName: 'userMobile', + label: '用户电话', + component: 'Input', + componentProps: { + placeholder: '请输入用户电话', + clearable: true, + }, + }, ]; } /** 表格列配置 */ -export function useGridColumns(): VxeTableGridOptions['columns'] { +export function useGridColumns(): VxeGridPropTypes.Columns { return [ { type: 'expand', @@ -162,11 +213,10 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, minWidth: 80, }, - { field: 'payPrice', title: '实际支付', - formatter: 'formatFenToYuanAmount', + formatter: 'formatAmount2', minWidth: 180, }, { @@ -212,3 +262,199 @@ export function useGridColumns(): VxeTableGridOptions['columns'] { }, ]; } + +/** 订单备注表单配置 */ +export function useRemarkFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'remark', + label: '备注', + component: 'Input', + componentProps: { + type: 'textarea', + rows: 3, + }, + }, + ]; +} + +/** 订单调价表单配置 */ +export function usePriceFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'payPrice', + label: '应付金额(总)', + component: 'Input', + componentProps: { + placeholder: '请输入应付金额(总)', + disabled: true, + formatter: (value: string) => `${value}元`, + }, + }, + { + fieldName: 'adjustPrice', + label: '订单调价', + component: 'InputNumber', + componentProps: { + placeholder: '请输入订单调价', + step: 0.1, + precision: 2, + }, + help: '订单调价。 正数,加价;负数,减价', + rules: 'required', + }, + { + fieldName: 'newPayPrice', + label: '调价后', + component: 'Input', + componentProps: { + placeholder: '', + formatter: (value: string) => `${value}元`, + }, + dependencies: { + triggerFields: ['payPrice', 'adjustPrice'], + disabled: true, + trigger(values, form) { + const originalPrice = convertToInteger(values.payPrice); + const adjustPrice = convertToInteger(values.adjustPrice); + const newPrice = originalPrice + adjustPrice; + form.setFieldValue('newPayPrice', formatToFraction(newPrice)); + }, + }, + }, + ]; +} + +/** 订单修改地址表单配置 */ +export function useAddressFormSchema(): VbenFormSchema[] { + return [ + { + component: 'Input', + fieldName: 'id', + dependencies: { + triggerFields: [''], + show: () => false, + }, + }, + { + fieldName: 'receiverName', + label: '收件人', + component: 'Input', + componentProps: { + placeholder: '请输入收件人名称', + }, + rules: 'required', + }, + { + fieldName: 'receiverMobile', + label: '手机号', + component: 'Input', + componentProps: { + placeholder: '请输入收件人手机号', + }, + rules: 'required', + }, + { + fieldName: 'receiverAreaId', + label: '所在地', + component: 'ApiTreeSelect', + componentProps: { + api: () => getAreaTree(), + fieldNames: { + label: 'name', + value: 'id', + children: 'children', + }, + placeholder: '请选择收件人所在地', + treeDefaultExpandAll: true, + }, + rules: 'required', + }, + { + fieldName: 'receiverDetailAddress', + label: '详细地址', + component: 'Input', + componentProps: { + placeholder: '请输入收件人详细地址', + type: 'textarea', + rows: 3, + }, + rules: 'required', + }, + ]; +} + +/** 订单发货表单配置 */ +export function useDeliveryFormSchema(): VbenFormSchema[] { + return [ + { + 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', + }, + defaultValue: 'express', + }, + { + fieldName: 'logisticsId', + label: '物流公司', + component: 'ApiSelect', + componentProps: { + api: getSimpleDeliveryExpressList, + fieldNames: { + label: 'name', + value: 'id', + }, + placeholder: '请选择物流公司', + }, + dependencies: { + triggerFields: ['expressType'], + show: (values) => values.expressType === 'express', + }, + rules: 'required', + }, + { + fieldName: 'logisticsNo', + label: '物流单号', + component: 'Input', + componentProps: { + placeholder: '请输入物流单号', + }, + dependencies: { + triggerFields: ['expressType'], + show: (values) => values.expressType === 'express', + }, + rules: 'required', + }, + ]; +} diff --git a/apps/web-ele/src/views/mall/trade/order/detail/data.ts b/apps/web-ele/src/views/mall/trade/order/detail/data.ts new file mode 100644 index 000000000..6d3acf0a0 --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/order/detail/data.ts @@ -0,0 +1,277 @@ +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { MallOrderApi } from '#/api/mall/trade/order'; + +import { h } from 'vue'; + +import { DICT_TYPE } from '@vben/constants'; +import { fenToYuan, formatDateTime } from '@vben/utils'; + +import { DictTag } from '#/components/dict-tag'; + +/** 订单基础信息 schema */ +export function useOrderInfoSchema() { + return [ + { + field: 'no', + label: '订单号', + }, + { + field: 'user.nickname', + label: '买家', + }, + { + field: 'type', + label: '订单类型', + render: (data: MallOrderApi.Order) => + h(DictTag, { + type: DICT_TYPE.TRADE_ORDER_TYPE, + value: data?.type, + }), + }, + { + field: 'terminal', + label: '订单来源', + render: (data: MallOrderApi.Order) => + h(DictTag, { + type: DICT_TYPE.TERMINAL, + value: data?.terminal, + }), + }, + { + field: 'userRemark', + label: '买家留言', + }, + { + field: 'remark', + label: '商家备注', + }, + { + field: 'payOrderId', + label: '支付单号', + }, + { + field: 'payChannelCode', + label: '付款方式', + render: (data: MallOrderApi.Order) => + h(DictTag, { + type: DICT_TYPE.PAY_CHANNEL_CODE, + value: data?.payChannelCode, + }), + }, + { + field: 'brokerageUser.nickname', + label: '推广用户', + }, + ]; +} + +/** 订单状态信息 schema */ +export function useOrderStatusSchema() { + return [ + { + field: 'status', + label: '订单状态', + render: (data: MallOrderApi.Order) => + h(DictTag, { + type: DICT_TYPE.TRADE_ORDER_STATUS, + value: data?.status, + }), + }, + { + field: 'reminder', + label: '提醒', + render: () => + h('div', { class: 'space-y-1' }, [ + h('div', '买家付款成功后,货款将直接进入您的商户号(微信、支付宝)'), + h('div', '请及时关注你发出的包裹状态,确保可以配送至买家手中'), + h( + 'div', + '如果买家表示没收到货或货物有问题,请及时联系买家处理,友好协商', + ), + ]), + }, + ]; +} + +/** 订单金额信息 schema */ +export function useOrderPriceSchema() { + return [ + { + field: 'totalPrice', + label: '商品总额', + render: (data: MallOrderApi.Order) => + `${fenToYuan(data?.totalPrice ?? 0)} 元`, + }, + { + field: 'deliveryPrice', + label: '运费金额', + render: (data: MallOrderApi.Order) => + `${fenToYuan(data?.deliveryPrice ?? 0)} 元`, + }, + { + field: 'adjustPrice', + label: '订单调价', + render: (data: MallOrderApi.Order) => + `${fenToYuan(data?.adjustPrice ?? 0)} 元`, + }, + { + field: 'couponPrice', + label: '优惠劵优惠', + render: (data: MallOrderApi.Order) => + h( + 'span', + { class: 'text-red-500' }, + `${fenToYuan(data?.couponPrice ?? 0)} 元`, + ), + }, + { + field: 'vipPrice', + label: 'VIP 优惠', + render: (data: MallOrderApi.Order) => + h( + 'span', + { class: 'text-red-500' }, + `${fenToYuan(data?.vipPrice ?? 0)} 元`, + ), + }, + { + field: 'discountPrice', + label: '活动优惠', + render: (data: MallOrderApi.Order) => + h( + 'span', + { class: 'text-red-500' }, + `${fenToYuan(data?.discountPrice ?? 0)} 元`, + ), + }, + { + field: 'pointPrice', + label: '积分抵扣', + render: (data: MallOrderApi.Order) => + h( + 'span', + { class: 'text-red-500' }, + `${fenToYuan(data?.pointPrice ?? 0)} 元`, + ), + }, + { + field: 'payPrice', + label: '应付金额', + render: (data: MallOrderApi.Order) => + `${fenToYuan(data?.payPrice ?? 0)} 元`, + }, + ]; +} + +/** 收货信息 schema */ +export function useDeliveryInfoSchema() { + return [ + { + field: 'deliveryType', + label: '配送方式', + render: (data: MallOrderApi.Order) => + h(DictTag, { + type: DICT_TYPE.TRADE_DELIVERY_TYPE, + value: data?.deliveryType, + }), + }, + { + field: 'receiverName', + label: '收货人', + }, + { + field: 'receiverMobile', + label: '联系电话', + }, + { + field: 'receiverAddress', + label: '收货地址', + render: (data: MallOrderApi.Order) => + `${data?.receiverAreaName} ${data?.receiverDetailAddress}`.trim(), + }, + { + field: 'deliveryTime', + label: '发货时间', + render: (data: MallOrderApi.Order) => + formatDateTime(data?.deliveryTime || '') as string, + }, + ]; +} + +/** 商品信息 columns */ +export function useProductColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'spuName', + title: '商品', + minWidth: 300, + slots: { default: 'spuName' }, + }, + { + field: 'price', + title: '商品原价', + width: 150, + formatter: 'formatFenToYuanAmount', + }, + { + field: 'count', + title: '数量', + width: 100, + }, + { + field: 'payPrice', + title: '合计', + width: 150, + formatter: 'formatFenToYuanAmount', + }, + { + field: 'afterSaleStatus', + title: '售后状态', + width: 120, + cellRender: { + name: 'CellDict', + props: { type: DICT_TYPE.TRADE_ORDER_ITEM_AFTER_SALE_STATUS }, + }, + }, + ]; +} + +/** 物流详情 columns */ +export function useExpressTrackColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'time', + title: '时间', + width: 180, + formatter: 'formatDateTime', + }, + { + field: 'content', + title: '物流状态', + minWidth: 300, + }, + ]; +} + +/** 操作日志 columns */ +export function useOperateLogColumns(): VxeTableGridOptions['columns'] { + return [ + { + field: 'createTime', + title: '操作时间', + width: 180, + formatter: 'formatDateTime', + }, + { + field: 'userType', + title: '操作人', + width: 100, + slots: { default: 'userType' }, + }, + { + field: 'content', + title: '操作内容', + minWidth: 200, + }, + ]; +} \ No newline at end of file diff --git a/apps/web-ele/src/views/mall/trade/order/detail/index.vue b/apps/web-ele/src/views/mall/trade/order/detail/index.vue new file mode 100644 index 000000000..b3e90bb3d --- /dev/null +++ b/apps/web-ele/src/views/mall/trade/order/detail/index.vue @@ -0,0 +1,456 @@ + + + diff --git a/apps/web-ele/src/views/mall/trade/order/index.vue b/apps/web-ele/src/views/mall/trade/order/index.vue index 22074910f..a13dae0ba 100644 --- a/apps/web-ele/src/views/mall/trade/order/index.vue +++ b/apps/web-ele/src/views/mall/trade/order/index.vue @@ -2,10 +2,9 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { MallOrderApi } from '#/api/mall/trade/order'; -import { h } from 'vue'; import { useRouter } from 'vue-router'; -import { DocAlert, Page, prompt, useVbenModal } from '@vben/common-ui'; +import { DocAlert, Page, useVbenModal } from '@vben/common-ui'; import { DeliveryTypeEnum, DICT_TYPE, @@ -13,26 +12,34 @@ import { } from '@vben/constants'; import { fenToYuan } from '@vben/utils'; -import { ElImage, ElInput, ElTag } from 'element-plus'; +import { ElImage, ElTag } from 'element-plus'; import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; -import { getOrderPage, updateOrderRemark } from '#/api/mall/trade/order'; +import { getOrderPage } from '#/api/mall/trade/order'; import { DictTag } from '#/components/dict-tag'; import { $t } from '#/locales'; import { useGridColumns, useGridFormSchema } from './data'; import DeleveryForm from './modules/delevery-form.vue'; +import RemarkForm from './modules/update-remark-form.vue'; + +const { push } = useRouter(); const [DeleveryFormModal, deleveryFormModalApi] = useVbenModal({ connectedComponent: DeleveryForm, destroyOnClose: true, }); +const [RemarkFormModal, remarkFormModalApi] = useVbenModal({ + connectedComponent: RemarkForm, + destroyOnClose: true, +}); + /** 刷新表格 */ -function onRefresh() { +function handleRefresh() { gridApi.query(); } -const { push } = useRouter(); + /** 详情 */ function handleDetail(row: MallOrderApi.Order) { push({ name: 'TradeOrderDetail', params: { id: row.id } }); @@ -44,27 +51,8 @@ function handleDelivery(row: MallOrderApi.Order) { } /** 备注 */ -function handleRemake(row: MallOrderApi.Order) { - prompt({ - component: () => { - return h(ElInput, { - defaultValue: row.remark, - rows: 3, - type: 'textarea', - }); - }, - content: '请输入订单备注', - title: '订单备注', - modelPropName: 'value', - }).then(async (val) => { - if (val) { - await updateOrderRemark({ - id: row.id as number, - remark: val, - }); - onRefresh(); - } - }); +function handleRemark(row: MallOrderApi.Order) { + remarkFormModalApi.setData(row).open(); } const [Grid, gridApi] = useVbenVxeGrid({ @@ -93,6 +81,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, rowConfig: { keyField: 'id', + isHover: true, }, toolbarConfig: { refresh: true, @@ -114,9 +103,10 @@ const [Grid, gridApi] = useVbenVxeGrid({ url="https://doc.iocoder.cn/mall/trade-cart/" /> - + + + -