Kaynağa Gözat

Merge branch 'hurx'

hurx 3 hafta önce
ebeveyn
işleme
0094ca477c

+ 31 - 1
src/api/pc/enterprise/order.ts

@@ -1,5 +1,13 @@
 import request from '@/utils/request';
-import { OrderMain, OrderProduct, OrderStatusStats, OrderCustomerFlowSaveBo, OrderCustomerFlowLinkBo, OrderCustomerFlow, OrderCustomerFlowNodeLink } from './orderTypes';
+import {
+  OrderMain,
+  OrderProduct,
+  OrderStatusStats,
+  OrderCustomerFlowSaveBo,
+  OrderCustomerFlowLinkBo,
+  OrderCustomerFlow,
+  OrderCustomerFlowNodeLink
+} from './orderTypes';
 
 // ==================== 订单管理 ====================
 
@@ -275,3 +283,25 @@ export function getOrderFlowNodes(orderId: number | string) {
     method: 'get'
   });
 }
+
+/**
+ * 查询订单的发货信息
+ */
+export function selectOrderDeliverByOrderId(params?: any) {
+  return request({
+    url: '/order/pcOrderDeliver/selectOrderDeliverByOrderId',
+    method: 'get',
+    params: params
+  });
+}
+
+/**
+ * 查询订单的物流信息
+ */
+export function queryTrack(params?: any) {
+  return request({
+    url: '/order/pcOrderDeliver/queryTrack',
+    method: 'get',
+    params: params
+  });
+}

+ 22 - 6
src/views/order/orderEvaluation/evaluation.vue

@@ -56,7 +56,14 @@
           </div>
           <div class="content-row">
             <span class="field-label">评价晒单</span>
-            <el-input v-model="item.content" type="textarea" :rows="4" placeholder="请输入评价内容" maxlength="200" :disabled="evaluateForm.evaluationType === 3" />
+            <el-input
+              v-model="item.content"
+              type="textarea"
+              :rows="4"
+              placeholder="请输入评价内容"
+              maxlength="200"
+              :disabled="evaluateForm.evaluationType === 3"
+            />
           </div>
           <div class="upload-row" v-if="evaluateForm.evaluationType !== 3">
             <el-upload
@@ -75,15 +82,15 @@
             </el-upload>
             <div v-if="item.images && item.images.length > 0" class="image-list">
               <div v-for="(img, imgIndex) in item.images" :key="imgIndex" class="image-item">
-                <el-image :src="img" fit="cover" style="width: 100px; height: 100px; border-radius: 6px;" />
-                <el-icon class="delete-icon" @click="handleDeleteImage(index, imgIndex)"><Close /></el-icon>
+                <el-image :src="img" fit="cover" style="width: 100px; height: 100px; border-radius: 6px" />
+                <el-icon class="delete-icon" @click="handleDeleteImage(index, imgIndex as any)"><Close /></el-icon>
               </div>
             </div>
           </div>
           <div class="upload-row" v-else>
             <div v-if="item.images && item.images.length > 0" class="image-list">
               <div v-for="(img, imgIndex) in item.images" :key="imgIndex" class="image-item">
-                <el-image :src="img" fit="cover" style="width: 100px; height: 100px; border-radius: 6px;" />
+                <el-image :src="img" fit="cover" style="width: 100px; height: 100px; border-radius: 6px" />
               </div>
             </div>
           </div>
@@ -430,7 +437,14 @@ onMounted(async () => {
         line-height: 32px;
       }
       .upload-row {
-        padding-left: 65px;
+        // 关键修改:启用 Flex 布局,让上传框和图片列表在同一行
+        display: flex;
+        align-items: flex-start; // 顶部对齐
+        gap: 10px; // 上传框和图片之间的间距
+        padding-left: 65px; // 保持原有的左侧缩进
+
+        // 如果图片很多,允许换行,但默认是在同一行开始
+        flex-wrap: wrap;
       }
     }
   }
@@ -477,12 +491,14 @@ onMounted(async () => {
   display: flex;
   gap: 10px;
   flex-wrap: wrap;
-  margin-top: 10px;
+  margin-top: 0;
 
   .image-item {
     position: relative;
     width: 100px;
     height: 100px;
+    // 防止图片项被压缩
+    flex-shrink: 0;
 
     .delete-icon {
       position: absolute;

+ 35 - 11
src/views/order/orderEvaluation/index.vue

@@ -54,7 +54,7 @@
               <span style="margin-right: 5px; margin-bottom: 2px">订单详情</span>
               <el-icon><ArrowRight /></el-icon>
             </div> -->
-            <div class="open-btn" v-if="order.products && order.products.length > 1" @click="order.expanded = !order.expanded">
+            <div class="open-btn" v-if="order.products && order.products.length > 5" @click="order.expanded = !order.expanded">
               <span style="margin-right: 5px"> {{ order.expanded ? '收起' : '展开' }}</span>
               <el-icon v-if="order.expanded"><ArrowUp /></el-icon>
               <el-icon v-else><ArrowDown /></el-icon>
@@ -62,18 +62,20 @@
           </div>
         </div>
         <div class="product-list">
+          <!-- 如果已展开 (expanded=true),显示全部;否则显示前 5 个 (slice(0, 5)) -->
           <div
-            v-for="(product, productIndex) in order.expanded ? order.products : order.products.slice(0, 1)"
-            :key="productIndex"
+            v-for="(product, productIndex) in order.expanded ? order.products : order.products.slice(0, 5)"
+            :key="product.id || productIndex"
             class="product-row"
           >
             <div class="product-info-cell">
               <div class="product-image">
                 <el-image :src="product.image" fit="contain">
-                  <template #error
-                    ><div class="image-placeholder">
-                      <el-icon :size="30" color="#ccc"><Picture /></el-icon></div
-                  ></template>
+                  <template #error>
+                    <div class="image-placeholder">
+                      <el-icon :size="30" color="#ccc"><Picture /></el-icon>
+                    </div>
+                  </template>
                 </el-image>
               </div>
               <div class="product-detail">
@@ -83,6 +85,8 @@
               </div>
               <div class="product-quantity">x{{ product.quantity }}</div>
             </div>
+
+            <!-- 金额、状态、操作列只在第一行显示 (保持原有逻辑) -->
             <div class="amount-cell" v-if="productIndex === 0">
               <div class="amount-info">
                 <span class="label">支付款:</span><span class="value highlight">¥{{ order.payAmount }}</span>
@@ -100,10 +104,18 @@
               <el-button v-if="activeMainTab === '2'" type="primary" link @click="handleViewEvaluation(order)">查看评价</el-button>
             </div>
           </div>
-          <!-- 显示更多商品提示 -->
-          <div v-if="!order.expanded && order.products.length > 1" class="more-products-hint">
-            该订单共 {{ order.products.length }} 件商品,点击展开查看全部
+
+          <!--  只有当商品总数大于 5 时,才显示展开/收起按钮和提示 -->
+          <div v-if="order.products.length > 5" class="more-products-hint" @click="order.expanded = !order.expanded">
+            <span style="cursor: pointer; color: #409eff">
+              {{ order.expanded ? '收起' : `该订单共 ${order.products.length} 件商品,点击展开查看剩余 ${order.products.length - 5} 件` }}
+            </span>
+            <el-icon style="vertical-align: middle; margin-left: 5px">
+              <component :is="order.expanded ? 'ArrowUp' : 'ArrowDown'" />
+            </el-icon>
           </div>
+
+          <!-- 如果商品数在 1-5 之间,不需要显示任何提示,也不需要展开按钮 -->
         </div>
       </div>
       <el-empty v-if="orderList.length === 0" description="暂无订单" />
@@ -114,7 +126,7 @@
 <script setup lang="ts">
 import { ref, reactive, computed, onMounted, getCurrentInstance, toRefs, ComponentInternalInstance, watch } from 'vue';
 import { useRouter } from 'vue-router';
-import { Search, Edit, ChatDotRound, Document, ArrowRight, Picture, Plus } from '@element-plus/icons-vue';
+import { Search, Edit, ChatDotRound, Document, ArrowRight, Picture, Plus, ArrowUp, ArrowDown } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
 import { PageTitle, StatusTabs } from '@/components';
 import { getOrderList, getOrderProducts, getEvalutionList } from '@/api/pc/enterprise/order';
@@ -476,6 +488,18 @@ onMounted(() => {
       color: #666;
       text-align: center;
       border-top: 1px solid #f0f0f0;
+      cursor: pointer; // 添加鼠标指针样式
+      transition: background 0.3s;
+
+      &:hover {
+        background: #eef1f6; // 悬停时背景微变
+        color: #409eff;
+      }
+
+      // 如果里面包含 span,确保 span 继承颜色
+      span {
+        color: inherit;
+      }
     }
   }
 }

+ 221 - 0
src/views/order/orderManage/auditDetail.vue

@@ -0,0 +1,221 @@
+<template>
+  <el-dialog v-model="visible" title="审批流" width="650px" @close="handleClose">
+    <div class="audit-container">
+      <div class="timeline-container">
+        <el-timeline>
+          <el-timeline-item v-for="(step, index) in progressSteps" :key="index" placement="top" :color="getStepColor(step, index)">
+            <template #dot>
+              <el-icon :size="20" :color="getStepColor(step, index)" style="margin-left: 2px">
+                <component :is="step.icon" />
+              </el-icon>
+            </template>
+            <div class="step-title">{{ step.title }}</div>
+            <div class="step-desc">{{ step.desc }}</div>
+            <div v-if="index <= currentStep" class="step-time">{{ step.time }}</div>
+          </el-timeline-item>
+        </el-timeline>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, watch } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import { ArrowLeft, Document, User, CircleCheck, Picture } from '@element-plus/icons-vue';
+import { getOrderFlowNodes, getOrderInfo } from '@/api/pc/enterprise/order';
+import { getContactInfo } from '@/api/pc/organization/index';
+import type { OrderCustomerFlowNodeLink } from '@/api/pc/enterprise/orderTypes';
+import { ElMessage } from 'element-plus';
+
+const props = defineProps<{
+  modelValue: boolean;
+  orderId?: string | number;
+}>();
+
+const emit = defineEmits(['update:modelValue']);
+
+const router = useRouter();
+const route = useRoute();
+const visible = ref(false);
+
+const getStepColor = (step: any, index: number) => {
+  if (index <= currentStep.value) {
+    return '#67C23A';
+  }
+  return '#C0C4CC';
+};
+
+const handleClose = () => {
+  visible.value = false;
+};
+
+// 格式化时间为 "2026/3/17 上午10:49" 格式
+const formatTime = (timeStr: string): string => {
+  if (!timeStr) return '';
+  const date = new Date(timeStr);
+  if (isNaN(date.getTime())) return timeStr;
+  return date.toLocaleString('zh-CN', {
+    year: 'numeric',
+    month: 'numeric',
+    day: 'numeric',
+    hour: 'numeric',
+    minute: '2-digit',
+    hour12: true
+  });
+};
+const auditOrderId = ref<any>(0);
+const currentStep = ref(1);
+const loading = ref(false);
+
+const progressSteps = ref<{ title: string; icon: any; desc: string; time: string; reviewStatus?: number }[]>([
+  { title: '提交订单', icon: Document, desc: '订单已提交', time: '', reviewStatus: 2 },
+  { title: '完成', icon: CircleCheck, desc: '交易完成', time: '', reviewStatus: 0 }
+]);
+
+// 订单时间信息(用于流程节点时间显示)
+const orderTimeInfo = reactive({
+  createTime: '', // 订单创建时间(提交订单节点)
+  updateTime: '' // 订单更新时间(完成节点)
+});
+
+// 根据 handlerId(逗号分隔的多个ID)解析审批人名称
+// 单人:返回姓名;多人:返回"xx或xx审核"
+const resolveHandlerName = async (handlerId: string): Promise<string> => {
+  if (!handlerId) return '';
+  const ids = handlerId
+    .split(',')
+    .map((s) => s.trim())
+    .filter(Boolean);
+  if (ids.length === 0) return '';
+  try {
+    const results = await Promise.all(ids.map((id) => getContactInfo(String(id)).catch(() => null)));
+    const names = results.map((res: any) => res?.data?.contactName || '').filter(Boolean);
+    if (names.length === 0) return '';
+    if (names.length === 1) return names[0];
+    return names.join('或') + '审核';
+  } catch {
+    return '';
+  }
+};
+// 加载订单详情
+const loadOrderDetail = async () => {
+  try {
+    loading.value = true;
+    const res = await getOrderInfo(auditOrderId.value);
+    if (res.code === 200 && res.data) {
+      const order = res.data;
+
+      // 保存订单时间信息(用于流程节点时间显示)
+      orderTimeInfo.createTime = order.createTime || '';
+      orderTimeInfo.updateTime = order.updateTime || '';
+    }
+  } catch (error) {
+    ElMessage.error('加载订单详情失败');
+  } finally {
+    loading.value = false;
+  }
+};
+
+// 加载审批流程节点
+const loadFlowNodes = async () => {
+  try {
+    const res = (await getOrderFlowNodes(auditOrderId.value)) as any;
+    if (res.code === 200) {
+      const apiNodes: OrderCustomerFlowNodeLink[] = res.data || [];
+
+      // 提交订单节点:显示订单的 createTime
+      const steps: { title: string; icon: any; desc: string; time: string; reviewStatus?: number }[] = [
+        { title: '提交订单', icon: Document, desc: '订单已提交', time: formatTime(orderTimeInfo.createTime), reviewStatus: 2 }
+      ];
+
+      // 并行解析所有节点的审批人名称
+      const handlerNames = await Promise.all(apiNodes.map((node) => resolveHandlerName(node.handlerId || node.handlerName || '')));
+
+      apiNodes.forEach((node, idx) => {
+        steps.push({
+          title: node.nodeName || '审批',
+          icon: User,
+          desc: handlerNames[idx] || '',
+          time: formatTime(node.updateTime || ''),
+          reviewStatus: node.reviewStatus ?? 0
+        });
+      });
+
+      // 完成节点:显示订单的 updateTime
+      steps.push({ title: '完成', icon: CircleCheck, desc: '交易完成', time: formatTime(orderTimeInfo.updateTime), reviewStatus: 0 });
+
+      progressSteps.value = steps;
+
+      // 计算当前步骤:找最后一个已处理节点的索引(reviewStatus > 0)
+      let lastActiveIndex = 0;
+      steps.forEach((step, idx) => {
+        if (step.reviewStatus && step.reviewStatus > 0) {
+          lastActiveIndex = idx;
+        }
+      });
+      // 若所有中间节点均已完成(reviewStatus === 2),则激活结束节点
+      const middleNodes = steps.slice(1, steps.length - 1);
+      if (middleNodes.length > 0 && middleNodes.every((s) => s.reviewStatus === 2)) {
+        lastActiveIndex = steps.length - 1;
+      }
+      currentStep.value = lastActiveIndex;
+    }
+  } catch (error) {
+    console.error('加载流程节点失败', error);
+  }
+};
+
+watch(
+  () => props.modelValue,
+  async (val) => {
+    visible.value = val;
+    if (val && props.orderId) {
+      auditOrderId.value = props.orderId;
+      // 先加载订单详情(获取 createTime/updateTime),再加载流程节点
+      await loadOrderDetail();
+      await loadFlowNodes();
+    }
+  }
+);
+
+watch(visible, (val) => {
+  emit('update:modelValue', val);
+});
+</script>
+
+<style scoped lang="scss">
+.audit-container {
+  .timeline-container {
+    max-height: 500px;
+    margin-left: 20px;
+    overflow-y: auto;
+    padding-right: 10px;
+
+    :deep(.el-timeline) {
+      padding-left: 0;
+    }
+
+    :deep(.el-timeline-item__timestamp) {
+      font-size: 12px;
+      color: #999;
+      margin-bottom: 4px;
+    }
+
+    .step-title {
+      margin-left: 10px;
+      font-size: 14px;
+      font-weight: 500;
+      color: #333;
+      margin-bottom: 4px;
+    }
+
+    .step-desc {
+      margin-left: 10px;
+      font-size: 13px;
+      color: #666;
+      line-height: 1.6;
+    }
+  }
+}
+</style>

+ 159 - 71
src/views/order/orderManage/index.vue

@@ -18,32 +18,32 @@
           style="width: 260px; margin-left: 10px"
         />
       </div>
+
+      <el-tree-select
+        v-model="queryParams.department"
+        style="width: 160px"
+        :data="deptList"
+        :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
+        value-key="deptId"
+        placeholder="下单部门"
+        check-strictly
+        :render-after-expand="false"
+        clearable
+      >
+      </el-tree-select>
+      <el-select v-model="queryParams.status" placeholder="状态" style="width: 160px; margin-left: 10px" clearable>
+        <el-option v-for="dict in order_status" :key="dict.value" :label="dict.label" :value="dict.value" />
+      </el-select>
+      <!-- <el-select v-model="queryParams.payType" placeholder="支付方式" style="width: 160px; margin-left: 10px" clearable
+        ><el-option v-for="dict in pay_method" :key="dict.value" :label="dict.label" :value="dict.value" />
+      </el-select> -->
+
       <div>
         <el-button type="primary" @click="handleQuery">搜索</el-button>
         <el-button @click="handleReset">重置</el-button>
       </div>
     </div>
     <div class="flex-row-between" style="margin-top: 10px">
-      <div class="flex-row-start">
-        <el-tree-select
-          v-model="queryParams.department"
-          style="width: 160px"
-          :data="deptList"
-          :props="{ value: 'deptId', label: 'deptName', children: 'children' }"
-          value-key="deptId"
-          placeholder="下单部门"
-          check-strictly
-          :render-after-expand="false"
-          clearable
-        >
-        </el-tree-select>
-        <el-select v-model="queryParams.status" placeholder="状态" style="width: 160px; margin-left: 10px" clearable>
-          <el-option v-for="dict in order_status" :key="dict.value" :label="dict.label" :value="dict.value" />
-        </el-select>
-        <el-select v-model="queryParams.payType" placeholder="支付方式" style="width: 160px; margin-left: 10px" clearable
-          ><el-option v-for="dict in pay_method" :key="dict.value" :label="dict.label" :value="dict.value" />
-        </el-select>
-      </div>
       <div class="flex-row-start">
         <el-dropdown>
           <el-button
@@ -117,7 +117,9 @@
                 <div class="product-price">¥{{ item.price }}</div>
                 <el-button size="small" @click="handleAddCart(item)">加入购物车</el-button>
               </div>
-              <div class="product-quantity">x{{ item.quantity }}</div>
+            </div>
+            <div class="quantity-cell">
+              <span class="quantity-text">x{{ item.quantity }}</span>
             </div>
             <div class="amount-cell" v-if="itemIndex === 0">
               <div class="amount-info">
@@ -127,15 +129,37 @@
                 <span class="label">含运费:</span><span class="value">¥{{ order.freight }}</span>
               </div>
             </div>
+            <div v-else style="width: 200px"></div>
             <div class="status-cell" v-if="itemIndex === 0">
-              <span class="status-text" :style="{ color: getStatusColor(order.status) }">{{ order.statusText }}</span>
-              <span v-if="order.auditStatus" :class="['audit-status', getAuditStatusClass(order.auditStatus)]">{{
-                order.auditStatus == '0' ? '待审批' : order.auditStatus == '1' ? '' : '审批驳回'
-              }}</span>
-              <!-- <el-button type="primary" link size="small" @click="handleViewDetail(order)">查看订单轨迹</el-button> -->
-              <el-button v-if="order.fileCount" type="primary" link size="small">审核文件({{ order.fileCount }})</el-button>
+              <div class="status-info">
+                <span
+                  class="status-text"
+                  :class="{ 'clickable': order.statusText === '待支付' }"
+                  :style="{ color: getStatusColor(order.status) }"
+                  @click="order.statusText === '待支付' && handlePayment(order)"
+                  >{{ order.statusText }}</span
+                >
+                <span v-if="order.auditStatus" :class="['audit-status', getAuditStatusClass(order.auditStatus), 'clickable-audit']">{{
+                  order.auditStatus == '0' ? '待审批' : order.auditStatus == '1' ? '' : '审批驳回'
+                }}</span>
+              </div>
+              <div class="action-buttons">
+                <el-button type="primary" v-if="order.statusText == '发货完成'" link @click="handleConfirmReceipt(order)">确认收货</el-button>
+                <el-button type="primary" v-if="order.statusText == '已完成' && order.evaluationStatus == '0'" link @click="handleEvaluation(order)"
+                  >评价</el-button
+                >
+                <el-button
+                  type="primary"
+                  v-if="order.statusText == '部分发货' || order.statusText == '发货完成' || order.statusText == '已完成'"
+                  link
+                  @click="handleViewLogistics(order)"
+                  >查看物流</el-button
+                >
+                <el-button type="primary" v-if="order.auditStatus != '0'" link @click="handleViewAudit(order)">查看审批流</el-button>
+                <el-button v-if="order.fileCount" type="primary" link size="small">审核文件({{ order.fileCount }})</el-button>
+              </div>
             </div>
-            <div v-else style="width: 340px"></div>
+            <div v-else style="width: 180px"></div>
             <!-- <div class="product-cell action-cell" v-if="itemIndex === 0">
               <el-button
                 v-for="action in getOrderActions(order)"
@@ -185,39 +209,11 @@
     <!-- 分页 -->
     <TablePagination v-model:page="queryParams.pageNum" v-model:page-size="queryParams.pageSize" :total="total" @change="fetchOrderList" />
 
-    <el-dialog v-model="evaluateDialogVisible" :title="evaluateDialogTitle" width="600px">
-      <div class="evaluate-product">
-        <div class="product-image">
-          <el-image :src="currentProduct?.image" fit="contain">
-            <template #error
-              ><div class="image-placeholder">
-                <el-icon :size="30" color="#ccc"><Picture /></el-icon></div
-            ></template>
-          </el-image>
-        </div>
-        <div class="product-info">
-          <div class="product-name">{{ currentProduct?.name }}</div>
-          <div class="product-spec">{{ currentProduct?.spec1 }} {{ currentProduct?.spec2 }}</div>
-        </div>
-      </div>
-      <el-form ref="evaluateFormRef" :model="evaluateForm" :rules="evaluateRules" label-width="80px">
-        <el-form-item label="商品评分" prop="deliverGoods">
-          <el-rate v-model="evaluateForm.deliverGoods" :colors="['#e60012', '#e60012', '#e60012']" />
-        </el-form-item>
-        <el-form-item label="评价内容" prop="content">
-          <el-input v-model="evaluateForm.content" type="textarea" :rows="4" placeholder="请输入评价内容" maxlength="200" show-word-limit />
-        </el-form-item>
-        <el-form-item label="上传图片">
-          <el-upload action="#" list-type="picture-card" :auto-upload="false" :limit="5">
-            <el-icon><Plus /></el-icon>
-          </el-upload>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <el-button @click="evaluateDialogVisible = false">取消</el-button>
-        <el-button type="danger" @click="handleSubmitEvaluate">提交评价</el-button>
-      </template>
-    </el-dialog>
+    <!-- 物流详情弹窗 -->
+    <LogisticsDetail v-model="showLogisticsDialog" :order-id="logisticsOrderId" />
+
+    <!-- 审批流弹窗 -->
+    <AuditDetail v-model="showAuditDialog" :order-id="auditOrderId" />
   </div>
 </template>
 
@@ -243,6 +239,8 @@ import { DeptInfo } from '@/api/pc/organization/types';
 import { addProductShoppingCart } from '@/api/goods/index';
 import { onPath } from '@/utils/siteConfig';
 import { parseTime } from '@/utils/ruoyi';
+import LogisticsDetail from './logisticsDetail.vue';
+import AuditDetail from './auditDetail.vue';
 
 // 格式化订单时间
 const formatOrderTime = (timeStr: string) => {
@@ -271,6 +269,12 @@ const evaluateFormRef = ref();
 const currentProduct = ref<any>(null);
 const currentOrder = ref<any>(null);
 
+const showLogisticsDialog = ref(false);
+const logisticsOrderId = ref<string | number>('');
+
+const showAuditDialog = ref(false);
+const auditOrderId = ref<string | number>('');
+
 const evaluateForm = reactive({ deliverGoods: 5, content: '', evaluationType: null });
 const evaluateRules = {
   deliverGoods: [{ required: true, message: '请选择评分', trigger: 'change' }],
@@ -414,14 +418,10 @@ const fetchOrderList = async () => {
       };
     }
 
-    console.log('发送到后端的参数:', params);
     const res = await getOrderList(params);
-    console.log('后端返回的数据:', res);
     if (res.code === 200) {
       // 调试:打印后端返回的第一条订单数据
       if (res.rows && res.rows.length > 0) {
-        console.log('后端���回的订单状态值:', res.rows[0].orderStatus);
-        console.log('完整的订单数据:', res.rows[0]);
       }
 
       // 获取订单商品数据
@@ -465,6 +465,7 @@ const fetchOrderList = async () => {
         statusText: getStatusText(order.orderStatus || ''),
         countdown: '',
         auditStatus: order.checkStatus,
+        evaluationStatus: order.evaluationStatus,
         fileCount: 0,
         checked: false,
         expanded: false,
@@ -594,9 +595,63 @@ const handleViewDetail = (order: any) => {
   router.push(`/order/orderManage/detail/${order.id}`);
 };
 
+const handleEvaluation = (order: any) => {
+  router.push({
+    path: '/order/orderEvaluation/evaluation',
+    query: {
+      orderId: order.id,
+      orderNo: order.orderNo,
+      orderTime: order.orderTime,
+      type: 1
+    },
+    state: {
+      products: JSON.stringify(order.products)
+    }
+  } as any);
+};
+
+/** 查看物流按钮操作 */
+const handleViewLogistics = (row?: any) => {
+  if (!row?.id) {
+    proxy?.$modal.msgWarning('订单ID不能为空');
+    return;
+  }
+  logisticsOrderId.value = row.id;
+
+  showLogisticsDialog.value = true;
+};
+
 const handleApplyAfter = (order: any) => {
   router.push(`/order/orderManage/applyAfter?orderId=${order.id}`);
 };
+
+const handlePayment = (order: any) => {
+  onPath('/payc?id=' + order.orderId);
+};
+
+const handleConfirmReceipt = async (order: any) => {
+  try {
+    const res = await batchConfirmation([order.id]);
+    if (res.code === 200) {
+      ElMessage.success('确认收货成功');
+      fetchOrderList();
+    }
+  } catch (error) {
+    if (error !== 'cancel') {
+      console.error('确认收货失败:', error);
+    }
+  }
+};
+
+const handleViewAudit = (order: any) => {
+  if (!order?.id) {
+    ElMessage.warning('订单ID不能为空');
+    return;
+  }
+  auditOrderId.value = order.id;
+  showAuditDialog.value = true;
+};
+
 const handleQuery = () => {
   queryParams.pageNum = 1;
   fetchOrderList();
@@ -869,14 +924,22 @@ onMounted(() => {
             margin-bottom: 5px;
           }
         }
-        .product-quantity {
-          font-size: 13px;
+      }
+      .quantity-cell {
+        width: 80px;
+        padding: 15px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        .quantity-text {
+          font-size: 14px;
           color: #666;
         }
       }
       .amount-cell {
         width: 200px;
         padding: 15px;
+        margin-top: 10px;
         .amount-info {
           margin-bottom: 5px;
           .label {
@@ -896,15 +959,27 @@ onMounted(() => {
         }
       }
       .status-cell {
-        width: 140px;
+        width: 180px;
         padding: 15px;
         display: flex;
-        align-items: flex-start;
-        justify-content: flex-end;
-        gap: 10px;
+        flex-direction: column;
+        align-items: flex-end;
+        gap: 8px;
+        .status-info {
+          display: flex;
+          align-items: center;
+          gap: 8px;
+        }
         .status-text {
           font-size: 14px;
           font-weight: 500;
+          &.clickable {
+            cursor: pointer;
+            text-decoration: underline;
+            &:hover {
+              opacity: 0.8;
+            }
+          }
         }
         .audit-status {
           font-size: 14px;
@@ -917,6 +992,19 @@ onMounted(() => {
           &.danger {
             color: #e60012;
           }
+          &.clickable-audit {
+            cursor: pointer;
+            &:hover {
+              text-decoration: underline;
+              opacity: 0.8;
+            }
+          }
+        }
+        .action-buttons {
+          display: flex;
+          flex-direction: column;
+          align-items: flex-end;
+          gap: 4px;
         }
       }
       .action-cell {

+ 197 - 0
src/views/order/orderManage/logisticsDetail.vue

@@ -0,0 +1,197 @@
+<template>
+  <el-dialog v-model="visible" title="物流信息" width="650px" @close="handleClose">
+    <div class="logistics-container">
+      <div class="section-title">单号查询</div>
+      <el-select v-model="selectedLogistics" placeholder="请选择物流单号" @change="handleLogisticNoChange" style="width: 100%">
+        <el-option
+          v-for="item in logisticsList"
+          :key="item.id"
+          :label="`${item.logisticNo},${getDictLabel(deliver_method, item.deliverMethod)}`"
+          :value="item.id"
+        />
+      </el-select>
+
+      <div class="section-title" style="margin-top: 20px">物流信息</div>
+      <div class="timeline-container">
+        <el-timeline>
+          <el-timeline-item
+            v-for="(item, index) in logisticsTrack"
+            :key="index"
+            :timestamp="item.time"
+            placement="top"
+            :color="index === 0 ? '#409EFF' : '#C0C4CC'"
+          >
+            <div class="track-number">{{ item.trackingNo }}</div>
+            <div class="track-content">{{ item.content }}</div>
+          </el-timeline-item>
+        </el-timeline>
+      </div>
+    </div>
+  </el-dialog>
+</template>
+
+<script setup lang="ts">
+import { ref, watch } from 'vue';
+const { proxy } = getCurrentInstance() as ComponentInternalInstance;
+const { deliver_method } = toRefs<any>(proxy?.useDict('deliver_method'));
+import { selectOrderDeliverByOrderId, queryTrack } from '@/api/pc/enterprise/order';
+
+interface TrackItem {
+  time: string;
+  trackingNo: string;
+  content: string;
+}
+
+const props = defineProps<{
+  modelValue: boolean;
+  orderId?: string | number;
+}>();
+
+const emit = defineEmits(['update:modelValue']);
+
+const visible = ref(false);
+const selectedLogistics = ref('');
+const logisticsList = ref<any[]>([]);
+const logisticsTrack = ref<TrackItem[]>([]);
+
+const getDictLabel = (dictOptions: any[], value: string) => {
+  if (!dictOptions || !value) return value;
+  const dict = dictOptions.find((item) => item.value === value);
+  return dict ? dict.label : value;
+};
+
+watch(
+  () => props.modelValue,
+  (val) => {
+    visible.value = val;
+    if (val && props.orderId) {
+      loadLogisticsData();
+    }
+  }
+);
+
+watch(visible, (val) => {
+  emit('update:modelValue', val);
+});
+
+watch(selectedLogistics, (val) => {
+  if (val) {
+    loadTrackData(val);
+  }
+});
+
+const loadLogisticsData = async () => {
+  if (!props.orderId) return;
+  try {
+    const res = await selectOrderDeliverByOrderId({ orderId: props.orderId });
+    console.log(res);
+
+    if (res.code === 200 && res.rows) {
+      logisticsList.value = res.rows;
+      if (logisticsList.value.length > 0) {
+        selectedLogistics.value = logisticsList.value[0].id as string;
+        handleLogisticNoChange(logisticsList.value[0].logisticNo);
+      }
+    }
+  } catch (error) {
+    console.error('获取物流单号失败:', error);
+  }
+};
+
+const handleLogisticNoChange = async (logisticNo: string) => {
+  const selected = logisticsList.value.find((item) => item.logisticNo === logisticNo);
+  if (!selected) return;
+
+  try {
+    const res = await queryTrack({ logisticNo: logisticNo });
+    if (res.status == 200 && res.data && res.data) {
+      logisticsTrack.value = res.data.map((item: any) => ({
+        time: item.time,
+        trackingNo: selected.logisticNo,
+        content: item.context
+      }));
+    } else {
+      logisticsTrack.value = [
+        {
+          time: (selected as any).createTime || '',
+          trackingNo: selected.orderCode ? `${selected.orderCode}` : '',
+          content: '已下单'
+        }
+      ];
+    }
+  } catch (error) {
+    console.error('查询物流轨迹失败:', error);
+    logisticsTrack.value = [];
+  }
+};
+
+const loadTrackData = async (logisticsId: string | number) => {
+  const selectedItem = logisticsList.value.find((item) => item.id === logisticsId);
+  if (!selectedItem) return;
+
+  try {
+    const res = await queryTrack({ logisticNo: selectedItem.logisticNo });
+    if (res.status == 200 && res.data && res.data) {
+      logisticsTrack.value = res.data.map((item: any) => ({
+        time: item.time,
+        trackingNo: selectedItem.logisticNo,
+        content: item.context
+      }));
+    } else {
+      logisticsTrack.value = [
+        {
+          time: (selectedItem as any).createTime || '',
+          trackingNo: selectedItem.orderCode ? `${selectedItem.orderCode}` : '',
+          content: '已下单'
+        }
+      ];
+    }
+  } catch (error) {
+    console.error('查询物流轨迹失败:', error);
+    logisticsTrack.value = [];
+  }
+};
+
+const handleClose = () => {
+  visible.value = false;
+};
+</script>
+
+<style scoped lang="scss">
+.logistics-container {
+  .section-title {
+    font-size: 14px;
+    font-weight: 500;
+    color: #333;
+    margin-bottom: 12px;
+  }
+
+  .timeline-container {
+    max-height: 500px;
+    overflow-y: auto;
+    padding-right: 10px;
+
+    :deep(.el-timeline) {
+      padding-left: 0;
+    }
+
+    :deep(.el-timeline-item__timestamp) {
+      font-size: 12px;
+      color: #999;
+      margin-bottom: 4px;
+    }
+
+    .track-number {
+      font-size: 12px;
+      color: #666;
+      margin-bottom: 4px;
+    }
+
+    .track-content {
+      font-size: 13px;
+      color: #333;
+      line-height: 1.6;
+    }
+  }
+}
+</style>

+ 164 - 17
src/views/organization/deptManage/index.vue

@@ -37,9 +37,9 @@
     </el-table>
 
     <!-- 新增/编辑部门弹窗 -->
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="500px" destroy-on-close>
-      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
-        <el-form-item label="上级部门">
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="700px" destroy-on-close>
+      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px">
+        <el-form-item label="上级部门" prop="parentId">
           <el-tree-select
             v-model="formData.parentId"
             :data="deptList"
@@ -54,19 +54,82 @@
         <el-form-item label="部门名称" prop="deptName">
           <el-input v-model="formData.deptName" placeholder="请输入部门名称" />
         </el-form-item>
-        <el-form-item label="状态" prop="status">
-          <el-radio-group v-model="formData.status">
-            <el-radio label="0">启用</el-radio>
-            <el-radio label="1">停用</el-radio>
-          </el-radio-group>
+        <el-form-item label="部门主管" prop="deptManage">
+          <el-select v-model="formData.deptManage" placeholder="请选择" clearable style="width: 100%">
+            <el-option v-for="contact in contactList" :key="contact.id" :label="contact.contactName" :value="contact.id" />
+          </el-select>
+        </el-form-item>
+        <el-row>
+          <el-col :span="12">
+            <el-form-item label="是否启用部门">
+              <el-radio-group v-model="formData.status">
+                <el-radio label="0">是</el-radio>
+                <el-radio label="1">否</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+          <el-col :span="12">
+            <el-form-item label="是否额度控制">
+              <el-radio-group v-model="formData.isLimit">
+                <el-radio label="0">是</el-radio>
+                <el-radio label="1">否</el-radio>
+              </el-radio-group>
+            </el-form-item>
+          </el-col>
+        </el-row>
+        <el-form-item label="选择年度" prop="selectYear">
+          <el-date-picker
+            v-model="formData.selectYear"
+            type="year"
+            placeholder="请选择年度"
+            value-format="YYYY"
+            style="width: 100%"
+            :disabled="formData.isLimit === '1'"
+          />
+        </el-form-item>
+        <div style="color: #999; font-size: 12px; margin-left: 120px; margin-bottom: 18px">
+          只能对当期年度进行额度设置,往期年度,显示最后一日数据,无法充值额度。
+        </div>
+        <el-form-item v-if="formData.isLimit === '0'" label="分项费用类型" prop="expenseTypeId">
+          <el-select v-model="formData.expenseTypeId" placeholder="请选择" style="width: 100%">
+            <el-option v-for="item in expenseTypeList" :key="item.id" :label="item.expenseName" :value="item.id" />
+          </el-select>
+          <!-- <el-button type="primary" link style="margin-left: 12px">查询其他分项费用情况</el-button> -->
+        </el-form-item>
+        <el-form-item label="现有额度(年)" prop="yearlyBudget">
+          <el-input v-model="formData.yearlyBudget" disabled />
+        </el-form-item>
+        <el-form-item label="已用额度(年)" prop="usedBudget">
+          <el-input v-model="formData.usedBudget" disabled />
+        </el-form-item>
+        <el-form-item label="剩余额度" prop="residueBudget">
+          <el-input v-model="formData.residueBudget" disabled />
         </el-form-item>
-        <el-form-item label="年度额度">
-          <el-input-number v-model="formData.yearlyBudget" :min="0" :precision="2" controls-position="right" style="width: 200px" />
+        <el-form-item v-if="formData.isLimit === '0'" label="充值额度" prop="recharge">
+          <el-input v-model="formData.recharge" style="width: 100%" />
         </el-form-item>
+        <el-form-item label="绑定状态">
+          <el-switch v-model="formData.bindStatus" :active-value="'0'" :inactive-value="'1'" />
+        </el-form-item>
+        <el-form-item v-if="formData.bindStatus === '0'" label="绑定地址" prop="bindAddress">
+          <el-radio-group v-model="formData.bindAddress" style="width: 100%">
+            <el-radio v-for="addr in addressList" :key="addr.id" :value="addr.id" style="display: block; margin-bottom: 10px">
+              {{ addr.provincialCityCountry }}-{{ addr.address }}-{{ addr.contactPhone }}
+            </el-radio>
+          </el-radio-group>
+        </el-form-item>
+        <div style="margin-left: 120px; margin-top: 10px">
+          <div style="font-weight: bold; margin-bottom: 8px">充值说明:</div>
+          <div style="color: #666; font-size: 13px; line-height: 1.8">
+            1. 需要给当前部门进行额度充值时,请在充值额度中填写"正数"即可。<br />
+            2. 需要给当前部门进行额度扣减时请在充值额度中填写"负数"即可,扣减额度不能高于剩余额度。<br />
+            3. 当现有额度为0时,则该部门的此项费用不能进行采购。
+          </div>
+        </div>
       </el-form>
       <template #footer>
         <el-button @click="dialogVisible = false">取消</el-button>
-        <el-button type="danger" @click="handleSubmit">确定</el-button>
+        <el-button type="primary" @click="handleSubmit">确定</el-button>
       </template>
     </el-dialog>
   </div>
@@ -76,8 +139,11 @@
 import { ref, reactive, computed, onMounted } from 'vue';
 import { ElMessage, ElMessageBox } from 'element-plus';
 import { PageTitle } from '@/components';
-import { getDeptTree, addDept, updateDept, deleteDept } from '@/api/pc/organization';
+import { getDeptTree, addDept, updateDept, deleteDept, getContactList } from '@/api/pc/organization';
+import { getAddressList } from '@/api/pc/enterprise';
+import { getItemExpenseList } from '@/api/pc/cost/itemExpense';
 import { DeptInfo } from '@/api/pc/organization/types';
+import { de } from 'element-plus/es/locale/index.mjs';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 
 const { sys_platform_yes_no } = toRefs<any>(proxy?.useDict('sys_platform_yes_no'));
@@ -91,18 +157,32 @@ const formData = reactive({
   deptId: undefined,
   deptName: '',
   parentId: undefined,
+  deptManage: undefined,
   status: '0',
-  yearlyBudget: 0,
+  isLimit: '1',
+  selectYear: new Date().getFullYear().toString(),
+  expenseTypeId: undefined,
+  yearlyBudget: '0.00',
+  usedBudget: '0.00',
+  residueBudget: '0.00',
+  recharge: undefined,
+  bindStatus: '1',
+  bindAddress: undefined,
   parentName: ''
 });
 
 const formRules = {
-  deptName: [{ required: true, message: '请输入部门名称', trigger: 'blur' }]
+  parentId: [{ required: true, message: '上级部门不能为空', trigger: 'change' }],
+  deptName: [{ required: true, message: '请输入部门名称', trigger: 'blur' }],
+  deptManage: [{ required: true, message: '部门主管不能为空', trigger: 'change' }]
 };
 
 const dialogTitle = computed(() => (editingRow.value ? '编辑部门' : '新增部门'));
 
 const deptList = ref([]);
+const contactList = ref([]);
+const addressList = ref([]);
+const expenseTypeList = ref([]);
 
 // 加载部门树
 const loadDeptTree = async () => {
@@ -130,8 +210,17 @@ const handleAdd = (parent: any) => {
   formData.deptId = undefined;
   formData.deptName = '';
   formData.parentId = parent ? parent.deptId : undefined;
+  formData.deptManage = undefined;
   formData.status = '0';
-  formData.yearlyBudget = 0;
+  formData.isLimit = '1';
+  formData.selectYear = new Date().getFullYear().toString();
+  formData.expenseTypeId = undefined;
+  formData.yearlyBudget = '0.00';
+  formData.usedBudget = '0.00';
+  formData.residueBudget = '0.00';
+  formData.recharge = undefined;
+  formData.bindStatus = '1';
+  formData.bindAddress = undefined;
   formData.parentName = parent ? parent.parentName : '';
   dialogVisible.value = true;
 };
@@ -142,8 +231,17 @@ const handleEdit = (row: any) => {
   formData.deptId = row.deptId;
   formData.deptName = row.deptName;
   formData.parentId = row.parentId == 100 ? undefined : row.parentId;
+  formData.deptManage = row.deptManage;
   formData.status = row.status || '0';
-  formData.yearlyBudget = row.yearlyBudget || 0;
+  formData.isLimit = row.isLimit || '1';
+  formData.selectYear = row.selectYear || new Date().getFullYear().toString();
+  formData.expenseTypeId = row.expenseTypeId;
+  formData.yearlyBudget = row.yearlyBudget || '0.00';
+  formData.usedBudget = row.usedBudget || '0.00';
+  formData.residueBudget = ((row.yearlyBudget || 0) - (row.usedBudget || 0)).toFixed(2);
+  formData.recharge = row.recharge;
+  formData.bindStatus = row.bindStatus || '1';
+  formData.bindAddress = row.bindAddress;
   formData.parentName = '';
   dialogVisible.value = true;
 };
@@ -171,11 +269,21 @@ const handleSubmit = async () => {
 
   try {
     const data = {
+      deptManage: formData.deptManage,
+      isLimit: formData.isLimit,
+      selectYear: formData.selectYear,
+      expenseTypeId: formData.expenseTypeId,
+      yearlyBudget: formData.yearlyBudget,
+      usedBudget: formData.usedBudget,
+      residueBudget: formData.residueBudget,
+      recharge: formData.recharge,
+      bindStatus: formData.bindStatus,
+      bindAddress: formData.bindAddress,
       deptId: formData.deptId,
       deptName: formData.deptName,
       parentId: formData.parentId,
       status: formData.status,
-      yearlyBudget: formData.yearlyBudget
+      parentName: parentRow.value ? parentRow.value.deptName : ''
     };
 
     if (editingRow.value) {
@@ -194,9 +302,48 @@ const handleSubmit = async () => {
   }
 };
 
+// 加载联系人列表
+const loadContactList = async () => {
+  try {
+    const res = await getContactList();
+    if (res.code === 200) {
+      contactList.value = res.rows || res.data || [];
+    }
+  } catch (error) {
+    console.error('获取联系人列表失败:', error);
+  }
+};
+
+// 加载收货地址列表
+const loadAddressList = async () => {
+  try {
+    const res = await getAddressList();
+    if (res.code === 200) {
+      addressList.value = res.rows || res.data || [];
+    }
+  } catch (error) {
+    console.error('获取收货地址列表失败:', error);
+  }
+};
+
+// 加载分项费用列表
+const loadExpenseTypeList = async () => {
+  try {
+    const res = await getItemExpenseList();
+    if (res.code === 200) {
+      expenseTypeList.value = res.rows || res.data || [];
+    }
+  } catch (error) {
+    console.error('获取分项费用列表失败:', error);
+  }
+};
+
 // 页面加载时获取部门树
 onMounted(() => {
   loadDeptTree();
+  loadContactList();
+  loadAddressList();
+  loadExpenseTypeList();
 });
 </script>
 

+ 22 - 12
src/views/payc/index.vue

@@ -30,14 +30,14 @@
       <div class="pay-for">
         <div class="pay-list flex-row-center">
           <img src="@/assets/images/pay/pay6.png" alt="" />
-          <div>暂存订单</div>
+          <div @click="onSubmit(9)">暂存订单</div>
         </div>
       </div>
     </div>
     <div class="pay-foot">
       <div class="foot-bos">
         <el-button class="bnt1" @click="handleCancelOrder">取消订单</el-button>
-        <el-button class="bnt2" type="primary" @click="onSubmit">提交订单</el-button>
+        <el-button class="bnt2" type="primary" @click="onSubmit(0)">提交订单</el-button>
       </div>
     </div>
   </div>
@@ -60,19 +60,29 @@ const getInfo = () => {
     if (res.code == 200) {
       totalAmount.value = res.data.totalAmount;
     }
-    console.log(res);
   });
 };
 
-const onSubmit = () => {
-  orderPay({
-    orderId: orderId.value,
-    payType: 0
-  }).then((res) => {
-    if (res.code == 200) {
-      onPath('/order/orderManage');
-    }
-  });
+const onSubmit = (way: number) => {
+  if (way == 9) {
+    orderPay({
+      orderId: orderId.value,
+      payType: way
+    }).then((res) => {
+      if (res.code == 200) {
+        onPath('/order/orderManage');
+      }
+    });
+  } else {
+    orderPay({
+      orderId: orderId.value,
+      payType: 0
+    }).then((res) => {
+      if (res.code == 200) {
+        onPath('/order/orderManage');
+      }
+    });
+  }
 };
 
 import { cancelOrder } from '@/api/pc/enterprise/order';