3 Комити a0183dce56 ... 925abdd1f0

Аутор SHA1 Порука Датум
  肖路 925abdd1f0 feat(config): 添加多域名配置支持 пре 4 недеља
  肖路 22b1cacd99 Merge remote-tracking branch 'origin/master' into master пре 4 недеља
  肖路 6eb41fec40 feat(order): 更新订单审核流程和审批节点显示功能 пре 4 недеља

+ 20 - 0
.env.development

@@ -37,3 +37,23 @@ VITE_APP_SSE = true
 
 # 是否开启多域名
 VITE_DOMAIN_NAME = true
+
+# 多域名配置
+VITE_DOMAIN_WWW = 'index.xiaoluwebsite.xyz'
+VITE_DOMAIN_B = 'b.xiaoluwebsite.xyz'
+VITE_DOMAIN_MRO = 'mro.xiaoluwebsite.xyz'
+VITE_DOMAIN_FULI = 'fuli.xiaoluwebsite.xyz'
+VITE_DOMAIN_REG = 'reg.xiaoluwebsite.xyz'
+VITE_DOMAIN_BREG = 'breg.xiaoluwebsite.xyz'
+VITE_DOMAIN_GREG = 'greg.xiaoluwebsite.xyz'
+VITE_DOMAIN_PASSPORT = 'pass.xiaoluwebsite.xyz'
+VITE_DOMAIN_SEARCH = 'search.xiaoluwebsite.xyz'
+VITE_DOMAIN_ITEM = 'item.xiaoluwebsite.xyz'
+VITE_DOMAIN_CART = 'cart.xiaoluwebsite.xyz'
+VITE_DOMAIN_TRAD = 'trad.xiaoluwebsite.xyz'
+VITE_DOMAIN_PAYC = 'payc.xiaoluwebsite.xyz'
+VITE_DOMAIN_ORDER = 'order.xiaoluwebsite.xyz'
+VITE_DOMAIN_PLAN = 'plan.xiaoluwebsite.xyz'
+VITE_DOMAIN_PLAN_INFO = 'planinfo.xiaoluwebsite.xyz'
+VITE_DOMAIN_I = 'i.xiaoluwebsite.xyz'
+VITE_DOMAIN_EASYBUV = 'easybuv.xiaoluwebsite.xyz'

+ 20 - 0
.env.production

@@ -40,3 +40,23 @@ VITE_APP_SSE = true
 
 # 是否开启多域名
 VITE_DOMAIN_NAME = true
+
+# 多域名配置
+VITE_DOMAIN_WWW = 'index.xiaoluwebsite.xyz'
+VITE_DOMAIN_B = 'b.xiaoluwebsite.xyz'
+VITE_DOMAIN_MRO = 'mro.xiaoluwebsite.xyz'
+VITE_DOMAIN_FULI = 'fuli.xiaoluwebsite.xyz'
+VITE_DOMAIN_REG = 'reg.xiaoluwebsite.xyz'
+VITE_DOMAIN_BREG = 'breg.xiaoluwebsite.xyz'
+VITE_DOMAIN_GREG = 'greg.xiaoluwebsite.xyz'
+VITE_DOMAIN_PASSPORT = 'pass.xiaoluwebsite.xyz'
+VITE_DOMAIN_SEARCH = 'search.xiaoluwebsite.xyz'
+VITE_DOMAIN_ITEM = 'item.xiaoluwebsite.xyz'
+VITE_DOMAIN_CART = 'cart.xiaoluwebsite.xyz'
+VITE_DOMAIN_TRAD = 'trad.xiaoluwebsite.xyz'
+VITE_DOMAIN_PAYC = 'payc.xiaoluwebsite.xyz'
+VITE_DOMAIN_ORDER = 'order.xiaoluwebsite.xyz'
+VITE_DOMAIN_PLAN = 'plan.xiaoluwebsite.xyz'
+VITE_DOMAIN_PLAN_INFO = 'planinfo.xiaoluwebsite.xyz'
+VITE_DOMAIN_I = 'i.xiaoluwebsite.xyz'
+VITE_DOMAIN_EASYBUV = 'easybuv.xiaoluwebsite.xyz'

+ 10 - 0
src/api/goods/index.ts

@@ -37,6 +37,16 @@ export const deleteProductShoppingCart = (ids: any) => {
   });
 };
 
+//导出购物车的商品
+export const exportProductShoppingCart = (data?: any) => {
+  return request({
+    url: '/product/myProduct/exportProductShoppingCart',
+    method: 'post',
+    data: data,
+    responseType: 'blob'
+  });
+};
+
 //新增商品浏览记录
 
 export const addProductBrowsingHistory = (id: any) => {

+ 22 - 2
src/api/pc/enterprise/order.ts

@@ -1,5 +1,5 @@
 import request from '@/utils/request';
-import { OrderMain, OrderProduct, OrderStatusStats, OrderCustomerFlowSaveBo, OrderCustomerFlowLinkBo, OrderCustomerFlow } from './orderTypes';
+import { OrderMain, OrderProduct, OrderStatusStats, OrderCustomerFlowSaveBo, OrderCustomerFlowLinkBo, OrderCustomerFlow, OrderCustomerFlowNodeLink } from './orderTypes';
 
 // ==================== 订单管理 ====================
 
@@ -236,7 +236,7 @@ export function startOrderFlow(id: number | string) {
   });
 }
 
-/**
+/**`
  * 关闭订单流程
  */
 export function closeOrderFlow(id: number | string) {
@@ -255,3 +255,23 @@ export function getReturnReason(params?: any) {
     params: params
   });
 }
+
+/**
+ * 查询当前用户能审核的订单 id
+ */
+export function getCheckOrderIds() {
+  return request({
+    url: '/order/pcOrder/getCheckOrderIds',
+    method: 'get'
+  });
+}
+
+/**
+ * 查询当前订单的流程节点列表
+ */
+export function getOrderFlowNodes(orderId: number | string) {
+  return request<any, OrderCustomerFlowNodeLink[]>({
+    url: `/order/pcOrder/getOrderFlowNodes/${orderId}`,
+    method: 'get'
+  });
+}

+ 21 - 0
src/api/pc/enterprise/orderTypes.ts

@@ -128,3 +128,24 @@ export interface OrderCustomerFlow {
   flowNodes?: OrderCustomerFlowNode[];
   [key: string]: any;
 }
+
+/**
+ * 订单流程节点链接
+ * 用于查询订单流程节点列表
+ */
+export interface OrderCustomerFlowNodeLink {
+  id?: number | string;
+  orderId?: number;
+  nodeId?: number | string;
+  nodeName?: string;
+  nodeType?: string;
+  sort?: number;
+  handlerId?: string;
+  handlerName?: string;
+  reviewStatus?: number;
+  auditTime?: string;
+  auditOpinion?: string;
+  createTime?: string;
+  updateTime?: string;
+  [key: string]: any;
+}

+ 1 - 1
src/api/pc/organization/index.ts

@@ -91,7 +91,7 @@ export function getContactList(params?: any) {
 /**
  * 查询联系人详情
  */
-export function getContactInfo(id: number) {
+export function getContactInfo(id: number | string) {
   return request({
     url: `/customer/organization/contact/${id}`,
     method: 'get'

+ 8 - 8
src/permission.ts

@@ -37,14 +37,14 @@ const isWhiteList = (path: string) => {
   return whiteList.some((pattern) => isPathMatch(pattern, path));
 };
 
-function getMainSiteUrl(path: string) {
-  if (import.meta.env.PROD) {
-    return `https://index.xiaoluwebsite.xyz${path}`;
-  } else {
-    const devPort = window.location.port || import.meta.env.VITE_APP_PORT;
-    return `https://index.xiaoluwebsite.xyz:${devPort}${path}`;
-  }
-}
+// function getMainSiteUrl(path: string) {
+//   if (import.meta.env.PROD) {
+//     return `https://index.xiaoluwebsite.xyz${path}`;
+//   } else {
+//     const devPort = window.location.port || import.meta.env.VITE_APP_PORT;
+//     return `https://index.xiaoluwebsite.xyz:${devPort}${path}`;
+//   }
+// }
 
 // 根据 isDiy 字段决定首页路径
 function getDomainHomePath(site: string, station: ReturnType<typeof stationStore>) {

+ 5 - 5
src/utils/request.ts

@@ -9,18 +9,18 @@ import { LoadingInstance } from 'element-plus/es/components/loading/src/loading'
 import FileSaver from 'file-saver';
 import { encryptBase64, encryptWithAes, generateAesKey, decryptWithAes, decryptBase64 } from '@/utils/crypto';
 import { encrypt, decrypt } from '@/utils/jsencrypt';
-import { onPath } from '@/utils/siteConfig';
+import { onPath, DOMAIN_MAP } from '@/utils/siteConfig';
 import router from '@/router';
 
 // 获取主站完整 URL(用于跨域跳转)
 function getMainSiteUrl(path: string) {
+  const mainDomain = DOMAIN_MAP.www;
   if (import.meta.env.PROD) {
-    return `https://index.xiaoluwebsite.xyz${path}`;
+    return `https://${mainDomain}${path}`;
   } else {
-    // 本地开发:指向 index.xiaoluwebsite.xyz 加上当前运行的端口
-    // 假设你启动 vite 后访问的是 http://index.xiaoluwebsite.xyz
+    // 本地开发:指向主站域名加上当前运行的端口
     const devPort = window.location.port || import.meta.env.VITE_APP_PORT;
-    return `https://index.xiaoluwebsite.xyz:${devPort}${path}`;
+    return `https://${mainDomain}:${devPort}${path}`;
   }
 }
 

+ 39 - 43
src/utils/siteConfig.ts

@@ -1,3 +1,34 @@
+// 从环境变量获取域名配置
+export const DOMAIN_MAP: Record<string, string> = {
+  www: import.meta.env.VITE_DOMAIN_WWW || 'index.xiaoluwebsite.xyz',
+  b: import.meta.env.VITE_DOMAIN_B || 'b.xiaoluwebsite.xyz',
+  mro: import.meta.env.VITE_DOMAIN_MRO || 'mro.xiaoluwebsite.xyz',
+  fuli: import.meta.env.VITE_DOMAIN_FULI || 'fuli.xiaoluwebsite.xyz',
+  reg: import.meta.env.VITE_DOMAIN_REG || 'reg.xiaoluwebsite.xyz',
+  breg: import.meta.env.VITE_DOMAIN_BREG || 'breg.xiaoluwebsite.xyz',
+  greg: import.meta.env.VITE_DOMAIN_GREG || 'greg.xiaoluwebsite.xyz',
+  passport: import.meta.env.VITE_DOMAIN_PASSPORT || 'pass.xiaoluwebsite.xyz',
+  search: import.meta.env.VITE_DOMAIN_SEARCH || 'search.xiaoluwebsite.xyz',
+  item: import.meta.env.VITE_DOMAIN_ITEM || 'item.xiaoluwebsite.xyz',
+  cart: import.meta.env.VITE_DOMAIN_CART || 'cart.xiaoluwebsite.xyz',
+  trad: import.meta.env.VITE_DOMAIN_TRAD || 'trad.xiaoluwebsite.xyz',
+  payc: import.meta.env.VITE_DOMAIN_PAYC || 'payc.xiaoluwebsite.xyz',
+  order: import.meta.env.VITE_DOMAIN_ORDER || 'order.xiaoluwebsite.xyz',
+  plan: import.meta.env.VITE_DOMAIN_PLAN || 'plan.xiaoluwebsite.xyz',
+  plan_info: import.meta.env.VITE_DOMAIN_PLAN_INFO || 'planinfo.xiaoluwebsite.xyz',
+  i: import.meta.env.VITE_DOMAIN_I || 'i.xiaoluwebsite.xyz',
+  easybuv: import.meta.env.VITE_DOMAIN_EASYBUV || 'easybuv.xiaoluwebsite.xyz'
+};
+
+// 反向域名映射 (域名 -> 站点名)
+const REVERSE_DOMAIN_MAP: Record<string, string> = Object.entries(DOMAIN_MAP).reduce(
+  (acc, [site, domain]) => {
+    acc[domain] = site;
+    return acc;
+  },
+  {} as Record<string, string>
+);
+
 // 每个站点允许的路由 (保持不变)
 export const SITE_ROUTES: Record<any, string[]> = {
   www: ['/', '/index', '/indexData', '/theme', '/diy'], //优易365主站
@@ -69,26 +100,12 @@ export function getCurrentSite(): any {
   // 无论是生产还是开发,现在都统一通过域名判断
   const host = window.location.hostname;
 
-  // 定义本地开发环境的域名映射关系
-  // 确保你的 hosts 文件已经配置了这些域名指向 127.0.0.1
-  if (host === 'index.xiaoluwebsite.xyz' || host === 'localhost') return 'www'; // 兼容未配hosts的情况
-  if (host === 'b.xiaoluwebsite.xyz') return 'b';
-  if (host === 'mro.xiaoluwebsite.xyz') return 'mro';
-  if (host === 'fuli.xiaoluwebsite.xyz') return 'fuli';
-  if (host === 'reg.xiaoluwebsite.xyz') return 'reg';
-  if (host === 'breg.xiaoluwebsite.xyz') return 'breg';
-  if (host === 'greg.xiaoluwebsite.xyz') return 'greg';
-  if (host === 'pass.xiaoluwebsite.xyz') return 'passport';
-  if (host === 'search.xiaoluwebsite.xyz') return 'search';
-  if (host === 'item.xiaoluwebsite.xyz') return 'item';
-  if (host === 'cart.xiaoluwebsite.xyz') return 'cart';
-  if (host === 'trad.xiaoluwebsite.xyz') return 'trad';
-  if (host === 'payc.xiaoluwebsite.xyz') return 'payc';
-  if (host === 'order.xiaoluwebsite.xyz') return 'order';
-  if (host === 'plan.xiaoluwebsite.xyz') return 'plan';
-  if (host === 'planinfo.xiaoluwebsite.xyz') return 'plan_info';
-  if (host === 'i.xiaoluwebsite.xyz') return 'i';
-  if (host === 'easybuv.xiaoluwebsite.xyz') return 'easybuv';
+  // 使用环境变量配置的域名映射
+  if (host === 'localhost') return 'www'; // 兼容未配hosts的情况
+  
+  // 从反向映射中查找站点
+  const site = REVERSE_DOMAIN_MAP[host];
+  if (site) return site;
 
   // 生产环境逻辑 (保持不变,或者合并到上面的判断中)
   if (import.meta.env.PROD) {
@@ -131,29 +148,8 @@ export function onPath(path: string) {
     const isSameSite = currentSite === targetSite;
 
     let url = '';
-    // 域名映射表 (保持不变)
-    const domainMap: Record<string, string> = {
-      www: 'index.xiaoluwebsite.xyz',
-      b: 'b.xiaoluwebsite.xyz',
-      mro: 'mro.xiaoluwebsite.xyz',
-      fuli: 'fuli.xiaoluwebsite.xyz',
-      reg: 'reg.xiaoluwebsite.xyz',
-      breg: 'breg.xiaoluwebsite.xyz',
-      greg: 'greg.xiaoluwebsite.xyz',
-      passport: 'pass.xiaoluwebsite.xyz',
-      search: 'search.xiaoluwebsite.xyz',
-      item: 'item.xiaoluwebsite.xyz',
-      cart: 'cart.xiaoluwebsite.xyz',
-      trad: 'trad.xiaoluwebsite.xyz',
-      payc: 'payc.xiaoluwebsite.xyz',
-      order: 'order.xiaoluwebsite.xyz',
-      plan: 'plan.xiaoluwebsite.xyz',
-      plan_info: 'planinfo.xiaoluwebsite.xyz',
-      i: 'i.xiaoluwebsite.xyz',
-      easybuv: 'easybuv.xiaoluwebsite.xyz'
-    };
-
-    const baseDomain = domainMap[targetSite];
+    // 使用环境变量配置的域名映射
+    const baseDomain = DOMAIN_MAP[targetSite];
 
     if (import.meta.env.PROD) {
       url = `https://${baseDomain}${path}`;

+ 18 - 2
src/views/cart/index.vue

@@ -6,7 +6,7 @@
           <img src="@/assets/images/cart1.png" alt="" />
           <div>我的购物车</div>
         </div>
-        <div class="head2">导出订单</div>
+        <div class="head2" @click="onExport">导出订单</div>
       </div>
       <el-table
         ref="multipleTableRef"
@@ -171,8 +171,10 @@ import {
   isProductInDefaultCollect,
   cancelProductCollect,
   addProductCollect,
-  favoritesList
+  favoritesList,
+  exportProductShoppingCart
 } from '@/api/goods/index';
+import FileSaver from 'file-saver';
 import { onPath } from '@/utils/siteConfig';
 
 onMounted(() => {
@@ -288,6 +290,16 @@ const onCancel = () => {
     }
   });
 };
+
+// 导出订单
+const onExport = () => {
+  exportProductShoppingCart().then((res: any) => {
+    const blob = new Blob([res], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
+    FileSaver.saveAs(blob, '购物车商品.xlsx');
+  }).catch(() => {
+    ElMessage.error('导出失败,请稍后重试');
+  });
+};
 </script>
 
 <style lang="scss" scoped>
@@ -320,6 +332,10 @@ const onCancel = () => {
       color: #e7000b;
       line-height: 25px;
       text-align: center;
+      cursor: pointer;
+      &:hover {
+        background-color: #fff0f0;
+      }
     }
   }
   .cart-info {

+ 3 - 2
src/views/enterprise/purchasePlan/index.vue

@@ -6,7 +6,7 @@
     <!-- 方案列表 -->
     <div v-loading="loading" class="plan-grid">
       <div v-for="(item, index) in planList" :key="index" class="plan-card">
-        <div class="plan-image">
+        <div class="plan-image" @click="onPath(`/plan_info?id=${item.id}`)">
           <el-image :src="item.image" fit="cover">
             <template #error
               ><div class="image-placeholder">
@@ -17,7 +17,7 @@
         <div class="plan-info">
           <div class="plan-name">{{ item.name }}</div>
           <div class="plan-desc">{{ item.description }}</div>
-          <div class="plan-link" @click="handleDetail(item)">
+          <div class="plan-link" @click="onPath(`/plan_info?id=${item.id}`)">
             了解详情 <el-icon><ArrowRight /></el-icon>
           </div>
         </div>
@@ -41,6 +41,7 @@ import { Picture, ArrowRight } from '@element-plus/icons-vue';
 import { ElMessage } from 'element-plus';
 import { PageTitle, StatusTabs, TablePagination } from '@/components';
 import { getProcurementProgramProductList } from '@/api/goods/index';
+import { onPath } from '@/utils/siteConfig';
 
 // 采购方案类型映射
 const typeMap: Record<string, string> = {

+ 16 - 3
src/views/order/orderAudit/index.vue

@@ -109,7 +109,7 @@
               <el-button v-if="order.fileCount" type="primary" link size="small"> 审核文件({{ order.fileCount }}) </el-button>
             </div>
             <div class="product-cell action-cell">
-              <template v-if="activeMainTab === 'myAudit' && order.checkStatus === '0'">
+              <template v-if="activeMainTab === 'myAudit' && order.checkStatus === '0' && checkableOrderIds.includes(order.id)">
                 <el-button type="success" link size="small" @click="handleApprove(order)">同意</el-button>
                 <el-button type="danger" link size="small" @click="handleReject(order)">拒绝</el-button>
               </template>
@@ -159,7 +159,7 @@ import { ElMessage, ElMessageBox } from 'element-plus';
 import { PageTitle, StatusTabs } from '@/components';
 import { getDeptTree } from '@/api/pc/organization';
 import { DeptInfo } from '@/api/pc/organization/types';
-import { getOrderList, getOrderProducts, checkOrderStatus } from '@/api/pc/enterprise/order';
+import { getOrderList, getOrderProducts, checkOrderStatus, getCheckOrderIds } from '@/api/pc/enterprise/order';
 const { proxy } = getCurrentInstance() as ComponentInternalInstance;
 const { complaints_suggestion_type } = toRefs<any>(proxy?.useDict('complaints_suggestion_type'));
 
@@ -174,6 +174,7 @@ const currentAuditOrder = ref<any>(null);
 const currentAuditAction = ref('');
 const loading = ref(false);
 const allOrders = ref<any[]>([]);
+const checkableOrderIds = ref<string[]>([]);
 const pageNum = ref(1);
 const pageSize = ref(5);
 const total = ref(0);
@@ -375,8 +376,19 @@ const handleStatusTabChange = () => {
 onMounted(() => {
   loadDeptTree();
   loadOrderList();
+  loadCheckOrderIds();
 });
 
+// 加载当前用户可审核的订单 ID 列表
+const loadCheckOrderIds = async () => {
+  try {
+    const res = await getCheckOrderIds();
+    checkableOrderIds.value = Array.isArray(res.data) ? res.data : [];
+  } catch (error) {
+    console.error('获取可审核订单ID失败:', error);
+  }
+};
+
 const getStatusText = (checkStatus: string) => {
   const map: Record<string, string> = {
     '0': '待审批',
@@ -438,7 +450,8 @@ const handleSubmitAudit = async () => {
 
     ElMessage.success(currentAuditAction.value === 'approve' ? '审批通过' : '已拒绝');
     auditDialogVisible.value = false;
-    loadOrderList();
+    await loadOrderList();
+    await loadCheckOrderIds();
   } catch (error) {
     console.error('审批失败:', error);
     ElMessage.error('审批失败,请重试');

+ 109 - 9
src/views/order/orderManage/detail.vue

@@ -21,7 +21,7 @@
             <div class="step-info">
               <div class="step-title">{{ step.title }}</div>
               <div class="step-desc">{{ step.desc }}</div>
-              <div class="step-time">{{ step.time }}</div>
+              <div v-if="index <= currentStep" class="step-time">{{ step.time }}</div>
             </div>
             <div v-if="index < progressSteps.length - 1" class="step-line" :class="{ active: index < currentStep }"></div>
           </div>
@@ -126,24 +126,119 @@
 <script setup lang="ts">
 import { ref, reactive, onMounted } from 'vue';
 import { useRouter, useRoute } from 'vue-router';
-import { ArrowLeft, Document, Search, CircleCheck, Picture } from '@element-plus/icons-vue';
-import { getOrderInfo, getOrderProducts } from '@/api/pc/enterprise/order';
+import { ArrowLeft, Document, User, CircleCheck, Picture } from '@element-plus/icons-vue';
+import { getOrderInfo, getOrderProducts, getOrderFlowNodes } from '@/api/pc/enterprise/order';
 import { getAddressInfo } from '@/api/pc/enterprise/address';
 import { getInvoiceList } from '@/api/pc/enterprise/invoice';
+import { getContactInfo } from '@/api/pc/organization/index';
 import { ElMessage } from 'element-plus';
+import type { OrderCustomerFlowNodeLink } from '@/api/pc/enterprise/orderTypes';
 
 const router = useRouter();
 const route = useRoute();
+
+// 格式化时间为 "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 orderId = ref<any>(0);
 const currentStep = ref(1);
 const loading = ref(false);
 
-const progressSteps = ref([
-  { title: '提交审核', icon: Document, desc: '采购一部提交订单审核', time: '2025/02/10 15:51:21' },
-  { title: '审核中', icon: Search, desc: '采购一部提交订单审批', time: '2025/02/10 15:51:21' },
-  { title: '审核完成', icon: CircleCheck, desc: '交易完成', time: '2025/02/10 15:51:21' }
+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 loadFlowNodes = async () => {
+  try {
+    const res = await getOrderFlowNodes(orderId.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);
+  }
+};
+
 const productList = ref<any[]>([]);
 
 const orderInfo = reactive({
@@ -182,6 +277,9 @@ const loadOrderDetail = async () => {
       orderInfo.purchaseReason = order.purchaseReason || '';
       orderInfo.remark = order.remark || '';
 
+      // 保存订单时间信息(用于流程节点时间显示)
+      orderTimeInfo.createTime = order.createTime || '';
+      orderTimeInfo.updateTime = order.updateTime || '';
       // 获取商品信息
       const productsRes = await getOrderProducts([orderId.value]);
       if (productsRes.code === 200 && productsRes.rows) {
@@ -227,12 +325,14 @@ const loadOrderDetail = async () => {
   }
 };
 
-onMounted(() => {
+onMounted(async () => {
   const paramId = route.query.orderId;
   // 直接使用字符串,不转换为数字,避免精度丢失
   orderId.value = paramId as string;
   if (orderId.value) {
-    loadOrderDetail();
+    // 先加载订单详情(获取 createTime/updateTime),再加载流程节点
+    await loadOrderDetail();
+    loadFlowNodes();
   } else {
     console.error('订单ID无效,无法加载订单详情');
   }

+ 13 - 8
src/views/organization/approvalFlow/create.vue

@@ -19,7 +19,7 @@
 
     <div class="flow-form">
       <div class="form-item">
-        <label>流程名称</label>
+        <label><span class="required-star">*</span>流程名称</label>
         <el-input v-model="flowName" placeholder="请输入" style="width: 100%" />
       </div>
     </div>
@@ -31,7 +31,7 @@
         <div class="node-header">流程发起</div>
         <div class="node-content">
           <span>发起人:所有人</span>
-          <el-icon><ArrowRight /></el-icon>
+
         </div>
       </div>
 
@@ -47,9 +47,8 @@
       <!-- 审批人节点 -->
       <div v-for="(node, index) in approvalNodes" :key="index" class="flow-node approval-node">
         <div class="node-header">
-          <span>审批人</span>
+          <span>审批人(或签)</span>
           <div class="node-actions">
-            <el-icon @click="handleEditNode(index)"><Edit /></el-icon>
             <el-icon @click="handleDeleteNode(index)"><Delete /></el-icon>
           </div>
         </div>
@@ -80,7 +79,7 @@
     <!-- 操作按钮 -->
     <div class="footer-actions">
       <el-button @click="handleCancel">取消</el-button>
-      <el-button type="primary" :loading="submitLoading" @click="handleSave" :disabled="!flowName.trim() || approvalNodes.length === 0">
+      <el-button type="primary" :loading="submitLoading" @click="handleSave">
         {{ isEdit ? '更新流程' : '创建流程' }}
       </el-button>
     </div>
@@ -160,8 +159,10 @@ const currentEditIndex = ref<number>(-1);
 
 const filteredContactList = computed(() => {
   const key = contactSearchKey.value.trim().toLowerCase();
-  if (!key) return contactList.value;
-  return contactList.value.filter((c) => {
+  // 只展示启用状态(status === '0')的员工
+  const enabledList = contactList.value.filter((c) => c.status === '0');
+  if (!key) return enabledList;
+  return enabledList.filter((c) => {
     const name = (c.contactName || c.name || '').toLowerCase();
     const phone = (c.phone || c.mobile || '').toLowerCase();
     return name.includes(key) || phone.includes(key);
@@ -495,6 +496,10 @@ onUnmounted(() => {
       font-size: 14px;
       color: #333;
       margin-bottom: 8px;
+      .required-star {
+        color: #f56c6c;
+        margin-right: 4px;
+      }
     }
     :deep(.el-input__wrapper) {
       background: #f5f5f5;
@@ -538,7 +543,7 @@ onUnmounted(() => {
 
   &.approval-node {
     .node-header {
-      background: #409eff;
+      background: #ff6b6b;
       color: #fff;
       padding: 8px 15px;
       font-size: 13px;

+ 5 - 4
src/views/organization/approvalFlow/index.vue

@@ -19,7 +19,7 @@
         <template #default="{ row }">
           <el-button type="primary" link size="small" @click="handleEdit(row)">编辑</el-button>
           <el-button :type="row.status === '1' ? 'warning' : 'success'" link size="small" @click="handleToggleStatus(row)">{{
-            row.status === '1' ? '关闭' : '开启'
+            row.status === '1' ? '开启' : '关闭'
           }}</el-button>
           <el-button type="danger" link size="small" @click="handleDisable(row)">删除</el-button>
         </template>
@@ -67,7 +67,8 @@ const handleEdit = (row: OrderCustomerFlow) => {
 };
 
 const handleToggleStatus = (row: OrderCustomerFlow) => {
-  const isActive = row.status === '1';
+
+  const isActive = row.status === '0';
   const actionText = isActive ? '关闭' : '开启';
   ElMessageBox.confirm(`确定要${actionText}"${row.flowName}"吗?`, '提示', {
     confirmButtonText: '确定',
@@ -77,9 +78,9 @@ const handleToggleStatus = (row: OrderCustomerFlow) => {
     .then(async () => {
       try {
         if (isActive) {
-          await closeOrderFlow(row.id as number);
+          await closeOrderFlow(row.id);
         } else {
-          await startOrderFlow(row.id as number);
+          await startOrderFlow(row.id);
         }
         ElMessage.success(`${actionText}成功`);
         loadFlowList();

+ 18 - 18
vite.config.ts

@@ -21,24 +21,24 @@ export default defineConfig(({ mode, command }) => {
     server: {
       host: '0.0.0.0',
       allowedHosts: [
-        'index.xiaoluwebsite.xyz',
-        'b.xiaoluwebsite.xyz',
-        'mro.xiaoluwebsite.xyz',
-        'fuli.xiaoluwebsite.xyz',
-        'reg.xiaoluwebsite.xyz',
-        'breg.xiaoluwebsite.xyz',
-        'greg.xiaoluwebsite.xyz',
-        'pass.xiaoluwebsite.xyz',
-        'search.xiaoluwebsite.xyz',
-        'item.xiaoluwebsite.xyz',
-        'cart.xiaoluwebsite.xyz',
-        'trad.xiaoluwebsite.xyz',
-        'payc.xiaoluwebsite.xyz',
-        'order.xiaoluwebsite.xyz',
-        'plan.xiaoluwebsite.xyz',
-        'planinfo.xiaoluwebsite.xyz',
-        'i.xiaoluwebsite.xyz',
-        'easybuv.xiaoluwebsite.xyz'
+        env.VITE_DOMAIN_WWW,
+        env.VITE_DOMAIN_B,
+        env.VITE_DOMAIN_MRO,
+        env.VITE_DOMAIN_FULI,
+        env.VITE_DOMAIN_REG,
+        env.VITE_DOMAIN_BREG,
+        env.VITE_DOMAIN_GREG,
+        env.VITE_DOMAIN_PASSPORT,
+        env.VITE_DOMAIN_SEARCH,
+        env.VITE_DOMAIN_ITEM,
+        env.VITE_DOMAIN_CART,
+        env.VITE_DOMAIN_TRAD,
+        env.VITE_DOMAIN_PAYC,
+        env.VITE_DOMAIN_ORDER,
+        env.VITE_DOMAIN_PLAN,
+        env.VITE_DOMAIN_PLAN_INFO,
+        env.VITE_DOMAIN_I,
+        env.VITE_DOMAIN_EASYBUV
       ],
       port: Number(env.VITE_APP_PORT),
       // port: Number(env.VITE_APP_PORT),