Bläddra i källkod

```
feat(delivery): 新增发货管理功能及订单详情优化

- 添加了快递公司列表与发货相关接口
- 实现订单发货弹窗组件 `delivery-modal.vue`
- 更新订单详情弹窗,支持发货操作和订单编号复制
- 表单新增 `NGradientText` 组件支持
- 路由新增配送管理页面 `operation_delivery-admin`
- 工具函数中增加文本复制到剪贴板方法 `copyTextToClipboard`
- 修复 `.env.test` 中测试服务地址配置问题
- 移除了首页模块无关注释代码,优化移动端显示样式
```

zhangtao 6 dagar sedan
förälder
incheckning
882d1704af

+ 2 - 2
.env.test

@@ -1,7 +1,7 @@
 # backend service base url, test environment
 # VITE_SERVICE_BASE_URL=http://74949mkfh190.vicp.fun
-# VITE_SERVICE_BASE_URL=http://192.168.1.206:8114 #付
-VITE_SERVICE_BASE_URL=http://192.168.0.157:8114 #王
+VITE_SERVICE_BASE_URL=http://192.168.1.206:8114 #付
+# VITE_SERVICE_BASE_URL=http://192.168.0.157:8114 #王
 # VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default
 # VITE_SERVICE_BASE_URL=https://shop.platform.zswlgz.com #服务器
 

+ 0 - 1
src/components/zt/ApiSelect/api-select.vue

@@ -49,7 +49,6 @@ async function fetchApi() {
     params[unref(bindValue).pageSizeFeild] = 10;
   }
   const res = await api(params);
-
   options.value = unref(bindValue).pagination ? [...options.value, ...res.data.records] : get(res, props.resultFeild);
   fetchLoading.value = false;
   if (unref(bindValue).pagination) {

+ 6 - 2
src/components/zt/Form/types/form.ts

@@ -9,6 +9,7 @@ import {
   type DynamicInputProps,
   type DynamicTagsProps,
   type FormItemRule,
+  type GradientTextProps,
   type InputNumberProps,
   type InputProps,
   type MentionProps,
@@ -32,6 +33,7 @@ import {
   NDatePicker,
   NDynamicInput,
   NDynamicTags,
+  NGradientText,
   NInput,
   NInputNumber,
   NMention,
@@ -130,7 +132,8 @@ export type FormSchema =
   | FormSchemaWithType<'ApiSelect', ApiSelectProps>
   | FormSchemaWithType<'ApiTreeSelect', ApiTreeSelectProps>
   | FormSchemaWithType<'zUpload', zuploadProps>
-  | FormSchemaWithType<'NDynamicInput', DynamicInputProps>;
+  | FormSchemaWithType<'NDynamicInput', DynamicInputProps>
+  | FormSchemaWithType<'NGradientText', GradientTextProps>;
 
 export interface RenderCallbackParams {
   schema: FormSchema;
@@ -213,7 +216,8 @@ export const componentMap = {
   ApiSelect,
   ApiTreeSelect,
   zUpload,
-  NDynamicInput
+  NDynamicInput,
+  NGradientText
 } as const;
 
 export type ComponentMap = typeof componentMap;

+ 2 - 1
src/locales/langs/en-us.ts

@@ -301,7 +301,8 @@ const local: App.I18n.Schema = {
     'config_fright-config': '',
     'config_order-splitting': '',
     delivery: '',
-    'delivery_normal-order': ''
+    'delivery_normal-order': '',
+    'operation_delivery-admin': ''
   },
   page: {
     login: {

+ 2 - 1
src/locales/langs/zh-cn.ts

@@ -298,7 +298,8 @@ const local: App.I18n.Schema = {
     'config_fright-config': '',
     'config_order-splitting': '',
     delivery: '',
-    'delivery_normal-order': ''
+    'delivery_normal-order': '',
+    'operation_delivery-admin': ''
   },
   page: {
     login: {

+ 1 - 0
src/router/elegant/imports.ts

@@ -39,6 +39,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
   manage_schedule: () => import("@/views/manage/schedule/index.vue"),
   manage_user: () => import("@/views/manage/user/index.vue"),
   operation_advertisement: () => import("@/views/operation/advertisement/index.vue"),
+  "operation_delivery-admin": () => import("@/views/operation/delivery-admin/index.vue"),
   operation_search: () => import("@/views/operation/search/index.vue"),
   plugin_barcode: () => import("@/views/plugin/barcode/index.vue"),
   plugin_charts_antv: () => import("@/views/plugin/charts/antv/index.vue"),

+ 9 - 0
src/router/elegant/routes.ts

@@ -316,6 +316,15 @@ export const generatedRoutes: GeneratedRoute[] = [
           i18nKey: 'route.operation_advertisement'
         }
       },
+      {
+        name: 'operation_delivery-admin',
+        path: '/operation/delivery-admin',
+        component: 'view.operation_delivery-admin',
+        meta: {
+          title: 'operation_delivery-admin',
+          i18nKey: 'route.operation_delivery-admin'
+        }
+      },
       {
         name: 'operation_search',
         path: '/operation/search',

+ 1 - 0
src/router/elegant/transform.ts

@@ -208,6 +208,7 @@ const routeMap: RouteMap = {
   "manage_user": "/manage/user",
   "operation": "/operation",
   "operation_advertisement": "/operation/advertisement",
+  "operation_delivery-admin": "/operation/delivery-admin",
   "operation_search": "/operation/search",
   "plugin": "/plugin",
   "plugin_barcode": "/plugin/barcode",

+ 23 - 0
src/service/api/delivery/normal-orde/index.ts

@@ -35,3 +35,26 @@ export function fetchGetNomalOrderInfo(orderNumber: string) {
     method: 'get'
   });
 }
+
+/**
+ *
+ * 快递公司列表
+ *  */
+export function fetchGetDevList(data: any) {
+  return request<Api.delivery.devList>({
+    url: '/platform/delivery/list',
+    method: 'get',
+    params: data
+  });
+}
+
+/**
+ * 发货
+ */
+export function fetchDeliveryOrder(data: any) {
+  return request({
+    url: '/platform/order/delivery',
+    method: 'PUT',
+    data
+  });
+}

+ 52 - 0
src/service/api/operation/delivery-admin/index.ts

@@ -0,0 +1,52 @@
+import { request } from '@/service/request';
+
+/**
+ * 分页获取物流公司
+ * @param params
+ * @returns
+ */
+export function fetchGetDelivery(params: any) {
+  return request<Api.delivery.devList>({
+    url: '/platform/delivery/page',
+    method: 'get',
+    params
+  });
+}
+/**
+ * 新增物流公司
+ * @param params
+ * @returns
+ */
+
+export function fetchAddDelivery(params: any) {
+  return request({
+    url: '/platform/delivery',
+    method: 'post',
+    data: params
+  });
+}
+
+/**
+ * 删除物流公司
+
+*/
+export function fetchDeleteDelivery(dvyId: number) {
+  return request({
+    url: `/platform/delivery/${dvyId}`,
+    method: 'delete'
+  });
+}
+
+/**
+ * 修改物流公司
+ * @param params
+ * @returns
+
+ */
+export function fetchUpdateDelivery(params: any) {
+  return request({
+    url: '/platform/delivery',
+    method: 'put',
+    data: params
+  });
+}

+ 31 - 0
src/typings/api.d.ts

@@ -881,6 +881,37 @@ declare namespace Api {
     }
   }
   namespace delivery {
+    interface devList {
+      /**
+       * 公司主页
+       */
+      companyHomeUrl?: string;
+      /**
+       * ID
+       */
+      dvyId?: number;
+      /**
+       * 物流公司名称
+       */
+      dvyName?: string;
+      /**
+       * 物流公司编号
+       */
+      dvyNo?: string;
+      /**
+       * 修改时间
+       */
+      modifyTime?: string;
+      /**
+       * 物流查询接口
+       */
+      queryUrl?: string;
+      /**
+       * 建立时间
+       */
+      recTime?: string;
+      [property: string]: any;
+    }
     interface deliveryOrder {
       /**
        * 实际总值

+ 1 - 0
src/typings/components.d.ts

@@ -84,6 +84,7 @@ declare module 'vue' {
     NGi: typeof import('naive-ui')['NGi']
     NGrid: typeof import('naive-ui')['NGrid']
     NIcon: typeof import('naive-ui')['NIcon']
+    NImage: typeof import('naive-ui')['NImage']
     NInput: typeof import('naive-ui')['NInput']
     NInputGroup: typeof import('naive-ui')['NInputGroup']
     NInputNumber: typeof import('naive-ui')['NInputNumber']

+ 2 - 0
src/typings/elegant-router.d.ts

@@ -62,6 +62,7 @@ declare module "@elegant-router/types" {
     "manage_user": "/manage/user";
     "operation": "/operation";
     "operation_advertisement": "/operation/advertisement";
+    "operation_delivery-admin": "/operation/delivery-admin";
     "operation_search": "/operation/search";
     "plugin": "/plugin";
     "plugin_barcode": "/plugin/barcode";
@@ -206,6 +207,7 @@ declare module "@elegant-router/types" {
     | "manage_schedule"
     | "manage_user"
     | "operation_advertisement"
+    | "operation_delivery-admin"
     | "operation_search"
     | "plugin_barcode"
     | "plugin_charts_antv"

+ 37 - 0
src/utils/zt/index.ts

@@ -299,3 +299,40 @@ function areAllFieldsFilled(obj: Record<string, any>): boolean {
 export function areAllItemsAllFieldsFilled(arr: Array<Record<string, any>>): boolean {
   return arr.every(item => areAllFieldsFilled(item));
 }
+
+/**
+ * 复制文本到剪贴板
+ * @param text 需要复制的文本内容
+ * @returns Promise<boolean> 复制成功返回true,失败返回false
+ */
+export async function copyTextToClipboard(text: string): Promise<boolean> {
+  try {
+    // 使用现代 Clipboard API(如果支持)
+    if (navigator.clipboard && window.isSecureContext) {
+      await navigator.clipboard.writeText(text);
+      window.$message?.success(`复制成功:${text}`);
+    }
+    // 降级方案:使用传统的 document.execCommand('copy')
+    const textArea = document.createElement('textarea');
+    textArea.value = text;
+
+    // 避免滚动到底部
+    textArea.style.top = '0';
+    textArea.style.left = '0';
+    textArea.style.position = 'fixed';
+    textArea.style.opacity = '0';
+
+    document.body.appendChild(textArea);
+    textArea.focus();
+    textArea.select();
+
+    const successful = document.execCommand('copy');
+    document.body.removeChild(textArea);
+
+    return successful;
+  } catch (error: any) {
+    window.$message?.error(error.message);
+    console.error('复制文本失败:', error);
+    return false;
+  }
+}

+ 129 - 0
src/views/delivery/normal-order/component/delivery-modal.vue

@@ -0,0 +1,129 @@
+<script setup lang="tsx">
+import { nextTick, ref } from 'vue';
+import { fetchDeliveryOrder } from '@/service/api/delivery/normal-orde';
+import { useModal } from '@/components/zt/Modal/hooks/useModal';
+import { useForm } from '@/components/zt/Form/hooks/useForm';
+import { deliveryInfo } from '../normal-order';
+const [registerModal, { openModal, setModalProps, setSubLoading, closeModal }] = useModal({
+  title: '订单发货',
+  width: 1200,
+  height: 800,
+  isShowHeaderText: false
+});
+const [registerForm, { validate, getFieldsValue, setFieldsValue }] = useForm({
+  schemas: deliveryInfo,
+  labelWidth: 120,
+  layout: 'horizontal',
+  gridProps: {
+    cols: '1',
+    itemResponsive: true
+  },
+  collapsedRows: 1,
+  showActionButtonGroup: false
+});
+const emit = defineEmits<{
+  (e: 'finish'): void;
+}>();
+
+const orderInfo = ref<Api.delivery.deliveryOrder>();
+const ShipmentColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[] = [
+  {
+    title: '商品',
+    key: 'goodsName',
+    width: 300,
+    render: row => {
+      return (
+        <div class={'flex items-center'}>
+          <n-image src={row.pic} width={80} height={80}></n-image>
+          <div class={'ml2'}>
+            <div class={'text-15px font-semibold'}> {row.skuName} </div>
+            <div class={'text-gray'}>规格: {'---'}</div>
+          </div>
+        </div>
+      );
+    }
+  },
+  {
+    title: '可发货数量',
+    key: 'num',
+    width: 100,
+    render: row => {
+      const count = Number(row.prodCount) - row.refundSuccessCount;
+      return (
+        <div class={'flex items-center justify-center'}>
+          <div>
+            {count}{' '}
+            {row.refundSuccessCount ? <n-tag type="success">已扣除退款成功:{row.refundSuccessCount}</n-tag> : ''}
+          </div>
+        </div>
+      );
+    }
+  },
+  {
+    title: '状态',
+    key: 'status',
+    align: 'center',
+    render: _row => {
+      return orderInfo.value?.hbOrderStatus == 1 ? <n-tag>等待发货</n-tag> : <n-tag type="success">已发货</n-tag>;
+    }
+  },
+  {
+    title: '发货数量',
+    key: 'count',
+    align: 'center',
+    render: row => {
+      const count = Number(row.prodCount) - row.refundSuccessCount;
+      return count;
+    }
+  }
+];
+function handleOpenMoadl(data: Api.delivery.deliveryOrder) {
+  orderInfo.value = data;
+  const Info = {
+    ...data.userAddrOrder
+  };
+  openModal();
+
+  nextTick(() => {
+    setFieldsValue(Info);
+  });
+}
+defineExpose({
+  handleOpenMoadl,
+  setModalProps
+});
+async function handleSubmit() {
+  setSubLoading(false);
+  await validate();
+  setSubLoading(true);
+  const form: Recordable = {
+    ...getFieldsValue(),
+    orderNumber: orderInfo.value?.orderNumber
+  };
+  delete form.receiver;
+  delete form.mobile;
+  delete form.address;
+  const { error } = await fetchDeliveryOrder(form);
+  if (!error) {
+    emit('finish');
+    closeModal();
+  } else {
+    setSubLoading(false);
+  }
+}
+</script>
+
+<template>
+  <BasicModal @register="registerModal" @ok="handleSubmit">
+    <div v-if="orderInfo">
+      <NCard :bordered="false" title="商品信息">
+        <NDataTable :scroll-x="800" :columns="ShipmentColumns" :data="orderInfo.orderItems" :bordered="false" />
+      </NCard>
+      <NCard :bordered="false" title="订单信息">
+        <BasicForm @register-form="registerForm"></BasicForm>
+      </NCard>
+    </div>
+  </BasicModal>
+</template>
+
+<style scoped></style>

+ 0 - 187
src/views/delivery/normal-order/component/normal-moadl.vue

@@ -1,187 +0,0 @@
-<script setup lang="ts">
-import { computed, ref } from 'vue';
-import dayjs from 'dayjs';
-import { fetchGetNomalOrderInfo } from '@/service/api/delivery/normal-orde';
-import { useAppStore } from '@/store/modules/app';
-import { useModal } from '@/components/zt/Modal/hooks/useModal';
-import { closeStatus, dvyStatus, orderColumns, orderStatusEnum } from '../normal-order';
-const [registerModal, { openModal, setModalLoading }] = useModal({
-  title: '订单详情',
-  isShowHeaderText: false,
-  showFooter: false,
-  width: 1200,
-  height: 800
-});
-const orderInfo = ref<Api.delivery.deliveryOrder>();
-const appStore = useAppStore();
-const TimeDown = ref<number>(0);
-const emit = defineEmits<{
-  (e: 'finish'): void;
-}>();
-async function open(orderNumber: string) {
-  openModal();
-  setModalLoading(true);
-  const { data, error } = await fetchGetNomalOrderInfo(orderNumber);
-  if (!error) {
-    orderInfo.value = data;
-    if (orderInfo.value.hbLogisticStatus == orderStatusEnum.WAIT_PAY) {
-      const createTime = dayjs(orderInfo.value.createTime);
-      const currentTime = dayjs();
-      const elapsed = currentTime.diff(createTime);
-      const fifteenMinutesInMillis = 15 * 60 * 1000;
-      TimeDown.value = fifteenMinutesInMillis - elapsed;
-    }
-  }
-  setModalLoading(false);
-}
-defineExpose({ open });
-function handleFinish() {
-  open(String(orderInfo.value?.orderNumber));
-  emit('finish');
-}
-
-const currentSteps = computed(() => {
-  switch (orderInfo.value?.hbOrderStatus) {
-    case orderStatusEnum.WAIT_PAY:
-      return 1;
-    case orderStatusEnum.ORDER_ACCEPT:
-      return 2;
-    case orderStatusEnum.ORDER_WAIT_DELIVERY:
-      return 2;
-    case orderStatusEnum.WAIT_DELIVERY:
-      return 2;
-    case orderStatusEnum.ORDER_ARRIVE:
-      return 3;
-    case orderStatusEnum.ORDER_COMPLETE:
-      return 4;
-
-    default:
-      return 1;
-  }
-});
-</script>
-
-<template>
-  <BasicModal @register="registerModal">
-    <div v-if="orderInfo">
-      <NFlex justify="space-between" align="center">
-        <NFlex>
-          <NTag>订单编号: {{ orderInfo?.orderNumber }}</NTag>
-          <NTag>下单时间: {{ orderInfo?.createTime }}</NTag>
-        </NFlex>
-        <NFlex vertical>
-          <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.WAIT_PAY">
-            <div class="text-16px font-semibold">商品已拍下,等待买家付款</div>
-            <div class="text-gray">
-              如买家未在
-              <NTag :type="TimeDown > 300094 ? 'success' : 'error'">
-                <NCountdown :duration="TimeDown" @finish="handleFinish" />
-              </NTag>
-              内付款,订单将 自动关闭。
-            </div>
-          </template>
-          <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.WAIT_DELIVERY">
-            <div class="text-16px font-semibold">等待商家发货</div>
-            <NButton>发货</NButton>
-          </template>
-          <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.ORDER_ARRIVE">
-            <div class="text-16px font-semibold">等待买家收货</div>
-            <div>商家已发货,等待确认收货</div>
-          </template>
-          <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.ORDER_CANCEL">
-            <div class="text-16px font-semibold">已取消</div>
-            <div>取消原因:{{ closeStatus[orderInfo.closeType as keyof typeof closeStatus] || '未知取消原因' }}</div>
-          </template>
-          <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.ORDER_COMPLETE">
-            <div class="text-16px font-semibold">交易成功</div>
-            <div>买家已收货</div>
-          </template>
-        </NFlex>
-      </NFlex>
-      <NDivider />
-      <template v-if="orderInfo.hbOrderStatus != orderStatusEnum.ORDER_CANCEL">
-        <NScrollbar x-scrollable>
-          <div class="p3">
-            <NSteps :current="currentSteps">
-              <NStep title="用户下单" :description="orderInfo.createTime" />
-              <NStep title="买家已付款" :description="orderInfo.payTime" />
-              <NStep title="卖家已发货" :description="orderInfo.dvyTime" />
-              <NStep title="买家已收货" :description="orderInfo.finallyTime" />
-            </NSteps>
-          </div>
-        </NScrollbar>
-        <NDivider />
-      </template>
-      <NDescriptions bordered :column="appStore.isMobile ? 1 : 4">
-        <NDescriptionsItem label="收货人信息">
-          <div>收货人姓名:{{ orderInfo?.userAddrOrder?.receiver || '---' }}</div>
-          <div>收货人手机号:{{ orderInfo?.userAddrOrder?.mobile || '---' }}</div>
-          <div>收货地址:{{ orderInfo?.userAddrOrder?.address || '---' }}</div>
-        </NDescriptionsItem>
-        <NDescriptionsItem label="配送信息">
-          <div>配送方式: {{ dvyStatus[orderInfo.dvyType as keyof typeof dvyStatus] || '暂无' }}</div>
-        </NDescriptionsItem>
-        <NDescriptionsItem label="付款信息">
-          <div>实付金额:{{ orderInfo.actualTotal }}元</div>
-          <div>
-            付款方式:{{
-              orderInfo.hbOrderStatus == orderStatusEnum.WAIT_PAY ||
-              orderInfo.hbOrderStatus == orderStatusEnum.ORDER_CANCEL
-                ? '暂无'
-                : '微信'
-            }}
-          </div>
-          <div>付款时间:{{ orderInfo.payTime || '暂无' }}</div>
-        </NDescriptionsItem>
-        <NDescriptionsItem label="买家信息">
-          <div>买家昵称:{{ orderInfo?.userInfo?.nickName || '---' }}</div>
-          <div>买家电话:{{ orderInfo?.userInfo?.mobile || '---' }}</div>
-          <div>买家留言:{{ orderInfo?.userInfo?.message || '暂无' }}</div>
-        </NDescriptionsItem>
-      </NDescriptions>
-      <NDivider />
-      <NCard title="订单信息" :bordered="false">
-        <NDataTable :columns="orderColumns" :data="orderInfo.orderItems" :bordered="false" />
-      </NCard>
-      <NCard title="费用信息" :bordered="false">
-        <NTable :single-line="false">
-          <NThead>
-            <NTr>
-              <NTh>费用类型</NTh>
-              <NTh>金额/元</NTh>
-            </NTr>
-          </NThead>
-          <NTbody>
-            <NTr>
-              <NTd>商品总额</NTd>
-              <NTd>{{ orderInfo.actualTotal }}</NTd>
-            </NTr>
-            <NTr>
-              <NTd>配送费(快递)</NTd>
-              <NTd>{{ orderInfo.freightAmount }}</NTd>
-            </NTr>
-            <NTr>
-              <NTd>积分</NTd>
-              <NTd>{{ orderInfo.offsetPoints }}</NTd>
-            </NTr>
-            <NTr>
-              <NTd v-if="orderInfo.hbOrderStatus == orderStatusEnum.WAIT_PAY">需付款</NTd>
-              <NTd
-                v-if="
-                  orderInfo.hbOrderStatus != orderStatusEnum.WAIT_PAY &&
-                  orderInfo.hbOrderStatus != orderStatusEnum.ORDER_CANCEL
-                "
-              >
-                实际付款
-              </NTd>
-              <NTd v-if="orderInfo.hbOrderStatus == orderStatusEnum.ORDER_CANCEL">应付款</NTd>
-              <NTd>{{ orderInfo.actualTotal }}</NTd>
-            </NTr>
-          </NTbody>
-        </NTable>
-      </NCard>
-    </div>
-  </BasicModal>
-</template>
-
-<style scoped></style>

+ 205 - 0
src/views/delivery/normal-order/component/normal-modal.vue

@@ -0,0 +1,205 @@
+<script setup lang="ts">
+import { computed, ref, useTemplateRef } from 'vue';
+import dayjs from 'dayjs';
+import { fetchGetNomalOrderInfo } from '@/service/api/delivery/normal-orde';
+import { useAppStore } from '@/store/modules/app';
+import { copyTextToClipboard } from '@/utils/zt';
+import { useModal } from '@/components/zt/Modal/hooks/useModal';
+import { closeStatus, dvyStatus, orderColumns, orderStatusEnum } from '../normal-order';
+import DeliveryModal from './delivery-modal.vue';
+const [registerModal, { openModal, setModalLoading }] = useModal({
+  title: '订单详情',
+  isShowHeaderText: false,
+  showFooter: false,
+  width: 1200,
+  height: 800
+});
+const orderInfo = ref<Api.delivery.deliveryOrder>();
+const appStore = useAppStore();
+const TimeDown = ref<number>(0);
+const ShipmentRef = useTemplateRef('Shipment');
+const emit = defineEmits<{
+  (e: 'finish'): void;
+}>();
+async function open(orderNumber: string) {
+  openModal();
+  setModalLoading(true);
+  const { data, error } = await fetchGetNomalOrderInfo(orderNumber);
+  if (!error) {
+    orderInfo.value = data;
+    if (orderInfo.value.hbLogisticStatus == orderStatusEnum.WAIT_PAY) {
+      const createTime = dayjs(orderInfo.value.createTime);
+      const currentTime = dayjs();
+      const elapsed = currentTime.diff(createTime);
+      const fifteenMinutesInMillis = 15 * 60 * 1000;
+      TimeDown.value = fifteenMinutesInMillis - elapsed;
+    }
+  }
+  setModalLoading(false);
+}
+defineExpose({ open });
+function handleFinish() {
+  open(String(orderInfo.value?.orderNumber));
+  emit('finish');
+}
+function handleShipment() {
+  ShipmentRef.value?.handleOpenMoadl(orderInfo.value as Api.delivery.deliveryOrder);
+}
+
+const currentSteps = computed(() => {
+  switch (orderInfo.value?.hbOrderStatus) {
+    case orderStatusEnum.WAIT_PAY:
+      return 1;
+    case orderStatusEnum.ORDER_ACCEPT:
+      return 2;
+    case orderStatusEnum.ORDER_WAIT_DELIVERY:
+      return 2;
+    case orderStatusEnum.WAIT_DELIVERY:
+      return 2;
+    case orderStatusEnum.ORDER_DELIVERY:
+      return 3;
+    case orderStatusEnum.ORDER_ARRIVE:
+      return 3;
+    case orderStatusEnum.ORDER_COMPLETE:
+      return 4;
+    default:
+      return 1;
+  }
+});
+function handleCopy() {
+  copyTextToClipboard(String(orderInfo.value?.orderNumber));
+}
+</script>
+
+<template>
+  <div>
+    <BasicModal @register="registerModal">
+      <div v-if="orderInfo">
+        <NFlex justify="space-between" align="center">
+          <NFlex>
+            <NTag>
+              <div class="flex items-center">
+                订单编号: {{ orderInfo?.orderNumber }}
+                <div @click="handleCopy">
+                  <SvgIcon icon="bxs:copy" class="mx-3 cursor-pointer text-[#f97316]"></SvgIcon>
+                </div>
+              </div>
+            </NTag>
+            <NTag>下单时间: {{ orderInfo?.createTime }}</NTag>
+          </NFlex>
+          <NFlex vertical>
+            <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.WAIT_PAY">
+              <div class="text-16px font-semibold">商品已拍下,等待买家付款</div>
+              <div class="text-gray">
+                如买家未在
+                <NTag :type="TimeDown > 300094 ? 'success' : 'error'">
+                  <NCountdown :duration="TimeDown" @finish="handleFinish" />
+                </NTag>
+                内付款,订单将 自动关闭。
+              </div>
+            </template>
+            <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.WAIT_DELIVERY">
+              <div class="text-16px font-semibold">等待商家发货</div>
+              <NButton size="small" type="primary" @click="handleShipment">发货</NButton>
+            </template>
+            <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.ORDER_ARRIVE">
+              <div class="text-16px font-semibold">等待买家收货</div>
+              <div>商家已发货,等待确认收货</div>
+            </template>
+            <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.ORDER_CANCEL">
+              <div class="text-16px font-semibold">已取消</div>
+              <div>取消原因:{{ closeStatus[orderInfo.closeType as keyof typeof closeStatus] || '未知取消原因' }}</div>
+            </template>
+            <template v-if="orderInfo.hbOrderStatus == orderStatusEnum.ORDER_COMPLETE">
+              <div class="text-16px font-semibold">交易成功</div>
+              <div>买家已收货</div>
+            </template>
+          </NFlex>
+        </NFlex>
+        <NDivider />
+        <template v-if="orderInfo.hbOrderStatus != orderStatusEnum.ORDER_CANCEL">
+          <div class="p3">
+            <NSteps :current="currentSteps" :vertical="appStore.isMobile">
+              <NStep title="用户下单" :description="orderInfo.createTime" />
+              <NStep title="买家已付款" :description="orderInfo.payTime" />
+              <NStep title="卖家已发货" :description="orderInfo.dvyTime" />
+              <NStep title="买家已收货" :description="orderInfo.finallyTime" />
+            </NSteps>
+          </div>
+          <NDivider />
+        </template>
+        <NDescriptions bordered :column="appStore.isMobile ? 1 : 4">
+          <NDescriptionsItem label="收货人信息">
+            <div>收货人姓名:{{ orderInfo?.userAddrOrder?.receiver || '---' }}</div>
+            <div>收货人手机号:{{ orderInfo?.userAddrOrder?.mobile || '---' }}</div>
+            <div>收货地址:{{ orderInfo?.userAddrOrder?.address || '---' }}</div>
+          </NDescriptionsItem>
+          <NDescriptionsItem label="配送信息">
+            <div>配送方式: {{ dvyStatus[orderInfo.dvyType as keyof typeof dvyStatus] || '暂无' }}</div>
+          </NDescriptionsItem>
+          <NDescriptionsItem label="付款信息">
+            <div>实付金额:{{ orderInfo.actualTotal }}元</div>
+            <div>
+              付款方式:{{
+                orderInfo.hbOrderStatus == orderStatusEnum.WAIT_PAY ||
+                orderInfo.hbOrderStatus == orderStatusEnum.ORDER_CANCEL
+                  ? '暂无'
+                  : '微信'
+              }}
+            </div>
+            <div>付款时间:{{ orderInfo.payTime || '暂无' }}</div>
+          </NDescriptionsItem>
+          <NDescriptionsItem label="买家信息">
+            <div>买家昵称:{{ orderInfo?.userInfo?.nickName || '---' }}</div>
+            <div>买家电话:{{ orderInfo?.userInfo?.mobile || '---' }}</div>
+            <div>买家留言:{{ orderInfo?.userInfo?.message || '暂无' }}</div>
+          </NDescriptionsItem>
+        </NDescriptions>
+        <NDivider />
+        <NCard title="订单信息" :bordered="false">
+          <NDataTable :columns="orderColumns" :data="orderInfo.orderItems" :bordered="false" />
+        </NCard>
+        <NCard title="费用信息" :bordered="false">
+          <NTable :single-line="false">
+            <NThead>
+              <NTr>
+                <NTh>费用类型</NTh>
+                <NTh>金额/元</NTh>
+              </NTr>
+            </NThead>
+            <NTbody>
+              <NTr>
+                <NTd>商品总额</NTd>
+                <NTd>{{ orderInfo.actualTotal }}</NTd>
+              </NTr>
+              <NTr>
+                <NTd>配送费(快递)</NTd>
+                <NTd>{{ orderInfo.freightAmount }}</NTd>
+              </NTr>
+              <NTr>
+                <NTd>积分</NTd>
+                <NTd>{{ orderInfo.offsetPoints }}</NTd>
+              </NTr>
+              <NTr>
+                <NTd v-if="orderInfo.hbOrderStatus == orderStatusEnum.WAIT_PAY">需付款</NTd>
+                <NTd
+                  v-if="
+                    orderInfo.hbOrderStatus != orderStatusEnum.WAIT_PAY &&
+                    orderInfo.hbOrderStatus != orderStatusEnum.ORDER_CANCEL
+                  "
+                >
+                  实际付款
+                </NTd>
+                <NTd v-if="orderInfo.hbOrderStatus == orderStatusEnum.ORDER_CANCEL">应付款</NTd>
+                <NTd>{{ orderInfo.actualTotal }}</NTd>
+              </NTr>
+            </NTbody>
+          </NTable>
+        </NCard>
+      </div>
+    </BasicModal>
+    <DeliveryModal ref="Shipment" @finish="emit('finish')"></DeliveryModal>
+  </div>
+</template>
+
+<style scoped></style>

+ 20 - 2
src/views/delivery/normal-order/index.vue

@@ -5,10 +5,11 @@ import { NImage, NTag } from 'naive-ui';
 import { fetchGetDeliveryOrderList, fetchGetDeliveryStatusNum } from '@/service/api/delivery/normal-orde';
 import { useAppStore } from '@/store/modules/app';
 import { defaultTransform, useNaivePaginatedTable } from '@/hooks/common/table';
+import { copyTextToClipboard } from '@/utils/zt';
 import { $t } from '@/locales';
 import { useForm } from '@/components/zt/Form/hooks/useForm';
 import { SearchForm, orderStatus, refundStatus } from './normal-order';
-import NormalMoadl from './component/normal-moadl.vue';
+import NormalMoadl from './component/normal-modal.vue';
 const appStore = useAppStore();
 const checkedRowKeys = ref([]);
 const activeTab = ref('all');
@@ -40,6 +41,9 @@ const searchParams = reactive({
 const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedTable({
   api: () => fetchGetDeliveryOrderList({ ...searchParams, orderStatus: activeTab.value, ...unref(searchForm) }),
   transform: response => defaultTransform(response),
+  paginationProps: {
+    pageSizes: [10, 20, 50, 100, 150, 200]
+  },
   onPaginationParamsChange: params => {
     searchParams.current = Number(params.page);
     searchParams.size = Number(params.pageSize);
@@ -56,7 +60,14 @@ const { columns, data, loading, getData, mobilePagination } = useNaivePaginatedT
           <div>
             <div class={'mb3 flex items-center'}>
               <n-tag>
-                订单编号:{row.orderNumber} 下单时间:{row.createTime} 门店名称 {row.shopName}
+                <div class={'flex items-center'}>
+                  订单编号:{row.orderNumber}
+                  <div onClick={() => handleCopy(row)}>
+                    <svgIcon icon={'bxs:copy'} class={'mx-3 cursor-pointer text-[#f97316]'}></svgIcon>
+                  </div>
+                  下单时间:
+                  {row.createTime} 门店名称 {row.shopName}
+                </div>
               </n-tag>
             </div>
             {row.orderItems?.map(item => {
@@ -237,6 +248,13 @@ function handleReset() {
   searchForm.value = getFieldsValue();
   getData();
 }
+async function handleCopy(row: Api.delivery.deliveryOrder) {
+  if (!row.orderNumber) {
+    window.$message?.error('订单编号不存在');
+    return;
+  }
+  await copyTextToClipboard(row.orderNumber);
+}
 </script>
 
 <template>

+ 79 - 2
src/views/delivery/normal-order/normal-order.ts

@@ -1,6 +1,7 @@
 import { h } from 'vue';
 import { NFlex, NImage, NTag } from 'naive-ui';
 import { fetchGetAllStoreList } from '@/service/api/goods/desk-category';
+import { fetchGetDevList } from '@/service/api/delivery/normal-orde';
 import type { FormSchema } from '@/components/zt/Form/types/form';
 
 export const SearchForm: FormSchema[] = [
@@ -153,7 +154,7 @@ export const orderColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[]
   {
     title: '商品',
     key: 'goods',
-    width: 200,
+    width: 300,
     render: row => {
       const goodsNodes = [
         h(NImage, { src: row.pic, width: 80, height: 80 }),
@@ -182,7 +183,7 @@ export const orderColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[]
       if (row.refundSuccessCount) {
         nodes.push(h(NTag, { class: 'ml2', type: 'success' }, `退款成功:${row.refundSuccessCount}`));
       }
-      return h(NFlex, { align: 'center' }, nodes);
+      return h(NFlex, { align: 'center' }, () => nodes);
     }
   },
   {
@@ -191,3 +192,79 @@ export const orderColumns: NaiveUI.TableColumn<Api.delivery.OrderItemElement>[]
     width: 100
   }
 ];
+
+export const deliveryInfo: FormSchema[] = [
+  {
+    label: '配送方式',
+    component: 'NGradientText',
+    field: 'dvyMethod',
+    render() {
+      return h('div', {}, '快递');
+    }
+  },
+  {
+    label: '收货人姓名',
+    component: 'NGradientText',
+    field: 'receiver',
+    render({ model, field }) {
+      return h('div', {}, model[field]);
+    }
+  },
+  {
+    label: '收货人手机号',
+    component: 'NInput',
+    field: 'mobile',
+    render({ model, field }) {
+      return h('div', {}, model[field]);
+    }
+  },
+  {
+    label: '收货地址',
+    component: 'NInput',
+    field: 'address',
+
+    render({ model, field }) {
+      return h('div', {}, model[field]);
+    }
+  },
+  {
+    label: '发货方式',
+    component: 'NRadioGroup',
+    field: 'dvyType',
+    componentProps: {
+      options: [
+        {
+          label: '快递',
+          value: 1
+        },
+        {
+          label: '自提',
+          value: 2
+        }
+      ]
+    },
+    defaultValue: 1,
+    required: true
+  },
+  {
+    label: '快递公司',
+    component: 'ApiSelect',
+    field: 'dvyId',
+    componentProps: {
+      api: fetchGetDevList,
+      params: {
+        current: 1,
+        size: 100
+      },
+      labelFeild: 'dvyName',
+      valueFeild: 'dvyId'
+    },
+    required: true
+  },
+  {
+    label: '快递单号',
+    component: 'NInput',
+    field: 'dvyFlowId',
+    required: true
+  }
+];

+ 10 - 44
src/views/home/index.vue

@@ -1,56 +1,18 @@
 <script setup lang="ts">
-// import { computed } from 'vue';
-// import { useAppStore } from '@/store/modules/app';
-// import HeaderBanner from './modules/header-banner.vue';
-// import CardData from './modules/card-data.vue';
-// import LineChart from './modules/line-chart.vue';
-// import PieChart from './modules/pie-chart.vue';
-// // import ProjectNews from './modules/project-news.vue';
-// // import CreativityBanner from './modules/creativity-banner.vue';
+import { useAppStore } from '@/store/modules/app';
 
-// const appStore = useAppStore();
-
-// const gap = computed(() => (appStore.isMobile ? 0 : 16));
+const appStore = useAppStore();
 </script>
 
 <template>
-  <!--
- <NSpace vertical :size="16">
-    <HeaderBanner />
-    <CardData />
-    <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
-      <NGi span="24 s:24 m:14">
-        <NCard :bordered="false" class="card-wrapper">
-          <LineChart />
-        </NCard>
-      </NGi>
-      <NGi span="24 s:24 m:10">
-        <NCard :bordered="false" class="card-wrapper">
-          <PieChart />
-        </NCard>
-      </NGi>
-    </NGrid>
--->
-  <!--
- <NGrid :x-gap="gap" :y-gap="16" responsive="screen" item-responsive>
-      <NGi span="24 s:24 m:14">
-        <ProjectNews />
-      </NGi>
-      <NGi span="24 s:24 m:10">
-        <CreativityBanner />
-      </NGi>
-    </NGrid>
--->
-
-  <!-- </NSpace> -->
   <div class="flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
     <NCard :bordered="false" class="flex-1 card-wrapper bg-white sm:flex-1-hidden" size="small">
       <div class="h-full w-full flex items-center justify-center">
-        <div class="flex items-center">
+        <div :class="[appStore.isMobile ? '' : 'flex items-center']">
           <img src="@/assets/imgs/index.png" alt="" class="h300px w300px" />
           <div class="ml3">
-            <div class="text-32px font-semibold">欢迎使用</div>
-            <div class="mt18px">使用过程,有任何建议和意见请及时反馈!</div>
+            <div class="text-32px font-semibold" :class="[appStore.isMobile ? 'text-center' : '']">欢迎使用</div>
+            <div class="mt18px">使用过程中,有任何建议和意见请及时反馈!</div>
           </div>
         </div>
       </div>
@@ -58,4 +20,8 @@
   </div>
 </template>
 
-<style scoped></style>
+<style scoped>
+.bgc {
+  background-image: url('@/assets/imgs/index.png');
+}
+</style>

+ 145 - 0
src/views/operation/delivery-admin/index.vue

@@ -0,0 +1,145 @@
+<script setup lang="tsx">
+import { NButton, NPopconfirm } from 'naive-ui';
+import {
+  fetchAddDelivery,
+  fetchDeleteDelivery,
+  fetchGetDelivery,
+  fetchUpdateDelivery
+} from '@/service/api/operation/delivery-admin';
+import { useTable } from '@/components/zt/Table/hooks/useTable';
+import { useModalFrom } from '@/components/zt/ModalForm/hooks/useModalForm';
+
+const columns: NaiveUI.TableColumn<Api.delivery.devList>[] = [
+  {
+    key: 'dvyName',
+    title: '物流公司名称',
+    align: 'center'
+  },
+  {
+    key: 'companyHomeUrl',
+    title: '公司主页',
+    align: 'center',
+    render(row) {
+      return (
+        <a href={row.companyHomeUrl} target="_blank" class={'underline'}>
+          {row.companyHomeUrl}
+        </a>
+      );
+    }
+  },
+  {
+    key: 'queryUrl',
+    title: '物流查询接口',
+    align: 'center'
+  },
+  {
+    key: 'recTime',
+    title: '创建时间',
+    align: 'center'
+  },
+  {
+    key: 'modifyTime',
+    title: '修改时间',
+    align: 'center'
+  },
+  {
+    key: 'dvyNo',
+    title: '物流公司编号',
+    align: 'center'
+  }
+];
+
+const [registerTable, { refresh, setTableLoading }] = useTable({
+  tableConfig: {
+    keyField: 'dvyId',
+    title: '物流公司列表',
+    showAddButton: true,
+    showSearch: false
+  }
+});
+
+async function handleDelete(row: Api.delivery.devList) {
+  setTableLoading(true);
+  await fetchDeleteDelivery(Number(row.dvyId));
+  refresh();
+}
+const [registerModalForm, { openModal, closeModal, getFieldsValue, setFieldsValue }] = useModalFrom({
+  modalConfig: {
+    title: '物流公司',
+    width: 700,
+    isShowHeaderText: true,
+    height: 500
+  },
+  formConfig: {
+    schemas: [
+      {
+        label: '',
+        field: 'dvyId',
+        component: 'NInput',
+        show: false
+      },
+      {
+        field: 'dvyName',
+        label: '物流公司',
+        component: 'NInput',
+        required: true
+      },
+      {
+        field: 'companyHomeUrl',
+        label: '公司主页',
+        component: 'NInput'
+      },
+      {
+        field: 'dvyNo',
+        label: '公司编号',
+        component: 'NInput',
+        required: true
+      },
+      {
+        field: 'queryUrl',
+        label: '物流查询接口',
+        component: 'NInput',
+        required: true
+      }
+    ],
+    gridProps: {
+      cols: '1'
+    },
+    labelWidth: 120
+  }
+});
+async function handleSubmit() {
+  const form = await getFieldsValue();
+  if (form.dvyId) {
+    await fetchUpdateDelivery(form);
+  } else {
+    await fetchAddDelivery(form);
+  }
+  closeModal();
+  refresh();
+}
+
+async function edit(row: Api.operation.HotSearch) {
+  openModal(row);
+  setFieldsValue(row);
+}
+</script>
+
+<template>
+  <LayoutTable>
+    <ZTable :columns="columns" :api="fetchGetDelivery" @register="registerTable" @add="openModal">
+      <template #op="{ row }">
+        <NButton size="small" ghost type="primary" @click="edit(row)">编辑</NButton>
+        <NPopconfirm @positive-click="handleDelete(row)">
+          <template #trigger>
+            <NButton size="small" type="error" ghost>删除</NButton>
+          </template>
+          确定删除吗?
+        </NPopconfirm>
+      </template>
+    </ZTable>
+    <BasicModelForm @register-modal-form="registerModalForm" @submit-form="handleSubmit"></BasicModelForm>
+  </LayoutTable>
+</template>
+
+<style scoped></style>