Ver código fonte

Merge remote-tracking branch 'origin/master' into master

肖路 2 semanas atrás
pai
commit
e40520520f

+ 75 - 0
src/api/order/orderStatusLog/index.ts

@@ -0,0 +1,75 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { OrderStatusLogVO, OrderStatusLogForm, OrderStatusLogQuery } from '@/api/order/orderStatusLog/types';
+
+/**
+ * 查询订单状态流转记录列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listOrderStatusLog = (query?: OrderStatusLogQuery): AxiosPromise<OrderStatusLogVO[]> => {
+  return request({
+    url: '/order/orderStatusLog/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询订单状态流转记录详细
+ * @param id
+ */
+export const getOrderStatusLog = (id: string | number): AxiosPromise<OrderStatusLogVO> => {
+  return request({
+    url: '/order/orderStatusLog/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增订单状态流转记录
+ * @param data
+ */
+export const addOrderStatusLog = (data: OrderStatusLogForm) => {
+  return request({
+    url: '/order/orderStatusLog',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改订单状态流转记录
+ * @param data
+ */
+export const updateOrderStatusLog = (data: OrderStatusLogForm) => {
+  return request({
+    url: '/order/orderStatusLog',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除订单状态流转记录
+ * @param id
+ */
+export const delOrderStatusLog = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/order/orderStatusLog/' + id,
+    method: 'delete'
+  });
+};
+
+/**
+ * 查询最新状态流转记录
+ * @param data
+ */
+export const selectNewOneLog = (data: OrderStatusLogForm) => {
+  return request({
+    url: '/order/orderStatusLog/selectNewOne',
+    method: 'post',
+    data: data
+  });
+};

+ 219 - 0
src/api/order/orderStatusLog/types.ts

@@ -0,0 +1,219 @@
+export interface OrderStatusLogVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 订单编号
+   */
+  orderNo: string;
+
+  /**
+   * 订单ID (关联订单主表)
+   */
+  orderId: string | number;
+
+  /**
+   * 客户编号
+   */
+  customerNo: string;
+
+  /**
+   * 客户ID (关联客户主表)
+   */
+  customerId: string | number;
+
+  /**
+   * 状态类型(发货方式)
+   */
+  deliverMethod: string;
+
+  /**
+   * 状态名称
+   */
+  statusName: string;
+
+  /**
+   * 驳回原因
+   */
+  turnDown: string;
+
+  /**
+   * 图片路径 (多张逗号分隔或JSON)
+   */
+  images: string;
+
+  /**
+   * 图片路径 (多张逗号分隔或JSON)Url
+   */
+  imagesUrl: string;
+  /**
+   * 物流单号
+   */
+  logisticNos: string;
+
+  /**
+   * 下一审批人
+   */
+  nextApprover: string;
+
+  /**
+   * 是否审批流程中 (1:否, 0:是)
+   */
+  isApprovalProcess: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface OrderStatusLogForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 订单编号
+   */
+  orderNo?: string;
+
+  /**
+   * 订单ID (关联订单主表)
+   */
+  orderId?: string | number;
+
+  /**
+   * 客户编号
+   */
+  customerNo?: string;
+
+  /**
+   * 客户ID (关联客户主表)
+   */
+  customerId?: string | number;
+
+  /**
+   * 状态类型(发货方式)
+   */
+  deliverMethod?: string;
+
+  /**
+   * 状态名称
+   */
+  statusName?: string;
+
+  /**
+   * 驳回原因
+   */
+  turnDown?: string;
+
+  /**
+   * 图片路径 (多张逗号分隔或JSON)
+   */
+  images?: string;
+
+  /**
+   * 物流单号
+   */
+  logisticNos?: string;
+
+  /**
+   * 下一审批人
+   */
+  nextApprover?: string;
+
+  /**
+   * 是否审批流程中 (1:否, 0:是)
+   */
+  isApprovalProcess?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface OrderStatusLogQuery extends PageQuery {
+  /**
+   * 订单编号
+   */
+  orderNo?: string;
+
+  /**
+   * 订单ID (关联订单主表)
+   */
+  orderId?: string | number;
+
+  /**
+   * 客户编号
+   */
+  customerNo?: string;
+
+  /**
+   * 客户ID (关联客户主表)
+   */
+  customerId?: string | number;
+
+  /**
+   * 状态类型(发货方式)
+   */
+  deliverMethod?: string;
+
+  /**
+   * 状态名称
+   */
+  statusName?: string;
+
+  /**
+   * 驳回原因
+   */
+  turnDown?: string;
+
+  /**
+   * 图片路径 (多张逗号分隔或JSON)
+   */
+  images?: string;
+
+  /**
+   * 物流单号
+   */
+  logisticNos?: string;
+
+  /**
+   * 下一审批人
+   */
+  nextApprover?: string;
+
+  /**
+   * 是否审批流程中 (1:否, 0:是)
+   */
+  isApprovalProcess?: string;
+
+  /**
+   * 状态(0正常 1停用)
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 1 - 0
src/api/partner/bank/types.ts

@@ -19,6 +19,7 @@ export interface PartnerBankForm {
   partnerId: number | string;
   account: string;
   registrationNumber?: string;
+  bankId?: number | string;
   accountBankName?: string;
   bankNumber?: string;
   bankLocation?: string;

+ 63 - 0
src/api/project/projectInfo/index.ts

@@ -0,0 +1,63 @@
+import request from '@/utils/request';
+import { AxiosPromise } from 'axios';
+import { ProjectInfoVO, ProjectInfoForm, ProjectInfoQuery } from '@/api/project/projectInfo/types';
+
+/**
+ * 查询项目信息列表
+ * @param query
+ * @returns {*}
+ */
+
+export const listProjectInfo = (query?: ProjectInfoQuery): AxiosPromise<ProjectInfoVO[]> => {
+  return request({
+    url: '/customer/projectInfo/list',
+    method: 'get',
+    params: query
+  });
+};
+
+/**
+ * 查询项目信息详细
+ * @param id
+ */
+export const getProjectInfo = (id: string | number): AxiosPromise<ProjectInfoVO> => {
+  return request({
+    url: '/customer/projectInfo/' + id,
+    method: 'get'
+  });
+};
+
+/**
+ * 新增项目信息
+ * @param data
+ */
+export const addProjectInfo = (data: ProjectInfoForm) => {
+  return request({
+    url: '/customer/projectInfo',
+    method: 'post',
+    data: data
+  });
+};
+
+/**
+ * 修改项目信息
+ * @param data
+ */
+export const updateProjectInfo = (data: ProjectInfoForm) => {
+  return request({
+    url: '/customer/projectInfo',
+    method: 'put',
+    data: data
+  });
+};
+
+/**
+ * 删除项目信息
+ * @param id
+ */
+export const delProjectInfo = (id: string | number | Array<string | number>) => {
+  return request({
+    url: '/customer/projectInfo/' + id,
+    method: 'delete'
+  });
+};

+ 140 - 0
src/api/project/projectInfo/types.ts

@@ -0,0 +1,140 @@
+export interface ProjectInfoVO {
+  /**
+   * 主键ID
+   */
+  id: string | number;
+
+  /**
+   * 项目编号
+   */
+  projectNo: string;
+
+  /**
+   * 项目logo
+   */
+  projectLogo: string;
+
+  /**
+   * 项目名称
+   */
+  projectName: string;
+
+  /**
+   * 项目类型
+   */
+  projectType: string;
+
+  /**
+   * 负责人id
+   */
+  leaderId: string | number;
+
+  /**
+   * 负责人
+   */
+  leaderName: string;
+
+  /**
+   * 状态
+   */
+  status: string;
+
+  /**
+   * 备注
+   */
+  remark: string;
+}
+
+export interface ProjectInfoForm extends BaseEntity {
+  /**
+   * 主键ID
+   */
+  id?: string | number;
+
+  /**
+   * 项目编号
+   */
+  projectNo?: string;
+
+  /**
+   * 项目logo
+   */
+  projectLogo?: string;
+
+  /**
+   * 项目名称
+   */
+  projectName?: string;
+
+  /**
+   * 项目类型
+   */
+  projectType?: string;
+
+  /**
+   * 负责人id
+   */
+  leaderId?: string | number;
+
+  /**
+   * 负责人
+   */
+  leaderName?: string;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+  /**
+   * 备注
+   */
+  remark?: string;
+}
+
+export interface ProjectInfoQuery extends PageQuery {
+  /**
+   * 项目编号
+   */
+  projectNo?: string;
+
+  /**
+   * 项目logo
+   */
+  projectLogo?: string;
+
+  /**
+   * 项目名称
+   */
+  projectName?: string;
+
+  /**
+   * 项目类型
+   */
+  projectType?: string;
+
+  /**
+   * 负责人id
+   */
+  leaderId?: string | number;
+
+  /**
+   * 负责人
+   */
+  leaderName?: string;
+
+  /**
+   * 状态
+   */
+  status?: string;
+
+  /**
+   * 平台标识
+   */
+  platformCode?: string;
+
+  /**
+   * 日期范围参数
+   */
+  params?: any;
+}

+ 52 - 57
src/views/bill/statementInvoice/addInvoiceDialog.vue

@@ -52,29 +52,22 @@
 
       <el-row :gutter="20">
         <el-col :span="24">
-          <el-form-item label="发票附件" prop="invoiceAttachment">
-            <el-button type="primary" size="small" @click="handleOpenFileSelector">
-              <el-icon><Upload /></el-icon>
-              点击上传
-            </el-button>
-            <div style="color: #999; font-size: 12px; margin-top: 5px">支持jpg/png/xlsx等文件</div>
-          </el-form-item>
-        </el-col>
-      </el-row>
-
-      <!-- 附件列表 -->
-      <el-row :gutter="20" v-if="fileList.length > 0">
-        <el-col :span="24">
-          <el-table :data="fileList" border style="width: 100%; margin-top: 10px">
-            <el-table-column type="index" label="序号" width="60" align="center" />
-            <el-table-column prop="name" label="附件名称" min-width="200" align="center" />
-            <el-table-column label="操作" width="150" align="center">
-              <template #default="scope">
-                <el-button link type="primary" size="small" @click="handlePreviewFile(scope.row)">预览</el-button>
-                <el-button link type="danger" size="small" @click="handleRemoveFile(scope.$index)">删除</el-button>
+          <el-form-item label="发票附件" prop="invoiceAnnex">
+            <el-upload
+              :action="uploadAction"
+              :on-success="handleUploadSuccess"
+              :before-upload="beforeUpload"
+              :on-remove="handleRemoveUploadFile"
+              :on-preview="handlePreviewUploadFile"
+              :file-list="fileList"
+              multiple
+            >
+              <el-button type="primary" icon="Upload">点击上传</el-button>
+              <template #tip>
+                <div class="el-upload__tip">支持jpg/png/xlsx等文件,单个文件不超过50MB</div>
               </template>
-            </el-table-column>
-          </el-table>
+            </el-upload>
+          </el-form-item>
         </el-col>
       </el-row>
     </el-form>
@@ -86,15 +79,9 @@
       </div>
     </template>
   </el-dialog>
-
-  <!-- 文件选择器 -->
-  <FileSelector v-model="fileSelectorVisible" :multiple="true" :allowed-types="[1, 2, 3, 4, 5]" title="选择发票附件" @confirm="handleFileSelected" />
 </template>
 
 <script setup name="AddInvoiceDialog" lang="ts">
-import { Upload } from '@element-plus/icons-vue';
-import FileSelector from '@/components/FileSelector/index.vue';
-
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { invoice_type } = toRefs<any>(proxy?.useDict('invoice_type'));
 interface InvoiceForm {
@@ -104,7 +91,7 @@ interface InvoiceForm {
   invoiceAmount?: number;
   invoiceDate?: string;
   remark?: string;
-  invoiceAttachment?: string;
+  invoiceAnnex?: string;
 }
 
 const initFormData: InvoiceForm = {
@@ -114,14 +101,14 @@ const initFormData: InvoiceForm = {
   invoiceAmount: undefined,
   remark: undefined,
   invoiceDate: undefined,
-  invoiceAttachment: undefined
+  invoiceAnnex: undefined
 };
 
 const formRef = ref<ElFormInstance>();
 const buttonLoading = ref(false);
 const form = ref<InvoiceForm>({ ...initFormData });
 const fileList = ref<any[]>([]);
-const fileSelectorVisible = ref(false);
+const uploadAction = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
 
 const dialog = reactive<DialogOption>({
   visible: false,
@@ -152,14 +139,15 @@ const open = (data?: InvoiceForm) => {
     Object.assign(form.value, data);
 
     // 解析附件地址并回显附件列表
-    if (data.invoiceAttachment) {
-      const urls = data.invoiceAttachment.split(',').filter((url) => url.trim());
+    if (data.invoiceAnnex) {
+      const urls = data.invoiceAnnex.split(',').filter((url) => url.trim());
       fileList.value = urls.map((url, index) => {
         const fileName = url.split('/').pop() || `附件${index + 1}`;
         return {
           name: fileName,
           url: url.trim(),
-          id: undefined
+          uid: Date.now() + index,
+          status: 'success'
         };
       });
     }
@@ -175,39 +163,46 @@ const reset = () => {
   formRef.value?.clearValidate();
 };
 
-/** 打开文件选择器 */
-const handleOpenFileSelector = () => {
-  fileSelectorVisible.value = true;
+/** 上传前校验 */
+const beforeUpload = (file: any) => {
+  const isLt50M = file.size / 1024 / 1024 < 50;
+  if (!isLt50M) {
+    proxy?.$modal.msgWarning('上传文件大小不能超过 50MB!');
+  }
+  return isLt50M;
 };
 
-/** 文件选择完成 */
-const handleFileSelected = (files: any[]) => {
-  if (files && files.length > 0) {
-    files.forEach((file) => {
-      fileList.value.push({
-        name: file.fileName || file.name,
-        url: file.fileUrl || file.url,
-        id: file.id
-      });
-    });
-    form.value.invoiceAttachment = fileList.value.map((f) => f.url || f.name).join(',');
+function handleUploadSuccess(response: any, file: any, fileListParam: any[]) {
+  if (response.code === 200) {
+    // 更新 fileList
+    fileList.value = fileListParam;
+    // 收集所有已上传成功的文件URL
+    const urls = fileListParam
+      .filter((f: any) => f.response?.code === 200 || f.url)
+      .map((f: any) => f.response?.data?.url || f.url)
+      .filter(Boolean);
+    form.value.invoiceAnnex = urls.join(',');
+    proxy?.$modal.msgSuccess('文件上传成功');
+  } else {
+    proxy?.$modal.msgError(response.msg || '文件上传失败');
   }
-  fileSelectorVisible.value = false;
-};
+}
 
 /** 删除文件 */
-const handleRemoveFile = (index: number) => {
-  fileList.value.splice(index, 1);
-  form.value.invoiceAttachment = fileList.value.map((f) => f.url || f.name).join(',');
+const handleRemoveUploadFile = (uploadFile: any) => {
+  form.value.invoiceAnnex = fileList.value
+    .map((f) => f.url)
+    .filter(Boolean)
+    .join(',');
 };
 
 /** 预览文件 */
-const handlePreviewFile = (file: any) => {
-  if (!file.url) {
+const handlePreviewUploadFile = (uploadFile: any) => {
+  if (uploadFile.url) {
+    window.open(uploadFile.url, '_blank');
+  } else {
     proxy?.$modal.msgWarning('文件地址不存在');
-    return;
   }
-  window.open(file.url, '_blank');
 };
 
 /** 提交表单 */

+ 8 - 1
src/views/bill/statementInvoice/index.vue

@@ -8,7 +8,14 @@
               <el-input v-model="queryParams.statementInvoiceNo" placeholder="请输入发票编号" clearable @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="开票日期" prop="statementDate">
-              <el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
+              <el-date-picker
+                v-model="dateRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                value-format="YYYY-MM-DD"
+              />
             </el-form-item>
             <el-form-item label="客户名称" prop="customerId">
               <el-select

+ 43 - 39
src/views/bill/statementOrder/addDrawer.vue

@@ -173,9 +173,16 @@
           <span style="color: #409eff; font-weight: 600">对账附件</span>
         </el-divider>
 
-        <div style="margin-bottom: 10px">
-          <el-button type="primary" icon="Upload" @click="handleOpenFileSelector">点击上传</el-button>
-        </div>
+        <el-upload
+          :action="uploadAction"
+          :on-success="handleUploadSuccess"
+          :before-upload="beforeUpload"
+          :show-file-list="false"
+          multiple
+          style="margin-bottom: 10px"
+        >
+          <el-button type="primary" icon="Upload">点击上传</el-button>
+        </el-upload>
 
         <el-table :data="fileList" border style="width: 100%; margin-bottom: 20px">
           <el-table-column type="index" label="序号" width="80" align="center" />
@@ -185,10 +192,11 @@
               {{ scope.row.url || '暂无地址' }}
             </template>
           </el-table-column>
-          <el-table-column label="操作" width="200" align="center">
+          <el-table-column label="操作" width="250" align="center">
             <template #default="scope">
               <el-button type="primary" link @click="handleDownloadFile(scope.row)">下载</el-button>
               <el-button type="primary" link @click="handlePreviewFile(scope.row)">预览</el-button>
+              <el-button type="danger" link @click="handleRemoveFile(scope.$index)">删除</el-button>
             </template>
           </el-table-column>
         </el-table>
@@ -209,9 +217,6 @@
 
   <!-- 客户订单抽屉 -->
   <OrderMainDrawer ref="orderMainDrawerRef" @success="handleOrderSelected" />
-
-  <!-- 文件选择器 -->
-  <FileSelector v-model="fileSelectorVisible" :multiple="true" :allowed-types="[1, 2, 3, 4, 5]" title="选择对账附件" @confirm="handleFileSelected" />
 </template>
 
 <script setup name="AddDrawer" lang="ts">
@@ -226,7 +231,6 @@ import { OrderDeliverVO } from '@/api/order/orderDeliver/types';
 import { listComStaff } from '@/api/company/comStaff';
 import { ComStaffVO, ComStaffQuery } from '@/api/company/comStaff/types';
 import OrderMainDrawer from './orderMainDrawer.vue';
-import FileSelector from '@/components/FileSelector/index.vue';
 import { getCustomerDeliverOrders } from '@/api/order/orderDeliver';
 
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
@@ -298,8 +302,7 @@ const customerLoading = ref(false);
 const customerOptions = ref<CustomerInfoVO[]>([]);
 const staffOptions = ref<ComStaffVO[]>([]);
 const orderMainDrawerRef = ref<any>();
-const fileSelectorRef = ref<any>();
-const fileSelectorVisible = ref(false);
+const uploadAction = import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload';
 const currentSelectedOrders = ref<OrderDeliverVO[]>([]);
 const preloadedOrders = ref<OrderDeliverVO[]>([]); // 预加载的订单列表
 
@@ -542,41 +545,42 @@ const handleProductCurrentChange = (val: number) => {
   productPage.pageNum = val;
 };
 
-/** 文件上传改变 */
-const handleFileChange = (file: any, uploadFileList: any[]) => {
-  // 更新文件列表
-  fileList.value = uploadFileList;
-  // 更新表单中的附件地址
-  form.value.annexAddress = uploadFileList.map((f) => f.name).join(',');
+/** 上传前校验 */
+const beforeUpload = (file: any) => {
+  const isLt50M = file.size / 1024 / 1024 < 50;
+  if (!isLt50M) {
+    proxy?.$modal.msgWarning('上传文件大小不能超过 50MB!');
+  }
+  return isLt50M;
+};
+
+/** 上传成功回调 */
+const handleUploadSuccess = (response: any, file: any) => {
+  if (response.code === 200) {
+    fileList.value.push({
+      name: file.name,
+      url: response.data.url,
+      id: undefined
+    });
+    // 更新表单中的附件地址
+    form.value.annexAddress = fileList.value
+      .map((f) => f.url)
+      .filter(Boolean)
+      .join(',');
+    proxy?.$modal.msgSuccess('上传成功');
+  } else {
+    proxy?.$modal.msgError(response.msg || '上传失败');
+  }
 };
 
 /** 删除文件 */
 const handleRemoveFile = (index: number) => {
   fileList.value.splice(index, 1);
   // 更新表单中的附件地址
-  form.value.annexAddress = fileList.value.map((f) => f.name).join(',');
-};
-
-/** 打开文件选择器 */
-const handleOpenFileSelector = () => {
-  fileSelectorVisible.value = true;
-};
-
-/** 处理文件选择完成 */
-const handleFileSelected = (files: any[]) => {
-  if (files && files.length > 0) {
-    // 将选中的文件添加到文件列表
-    files.forEach((file) => {
-      fileList.value.push({
-        name: file.fileName || file.name,
-        url: file.fileUrl || file.url,
-        id: file.id
-      });
-    });
-    // 更新表单中的附件地址
-    form.value.annexAddress = fileList.value.map((f) => f.url || f.name).join(',');
-  }
-  fileSelectorVisible.value = false;
+  form.value.annexAddress = fileList.value
+    .map((f) => f.url)
+    .filter(Boolean)
+    .join(',');
 };
 
 /** 下载文件 */

+ 8 - 1
src/views/bill/statementOrder/index.vue

@@ -8,7 +8,14 @@
               <el-input v-model="queryParams.statementOrderNo" placeholder="请输入对账单号" clearable @keyup.enter="handleQuery" />
             </el-form-item>
             <el-form-item label="对账日期" prop="statementDate">
-              <el-date-picker v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间" />
+              <el-date-picker
+                v-model="dateRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                value-format="YYYY-MM-DD"
+              />
             </el-form-item>
             <el-form-item label="客户名称" prop="customerName">
               <el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable @keyup.enter="handleQuery" />

+ 2 - 2
src/views/order/orderAssignment/index.vue

@@ -90,7 +90,7 @@
           <template #default="scope">
             <el-button link type="primary" @click="handleReview(scope.row)">详情</el-button>
             <el-button link type="primary" v-if="scope.row.assignmentStatus != '1'" @click="handleAssignment(scope.row)">分配</el-button>
-            <!-- <el-button link type="primary" v-if="scope.row.assignmentStatus == '1'" @click="handleAssignment(scope.row)">重新分配</el-button> -->
+            <el-button link type="primary" v-if="scope.row.assignmentStatus == '1'" @click="handleAssignment(scope.row)">分配记录</el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -282,7 +282,7 @@ const handleAssignment = async (row?: OrderMainVO | null) => {
 
   if (row) {
     // 单个订单分配 - 直接打开统一的分配抽屉
-    splitAssignDialogRef.value?.open(row.id);
+    splitAssignDialogRef.value?.open(row.id, row.assignmentStatus);
   } else {
     // 批量分配 - 只支持整单分配
     if (!ids.value || ids.value.length === 0) {

+ 12 - 6
src/views/order/orderAssignment/splitAssignDialog.vue

@@ -45,7 +45,7 @@
       </div>
 
       <el-tabs v-model="activeTab" class="detail-tabs">
-        <el-tab-pane label="待分配" name="pending">
+        <el-tab-pane label="待分配" name="pending" v-if="assignmentStatus != '1'">
           <div class="tab-actions">
             <el-button type="primary" @click="handleBatchAssign">+ 批量分配</el-button>
           </div>
@@ -73,7 +73,7 @@
               </template>
             </el-table-column>
             <el-table-column label="商品编号" prop="productNo" align="center" />
-            <el-table-column label="商品名称" prop="productName" min-width="200" show-overflow-tooltip align="center" />
+            <el-table-column label="商品名称" prop="productName" min-width="200" show-overflow-tooltip align="left" />
             <el-table-column label="数量" prop="orderQuantity" align="center" />
             <el-table-column label="单价" prop="orderPrice" align="center" />
             <el-table-column label="小计" align="center">
@@ -109,7 +109,7 @@
               </template>
             </el-table-column>
             <el-table-column label="商品编号" prop="productNo" align="center" />
-            <el-table-column label="商品名称" prop="productName" min-width="200" show-overflow-tooltip align="center" />
+            <el-table-column label="商品名称" prop="productName" min-width="200" show-overflow-tooltip align="left" />
             <el-table-column label="数量" prop="orderQuantity" align="center" />
             <el-table-column label="单价" prop="orderPrice" align="center" />
             <el-table-column label="小计" align="center">
@@ -120,6 +120,7 @@
                 <dict-tag :options="order_assignment_status" :value="scope.row.assignmentStatus" />
               </template>
             </el-table-column>
+            <el-table-column label="分配人" prop="assigneeName" min-width="200" align="left" />
           </el-table>
         </el-tab-pane>
 
@@ -297,11 +298,16 @@ const assignedProducts = ref<any[]>([]);
 // 分配记录
 const assignRecords = ref<any[]>([]);
 
+const assignmentStatus = ref('0');
+
 /** 打开订单分配详情抽屉 */
-const open = async (orderId: string | number) => {
+const open = async (orderId: string | number, status: string) => {
   reset();
   drawer.visible = true;
-
+  assignmentStatus.value = status;
+  if (status == '1') {
+    activeTab.value = 'assigned';
+  }
   try {
     // 获取订单详情
     const res = await getOrderMain(orderId);
@@ -508,7 +514,7 @@ const submitAssign = async () => {
     selectedAssignProducts.value = [];
 
     // 刷新抽屉数据
-    await open(orderInfo.value.id);
+    await open(orderInfo.value.id, '0');
     emit('success');
   } catch (error) {
     console.error('分配失败:', error);

+ 314 - 0
src/views/order/saleOrder/addOrderStatusLogDrawer.vue

@@ -0,0 +1,314 @@
+<template>
+  <!-- 订单状态日志抽屉 -->
+  <el-drawer v-model="drawerVisible" title="变更物流状态" size="38%" :destroy-on-close="true" :close-on-click-modal="true">
+    <div class="status-log-container">
+      <el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="物流单号" prop="logisticNos">
+          <el-input v-model="form.logisticNos" disabled />
+        </el-form-item>
+
+        <el-form-item label="包裹单号" prop="packageNo">
+          <el-input v-model="form.packageNo" disabled />
+        </el-form-item>
+
+        <el-form-item label="所在状态" prop="statusName">
+          <el-select v-model="form.statusName" placeholder="请选择状态" style="width: 100%" @change="handleStatusChange">
+            <el-option
+              v-for="item in statusOptions"
+              :key="item.value"
+              :label="item.label"
+              :value="item.value"
+              :disabled="isStatusDisabled(item.value)"
+            />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="发货人" prop="shipper">
+          <el-input v-model="form.shipper" disabled />
+        </el-form-item>
+
+        <el-form-item label="操作时间" prop="operateTime">
+          <el-date-picker v-model="form.operateTime" type="date" placeholder="请选择操作时间" style="width: 100%" />
+        </el-form-item>
+
+        <el-form-item label="快递员手机号" prop="courierPhone">
+          <el-input v-model="form.courierPhone" disabled />
+        </el-form-item>
+
+        <el-form-item label="上传图片" prop="images" v-if="form.statusName === '已签收'">
+          <div class="image-selector-wrapper">
+            <!-- 修改点 1: 使用 !selectedImages.length 替代 length === 0,并确保样式一致 -->
+            <div
+              v-if="!selectedImages || selectedImages.length === 0"
+              class="upload-box"
+              @click="showFileSelector = true"
+              style="
+                width: 150px;
+                height: 150px;
+                border: 2px dashed #d9d9d9;
+                border-radius: 4px;
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                cursor: pointer;
+                transition: all 0.3s;
+              "
+            >
+              <div style="text-align: center; color: #8c939d">
+                <el-icon :size="40" style="margin-bottom: 8px">
+                  <Plus />
+                </el-icon>
+                <div style="font-size: 14px">点击上传</div>
+              </div>
+            </div>
+
+            <!-- 修改点 2: 列表渲染区域 -->
+            <div v-else class="selected-images-list">
+              <div
+                v-for="(img, index) in selectedImages"
+                :key="img.url || index"
+                style="position: relative; display: inline-block; margin: 0 8px 8px 0"
+              >
+                <!-- 确保 img.url 存在才渲染,防止报错 -->
+                <el-image
+                  v-if="img.url"
+                  :src="img.url"
+                  style="width: 150px; height: 150px"
+                  fit="cover"
+                  :preview-src-list="selectedImages.filter((i) => i.url).map((i) => i.url)"
+                />
+                <el-button
+                  type="danger"
+                  :icon="Delete"
+                  circle
+                  size="small"
+                  style="position: absolute; top: 5px; right: 5px; z-index: 10"
+                  @click="removeImage(index)"
+                />
+              </div>
+
+              <!-- 修改点 3: 如果未满 3 张,显示“继续添加”按钮 -->
+              <div
+                v-if="selectedImages.length < 3"
+                class="upload-box-more"
+                @click="showFileSelector = true"
+                style="
+                  width: 150px;
+                  height: 150px;
+                  border: 2px dashed #d9d9d9;
+                  border-radius: 4px;
+                  display: inline-flex;
+                  align-items: center;
+                  justify-content: center;
+                  cursor: pointer;
+                  transition: all 0.3s;
+                  vertical-align: top;
+                  background-color: #f5f7fa; /* 加个背景色区分 */
+                "
+              >
+                <el-icon :size="30" color="#8c939d">
+                  <Plus />
+                </el-icon>
+              </div>
+            </div>
+          </div>
+        </el-form-item>
+
+        <!-- 文件选择器 -->
+        <FileSelector v-model="showFileSelector" :multiple="true" :allowed-types="[1]" title="选择图片" @confirm="handleImagesSelected" />
+      </el-form>
+    </div>
+
+    <template #footer>
+      <div class="drawer-footer">
+        <el-button @click="closeDrawer">取消</el-button>
+        <el-button type="primary" @click="handleSubmit" :loading="submitLoading">保存</el-button>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+
+<script setup name="AddOrderStatusLogDrawer" lang="ts">
+import { ref, reactive } from 'vue';
+import { ElMessage } from 'element-plus';
+import { Plus, Delete } from '@element-plus/icons-vue';
+import { addOrderStatusLog } from '@/api/order/orderStatusLog';
+import FileSelector from '@/components/FileSelector/index.vue';
+
+const drawerVisible = ref(false);
+const submitLoading = ref(false);
+const formRef = ref<any>(null);
+const showFileSelector = ref(false);
+const selectedImages = ref<any[]>([]);
+
+// 表单数据
+const form = reactive({
+  orderNo: '',
+  orderId: undefined as string | number | undefined,
+  customerNo: '',
+  customerId: undefined as string | number | undefined,
+  deliverMethod: '0',
+  statusName: '',
+  logisticNos: '',
+  packageNo: '',
+  shipper: '',
+  operateTime: '',
+  courierPhone: '',
+  images: '',
+  status: '0'
+});
+
+// 状态顺序定义
+const statusOrder = ['已下单', '已揽件', '运输中', '派件中', '已签收'];
+
+// 当前订单状态
+const currentStatus = ref('');
+
+// 状态选项
+const statusOptions = [
+  { label: '已下单', value: '已下单' },
+  { label: '已揽件', value: '已揽件' },
+  { label: '运输中', value: '运输中' },
+  { label: '派件中', value: '派件中' },
+  { label: '已签收', value: '已签收' }
+];
+const rules = {
+  statusName: [{ required: true, message: '请选择状态名称', trigger: 'change' }]
+};
+
+// 打开抽屉
+const openDrawer = (orderData?: any, currentOrderStatus?: string) => {
+  if (orderData) {
+    form.orderNo = orderData.orderNo || '';
+    form.orderId = orderData.orderId || '';
+    form.shipper = orderData.deliverMan || '';
+    form.courierPhone = orderData.phone || '';
+    form.packageNo = orderData.deliverCode || '';
+    form.logisticNos = orderData.deliverCode || '';
+    form.customerNo = orderData.customerNo || '';
+    form.customerId = orderData.customerId;
+    form.operateTime = orderData.createTime || '';
+    form.deliverMethod = orderData.deliverMethod || '0';
+    form.statusName = currentOrderStatus || '';
+    form.images = orderData.images || '';
+
+    selectedImages.value = [];
+    if (form.images) {
+      const urls = form.images.split(',');
+      selectedImages.value = urls.filter((u) => u && u.trim()).map((u) => ({ url: u.trim(), name: 'Image' }));
+    }
+
+    currentStatus.value = currentOrderStatus || '';
+  }
+  drawerVisible.value = true;
+};
+
+// 关闭抽屉
+const closeDrawer = () => {
+  drawerVisible.value = false;
+  resetForm();
+};
+
+// 判断是否禁用某个状态选项
+const isStatusDisabled = (statusValue: string) => {
+  if (!currentStatus.value) return false;
+  const currentIndex = statusOrder.indexOf(currentStatus.value);
+  const targetIndex = statusOrder.indexOf(statusValue);
+  // 如果目标状态在当前状态之前(索引更小),则禁用
+  return targetIndex < currentIndex;
+};
+
+// 重置表单
+const resetForm = () => {
+  form.statusName = '';
+  form.logisticNos = '';
+  form.packageNo = '';
+  form.orderId = undefined;
+  form.shipper = '';
+  form.operateTime = '';
+  form.courierPhone = '';
+  form.images = '';
+  selectedImages.value = [];
+  showFileSelector.value = false;
+  currentStatus.value = '';
+};
+
+// 处理选择的图片 (优化健壮性)
+const handleImagesSelected = (files: any[]) => {
+  if (!files || files.length === 0) {
+    showFileSelector.value = false;
+    return;
+  }
+
+  const remainingSlots = 3 - selectedImages.value.length;
+  if (remainingSlots <= 0) {
+    ElMessage.warning('最多上传 3 张图片');
+    showFileSelector.value = false;
+    return;
+  }
+
+  const filesToAdd = files.slice(0, remainingSlots);
+
+  // 映射为标准格式 { url, name }
+  const newItems = filesToAdd
+    .map((f) => ({
+      url: f.fileUrl || f.url || f.path, // 兼容多种返回字段
+      name: f.fileName || 'Image'
+    }))
+    .filter((item) => item.url); // 过滤掉没有 url 的无效项
+
+  if (newItems.length > 0) {
+    // 重新赋值数组以触发视图更新
+    selectedImages.value = [...selectedImages.value, ...newItems];
+    form.images = selectedImages.value.map((img) => img.url).join(',');
+  }
+
+  showFileSelector.value = false;
+};
+
+// 移除已选择的图片
+const removeImage = (index: number) => {
+  selectedImages.value.splice(index, 1);
+  form.images = selectedImages.value.map((img) => img.url).join(',');
+};
+
+// 状态变化处理
+const handleStatusChange = () => {
+  // 当状态变化时,如果不是已签收,清空图片
+  if (form.statusName !== '已签收') {
+    form.images = '';
+    selectedImages.value = [];
+  }
+};
+const handleSubmit = async () => {
+  await formRef.value?.validate();
+  submitLoading.value = true;
+  try {
+    await addOrderStatusLog(form);
+    ElMessage.success('保存成功');
+    closeDrawer();
+  } catch (error) {
+    console.error('保存失败:', error);
+    ElMessage.error('保存失败');
+  } finally {
+    submitLoading.value = false;
+  }
+};
+
+// 暴露方法给父组件
+defineExpose({
+  openDrawer
+});
+</script>
+
+<style scoped>
+.status-log-container {
+  padding: 20px;
+}
+
+.drawer-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+</style>

+ 1 - 3
src/views/order/saleOrder/deliverDialog.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-dialog :model-value="modelValue" title="发货" width="1200px" @update:model-value="handleDialogChange" @open="handleOpen" @close="handleClose">
+  <el-dialog :model-value="modelValue" title="发货" width="50%" @update:model-value="handleDialogChange" @open="handleOpen" @close="handleClose">
     <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
       <el-row :gutter="20">
         <el-col :span="12">
@@ -255,8 +255,6 @@ const handleLogisticsCompanyChange = (val: any) => {
 const loadProductList = async () => {
   try {
     const res = await getOrderMain(props.orderId);
-    console.log(res.data.orderProductList);
-
     // 为每个商品添加发货数量字段,默认为未发货数量
     productList.value = (res.data.orderProductList || []).map((item: OrderProductVO) => ({
       ...item,

+ 436 - 0
src/views/order/saleOrder/editDeliverDialog.vue

@@ -0,0 +1,436 @@
+<template>
+  <el-dialog
+    :model-value="modelValue"
+    title="编辑发货信息"
+    width="50%"
+    @update:model-value="handleDialogChange"
+    @open="handleOpen"
+    @close="handleClose"
+  >
+    <el-form :model="form" :rules="rules" ref="formRef" label-width="120px">
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item label="订单编号" prop="orderCode">
+            <el-input v-model="form.orderCode" disabled />
+          </el-form-item>
+        </el-col>
+
+        <el-col :span="12" v-if="form.deliverMethod === '0'">
+          <el-form-item label="送货人" prop="deliverMan">
+            <el-input v-model="form.deliverMan" placeholder="请输入送货人姓名" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12" v-if="form.deliverMethod === '1'">
+          <el-form-item label="物流公司名称" prop="logisticsCompanyId">
+            <el-select
+              v-model="form.logisticsCompanyId"
+              placeholder="请选择"
+              style="width: 100%"
+              filterable
+              @change="handleLogisticsCompanyChange"
+              disabled
+            >
+              <el-option v-for="company in logisticsCompanyList" :key="company.id" :label="company.logisticsName" :value="company.id" />
+            </el-select>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12">
+          <el-form-item v-if="form.deliverMethod === '1'" label="物流单号" prop="logisticNo">
+            <el-input v-model="form.logisticNo" placeholder="请输入物流单号" disabled />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12">
+          <el-form-item label="发货方式" prop="deliverMethod">
+            <el-radio-group v-model="form.deliverMethod">
+              <el-radio v-for="dict in deliver_method" :key="dict.value" :value="dict.value" disabled>{{ dict.label }}</el-radio>
+            </el-radio-group>
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="12" v-if="form.deliverMethod === '1'">
+          <el-form-item label="收货人手机" prop="consigneePhone">
+            <el-input v-model="form.consigneePhone" placeholder="请输入收货人手机" />
+          </el-form-item>
+        </el-col>
+        <el-col :span="12" v-if="form.deliverMethod === '0'">
+          <el-form-item label="手机号码" prop="phone">
+            <el-input v-model="form.phone" placeholder="请输入手机号码" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+      <el-row :gutter="20">
+        <el-col :span="24">
+          <el-form-item label="发货备注" prop="deliverRemark">
+            <el-input v-model="form.deliverRemark" type="textarea" :rows="3" placeholder="请输入内容" />
+          </el-form-item>
+        </el-col>
+      </el-row>
+    </el-form>
+
+    <!-- 商品列表 -->
+    <el-table :data="productList" border style="width: 100%" max-height="600px">
+      <el-table-column prop="productNo" label="产品编号" width="100" />
+      <el-table-column label="产品图片" width="100">
+        <template #default="scope">
+          <el-image v-if="scope.row.productImage" :src="scope.row.productImage" style="width: 60px; height: 60px" fit="cover" />
+          <span v-else>暂无图片</span>
+        </template>
+      </el-table-column>
+      <el-table-column prop="productName" label="产品名称" min-width="200" show-overflow-tooltip />
+      <el-table-column prop="productUnit" label="单位" width="120" />
+      <el-table-column prop="orderPrice" label="商品单价" width="160" />
+      <el-table-column label="发货数量">
+        <template #default="scope">
+          <el-input-number
+            v-model="scope.row.deliverNum"
+            :min="0"
+            :max="scope.row.unsentQuantity"
+            :precision="0"
+            size="small"
+            disabled
+            :controls="false"
+            style="width: 100%"
+            @change="handleDeliveryQuantityChange(scope.$index)"
+          />
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <template #footer>
+      <div class="dialog-footer">
+        <el-button @click="handleCancel">取消</el-button>
+        <el-button type="primary" :loading="submitLoading" @click="handleSubmit">确认</el-button>
+      </div>
+    </template>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed, onMounted } from 'vue';
+import { getOrderMain } from '@/api/order/orderMain';
+import { OrderMainVO } from '@/api/order/orderMain/types';
+import { listOrderProduct } from '@/api/order/orderProduct';
+import { OrderProductVO } from '@/api/order/orderProduct/types';
+import { listLogisticsCompany } from '@/api/company/logisticsCompany';
+import { LogisticsCompanyVO } from '@/api/company/logisticsCompany/types';
+import { getOrderDeliver, addOrderDeliver, updateOrderDeliver } from '@/api/order/orderDeliver';
+import { OrderDeliverVO, OrderDeliverForm } from '@/api/order/orderDeliver/types';
+import { ElMessage } from 'element-plus';
+import { de } from 'element-plus/es/locale/index.mjs';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { deliver_method } = toRefs<any>(proxy?.useDict('deliver_method'));
+
+interface Props {
+  modelValue: boolean;
+  orderId?: string | number;
+  orderNo?: string;
+  operateType?: string;
+  deliverId?: string | number;
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: boolean): void;
+  (e: 'success'): void;
+}
+
+const props = defineProps<Props>();
+const emit = defineEmits<Emits>();
+
+const formRef = ref();
+const submitLoading = ref(false);
+const productList = ref<any[]>([]);
+const total = ref(0);
+
+// 物流公司列表
+const logisticsCompanyList = ref<LogisticsCompanyVO[]>([]);
+
+// 查询参数
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 20,
+  orderId: undefined as string | number | undefined
+});
+
+// 表单数据
+const form = reactive<OrderDeliverForm>({
+  orderId: undefined,
+  orderCode: '',
+  logisticPackNo: '',
+  deliverMethod: '1',
+  deliverMan: '',
+  phone: '',
+  logisticsStatus: '',
+  deliverRemark: '',
+  checklistRemark: '',
+  logisticsCompanyId: undefined,
+  logisticsCompanyCode: '',
+  logisticNo: '',
+  logisticPackStatus: '',
+  consigneePhone: '',
+  remark: '',
+  orderDeliverProducts: []
+});
+
+// 动态校验规则
+// 动态校验规则
+const rules = computed(() => {
+  const baseRules: any = {
+    deliverMethod: [{ required: true, message: '请选择发货方式', trigger: 'change' }]
+  };
+
+  // 第三方物流(deliverMethod === '1')
+  if (form.deliverMethod === '1') {
+    baseRules.logisticsCompanyId = [{ required: true, message: '请选择物流公司', trigger: 'change' }];
+    baseRules.logisticNo = [{ required: true, message: '请输入物流单号', trigger: 'blur' }];
+    baseRules.consigneePhone = [
+      { required: true, message: '请输入收货人手机号码', trigger: 'blur' },
+      { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
+    ];
+  }
+
+  // 自有物流(deliverMethod === '0')
+  if (form.deliverMethod === '0') {
+    baseRules.deliverMan = [{ required: true, message: '请输入送货人姓名', trigger: 'blur' }];
+    baseRules.phone = [
+      { required: true, message: '请输入手机号码', trigger: 'blur' },
+      { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
+    ];
+  }
+
+  return baseRules;
+});
+
+// 对话框状态变化
+const handleDialogChange = (val: boolean) => {
+  emit('update:modelValue', val);
+};
+
+// 对话框打开时触发
+const handleOpen = async () => {
+  resetForm();
+
+  // 如果传入deliverId,直接根据deliverId查询回显
+  if (props.deliverId) {
+    await loadDeliverInfoById(props.deliverId);
+  } else if (props.orderId) {
+    form.orderId = props.orderId;
+    form.orderCode = props.orderNo || '';
+    queryParams.value.orderId = props.orderId;
+    await loadProductList();
+  }
+};
+
+// 对话框关闭时触发
+const handleClose = () => {
+  resetForm();
+};
+
+// 重置表单
+const resetForm = () => {
+  form.orderId = undefined;
+  form.orderCode = '';
+  form.logisticsCompanyId = undefined;
+  form.logisticsCompanyCode = '';
+  form.logisticNo = '';
+  form.deliverMethod = '1';
+  form.deliverMan = '';
+  form.phone = '';
+  form.consigneePhone = '';
+  form.deliverRemark = '';
+  productList.value = [];
+  queryParams.value.pageNum = 1;
+  formRef.value?.clearValidate();
+};
+
+const handleLogisticsCompanyChange = (val: any) => {
+  const selectedCompany = logisticsCompanyList.value.find((item) => item.id === val);
+  if (selectedCompany) {
+    form.logisticsCompanyCode = selectedCompany.logisticsCode;
+  }
+};
+
+// 加载商品列表
+const loadProductList = async () => {
+  try {
+    const res = await getOrderMain(props.orderId);
+    // 为每个商品添加发货数量字段,默认为未发货数量
+    productList.value = (res.data.orderProductList || []).map((item: OrderProductVO) => ({
+      ...item,
+      deliverNum: 0,
+      productNo: item.productNo,
+      productId: item.productId,
+      orderPrice: item.orderPrice,
+      productUnit: item.productUnit,
+      productUnitId: item.productUnitId
+    }));
+    total.value = res.data.orderProductList.length || 0;
+  } catch (error) {
+    console.error('加载商品列表失败:', error);
+    ElMessage.error('加载商品列表失败');
+    productList.value = [];
+    total.value = 0;
+  }
+};
+
+// 加载物流公司列表
+const loadLogisticsCompanyList = async () => {
+  try {
+    const res = await listLogisticsCompany({
+      isShow: '0',
+      pageNum: 1,
+      pageSize: 1000
+    });
+    logisticsCompanyList.value = res.rows || [];
+  } catch (error) {
+    console.error('加载物流公司列表失败:', error);
+    logisticsCompanyList.value = [];
+  }
+};
+
+// 加载发货信息
+const loadDeliverInfo = async () => {
+  try {
+    const res = await getOrderDeliver(props.orderId);
+    if (res.code === 200 && res.data) {
+      const deliverData = res.data;
+      form.logisticsCompanyId = deliverData.logisticsCompanyId;
+      form.logisticsCompanyCode = deliverData.logisticsCompanyCode;
+      form.logisticNo = deliverData.logisticNo;
+      form.deliverMethod = deliverData.deliverMethod || '1';
+      form.consigneePhone = deliverData.consigneePhone;
+      form.deliverRemark = deliverData.deliverRemark;
+    }
+  } catch (error) {
+    console.error('加载发货信息失败:', error);
+  }
+};
+
+// 根据deliverId加载发货信息
+const loadDeliverInfoById = async (deliverId: string | number) => {
+  try {
+    const res = await getOrderDeliver(deliverId);
+    if (res.code === 200 && res.data) {
+      const deliverData = res.data;
+      form.orderId = deliverData.orderId;
+      form.orderCode = deliverData.orderCode;
+      form.logisticsCompanyId = deliverData.logisticsCompanyId;
+      form.logisticsCompanyCode = deliverData.logisticsCompanyCode;
+      form.logisticNo = deliverData.logisticNo;
+      form.deliverMan = deliverData.deliverMan;
+      form.phone = deliverData.phone;
+      form.deliverMethod = deliverData.deliverMethod || '1';
+      form.consigneePhone = deliverData.consigneePhone;
+      form.deliverRemark = deliverData.deliverRemark;
+      productList.value = deliverData.deliverProductList;
+    }
+  } catch (error) {
+    console.error('加载发货信息失败:', error);
+    ElMessage.error('加载发货信息失败');
+  }
+};
+
+// 发货数量变化
+const handleDeliveryQuantityChange = (index: number) => {
+  const product = productList.value[index];
+  if (product) {
+    // 确保发货数量不超过未发货数量
+    if (product.deliverNum > product.unsentQuantity) {
+      product.deliverNum = product.unsentQuantity;
+      ElMessage.warning('发货数量不能大于未发货数量');
+    }
+    // 确保发货数量不小于0
+    if (product.deliverNum < 0) {
+      product.deliverNum = 0;
+    }
+  }
+};
+
+// 删除商品
+const handleDeleteProduct = (index: number) => {
+  productList.value.splice(index, 1);
+};
+
+// 分页大小变化
+const handleSizeChange = () => {
+  queryParams.value.pageNum = 1;
+  loadProductList();
+};
+
+// 页码变化
+const handleCurrentChange = () => {
+  loadProductList();
+};
+
+// 取消
+const handleCancel = () => {
+  emit('update:modelValue', false);
+};
+
+// 提交
+const handleSubmit = async () => {
+  try {
+    // 验证表单
+    await formRef.value?.validate();
+
+    // 验证是否有发货商品
+    const deliveryProducts = productList.value.filter((item) => item.deliverNum > 0);
+    if (deliveryProducts.length === 0) {
+      ElMessage.warning('请至少选择一个商品进行发货');
+      return;
+    }
+
+    submitLoading.value = true;
+
+    // 组装发货数据
+    const deliveryData: OrderDeliverForm = {
+      id: props.deliverId,
+      orderId: form.orderId,
+      orderCode: form.orderCode,
+      logisticsCompanyId: form.logisticsCompanyId,
+      logisticsCompanyCode: form.logisticsCompanyCode,
+      logisticNo: form.logisticNo,
+      deliverMethod: form.deliverMethod,
+      deliverMan: form.deliverMan,
+      phone: form.phone,
+      consigneePhone: form.consigneePhone,
+      deliverRemark: form.deliverRemark
+    };
+
+    // 调用发货API
+    await updateOrderDeliver(deliveryData);
+
+    ElMessage.success('编辑成功');
+    emit('success');
+    emit('update:modelValue', false);
+  } catch (error) {
+    console.error('发货失败:', error);
+    if (error !== false) {
+      // 不是表单验证失败
+      ElMessage.error('发货失败,请重试');
+    }
+  } finally {
+    submitLoading.value = false;
+  }
+};
+
+// 组件挂载时加载物流公司列表
+onMounted(() => {
+  loadLogisticsCompanyList();
+});
+</script>
+
+<style scoped lang="scss">
+.mt-4 {
+  margin-top: 16px;
+}
+
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+</style>

+ 2 - 2
src/views/order/saleOrder/index.vue

@@ -623,12 +623,12 @@ const getButtonsByStatus = (orderStatus: string, checkStatus: string): ActionBut
   }
 
   // 发货完成或已完成:显示查看物流按钮
-  if (orderStatus === OrderStatus.SHIPMENT_COMPLETED || orderStatus === OrderStatus.COMPLETED) {
+  if (orderStatus === OrderStatus.PARTIAL_SHIPMENT || orderStatus === OrderStatus.SHIPMENT_COMPLETED || orderStatus === OrderStatus.COMPLETED) {
     buttons.push({ label: '查看物流', handler: handleViewLogistics });
   }
 
   // 非已取消和已关闭状态:显示取消订单按钮
-  if (orderStatus !== OrderStatus.CANCELLED && orderStatus !== OrderStatus.CLOSED) {
+  if (orderStatus !== OrderStatus.CANCELLED && orderStatus !== OrderStatus.CLOSED && orderStatus !== OrderStatus.SHIPMENT_COMPLETED) {
     buttons.push({ label: '取消订单', handler: handleCancel });
   }
 

+ 3 - 3
src/views/order/saleOrder/logisticsDetail.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-dialog v-model="visible" title="物流信息" width="700px" :before-close="handleClose">
+  <el-drawer v-model="visible" title="物流信息" size="38%" direction="rtl" :before-close="handleClose" :close-on-click-modal="true">
     <div class="logistics-detail">
       <div class="section-title">单号查询</div>
 
@@ -21,7 +21,7 @@
       <el-timeline v-if="logisticsInfo.length > 0">
         <el-timeline-item v-for="(item, index) in logisticsInfo" :key="index" :timestamp="item.time" placement="top">
           <div class="timeline-content">
-            <div class="timeline-status">{{ index }}</div>
+            <div class="timeline-status">{{ index + 1 }}</div>
             <div class="timeline-detail">
               <div>{{ item.time }}</div>
               <div>{{ item.location }}</div>
@@ -33,7 +33,7 @@
 
       <el-empty v-else description="暂无物流信息" />
     </div>
-  </el-dialog>
+  </el-drawer>
 </template>
 
 <script setup lang="ts">

+ 6 - 2
src/views/order/saleOrder/orderAffirm.vue

@@ -33,15 +33,19 @@
 
         <el-row :gutter="20">
           <!-- 第二行 -->
-          <el-col :span="8"> </el-col>
           <el-col :span="8">
             <el-form-item label="信用额度">
               <el-input v-model="orderInfo.creditLimit" placeholder="0" disabled />
             </el-form-item>
           </el-col>
+          <el-col :span="8">
+            <el-form-item label="临时额度">
+              <el-input v-model="orderInfo.temporaryQuota" placeholder="0" disabled />
+            </el-form-item>
+          </el-col>
           <el-col :span="8">
             <el-form-item label="剩余额度">
-              <el-input value="0" disabled />
+              <el-input v-model="orderInfo.remainingQuota" placeholder="0" disabled />
             </el-form-item>
           </el-col>
         </el-row>

+ 159 - 20
src/views/order/saleOrder/sendDetail.vue

@@ -36,7 +36,7 @@
           </div>
           <div class="detail-item">
             <span class="label">发票类型:</span>
-            <span>{{ invoiceTypeInfo.invoiceTypeNo || '--' }},{{ invoiceTypeInfo.invoiceTypeName || '--' }}</span>
+            <span>{{ orderDetail.invoiceType }}</span>
           </div>
           <div class="detail-item">
             <span class="label">发货仓库:</span>
@@ -154,32 +154,56 @@
       </el-table>
     </el-card>
     <!-- 发货对话框 -->
-    <DeliverDialog v-model="showDeliverDialog" :order-id="currentOrderId" :order-no="currentOrderNo" @success="handleDeliverSuccess" />
+    <DeliverDialog
+      v-model="showDeliverDialog"
+      :order-id="currentOrderId"
+      :order-no="currentOrderNo"
+      :operate-type="operateType"
+      @success="handleDeliverSuccess"
+    />
+
+    <!-- 编辑发货信息对话框 -->
+    <EditDeliverDialog v-model="showEditDeliverDialog" :deliver-id="editDeliverId" @success="handleDeliverSuccess" />
     <!-- 发货信息 -->
     <el-card shadow="never" class="mb-2" v-show="orderDetail.orderStatus != '0'">
       <template #header>
         <div class="card-header">
-          <span>发货信息:共{{ 0 }}个包裹</span>
-          <el-button type="primary" style="float: right" @click="handleAddDeliver(orderDetail)">添加发货信息</el-button>
+          <span>发货信息:共{{ orderDeliverList.length }}个包裹</span>
+          <el-button type="primary" v-if="orderDetail.orderStatus == '2' || orderDetail.orderStatus == '3'" @click="handleAddDeliver(orderDetail)"
+            >添加发货信息</el-button
+          >
         </div>
       </template>
-      <div v-show="totalQuantitySent > 0">
-        <div style="white-space: nowrap" class="mb-2">
-          <span style="margin-right: 16px">发货单号:--</span>
-          <span style="margin-right: 16px">发货时间:--</span>
-          <span style="margin-right: 16px">发货方式:--</span>
-          <span style="margin-right: 16px">送货人:--</span>
-          <span style="margin-right: 16px">手机:--</span>
-          <span style="margin-right: 16px">物流状态:--</span>
-          <span>发货备注:--</span>
+      <el-card v-for="deliver in orderDeliverList" :key="deliver.id">
+        <div class="mb-2" style="background: #f3f3f3; padding: 10px">
+          <el-row :gutter="20" justify="space-between" align="middle">
+            <el-col :span="18">
+              <div style="display: flex; flex-wrap: wrap; gap: 16px">
+                <span>发货单号:{{ (deliver as any).deliverCode || '--' }}</span>
+                <span>发货时间:{{ (deliver as any).createTime || '--' }}</span>
+                <span>发货方式:{{ getDictLabel(deliver_method, deliver.deliverMethod || '--') }}</span>
+                <span>送货人:{{ deliver.deliverMan || '--' }}</span>
+                <span>手机:{{ deliver.phone || '--' }}</span>
+                <span style="margin-left: 30px">物流状态:{{ deliver.logisticsStatus || '--' }}</span>
+                <span style="margin-left: 20px">发货备注:{{ deliver.deliverRemark || '--' }}</span>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div style="text-align: right">
+                <el-button type="primary" @click="handleEditDeliver(deliver)">编辑发货信息</el-button>
+                <el-button type="primary" v-if="deliver.deliverMethod == '0'" @click="handleEditLogistics(deliver)">变更物流状态</el-button>
+                <el-button type="primary" @click="handleViewLogistics(orderDetail)">查看物流</el-button>
+              </div>
+            </el-col>
+          </el-row>
         </div>
-        <el-table :data="deliverProductList" border style="width: 100%">
+        <el-table :data="deliver.deliverProductList" border style="width: 100%">
           <el-table-column label="产品编号" prop="productNo" align="center" />
           <el-table-column label="商品名称" prop="productName" align="center" />
           <el-table-column label="单位" prop="productUnit" align="center" />
           <el-table-column label="发货数量" prop="deliverNum" align="center" />
         </el-table>
-      </div>
+      </el-card>
     </el-card>
 
     <!-- A10备货信息 -->
@@ -207,18 +231,27 @@
     <div class="text-center mt-4">
       <el-button @click="goBack">返回</el-button>
     </div>
+    <!-- 物流详情对话框 -->
+    <LogisticsDetail v-model="showLogisticsDialog" :order-id="logisticsOrderId" />
+    <!-- 订单状态日志抽屉 -->
+    <AddOrderStatusLogDrawer ref="statusLogDrawerRef" />
   </div>
 </template>
 
 <script setup name="SendDetail" lang="ts">
 import { ref, computed, onMounted } from 'vue';
 import DeliverDialog from './deliverDialog.vue';
+import EditDeliverDialog from './editDeliverDialog.vue';
+import AddOrderStatusLogDrawer from './addOrderStatusLogDrawer.vue';
 import { useRoute, useRouter } from 'vue-router';
 import { getOrderMain } from '@/api/order/orderMain';
 import { OrderMainVO } from '@/api/order/orderMain/types';
 import { listOrderProduct } from '@/api/order/orderProduct';
 import { OrderProductVO } from '@/api/order/orderProduct/types';
 import { DeliverProductVO } from '@/api/order/deliverProduct/types';
+import { listOrderDeliver } from '@/api/order/orderDeliver';
+import { OrderDeliverVO } from '@/api/order/orderDeliver/types';
+import { listDeliverProduct } from '@/api/order/deliverProduct';
 import { getShippingAddress } from '@/api/customer/customerFile/shippingAddress';
 import { ShippingAddressVO } from '@/api/customer/customerFile/shippingAddress/types';
 import { getWarehouse } from '@/api/company/warehouse';
@@ -229,10 +262,12 @@ import { getCustomerInfo } from '@/api/customer/customerFile/customerInfo';
 import { CustomerInfoVO } from '@/api/customer/customerFile/customerInfo/types';
 import { getInvoiceType } from '@/api/customer/invoiceType';
 import { InvoiceTypeVO } from '@/api/customer/invoiceType/types';
+import { selectNewOneLog } from '@/api/order/orderStatusLog';
 
+import LogisticsDetail from './logisticsDetail.vue';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
-const { order_status, payment_status, fee_type, pay_method } = toRefs<any>(
-  proxy?.useDict('order_status', 'payment_status', 'fee_type', 'pay_method')
+const { order_status, payment_status, fee_type, pay_method, deliver_method } = toRefs<any>(
+  proxy?.useDict('order_status', 'payment_status', 'fee_type', 'pay_method', 'deliver_method')
 );
 const route = useRoute();
 const router = useRouter();
@@ -255,16 +290,30 @@ const invoiceTypeInfo = ref<InvoiceTypeVO>({} as InvoiceTypeVO);
 // 商品明细列表
 const productList = ref<OrderProductVO[]>([]);
 
+// 发货包裹列表
+const orderDeliverList = ref<OrderDeliverVO[]>([]);
 const deliverProductList = ref<DeliverProductVO[]>([]);
 
 // 收货地址信息
 const shippingAddress = ref<ShippingAddressVO>({} as ShippingAddressVO);
 
+const operateType = ref('add');
+
 // 发货对话框
 const showDeliverDialog = ref(false);
 const currentOrderId = ref<string | number>();
 const currentOrderNo = ref<string>();
 
+// 编辑发货对话框
+const showEditDeliverDialog = ref(false);
+const editDeliverId = ref<string | number>();
+
+const showLogisticsDialog = ref(false);
+const logisticsOrderId = ref<string | number>();
+
+// 订单状态日志抽屉
+const statusLogDrawerRef = ref<any>(null);
+
 /** 发货成功回调 */
 const handleDeliverSuccess = () => {};
 
@@ -308,6 +357,11 @@ const getOrderDetail = async () => {
       await getDeliverProductList(orderDetail.value);
     }
 
+    // 获取发货单及关联的物流商品信息
+    if (orderDetail.value.id) {
+      await getOrderDeliverListData(orderDetail.value.id);
+    }
+
     // 获取收货地址
     if (orderDetail.value.shippingAddressId) {
       await getShippingAddressDetail(orderDetail.value.shippingAddressId);
@@ -329,9 +383,9 @@ const getOrderDetail = async () => {
     }
 
     // 获取发票类型信息
-    if (orderDetail.value.invoiceType) {
-      await getInvoiceTypeDetail(orderDetail.value.invoiceType);
-    }
+    // if (orderDetail.value.invoiceType) {
+    //   await getInvoiceTypeDetail(orderDetail.value.invoiceType);
+    // }
   } catch (error) {
     console.error('获取订单详情失败:', error);
     proxy?.$modal.msgError('获取订单详情失败');
@@ -344,11 +398,64 @@ const handleAddDeliver = (row?: OrderMainVO) => {
     proxy?.$modal.msgWarning('订单ID不能为空');
     return;
   }
+  operateType.value = 'add';
   currentOrderId.value = row.id;
   currentOrderNo.value = row.orderNo;
   showDeliverDialog.value = true;
 };
 
+/** 编辑发货信息操作 */
+const handleEditDeliver = (deliver: any) => {
+  if (!deliver?.id) {
+    proxy?.$modal.msgWarning('发货信息ID不能为空');
+    return;
+  }
+  editDeliverId.value = deliver.id;
+  showEditDeliverDialog.value = true;
+};
+
+const handleViewLogistics = (row?: OrderMainVO) => {
+  if (!row?.id) {
+    proxy?.$modal.msgWarning('订单ID不能为空');
+    return;
+  }
+  logisticsOrderId.value = row.id;
+
+  showLogisticsDialog.value = true;
+};
+
+/** 变更物流状态 */
+const handleEditLogistics = async (row?: any) => {
+  if (!row?.orderId) {
+    proxy?.$modal.msgWarning('订单ID不能为空');
+    return;
+  }
+
+  // 补充客户信息
+  row.customerId = orderDetail.value.customerId;
+  row.customerNo = orderDetail.value.customerCode;
+
+  let currentOrderStatus = '';
+
+  try {
+    const res = await selectNewOneLog({
+      orderId: row.orderId,
+      customerId: row.customerId,
+      logisticNos: row.deliverCode
+    });
+
+    if (res.code == 200 && res.data) {
+      row.images = res.data.images;
+      currentOrderStatus = res.data.statusName || '';
+    }
+  } catch (error) {
+    console.error('查询物流失败:', error);
+  }
+
+  // 4. 使用获取到的状态打开弹窗
+  statusLogDrawerRef.value?.openDrawer(row, currentOrderStatus);
+};
+
 // 获取商品明细列表
 const getProductList = async (orderDetail: OrderMainVO) => {
   try {
@@ -367,6 +474,31 @@ const getDeliverProductList = async (orderDetail: OrderMainVO) => {
   }
 };
 
+// 获取发货单列表
+const getOrderDeliverListData = async (orderId: string | number) => {
+  try {
+    const res = await listOrderDeliver({ orderId, pageNum: 1, pageSize: 100 });
+    const deliverList = (res as any).rows || res.data || [];
+
+    // 为每条发货记录获取商品明细
+    for (const deliver of deliverList) {
+      if (deliver.id) {
+        try {
+          const productRes = await listDeliverProduct({ deliverId: deliver.id, pageNum: 1, pageSize: 100 });
+          deliver.deliverProductList = (productRes as any).rows || productRes.data || [];
+        } catch (error) {
+          console.error(`获取发货单${deliver.id}的商品明细失败:`, error);
+          deliver.deliverProductList = [];
+        }
+      }
+    }
+
+    orderDeliverList.value = deliverList;
+  } catch (error) {
+    console.error('获取发货单列表失败:', error);
+  }
+};
+
 // 获取收货地址详情
 const getShippingAddressDetail = async (addressId: string | number) => {
   try {
@@ -496,6 +628,13 @@ onMounted(() => {
   flex: 1;
 }
 
+.card-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  min-height: 40px;
+}
+
 @media print {
   .el-button {
     display: none;

+ 45 - 14
src/views/partner/merchant/components/BankDialog.vue

@@ -1,51 +1,53 @@
 <template>
-  <el-dialog v-model="dialogVisible" title="银行账户管理" width="600px" append-to-body @close="handleClose">
-    <el-form ref="formRef" :model="formData" :rules="rules" label-width="120px">
+  <el-dialog v-model="dialogVisible" title="银行账户管理" width="850px" append-to-body @close="handleClose">
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
       <el-row :gutter="20">
         <el-col :span="12">
           <el-form-item label="开户名称" prop="account">
-            <el-input v-model="formData.account" placeholder="请输入" />
+            <el-input v-model="form.account" placeholder="请输入" />
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="财务登记号">
-            <el-input v-model="formData.registrationNumber" placeholder="请输入" />
+            <el-input v-model="form.registrationNumber" placeholder="请输入" />
           </el-form-item>
         </el-col>
       </el-row>
       <el-row :gutter="20">
         <el-col :span="12">
-          <el-form-item label="开户银行名称">
-            <el-input v-model="formData.accountBankName" placeholder="请输入" />
+          <el-form-item label="开户银行名称" prop="bankId">
+            <el-select v-model="form.bankId" placeholder="请选择" class="w-full" filterable @change="handleBankChange">
+              <el-option v-for="bank in bankList" :key="bank.id" :label="`${bank.bnId} , ${bank.bnName}`" :value="bank.id" />
+            </el-select>
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="银行账号">
-            <el-input v-model="formData.bankNumber" placeholder="请输入" />
+            <el-input v-model="form.bankNumber" placeholder="请输入" />
           </el-form-item>
         </el-col>
       </el-row>
       <el-row :gutter="20">
         <el-col :span="12">
           <el-form-item label="开户银行所在地">
-            <el-input v-model="formData.bankLocation" placeholder="请输入" />
+            <el-input v-model="form.bankLocation" placeholder="请输入" />
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="银行联行号">
-            <el-input v-model="formData.bankInterbankNumber" placeholder="请输入" />
+            <el-input v-model="form.bankInterbankNumber" placeholder="请输入" />
           </el-form-item>
         </el-col>
       </el-row>
       <el-row :gutter="20">
         <el-col :span="12">
           <el-form-item label="电话">
-            <el-input v-model="formData.phone" placeholder="请输入" />
+            <el-input v-model="form.phone" placeholder="请输入" />
           </el-form-item>
         </el-col>
         <el-col :span="12">
           <el-form-item label="地址">
-            <el-input v-model="formData.address" placeholder="请输入" />
+            <el-input v-model="form.address" placeholder="请输入" />
           </el-form-item>
         </el-col>
       </el-row>
@@ -58,9 +60,11 @@
 </template>
 
 <script setup lang="ts">
-import { ref, computed } from 'vue';
+import { ref, computed, watch } from 'vue';
 import type { PartnerBankForm } from '@/api/partner/bank/types';
-
+import { listBank } from '@/api/company/bank';
+import { BankVO } from '@/api/company/bank/types';
+const bankList = ref<BankVO[]>([]);
 interface Props {
   visible: boolean;
   formData: PartnerBankForm;
@@ -85,6 +89,21 @@ const rules = {
 };
 
 const formRef = ref();
+const form = ref<PartnerBankForm>({ ...props.formData });
+
+watch(
+  () => props.formData,
+  (newVal) => {
+    form.value = { ...newVal };
+  },
+  { deep: true }
+);
+
+const handleBankChange = (bankId: string | number) => {
+  const bank = bankList.value.find((b) => b.id === bankId);
+  form.value.accountBankName = bank?.bnName || '';
+  form.value.bankLocation = bank?.bnAddr || '';
+};
 
 const handleClose = () => {
   emit('update:visible', false);
@@ -94,8 +113,20 @@ const handleClose = () => {
 const handleSubmit = () => {
   formRef.value?.validate((valid: boolean) => {
     if (valid) {
-      emit('submit', props.formData);
+      emit('submit', form.value);
     }
   });
 };
+// 获取银行列表
+const getBankList = async () => {
+  try {
+    const res = await listBank({ pageNum: 1, pageSize: 9999, isShow: '0' });
+    bankList.value = res.rows || [];
+  } catch (error) {
+    console.error('获取银行列表失败', error);
+  }
+};
+onMounted(() => {
+  getBankList();
+});
 </script>

+ 1 - 1
src/views/partner/merchant/components/ContactDialog.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-dialog v-model="dialogVisible" title="联系人管理" width="600px" append-to-body @close="handleClose">
+  <el-dialog v-model="dialogVisible" title="联系人管理" width="800px" append-to-body @close="handleClose">
     <el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
       <el-row :gutter="20">
         <el-col :span="12">

+ 1 - 1
src/views/partner/merchant/components/QualificationDialog.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-dialog v-model="dialogVisible" title="资质管理" width="600px" append-to-body @close="handleClose">
+  <el-dialog v-model="dialogVisible" title="资质管理" width="800px" append-to-body @close="handleClose">
     <el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
       <el-row :gutter="20">
         <el-col :span="12">

+ 1 - 1
src/views/partner/merchant/components/UserDialog.vue

@@ -1,5 +1,5 @@
 <template>
-  <el-dialog v-model="dialogVisible" title="用户账号管理" width="600px" append-to-body @close="handleClose">
+  <el-dialog v-model="dialogVisible" title="用户账号管理" width="800px" append-to-body @close="handleClose">
     <el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
       <el-row :gutter="20">
         <el-col :span="12">

+ 1 - 1
src/views/partner/merchant/components/WarehouseDialog.vue

@@ -1,6 +1,6 @@
 <template>
   <!-- 仓库管理对话框组件 -->
-  <el-dialog v-model="dialogVisible" title="仓库管理" width="600px" append-to-body @close="handleClose">
+  <el-dialog v-model="dialogVisible" title="仓库管理" width="800px" append-to-body @close="handleClose">
     <el-form ref="formRef" :model="formData" :rules="rules" label-width="100px">
       <el-row :gutter="20">
         <el-col :span="12">

+ 3 - 0
src/views/partner/merchant/index.vue

@@ -743,6 +743,7 @@ const submitForm = () => {
               partnerId: currentPartnerId.value,
               account: itemAny.account,
               registrationNumber: itemAny.registrationNumber,
+              bankId: itemAny.bankId,
               accountBankName: itemAny.accountBankName,
               bankNumber: itemAny.bankNumber,
               bankLocation: itemAny.bankLocation,
@@ -759,6 +760,7 @@ const submitForm = () => {
               partnerId: currentPartnerId.value,
               account: itemAny.account,
               registrationNumber: itemAny.registrationNumber,
+              bankId: itemAny.bankId,
               accountBankName: itemAny.accountBankName,
               bankNumber: itemAny.bankNumber,
               bankLocation: itemAny.bankLocation,
@@ -1115,6 +1117,7 @@ const handleAddBank = () => {
     partnerId: 0,
     account: '',
     registrationNumber: '',
+    bankId: null,
     accountBankName: '',
     bankNumber: '',
     bankLocation: '',

+ 197 - 0
src/views/project/projectInfo/addEditDrawer.vue

@@ -0,0 +1,197 @@
+<template>
+  <el-drawer v-model="visible" :title="title" :size="'50%'" :before-close="handleClose" destroy-on-close :close-on-click-modal="true">
+    <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+      <el-form-item label="项目编号" prop="projectNo">
+        <el-input v-model="form.projectNo" placeholder="请输入项目编号" />
+      </el-form-item>
+      <el-form-item label="项目logo" prop="projectLogo">
+        <div class="logo-upload" @click="showFileSelector = true">
+          <img v-if="form.projectLogo" :src="form.projectLogo" alt="项目logo" class="logo-image" />
+          <div v-else class="logo-placeholder">
+            <el-icon :size="40"><Plus /></el-icon>
+            <span>点击上传</span>
+          </div>
+        </div>
+        <FileSelector v-model="showFileSelector" :allowed-types="[1]" @confirm="handleFileConfirm" />
+      </el-form-item>
+      <el-form-item label="项目名称" prop="projectName">
+        <el-input v-model="form.projectName" placeholder="请输入项目名称" />
+      </el-form-item>
+      <el-form-item label="负责人" prop="leaderId">
+        <el-select v-model="form.leaderId" placeholder="请选择负责人" filterable @change="handleLeaderChange">
+          <el-option v-for="staff in staffList" :key="staff.staffId" :label="staff.staffName" :value="staff.staffId" />
+        </el-select>
+      </el-form-item>
+      <el-form-item label="备注" prop="remark">
+        <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
+      </el-form-item>
+    </el-form>
+    <template #footer>
+      <div class="drawer-footer">
+        <el-button @click="handleClose">取 消</el-button>
+        <el-button :loading="buttonLoading" type="primary" @click="handleSubmit">确 定</el-button>
+      </div>
+    </template>
+  </el-drawer>
+</template>
+
+<script setup lang="ts">
+import { ref, watch, onMounted } from 'vue';
+import { ProjectInfoForm } from '@/api/project/projectInfo/types';
+import { addProjectInfo, updateProjectInfo } from '@/api/project/projectInfo';
+import FileSelector from '@/components/FileSelector/index.vue';
+import { ElMessage } from 'element-plus';
+import { Plus } from '@element-plus/icons-vue';
+import { listComStaff } from '@/api/company/comStaff';
+import { ComStaffVO } from '@/api/company/comStaff/types';
+
+interface Props {
+  modelValue: boolean;
+  title?: string;
+  formData?: ProjectInfoForm;
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: boolean): void;
+  (e: 'success'): void;
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  title: '添加项目信息'
+});
+
+const emit = defineEmits<Emits>();
+
+const formRef = ref();
+const buttonLoading = ref(false);
+const visible = ref(false);
+const showFileSelector = ref(false);
+const staffList = ref<ComStaffVO[]>([]);
+
+const initFormData: ProjectInfoForm = {
+  id: undefined,
+  projectNo: undefined,
+  projectLogo: undefined,
+  projectName: undefined,
+  projectType: undefined,
+  leaderId: undefined,
+  leaderName: undefined,
+  status: undefined,
+  remark: undefined
+};
+
+const form = ref<ProjectInfoForm>({ ...initFormData });
+
+const rules = {};
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    visible.value = val;
+    if (val && props.formData) {
+      form.value = { ...props.formData };
+    } else if (val) {
+      form.value = { ...initFormData };
+    }
+  }
+);
+
+watch(visible, (val) => {
+  emit('update:modelValue', val);
+});
+
+const handleClose = () => {
+  visible.value = false;
+  formRef.value?.resetFields();
+};
+
+const handleFileConfirm = (files: any[]) => {
+  if (files && files.length > 0) {
+    form.value.projectLogo = files[0].url;
+  }
+};
+
+const handleLeaderChange = (value: string | number) => {
+  const selectedStaff = staffList.value.find((staff) => staff.staffId === value);
+  if (selectedStaff) {
+    form.value.leaderName = selectedStaff.staffName;
+  }
+};
+
+const loadStaffList = async () => {
+  try {
+    const res = await listComStaff({ pageNum: 1, pageSize: 1000 });
+    staffList.value = res.rows || [];
+  } catch (error) {
+    console.error('加载员工列表失败:', error);
+  }
+};
+
+onMounted(() => {
+  loadStaffList();
+});
+
+const handleSubmit = async () => {
+  await formRef.value?.validate(async (valid: boolean) => {
+    if (valid) {
+      buttonLoading.value = true;
+      try {
+        if (form.value.id) {
+          await updateProjectInfo(form.value);
+        } else {
+          await addProjectInfo(form.value);
+        }
+        ElMessage.success('操作成功');
+        visible.value = false;
+        emit('success');
+      } finally {
+        buttonLoading.value = false;
+      }
+    }
+  });
+};
+</script>
+
+<style scoped lang="scss">
+.drawer-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 10px;
+}
+
+.logo-upload {
+  width: 100px;
+  height: 100px;
+  border-radius: 50%;
+  border: 2px dashed #d9d9d9;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  overflow: hidden;
+  transition: all 0.3s;
+
+  &:hover {
+    border-color: #409eff;
+  }
+
+  .logo-image {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+  }
+
+  .logo-placeholder {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    color: #8c939d;
+    font-size: 12px;
+
+    span {
+      margin-top: 8px;
+    }
+  }
+}
+</style>

+ 200 - 0
src/views/project/projectInfo/index.vue

@@ -0,0 +1,200 @@
+<template>
+  <div class="p-2">
+    <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
+      <div v-show="showSearch" class="mb-[10px]">
+        <el-card shadow="hover">
+          <el-form ref="queryFormRef" :model="queryParams" :inline="true">
+            <el-form-item label="项目编号" prop="projectNo">
+              <el-input v-model="queryParams.projectNo" placeholder="请输入项目编号" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+
+            <el-form-item label="项目名称" prop="projectName">
+              <el-input v-model="queryParams.projectName" placeholder="请输入项目名称" clearable @keyup.enter="handleQuery" />
+            </el-form-item>
+            <el-form-item label="提交时间" prop="createTime">
+              <el-date-picker
+                v-model="dateRange"
+                type="daterange"
+                range-separator="至"
+                start-placeholder="开始时间"
+                end-placeholder="结束时间"
+                value-format="YYYY-MM-DD"
+              />
+            </el-form-item>
+            <el-form-item>
+              <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+              <el-button icon="Refresh" @click="resetQuery">重置</el-button>
+            </el-form-item>
+          </el-form>
+        </el-card>
+      </div>
+    </transition>
+
+    <el-card shadow="never">
+      <template #header>
+        <el-row :gutter="10" class="mb8">
+          <el-col :span="1.5">
+            <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['customer:projectInfo:add']">新增</el-button>
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['customer:projectInfo:edit']"
+              >修改</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['customer:projectInfo:remove']"
+              >删除</el-button
+            >
+          </el-col>
+          <el-col :span="1.5">
+            <el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['customer:projectInfo:export']">导出</el-button>
+          </el-col>
+          <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
+        </el-row>
+      </template>
+
+      <el-table v-loading="loading" border :data="projectInfoList" @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" align="center" />
+        <el-table-column label="项目编号" align="center" prop="projectNo" />
+        <el-table-column label="项目logo" align="center" prop="projectLogo" width="200">
+          <template #default="scope">
+            <el-image v-if="scope.row.projectLogo" :src="scope.row.projectLogo" style="width: 60px; height: 60px; border-radius: 50%" fit="cover" />
+            <span v-else>暂无图片</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="项目名称" align="center" prop="projectName" />
+        <!-- <el-table-column label="项目类型" align="center" prop="projectType" /> -->
+        <el-table-column label="负责人" align="center" prop="leaderName" />
+        <!-- <el-table-column label="状态" align="center" prop="status" /> -->
+        <el-table-column label="备注" align="center" prop="remark" />
+        <el-table-column label="创建时间" align="center" prop="createTime" />
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+          <template #default="scope">
+            <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['customer:projectInfo:edit']">编辑</el-button>
+            <el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['customer:projectInfo:remove']"
+              >删除</el-button
+            >
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
+    </el-card>
+    <!-- 添加或修改项目信息抽屉 -->
+    <AddEditDrawer v-model="drawerVisible" :title="drawerTitle" :form-data="currentFormData" @success="handleDrawerSuccess" />
+  </div>
+</template>
+
+<script setup name="ProjectInfo" lang="ts">
+import { listProjectInfo, getProjectInfo, delProjectInfo } from '@/api/project/projectInfo';
+import AddEditDrawer from './addEditDrawer.vue';
+import { ProjectInfoVO, ProjectInfoQuery, ProjectInfoForm } from '@/api/project/projectInfo/types';
+
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+
+const projectInfoList = ref<ProjectInfoVO[]>([]);
+const drawerVisible = ref(false);
+const drawerTitle = ref('');
+const currentFormData = ref<ProjectInfoForm>();
+const loading = ref(true);
+const showSearch = ref(true);
+const ids = ref<Array<string | number>>([]);
+const single = ref(true);
+const multiple = ref(true);
+const total = ref(0);
+
+const queryFormRef = ref<ElFormInstance>();
+
+const data = reactive<PageData<ProjectInfoForm, ProjectInfoQuery>>({
+  form: {},
+  queryParams: {
+    pageNum: 1,
+    pageSize: 10,
+    projectNo: undefined,
+    projectLogo: undefined,
+    projectName: undefined,
+    projectType: undefined,
+    leaderId: undefined,
+    leaderName: undefined,
+    status: undefined,
+    platformCode: undefined,
+    params: {}
+  },
+  rules: {}
+});
+
+const { queryParams } = toRefs(data);
+const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
+/** 查询项目信息列表 */
+const getList = async () => {
+  loading.value = true;
+  const res = await listProjectInfo(proxy?.addDateRange(queryParams.value, dateRange.value));
+  projectInfoList.value = res.rows;
+  total.value = res.total;
+  loading.value = false;
+};
+
+/** 抽屉成功回调 */
+const handleDrawerSuccess = () => {
+  getList();
+};
+
+/** 搜索按钮操作 */
+const handleQuery = () => {
+  queryParams.value.pageNum = 1;
+  getList();
+};
+
+/** 重置按钮操作 */
+const resetQuery = () => {
+  queryFormRef.value?.resetFields();
+  handleQuery();
+};
+
+/** 多选框选中数据 */
+const handleSelectionChange = (selection: ProjectInfoVO[]) => {
+  ids.value = selection.map((item) => item.id);
+  single.value = selection.length != 1;
+  multiple.value = !selection.length;
+};
+
+/** 新增按钮操作 */
+const handleAdd = () => {
+  currentFormData.value = undefined;
+  drawerTitle.value = '添加项目信息';
+  drawerVisible.value = true;
+};
+
+/** 修改按钮操作 */
+const handleUpdate = async (row?: ProjectInfoVO) => {
+  const _id = row?.id || ids.value[0];
+  const res = await getProjectInfo(_id);
+  currentFormData.value = res.data;
+  drawerTitle.value = '修改项目信息';
+  drawerVisible.value = true;
+};
+
+/** 删除按钮操作 */
+const handleDelete = async (row?: ProjectInfoVO) => {
+  const _ids = row?.id || ids.value;
+  await proxy?.$modal.confirm('是否确认删除项目信息编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
+  await delProjectInfo(_ids);
+  proxy?.$modal.msgSuccess('删除成功');
+  await getList();
+};
+
+/** 导出按钮操作 */
+const handleExport = () => {
+  proxy?.download(
+    'customer/projectInfo/export',
+    {
+      ...queryParams.value
+    },
+    `projectInfo_${new Date().getTime()}.xlsx`
+  );
+};
+
+onMounted(() => {
+  getList();
+});
+</script>